diff --git a/behat_suites.yml b/behat_suites.yml index f662dc1..c6a2d01 100644 --- a/behat_suites.yml +++ b/behat_suites.yml @@ -82,6 +82,7 @@ httpCache: - Ibexa\Behat\Core\Context\ConfigurationContext - Ibexa\Behat\API\Context\ContentContext - Ibexa\Behat\API\Context\LanguageContext + - Ibexa\Behat\API\Context\RoleContext setup-token: paths: - '%paths.base%/vendor/ibexa/http-cache/features/setup/invalidateToken.feature' diff --git a/features/setup/setup.feature b/features/setup/setup.feature index ae1685c..e914de2 100644 --- a/features/setup/setup.feature +++ b/features/setup/setup.feature @@ -55,3 +55,18 @@ Feature: Set system to desired state before tests match: Identifier\ContentType: [embeddedContentType] """ + + Scenario: Set up siteaccesses with distinct HTTP cache TTLs + # Dedicated siteaccesses so that TTL assertions do not depend on the default TTL, + # and so that shortening the TTL does not affect the timing of any other suite. + Given I add a siteaccess "cache_ttl_long" to "site_group" with settings + | key | value | + | content.default_ttl | 60 | + And I add a siteaccess "cache_ttl_short" to "site_group" with settings + | key | value | + | content.default_ttl | 5 | + # The Anonymous role's existing user/login policy is limited by SiteAccess, so without this + # the siteaccesses above are unreachable anonymously + And I add policies to "Anonymous" + | module | function | + | user | login | diff --git a/features/varnish/cacheKey.feature b/features/varnish/cacheKey.feature new file mode 100644 index 0000000..bc28e85 --- /dev/null +++ b/features/varnish/cacheKey.feature @@ -0,0 +1,17 @@ +Feature: As a site administrator I want equivalent requests to share one cache object + + @varnish6 @varnish7 + Scenario: Query string parameter order does not create a second cache object + # vcl_recv normalises the cache key with std.querysort, so the two requests below + # differ only in the order the parameters were written and must hit the same object + Given I create "Folder" Content items in root in "eng-GB" + | name | short_name | + | TestFolder | QsTestItem | + When I am on "/site/QsTestItem?b=2&a=1" + Then response headers contain + | Header | Value | + | x-cache | MISS | + When I am on "/site/QsTestItem?a=1&b=2" + Then response headers contain + | Header | Value | + | x-cache | HIT | diff --git a/features/varnish/tags.feature b/features/varnish/tags.feature new file mode 100644 index 0000000..75e4ca2 --- /dev/null +++ b/features/varnish/tags.feature @@ -0,0 +1,17 @@ +Feature: As a site administrator I want responses to carry the cache tags Varnish invalidates on + + @varnish6 @varnish7 + Scenario: A Content view response is tagged with the content and location it was built from + # Tags are what every purge in this bundle keys on, but they are otherwise only tested + # indirectly - through whether a purge happened to work. fos_http_cache is configured + # with tag_mode: purgekeys, so TagHandler emits them space separated in an xkey header, + # always seeded with ez-all. The header only reaches clients inside the debuggers ACL. + Given I create "Folder" Content items in root in "eng-GB" + | name | short_name | + | TestFolder | TagTestItem | + When I visit "TagTestItem" on siteaccess "site" + Then response headers match pattern + | Header | Pattern | + | xkey | /(^\| )ez-all( \|$)/ | + | xkey | /(^\| )c\d+( \|$)/ | + | xkey | /(^\| )l\d+( \|$)/ | diff --git a/features/varnish/ttl.feature b/features/varnish/ttl.feature new file mode 100644 index 0000000..5b6bf60 --- /dev/null +++ b/features/varnish/ttl.feature @@ -0,0 +1,107 @@ +Feature: As a site administrator I want Varnish to apply the configured TTL and to serve stale content while it refreshes + + # The siteaccesses used here are created by the setup suite and only differ in + # ibexa.system..content.default_ttl: cache_ttl_long is 60s, cache_ttl_short is 5s. + # Keeping them separate means the timing of the other suites is untouched and the assertions + # do not depend on whatever the default TTL happens to be. + + @varnish6 @varnish7 + Scenario: The TTL Varnish stores an object with comes from content.default_ttl + Given I create "Folder" Content items in root in "eng-GB" + | name | short_name | + | TestFolder | TtlTestItem | + When I visit "TtlTestItem" on siteaccess "cache_ttl_long" + And I reload the page + Then response headers contain + | Header | Value | + | x-cache | HIT | + And response headers match pattern + | Header | Pattern | + | x-cache-ttl | /^(5[0-9]\|60)\.\d+$/ | + When I visit "TtlTestItem" on siteaccess "cache_ttl_short" + And I reload the page + Then response headers contain + | Header | Value | + | x-cache | HIT | + And response headers match pattern + | Header | Pattern | + | x-cache-ttl | /^[0-4]\.\d+$/ | + + @varnish6 @varnish7 + Scenario: Responses for anonymous users may be cached by shared proxies for a short while + Given I create "Folder" Content items in root in "eng-GB" + | name | short_name | + | TestFolder | AnonCacheItem | + When I visit "AnonCacheItem" on siteaccess "cache_ttl_long" + Then response headers contain + | Header | Value | + | cache-control | public, s-maxage=600, stale-while-revalidate=300, stale-if-error=300 | + # The user context hash is an internal detail and must never be advertised to clients + And response headers match pattern + | Header | Pattern | + | vary | /^(?!.*X-User-Context-Hash).*$/i | + + @varnish6 @varnish7 + Scenario: Responses for logged-in users are not cacheable by shared proxies or browsers + Given I create "Folder" Content items in root in "eng-GB" + | name | short_name | + | TestFolder | AdminCacheItem | + And I am viewing the pages on siteaccess "cache_ttl_long" as "admin" with password "publish" + When I visit "AdminCacheItem" on siteaccess "cache_ttl_long" + Then response headers contain + | Header | Value | + | cache-control | private, no-cache, no-store, must-revalidate | + + @varnish6 @varnish7 + Scenario: An expired object is served from grace and refreshed in the background + Given I create "Folder" Content items in root in "eng-GB" + | name | short_name | + | TestFolder | StaleTestItem | + And I visit "StaleTestItem" on siteaccess "cache_ttl_short" + And I reload the page + And response headers contain + | Header | Value | + | x-cache | HIT | + When I wait 7 seconds + And I reload the page + # A negative obj.ttl proves the object is past its TTL and delivered from the grace window + Then response headers contain + | Header | Value | + | x-cache | HIT | + And response headers match pattern + | Header | Pattern | + | x-cache-ttl | /^-\d/ | + # The stale hit triggered a background fetch; the first request to the object it + # inserted reports MISS, the one after that is a hit on a fresh object again + When I wait 2 seconds + And I reload the page + And I reload the page + Then response headers contain + | Header | Value | + | x-cache | HIT | + And response headers match pattern + | Header | Pattern | + | x-cache-ttl | /^[0-4]\.\d+$/ | + + @varnish6 + Scenario: A logged-in user in the grace window gets a refreshed response, not a stale one + # varnish6.vcl vcl_hit: when the request carries a session cookie and the object is in + # grace, it does return (miss) so that editors are never shown stale content. + # varnish7.vcl has no vcl_hit at all and cannot be given the same one, because + # return (miss) was removed from that subroutine in Varnish 7 - so Varnish 7 and later + # deliver the stale object here instead. Tracked as a separate ticket; once varnish7.vcl + # gains the equivalent (return (pass)), this scenario can be tagged @varnish7 as well. + Given I create "Folder" Content items in root in "eng-GB" + | name | short_name | + | TestFolder | StaleAdminTestItem | + And I am viewing the pages on siteaccess "cache_ttl_short" as "admin" with password "publish" + And I visit "StaleAdminTestItem" on siteaccess "cache_ttl_short" + And I reload the page + And response headers contain + | Header | Value | + | x-cache | HIT | + When I wait 7 seconds + And I reload the page + Then response headers contain + | Header | Value | + | x-cache | MISS |