diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b0756ef --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# Enforce Unix newlines +* text=lf + +# Exclude unused files +# see: https://redd.it/2jzp6k +/tests export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/CONTRIBUTING.md export-ignore +/phpcs.xml.dist export-ignore +/phpstan.neon.dist export-ignore +/phpunit.xml.dist export-ignore +/README.md export-ignore diff --git a/.gitignore b/.gitignore index 7579f74..9668a62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ -vendor +/.idea +/coverage +/vendor +.phpunit.result.cache composer.lock diff --git a/.travis.yml b/.travis.yml index 0479fcd..c9e508b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,26 @@ language: php -php: - - 5.5 - - 5.6 - - 7.0 - - 7.1 +dist: trusty -before_script: composer install +matrix: + include: + - php: 7.2 + - php: 7.3 + - php: 7.4 + env: ANALYSIS='true' + - php: nightly + allow_failures: + - php: nightly -script: phpunit --coverage-text +before_script: + - if [[ "$ANALYSIS" == 'true' ]]; then composer require php-coveralls/php-coveralls:^2.1.0 ; fi + - composer install -n + +script: + - if [[ "$ANALYSIS" != 'true' ]]; then vendor/bin/phpunit ; fi + - if [[ "$ANALYSIS" == 'true' ]]; then vendor/bin/phpunit --coverage-clover clover.xml ; fi + - if [[ "$ANALYSIS" == 'true' ]]; then vendor/bin/phpcs ; fi + - if [[ "$ANALYSIS" == 'true' ]]; then vendor/bin/phpstan analyse src ; fi + +after_success: + - if [[ "$ANALYSIS" == 'true' ]]; then vendor/bin/php-coveralls --coverage_clover=clover.xml -v ; fi diff --git a/README.md b/README.md index 912a872..9f29b74 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Slim Framework HTTP Cache [![Build Status](https://travis-ci.org/slimphp/Slim-HttpCache.svg?branch=master)](https://travis-ci.org/slimphp/Slim-HttpCache) +[![Latest Stable Version](https://poser.pugx.org/slim/http-cache/v)](//packagist.org/packages/slim/http-cache) +[![License](https://poser.pugx.org/slim/http-cache/license)](https://packagist.org/packages/slim/http-cache) This repository contains a Slim Framework HTTP cache middleware and service provider. @@ -12,30 +14,38 @@ Via Composer $ composer require slim/http-cache ``` -Requires Slim 3.0.0 or newer. +Requires Slim 4.0.0 or newer. ## Usage ```php -$app = new \Slim\App(); +declare(strict_types=1); -// Register middleware +use Psr\Http\Message\ResponseInterface as Response; +use Psr\Http\Message\ServerRequestInterface as Request; + +require __DIR__.'/../vendor/autoload.php'; + +$app = \Slim\Factory\AppFactory::create(); + +// Register the http cache middleware. $app->add(new \Slim\HttpCache\Cache('public', 86400)); -// Fetch DI Container -$container = $app->getContainer(); +// Create the cache provider. +$cacheProvider = new \Slim\HttpCache\CacheProvider(); -// Register service provider -$container['cache'] = function () { - return new \Slim\HttpCache\CacheProvider(); -}; +// Register a route and let the closure callback inherit the cache provider. +$app->get( + '/', + function (Request $request, Response $response, array $args) use ($cacheProvider): Response { + // Use the cache provider. + $response = $cacheProvider->withEtag($response, 'abc'); -// Example route with ETag header -$app->get('/foo', function ($req, $res, $args) { - $resWithEtag = $this->cache->withEtag($res, 'abc'); + $response->getBody()->write('Hello world!'); - return $resWithEtag; -}); + return $response; + } +); $app->run(); ``` diff --git a/composer.json b/composer.json index 6d6a38a..bea90df 100644 --- a/composer.json +++ b/composer.json @@ -1,33 +1,51 @@ { - "name": "slim/http-cache", - "type": "library", - "description": "Slim Framework HTTP cache middleware and service provider", - "keywords": ["slim","framework","middleware","cache"], - "homepage": "http://slimframework.com", - "license": "MIT", - "authors": [ - { - "name": "Josh Lockhart", - "email": "hello@joshlockhart.com", - "homepage": "http://joshlockhart.com" - } - ], - "require": { - "php": ">=5.5.0", - "psr/http-message": "^1.0" - }, - "require-dev": { - "slim/slim": "^3.0", - "phpunit/phpunit": "^4.0" - }, - "autoload": { - "psr-4": { - "Slim\\HttpCache\\": "src" - } - }, - "autoload-dev": { - "psr-4": { - "Slim\\HttpCache\\Tests\\": "tests" - } + "name": "slim/http-cache", + "type": "library", + "description": "Slim Framework HTTP cache middleware and service provider", + "keywords": [ + "slim", + "framework", + "middleware", + "cache" + ], + "homepage": "https://www.slimframework.com", + "license": "MIT", + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "require": { + "php": "^7.2", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "slim/psr7": "^1.1", + "phpunit/phpunit": "^8.5", + "squizlabs/php_codesniffer": "^3.5", + "phpstan/phpstan": "^0.12.28" + }, + "autoload": { + "psr-4": { + "Slim\\HttpCache\\": "src" } + }, + "autoload-dev": { + "psr-4": { + "Slim\\HttpCache\\Tests\\": "tests" + } + }, + "scripts": { + "test": [ + "@phpcs", + "@phpstan", + "@phpunit" + ], + "phpcs": "phpcs", + "phpstan": "phpstan analyse src --memory-limit=-1", + "phpunit": "phpunit" + } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..94458ea --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,17 @@ + + + Slim coding standard + + + + + + + + + + + + src + tests + diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..036e5a5 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,3 @@ +parameters: + level: max + inferPrivatePropertyTypeFromConstructor: true diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8259dc4..1f1adc7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,25 +1,35 @@ - - - - tests/ + + ./tests/ - - - src/ + + ./src/ + + + diff --git a/src/Cache.php b/src/Cache.php index 6fe9e2f..4f0c147 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -1,10 +1,28 @@ type = $type; $this->maxAge = $maxAge; @@ -42,36 +60,37 @@ public function __construct($type = 'private', $maxAge = 86400, $mustRevalidate } /** - * Invoke cache middleware - * - * @param RequestInterface $request A PSR7 request object - * @param ResponseInterface $response A PSR7 response object - * @param callable $next The next middleware callable - * - * @return ResponseInterface A PSR7 response object + * {@inheritDoc} */ - public function __invoke(RequestInterface $request, ResponseInterface $response, callable $next) + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - $response = $next($request, $response); + $response = $handler->handle($request); // Cache-Control header if (!$response->hasHeader('Cache-Control')) { if ($this->maxAge === 0) { - $response = $response->withHeader('Cache-Control', sprintf( - '%s, no-cache%s', - $this->type, - $this->mustRevalidate ? ', must-revalidate' : '' - )); + $response = $response->withHeader( + 'Cache-Control', + sprintf( + '%s, no-cache%s', + $this->type, + $this->mustRevalidate ? ', must-revalidate' : '' + ) + ); } else { - $response = $response->withHeader('Cache-Control', sprintf( - '%s, max-age=%s%s', - $this->type, - $this->maxAge, - $this->mustRevalidate ? ', must-revalidate' : '' - )); + $response = $response->withHeader( + 'Cache-Control', + sprintf( + '%s, max-age=%s%s', + $this->type, + $this->maxAge, + $this->mustRevalidate ? ', must-revalidate' : '' + ) + ); } } + // ETag header and conditional GET check $etag = $response->getHeader('ETag'); $etag = reset($etag); @@ -81,7 +100,7 @@ public function __invoke(RequestInterface $request, ResponseInterface $response, if ($ifNoneMatch) { $etagList = preg_split('@\s*,\s*@', $ifNoneMatch); - if (in_array($etag, $etagList) || in_array('*', $etagList)) { + if (is_array($etagList) && (in_array($etag, $etagList) || in_array('*', $etagList))) { return $response->withStatus(304); } } @@ -92,7 +111,7 @@ public function __invoke(RequestInterface $request, ResponseInterface $response, $lastModified = $response->getHeaderLine('Last-Modified'); if ($lastModified) { - if (!is_integer($lastModified)) { + if (!is_numeric($lastModified)) { $lastModified = strtotime($lastModified); } diff --git a/src/CacheProvider.php b/src/CacheProvider.php index e39cbad..17d46af 100644 --- a/src/CacheProvider.php +++ b/src/CacheProvider.php @@ -1,37 +1,56 @@ withHeader('Cache-Control', $headerValue); @@ -40,11 +59,11 @@ public function allowCache(ResponseInterface $response, $type = 'private', $maxA /** * Disable client-side HTTP caching * - * @param ResponseInterface $response PSR7 response object + * @param ResponseInterface $response PSR7 response object * * @return ResponseInterface A new PSR7 response object with `Cache-Control` header */ - public function denyCache(ResponseInterface $response) + public function denyCache(ResponseInterface $response): ResponseInterface { return $response->withHeader('Cache-Control', 'no-store,no-cache'); } @@ -52,13 +71,13 @@ public function denyCache(ResponseInterface $response) /** * Add `Expires` header to PSR7 response object * - * @param ResponseInterface $response A PSR7 response object - * @param int|string $time A UNIX timestamp or a valid `strtotime()` string + * @param ResponseInterface $response A PSR7 response object + * @param int|string $time A UNIX timestamp or a valid `strtotime()` string * * @return ResponseInterface A new PSR7 response object with `Expires` header * @throws InvalidArgumentException if the expiration date cannot be parsed */ - public function withExpires(ResponseInterface $response, $time) + public function withExpires(ResponseInterface $response, $time): ResponseInterface { if (!is_integer($time)) { $time = strtotime($time); @@ -73,21 +92,21 @@ public function withExpires(ResponseInterface $response, $time) /** * Add `ETag` header to PSR7 response object * - * @param ResponseInterface $response A PSR7 response object - * @param string $value The ETag value - * @param string $type ETag type: "strong" or "weak" + * @param ResponseInterface $response A PSR7 response object + * @param string $value The ETag value + * @param string $type ETag type: "strong" or "weak" * * @return ResponseInterface A new PSR7 response object with `ETag` header * @throws InvalidArgumentException if the etag type is invalid */ - public function withEtag(ResponseInterface $response, $value, $type = 'strong') + public function withEtag(ResponseInterface $response, string $value, string $type = 'strong'): ResponseInterface { if (!in_array($type, ['strong', 'weak'])) { throw new InvalidArgumentException('Invalid etag type. Must be "strong" or "weak".'); } - $value = '"' . $value . '"'; + $value = '"'.$value.'"'; if ($type === 'weak') { - $value = 'W/' . $value; + $value = 'W/'.$value; } return $response->withHeader('ETag', $value); @@ -96,13 +115,13 @@ public function withEtag(ResponseInterface $response, $value, $type = 'strong') /** * Add `Last-Modified` header to PSR7 response object * - * @param ResponseInterface $response A PSR7 response object - * @param int|string $time A UNIX timestamp or a valid `strtotime()` string + * @param ResponseInterface $response A PSR7 response object + * @param int|string $time A UNIX timestamp or a valid `strtotime()` string * * @return ResponseInterface A new PSR7 response object with `Last-Modified` header * @throws InvalidArgumentException if the last modified date cannot be parsed */ - public function withLastModified(ResponseInterface $response, $time) + public function withLastModified(ResponseInterface $response, $time): ResponseInterface { if (!is_integer($time)) { $time = strtotime($time); diff --git a/tests/CacheProviderTest.php b/tests/CacheProviderTest.php index b7e6b68..8c3907f 100644 --- a/tests/CacheProviderTest.php +++ b/tests/CacheProviderTest.php @@ -1,15 +1,37 @@ createResponse(); + } + public function testAllowCache() { $cacheProvider = new CacheProvider(); - $res = $cacheProvider->allowCache(new Response(), 'private', 43200); + $res = $cacheProvider->allowCache($this->createResponse(), 'private', 43200); $cacheControl = $res->getHeaderLine('Cache-Control'); @@ -19,17 +41,36 @@ public function testAllowCache() public function testAllowCacheWithMustRevalidate() { $cacheProvider = new CacheProvider(); - $res = $cacheProvider->allowCache(new Response(), 'private', 43200, true); + $res = $cacheProvider->allowCache($this->createResponse(), 'private', 43200, true); $cacheControl = $res->getHeaderLine('Cache-Control'); $this->assertEquals('private, max-age=43200, must-revalidate', $cacheControl); } + public function testAllowCacheWithInvalidType() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid Cache-Control type. Must be "public" or "private".'); + + $cacheProvider = new CacheProvider(); + $cacheProvider->allowCache($this->createResponse(), 'unknown'); + } + + public function testAllowCacheWithMaxAgeAsString() + { + $cacheProvider = new CacheProvider(); + $res = $cacheProvider->allowCache($this->createResponse(), 'private', '+30 seconds'); + + $cacheControl = $res->getHeaderLine('Cache-Control'); + + $this->assertEquals('private, max-age=30', $cacheControl); + } + public function testDenyCache() { $cacheProvider = new CacheProvider(); - $res = $cacheProvider->denyCache(new Response()); + $res = $cacheProvider->denyCache($this->createResponse()); $cacheControl = $res->getHeaderLine('Cache-Control'); @@ -40,53 +81,91 @@ public function testWithExpires() { $now = time(); $cacheProvider = new CacheProvider(); - $res = $cacheProvider->withExpires(new Response(), $now); + $res = $cacheProvider->withExpires($this->createResponse(), $now); $expires = $res->getHeaderLine('Expires'); $this->assertEquals(gmdate('D, d M Y H:i:s T', $now), $expires); } + public function testWithExpiresTimeAsString() + { + $cacheProvider = new CacheProvider(); + $res = $cacheProvider->withExpires($this->createResponse(), '+30 seconds'); + $time = strtotime('+30 seconds'); + + $expires = $res->getHeaderLine('Expires'); + $this->assertEquals(gmdate('D, d M Y H:i:s T', $time), $expires); + } + + public function testWithExpiresTimeAsInvalidString() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expiration value could not be parsed with `strtotime()`.'); + + $cacheProvider = new CacheProvider(); + $cacheProvider->withExpires($this->createResponse(), 'this-is-not-a-valid-datetime'); + } + public function testWithETag() { $etag = 'abc'; $cacheProvider = new CacheProvider(); - $res = $cacheProvider->withEtag(new Response(), $etag); + $res = $cacheProvider->withEtag($this->createResponse(), $etag); $etagHeader = $res->getHeaderLine('ETag'); - $this->assertEquals('"' . $etag . '"', $etagHeader); + $this->assertEquals('"'.$etag.'"', $etagHeader); } public function testWithETagWeak() { $etag = 'abc'; $cacheProvider = new CacheProvider(); - $res = $cacheProvider->withEtag(new Response(), $etag, 'weak'); + $res = $cacheProvider->withEtag($this->createResponse(), $etag, 'weak'); $etagHeader = $res->getHeaderLine('ETag'); - $this->assertEquals('W/"' . $etag . '"', $etagHeader); + $this->assertEquals('W/"'.$etag.'"', $etagHeader); } - /** - * @expectedException \InvalidArgumentException - */ public function testWithETagInvalidType() { + $this->expectException(InvalidArgumentException::class); + $etag = 'abc'; $cacheProvider = new CacheProvider(); - $cacheProvider->withEtag(new Response(), $etag, 'bork'); + $cacheProvider->withEtag($this->createResponse(), $etag, 'bork'); } public function testWithLastModified() { $now = time(); $cacheProvider = new CacheProvider(); - $res = $cacheProvider->withLastModified(new Response(), $now); + $res = $cacheProvider->withLastModified($this->createResponse(), $now); $lastModified = $res->getHeaderLine('Last-Modified'); $this->assertEquals(gmdate('D, d M Y H:i:s T', $now), $lastModified); } + + public function testWithLastModifiedTimeAsString() + { + $cacheProvider = new CacheProvider(); + $res = $cacheProvider->withLastModified($this->createResponse(), '+30 seconds'); + $time = strtotime('+30 seconds'); + + $lastModified = $res->getHeaderLine('Last-Modified'); + + $this->assertEquals(gmdate('D, d M Y H:i:s T', $time), $lastModified); + } + + public function testWithLastModifiedTimeAsInvalidString() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Last Modified value could not be parsed with `strtotime()`.'); + + $cacheProvider = new CacheProvider(); + $cacheProvider->withLastModified($this->createResponse(), 'this-is-not-a-valid-datetime'); + } } diff --git a/tests/CacheTest.php b/tests/CacheTest.php index 110f732..e8b4d57 100644 --- a/tests/CacheTest.php +++ b/tests/CacheTest.php @@ -1,36 +1,74 @@ createServerRequest('GET', 'https://example.com:443/foo/bar?abc=123'); + } + + protected function createResponse(): ResponseInterface + { + $responseFactory = new ResponseFactory(); + + return $responseFactory->createResponse(); + } + + /** + * Create a request handler that simply assigns the $request that it receives to a public property + * of the returned response, so that we can then inspect that request. + * + * @param ResponseInterface|null $response + * + * @return RequestHandlerInterface + */ + protected function createRequestHandler(ResponseInterface $response = null): RequestHandlerInterface { - $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123'); - $headers = new Headers(); - $cookies = []; - $serverParams = []; - $body = new Body(fopen('php://temp', 'r+')); + $response = $response ?? $this->createResponse(); + + return new class($response) implements RequestHandlerInterface { + private $response; + + public function __construct(ResponseInterface $response) + { + $this->response = $response; + } - return new Request('GET', $uri, $headers, $cookies, $serverParams, $body); + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->response; + } + }; } public function testCacheControlHeader() { $cache = new Cache('public', 86400); $req = $this->requestFactory(); - $res = new Response(); - $next = function (Request $req, Response $res) { - return $res; - }; - $res = $cache($req, $res, $next); + + $res = $cache->process($req, $this->createRequestHandler()); $cacheControl = $res->getHeaderLine('Cache-Control'); @@ -41,11 +79,8 @@ public function testCacheControlHeaderWithMustRevalidate() { $cache = new Cache('private', 86400, true); $req = $this->requestFactory(); - $res = new Response(); - $next = function (Request $req, Response $res) { - return $res; - }; - $res = $cache($req, $res, $next); + + $res = $cache->process($req, $this->createRequestHandler()); $cacheControl = $res->getHeaderLine('Cache-Control'); @@ -56,11 +91,8 @@ public function testCacheControlHeaderWithZeroMaxAge() { $cache = new Cache('private', 0, false); $req = $this->requestFactory(); - $res = new Response(); - $next = function (Request $req, Response $res) { - return $res; - }; - $res = $cache($req, $res, $next); + + $res = $cache->process($req, $this->createRequestHandler()); $cacheControl = $res->getHeaderLine('Cache-Control'); @@ -71,11 +103,9 @@ public function testCacheControlHeaderDoesNotOverrideExistingHeader() { $cache = new Cache('public', 86400); $req = $this->requestFactory(); - $res = new Response(); - $next = function (Request $req, Response $res) { - return $res->withHeader('Cache-Control', 'no-cache,no-store'); - }; - $res = $cache($req, $res, $next); + + $res = $this->createResponse()->withHeader('Cache-Control', 'no-cache,no-store'); + $res = $cache->process($req, $this->createRequestHandler($res)); $cacheControl = $res->getHeaderLine('Cache-Control'); @@ -88,12 +118,11 @@ public function testLastModifiedWithCacheHit() $lastModified = gmdate('D, d M Y H:i:s T', $now + 86400); $ifModifiedSince = gmdate('D, d M Y H:i:s T', $now + 86400); $cache = new Cache('public', 86400); + $req = $this->requestFactory()->withHeader('If-Modified-Since', $ifModifiedSince); - $res = new Response(); - $next = function (Request $req, Response $res) use ($lastModified) { - return $res->withHeader('Last-Modified', $lastModified); - }; - $res = $cache($req, $res, $next); + + $res = $this->createResponse()->withHeader('Last-Modified', $lastModified); + $res = $cache->process($req, $this->createRequestHandler($res)); $this->assertEquals(304, $res->getStatusCode()); } @@ -105,11 +134,9 @@ public function testLastModifiedWithCacheHitAndNewerDate() $ifModifiedSince = gmdate('D, d M Y H:i:s T', $now + 172800); // <-- Newer date $cache = new Cache('public', 86400); $req = $this->requestFactory()->withHeader('If-Modified-Since', $ifModifiedSince); - $res = new Response(); - $next = function (Request $req, Response $res) use ($lastModified) { - return $res->withHeader('Last-Modified', $lastModified); - }; - $res = $cache($req, $res, $next); + + $res = $this->createResponse()->withHeader('Last-Modified', $lastModified); + $res = $cache->process($req, $this->createRequestHandler($res)); $this->assertEquals(304, $res->getStatusCode()); } @@ -121,11 +148,9 @@ public function testLastModifiedWithCacheHitAndOlderDate() $ifModifiedSince = gmdate('D, d M Y H:i:s T', $now); // <-- Older date $cache = new Cache('public', 86400); $req = $this->requestFactory()->withHeader('If-Modified-Since', $ifModifiedSince); - $res = new Response(); - $next = function (Request $req, Response $res) use ($lastModified) { - return $res->withHeader('Last-Modified', $lastModified); - }; - $res = $cache($req, $res, $next); + + $res = $this->createResponse()->withHeader('Last-Modified', $lastModified); + $res = $cache->process($req, $this->createRequestHandler($res)); $this->assertEquals(200, $res->getStatusCode()); } @@ -137,11 +162,9 @@ public function testLastModifiedWithCacheMiss() $ifModifiedSince = gmdate('D, d M Y H:i:s T', $now - 86400); $cache = new Cache('public', 86400); $req = $this->requestFactory()->withHeader('If-Modified-Since', $ifModifiedSince); - $res = new Response(); - $next = function (Request $req, Response $res) use ($lastModified) { - return $res->withHeader('Last-Modified', $lastModified); - }; - $res = $cache($req, $res, $next); + + $res = $this->createResponse()->withHeader('Last-Modified', $lastModified); + $res = $cache->process($req, $this->createRequestHandler($res)); $this->assertEquals(200, $res->getStatusCode()); } @@ -152,11 +175,9 @@ public function testETagWithCacheHit() $ifNoneMatch = 'abc'; $cache = new Cache('public', 86400); $req = $this->requestFactory()->withHeader('If-None-Match', $ifNoneMatch); - $res = new Response(); - $next = function (Request $req, Response $res) use ($etag) { - return $res->withHeader('ETag', $etag); - }; - $res = $cache($req, $res, $next); + + $res = $this->createResponse()->withHeader('Etag', $etag); + $res = $cache->process($req, $this->createRequestHandler($res)); $this->assertEquals(304, $res->getStatusCode()); } @@ -167,11 +188,9 @@ public function testETagWithCacheMiss() $ifNoneMatch = 'xyz'; $cache = new Cache('public', 86400); $req = $this->requestFactory()->withHeader('If-None-Match', $ifNoneMatch); - $res = new Response(); - $next = function (Request $req, Response $res) use ($etag) { - return $res->withHeader('ETag', $etag); - }; - $res = $cache($req, $res, $next); + + $res = $this->createResponse()->withHeader('Etag', $etag); + $res = $cache->process($req, $this->createRequestHandler($res)); $this->assertEquals(200, $res->getStatusCode()); } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 5330160..47fd7f1 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,2 +1,10 @@