From 809f84c77572f8cc8d5c248749c1f1d3dd31c229 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 19 Apr 2022 15:06:28 -0700 Subject: [PATCH] E2E_Core_ErrorTest - Demonstrate whether error-pages are well-formed --- CRM/Core/Page/FakeError.php | 59 ++++++++++++ CRM/Core/xml/Menu/Misc.xml | 7 ++ tests/phpunit/E2E/Core/ErrorTest.php | 137 +++++++++++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 CRM/Core/Page/FakeError.php create mode 100644 tests/phpunit/E2E/Core/ErrorTest.php diff --git a/CRM/Core/Page/FakeError.php b/CRM/Core/Page/FakeError.php new file mode 100644 index 0000000000..a98dda740c --- /dev/null +++ b/CRM/Core/Page/FakeError.php @@ -0,0 +1,59 @@ +decode(CRM_Utils_Request::retrieve('token', 'String')); + } + catch (\Exception $e) { + $claims = []; + } + + if (empty($claims['civi.fake-error'])) { + echo 'Hello world'; + return; + } + + switch ($claims['civi.fake-error']) { + case 'exception': + throw new \CRM_Core_Exception("This is a fake problem (exception)."); + + case 'fatal': + CRM_Core_Error::fatal('This is a fake problem (fatal).'); + break; + + case 'permission': + CRM_Utils_System::permissionDenied(); + break; + + default: + return 'Unrecognized error type.'; + } + } + +} diff --git a/CRM/Core/xml/Menu/Misc.xml b/CRM/Core/xml/Menu/Misc.xml index 5be55e8322..89b5f35e4f 100644 --- a/CRM/Core/xml/Menu/Misc.xml +++ b/CRM/Core/xml/Menu/Misc.xml @@ -178,6 +178,13 @@ QUnit administer CiviCRM + + civicrm/dev/fake-error + CRM_Core_Page_FakeError + Fake Error + true + *always allow* + civicrm/profile-editor/schema CRM_UF_Page_ProfileEditor::getSchemaJSON diff --git a/tests/phpunit/E2E/Core/ErrorTest.php b/tests/phpunit/E2E/Core/ErrorTest.php new file mode 100644 index 0000000000..90f7c4edda --- /dev/null +++ b/tests/phpunit/E2E/Core/ErrorTest.php @@ -0,0 +1,137 @@ + ['frontend://civicrm/dev/fake-error', 'fatal'], + 'frontend_exception' => ['frontend://civicrm/dev/fake-error', 'exception'], + 'frontend_permission' => ['frontend://civicrm/dev/fake-error', 'permission'], + 'backend_fatal' => ['backend://civicrm/dev/fake-error', 'fatal'], + 'backend_exception' => ['backend://civicrm/dev/fake-error', 'exception'], + 'backend_permission' => ['backend://civicrm/dev/fake-error', 'permission'], + ]; + } + + /** + * When showing an error screen, does the basic message come through? + * + * @param string $url + * Ex: 'frontend://civicrm/dev/fake-error' + * @param string $errorType + * Ex: 'fatal' or 'exception' + * @dataProvider getErrorTypes + */ + public function testErrorMessage(string $url, string $errorType) { + $this->skipIfNonCompliant(__FUNCTION__, $errorType); + $messages = [ + 'fatal' => '/This is a fake problem \(fatal\)/', + 'exception' => '/This is a fake problem \(exception\)/', + 'permission' => '/(You do not have permission|You are not authorized to access)/', + ]; + $response = $this->provokeError($url, $errorType); + $this->assertBodyRegexp($messages[$errorType] ?? 'Test error: Invalid error type', $response); + } + + /** + * When showing an error screen, does the HTTP status indicate an error? + * + * @param string $url + * Ex: 'frontend://civicrm/dev/fake-error' + * @param string $errorType + * Ex: 'fatal' or 'exception' + * @dataProvider getErrorTypes + */ + public function testErrorStatus(string $url, string $errorType) { + $this->skipIfNonCompliant(__FUNCTION__, $errorType); + $httpCodes = [ + 'fatal' => 500, + 'exception' => 500, + 'permission' => 403, + ]; + $response = $this->provokeError($url, $errorType); + $this->assertStatusCode($httpCodes[$errorType] ?? 'Test error: Invalid error type', $response); + } + + /** + * @param string $url + * Ex: 'frontend://civicrm/dev/fake-error' + * @param string $errorType + * Ex: 'fatal' or 'exception' + * @dataProvider getErrorTypes + */ + public function testErrorChrome(string $url, string $errorType) { + $this->skipIfNonCompliant(__FUNCTION__, $errorType); + $patterns = [ + 'Backdrop' => '/body class=\".*not-logged-in/', + 'Drupal' => '/body class=\".*not-logged-in/', + 'Drupal8' => '/body class=\".*not-logged-in/', + 'WordPress' => '/ role=.navigation./', + ]; + if (!isset($patterns[CIVICRM_UF])) { + $this->markTestIncomplete('testErrorChrome() cannot check for chrome on ' . CIVICRM_UF); + } + + $response = $this->provokeError($url, $errorType); + $this->assertContentType('text/html', $response); + $this->assertBodyRegexp($patterns[CIVICRM_UF], $response, 'Body should have some chrome/decoration'); + } + + /** + * @param string $url + * @param string $errorType + * @return \Psr\Http\Message\ResponseInterface + */ + protected function provokeError(string $url, string $errorType) { + $http = $this->createGuzzle(['http_errors' => FALSE]); + $jwt = \Civi::service('crypto.jwt')->encode([ + 'exp' => \CRM_Utils_Time::time() + 3600, + 'civi.fake-error' => $errorType, + ]); + return $http->get("$url?token=$jwt"); + } + + protected function skipIfNonCompliant($func, $errorType) { + if (getenv('FORCE_ALL')) { + return; + } + $sig = implode('_', [CIVICRM_UF, $func, $errorType]); + foreach ($this->nonCompliant as $nonCompliant) { + if (preg_match($nonCompliant, $sig)) { + $this->markTestIncomplete("Skipping non-compliant scenario ($sig matches $nonCompliant)"); + } + } + } + +} -- 2.25.1