sonarrradarrplexorganizrnginxdashboardmuximuxlandingpagestartpagelandinghtpcserverhomepagesabnzbdheimdallembycouchpotatonzbgetbookmarkapplication-dashboard
		
		
		
		
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							469 lines
						
					
					
						
							16 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							469 lines
						
					
					
						
							16 KiB
						
					
					
				| <?php | |
| 
 | |
| /* | |
|  * This file is part of the Symfony package. | |
|  * | |
|  * (c) Fabien Potencier <fabien@symfony.com> | |
|  * | |
|  * This code is partially based on the Rack-Cache library by Ryan Tomayko, | |
|  * which is released under the MIT license. | |
|  * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) | |
|  * | |
|  * For the full copyright and license information, please view the LICENSE | |
|  * file that was distributed with this source code. | |
|  */ | |
| 
 | |
| namespace Symfony\Component\HttpKernel\Tests\HttpCache; | |
| 
 | |
| use PHPUnit\Framework\TestCase; | |
| use Symfony\Component\HttpFoundation\Response; | |
| use Symfony\Component\HttpKernel\HttpCache\ResponseCacheStrategy; | |
| 
 | |
| class ResponseCacheStrategyTest extends TestCase | |
| { | |
|     public function testMinimumSharedMaxAgeWins() | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         $response1 = new Response(); | |
|         $response1->setSharedMaxAge(60); | |
|         $cacheStrategy->add($response1); | |
| 
 | |
|         $response2 = new Response(); | |
|         $response2->setSharedMaxAge(3600); | |
|         $cacheStrategy->add($response2); | |
| 
 | |
|         $response = new Response(); | |
|         $response->setSharedMaxAge(86400); | |
|         $cacheStrategy->update($response); | |
| 
 | |
|         $this->assertSame('60', $response->headers->getCacheControlDirective('s-maxage')); | |
|     } | |
| 
 | |
|     public function testSharedMaxAgeNotSetIfNotSetInAnyEmbeddedRequest() | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         $response1 = new Response(); | |
|         $response1->setSharedMaxAge(60); | |
|         $cacheStrategy->add($response1); | |
| 
 | |
|         $response2 = new Response(); | |
|         $cacheStrategy->add($response2); | |
| 
 | |
|         $response = new Response(); | |
|         $response->setSharedMaxAge(86400); | |
|         $cacheStrategy->update($response); | |
| 
 | |
|         $this->assertFalse($response->headers->hasCacheControlDirective('s-maxage')); | |
|     } | |
| 
 | |
|     public function testSharedMaxAgeNotSetIfNotSetInMasterRequest() | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         $response1 = new Response(); | |
|         $response1->setSharedMaxAge(60); | |
|         $cacheStrategy->add($response1); | |
| 
 | |
|         $response2 = new Response(); | |
|         $response2->setSharedMaxAge(3600); | |
|         $cacheStrategy->add($response2); | |
| 
 | |
|         $response = new Response(); | |
|         $cacheStrategy->update($response); | |
| 
 | |
|         $this->assertFalse($response->headers->hasCacheControlDirective('s-maxage')); | |
|     } | |
| 
 | |
|     public function testMasterResponseNotCacheableWhenEmbeddedResponseRequiresValidation() | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         $embeddedResponse = new Response(); | |
|         $embeddedResponse->setLastModified(new \DateTime()); | |
|         $cacheStrategy->add($embeddedResponse); | |
| 
 | |
|         $masterResponse = new Response(); | |
|         $masterResponse->setSharedMaxAge(3600); | |
|         $cacheStrategy->update($masterResponse); | |
| 
 | |
|         $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); | |
|         $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); | |
|         $this->assertFalse($masterResponse->isFresh()); | |
|     } | |
| 
 | |
|     public function testValidationOnMasterResponseIsNotPossibleWhenItContainsEmbeddedResponses() | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         // This master response uses the "validation" model | |
|         $masterResponse = new Response(); | |
|         $masterResponse->setLastModified(new \DateTime()); | |
|         $masterResponse->setEtag('foo'); | |
| 
 | |
|         // Embedded response uses "expiry" model | |
|         $embeddedResponse = new Response(); | |
|         $masterResponse->setSharedMaxAge(3600); | |
|         $cacheStrategy->add($embeddedResponse); | |
| 
 | |
