db backup prior to drupal security update
[yaffs-website] / vendor / symfony / http-kernel / Tests / HttpCache / HttpCacheTest.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\HttpKernel\Tests\HttpCache;
13
14 use Symfony\Component\HttpKernel\HttpCache\HttpCache;
15 use Symfony\Component\HttpFoundation\Request;
16 use Symfony\Component\HttpFoundation\Response;
17 use Symfony\Component\HttpKernel\HttpKernelInterface;
18
19 /**
20  * @group time-sensitive
21  */
22 class HttpCacheTest extends HttpCacheTestCase
23 {
24     public function testTerminateDelegatesTerminationOnlyForTerminableInterface()
25     {
26         $storeMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface')
27             ->disableOriginalConstructor()
28             ->getMock();
29
30         // does not implement TerminableInterface
31         $kernel = new TestKernel();
32         $httpCache = new HttpCache($kernel, $storeMock);
33         $httpCache->terminate(Request::create('/'), new Response());
34
35         $this->assertFalse($kernel->terminateCalled, 'terminate() is never called if the kernel class does not implement TerminableInterface');
36
37         // implements TerminableInterface
38         $kernelMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Kernel')
39             ->disableOriginalConstructor()
40             ->setMethods(array('terminate', 'registerBundles', 'registerContainerConfiguration'))
41             ->getMock();
42
43         $kernelMock->expects($this->once())
44             ->method('terminate');
45
46         $kernel = new HttpCache($kernelMock, $storeMock);
47         $kernel->terminate(Request::create('/'), new Response());
48     }
49
50     public function testPassesOnNonGetHeadRequests()
51     {
52         $this->setNextResponse(200);
53         $this->request('POST', '/');
54         $this->assertHttpKernelIsCalled();
55         $this->assertResponseOk();
56         $this->assertTraceContains('pass');
57         $this->assertFalse($this->response->headers->has('Age'));
58     }
59
60     public function testInvalidatesOnPostPutDeleteRequests()
61     {
62         foreach (array('post', 'put', 'delete') as $method) {
63             $this->setNextResponse(200);
64             $this->request($method, '/');
65
66             $this->assertHttpKernelIsCalled();
67             $this->assertResponseOk();
68             $this->assertTraceContains('invalidate');
69             $this->assertTraceContains('pass');
70         }
71     }
72
73     public function testDoesNotCacheWithAuthorizationRequestHeaderAndNonPublicResponse()
74     {
75         $this->setNextResponse(200, array('ETag' => '"Foo"'));
76         $this->request('GET', '/', array('HTTP_AUTHORIZATION' => 'basic foobarbaz'));
77
78         $this->assertHttpKernelIsCalled();
79         $this->assertResponseOk();
80         $this->assertEquals('private', $this->response->headers->get('Cache-Control'));
81
82         $this->assertTraceContains('miss');
83         $this->assertTraceNotContains('store');
84         $this->assertFalse($this->response->headers->has('Age'));
85     }
86
87     public function testDoesCacheWithAuthorizationRequestHeaderAndPublicResponse()
88     {
89         $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '"Foo"'));
90         $this->request('GET', '/', array('HTTP_AUTHORIZATION' => 'basic foobarbaz'));
91
92         $this->assertHttpKernelIsCalled();
93         $this->assertResponseOk();
94         $this->assertTraceContains('miss');
95         $this->assertTraceContains('store');
96         $this->assertTrue($this->response->headers->has('Age'));
97         $this->assertEquals('public', $this->response->headers->get('Cache-Control'));
98     }
99
100     public function testDoesNotCacheWithCookieHeaderAndNonPublicResponse()
101     {
102         $this->setNextResponse(200, array('ETag' => '"Foo"'));
103         $this->request('GET', '/', array(), array('foo' => 'bar'));
104
105         $this->assertHttpKernelIsCalled();
106         $this->assertResponseOk();
107         $this->assertEquals('private', $this->response->headers->get('Cache-Control'));
108         $this->assertTraceContains('miss');
109         $this->assertTraceNotContains('store');
110         $this->assertFalse($this->response->headers->has('Age'));
111     }
112
113     public function testDoesNotCacheRequestsWithACookieHeader()
114     {
115         $this->setNextResponse(200);
116         $this->request('GET', '/', array(), array('foo' => 'bar'));
117
118         $this->assertHttpKernelIsCalled();
119         $this->assertResponseOk();
120         $this->assertEquals('private', $this->response->headers->get('Cache-Control'));
121         $this->assertTraceContains('miss');
122         $this->assertTraceNotContains('store');
123         $this->assertFalse($this->response->headers->has('Age'));
124     }
125
126     public function testRespondsWith304WhenIfModifiedSinceMatchesLastModified()
127     {
128         $time = \DateTime::createFromFormat('U', time());
129
130         $this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822), 'Content-Type' => 'text/plain'), 'Hello World');
131         $this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822)));
132
133         $this->assertHttpKernelIsCalled();
134         $this->assertEquals(304, $this->response->getStatusCode());
135         $this->assertEquals('', $this->response->headers->get('Content-Type'));
136         $this->assertEmpty($this->response->getContent());
137         $this->assertTraceContains('miss');
138         $this->assertTraceContains('store');
139     }
140
141     public function testRespondsWith304WhenIfNoneMatchMatchesETag()
142     {
143         $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '12345', 'Content-Type' => 'text/plain'), 'Hello World');
144         $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345'));
145
146         $this->assertHttpKernelIsCalled();
147         $this->assertEquals(304, $this->response->getStatusCode());
148         $this->assertEquals('', $this->response->headers->get('Content-Type'));
149         $this->assertTrue($this->response->headers->has('ETag'));
150         $this->assertEmpty($this->response->getContent());
151         $this->assertTraceContains('miss');
152         $this->assertTraceContains('store');
153     }
154
155     public function testRespondsWith304OnlyIfIfNoneMatchAndIfModifiedSinceBothMatch()
156     {
157         $time = \DateTime::createFromFormat('U', time());
158
159         $this->setNextResponse(200, array(), '', function ($request, $response) use ($time) {
160             $response->setStatusCode(200);
161             $response->headers->set('ETag', '12345');
162             $response->headers->set('Last-Modified', $time->format(DATE_RFC2822));
163             $response->headers->set('Content-Type', 'text/plain');
164             $response->setContent('Hello World');
165         });
166
167         // only ETag matches
168         $t = \DateTime::createFromFormat('U', time() - 3600);
169         $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => $t->format(DATE_RFC2822)));
170         $this->assertHttpKernelIsCalled();
171         $this->assertEquals(200, $this->response->getStatusCode());
172
173         // only Last-Modified matches
174         $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '1234', 'HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822)));
175         $this->assertHttpKernelIsCalled();
176         $this->assertEquals(200, $this->response->getStatusCode());
177
178         // Both matches
179         $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822)));
180         $this->assertHttpKernelIsCalled();
181         $this->assertEquals(304, $this->response->getStatusCode());
182     }
183
184     public function testIncrementsMaxAgeWhenNoDateIsSpecifiedEventWhenUsingETag()
185     {
186         $this->setNextResponse(
187             200,
188             array(
189                 'ETag' => '1234',
190                 'Cache-Control' => 'public, s-maxage=60',
191             )
192         );
193
194         $this->request('GET', '/');
195         $this->assertHttpKernelIsCalled();
196         $this->assertEquals(200, $this->response->getStatusCode());
197         $this->assertTraceContains('miss');
198         $this->assertTraceContains('store');
199
200         sleep(2);
201
202         $this->request('GET', '/');
203         $this->assertHttpKernelIsNotCalled();
204         $this->assertEquals(200, $this->response->getStatusCode());
205         $this->assertTraceContains('fresh');
206         $this->assertEquals(2, $this->response->headers->get('Age'));
207     }
208
209     public function testValidatesPrivateResponsesCachedOnTheClient()
210     {
211         $this->setNextResponse(200, array(), '', function ($request, $response) {
212             $etags = preg_split('/\s*,\s*/', $request->headers->get('IF_NONE_MATCH'));
213             if ($request->cookies->has('authenticated')) {
214                 $response->headers->set('Cache-Control', 'private, no-store');
215                 $response->setETag('"private tag"');
216                 if (in_array('"private tag"', $etags)) {
217                     $response->setStatusCode(304);
218                 } else {
219                     $response->setStatusCode(200);
220                     $response->headers->set('Content-Type', 'text/plain');
221                     $response->setContent('private data');
222                 }
223             } else {
224                 $response->headers->set('Cache-Control', 'public');
225                 $response->setETag('"public tag"');
226                 if (in_array('"public tag"', $etags)) {
227                     $response->setStatusCode(304);
228                 } else {
229                     $response->setStatusCode(200);
230                     $response->headers->set('Content-Type', 'text/plain');
231                     $response->setContent('public data');
232                 }
233             }
234         });
235
236         $this->request('GET', '/');
237         $this->assertHttpKernelIsCalled();
238         $this->assertEquals(200, $this->response->getStatusCode());
239         $this->assertEquals('"public tag"', $this->response->headers->get('ETag'));
240         $this->assertEquals('public data', $this->response->getContent());
241         $this->assertTraceContains('miss');
242         $this->assertTraceContains('store');
243
244         $this->request('GET', '/', array(), array('authenticated' => ''));
245         $this->assertHttpKernelIsCalled();
246         $this->assertEquals(200, $this->response->getStatusCode());
247         $this->assertEquals('"private tag"', $this->response->headers->get('ETag'));
248         $this->assertEquals('private data', $this->response->getContent());
249         $this->assertTraceContains('stale');
250         $this->assertTraceContains('invalid');
251         $this->assertTraceNotContains('store');
252     }
253
254     public function testStoresResponsesWhenNoCacheRequestDirectivePresent()
255     {
256         $time = \DateTime::createFromFormat('U', time() + 5);
257
258         $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822)));
259         $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache'));
260
261         $this->assertHttpKernelIsCalled();
262         $this->assertTraceContains('store');
263         $this->assertTrue($this->response->headers->has('Age'));
264     }
265
266     public function testReloadsResponsesWhenCacheHitsButNoCacheRequestDirectivePresentWhenAllowReloadIsSetTrue()
267     {
268         $count = 0;
269
270         $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count) {
271             ++$count;
272             $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World');
273         });
274
275         $this->request('GET', '/');
276         $this->assertEquals(200, $this->response->getStatusCode());
277         $this->assertEquals('Hello World', $this->response->getContent());
278         $this->assertTraceContains('store');
279
280         $this->request('GET', '/');
281         $this->assertEquals(200, $this->response->getStatusCode());
282         $this->assertEquals('Hello World', $this->response->getContent());
283         $this->assertTraceContains('fresh');
284
285         $this->cacheConfig['allow_reload'] = true;
286         $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache'));
287         $this->assertEquals(200, $this->response->getStatusCode());
288         $this->assertEquals('Goodbye World', $this->response->getContent());
289         $this->assertTraceContains('reload');
290         $this->assertTraceContains('store');
291     }
292
293     public function testDoesNotReloadResponsesWhenAllowReloadIsSetFalseDefault()
294     {
295         $count = 0;
296
297         $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count) {
298             ++$count;
299             $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World');
300         });
301
302         $this->request('GET', '/');
303         $this->assertEquals(200, $this->response->getStatusCode());
304         $this->assertEquals('Hello World', $this->response->getContent());
305         $this->assertTraceContains('store');
306
307         $this->request('GET', '/');
308         $this->assertEquals(200, $this->response->getStatusCode());
309         $this->assertEquals('Hello World', $this->response->getContent());
310         $this->assertTraceContains('fresh');
311
312         $this->cacheConfig['allow_reload'] = false;
313         $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache'));
314         $this->assertEquals(200, $this->response->getStatusCode());
315         $this->assertEquals('Hello World', $this->response->getContent());
316         $this->assertTraceNotContains('reload');
317
318         $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache'));
319         $this->assertEquals(200, $this->response->getStatusCode());
320         $this->assertEquals('Hello World', $this->response->getContent());
321         $this->assertTraceNotContains('reload');
322     }
323
324     public function testRevalidatesFreshCacheEntryWhenMaxAgeRequestDirectiveIsExceededWhenAllowRevalidateOptionIsSetTrue()
325     {
326         $count = 0;
327
328         $this->setNextResponse(200, array(), '', function ($request, $response) use (&$count) {
329             ++$count;
330             $response->headers->set('Cache-Control', 'public, max-age=10000');
331             $response->setETag($count);
332             $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World');
333         });
334
335         $this->request('GET', '/');
336         $this->assertEquals(200, $this->response->getStatusCode());
337         $this->assertEquals('Hello World', $this->response->getContent());
338         $this->assertTraceContains('store');
339
340         $this->request('GET', '/');
341         $this->assertEquals(200, $this->response->getStatusCode());
342         $this->assertEquals('Hello World', $this->response->getContent());
343         $this->assertTraceContains('fresh');
344
345         $this->cacheConfig['allow_revalidate'] = true;
346         $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0'));
347         $this->assertEquals(200, $this->response->getStatusCode());
348         $this->assertEquals('Goodbye World', $this->response->getContent());
349         $this->assertTraceContains('stale');
350         $this->assertTraceContains('invalid');
351         $this->assertTraceContains('store');
352     }
353
354     public function testDoesNotRevalidateFreshCacheEntryWhenEnableRevalidateOptionIsSetFalseDefault()
355     {
356         $count = 0;
357
358         $this->setNextResponse(200, array(), '', function ($request, $response) use (&$count) {
359             ++$count;
360             $response->headers->set('Cache-Control', 'public, max-age=10000');
361             $response->setETag($count);
362             $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World');
363         });
364
365         $this->request('GET', '/');
366         $this->assertEquals(200, $this->response->getStatusCode());
367         $this->assertEquals('Hello World', $this->response->getContent());
368         $this->assertTraceContains('store');
369
370         $this->request('GET', '/');
371         $this->assertEquals(200, $this->response->getStatusCode());
372         $this->assertEquals('Hello World', $this->response->getContent());
373         $this->assertTraceContains('fresh');
374
375         $this->cacheConfig['allow_revalidate'] = false;
376         $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0'));
377         $this->assertEquals(200, $this->response->getStatusCode());
378         $this->assertEquals('Hello World', $this->response->getContent());
379         $this->assertTraceNotContains('stale');
380         $this->assertTraceNotContains('invalid');
381         $this->assertTraceContains('fresh');
382
383         $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0'));
384         $this->assertEquals(200, $this->response->getStatusCode());
385         $this->assertEquals('Hello World', $this->response->getContent());
386         $this->assertTraceNotContains('stale');
387         $this->assertTraceNotContains('invalid');
388         $this->assertTraceContains('fresh');
389     }
390
391     public function testFetchesResponseFromBackendWhenCacheMisses()
392     {
393         $time = \DateTime::createFromFormat('U', time() + 5);
394         $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822)));
395
396         $this->request('GET', '/');
397         $this->assertEquals(200, $this->response->getStatusCode());
398         $this->assertTraceContains('miss');
399         $this->assertTrue($this->response->headers->has('Age'));
400     }
401
402     public function testDoesNotCacheSomeStatusCodeResponses()
403     {
404         foreach (array_merge(range(201, 202), range(204, 206), range(303, 305), range(400, 403), range(405, 409), range(411, 417), range(500, 505)) as $code) {
405             $time = \DateTime::createFromFormat('U', time() + 5);
406             $this->setNextResponse($code, array('Expires' => $time->format(DATE_RFC2822)));
407
408             $this->request('GET', '/');
409             $this->assertEquals($code, $this->response->getStatusCode());
410             $this->assertTraceNotContains('store');
411             $this->assertFalse($this->response->headers->has('Age'));
412         }
413     }
414
415     public function testDoesNotCacheResponsesWithExplicitNoStoreDirective()
416     {
417         $time = \DateTime::createFromFormat('U', time() + 5);
418         $this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'no-store'));
419
420         $this->request('GET', '/');
421         $this->assertTraceNotContains('store');
422         $this->assertFalse($this->response->headers->has('Age'));
423     }
424
425     public function testDoesNotCacheResponsesWithoutFreshnessInformationOrAValidator()
426     {
427         $this->setNextResponse();
428
429         $this->request('GET', '/');
430         $this->assertEquals(200, $this->response->getStatusCode());
431         $this->assertTraceNotContains('store');
432     }
433
434     public function testCachesResponsesWithExplicitNoCacheDirective()
435     {
436         $time = \DateTime::createFromFormat('U', time() + 5);
437         $this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, no-cache'));
438
439         $this->request('GET', '/');
440         $this->assertTraceContains('store');
441         $this->assertTrue($this->response->headers->has('Age'));
442     }
443
444     public function testCachesResponsesWithAnExpirationHeader()
445     {
446         $time = \DateTime::createFromFormat('U', time() + 5);
447         $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822)));
448
449         $this->request('GET', '/');
450         $this->assertEquals(200, $this->response->getStatusCode());
451         $this->assertEquals('Hello World', $this->response->getContent());
452         $this->assertNotNull($this->response->headers->get('Date'));
453         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
454         $this->assertTraceContains('miss');
455         $this->assertTraceContains('store');
456
457         $values = $this->getMetaStorageValues();
458         $this->assertCount(1, $values);
459     }
460
461     public function testCachesResponsesWithAMaxAgeDirective()
462     {
463         $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=5'));
464
465         $this->request('GET', '/');
466         $this->assertEquals(200, $this->response->getStatusCode());
467         $this->assertEquals('Hello World', $this->response->getContent());
468         $this->assertNotNull($this->response->headers->get('Date'));
469         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
470         $this->assertTraceContains('miss');
471         $this->assertTraceContains('store');
472
473         $values = $this->getMetaStorageValues();
474         $this->assertCount(1, $values);
475     }
476
477     public function testCachesResponsesWithASMaxAgeDirective()
478     {
479         $this->setNextResponse(200, array('Cache-Control' => 's-maxage=5'));
480
481         $this->request('GET', '/');
482         $this->assertEquals(200, $this->response->getStatusCode());
483         $this->assertEquals('Hello World', $this->response->getContent());
484         $this->assertNotNull($this->response->headers->get('Date'));
485         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
486         $this->assertTraceContains('miss');
487         $this->assertTraceContains('store');
488
489         $values = $this->getMetaStorageValues();
490         $this->assertCount(1, $values);
491     }
492
493     public function testCachesResponsesWithALastModifiedValidatorButNoFreshnessInformation()
494     {
495         $time = \DateTime::createFromFormat('U', time());
496         $this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822)));
497
498         $this->request('GET', '/');
499         $this->assertEquals(200, $this->response->getStatusCode());
500         $this->assertEquals('Hello World', $this->response->getContent());
501         $this->assertTraceContains('miss');
502         $this->assertTraceContains('store');
503     }
504
505     public function testCachesResponsesWithAnETagValidatorButNoFreshnessInformation()
506     {
507         $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '"123456"'));
508
509         $this->request('GET', '/');
510         $this->assertEquals(200, $this->response->getStatusCode());
511         $this->assertEquals('Hello World', $this->response->getContent());
512         $this->assertTraceContains('miss');
513         $this->assertTraceContains('store');
514     }
515
516     public function testHitsCachedResponsesWithExpiresHeader()
517     {
518         $time1 = \DateTime::createFromFormat('U', time() - 5);
519         $time2 = \DateTime::createFromFormat('U', time() + 5);
520         $this->setNextResponse(200, array('Cache-Control' => 'public', 'Date' => $time1->format(DATE_RFC2822), 'Expires' => $time2->format(DATE_RFC2822)));
521
522         $this->request('GET', '/');
523         $this->assertHttpKernelIsCalled();
524         $this->assertEquals(200, $this->response->getStatusCode());
525         $this->assertNotNull($this->response->headers->get('Date'));
526         $this->assertTraceContains('miss');
527         $this->assertTraceContains('store');
528         $this->assertEquals('Hello World', $this->response->getContent());
529
530         $this->request('GET', '/');
531         $this->assertHttpKernelIsNotCalled();
532         $this->assertEquals(200, $this->response->getStatusCode());
533         $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2);
534         $this->assertTrue($this->response->headers->get('Age') > 0);
535         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
536         $this->assertTraceContains('fresh');
537         $this->assertTraceNotContains('store');
538         $this->assertEquals('Hello World', $this->response->getContent());
539     }
540
541     public function testHitsCachedResponseWithMaxAgeDirective()
542     {
543         $time = \DateTime::createFromFormat('U', time() - 5);
544         $this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, max-age=10'));
545
546         $this->request('GET', '/');
547         $this->assertHttpKernelIsCalled();
548         $this->assertEquals(200, $this->response->getStatusCode());
549         $this->assertNotNull($this->response->headers->get('Date'));
550         $this->assertTraceContains('miss');
551         $this->assertTraceContains('store');
552         $this->assertEquals('Hello World', $this->response->getContent());
553
554         $this->request('GET', '/');
555         $this->assertHttpKernelIsNotCalled();
556         $this->assertEquals(200, $this->response->getStatusCode());
557         $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2);
558         $this->assertTrue($this->response->headers->get('Age') > 0);
559         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
560         $this->assertTraceContains('fresh');
561         $this->assertTraceNotContains('store');
562         $this->assertEquals('Hello World', $this->response->getContent());
563     }
564
565     public function testHitsCachedResponseWithSMaxAgeDirective()
566     {
567         $time = \DateTime::createFromFormat('U', time() - 5);
568         $this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 's-maxage=10, max-age=0'));
569
570         $this->request('GET', '/');
571         $this->assertHttpKernelIsCalled();
572         $this->assertEquals(200, $this->response->getStatusCode());
573         $this->assertNotNull($this->response->headers->get('Date'));
574         $this->assertTraceContains('miss');
575         $this->assertTraceContains('store');
576         $this->assertEquals('Hello World', $this->response->getContent());
577
578         $this->request('GET', '/');
579         $this->assertHttpKernelIsNotCalled();
580         $this->assertEquals(200, $this->response->getStatusCode());
581         $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2);
582         $this->assertTrue($this->response->headers->get('Age') > 0);
583         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
584         $this->assertTraceContains('fresh');
585         $this->assertTraceNotContains('store');
586         $this->assertEquals('Hello World', $this->response->getContent());
587     }
588
589     public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformation()
590     {
591         $this->setNextResponse();
592
593         $this->cacheConfig['default_ttl'] = 10;
594         $this->request('GET', '/');
595         $this->assertHttpKernelIsCalled();
596         $this->assertTraceContains('miss');
597         $this->assertTraceContains('store');
598         $this->assertEquals('Hello World', $this->response->getContent());
599         $this->assertRegExp('/s-maxage=10/', $this->response->headers->get('Cache-Control'));
600
601         $this->cacheConfig['default_ttl'] = 10;
602         $this->request('GET', '/');
603         $this->assertHttpKernelIsNotCalled();
604         $this->assertEquals(200, $this->response->getStatusCode());
605         $this->assertTraceContains('fresh');
606         $this->assertTraceNotContains('store');
607         $this->assertEquals('Hello World', $this->response->getContent());
608         $this->assertRegExp('/s-maxage=10/', $this->response->headers->get('Cache-Control'));
609     }
610
611     public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformationAndAfterTtlWasExpired()
612     {
613         $this->setNextResponse();
614
615         $this->cacheConfig['default_ttl'] = 2;
616         $this->request('GET', '/');
617         $this->assertHttpKernelIsCalled();
618         $this->assertTraceContains('miss');
619         $this->assertTraceContains('store');
620         $this->assertEquals('Hello World', $this->response->getContent());
621         $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control'));
622
623         $this->request('GET', '/');
624         $this->assertHttpKernelIsNotCalled();
625         $this->assertEquals(200, $this->response->getStatusCode());
626         $this->assertTraceContains('fresh');
627         $this->assertTraceNotContains('store');
628         $this->assertEquals('Hello World', $this->response->getContent());
629         $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control'));
630
631         // expires the cache
632         $values = $this->getMetaStorageValues();
633         $this->assertCount(1, $values);
634         $tmp = unserialize($values[0]);
635         $time = \DateTime::createFromFormat('U', time() - 5);
636         $tmp[0][1]['date'] = $time->format(DATE_RFC2822);
637         $r = new \ReflectionObject($this->store);
638         $m = $r->getMethod('save');
639         $m->setAccessible(true);
640         $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp));
641
642         $this->request('GET', '/');
643         $this->assertHttpKernelIsCalled();
644         $this->assertEquals(200, $this->response->getStatusCode());
645         $this->assertTraceContains('stale');
646         $this->assertTraceContains('invalid');
647         $this->assertTraceContains('store');
648         $this->assertEquals('Hello World', $this->response->getContent());
649         $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control'));
650
651         $this->setNextResponse();
652
653         $this->request('GET', '/');
654         $this->assertHttpKernelIsNotCalled();
655         $this->assertEquals(200, $this->response->getStatusCode());
656         $this->assertTraceContains('fresh');
657         $this->assertTraceNotContains('store');
658         $this->assertEquals('Hello World', $this->response->getContent());
659         $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control'));
660     }
661
662     public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformationAndAfterTtlWasExpiredWithStatus304()
663     {
664         $this->setNextResponse();
665
666         $this->cacheConfig['default_ttl'] = 2;
667         $this->request('GET', '/');
668         $this->assertHttpKernelIsCalled();
669         $this->assertTraceContains('miss');
670         $this->assertTraceContains('store');
671         $this->assertEquals('Hello World', $this->response->getContent());
672         $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control'));
673
674         $this->request('GET', '/');
675         $this->assertHttpKernelIsNotCalled();
676         $this->assertEquals(200, $this->response->getStatusCode());
677         $this->assertTraceContains('fresh');
678         $this->assertTraceNotContains('store');
679         $this->assertEquals('Hello World', $this->response->getContent());
680
681         // expires the cache
682         $values = $this->getMetaStorageValues();
683         $this->assertCount(1, $values);
684         $tmp = unserialize($values[0]);
685         $time = \DateTime::createFromFormat('U', time() - 5);
686         $tmp[0][1]['date'] = $time->format(DATE_RFC2822);
687         $r = new \ReflectionObject($this->store);
688         $m = $r->getMethod('save');
689         $m->setAccessible(true);
690         $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp));
691
692         $this->request('GET', '/');
693         $this->assertHttpKernelIsCalled();
694         $this->assertEquals(200, $this->response->getStatusCode());
695         $this->assertTraceContains('stale');
696         $this->assertTraceContains('valid');
697         $this->assertTraceContains('store');
698         $this->assertTraceNotContains('miss');
699         $this->assertEquals('Hello World', $this->response->getContent());
700         $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control'));
701
702         $this->request('GET', '/');
703         $this->assertHttpKernelIsNotCalled();
704         $this->assertEquals(200, $this->response->getStatusCode());
705         $this->assertTraceContains('fresh');
706         $this->assertTraceNotContains('store');
707         $this->assertEquals('Hello World', $this->response->getContent());
708         $this->assertRegExp('/s-maxage=2/', $this->response->headers->get('Cache-Control'));
709     }
710
711     public function testDoesNotAssignDefaultTtlWhenResponseHasMustRevalidateDirective()
712     {
713         $this->setNextResponse(200, array('Cache-Control' => 'must-revalidate'));
714
715         $this->cacheConfig['default_ttl'] = 10;
716         $this->request('GET', '/');
717         $this->assertHttpKernelIsCalled();
718         $this->assertEquals(200, $this->response->getStatusCode());
719         $this->assertTraceContains('miss');
720         $this->assertTraceNotContains('store');
721         $this->assertNotRegExp('/s-maxage/', $this->response->headers->get('Cache-Control'));
722         $this->assertEquals('Hello World', $this->response->getContent());
723     }
724
725     public function testFetchesFullResponseWhenCacheStaleAndNoValidatorsPresent()
726     {
727         $time = \DateTime::createFromFormat('U', time() + 5);
728         $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822)));
729
730         // build initial request
731         $this->request('GET', '/');
732         $this->assertHttpKernelIsCalled();
733         $this->assertEquals(200, $this->response->getStatusCode());
734         $this->assertNotNull($this->response->headers->get('Date'));
735         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
736         $this->assertNotNull($this->response->headers->get('Age'));
737         $this->assertTraceContains('miss');
738         $this->assertTraceContains('store');
739         $this->assertEquals('Hello World', $this->response->getContent());
740
741         // go in and play around with the cached metadata directly ...
742         $values = $this->getMetaStorageValues();
743         $this->assertCount(1, $values);
744         $tmp = unserialize($values[0]);
745         $time = \DateTime::createFromFormat('U', time());
746         $tmp[0][1]['expires'] = $time->format(DATE_RFC2822);
747         $r = new \ReflectionObject($this->store);
748         $m = $r->getMethod('save');
749         $m->setAccessible(true);
750         $m->invoke($this->store, 'md'.hash('sha256', 'http://localhost/'), serialize($tmp));
751
752         // build subsequent request; should be found but miss due to freshness
753         $this->request('GET', '/');
754         $this->assertHttpKernelIsCalled();
755         $this->assertEquals(200, $this->response->getStatusCode());
756         $this->assertTrue($this->response->headers->get('Age') <= 1);
757         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
758         $this->assertTraceContains('stale');
759         $this->assertTraceNotContains('fresh');
760         $this->assertTraceNotContains('miss');
761         $this->assertTraceContains('store');
762         $this->assertEquals('Hello World', $this->response->getContent());
763     }
764
765     public function testValidatesCachedResponsesWithLastModifiedAndNoFreshnessInformation()
766     {
767         $time = \DateTime::createFromFormat('U', time());
768         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time) {
769             $response->headers->set('Cache-Control', 'public');
770             $response->headers->set('Last-Modified', $time->format(DATE_RFC2822));
771             if ($time->format(DATE_RFC2822) == $request->headers->get('IF_MODIFIED_SINCE')) {
772                 $response->setStatusCode(304);
773                 $response->setContent('');
774             }
775         });
776
777         // build initial request
778         $this->request('GET', '/');
779         $this->assertHttpKernelIsCalled();
780         $this->assertEquals(200, $this->response->getStatusCode());
781         $this->assertNotNull($this->response->headers->get('Last-Modified'));
782         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
783         $this->assertEquals('Hello World', $this->response->getContent());
784         $this->assertTraceContains('miss');
785         $this->assertTraceContains('store');
786         $this->assertTraceNotContains('stale');
787
788         // build subsequent request; should be found but miss due to freshness
789         $this->request('GET', '/');
790         $this->assertHttpKernelIsCalled();
791         $this->assertEquals(200, $this->response->getStatusCode());
792         $this->assertNotNull($this->response->headers->get('Last-Modified'));
793         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
794         $this->assertTrue($this->response->headers->get('Age') <= 1);
795         $this->assertEquals('Hello World', $this->response->getContent());
796         $this->assertTraceContains('stale');
797         $this->assertTraceContains('valid');
798         $this->assertTraceContains('store');
799         $this->assertTraceNotContains('miss');
800     }
801
802     public function testValidatesCachedResponsesUseSameHttpMethod()
803     {
804         $test = $this;
805
806         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($test) {
807             $test->assertSame('OPTIONS', $request->getMethod());
808         });
809
810         // build initial request
811         $this->request('OPTIONS', '/');
812
813         // build subsequent request
814         $this->request('OPTIONS', '/');
815     }
816
817     public function testValidatesCachedResponsesWithETagAndNoFreshnessInformation()
818     {
819         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) {
820             $response->headers->set('Cache-Control', 'public');
821             $response->headers->set('ETag', '"12345"');
822             if ($response->getETag() == $request->headers->get('IF_NONE_MATCH')) {
823                 $response->setStatusCode(304);
824                 $response->setContent('');
825             }
826         });
827
828         // build initial request
829         $this->request('GET', '/');
830         $this->assertHttpKernelIsCalled();
831         $this->assertEquals(200, $this->response->getStatusCode());
832         $this->assertNotNull($this->response->headers->get('ETag'));
833         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
834         $this->assertEquals('Hello World', $this->response->getContent());
835         $this->assertTraceContains('miss');
836         $this->assertTraceContains('store');
837
838         // build subsequent request; should be found but miss due to freshness
839         $this->request('GET', '/');
840         $this->assertHttpKernelIsCalled();
841         $this->assertEquals(200, $this->response->getStatusCode());
842         $this->assertNotNull($this->response->headers->get('ETag'));
843         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
844         $this->assertTrue($this->response->headers->get('Age') <= 1);
845         $this->assertEquals('Hello World', $this->response->getContent());
846         $this->assertTraceContains('stale');
847         $this->assertTraceContains('valid');
848         $this->assertTraceContains('store');
849         $this->assertTraceNotContains('miss');
850     }
851
852     public function testServesResponseWhileFreshAndRevalidatesWithLastModifiedInformation()
853     {
854         $time = \DateTime::createFromFormat('U', time());
855
856         $this->setNextResponse(200, array(), 'Hello World', function (Request $request, Response $response) use ($time) {
857             $response->setSharedMaxAge(10);
858             $response->headers->set('Last-Modified', $time->format(DATE_RFC2822));
859         });
860
861         // prime the cache
862         $this->request('GET', '/');
863
864         // next request before s-maxage has expired: Serve from cache
865         // without hitting the backend
866         $this->request('GET', '/');
867         $this->assertHttpKernelIsNotCalled();
868         $this->assertEquals(200, $this->response->getStatusCode());
869         $this->assertEquals('Hello World', $this->response->getContent());
870         $this->assertTraceContains('fresh');
871
872         sleep(15); // expire the cache
873
874         $that = $this;
875
876         $this->setNextResponse(304, array(), '', function (Request $request, Response $response) use ($time, $that) {
877             $that->assertEquals($time->format(DATE_RFC2822), $request->headers->get('IF_MODIFIED_SINCE'));
878         });
879
880         $this->request('GET', '/');
881         $this->assertHttpKernelIsCalled();
882         $this->assertEquals(200, $this->response->getStatusCode());
883         $this->assertEquals('Hello World', $this->response->getContent());
884         $this->assertTraceContains('stale');
885         $this->assertTraceContains('valid');
886     }
887
888     public function testReplacesCachedResponsesWhenValidationResultsInNon304Response()
889     {
890         $time = \DateTime::createFromFormat('U', time());
891         $count = 0;
892         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time, &$count) {
893             $response->headers->set('Last-Modified', $time->format(DATE_RFC2822));
894             $response->headers->set('Cache-Control', 'public');
895             switch (++$count) {
896                 case 1:
897                     $response->setContent('first response');
898                     break;
899                 case 2:
900                     $response->setContent('second response');
901                     break;
902                 case 3:
903                     $response->setContent('');
904                     $response->setStatusCode(304);
905                     break;
906             }
907         });
908
909         // first request should fetch from backend and store in cache
910         $this->request('GET', '/');
911         $this->assertEquals(200, $this->response->getStatusCode());
912         $this->assertEquals('first response', $this->response->getContent());
913
914         // second request is validated, is invalid, and replaces cached entry
915         $this->request('GET', '/');
916         $this->assertEquals(200, $this->response->getStatusCode());
917         $this->assertEquals('second response', $this->response->getContent());
918
919         // third response is validated, valid, and returns cached entry
920         $this->request('GET', '/');
921         $this->assertEquals(200, $this->response->getStatusCode());
922         $this->assertEquals('second response', $this->response->getContent());
923
924         $this->assertEquals(3, $count);
925     }
926
927     public function testPassesHeadRequestsThroughDirectlyOnPass()
928     {
929         $that = $this;
930         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($that) {
931             $response->setContent('');
932             $response->setStatusCode(200);
933             $that->assertEquals('HEAD', $request->getMethod());
934         });
935
936         $this->request('HEAD', '/', array('HTTP_EXPECT' => 'something ...'));
937         $this->assertHttpKernelIsCalled();
938         $this->assertEquals('', $this->response->getContent());
939     }
940
941     public function testUsesCacheToRespondToHeadRequestsWhenFresh()
942     {
943         $that = $this;
944         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($that) {
945             $response->headers->set('Cache-Control', 'public, max-age=10');
946             $response->setContent('Hello World');
947             $response->setStatusCode(200);
948             $that->assertNotEquals('HEAD', $request->getMethod());
949         });
950
951         $this->request('GET', '/');
952         $this->assertHttpKernelIsCalled();
953         $this->assertEquals('Hello World', $this->response->getContent());
954
955         $this->request('HEAD', '/');
956         $this->assertHttpKernelIsNotCalled();
957         $this->assertEquals(200, $this->response->getStatusCode());
958         $this->assertEquals('', $this->response->getContent());
959         $this->assertEquals(strlen('Hello World'), $this->response->headers->get('Content-Length'));
960     }
961
962     public function testSendsNoContentWhenFresh()
963     {
964         $time = \DateTime::createFromFormat('U', time());
965         $that = $this;
966         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($that, $time) {
967             $response->headers->set('Cache-Control', 'public, max-age=10');
968             $response->headers->set('Last-Modified', $time->format(DATE_RFC2822));
969         });
970
971         $this->request('GET', '/');
972         $this->assertHttpKernelIsCalled();
973         $this->assertEquals('Hello World', $this->response->getContent());
974
975         $this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822)));
976         $this->assertHttpKernelIsNotCalled();
977         $this->assertEquals(304, $this->response->getStatusCode());
978         $this->assertEquals('', $this->response->getContent());
979     }
980
981     public function testInvalidatesCachedResponsesOnPost()
982     {
983         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) {
984             if ('GET' == $request->getMethod()) {
985                 $response->setStatusCode(200);
986                 $response->headers->set('Cache-Control', 'public, max-age=500');
987                 $response->setContent('Hello World');
988             } elseif ('POST' == $request->getMethod()) {
989                 $response->setStatusCode(303);
990                 $response->headers->set('Location', '/');
991                 $response->headers->remove('Cache-Control');
992                 $response->setContent('');
993             }
994         });
995
996         // build initial request to enter into the cache
997         $this->request('GET', '/');
998         $this->assertHttpKernelIsCalled();
999         $this->assertEquals(200, $this->response->getStatusCode());
1000         $this->assertEquals('Hello World', $this->response->getContent());
1001         $this->assertTraceContains('miss');
1002         $this->assertTraceContains('store');
1003
1004         // make sure it is valid
1005         $this->request('GET', '/');
1006         $this->assertHttpKernelIsNotCalled();
1007         $this->assertEquals(200, $this->response->getStatusCode());
1008         $this->assertEquals('Hello World', $this->response->getContent());
1009         $this->assertTraceContains('fresh');
1010
1011         // now POST to same URL
1012         $this->request('POST', '/helloworld');
1013         $this->assertHttpKernelIsCalled();
1014         $this->assertEquals('/', $this->response->headers->get('Location'));
1015         $this->assertTraceContains('invalidate');
1016         $this->assertTraceContains('pass');
1017         $this->assertEquals('', $this->response->getContent());
1018
1019         // now make sure it was actually invalidated
1020         $this->request('GET', '/');
1021         $this->assertHttpKernelIsCalled();
1022         $this->assertEquals(200, $this->response->getStatusCode());
1023         $this->assertEquals('Hello World', $this->response->getContent());
1024         $this->assertTraceContains('stale');
1025         $this->assertTraceContains('invalid');
1026         $this->assertTraceContains('store');
1027     }
1028
1029     public function testServesFromCacheWhenHeadersMatch()
1030     {
1031         $count = 0;
1032         $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) {
1033             $response->headers->set('Vary', 'Accept User-Agent Foo');
1034             $response->headers->set('Cache-Control', 'public, max-age=10');
1035             $response->headers->set('X-Response-Count', ++$count);
1036             $response->setContent($request->headers->get('USER_AGENT'));
1037         });
1038
1039         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0'));
1040         $this->assertEquals(200, $this->response->getStatusCode());
1041         $this->assertEquals('Bob/1.0', $this->response->getContent());
1042         $this->assertTraceContains('miss');
1043         $this->assertTraceContains('store');
1044
1045         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0'));
1046         $this->assertEquals(200, $this->response->getStatusCode());
1047         $this->assertEquals('Bob/1.0', $this->response->getContent());
1048         $this->assertTraceContains('fresh');
1049         $this->assertTraceNotContains('store');
1050         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
1051     }
1052
1053     public function testStoresMultipleResponsesWhenHeadersDiffer()
1054     {
1055         $count = 0;
1056         $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) {
1057             $response->headers->set('Vary', 'Accept User-Agent Foo');
1058             $response->headers->set('Cache-Control', 'public, max-age=10');
1059             $response->headers->set('X-Response-Count', ++$count);
1060             $response->setContent($request->headers->get('USER_AGENT'));
1061         });
1062
1063         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0'));
1064         $this->assertEquals(200, $this->response->getStatusCode());
1065         $this->assertEquals('Bob/1.0', $this->response->getContent());
1066         $this->assertEquals(1, $this->response->headers->get('X-Response-Count'));
1067
1068         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0'));
1069         $this->assertEquals(200, $this->response->getStatusCode());
1070         $this->assertTraceContains('miss');
1071         $this->assertTraceContains('store');
1072         $this->assertEquals('Bob/2.0', $this->response->getContent());
1073         $this->assertEquals(2, $this->response->headers->get('X-Response-Count'));
1074
1075         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0'));
1076         $this->assertTraceContains('fresh');
1077         $this->assertEquals('Bob/1.0', $this->response->getContent());
1078         $this->assertEquals(1, $this->response->headers->get('X-Response-Count'));
1079
1080         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0'));
1081         $this->assertTraceContains('fresh');
1082         $this->assertEquals('Bob/2.0', $this->response->getContent());
1083         $this->assertEquals(2, $this->response->headers->get('X-Response-Count'));
1084
1085         $this->request('GET', '/', array('HTTP_USER_AGENT' => 'Bob/2.0'));
1086         $this->assertTraceContains('miss');
1087         $this->assertEquals('Bob/2.0', $this->response->getContent());
1088         $this->assertEquals(3, $this->response->headers->get('X-Response-Count'));
1089     }
1090
1091     public function testShouldCatchExceptions()
1092     {
1093         $this->catchExceptions();
1094
1095         $this->setNextResponse();
1096         $this->request('GET', '/');
1097
1098         $this->assertExceptionsAreCaught();
1099     }
1100
1101     public function testShouldCatchExceptionsWhenReloadingAndNoCacheRequest()
1102     {
1103         $this->catchExceptions();
1104
1105         $this->setNextResponse();
1106         $this->cacheConfig['allow_reload'] = true;
1107         $this->request('GET', '/', array(), array(), false, array('Pragma' => 'no-cache'));
1108
1109         $this->assertExceptionsAreCaught();
1110     }
1111
1112     public function testShouldNotCatchExceptions()
1113     {
1114         $this->catchExceptions(false);
1115
1116         $this->setNextResponse();
1117         $this->request('GET', '/');
1118
1119         $this->assertExceptionsAreNotCaught();
1120     }
1121
1122     public function testEsiCacheSendsTheLowestTtl()
1123     {
1124         $responses = array(
1125             array(
1126                 'status' => 200,
1127                 'body' => '<esi:include src="/foo" /> <esi:include src="/bar" />',
1128                 'headers' => array(
1129                     'Cache-Control' => 's-maxage=300',
1130                     'Surrogate-Control' => 'content="ESI/1.0"',
1131                 ),
1132             ),
1133             array(
1134                 'status' => 200,
1135                 'body' => 'Hello World!',
1136                 'headers' => array('Cache-Control' => 's-maxage=300'),
1137             ),
1138             array(
1139                 'status' => 200,
1140                 'body' => 'My name is Bobby.',
1141                 'headers' => array('Cache-Control' => 's-maxage=100'),
1142             ),
1143         );
1144
1145         $this->setNextResponses($responses);
1146
1147         $this->request('GET', '/', array(), array(), true);
1148         $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent());
1149
1150         // check for 100 or 99 as the test can be executed after a second change
1151         $this->assertTrue(in_array($this->response->getTtl(), array(99, 100)));
1152     }
1153
1154     public function testEsiCacheForceValidation()
1155     {
1156         $responses = array(
1157             array(
1158                 'status' => 200,
1159                 'body' => '<esi:include src="/foo" /> <esi:include src="/bar" />',
1160                 'headers' => array(
1161                     'Cache-Control' => 's-maxage=300',
1162                     'Surrogate-Control' => 'content="ESI/1.0"',
1163                 ),
1164             ),
1165             array(
1166                 'status' => 200,
1167                 'body' => 'Hello World!',
1168                 'headers' => array('ETag' => 'foobar'),
1169             ),
1170             array(
1171                 'status' => 200,
1172                 'body' => 'My name is Bobby.',
1173                 'headers' => array('Cache-Control' => 's-maxage=100'),
1174             ),
1175         );
1176
1177         $this->setNextResponses($responses);
1178
1179         $this->request('GET', '/', array(), array(), true);
1180         $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent());
1181         $this->assertNull($this->response->getTtl());
1182         $this->assertTrue($this->response->mustRevalidate());
1183         $this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
1184         $this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache'));
1185     }
1186
1187     public function testEsiRecalculateContentLengthHeader()
1188     {
1189         $responses = array(
1190             array(
1191                 'status' => 200,
1192                 'body' => '<esi:include src="/foo" />',
1193                 'headers' => array(
1194                     'Content-Length' => 26,
1195                     'Cache-Control' => 's-maxage=300',
1196                     'Surrogate-Control' => 'content="ESI/1.0"',
1197                 ),
1198             ),
1199             array(
1200                 'status' => 200,
1201                 'body' => 'Hello World!',
1202                 'headers' => array(),
1203             ),
1204         );
1205
1206         $this->setNextResponses($responses);
1207
1208         $this->request('GET', '/', array(), array(), true);
1209         $this->assertEquals('Hello World!', $this->response->getContent());
1210         $this->assertEquals(12, $this->response->headers->get('Content-Length'));
1211     }
1212
1213     public function testClientIpIsAlwaysLocalhostForForwardedRequests()
1214     {
1215         $this->setNextResponse();
1216         $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1'));
1217
1218         $this->assertEquals('127.0.0.1', $this->kernel->getBackendRequest()->server->get('REMOTE_ADDR'));
1219     }
1220
1221     /**
1222      * @dataProvider getTrustedProxyData
1223      */
1224     public function testHttpCacheIsSetAsATrustedProxy(array $existing, array $expected)
1225     {
1226         Request::setTrustedProxies($existing);
1227
1228         $this->setNextResponse();
1229         $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1'));
1230
1231         $this->assertEquals($expected, Request::getTrustedProxies());
1232     }
1233
1234     public function getTrustedProxyData()
1235     {
1236         return array(
1237             array(array(), array('127.0.0.1')),
1238             array(array('10.0.0.2'), array('10.0.0.2', '127.0.0.1')),
1239             array(array('10.0.0.2', '127.0.0.1'), array('10.0.0.2', '127.0.0.1')),
1240         );
1241     }
1242
1243     /**
1244      * @dataProvider getXForwardedForData
1245      */
1246     public function testXForwarderForHeaderForForwardedRequests($xForwardedFor, $expected)
1247     {
1248         $this->setNextResponse();
1249         $server = array('REMOTE_ADDR' => '10.0.0.1');
1250         if (false !== $xForwardedFor) {
1251             $server['HTTP_X_FORWARDED_FOR'] = $xForwardedFor;
1252         }
1253         $this->request('GET', '/', $server);
1254
1255         $this->assertEquals($expected, $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For'));
1256     }
1257
1258     public function getXForwardedForData()
1259     {
1260         return array(
1261             array(false, '10.0.0.1'),
1262             array('10.0.0.2', '10.0.0.2, 10.0.0.1'),
1263             array('10.0.0.2, 10.0.0.3', '10.0.0.2, 10.0.0.3, 10.0.0.1'),
1264         );
1265     }
1266
1267     public function testXForwarderForHeaderForPassRequests()
1268     {
1269         $this->setNextResponse();
1270         $server = array('REMOTE_ADDR' => '10.0.0.1');
1271         $this->request('POST', '/', $server);
1272
1273         $this->assertEquals('10.0.0.1', $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For'));
1274     }
1275
1276     public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponses()
1277     {
1278         $time = \DateTime::createFromFormat('U', time());
1279
1280         $responses = array(
1281             array(
1282                 'status' => 200,
1283                 'body' => '<esi:include src="/hey" />',
1284                 'headers' => array(
1285                     'Surrogate-Control' => 'content="ESI/1.0"',
1286                     'ETag' => 'hey',
1287                     'Last-Modified' => $time->format(DATE_RFC2822),
1288                 ),
1289             ),
1290             array(
1291                 'status' => 200,
1292                 'body' => 'Hey!',
1293                 'headers' => array(),
1294             ),
1295         );
1296
1297         $this->setNextResponses($responses);
1298
1299         $this->request('GET', '/', array(), array(), true);
1300         $this->assertNull($this->response->getETag());
1301         $this->assertNull($this->response->getLastModified());
1302     }
1303
1304     public function testDoesNotCacheOptionsRequest()
1305     {
1306         $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'get');
1307         $this->request('GET', '/');
1308         $this->assertHttpKernelIsCalled();
1309
1310         $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'options');
1311         $this->request('OPTIONS', '/');
1312         $this->assertHttpKernelIsCalled();
1313
1314         $this->request('GET', '/');
1315         $this->assertHttpKernelIsNotCalled();
1316         $this->assertSame('get', $this->response->getContent());
1317     }
1318 }
1319
1320 class TestKernel implements HttpKernelInterface
1321 {
1322     public $terminateCalled = false;
1323
1324     public function terminate(Request $request, Response $response)
1325     {
1326         $this->terminateCalled = true;
1327     }
1328
1329     public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
1330     {
1331     }
1332 }