Skip to content

Commit 3ee27b2

Browse files
committed
Unit tests and cleanup
1 parent 3fcabf2 commit 3ee27b2

File tree

11 files changed

+700
-26
lines changed

11 files changed

+700
-26
lines changed

.github/workflows/pr.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,35 @@ jobs:
114114
run: |
115115
./scripts/code-analysis
116116
117+
php-unit-tests:
118+
name: PHP unit tests
119+
runs-on: ubuntu-latest
120+
strategy:
121+
matrix:
122+
php-versions: [ '8.3' ]
123+
steps:
124+
- uses: actions/checkout@master
125+
- name: Setup PHP, with composer and extensions
126+
uses: shivammathur/setup-php@v2
127+
with:
128+
php-version: ${{ matrix.php-versions }}
129+
extensions: json
130+
coverage: none
131+
tools: composer:v2
132+
# https://github.com/shivammathur/setup-php#cache-composer-dependencies
133+
- name: Get composer cache directory
134+
id: composer-cache
135+
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
136+
- name: Cache dependencies
137+
uses: actions/cache@v2
138+
with:
139+
path: ${{ steps.composer-cache.outputs.dir }}
140+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
141+
restore-keys: ${{ runner.os }}-composer-
142+
- name: Code analysis
143+
run: |
144+
./scripts/unit-tests
145+
117146
coding-standards-markdown:
118147
name: Markdown coding standards
119148
runs-on: ubuntu-latest

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
* [PR-4](https://github.com/OS2web/os2web_key/pull/4)
11+
Added HashiCorp Vault key provider
1012
* [PR-2](https://github.com/OS2web/os2web_key/pull/2)
1113
Updated documentation
1214
* [PR-1](https://github.com/OS2web/os2web_key/pull/1)

composer.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,24 @@
1919
"require-dev": {
2020
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
2121
"drupal/coder": "^8.3",
22+
"drupal/core-dev": "^9 || ^10",
2223
"ergebnis/composer-normalize": "^2.42",
2324
"mglaman/phpstan-drupal": "^1.2",
2425
"phpstan/extension-installer": "^1.3",
25-
"phpstan/phpstan-deprecation-rules": "^1.1"
26+
"phpstan/phpstan-deprecation-rules": "^1.1",
27+
"phpunit/phpunit": "^9.6"
2628
},
2729
"repositories": [
2830
{
2931
"type": "composer",
3032
"url": "https://packages.drupal.org/8"
3133
}
3234
],
35+
"autoload-dev": {
36+
"psr-4": {
37+
"Drupal\\Tests\\os2web_key\\": "tests/src/"
38+
}
39+
},
3340
"config": {
3441
"allow-plugins": {
3542
"dealerdirect/phpcodesniffer-composer-installer": true,

os2web_key.services.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ services:
2929
- '@logger.channel.os2web_key'
3030
- '@http_client'
3131
- '@os2web_key.psr16_cache'
32+
- '@os2web_key.key_helper'

phpunit.xml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!-- For how to customize PHPUnit configuration, see core/tests/README.md. -->
4+
<!-- TODO set checkForUnintentionallyCoveredCode="true" once https://www.drupal.org/node/2626832 is resolved. -->
5+
<!-- PHPUnit expects functional tests to be run with either a privileged user
6+
or your current system user. See core/tests/README.md and
7+
https://www.drupal.org/node/2116263 for details.
8+
-->
9+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
bootstrap="tests/bootstrap.php" colors="true"
11+
beStrictAboutTestsThatDoNotTestAnything="true"
12+
beStrictAboutOutputDuringTests="true"
13+
beStrictAboutChangesToGlobalState="true"
14+
failOnWarning="true"
15+
printerClass="\Drupal\Tests\Listeners\HtmlOutputPrinter"
16+
cacheResult="false"
17+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
18+
<php>
19+
<!-- Set error reporting to E_ALL. -->
20+
<ini name="error_reporting" value="32767"/>
21+
<!-- Do not limit the amount of memory tests take to run. -->
22+
<ini name="memory_limit" value="-1"/>
23+
<!-- Example SIMPLETEST_BASE_URL value: http://localhost -->
24+
<env name="SIMPLETEST_BASE_URL" value=""/>
25+
<!-- Example SIMPLETEST_DB value: mysql://username:password@localhost/database_name#table_prefix -->
26+
<env name="SIMPLETEST_DB" value="mysql://db:db@mariadb/db"/>
27+
<!-- Example BROWSERTEST_OUTPUT_DIRECTORY value: /path/to/webroot/sites/simpletest/browser_output -->
28+
<env name="BROWSERTEST_OUTPUT_DIRECTORY" value=""/>
29+
<!-- By default, browser tests will output links that use the base URL set
30+
in SIMPLETEST_BASE_URL. However, if your SIMPLETEST_BASE_URL is an internal
31+
path (such as may be the case in a virtual or Docker-based environment),
32+
you can set the base URL used in the browser test output links to something
33+
reachable from your host machine here. This will allow you to follow them
34+
directly and view the output. -->
35+
<env name="BROWSERTEST_OUTPUT_BASE_URL" value=""/>
36+
37+
<!-- Deprecation testing is managed through Symfony's PHPUnit Bridge.
38+
The environment variable SYMFONY_DEPRECATIONS_HELPER is used to configure
39+
the behavior of the deprecation tests.
40+
See https://symfony.com/doc/current/components/phpunit_bridge.html#configuration
41+
Drupal core's testing framework is setting this variable to its defaults.
42+
Projects with their own requirements need to manage this variable
43+
explicitly.
44+
-->
45+
<!-- To disable deprecation testing completely uncomment the next line. -->
46+
<!-- <env name="SYMFONY_DEPRECATIONS_HELPER" value="disabled"/> -->
47+
<!-- Deprecation errors can be selectively ignored by specifying a file of
48+
regular expression patterns for exclusion.
49+
See https://symfony.com/doc/current/components/phpunit_bridge.html#ignoring-deprecations
50+
Uncomment the line below to specify a custom deprecations ignore file.
51+
NOTE: it may be required to specify the full path to the file to run tests
52+
correctly.
53+
-->
54+
<!-- <env name="SYMFONY_DEPRECATIONS_HELPER" value="ignoreFile=.deprecation-ignore.txt"/> -->
55+
56+
<!-- Example for changing the driver class for mink tests MINK_DRIVER_CLASS value: 'Drupal\FunctionalJavascriptTests\DrupalSelenium2Driver' -->
57+
<env name="MINK_DRIVER_CLASS" value=''/>
58+
<!-- Example for changing the driver args to mink tests MINK_DRIVER_ARGS value: '["http://127.0.0.1:8510"]' -->
59+
<env name="MINK_DRIVER_ARGS" value=''/>
60+
<!-- Example for changing the driver args to webdriver tests MINK_DRIVER_ARGS_WEBDRIVER value: '["chrome", { "goog:chromeOptions": { "w3c": false } }, "http://localhost:4444/wd/hub"]' For using the Firefox browser, replace "chrome" with "firefox" -->
61+
<env name="MINK_DRIVER_ARGS_WEBDRIVER" value=''/>
62+
</php>
63+
<testsuites>
64+
<testsuite name="unit">
65+
<file>./tests/TestSuites/UnitTestSuite.php</file>
66+
</testsuite>
67+
<testsuite name="kernel">
68+
<file>./tests/TestSuites/KernelTestSuite.php</file>
69+
</testsuite>
70+
<testsuite name="functional">
71+
<file>./tests/TestSuites/FunctionalTestSuite.php</file>
72+
</testsuite>
73+
<testsuite name="functional-javascript">
74+
<file>./tests/TestSuites/FunctionalJavascriptTestSuite.php</file>
75+
</testsuite>
76+
<testsuite name="build">
77+
<file>./tests/TestSuites/BuildTestSuite.php</file>
78+
</testsuite>
79+
</testsuites>
80+
<listeners>
81+
<listener class="\Drupal\Tests\Listeners\DrupalListener">
82+
</listener>
83+
</listeners>
84+
<!-- Settings for coverage reports. -->
85+
<coverage>
86+
<include>
87+
<directory>./includes</directory>
88+
<directory>./lib</directory>
89+
<directory>./modules</directory>
90+
<directory>../modules</directory>
91+
<directory>../sites</directory>
92+
</include>
93+
<exclude>
94+
<directory>./modules/*/src/Tests</directory>
95+
<directory>./modules/*/tests</directory>
96+
<directory>../modules/*/src/Tests</directory>
97+
<directory>../modules/*/tests</directory>
98+
<directory>../modules/*/*/src/Tests</directory>
99+
<directory>../modules/*/*/tests</directory>
100+
<directory suffix=".api.php">./lib/**</directory>
101+
<directory suffix=".api.php">./modules/**</directory>
102+
<directory suffix=".api.php">../modules/**</directory>
103+
</exclude>
104+
</coverage>
105+
</phpunit>

scripts/unit-tests

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bash
2+
script_dir=$(pwd)
3+
module_name=$(basename "$script_dir")
4+
drupal_dir=vendor/drupal-module-code-analysis
5+
# Relative to $drupal_dir
6+
module_path=web/modules/contrib/$module_name
7+
8+
cd "$script_dir" || exit
9+
10+
drupal_composer() {
11+
composer --working-dir="$drupal_dir" --no-interaction "$@"
12+
}
13+
14+
# Create new Drupal 10 project
15+
if [ ! -f "$drupal_dir/composer.json" ]; then
16+
composer --no-interaction create-project drupal/recommended-project:^10 "$drupal_dir"
17+
fi
18+
# Copy our code into the modules folder
19+
mkdir -p "$drupal_dir/$module_path"
20+
# https://stackoverflow.com/a/15373763
21+
rsync --archive --compress . --filter=':- .gitignore' --exclude "$drupal_dir" --exclude .git "$drupal_dir/$module_path"
22+
23+
drupal_composer config minimum-stability dev
24+
25+
# Allow ALL plugins
26+
# https://getcomposer.org/doc/06-config.md#allow-plugins
27+
drupal_composer config --no-plugins allow-plugins true
28+
29+
# Making Drupal 10 compatible
30+
drupal_composer require psr/http-message:^1.0
31+
drupal_composer require mglaman/composer-drupal-lenient
32+
drupal_composer config --merge --json extra.drupal-lenient.allowed-list '["drupal/coc_forms_auto_export", "drupal/webform_node_element"]'
33+
34+
drupal_composer require wikimedia/composer-merge-plugin
35+
drupal_composer config extra.merge-plugin.include "$module_path/composer.json"
36+
# https://www.drupal.org/project/drupal/issues/3220043#comment-14845434
37+
drupal_composer require --dev symfony/phpunit-bridge
38+
39+
40+
# Run PHPUnit
41+
(cd "$drupal_dir" && vendor/bin/phpunit "$module_path/tests/src/Unit/KeyHelperUnitTest.php")

src/KeyHelper.php

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,14 @@ public function __construct(
3232
* @return array{cert: string, pkey: string}
3333
* The certificates.
3434
*/
35-
public function getCertificates(KeyInterface $key, bool $parseCertificate = FALSE): array {
35+
public function getCertificates(KeyInterface $key): array {
3636
$type = $key->getKeyType();
3737
if (!($type instanceof CertificateKeyType)) {
3838
throw $this->createSslRuntimeException(sprintf('Invalid key type: %s', $type::class), $key);
3939
}
40-
$contents = $key->getKeyValue();
4140

42-
if ($parseCertificate) {
43-
return $this->parseCertificates(
44-
$contents,
45-
$type->getInputFormat(),
46-
$type->getPassphrase(),
47-
$key
48-
);
49-
}
50-
else {
51-
return ['cert' => $contents];
52-
}
41+
return $this->parseCertificates($key->getKeyValue(), $type->getInputFormat(), $type->getPassphrase(), $key);
42+
5343
}
5444

5545
/**
@@ -90,7 +80,7 @@ public function getOidcValues(KeyInterface $key): array {
9080
* Parse certificates.
9181
*
9282
* @return array{cert: string, pkey: string}
93-
* The certificates.
83+
* The certificates, note that the certificate has no passphrase.
9484
*/
9585
public function parseCertificates(
9686
string $contents,
@@ -102,6 +92,7 @@ public function parseCertificates(
10292
CertificateKeyType::CERT => NULL,
10393
CertificateKeyType::PKEY => NULL,
10494
];
95+
10596
switch ($format) {
10697
case CertificateKeyType::FORMAT_PFX:
10798
if (!openssl_pkcs12_read($contents, $certificates, $passphrase)) {
@@ -114,30 +105,42 @@ public function parseCertificates(
114105
if (FALSE === $certificate) {
115106
throw $this->createSslRuntimeException('Error reading certificate', $key);
116107
}
117-
if (!@openssl_x509_export($certificate, $certificates['cert'])) {
108+
if (!@openssl_x509_export($certificate, $certificates[CertificateKeyType::CERT])) {
118109
throw $this->createSslRuntimeException('Error exporting x509 certificate', $key);
119110
}
120111
$pkey = @openssl_pkey_get_private($contents, $passphrase);
121112
if (FALSE === $pkey) {
122113
throw $this->createSslRuntimeException('Error reading private key', $key);
123114
}
124-
if (!@openssl_pkey_export($pkey, $certificates['pkey'])) {
115+
if (!@openssl_pkey_export($pkey, $certificates[CertificateKeyType::PKEY])) {
125116
throw $this->createSslRuntimeException('Error exporting private key', $key);
126117
}
127118
break;
128119
}
129120

130121
if (!isset($certificates[CertificateKeyType::CERT], $certificates[CertificateKeyType::PKEY])) {
131-
throw $this->createRuntimeException("Cannot read certificate parts 'cert' and 'pkey'", $key);
122+
throw $this->createRuntimeException("Cannot read certificate parts CertificateKeyType::CERT and CertificateKeyType::PKEY", $key);
132123
}
133124

134125
return $certificates;
135126
}
136127

137128
/**
138-
* Create a passwordless certificate.
129+
* Converts certificates to format.
130+
*
131+
* @param array $certificates
132+
* Output from parseCertificates.
133+
* @param string $format
134+
* Format to convert into.
135+
* @param ?KeyInterface $key
136+
* The key, for debugging purposes.
137+
*
138+
* @return string
139+
* The converted certificate.
140+
*
141+
* @see self::parseCertificates()
139142
*/
140-
public function createPasswordlessCertificate(array $certificates, string $format, ?KeyInterface $key): string {
143+
public function convertCertificates(array $certificates, string $format, ?KeyInterface $key): string {
141144
$cert = $certificates[CertificateKeyType::CERT] ?? NULL;
142145
if (!isset($cert)) {
143146
throw $this->createRuntimeException('Certificate part "cert" not found', $key);
@@ -196,7 +199,14 @@ public function createRuntimeException(string $message, ?KeyInterface $key, ?str
196199
* Create an SSL runtime exception.
197200
*/
198201
public function createSslRuntimeException(string $message, ?KeyInterface $key): RuntimeException {
199-
return $this->createRuntimeException($message, $key, openssl_error_string() ?: NULL);
202+
// @see https://www.php.net/manual/en/function.openssl-error-string.php.
203+
$sslError = NULL;
204+
205+
while ($errorMessage = openssl_error_string()) {
206+
$sslError = $errorMessage;
207+
}
208+
209+
return $this->createRuntimeException($message, $key, $sslError);
200210
}
201211

202212
}

0 commit comments

Comments
 (0)