|         $cacheStrategy->update($masterResponse); | |
| 
 | |
|         $this->assertFalse($masterResponse->isValidateable()); | |
|         $this->assertFalse($masterResponse->headers->has('Last-Modified')); | |
|         $this->assertFalse($masterResponse->headers->has('ETag')); | |
|         $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); | |
|         $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); | |
|     } | |
| 
 | |
|     public function testMasterResponseWithValidationIsUnchangedWhenThereIsNoEmbeddedResponse() | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         $masterResponse = new Response(); | |
|         $masterResponse->setLastModified(new \DateTime()); | |
|         $cacheStrategy->update($masterResponse); | |
| 
 | |
|         $this->assertTrue($masterResponse->isValidateable()); | |
|     } | |
| 
 | |
|     public function testMasterResponseWithExpirationIsUnchangedWhenThereIsNoEmbeddedResponse() | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         $masterResponse = new Response(); | |
|         $masterResponse->setSharedMaxAge(3600); | |
|         $cacheStrategy->update($masterResponse); | |
| 
 | |
|         $this->assertTrue($masterResponse->isFresh()); | |
|     } | |
| 
 | |
|     public function testMasterResponseIsNotCacheableWhenEmbeddedResponseIsNotCacheable() | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         $masterResponse = new Response(); | |
|         $masterResponse->setSharedMaxAge(3600); // Public, cacheable | |
|  | |
|         /* This response has no validation or expiration information. | |
|            That makes it uncacheable, it is always stale. | |
|            (It does *not* make this private, though.) */ | |
|         $embeddedResponse = new Response(); | |
|         $this->assertFalse($embeddedResponse->isFresh()); // not fresh, as no lifetime is provided | |
|  | |
|         $cacheStrategy->add($embeddedResponse); | |
|         $cacheStrategy->update($masterResponse); | |
| 
 | |
|         $this->assertTrue($masterResponse->headers->hasCacheControlDirective('no-cache')); | |
|         $this->assertTrue($masterResponse->headers->hasCacheControlDirective('must-revalidate')); | |
|         $this->assertFalse($masterResponse->isFresh()); | |
|     } | |
| 
 | |
|     public function testEmbeddingPrivateResponseMakesMainResponsePrivate() | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         $masterResponse = new Response(); | |
|         $masterResponse->setSharedMaxAge(3600); // public, cacheable | |
|  | |
|         // The embedded response might for example contain per-user data that remains valid for 60 seconds | |
|         $embeddedResponse = new Response(); | |
|         $embeddedResponse->setPrivate(); | |
|         $embeddedResponse->setMaxAge(60); // this would implicitly set "private" as well, but let's be explicit | |
|  | |
|         $cacheStrategy->add($embeddedResponse); | |
|         $cacheStrategy->update($masterResponse); | |
| 
 | |
|         $this->assertTrue($masterResponse->headers->hasCacheControlDirective('private')); | |
|         $this->assertFalse($masterResponse->headers->hasCacheControlDirective('public')); | |
|     } | |
| 
 | |
|     public function testEmbeddingPublicResponseDoesNotMakeMainResponsePublic() | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         $masterResponse = new Response(); | |
|         $masterResponse->setPrivate(); // this is the default, but let's be explicit | |
|         $masterResponse->setMaxAge(100); | |
| 
 | |
|         $embeddedResponse = new Response(); | |
|         $embeddedResponse->setPublic(); | |
|         $embeddedResponse->setSharedMaxAge(100); | |
| 
 | |
|         $cacheStrategy->add($embeddedResponse); | |
|         $cacheStrategy->update($masterResponse); | |
| 
 | |
|         $this->assertTrue($masterResponse->headers->hasCacheControlDirective('private')); | |
|         $this->assertFalse($masterResponse->headers->hasCacheControlDirective('public')); | |
|     } | |
| 
 | |
|     public function testResponseIsExiprableWhenEmbeddedResponseCombinesExpiryAndValidation() | |
|     { | |
|         /* When "expiration wins over validation" (https://symfony.com/doc/current/http_cache/validation.html) | |
|          * and both the main and embedded response provide s-maxage, then the more restricting value of both | |
|          * should be fine, regardless of whether the embedded response can be validated later on or must be | |
|          * completely regenerated. | |
|          */ | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         $masterResponse = new Response(); | |
|         $masterResponse->setSharedMaxAge(3600); | |
| 
 | |
|         $embeddedResponse = new Response(); | |
|         $embeddedResponse->setSharedMaxAge(60); | |
|         $embeddedResponse->setEtag('foo'); | |
| 
 | |
|         $cacheStrategy->add($embeddedResponse); | |
|         $cacheStrategy->update($masterResponse); | |
| 
 | |
|         $this->assertSame('60', $masterResponse->headers->getCacheControlDirective('s-maxage')); | |
|     } | |
| 
 | |
