From 592a8fa9dcc431ab1af5d585c9e123d828a760f0 Mon Sep 17 00:00:00 2001 From: Vidar Langseid Date: Tue, 11 Aug 2026 08:25:04 +0200 Subject: [PATCH 1/2] IBX-12241: Added Behat coverage for Varnish TTL, stale cache, cache keys and tagging --- features/setup/setup.feature | 10 +++ features/varnish/cacheKey.feature | 17 +++++ features/varnish/tags.feature | 17 +++++ features/varnish/ttl.feature | 107 ++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 features/varnish/cacheKey.feature create mode 100644 features/varnish/tags.feature create mode 100644 features/varnish/ttl.feature diff --git a/features/setup/setup.feature b/features/setup/setup.feature index ae1685c..c790835 100644 --- a/features/setup/setup.feature +++ b/features/setup/setup.feature @@ -55,3 +55,13 @@ 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 | 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 | From 108fbfdc209f464fecf9099f65c4776f32f2c4f5 Mon Sep 17 00:00:00 2001 From: Vidar Langseid Date: Tue, 11 Aug 2026 10:12:50 +0200 Subject: [PATCH 2/2] fixup! IBX-12241: Added Behat coverage for Varnish TTL, stale cache, cache keys and tagging --- behat_suites.yml | 1 + features/setup/setup.feature | 5 +++++ 2 files changed, 6 insertions(+) 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 c790835..e914de2 100644 --- a/features/setup/setup.feature +++ b/features/setup/setup.feature @@ -65,3 +65,8 @@ Feature: Set system to desired state before tests 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 |