From 691c30b48ce1680e7690f0b8beb310860866f061 Mon Sep 17 00:00:00 2001 From: Joseph Zhao Date: Tue, 23 Jul 2024 16:15:50 +1000 Subject: [PATCH] Add a new test base for testing the Canary site --- composer.json | 6 + phpunit/integration/BrowserKitTrait.php | 158 ++++++++++++++++++ .../Functional/PasswordLengthTest.php | 56 +++++++ phpunit/integration/DrupalTrait.php | 122 ++++++++++++++ phpunit/integration/ExistingSiteBase.php | 109 ++++++++++++ phpunit/phpunit.xml | 5 +- 6 files changed, 455 insertions(+), 1 deletion(-) create mode 100644 phpunit/integration/BrowserKitTrait.php create mode 100644 phpunit/integration/Canary/Security/Functional/PasswordLengthTest.php create mode 100644 phpunit/integration/DrupalTrait.php create mode 100644 phpunit/integration/ExistingSiteBase.php diff --git a/composer.json b/composer.json index cd4d5837..13812e79 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,12 @@ "phploc/phploc": "^7.0", "phpmd/phpmd": "^2.10" }, + "autoload": { + "psr-4": { + "GovCMS\\Tests\\": ["phpunit/tests"], + "GovCMS\\Tests\\Integration\\": ["phpunit/integration"] + } + }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true diff --git a/phpunit/integration/BrowserKitTrait.php b/phpunit/integration/BrowserKitTrait.php new file mode 100644 index 00000000..f9162094 --- /dev/null +++ b/phpunit/integration/BrowserKitTrait.php @@ -0,0 +1,158 @@ +mink->getSession($name); + } + + /** + * Returns the driver instance. + * + * @return \Behat\Mink\Driver\DriverInterface + * The driver instance. + */ + protected function getDriverInstance(): DriverInterface { + if (!isset($this->driver)) { + $client = new DrupalTestBrowser(); + $client->followMetaRefresh(); + $this->driver = new BrowserKitDriver($client); + } + return $this->driver; + } + + /** + * Gets the current page. + * + * @return \Behat\Mink\Element\DocumentElement + * The current page. + */ + protected function getCurrentPage(): DocumentElement { + return $this->getSession()->getPage(); + } + + /** + * Gets the content of the current page. + * + * @return string + * The content of the current page. + */ + protected function getCurrentPageContent(): string { + return $this->getCurrentPage()->getContent(); + } + + /** + * Sets-up a Mink session. + */ + protected function setupMinkSession(): void + { + if (empty($this->baseUrl)) { + $this->baseUrl = getenv('SIMPLETEST_BASE_URL') ?: 'http://localhost'; + } + + $driver = $this->getDriverInstance(); + $selectors_handler = new SelectorsHandler([ + 'hidden_field_selector' => new HiddenFieldSelector(), + ]); + $session = new Session($driver, $selectors_handler); + $this->mink = new Mink([ + 'default' => $session, + ]); + $this->mink->setDefaultSessionName('default'); + $session->start(); + + // Create the artifacts directory if necessary. + $output_dir = getenv('BROWSERTEST_OUTPUT_DIRECTORY'); + if ($output_dir && !is_dir($output_dir)) { + mkdir($output_dir, 0777, true); + } + + if ($driver instanceof BrowserKitDriver) { + // Inject a Guzzle middleware to generate debug output for every request + // performed in the test. + + // Turn off curl timeout. Having a timeout is not a problem in a normal + // test running, but it is a problem when debugging. Also, disable SSL + // peer verification so that testing under HTTPS always works. + $handler_stack = HandlerStack::create(); + $handler_stack->push($this->getResponseLogHandler()); + $client = new Client(['timeout' => null, 'verify' => false, 'handler' => $handler_stack]); + $driver->getClient()->setClient($client); + } + + // According to the W3C WebDriver specification a cookie can only be set if + // the cookie domain is equal to the domain of the active document. When the + // browser starts up the active document is not our domain but 'about:blank' + // or similar. To be able to set our User-Agent and Xdebug cookies at the + // start of the test we now do a request to the front page so the active + // document matches the domain. + // @see https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie + // @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=20975 + $this->visit($this->baseUrl . '/core/misc/druplicon.png'); + + // Copies cookies from the current environment, for example, XDEBUG_SESSION + // in order to support Xdebug. + $cookies = $this->extractCookiesFromRequest(Request::createFromGlobals()); + if (isset($cookies['XDEBUG_SESSION'][0])) { + $session->setCookie('XDEBUG_SESSION', $cookies['XDEBUG_SESSION'][0]); + } + } + + /** + * Stops the Mink session. + */ + protected function tearDownMinkSession(): void { + $this->getSession()->stop(); + // Avoid leaking memory in test cases (which are retained for a long time) + // by removing references to all the things. + $this->mink = null; + } + + /** + * Visits the specified URL. + * + * @param string $url + * The URL to visit. If the URL does not contain a scheme, the base URL will be prepended. + */ + protected function visit(string $url): void { + if (!parse_url($url, PHP_URL_SCHEME)) { + $url = $this->baseUrl . $url; + } + $this->getSession()->visit($url); + } + +} diff --git a/phpunit/integration/Canary/Security/Functional/PasswordLengthTest.php b/phpunit/integration/Canary/Security/Functional/PasswordLengthTest.php new file mode 100644 index 00000000..4f60b2cd --- /dev/null +++ b/phpunit/integration/Canary/Security/Functional/PasswordLengthTest.php @@ -0,0 +1,56 @@ +drupalCreateUser([ + 'administer users', + ]); + $this->drupalLogin($user); + + // Test user creation page for valid password length. + $name = $this->randomMachineName(); + $edit = [ + 'name' => $name, + 'mail' => $this->randomMachineName() . '@example.com', + 'pass[pass1]' => $pass = $this->randomString(13), + 'pass[pass2]' => $pass, + 'notify' => FALSE, + ]; + + $this->drupalGet('admin/people/create'); + $this->submitForm($edit, 'Create new account'); + $this->assertSession()->pageTextContains('The password does not satisfy the password policies.'); + $this->assertSession()->pageTextContains('Password length must be at least 14 characters.'); + } + + /** + * {@inheritdoc} + */ + #[Override] + protected function setUp(): void { + parent::setUp(); + // Set up the test here. + } + +} \ No newline at end of file diff --git a/phpunit/integration/DrupalTrait.php b/phpunit/integration/DrupalTrait.php new file mode 100644 index 00000000..18915a22 --- /dev/null +++ b/phpunit/integration/DrupalTrait.php @@ -0,0 +1,122 @@ +baseUrl = $base_url; + + // Include the class loader. + $this->classLoader = require '/app/web/autoload.php'; + + // Parse the base URL. + $parsedUrl = parse_url($this->baseUrl); + $host = $parsedUrl['host'] . (isset($parsedUrl['port']) ? ':' . $parsedUrl['port'] : ''); + $path = isset($parsedUrl['path']) ? rtrim($parsedUrl['path'], '/') : ''; + $port = $parsedUrl['port'] ?? 80; + + // Set up server variables. + $server = [ + 'HTTP_HOST' => $host, + 'SERVER_PORT' => $port, + 'REQUEST_URI' => $path . '/', + 'SCRIPT_FILENAME' => $path . '/index.php', + 'SCRIPT_NAME' => $path . '/index.php', + 'PHP_SELF' => $path . '/index.php', + ]; + + // Adjust server variables for HTTPS if necessary. + if ($parsedUrl['scheme'] === 'https') { + $server['HTTPS'] = 'on'; + } + + // Create the request object. + $request = Request::create($this->baseUrl . '/', 'GET', [], [], [], $server); + + // Initialize the Drupal kernel. + $this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE, DRUPAL_ROOT); + + // The DrupalKernel only initializes the environment once which is where + // it sets the Drupal error handler. We can therefore only restore it + // once. + if (!static::$restoredErrorHandler) { + restore_error_handler(); + restore_exception_handler(); + static::$restoredErrorHandler = true; + } + + // Change the working directory to Drupal root and boot the kernel. + chdir(DRUPAL_ROOT); + $this->kernel->boot(); + $this->kernel->preHandle($request); + $this->container = $this->kernel->getContainer(); + } + + /** + * Delete test data. + */ + protected function tearDownDrupal(): void { + // Invalidate cache. + \Drupal::service('cache_tags.invalidator')->resetChecksums(); + + // Destroy the testing kernel. + if (isset($this->kernel)) { + $this->kernel->shutdown(); + } + + \Drupal::unsetContainer(); + $this->container = NULL; + } + +} diff --git a/phpunit/integration/ExistingSiteBase.php b/phpunit/integration/ExistingSiteBase.php new file mode 100644 index 00000000..bdc59fee --- /dev/null +++ b/phpunit/integration/ExistingSiteBase.php @@ -0,0 +1,109 @@ +setupMinkSession(); + $this->setupDrupal(); + + // Check if the Shield module is enabled and disable it if it is. + $shieldConfig = $this->config('shield.settings'); + $this->wasShieldEnabled = $shieldConfig->get('shield_enable'); + + if ($this->wasShieldEnabled) { + $shieldConfig->set('shield_enable', FALSE)->save(); + } + } + + /** + * Gets the configuration object. + * + * @param string $name + * The name of the configuration object. + * + * @return \Drupal\Core\Config\Config + * The configuration object. + * @throws \Exception + */ + protected function config(string $name): \Drupal\Core\Config\Config { + return $this->container->get('config.factory')->getEditable($name); + } + + /** + * {@inheritdoc} + */ + protected function tearDown(): void { + // Re-enable the Shield module if it was originally enabled. + if ($this->wasShieldEnabled) { + $this->config('shield.settings')->set('shield_enable', TRUE)->save(); + } + + parent::tearDown(); + + $this->tearDownDrupal(); + $this->tearDownMinkSession(); + } + + /** + * Prepares the request for the test case. + * + * Override this method to modify the request before it is handled. + */ + protected function prepareRequest() {} + +} diff --git a/phpunit/phpunit.xml b/phpunit/phpunit.xml index b1201860..47a781a0 100644 --- a/phpunit/phpunit.xml +++ b/phpunit/phpunit.xml @@ -37,7 +37,10 @@ /app/web/themes - /app/tests/phpunit/integration + /app/tests/phpunit/integration/GovCMS + + + /app/tests/phpunit/integration/Canary /app/tests/phpunit/tests