|     public function testResponseIsExpirableButNotValidateableWhenMasterResponseCombinesExpirationAndValidation() | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
| 
 | |
|         $masterResponse = new Response(); | |
|         $masterResponse->setSharedMaxAge(3600); | |
|         $masterResponse->setEtag('foo'); | |
|         $masterResponse->setLastModified(new \DateTime()); | |
| 
 | |
|         $embeddedResponse = new Response(); | |
|         $embeddedResponse->setSharedMaxAge(60); | |
| 
 | |
|         $cacheStrategy->add($embeddedResponse); | |
|         $cacheStrategy->update($masterResponse); | |
| 
 | |
|         $this->assertSame('60', $masterResponse->headers->getCacheControlDirective('s-maxage')); | |
|         $this->assertFalse($masterResponse->isValidateable()); | |
|     } | |
| 
 | |
|     /** | |
|      * @dataProvider cacheControlMergingProvider | |
|      */ | |
|     public function testCacheControlMerging(array $expects, array $master, array $surrogates) | |
|     { | |
|         $cacheStrategy = new ResponseCacheStrategy(); | |
|         $buildResponse = function ($config) { | |
|             $response = new Response(); | |
| 
 | |
|             foreach ($config as $key => $value) { | |
|                 switch ($key) { | |
|                     case 'age': | |
|                         $response->headers->set('Age', $value); | |
|                         break; | |
| 
 | |
|                     case 'expires': | |
|                         $expires = clone $response->getDate(); | |
|                         $expires->modify('+'.$value.' seconds'); | |
|                         $response->setExpires($expires); | |
|                         break; | |
| 
 | |
|                     case 'max-age': | |
|                         $response->setMaxAge($value); | |
|                         break; | |
| 
 | |
|                     case 's-maxage': | |
|                         $response->setSharedMaxAge($value); | |
|                         break; | |
| 
 | |
|                     case 'private': | |
|                         $response->setPrivate(); | |
|                         break; | |
| 
 | |
|                     case 'public': | |
|                         $response->setPublic(); | |
|                         break; | |
| 
 | |
|                     default: | |
|                         $response->headers->addCacheControlDirective($key, $value); | |
|                 } | |
|             } | |
| 
 | |
|             return $response; | |
|         }; | |
| 
 | |
|         foreach ($surrogates as $config) { | |
|             $cacheStrategy->add($buildResponse($config)); | |
|         } | |
| 
 | |
|         $response = $buildResponse($master); | |
|         $cacheStrategy->update($response); | |
| 
 | |
|         foreach ($expects as $key => $value) { | |
|             if ('expires' === $key) { | |
|                 $this->assertSame($value, $response->getExpires()->format('U') - $response->getDate()->format('U')); | |
|             } elseif ('age' === $key) { | |
|                 $this->assertSame($value, $response->getAge()); | |
|             } elseif (true === $value) { | |
|                 $this->assertTrue($response->headers->hasCacheControlDirective($key), sprintf('Cache-Control header must have "%s" flag', $key)); | |
|             } elseif (false === $value) { | |
|                 $this->assertFalse( | |
|                     $response->headers->hasCacheControlDirective($key), | |
|                     sprintf('Cache-Control header must NOT have "%s" flag', $key) | |
|                 ); | |
|             } else { | |
|                 $this->assertSame($value, $response->headers->getCacheControlDirective($key), sprintf('Cache-Control flag "%s" should be "%s"', $key, $value)); | |
|             } | |
|         } | |
|     } | |
| 
 | |
