5e03bc7a2a878a84483763e7590612308ead630b
[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         $this->setNextResponse(304, array(), '', function (Request $request, Response $response) use ($time) {
875             $this->assertEquals($time->format(DATE_RFC2822), $request->headers->get('IF_MODIFIED_SINCE'));
876         });
877
878         $this->request('GET', '/');
879         $this->assertHttpKernelIsCalled();
880         $this->assertEquals(200, $this->response->getStatusCode());
881         $this->assertEquals('Hello World', $this->response->getContent());
882         $this->assertTraceContains('stale');
883         $this->assertTraceContains('valid');
884     }
885
886     public function testReplacesCachedResponsesWhenValidationResultsInNon304Response()
887     {
888         $time = \DateTime::createFromFormat('U', time());
889         $count = 0;
890         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time, &$count) {
891             $response->headers->set('Last-Modified', $time->format(DATE_RFC2822));
892             $response->headers->set('Cache-Control', 'public');
893             switch (++$count) {
894                 case 1:
895                     $response->setContent('first response');
896                     break;
897                 case 2:
898                     $response->setContent('second response');
899                     break;
900                 case 3:
901                     $response->setContent('');
902                     $response->setStatusCode(304);
903                     break;
904             }
905         });
906
907         // first request should fetch from backend and store in cache
908         $this->request('GET', '/');
909         $this->assertEquals(200, $this->response->getStatusCode());
910         $this->assertEquals('first response', $this->response->getContent());
911
912         // second request is validated, is invalid, and replaces cached entry
913         $this->request('GET', '/');
914         $this->assertEquals(200, $this->response->getStatusCode());
915         $this->assertEquals('second response', $this->response->getContent());
916
917         // third response is validated, valid, and returns cached entry
918         $this->request('GET', '/');
919         $this->assertEquals(200, $this->response->getStatusCode());
920         $this->assertEquals('second response', $this->response->getContent());
921
922         $this->assertEquals(3, $count);
923     }
924
925     public function testPassesHeadRequestsThroughDirectlyOnPass()
926     {
927         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) {
928             $response->setContent('');
929             $response->setStatusCode(200);
930             $this->assertEquals('HEAD', $request->getMethod());
931         });
932
933         $this->request('HEAD', '/', array('HTTP_EXPECT' => 'something ...'));
934         $this->assertHttpKernelIsCalled();
935         $this->assertEquals('', $this->response->getContent());
936     }
937
938     public function testUsesCacheToRespondToHeadRequestsWhenFresh()
939     {
940         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) {
941             $response->headers->set('Cache-Control', 'public, max-age=10');
942             $response->setContent('Hello World');
943             $response->setStatusCode(200);
944             $this->assertNotEquals('HEAD', $request->getMethod());
945         });
946
947         $this->request('GET', '/');
948         $this->assertHttpKernelIsCalled();
949         $this->assertEquals('Hello World', $this->response->getContent());
950
951         $this->request('HEAD', '/');
952         $this->assertHttpKernelIsNotCalled();
953         $this->assertEquals(200, $this->response->getStatusCode());
954         $this->assertEquals('', $this->response->getContent());
955         $this->assertEquals(strlen('Hello World'), $this->response->headers->get('Content-Length'));
956     }
957
958     public function testSendsNoContentWhenFresh()
959     {
960         $time = \DateTime::createFromFormat('U', time());
961         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time) {
962             $response->headers->set('Cache-Control', 'public, max-age=10');
963             $response->headers->set('Last-Modified', $time->format(DATE_RFC2822));
964         });
965
966         $this->request('GET', '/');
967         $this->assertHttpKernelIsCalled();
968         $this->assertEquals('Hello World', $this->response->getContent());
969
970         $this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822)));
971         $this->assertHttpKernelIsNotCalled();
972         $this->assertEquals(304, $this->response->getStatusCode());
973         $this->assertEquals('', $this->response->getContent());
974     }
975
976     public function testInvalidatesCachedResponsesOnPost()
977     {
978         $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) {
979             if ('GET' == $request->getMethod()) {
980                 $response->setStatusCode(200);
981                 $response->headers->set('Cache-Control', 'public, max-age=500');
982                 $response->setContent('Hello World');
983             } elseif ('POST' == $request->getMethod()) {
984                 $response->setStatusCode(303);
985                 $response->headers->set('Location', '/');
986                 $response->headers->remove('Cache-Control');
987                 $response->setContent('');
988             }
989         });
990
991         // build initial request to enter into the cache
992         $this->request('GET', '/');
993         $this->assertHttpKernelIsCalled();
994         $this->assertEquals(200, $this->response->getStatusCode());
995         $this->assertEquals('Hello World', $this->response->getContent());
996         $this->assertTraceContains('miss');
997         $this->assertTraceContains('store');
998
999         // make sure it is valid
1000         $this->request('GET', '/');
1001         $this->assertHttpKernelIsNotCalled();
1002         $this->assertEquals(200, $this->response->getStatusCode());
1003         $this->assertEquals('Hello World', $this->response->getContent());
1004         $this->assertTraceContains('fresh');
1005
1006         // now POST to same URL
1007         $this->request('POST', '/helloworld');
1008         $this->assertHttpKernelIsCalled();
1009         $this->assertEquals('/', $this->response->headers->get('Location'));
1010         $this->assertTraceContains('invalidate');
1011         $this->assertTraceContains('pass');
1012         $this->assertEquals('', $this->response->getContent());
1013
1014         // now make sure it was actually invalidated
1015         $this->request('GET', '/');
1016         $this->assertHttpKernelIsCalled();
1017         $this->assertEquals(200, $this->response->getStatusCode());
1018         $this->assertEquals('Hello World', $this->response->getContent());
1019         $this->assertTraceContains('stale');
1020         $this->assertTraceContains('invalid');
1021         $this->assertTraceContains('store');
1022     }
1023
1024     public function testServesFromCacheWhenHeadersMatch()
1025     {
1026         $count = 0;
1027         $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) {
1028             $response->headers->set('Vary', 'Accept User-Agent Foo');
1029             $response->headers->set('Cache-Control', 'public, max-age=10');
1030             $response->headers->set('X-Response-Count', ++$count);
1031             $response->setContent($request->headers->get('USER_AGENT'));
1032         });
1033
1034         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0'));
1035         $this->assertEquals(200, $this->response->getStatusCode());
1036         $this->assertEquals('Bob/1.0', $this->response->getContent());
1037         $this->assertTraceContains('miss');
1038         $this->assertTraceContains('store');
1039
1040         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0'));
1041         $this->assertEquals(200, $this->response->getStatusCode());
1042         $this->assertEquals('Bob/1.0', $this->response->getContent());
1043         $this->assertTraceContains('fresh');
1044         $this->assertTraceNotContains('store');
1045         $this->assertNotNull($this->response->headers->get('X-Content-Digest'));
1046     }
1047
1048     public function testStoresMultipleResponsesWhenHeadersDiffer()
1049     {
1050         $count = 0;
1051         $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) {
1052             $response->headers->set('Vary', 'Accept User-Agent Foo');
1053             $response->headers->set('Cache-Control', 'public, max-age=10');
1054             $response->headers->set('X-Response-Count', ++$count);
1055             $response->setContent($request->headers->get('USER_AGENT'));
1056         });
1057
1058         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0'));
1059         $this->assertEquals(200, $this->response->getStatusCode());
1060         $this->assertEquals('Bob/1.0', $this->response->getContent());
1061         $this->assertEquals(1, $this->response->headers->get('X-Response-Count'));
1062
1063         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0'));
1064         $this->assertEquals(200, $this->response->getStatusCode());
1065         $this->assertTraceContains('miss');
1066         $this->assertTraceContains('store');
1067         $this->assertEquals('Bob/2.0', $this->response->getContent());
1068         $this->assertEquals(2, $this->response->headers->get('X-Response-Count'));
1069
1070         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0'));
1071         $this->assertTraceContains('fresh');
1072         $this->assertEquals('Bob/1.0', $this->response->getContent());
1073         $this->assertEquals(1, $this->response->headers->get('X-Response-Count'));
1074
1075         $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0'));
1076         $this->assertTraceContains('fresh');
1077         $this->assertEquals('Bob/2.0', $this->response->getContent());
1078         $this->assertEquals(2, $this->response->headers->get('X-Response-Count'));
1079
1080         $this->request('GET', '/', array('HTTP_USER_AGENT' => 'Bob/2.0'));
1081         $this->assertTraceContains('miss');
1082         $this->assertEquals('Bob/2.0', $this->response->getContent());
1083         $this->assertEquals(3, $this->response->headers->get('X-Response-Count'));
1084     }
1085
1086     public function testShouldCatchExceptions()
1087     {
1088         $this->catchExceptions();
1089
1090         $this->setNextResponse();
1091         $this->request('GET', '/');
1092
1093         $this->assertExceptionsAreCaught();
1094     }
1095
1096     public function testShouldCatchExceptionsWhenReloadingAndNoCacheRequest()
1097     {
1098         $this->catchExceptions();
1099
1100         $this->setNextResponse();
1101         $this->cacheConfig['allow_reload'] = true;
1102         $this->request('GET', '/', array(), array(), false, array('Pragma' => 'no-cache'));
1103
1104         $this->assertExceptionsAreCaught();
1105     }
1106
1107     public function testShouldNotCatchExceptions()
1108     {
1109         $this->catchExceptions(false);
1110
1111         $this->setNextResponse();
1112         $this->request('GET', '/');
1113
1114         $this->assertExceptionsAreNotCaught();
1115     }
1116
1117     public function testEsiCacheSendsTheLowestTtl()
1118     {
1119         $responses = array(
1120             array(
1121                 'status' => 200,
1122                 'body' => '<esi:include src="/foo" /> <esi:include src="/bar" />',
1123                 'headers' => array(
1124                     'Cache-Control' => 's-maxage=300',
1125                     'Surrogate-Control' => 'content="ESI/1.0"',
1126                 ),
1127             ),
1128             array(
1129                 'status' => 200,
1130                 'body' => 'Hello World!',
1131                 'headers' => array('Cache-Control' => 's-maxage=300'),
1132             ),
1133             array(
1134                 'status' => 200,
1135                 'body' => 'My name is Bobby.',
1136                 'headers' => array('Cache-Control' => 's-maxage=100'),
1137             ),
1138         );
1139
1140         $this->setNextResponses($responses);
1141
1142         $this->request('GET', '/', array(), array(), true);
1143         $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent());
1144
1145         // check for 100 or 99 as the test can be executed after a second change
1146         $this->assertTrue(in_array($this->response->getTtl(), array(99, 100)));
1147     }
1148
1149     public function testEsiCacheForceValidation()
1150     {
1151         $responses = array(
1152             array(
1153                 'status' => 200,
1154                 'body' => '<esi:include src="/foo" /> <esi:include src="/bar" />',
1155                 'headers' => array(
1156                     'Cache-Control' => 's-maxage=300',
1157                     'Surrogate-Control' => 'content="ESI/1.0"',
1158                 ),
1159             ),
1160             array(
1161                 'status' => 200,
1162                 'body' => 'Hello World!',
1163                 'headers' => array('ETag' => 'foobar'),
1164             ),
1165             array(
1166                 'status' => 200,
1167                 'body' => 'My name is Bobby.',
1168                 'headers' => array('Cache-Control' => 's-maxage=100'),
1169             ),
1170         );
1171
1172         $this->setNextResponses($responses);
1173
1174         $this->request('GET', '/', array(), array(), true);
1175         $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent());
1176         $this->assertNull($this->response->getTtl());
1177         $this->assertTrue($this->response->mustRevalidate());
1178         $this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
1179         $this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache'));
1180     }
1181
1182     public function testEsiRecalculateContentLengthHeader()
1183     {
1184         $responses = array(
1185             array(
1186                 'status' => 200,
1187                 'body' => '<esi:include src="/foo" />',
1188                 'headers' => array(
1189                     'Content-Length' => 26,
1190                     'Cache-Control' => 's-maxage=300',
1191                     'Surrogate-Control' => 'content="ESI/1.0"',
1192                 ),
1193             ),
1194             array(
1195                 'status' => 200,
1196                 'body' => 'Hello World!',
1197                 'headers' => array(),
1198             ),
1199         );
1200
1201         $this->setNextResponses($responses);
1202
1203         $this->request('GET', '/', array(), array(), true);
1204         $this->assertEquals('Hello World!', $this->response->getContent());
1205         $this->assertEquals(12, $this->response->headers->get('Content-Length'));
1206     }
1207
1208     public function testClientIpIsAlwaysLocalhostForForwardedRequests()
1209     {
1210         $this->setNextResponse();
1211         $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1'));
1212
1213         $this->assertEquals('127.0.0.1', $this->kernel->getBackendRequest()->server->get('REMOTE_ADDR'));
1214     }
1215
1216     /**
1217      * @dataProvider getTrustedProxyData
1218      */
1219     public function testHttpCacheIsSetAsATrustedProxy(array $existing, array $expected)
1220     {
1221         Request::setTrustedProxies($existing, -1);
1222
1223         $this->setNextResponse();
1224         $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1'));
1225
1226         $this->assertEquals($expected, Request::getTrustedProxies());
1227     }
1228
1229     public function getTrustedProxyData()
1230     {
1231         return array(
1232             array(array(), array('127.0.0.1')),
1233             array(array('10.0.0.2'), array('10.0.0.2', '127.0.0.1')),
1234             array(array('10.0.0.2', '127.0.0.1'), array('10.0.0.2', '127.0.0.1')),
1235         );
1236     }
1237
1238     /**
1239      * @dataProvider getXForwardedForData
1240      */
1241     public function testXForwarderForHeaderForForwardedRequests($xForwardedFor, $expected)
1242     {
1243         $this->setNextResponse();
1244         $server = array('REMOTE_ADDR' => '10.0.0.1');
1245         if (false !== $xForwardedFor) {
1246             $server['HTTP_X_FORWARDED_FOR'] = $xForwardedFor;
1247         }
1248         $this->request('GET', '/', $server);
1249
1250         $this->assertEquals($expected, $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For'));
1251     }
1252
1253     public function getXForwardedForData()
1254     {
1255         return array(
1256             array(false, '10.0.0.1'),
1257             array('10.0.0.2', '10.0.0.2, 10.0.0.1'),
1258             array('10.0.0.2, 10.0.0.3', '10.0.0.2, 10.0.0.3, 10.0.0.1'),
1259         );
1260     }
1261
1262     public function testXForwarderForHeaderForPassRequests()
1263     {
1264         $this->setNextResponse();
1265         $server = array('REMOTE_ADDR' => '10.0.0.1');
1266         $this->request('POST', '/', $server);
1267
1268         $this->assertEquals('10.0.0.1', $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For'));
1269     }
1270
1271     public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponses()
1272     {
1273         $time = \DateTime::createFromFormat('U', time());
1274
1275         $responses = array(
1276             array(
1277                 'status' => 200,
1278                 'body' => '<esi:include src="/hey" />',
1279                 'headers' => array(
1280                     'Surrogate-Control' => 'content="ESI/1.0"',
1281                     'ETag' => 'hey',
1282                     'Last-Modified' => $time->format(DATE_RFC2822),
1283                 ),
1284             ),
1285             array(
1286                 'status' => 200,
1287                 'body' => 'Hey!',
1288                 'headers' => array(),
1289             ),
1290         );
1291
1292         $this->setNextResponses($responses);
1293
1294         $this->request('GET', '/', array(), array(), true);
1295         $this->assertNull($this->response->getETag());
1296         $this->assertNull($this->response->getLastModified());
1297     }
1298
1299     public function testDoesNotCacheOptionsRequest()
1300     {
1301         $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'get');
1302         $this->request('GET', '/');
1303         $this->assertHttpKernelIsCalled();
1304
1305         $this->setNextResponse(200, array('Cache-Control' => 'public, s-maxage=60'), 'options');
1306         $this->request('OPTIONS', '/');
1307         $this->assertHttpKernelIsCalled();
1308
1309         $this->request('GET', '/');
1310         $this->assertHttpKernelIsNotCalled();
1311         $this->assertSame('get', $this->response->getContent());
1312     }
1313 }
1314
1315 class TestKernel implements HttpKernelInterface
1316 {
1317     public $terminateCalled = false;
1318
1319     public function terminate(Request $request, Response $response)
1320     {
1321         $this->terminateCalled = true;
1322     }
1323
1324     public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
1325     {
1326     }
1327 }