From e323674adfdf93e40ef182767adb3bf58e98ff3b Mon Sep 17 00:00:00 2001 From: Frank Mullenger Date: Fri, 15 Feb 2019 11:30:51 +1300 Subject: [PATCH 1/8] NEW: Adding Guzzle dependency. --- composer.json | 3 ++- src/Traits/Fetcher.php | 46 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/Traits/Fetcher.php diff --git a/composer.json b/composer.json index fe8dedf..ead053a 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ ], "require": { "silverstripe/framework": "^4.0", - "silverstripe/versioned": "^1.0" + "silverstripe/versioned": "^1.0", + "guzzlehttp/guzzle": "^6.3.3" }, "require-dev": { "phpunit/phpunit": "^5.7", diff --git a/src/Traits/Fetcher.php b/src/Traits/Fetcher.php new file mode 100644 index 0000000..b46bd38 --- /dev/null +++ b/src/Traits/Fetcher.php @@ -0,0 +1,46 @@ +clientConfig, $extraConfig); + } + + /** + * Fetch a response for a URL using Guzzle client. + * + * @param string $url + * @param array|null $extraConfig Extra configuration + * @return ResponseInterface + */ + public function fetchResponse(string $url, ? array $extraConfig = []) : ? ResponseInterface + { + $config = $this->getClientConfig($extraConfig); + $client = new Client($config); + return $client->get($url); + } +} From df59195634886bc9b2aa6525921cf861e8d82e91 Mon Sep 17 00:00:00 2001 From: Frank Mullenger Date: Fri, 15 Feb 2019 11:56:32 +1300 Subject: [PATCH 2/8] NEW: Session check. --- readme.md | 1 + src/Checks/SessionCheck.php | 86 +++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/Checks/SessionCheck.php diff --git a/readme.md b/readme.md index 1fcb7ea..f0be5cb 100644 --- a/readme.md +++ b/readme.md @@ -91,6 +91,7 @@ SilverStripe\EnvironmentCheck\EnvironmentCheckSuite: * `ExternalURLCheck`: Checks that one or more URLs are reachable via HTTP. * `SMTPConnectCheck`: Checks if the SMTP connection configured through PHP.ini works as expected. * `SolrIndexCheck`: Checks if the Solr cores of given class are available. + * `SessionCheck`: Checks that a given URL does not generate a session. ## Monitoring Checks diff --git a/src/Checks/SessionCheck.php b/src/Checks/SessionCheck.php new file mode 100644 index 0000000..6884b95 --- /dev/null +++ b/src/Checks/SessionCheck.php @@ -0,0 +1,86 @@ +url = $url; + $this->clientConfig = [ + 'base_uri' => Director::absoluteBaseURL(), + 'timeout' => 10.0, + ]; + } + + /** + * Check that the response for URL does not create a session + * + * @return array + */ + public function check(): array + { + $response = $this->fetchResponse($this->url); + $cookie = $this->getCookie($response); + $fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); + + if ($cookie) { + return [ + EnvironmentCheck::ERROR, + "Sessions are being set for {$fullURL} : Set-Cookie => " . $cookie, + ]; + } + return [ + EnvironmentCheck::OK, + "Sessions are not being created for {$fullURL} 👍", + ]; + } + + /** + * Get PHPSESSID or SECSESSID cookie set from the response if it exists. + * + * @param ResponseInterface $response + * @return string|null Cookie contents or null if it doesn't exist + */ + public function getCookie(ResponseInterface $response): ?string + { + $result = null; + $cookies = $response->getHeader('Set-Cookie'); + + foreach ($cookies as $cookie) { + if (strpos($cookie, 'SESSID') !== false) { + $result = $cookie; + } + } + return $result; + } +} From c1bfd5640db7e3a8ab08aa0ef9a754757a0c3204 Mon Sep 17 00:00:00 2001 From: Frank Mullenger Date: Fri, 15 Feb 2019 11:57:14 +1300 Subject: [PATCH 3/8] NEW: Cache headers check. --- readme.md | 2 + src/Checks/CacheHeadersCheck.php | 220 +++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 src/Checks/CacheHeadersCheck.php diff --git a/readme.md b/readme.md index f0be5cb..b034876 100644 --- a/readme.md +++ b/readme.md @@ -92,6 +92,8 @@ SilverStripe\EnvironmentCheck\EnvironmentCheckSuite: * `SMTPConnectCheck`: Checks if the SMTP connection configured through PHP.ini works as expected. * `SolrIndexCheck`: Checks if the Solr cores of given class are available. * `SessionCheck`: Checks that a given URL does not generate a session. + * `CacheHeadersCheck`: Check cache headers in response for directives that must either be included or excluded as well + checking for existence of ETag. ## Monitoring Checks diff --git a/src/Checks/CacheHeadersCheck.php b/src/Checks/CacheHeadersCheck.php new file mode 100644 index 0000000..a70effc --- /dev/null +++ b/src/Checks/CacheHeadersCheck.php @@ -0,0 +1,220 @@ +url = $url; + $this->mustInclude = $mustInclude; + $this->mustExclude = $mustExclude; + + $this->clientConfig = [ + 'base_uri' => Director::absoluteBaseURL(), + 'timeout' => 10.0, + ]; + + // Using a validation result to capture messages + $this->result = new ValidationResult(); + } + + /** + * Check that correct caching headers are present. + * + * @return void + */ + public function check() + { + $response = $this->fetchResponse($this->url); + $fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); + if ($response === null) { + return [ + EnvironmentCheck::ERROR, + "Cache headers check request failed for $fullURL", + ]; + } + + //Check that Etag exists + $this->checkEtag($response); + + // Check Cache-Control settings + $this->checkCacheControl($response); + + if ($this->result->isValid()) { + return [ + EnvironmentCheck::OK, + $this->getMessage(), + ]; + } else { + // @todo Ability to return a warning + return [ + EnvironmentCheck::ERROR, + $this->getMessage(), + ]; + } + } + + /** + * Collate messages from ValidationResult so that it is clear which parts + * of the check passed and which failed. + * + * @return string + */ + private function getMessage(): string + { + $ret = ''; + // Filter good messages + $goodTypes = [ValidationResult::TYPE_GOOD, ValidationResult::TYPE_INFO]; + $good = array_filter( + $this->result->getMessages(), + function ($val, $key) use ($goodTypes) { + if (in_array($val['messageType'], $goodTypes)) { + return true; + } + return false; + }, + ARRAY_FILTER_USE_BOTH + ); + if (!empty($good)) { + $ret .= "GOOD: " . implode('; ', array_column($good, 'message')) . " "; + } + + // Filter bad messages + $badTypes = [ValidationResult::TYPE_ERROR, ValidationResult::TYPE_WARNING]; + $bad = array_filter( + $this->result->getMessages(), + function ($val, $key) use ($badTypes) { + if (in_array($val['messageType'], $badTypes)) { + return true; + } + return false; + }, + ARRAY_FILTER_USE_BOTH + ); + if (!empty($bad)) { + $ret .= "BAD: " . implode('; ', array_column($bad, 'message')); + } + return $ret; + } + + /** + * Check that ETag header exists + * + * @param ResponseInterface $response + * @return void + */ + private function checkEtag(ResponseInterface $response): void + { + $eTag = $response->getHeaderLine('ETag'); + $fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); + + if ($eTag) { + $this->result->addMessage( + "$fullURL includes an Etag header in response", + ValidationResult::TYPE_GOOD + ); + return; + } + $this->result->addError( + "$fullURL is missing an Etag header", + ValidationResult::TYPE_WARNING + ); + } + + /** + * Check that the correct header settings are either included or excluded. + * + * @param ResponseInterface $response + * @return void + */ + private function checkCacheControl(ResponseInterface $response): void + { + $cacheControl = $response->getHeaderLine('Cache-Control'); + $vals = array_map('trim', explode(',', $cacheControl)); + $fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); + + // All entries from must contain should be present + if ($this->mustInclude == array_intersect($this->mustInclude, $vals)) { + $matched = implode(",", $this->mustInclude); + $this->result->addMessage( + "$fullURL includes all settings: {$matched}", + ValidationResult::TYPE_GOOD + ); + } else { + $missing = implode(",", array_diff($this->mustInclude, $vals)); + $this->result->addError( + "$fullURL is excluding some settings: {$missing}" + ); + } + + // All entries from must exclude should not be present + if (empty(array_intersect($this->mustExclude, $vals))) { + $missing = implode(",", $this->mustExclude); + $this->result->addMessage( + "$fullURL excludes all settings: {$missing}", + ValidationResult::TYPE_GOOD + ); + } else { + $matched = implode(",", array_intersect($this->mustExclude, $vals)); + $this->result->addError( + "$fullURL is including some settings: {$matched}" + ); + } + } +} From 27994e51faaa8940b5582bfe4a7560975091e44f Mon Sep 17 00:00:00 2001 From: Frank Mullenger Date: Fri, 15 Feb 2019 13:42:41 +1300 Subject: [PATCH 4/8] FIX: Removing return type declarations. --- src/Checks/CacheHeadersCheck.php | 6 +++--- src/Checks/SessionCheck.php | 4 ++-- src/Traits/Fetcher.php | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Checks/CacheHeadersCheck.php b/src/Checks/CacheHeadersCheck.php index a70effc..4004ca4 100644 --- a/src/Checks/CacheHeadersCheck.php +++ b/src/Checks/CacheHeadersCheck.php @@ -116,7 +116,7 @@ class CacheHeadersCheck implements EnvironmentCheck * * @return string */ - private function getMessage(): string + private function getMessage() { $ret = ''; // Filter good messages @@ -159,7 +159,7 @@ class CacheHeadersCheck implements EnvironmentCheck * @param ResponseInterface $response * @return void */ - private function checkEtag(ResponseInterface $response): void + private function checkEtag(ResponseInterface $response) { $eTag = $response->getHeaderLine('ETag'); $fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); @@ -183,7 +183,7 @@ class CacheHeadersCheck implements EnvironmentCheck * @param ResponseInterface $response * @return void */ - private function checkCacheControl(ResponseInterface $response): void + private function checkCacheControl(ResponseInterface $response) { $cacheControl = $response->getHeaderLine('Cache-Control'); $vals = array_map('trim', explode(',', $cacheControl)); diff --git a/src/Checks/SessionCheck.php b/src/Checks/SessionCheck.php index 6884b95..d7779da 100644 --- a/src/Checks/SessionCheck.php +++ b/src/Checks/SessionCheck.php @@ -47,7 +47,7 @@ class SessionCheck implements EnvironmentCheck * * @return array */ - public function check(): array + public function check() { $response = $this->fetchResponse($this->url); $cookie = $this->getCookie($response); @@ -71,7 +71,7 @@ class SessionCheck implements EnvironmentCheck * @param ResponseInterface $response * @return string|null Cookie contents or null if it doesn't exist */ - public function getCookie(ResponseInterface $response): ?string + public function getCookie(ResponseInterface $response) { $result = null; $cookies = $response->getHeader('Set-Cookie'); diff --git a/src/Traits/Fetcher.php b/src/Traits/Fetcher.php index b46bd38..f34662c 100644 --- a/src/Traits/Fetcher.php +++ b/src/Traits/Fetcher.php @@ -25,7 +25,7 @@ trait Fetcher * @param array $extraConfig * @return array */ - private function getClientConfig(array $extraConfig = []) : array + private function getClientConfig(array $extraConfig = []) { return array_merge($this->clientConfig, $extraConfig); } @@ -37,7 +37,7 @@ trait Fetcher * @param array|null $extraConfig Extra configuration * @return ResponseInterface */ - public function fetchResponse(string $url, ? array $extraConfig = []) : ? ResponseInterface + public function fetchResponse(string $url, ? array $extraConfig = []) { $config = $this->getClientConfig($extraConfig); $client = new Client($config); From 90f0556ca479fbdd3cc7603c5fbfcb75aa0e4e6a Mon Sep 17 00:00:00 2001 From: Frank Mullenger Date: Fri, 15 Feb 2019 13:52:58 +1300 Subject: [PATCH 5/8] FIX: Removal of optional param. --- src/Traits/Fetcher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/Fetcher.php b/src/Traits/Fetcher.php index f34662c..afba398 100644 --- a/src/Traits/Fetcher.php +++ b/src/Traits/Fetcher.php @@ -37,7 +37,7 @@ trait Fetcher * @param array|null $extraConfig Extra configuration * @return ResponseInterface */ - public function fetchResponse(string $url, ? array $extraConfig = []) + public function fetchResponse(string $url, array $extraConfig = []) { $config = $this->getClientConfig($extraConfig); $client = new Client($config); From 2674beb94aa898365fe1ae426f13af240022bfb1 Mon Sep 17 00:00:00 2001 From: Frank Mullenger Date: Fri, 15 Feb 2019 13:58:08 +1300 Subject: [PATCH 6/8] FIX: Line length. --- src/Checks/CacheHeadersCheck.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Checks/CacheHeadersCheck.php b/src/Checks/CacheHeadersCheck.php index 4004ca4..eb8f858 100644 --- a/src/Checks/CacheHeadersCheck.php +++ b/src/Checks/CacheHeadersCheck.php @@ -15,7 +15,7 @@ use SilverStripe\EnvironmentCheck\EnvironmentCheck; * also must be excluded from Cache-Control headers in response. Also checks for * existence of ETag. * - * @example SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck("/", ["must-revalidate", "max-age=120"], ["no-store"]) + * @example SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck("/",["must-revalidate", "max-age=120"],["no-store"]) * @package environmentcheck */ class CacheHeadersCheck implements EnvironmentCheck From 578c73e6ce226faa43ef0c5e3b4a39083edefe99 Mon Sep 17 00:00:00 2001 From: Frank Mullenger Date: Wed, 20 Mar 2019 09:38:58 +1300 Subject: [PATCH 7/8] NEW: Refactor to provide unit tests for session check. --- _config/injector.yml | 8 ++++ src/Checks/SessionCheck.php | 21 ++------- src/Services/ClientFactory.php | 49 ++++++++++++++++++++ src/Traits/Fetcher.php | 43 +++++++++-------- tests/Checks/SessionCheckTest.php | 76 +++++++++++++++++++++++++++++++ 5 files changed, 160 insertions(+), 37 deletions(-) create mode 100644 src/Services/ClientFactory.php create mode 100644 tests/Checks/SessionCheckTest.php diff --git a/_config/injector.yml b/_config/injector.yml index 2b35577..6885610 100644 --- a/_config/injector.yml +++ b/_config/injector.yml @@ -22,3 +22,11 @@ SilverStripe\Core\Injector\Injector: class: SilverStripe\EnvironmentCheck\Checks\SolrIndexCheck URLCheck: class: SilverStripe\EnvironmentCheck\Checks\URLCheck + EnvCheckClient: + factory: 'SilverStripe\EnvironmentCheck\Services\ClientFactory' + constructor: + timeout: 10.0 + +SilverStripe\EnvironmentCheck\Checks\SessionCheck: + dependencies: + client: '%$EnvCheckClient' diff --git a/src/Checks/SessionCheck.php b/src/Checks/SessionCheck.php index d7779da..47a95bd 100644 --- a/src/Checks/SessionCheck.php +++ b/src/Checks/SessionCheck.php @@ -2,10 +2,7 @@ namespace SilverStripe\EnvironmentCheck\Checks; -use SilverStripe\Control\Director; -use SilverStripe\Control\Controller; use Psr\Http\Message\ResponseInterface; -use SilverStripe\Core\Config\Configurable; use SilverStripe\EnvironmentCheck\Traits\Fetcher; use SilverStripe\EnvironmentCheck\EnvironmentCheck; @@ -17,16 +14,8 @@ use SilverStripe\EnvironmentCheck\EnvironmentCheck; */ class SessionCheck implements EnvironmentCheck { - use Configurable; use Fetcher; - /** - * URL to check - * - * @var string - */ - protected $url; - /** * Set up check with URL * @@ -35,11 +24,7 @@ class SessionCheck implements EnvironmentCheck */ public function __construct($url = '') { - $this->url = $url; - $this->clientConfig = [ - 'base_uri' => Director::absoluteBaseURL(), - 'timeout' => 10.0, - ]; + $this->setURL($url); } /** @@ -49,9 +34,9 @@ class SessionCheck implements EnvironmentCheck */ public function check() { - $response = $this->fetchResponse($this->url); + $response = $this->client->get($this->getURL()); $cookie = $this->getCookie($response); - $fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); + $fullURL = $this->getURL(); if ($cookie) { return [ diff --git a/src/Services/ClientFactory.php b/src/Services/ClientFactory.php new file mode 100644 index 0000000..ef066f0 --- /dev/null +++ b/src/Services/ClientFactory.php @@ -0,0 +1,49 @@ +getConfig($params)); + } + + /** + * Merge config provided from yaml with default config + * + * @param array $overrides + * @return array + */ + public function getConfig(array $overrides) + { + return array_merge( + $this->config()->get('default_config'), + $overrides + ); + } +} diff --git a/src/Traits/Fetcher.php b/src/Traits/Fetcher.php index afba398..60dd5c3 100644 --- a/src/Traits/Fetcher.php +++ b/src/Traits/Fetcher.php @@ -2,45 +2,50 @@ namespace SilverStripe\EnvironmentCheck\Traits; -use GuzzleHttp\Client; -use Psr\Http\Message\ResponseInterface; +use SilverStripe\Control\Director; /** - * Simple helper for fetching responses using Guzzle client. + * Simple helper for env checks which require HTTP clients. * * @package environmentcheck */ trait Fetcher { /** - * Configuration for the Guzzle client + * Client for making requests, set vi Injector. * - * @var array + * @see SilverStripe\EnvironmentCheck\Services + * + * @var GuzzleHttp\Client */ - protected $clientConfig = []; + public $client = null; /** - * Merges configuration arrays and returns the result + * Absolute URL for requests. * - * @param array $extraConfig - * @return array + * @var string */ - private function getClientConfig(array $extraConfig = []) + protected $url; + + /** + * Set URL for requests. + * + * @param string $url Relative URL + * @return self + */ + public function setURL($url) { - return array_merge($this->clientConfig, $extraConfig); + $this->url = Director::absoluteURL($url); + return $this; } /** - * Fetch a response for a URL using Guzzle client. + * Getter for URL * - * @param string $url - * @param array|null $extraConfig Extra configuration - * @return ResponseInterface + * @return string */ - public function fetchResponse(string $url, array $extraConfig = []) + public function getURL() { - $config = $this->getClientConfig($extraConfig); - $client = new Client($config); - return $client->get($url); + return $this->url; } } diff --git a/tests/Checks/SessionCheckTest.php b/tests/Checks/SessionCheckTest.php new file mode 100644 index 0000000..a84bcef --- /dev/null +++ b/tests/Checks/SessionCheckTest.php @@ -0,0 +1,76 @@ +sessionCheck = new SessionCheck('/'); + } + + /** + * Env check reports error when session cookies are being set. + * + * @return void + */ + public function testSessionSet() + { + // Create a mock and queue two responses. + $mock = new MockHandler([ + new Response(200, ['Set-Cookie' => 'PHPSESSID:foo']), + new Response(200, ['Set-Cookie' => 'SECSESSID:bar']) + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $this->sessionCheck->client = $client; + + // Check for PHPSESSID + $this->assertContains(EnvironmentCheck::ERROR, $this->sessionCheck->check()); + + // Check for SECSESSID + $this->assertContains(EnvironmentCheck::ERROR, $this->sessionCheck->check()); + } + + /** + * Env check responds OK when no session cookies are set in response. + * + * @return void + */ + public function testSessionNotSet() + { + // Create a mock and queue two responses. + $mock = new MockHandler([ + new Response(200) + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $this->sessionCheck->client = $client; + + $this->assertContains(EnvironmentCheck::OK, $this->sessionCheck->check()); + } +} From 8f7f8570f2b6a66883efcd0d11ff7505a5dbada1 Mon Sep 17 00:00:00 2001 From: Frank Mullenger Date: Wed, 20 Mar 2019 10:21:00 +1300 Subject: [PATCH 8/8] NEW: Refactor to provide unit tests for cache headers check. --- _config/injector.yml | 3 + src/Checks/CacheHeadersCheck.php | 25 ++---- tests/Checks/CacheHeadersCheckTest.php | 101 +++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 19 deletions(-) create mode 100644 tests/Checks/CacheHeadersCheckTest.php diff --git a/_config/injector.yml b/_config/injector.yml index 6885610..b92a2ab 100644 --- a/_config/injector.yml +++ b/_config/injector.yml @@ -30,3 +30,6 @@ SilverStripe\Core\Injector\Injector: SilverStripe\EnvironmentCheck\Checks\SessionCheck: dependencies: client: '%$EnvCheckClient' +SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck: + dependencies: + client: '%$EnvCheckClient' diff --git a/src/Checks/CacheHeadersCheck.php b/src/Checks/CacheHeadersCheck.php index eb8f858..f42f174 100644 --- a/src/Checks/CacheHeadersCheck.php +++ b/src/Checks/CacheHeadersCheck.php @@ -20,16 +20,8 @@ use SilverStripe\EnvironmentCheck\EnvironmentCheck; */ class CacheHeadersCheck implements EnvironmentCheck { - use Configurable; use Fetcher; - /** - * URL to check - * - * @var string - */ - protected $url; - /** * Settings that must be included in the Cache-Control header * @@ -61,17 +53,9 @@ class CacheHeadersCheck implements EnvironmentCheck */ public function __construct($url = '', $mustInclude = [], $mustExclude = []) { - $this->url = $url; + $this->setURL($url); $this->mustInclude = $mustInclude; $this->mustExclude = $mustExclude; - - $this->clientConfig = [ - 'base_uri' => Director::absoluteBaseURL(), - 'timeout' => 10.0, - ]; - - // Using a validation result to capture messages - $this->result = new ValidationResult(); } /** @@ -81,8 +65,11 @@ class CacheHeadersCheck implements EnvironmentCheck */ public function check() { - $response = $this->fetchResponse($this->url); - $fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url); + // Using a validation result to capture messages + $this->result = new ValidationResult(); + + $response = $this->client->get($this->getURL()); + $fullURL = $this->getURL(); if ($response === null) { return [ EnvironmentCheck::ERROR, diff --git a/tests/Checks/CacheHeadersCheckTest.php b/tests/Checks/CacheHeadersCheckTest.php new file mode 100644 index 0000000..9fe0a7a --- /dev/null +++ b/tests/Checks/CacheHeadersCheckTest.php @@ -0,0 +1,101 @@ + 'must-revalidate', 'ETag' => '123']), + new Response(200, ['Cache-Control' =>'no-cache', 'ETag' => '123']), + new Response(200, ['ETag' => '123']), + new Response(200, ['Cache-Control' => 'must-revalidate, private', 'ETag' => '123']), + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + + $cacheHeadersCheck = new CacheHeadersCheck('/', ['must-revalidate']); + $cacheHeadersCheck->client = $client; + + // Run checks for each response above + $this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check()); + $this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check()); + $this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check()); + $this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check()); + } + + /** + * Test that directives that must be excluded, are. + * + * @return void + */ + public function testMustExclude() + { + // Create a mock and queue responses + $mock = new MockHandler([ + new Response(200, ['Cache-Control' => 'must-revalidate', 'ETag' => '123']), + new Response(200, ['Cache-Control' =>'no-cache', 'ETag' => '123']), + new Response(200, ['ETag' => '123']), + new Response(200, ['Cache-Control' =>'private, no-store', ' ETag' => '123']), + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + + $cacheHeadersCheck = new CacheHeadersCheck('/', [], ["no-store", "no-cache", "private"]); + $cacheHeadersCheck->client = $client; + + // Run checks for each response above + $this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check()); + $this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check()); + $this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check()); + $this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check()); + } + + /** + * Test that Etag header must exist in response. + * + * @return void + */ + public function testEtag() + { + // Create a mock and queue responses + $mock = new MockHandler([ + new Response(200, ['Cache-Control' => 'must-revalidate', 'ETag' => '123']), + new Response(200, ['Cache-Control' =>'no-cache']), + new Response(200, ['ETag' => '123']), + new Response(200, []), + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + + $cacheHeadersCheck = new CacheHeadersCheck('/'); + $cacheHeadersCheck->client = $client; + + // Run checks for each response above + $this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check()); + $this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check()); + $this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check()); + $this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check()); + } +}