Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
158 changes: 158 additions & 0 deletions phpunit/integration/BrowserKitTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

declare(strict_types=1);

namespace GovCMS\Tests\Integration;

use Behat\Mink\Driver\BrowserKitDriver;
use Behat\Mink\Driver\DriverInterface;
use Behat\Mink\Element\DocumentElement;
use Behat\Mink\Mink;
use Behat\Mink\Selector\SelectorsHandler;
use Behat\Mink\Session;
use Drupal\Tests\DrupalTestBrowser;
use Drupal\Tests\HiddenFieldSelector;
use Drupal\Tests\XdebugRequestTrait;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Symfony\Component\HttpFoundation\Request;

/**
* Provides a browser trait for shared test setup functionality.
*/
trait BrowserKitTrait {
use XdebugRequestTrait;

protected ?Mink $mink;
protected DriverInterface $driver;
protected string $baseUrl;

/**
* Gets the Mink session.
*
* @param string|null $name
* The session name. If not provided, the default session will be returned.
*
* @return \Behat\Mink\Session
* The Mink session.
*/
protected function getSession(?string $name = null): Session {
return $this->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);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace GovCMS\Tests\Integration\Canary\Security\Functional;

use GovCMS\Tests\Integration\ExistingSiteBase;

/**
* Tests password length requirement for user.
*
* @group security
* @group canary
*/
class PasswordLengthTest extends ExistingSiteBase {

/**
* {@inheritdoc}
*/
protected $profile = 'govcms';

/**
* Tests that the password length must be at least 14 characters.
*/
public function testPasswordLengthRequirement() {
$user = $this->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.
}

}
122 changes: 122 additions & 0 deletions phpunit/integration/DrupalTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

declare(strict_types=1);

namespace GovCMS\Tests\Integration;

use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;
use Composer\Autoload\ClassLoader;

/**
* Provides a trait for shared test setup functionality.
*/
trait DrupalTrait {

/**
* The base URL.
*
* @var string
*/
protected string $baseUrl;

/**
* The class loader to use for installation and initialization of setup.
*
* @var ClassLoader
*/
protected ClassLoader $classLoader;

/**
* The container.
*
* @var \Drupal\Core\DependencyInjection\ContainerBuilder
*/
protected $container;

/**
* The Drupal kernel.
*
* @var DrupalKernel
*/
protected DrupalKernel $kernel;

/**
* A flag to track when we've restored the error handler.
*
* @var bool
*/
protected static bool $restoredErrorHandler = false;

/**
* Bootstrap Drupal.
*/
protected function setupDrupal(): void {
// Retrieve the base URL from the environment variable and set it globally.
global $base_url;
$base_url = getenv('SIMPLETEST_BASE_URL') ?: 'http://localhost';
$this->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;
}

}
Loading