4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * This code is partially based on the Rack-Cache library by Ryan Tomayko,
9 * which is released under the MIT license.
10 * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801)
12 * For the full copyright and license information, please view the LICENSE
13 * file that was distributed with this source code.
16 namespace Symfony\Component\HttpKernel\Tests\HttpCache;
18 use PHPUnit\Framework\TestCase;
19 use Symfony\Component\HttpFoundation\Response;
20 use Symfony\Component\HttpKernel\HttpCache\ResponseCacheStrategy;
22 class ResponseCacheStrategyTest extends TestCase
24 public function testMinimumSharedMaxAgeWins()
26 $cacheStrategy = new ResponseCacheStrategy();
28 $response1 = new Response();
29 $response1->setSharedMaxAge(60);
30 $cacheStrategy->add($response1);
32 $response2 = new Response();
33 $response2->setSharedMaxAge(3600);
34 $cacheStrategy->add($response2);
36 $response = new Response();
37 $response->setSharedMaxAge(86400);
38 $cacheStrategy->update($response);
40 $this->assertSame('60', $response->headers->getCacheControlDirective('s-maxage'));
43 public function testSharedMaxAgeNotSetIfNotSetInAnyEmbeddedRequest()
45 $cacheStrategy = new ResponseCacheStrategy();
47 $response1 = new Response();
48 $response1->setSharedMaxAge(60);
49 $cacheStrategy->add($response1);
51 $response2 = new Response();
52 $cacheStrategy->add($response2);
54 $response = new Response();
55 $response->setSharedMaxAge(86400);
56 $cacheStrategy->update($response);
58 $this->assertFalse($response->headers->hasCacheControlDirective('s-maxage'));
61 public function testSharedMaxAgeNotSetIfNotSetInMasterRequest()
63 $cacheStrategy = new ResponseCacheStrategy();
65 $response1 = new Response();
66 $response1->setSharedMaxAge(60);
67 $cacheStrategy->add($response1);
69 $response2 = new Response();
70 $response2->setSharedMaxAge(3600);
71 $cacheStrategy->add($response2);
73 $response = new Response();
74 $cacheStrategy->update($response);
76 $this->assertFalse($response->headers->hasCacheControlDirective('s-maxage'));
79 public function testMasterResponseNotCacheableWhenEmbeddedResponseRequiresValidation()
81 $cacheStrategy = new ResponseCacheStrategy();
83 $embeddedResponse = new Response();
84 $embeddedResponse->setLastModified(new \DateTime());
85 $cacheStrategy->add($embeddedResponse);
87 $masterResponse = new Response();
88 $masterResponse->setSharedMaxAge(3600);
89 $cacheStrategy->update($masterResponse);
91 $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache'));
92 $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate'));
93 $this->assertFalse($masterResponse->isFresh());
96 public function testValidationOnMasterResponseIsNotPossibleWhenItContainsEmbeddedResponses()
98 $cacheStrategy = new ResponseCacheStrategy();
100 // This master response uses the "validation" model
101 $masterResponse = new Response();
102 $masterResponse->setLastModified(new \DateTime());
103 $masterResponse->setEtag('foo');
105 // Embedded response uses "expiry" model
106 $embeddedResponse = new Response();
107 $masterResponse->setSharedMaxAge(3600);
108 $cacheStrategy->add($embeddedResponse);
110 $cacheStrategy->update($masterResponse);
112 $this->assertFalse($masterResponse->isValidateable());
113 $this->assertFalse($masterResponse->headers->has('Last-Modified'));
114 $this->assertFalse($masterResponse->headers->has('ETag'));
115 $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache'));
116 $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate'));
119 public function testMasterResponseWithValidationIsUnchangedWhenThereIsNoEmbeddedResponse()
121 $cacheStrategy = new ResponseCacheStrategy();
123 $masterResponse = new Response();
124 $masterResponse->setLastModified(new \DateTime());
125 $cacheStrategy->update($masterResponse);
127 $this->assertTrue($masterResponse->isValidateable());
130 public function testMasterResponseWithExpirationIsUnchangedWhenThereIsNoEmbeddedResponse()
132 $cacheStrategy = new ResponseCacheStrategy();
134 $masterResponse = new Response();
135 $masterResponse->setSharedMaxAge(3600);
136 $cacheStrategy->update($masterResponse);
138 $this->assertTrue($masterResponse->isFresh());
141 public function testMasterResponseIsNotCacheableWhenEmbeddedResponseIsNotCacheable()
143 $cacheStrategy = new ResponseCacheStrategy();
145 $masterResponse = new Response();
146 $masterResponse->setSharedMaxAge(3600); // Public, cacheable
148 /* This response has no validation or expiration information.
149 That makes it uncacheable, it is always stale.
150 (It does *not* make this private, though.) */
151 $embeddedResponse = new Response();
152 $this->assertFalse($embeddedResponse->isFresh()); // not fresh, as no lifetime is provided
154 $cacheStrategy->add($embeddedResponse);
155 $cacheStrategy->update($masterResponse);
157 $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache'));
158 $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate'));
159 $this->assertFalse($masterResponse->isFresh());
162 public function testEmbeddingPrivateResponseMakesMainResponsePrivate()
164 $cacheStrategy = new ResponseCacheStrategy();
166 $masterResponse = new Response();
167 $masterResponse->setSharedMaxAge(3600); // public, cacheable
169 // The embedded response might for example contain per-user data that remains valid for 60 seconds
170 $embeddedResponse = new Response();
171 $embeddedResponse->setPrivate();
172 $embeddedResponse->setMaxAge(60); // this would implicitly set "private" as well, but let's be explicit
174 $cacheStrategy->add($embeddedResponse);
175 $cacheStrategy->update($masterResponse);
177 $this->assertTrue($masterResponse->headers->hasCacheControlDirective('private'));
178 $this->assertFalse($masterResponse->headers->hasCacheControlDirective('public'));
181 public function testEmbeddingPublicResponseDoesNotMakeMainResponsePublic()
183 $cacheStrategy = new ResponseCacheStrategy();
185 $masterResponse = new Response();
186 $masterResponse->setPrivate(); // this is the default, but let's be explicit
187 $masterResponse->setMaxAge(100);
189 $embeddedResponse = new Response();
190 $embeddedResponse->setPublic();
191 $embeddedResponse->setSharedMaxAge(100);
193 $cacheStrategy->add($embeddedResponse);
194 $cacheStrategy->update($masterResponse);
196 $this->assertTrue($masterResponse->headers->hasCacheControlDirective('private'));
197 $this->assertFalse($masterResponse->headers->hasCacheControlDirective('public'));
200 public function testResponseIsExiprableWhenEmbeddedResponseCombinesExpiryAndValidation()
202 /* When "expiration wins over validation" (https://symfony.com/doc/current/http_cache/validation.html)
203 * and both the main and embedded response provide s-maxage, then the more restricting value of both
204 * should be fine, regardless of whether the embedded response can be validated later on or must be
205 * completely regenerated.
207 $cacheStrategy = new ResponseCacheStrategy();
209 $masterResponse = new Response();
210 $masterResponse->setSharedMaxAge(3600);
212 $embeddedResponse = new Response();
213 $embeddedResponse->setSharedMaxAge(60);
214 $embeddedResponse->setEtag('foo');
216 $cacheStrategy->add($embeddedResponse);
217 $cacheStrategy->update($masterResponse);
219 $this->assertSame('60', $masterResponse->headers->getCacheControlDirective('s-maxage'));
222 public function testResponseIsExpirableButNotValidateableWhenMasterResponseCombinesExpirationAndValidation()
224 $cacheStrategy = new ResponseCacheStrategy();
226 $masterResponse = new Response();
227 $masterResponse->setSharedMaxAge(3600);
228 $masterResponse->setEtag('foo');
229 $masterResponse->setLastModified(new \DateTime());
231 $embeddedResponse = new Response();
232 $embeddedResponse->setSharedMaxAge(60);
234 $cacheStrategy->add($embeddedResponse);
235 $cacheStrategy->update($masterResponse);
237 $this->assertSame('60', $masterResponse->headers->getCacheControlDirective('s-maxage'));
238 $this->assertFalse($masterResponse->isValidateable());