/
home
/
sjslayjy
/
public_html
/
theweavenest
/
vendor
/
symfony
/
http-kernel
/
Tests
/
HttpCache
/
Upload File
HOME
<?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * 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 testSharedMaxAgeNotSetIfNotSetInMainRequest() { $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 testExpiresHeaderUpdatedFromMaxAge() { $cacheStrategy = new ResponseCacheStrategy(); $response1 = new Response(); $response1->setExpires(new \DateTime('+ 1 hour')); $response1->setPublic(); $cacheStrategy->add($response1); $response = new Response(); $response->setMaxAge(0); $response->setSharedMaxAge(86400); $cacheStrategy->update($response); $this->assertSame('0', $response->headers->getCacheControlDirective('max-age')); $this->assertSame('3600', $response->headers->getCacheControlDirective('s-maxage')); // Expires header must be same as Date header because "max-age" is 0. $this->assertSame($response->headers->get('Date'), $response->headers->get('Expires')); } public function testMaxAgeUpdatedFromExpiresHeader() { $cacheStrategy = new ResponseCacheStrategy(); $response1 = new Response(); $response1->setExpires(new \DateTime('+ 1 hour', new \DateTimeZone('UTC'))); $response1->setPublic(); $cacheStrategy->add($response1); $response = new Response(); $response->setMaxAge(86400); $cacheStrategy->update($response); $this->assertSame('3600', $response->headers->getCacheControlDirective('max-age')); $this->assertNull($response->headers->getCacheControlDirective('s-maxage')); $this->assertSame((new \DateTime('+ 1 hour', new \DateTimeZone('UTC')))->format('D, d M Y H:i:s').' GMT', $response->headers->get('Expires')); } public function testMaxAgeAndSharedMaxAgeUpdatedFromExpiresHeader() { $cacheStrategy = new ResponseCacheStrategy(); $response1 = new Response(); $response1->setExpires(new \DateTime('+ 1 day', new \DateTimeZone('UTC'))); $response1->setPublic(); $cacheStrategy->add($response1); $response = new Response(); $response->setMaxAge(3600); $response->setSharedMaxAge(86400); $cacheStrategy->update($response); $this->assertSame('3600', $response->headers->getCacheControlDirective('max-age')); $this->assertSame('86400', $response->headers->getCacheControlDirective('s-maxage')); $this->assertSame((new \DateTime('+ 1 hour', new \DateTimeZone('UTC')))->format('D, d M Y H:i:s').' GMT', $response->headers->get('Expires')); } public function testMainResponseNotCacheableWhenEmbeddedResponseRequiresValidation() { $cacheStrategy = new ResponseCacheStrategy(); $embeddedResponse = new Response(); $embeddedResponse->setLastModified(new \DateTimeImmutable()); $cacheStrategy->add($embeddedResponse); $mainResponse = new Response(); $mainResponse->setSharedMaxAge(3600); $cacheStrategy->update($mainResponse); $this->assertTrue($mainResponse->headers->hasCacheControlDirective('no-cache')); $this->assertTrue($mainResponse->headers->hasCacheControlDirective('must-revalidate')); $this->assertFalse($mainResponse->isFresh()); } public function testValidationOnMainResponseIsNotPossibleWhenItContainsEmbeddedResponses() { $cacheStrategy = new ResponseCacheStrategy(); // This main response uses the "validation" model $mainResponse = new Response(); $mainResponse->setLastModified(new \DateTimeImmutable()); $mainResponse->setEtag('foo'); // Embedded response uses "expiry" model $embeddedResponse = new Response(); $mainResponse->setSharedMaxAge(3600); $cacheStrategy->add($embeddedResponse); $cacheStrategy->update($mainResponse); $this->assertFalse($mainResponse->isValidateable()); $this->assertFalse($mainResponse->headers->has('Last-Modified')); $this->assertFalse($mainResponse->headers->has('ETag')); $this->assertTrue($mainResponse->headers->hasCacheControlDirective('no-cache')); $this->assertTrue($mainResponse->headers->hasCacheControlDirective('must-revalidate')); } public function testMainResponseWithValidationIsUnchangedWhenThereIsNoEmbeddedResponse() { $cacheStrategy = new ResponseCacheStrategy(); $mainResponse = new Response(); $mainResponse->setLastModified(new \DateTimeImmutable()); $cacheStrategy->update($mainResponse); $this->assertTrue($mainResponse->isValidateable()); } public function testMainResponseWithExpirationIsUnchangedWhenThereIsNoEmbeddedResponse() { $cacheStrategy = new ResponseCacheStrategy(); $mainResponse = new Response(); $mainResponse->setSharedMaxAge(3600); $cacheStrategy->update($mainResponse); $this->assertTrue($mainResponse->isFresh()); } public function testLastModifiedIsMergedWithEmbeddedResponse() { $cacheStrategy = new ResponseCacheStrategy(); $mainResponse = new Response(); $mainResponse->setLastModified(new \DateTimeImmutable('-2 hour')); $embeddedDate = new \DateTimeImmutable('-1 hour'); $embeddedResponse = new Response(); $embeddedResponse->setLastModified($embeddedDate); $cacheStrategy->add($embeddedResponse); $cacheStrategy->update($mainResponse); $this->assertTrue($mainResponse->headers->has('Last-Modified')); $this->assertSame($embeddedDate->getTimestamp(), $mainResponse->getLastModified()->getTimestamp()); } public function testLastModifiedIsRemovedWhenEmbeddedResponseHasNoLastModified() { $cacheStrategy = new ResponseCacheStrategy(); $mainResponse = new Response(); $mainResponse->setLastModified(new \DateTimeImmutable('-2 hour')); $embeddedResponse = new Response(); $cacheStrategy->add($embeddedResponse); $cacheStrategy->update($mainResponse); $this->assertFalse($mainResponse->headers->has('Last-Modified')); } public function testLastModifiedIsNotAddedWhenMainResponseHasNoLastModified() { $cacheStrategy = new ResponseCacheStrategy(); $mainResponse = new Response(); $embeddedResponse = new Response(); $embeddedResponse->setLastModified(new \DateTimeImmutable('-2 hour')); $cacheStrategy->add($embeddedResponse); $cacheStrategy->update($mainResponse); $this->assertFalse($mainResponse->headers->has('Last-Modified')); } public function testMainResponseIsNotCacheableWhenEmbeddedResponseIsNotCacheable() { $cacheStrategy = new ResponseCacheStrategy(); $mainResponse = new Response(); $mainResponse->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($mainResponse); $this->assertTrue($mainResponse->headers->hasCacheControlDirective('no-cache')); $this->assertTrue($mainResponse->headers->hasCacheControlDirective('must-revalidate')); $this->assertFalse($mainResponse->isFresh()); } public function testEmbeddingPrivateResponseMakesMainResponsePrivate() { $cacheStrategy = new ResponseCacheStrategy(); $mainResponse = new Response(); $mainResponse->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($mainResponse); $this->assertTrue($mainResponse->headers->hasCacheControlDirective('private')); $this->assertFalse($mainResponse->headers->hasCacheControlDirective('public')); } public function testEmbeddingPublicResponseDoesNotMakeMainResponsePublic() { $cacheStrategy = new ResponseCacheStrategy(); $mainResponse = new Response(); $mainResponse->setPrivate(); // this is the default, but let's be explicit $mainResponse->setMaxAge(100); $embeddedResponse = new Response(); $embeddedResponse->setPublic(); $embeddedResponse->setSharedMaxAge(100); $cacheStrategy->add($embeddedResponse); $cacheStrategy->update($mainResponse); $this->assertTrue($mainResponse->headers->hasCacheControlDirective('private')); $this->assertFalse($mainResponse->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(); $mainResponse = new Response(); $mainResponse->setSharedMaxAge(3600); $embeddedResponse = new Response(); $embeddedResponse->setSharedMaxAge(60); $embeddedResponse->setEtag('foo'); $cacheStrategy->add($embeddedResponse); $cacheStrategy->update($mainResponse); $this->assertEqualsWithDelta(60, (int) $mainResponse->headers->getCacheControlDirective('s-maxage'), 1); } public function testResponseIsExpirableButNotValidateableWhenMainResponseCombinesExpirationAndValidation() { $cacheStrategy = new ResponseCacheStrategy(); $mainResponse = new Response(); $mainResponse->setSharedMaxAge(3600); $mainResponse->setEtag('foo'); $mainResponse->setLastModified(new \DateTimeImmutable()); $embeddedResponse = new Response(); $embeddedResponse->setSharedMaxAge(60); $cacheStrategy->add($embeddedResponse); $cacheStrategy->update($mainResponse); $this->assertSame('60', $mainResponse->headers->getCacheControlDirective('s-maxage')); $this->assertFalse($mainResponse->isValidateable()); } /** * @group time-sensitive * * @dataProvider cacheControlMergingProvider */ public function testCacheControlMerging(array $expects, array $main, 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 = $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($main); $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 static 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, 'max-age' => null, 's-maxage' => '60'], ['public' => true, 's-maxage' => 3600], [ ['public' => true, 'max-age' => 60], ], ]; yield 's-maxage may be set to 0' => [ ['public' => true, 's-maxage' => '0', 'max-age' => null], ['public' => true, 's-maxage' => '0'], [ ['public' => true, 's-maxage' => '60'], ], ]; yield 's-maxage may be set to 0, and works independently from maxage' => [ ['public' => true, 's-maxage' => '0', 'max-age' => '30'], ['public' => true, 's-maxage' => '0', 'max-age' => '30'], [ ['public' => true, 'max-age' => '60'], ], ]; yield 'public subresponse without lifetime does not remove lifetime for main response' => [ ['public' => true, 's-maxage' => '30', 'max-age' => null], ['public' => true, 's-maxage' => '30'], [ ['public' => true], ], ]; yield 'lifetime for subresponse is kept when main response has no lifetime' => [ ['public' => true, 'max-age' => '30'], ['public' => true], [ ['public' => true, 'max-age' => '30'], ], ]; yield 's-maxage on the subresponse implies public, so the result is public as well' => [ ['public' => true, 'max-age' => '10', 's-maxage' => null], ['public' => true, 'max-age' => '10'], [ ['max-age' => '30', 's-maxage' => '20'], ], ]; 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'], ], ]; } }