Skip to content

Commit 199c83e

Browse files
authored
Use GitHub Actions for testing (#330)
* Add workflow file for tests on github actions * Remove travis ci * Check CI variable correctly and fix PHPStan issue * Stop running tests on PHP 7.2 * Use openssl_pkey_get_* methods on keys before passing to openssl_pkey_derive * Update link to badge in README * Reduce setup-php extensions to bare minimum * Remove PR branch from workflows file * Add builds for PHP 8.1 * Stop skipping PushServiceTest on CI * Run tests with web-push-testing * Remove invalid assert from PushServiceTest * Add fallback for running WebPushTest on CI environment * Remove PHP 7.2 from tests as that is no longer supported * Resolve phpstan errors * Skip running php-cs-fixer on PHP 8.1 PHP 8.1 is currently not supported by this. * Remove extra conversions of public & private key strings * Throw exception instead of silent fallback when unpack fails
1 parent 92dace6 commit 199c83e

File tree

8 files changed

+183
-152
lines changed

8 files changed

+183
-152
lines changed

.github/workflows/tests.yml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
tags:
8+
- 'v*'
9+
pull_request:
10+
branches:
11+
- master
12+
13+
jobs:
14+
tests:
15+
runs-on: ubuntu-latest
16+
strategy:
17+
matrix:
18+
php: ['7.3', '7.4', '8.0', '8.1']
19+
20+
name: PHP ${{ matrix.php }}
21+
22+
steps:
23+
- name: Checkout repository
24+
uses: actions/checkout@v2
25+
26+
- name: Setup PHP
27+
uses: shivammathur/setup-php@v2
28+
with:
29+
php-version: ${{ matrix.php }}
30+
extensions: curl, mbstring, openssl, gmp
31+
coverage: none
32+
33+
- name: Setup node
34+
uses: actions/setup-node@v2
35+
with:
36+
node-version: '16'
37+
38+
- name: Cache Composer dependencies
39+
uses: actions/cache@v2
40+
with:
41+
path: /tmp/composer-cache
42+
key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
43+
44+
- name: Prepare composer
45+
run: |
46+
EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')"
47+
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
48+
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
49+
50+
if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
51+
then
52+
>&2 echo 'ERROR: Invalid installer checksum'
53+
rm composer-setup.php
54+
exit 1
55+
fi
56+
57+
sudo php composer-setup.php --quiet --install-dir=/usr/local/bin --filename=composer
58+
RESULT=$?
59+
rm composer-setup.php
60+
composer config discard-changes true
61+
composer install
62+
63+
- name: Setup web-push-testing-service
64+
run: |
65+
npm install web-push-testing -g
66+
67+
- name: Run tests
68+
run: |
69+
web-push-testing --port 9012 start
70+
composer test:unit
71+
web-push-testing --port 9012 stop
72+
73+
- name: Run PHPStan
74+
run: composer test:typing
75+
76+
- name: Run php-cs-fixer
77+
if: ${{ matrix.php != '8.1' }}
78+
run: composer test:syntax

.travis.yml

Lines changed: 0 additions & 54 deletions
This file was deleted.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# WebPush
22
> Web Push library for PHP
33
4-
[![Build Status](https://travis-ci.org/web-push-libs/web-push-php.svg?branch=master)](https://travis-ci.org/web-push-libs/web-push-php)
4+
[![Build Status](https://github.com/web-push-libs/web-push-php/actions/workflows/tests.yml/badge.svg)](https://github.com/web-push-libs/web-push-php/actions/workflows/tests.yml)
55
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/d60e8eea-aea1-4739-8ce0-a3c3c12c6ccf/mini.png)](https://insight.sensiolabs.com/projects/d60e8eea-aea1-4739-8ce0-a3c3c12c6ccf)
66

77
WebPush can be used to send notifications to endpoints which server delivers Web Push notifications as described in

phpstan.neon

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ parameters:
33
paths:
44
- src
55
checkMissingIterableValueType: false
6+
reportUnmatchedIgnoredErrors: false
67
ignoreErrors:
7-
- '#Unreachable statement \- code above always terminates\.#'
8+
- '#Unreachable statement \- code above always terminates\.#'

src/Encryption.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,6 @@ public static function deterministicEncrypt(string $payload, string $userPublicK
105105
$sharedSecret = self::calculateAgreementKey($localJwk, $userJwk);
106106

107107
$sharedSecret = str_pad($sharedSecret, 32, chr(0), STR_PAD_LEFT);
108-
if (!$sharedSecret) {
109-
throw new \ErrorException('Failed to convert shared secret from hexadecimal to binary');
110-
}
111108

112109
// section 4.3
113110
$ikm = self::getIKM($userAuthToken, $userPublicKey, $localPublicKey, $sharedSecret, $contentEncoding);
@@ -362,17 +359,31 @@ private static function calculateAgreementKey(JWK $private_key, JWK $public_key)
362359
}
363360
}
364361

362+
/**
363+
* @throws \ErrorException
364+
*/
365365
private static function convertBase64ToBigInteger(string $value): BigInteger
366366
{
367367
$value = unpack('H*', Base64Url::decode($value));
368368

369+
if ($value === false) {
370+
throw new \ErrorException('Unable to unpack hex value from string');
371+
}
372+
369373
return BigInteger::fromBase($value[1], 16);
370374
}
371375

376+
/**
377+
* @throws \ErrorException
378+
*/
372379
private static function convertBase64ToGMP(string $value): \GMP
373380
{
374381
$value = unpack('H*', Base64Url::decode($value));
375382

383+
if ($value === false) {
384+
throw new \ErrorException('Unable to unpack hex value from string');
385+
}
386+
376387
return gmp_init($value[1], 16);
377388
}
378389

src/WebPush.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,10 @@ protected function prepare(array $notifications): array
236236
$encryptionContentCodingHeader = Encryption::getContentCodingHeader($salt, $localPublicKey, $contentEncoding);
237237
$content = $encryptionContentCodingHeader.$cipherText;
238238

239-
$headers['Content-Length'] = Utils::safeStrlen($content);
239+
$headers['Content-Length'] = (string) Utils::safeStrlen($content);
240240
} else {
241241
$headers = [
242-
'Content-Length' => 0,
242+
'Content-Length' => '0',
243243
];
244244

245245
$content = '';

tests/PushServiceTest.php

Lines changed: 22 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ final class PushServiceTest extends PHPUnit\Framework\TestCase
1818
{
1919
private static $timeout = 30;
2020
private static $portNumber = 9012;
21-
private static $testSuiteId;
2221
private static $testServiceUrl;
23-
private static $vapidKeys = [
22+
public static $vapidKeys = [
2423
'subject' => 'http://test.com',
2524
'publicKey' => 'BA6jvk34k6YjElHQ6S0oZwmrsqHdCNajxcod6KJnI77Dagikfb--O_kYXcR2eflRz6l3PcI2r8fPCH3BElLQHDk',
2625
'privateKey' => '-3CdhFOqjzixgAbUSa0Zv9zi-dwDVmWO7672aBxSFPQ',
@@ -37,34 +36,13 @@ public static function setUpBeforeClass(): void
3736
self::$testServiceUrl = 'http://localhost:'.self::$portNumber;
3837
}
3938

40-
/**
41-
* {@inheritdoc}
42-
*/
43-
protected function setUp(): void
44-
{
45-
if (!(getenv('TRAVIS') || getenv('CI'))) {
46-
$this->markTestSkipped('This test does not run on Travis.');
47-
}
48-
49-
$startApiCurl = curl_init(self::$testServiceUrl.'/api/start-test-suite/');
50-
curl_setopt_array($startApiCurl, [
51-
CURLOPT_POST => true,
52-
CURLOPT_POSTFIELDS => [],
53-
CURLOPT_RETURNTRANSFER => true,
54-
CURLOPT_TIMEOUT => self::$timeout,
55-
]);
56-
57-
$parsedResp = $this->getResponse($startApiCurl);
58-
self::$testSuiteId = $parsedResp->{'data'}->{'testSuiteId'};
59-
}
60-
6139
public function browserProvider()
6240
{
6341
return [
64-
['firefox', 'stable', ['VAPID' => self::$vapidKeys]],
65-
['firefox', 'beta', ['VAPID' => self::$vapidKeys]],
66-
['chrome', 'stable', ['VAPID' => self::$vapidKeys]],
67-
['chrome', 'beta', ['VAPID' => self::$vapidKeys]],
42+
['firefox', ['VAPID' => self::$vapidKeys]],
43+
['chrome', ['VAPID' => self::$vapidKeys]],
44+
['firefox', []],
45+
['chrome', []],
6846
];
6947
}
7048

@@ -92,30 +70,25 @@ public function retryTest($retryCount, $test)
9270
* @dataProvider browserProvider
9371
* Run integration tests with browsers
9472
*/
95-
public function testBrowsers($browserId, $browserVersion, $options)
73+
public function testBrowsers($browserId, $options)
9674
{
97-
$this->retryTest(2, $this->createClosureTest($browserId, $browserVersion, $options));
75+
$this->retryTest(2, $this->createClosureTest($browserId, $options));
9876
}
9977

100-
protected function createClosureTest($browserId, $browserVersion, $options)
78+
protected function createClosureTest($browserId, $options)
10179
{
102-
return function () use ($browserId, $browserVersion, $options) {
80+
return function () use ($browserId, $options) {
10381
$this->webPush = new WebPush($options);
10482
$this->webPush->setAutomaticPadding(false);
105-
106-
$subscriptionParameters = [
107-
'testSuiteId' => self::$testSuiteId,
108-
'browserName' => $browserId,
109-
'browserVersion' => $browserVersion,
110-
];
83+
$subscriptionParameters = [];
11184

11285
if (array_key_exists('VAPID', $options)) {
113-
$subscriptionParameters['vapidPublicKey'] = self::$vapidKeys['publicKey'];
86+
$subscriptionParameters['applicationServerKey'] = self::$vapidKeys['publicKey'];
11487
}
11588

11689
$subscriptionParameters = json_encode($subscriptionParameters, JSON_THROW_ON_ERROR);
11790

118-
$getSubscriptionCurl = curl_init(self::$testServiceUrl.'/api/get-subscription/');
91+
$getSubscriptionCurl = curl_init(self::$testServiceUrl.'/subscribe');
11992
curl_setopt_array($getSubscriptionCurl, [
12093
CURLOPT_POST => true,
12194
CURLOPT_POSTFIELDS => $subscriptionParameters,
@@ -128,18 +101,17 @@ protected function createClosureTest($browserId, $browserVersion, $options)
128101
]);
129102

130103
$parsedResp = $this->getResponse($getSubscriptionCurl);
131-
$testId = $parsedResp->{'data'}->{'testId'};
132-
$subscription = $parsedResp->{'data'}->{'subscription'};
104+
$subscription = $parsedResp->{'data'};
133105

134-
$supportedContentEncodings = property_exists($subscription, 'supportedContentEncodings') ?
135-
$subscription->{'supportedContentEncodings'} :
136-
["aesgcm"];
106+
$supportedContentEncodings = ['aesgcm', 'aes128gcm'];
137107

138108
$endpoint = $subscription->{'endpoint'};
139109
$keys = $subscription->{'keys'};
140110
$auth = $keys->{'auth'};
141111
$p256dh = $keys->{'p256dh'};
112+
$clientHash = $subscription->{'clientHash'};
142113
$payload = 'hello';
114+
$messageIndex = 0;
143115

144116
foreach ($supportedContentEncodings as $contentEncoding) {
145117
if (!in_array($contentEncoding, ['aesgcm', 'aes128gcm'])) {
@@ -150,16 +122,14 @@ protected function createClosureTest($browserId, $browserVersion, $options)
150122

151123
$subscription = new Subscription($endpoint, $p256dh, $auth, $contentEncoding);
152124
$report = $this->webPush->sendOneNotification($subscription, $payload);
153-
$this->assertInstanceOf(\Generator::class, $report);
154125
$this->assertInstanceOf(\Minishlink\WebPush\MessageSentReport::class, $report);
155126
$this->assertTrue($report->isSuccess());
156127

157128
$dataString = json_encode([
158-
'testSuiteId' => self::$testSuiteId,
159-
'testId' => $testId,
160-
], JSON_THROW_ON_ERROR);
129+
'clientHash' => $clientHash,
130+
]);
161131

162-
$getNotificationCurl = curl_init(self::$testServiceUrl.'/api/get-notification-status/');
132+
$getNotificationCurl = curl_init(self::$testServiceUrl.'/get-notifications');
163133
curl_setopt_array($getNotificationCurl, [
164134
CURLOPT_POST => true,
165135
CURLOPT_POSTFIELDS => $dataString,
@@ -174,39 +144,16 @@ protected function createClosureTest($browserId, $browserVersion, $options)
174144
$parsedResp = $this->getResponse($getNotificationCurl);
175145

176146
if (!property_exists($parsedResp->{'data'}, 'messages')) {
177-
throw new Exception('web-push-testing-service error, no messages: '.json_encode($parsedResp, JSON_THROW_ON_ERROR));
147+
throw new Exception('web-push-testing error, no messages: '.json_encode($parsedResp));
178148
}
179149

180150
$messages = $parsedResp->{'data'}->{'messages'};
181-
$this->assertEquals(1, is_countable($messages) ? count($messages) : 0);
182-
$this->assertEquals($payload, $messages[0]);
151+
$this->assertEquals($payload, $messages[$messageIndex]);
152+
$this->assertCount(++$messageIndex, $messages);
183153
}
184154
};
185155
}
186156

187-
protected function tearDown(): void
188-
{
189-
$dataString = '{ "testSuiteId": '.self::$testSuiteId.' }';
190-
$curl = curl_init(self::$testServiceUrl.'/api/end-test-suite/');
191-
curl_setopt_array($curl, [
192-
CURLOPT_POST => true,
193-
CURLOPT_POSTFIELDS => $dataString,
194-
CURLOPT_RETURNTRANSFER => true,
195-
CURLOPT_HTTPHEADER => [
196-
'Content-Type: application/json',
197-
'Content-Length: '.strlen($dataString),
198-
],
199-
CURLOPT_TIMEOUT => self::$timeout,
200-
]);
201-
$this->getResponse($curl);
202-
self::$testSuiteId = null;
203-
}
204-
205-
public static function tearDownAfterClass(): void
206-
{
207-
exec('web-push-testing-service stop phpunit');
208-
}
209-
210157
private function getResponse($ch)
211158
{
212159
$resp = curl_exec($ch);

0 commit comments

Comments
 (0)