|     public function cacheControlMergingProvider() | |
|     { | |
|         yield 'result is public if all responses are public' => [ | |
|             ['private' => false, 'public' => true], | |
|             ['public' => true], | |
|             [ | |
|                 ['public' => true], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'result is private by default' => [ | |
|             ['private' => true, 'public' => false], | |
|             ['public' => true], | |
|             [ | |
|                 [], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'combines public and private responses' => [ | |
|             ['must-revalidate' => false, 'private' => true, 'public' => false], | |
|             ['public' => true], | |
|             [ | |
|                 ['private' => true], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'inherits no-cache from surrogates' => [ | |
|             ['no-cache' => true, 'public' => false], | |
|             ['public' => true], | |
|             [ | |
|                 ['no-cache' => true], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'inherits no-store from surrogate' => [ | |
|             ['no-store' => true, 'public' => false], | |
|             ['public' => true], | |
|             [ | |
|                 ['no-store' => true], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'resolve to lowest possible max-age' => [ | |
|             ['public' => false, 'private' => true, 's-maxage' => false, 'max-age' => '60'], | |
|             ['public' => true, 'max-age' => 3600], | |
|             [ | |
|                 ['private' => true, 'max-age' => 60], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'resolves multiple max-age' => [ | |
|             ['public' => false, 'private' => true, 's-maxage' => false, 'max-age' => '60'], | |
|             ['private' => true, 'max-age' => 100], | |
|             [ | |
|                 ['private' => true, 'max-age' => 3600], | |
|                 ['public' => true, 'max-age' => 60, 's-maxage' => 60], | |
|                 ['private' => true, 'max-age' => 60], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'merge max-age and s-maxage' => [ | |
|             ['public' => true, 's-maxage' => '60', 'max-age' => null], | |
|             ['public' => true, 's-maxage' => 3600], | |
|             [ | |
|                 ['public' => true, 'max-age' => 60], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'result is private when combining private responses' => [ | |
|             ['no-cache' => false, 'must-revalidate' => false, 'private' => true], | |
|             ['s-maxage' => 60, 'private' => true], | |
|             [ | |
|                 ['s-maxage' => 60, 'private' => true], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'result can have s-maxage and max-age' => [ | |
|             ['public' => true, 'private' => false, 's-maxage' => '60', 'max-age' => '30'], | |
|             ['s-maxage' => 100, 'max-age' => 2000], | |
|             [ | |
|                 ['s-maxage' => 1000, 'max-age' => 30], | |
|                 ['s-maxage' => 500, 'max-age' => 500], | |
|                 ['s-maxage' => 60, 'max-age' => 1000], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'does not set headers without value' => [ | |
|             ['max-age' => null, 's-maxage' => null, 'public' => null], | |
|             ['private' => true], | |
|             [ | |
|                 ['private' => true], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'max-age 0 is sent to the client' => [ | |
|             ['private' => true, 'max-age' => '0'], | |
|             ['max-age' => 0, 'private' => true], | |
|             [ | |
|                 ['max-age' => 60, 'private' => true], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'max-age is relative to age' => [ | |
|             ['max-age' => '240', 'age' => 60], | |
|             ['max-age' => 180], | |
|             [ | |
|                 ['max-age' => 600, 'age' => 60], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'retains lowest age of all responses' => [ | |
|             ['max-age' => '160', 'age' => 60], | |
|             ['max-age' => 600, 'age' => 60], | |
|             [ | |
|                 ['max-age' => 120, 'age' => 20], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'max-age can be less than age, essentially expiring the response' => [ | |
|             ['age' => 120, 'max-age' => '90'], | |
|             ['max-age' => 90, 'age' => 120], | |
|             [ | |
|                 ['max-age' => 120, 'age' => 60], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'max-age is 0 regardless of age' => [ | |
|             ['max-age' => '0'], | |
|             ['max-age' => 60], | |
|             [ | |
|                 ['max-age' => 0, 'age' => 60], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'max-age is not negative' => [ | |
|             ['max-age' => '0'], | |
|             ['max-age' => 0], | |
|             [ | |
|                 ['max-age' => 0, 'age' => 60], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'calculates lowest Expires header' => [ | |
|             ['expires' => 60], | |
|             ['expires' => 60], | |
|             [ | |
|                 ['expires' => 120], | |
|             ], | |
|         ]; | |
| 
 | |
|         yield 'calculates Expires header relative to age' => [ | |
|             ['expires' => 210, 'age' => 120], | |
|             ['expires' => 90], | |
|             [ | |
|                 ['expires' => 600, 'age' => '120'], | |
|             ], | |
|         ]; | |
|     } | |
| }
 | |
| 
 |