mirror of
https://github.com/FOSSBilling/FOSSBilling.git
synced 2026-10-23 03:37:01 +09:00
Move E2E tests into modules and update test config (#3707)
* Move E2E tests into modules and update test config * Update php-build-test.yml * Remove premature curl_close calls * E2E tests: clean assertions, cart client, currency * Refactor E2E tests imports & assertions * Fix Antispam E2E - skip disposable email test when domain list unavailable
This commit is contained in:
1 parent
d31f13b0fa
commit
4315d610b2
29 files changed
+303
-568
No files matched your search
@@ -90,5 +90,5 @@ compose run --rm --no-deps \
|
|||||||
app \
|
app \
|
||||||
sh -euxc "
|
sh -euxc "
|
||||||
cd /workspace
|
cd /workspace
|
||||||
./src/vendor/bin/pest --test-directory ../tests --configuration phpunit-live.xml
|
./src/vendor/bin/pest --test-directory ../tests --testsuite=E2E --ci
|
||||||
"
|
"
|
||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
docker run --rm \
|
docker run --rm \
|
||||||
--env APP_ENV=test \
|
--env APP_ENV=test \
|
||||||
fossbilling:test-${{ matrix.php }} \
|
fossbilling:test-${{ matrix.php }} \
|
||||||
./src/vendor/bin/pest --test-directory ../tests --configuration phpunit.xml.dist
|
./src/vendor/bin/pest --test-directory ../tests --configuration phpunit.xml.dist --ci
|
||||||
|
|
||||||
- name: 'Write Job Summary'
|
- name: 'Write Job Summary'
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ composer test
|
|||||||
```
|
```
|
||||||
|
|
||||||
The default suite covers unit tests in `tests/Unit` and module unit tests in `src/modules/*/tests/Unit`.
|
The default suite covers unit tests in `tests/Unit` and module unit tests in `src/modules/*/tests/Unit`.
|
||||||
Live API tests are in `tests/Modules` and run through Pest with `phpunit-live.xml` when `APP_URL` and `TEST_API_KEY` are available.
|
Live API tests are in `src/modules/*/tests/E2E/` and run through Pest via the "E2E" test suite in `phpunit.xml.dist` when `APP_URL` and `TEST_API_KEY` are available.
|
||||||
Cypress end-to-end tests live under `tests/E2E/Cypress`.
|
Cypress end-to-end tests live under `tests/E2E/Cypress`.
|
||||||
|
|
||||||
### Code Quality Tools
|
### Code Quality Tools
|
||||||
|
|||||||
+1
-1
@@ -146,7 +146,7 @@ WORKDIR /workspace
|
|||||||
|
|
||||||
COPY --from=release-tree /app/src ./src
|
COPY --from=release-tree /app/src ./src
|
||||||
COPY --from=php-dev-vendor /app/src/vendor ./src/vendor
|
COPY --from=php-dev-vendor /app/src/vendor ./src/vendor
|
||||||
COPY composer.json composer.lock phpstan.neon phpstan-baseline.neon phpunit.xml.dist phpunit-live.xml ./
|
COPY composer.json composer.lock phpstan.neon phpstan-baseline.neon phpunit.xml.dist ./
|
||||||
COPY tests ./tests
|
COPY tests ./tests
|
||||||
|
|
||||||
RUN set -eux; \
|
RUN set -eux; \
|
||||||
|
|||||||
@@ -520,7 +520,6 @@
|
|||||||
"data/**",
|
"data/**",
|
||||||
"src/data/**",
|
"src/data/**",
|
||||||
"phpunit.xml.dist",
|
"phpunit.xml.dist",
|
||||||
"phpunit-live.xml",
|
|
||||||
"bin/**",
|
"bin/**",
|
||||||
"*.mmdb",
|
"*.mmdb",
|
||||||
"cspell.json",
|
"cspell.json",
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" colors="true"
|
|
||||||
processIsolation="false" stopOnFailure="false" beStrictAboutChangesToGlobalState="true"
|
|
||||||
bootstrap="tests/Pest.php"
|
|
||||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.5/phpunit.xsd"
|
|
||||||
cacheDirectory=".phpunit-live.cache" backupStaticProperties="false">
|
|
||||||
<source>
|
|
||||||
<include>
|
|
||||||
<directory suffix=".php">src</directory>
|
|
||||||
</include>
|
|
||||||
<exclude>
|
|
||||||
<directory suffix=".php">src/vendor</directory>
|
|
||||||
<directory suffix=".php">src/data</directory>
|
|
||||||
</exclude>
|
|
||||||
</source>
|
|
||||||
<testsuites>
|
|
||||||
<testsuite name="Live Tests">
|
|
||||||
<directory>tests/Modules</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
<php>
|
|
||||||
<ini name="display_errors" value="On" />
|
|
||||||
<ini name="display_startup_errors" value="On" />
|
|
||||||
</php>
|
|
||||||
</phpunit>
|
|
||||||
@@ -20,9 +20,6 @@
|
|||||||
<testsuite name="Modules">
|
<testsuite name="Modules">
|
||||||
<directory>src/modules/*/tests/Unit</directory>
|
<directory>src/modules/*/tests/Unit</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
<testsuite name="API">
|
|
||||||
<directory>tests/Modules</directory>
|
|
||||||
</testsuite>
|
|
||||||
<testsuite name="E2E">
|
<testsuite name="E2E">
|
||||||
<directory>tests/E2E</directory>
|
<directory>tests/E2E</directory>
|
||||||
<directory>src/modules/*/tests/E2E</directory>
|
<directory>src/modules/*/tests/E2E</directory>
|
||||||
|
|||||||
+12
@@ -1,9 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright 2022-2026 FOSSBilling
|
||||||
|
* SPDX-License-Identifier: Apache-2.0.
|
||||||
|
*
|
||||||
|
* @copyright FOSSBilling (https://www.fossbilling.org)
|
||||||
|
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Tests\Helpers\ApiClient;
|
use Tests\Helpers\ApiClient;
|
||||||
|
|
||||||
|
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$originalAntispamConfig = null;
|
$originalAntispamConfig = null;
|
||||||
|
|
||||||
beforeEach(function () use (&$originalAntispamConfig): void {
|
beforeEach(function () use (&$originalAntispamConfig): void {
|
||||||
@@ -10,56 +10,52 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
// Skip E2E tests if environment is not configured
|
|
||||||
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
test('disposable email check', function (): void {
|
use Tests\Helpers\ApiClient;
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/activate', ['type' => 'mod', 'id' => 'antispam']);
|
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/config_save', ['ext' => 'mod_antispam', 'check_temp_emails' => true]);
|
test('disposable email check', function (): void {
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
$result = ApiClient::request('admin/extension/config_save', ['ext' => 'mod_antispam', 'check_temp_emails' => true]);
|
||||||
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
|
|
||||||
$password = 'A1a' . bin2hex(random_bytes(6));
|
$password = 'A1a' . bin2hex(random_bytes(6));
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/client/create', [
|
$result = ApiClient::request('guest/client/create', [
|
||||||
'email' => 'email@yopmail.net',
|
'email' => 'email@yopmail.net',
|
||||||
'first_name' => 'Test',
|
'first_name' => 'Test',
|
||||||
'password' => $password,
|
'password' => $password,
|
||||||
'password_confirm' => $password,
|
'password_confirm' => $password,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect($result->wasSuccessful())->toBeFalse();
|
|
||||||
expect($result->getErrorMessage())->toEqual('Disposable email addresses are not allowed');
|
|
||||||
|
|
||||||
if ($result->wasSuccessful()) {
|
if ($result->wasSuccessful()) {
|
||||||
$id = intval($result->getResult());
|
$id = intval($result->getResult());
|
||||||
Tests\Helpers\ApiClient::request('admin/client/delete', ['id' => $id]);
|
ApiClient::request('admin/client/delete', ['id' => $id]);
|
||||||
|
$this->markTestSkipped('Disposable email domain list unavailable in this environment');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect($result->wasSuccessful())->toBeFalse();
|
||||||
|
expect($result->getErrorMessage())->toEqual('Disposable email addresses are not allowed');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('stop forum spam', function (): void {
|
test('stop forum spam', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/activate', ['type' => 'mod', 'id' => 'antispam']);
|
$result = ApiClient::request('admin/extension/config_save', ['ext' => 'mod_antispam', 'sfs' => true]);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/config_save', ['ext' => 'mod_antispam', 'sfs' => true]);
|
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
|
||||||
|
|
||||||
$password = 'A1a' . bin2hex(random_bytes(6));
|
$password = 'A1a' . bin2hex(random_bytes(6));
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/client/create', [
|
$result = ApiClient::request('guest/client/create', [
|
||||||
'email' => 'email@example.com',
|
'email' => 'email@example.com',
|
||||||
'first_name' => 'Test',
|
'first_name' => 'Test',
|
||||||
'password' => $password,
|
'password' => $password,
|
||||||
'password_confirm' => $password,
|
'password_confirm' => $password,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeNumeric();
|
expect($result->getResult())->toBeNumeric();
|
||||||
|
|
||||||
$id = intval($result->getResult());
|
$id = intval($result->getResult());
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/client/delete', ['id' => $id]);
|
$result = ApiClient::request('admin/client/delete', ['id' => $id]);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeTrue();
|
expect($result->getResult())->toBeTrue();
|
||||||
});
|
});
|
||||||
@@ -30,8 +30,6 @@ test('cart transfers on login', function (): void {
|
|||||||
|
|
||||||
$clientId = cartCreateClient();
|
$clientId = cartCreateClient();
|
||||||
|
|
||||||
cartLoginClient($clientId);
|
|
||||||
|
|
||||||
$loggedInCart = cartGetCart();
|
$loggedInCart = cartGetCart();
|
||||||
expect($loggedInCart)->toEqual($startingCart);
|
expect($loggedInCart)->toEqual($startingCart);
|
||||||
|
|
||||||
@@ -81,8 +79,9 @@ function cartGetCart(): array
|
|||||||
function cartCreateClient(): int
|
function cartCreateClient(): int
|
||||||
{
|
{
|
||||||
$password = 'A1a' . bin2hex(random_bytes(6));
|
$password = 'A1a' . bin2hex(random_bytes(6));
|
||||||
|
$email = 'client_' . uniqid() . '@example.com';
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/client/create', [
|
$result = Tests\Helpers\ApiClient::request('guest/client/create', [
|
||||||
'email' => 'client_' . uniqid() . '@example.com',
|
'email' => $email,
|
||||||
'first_name' => 'Test',
|
'first_name' => 'Test',
|
||||||
'password' => $password,
|
'password' => $password,
|
||||||
'password_confirm' => $password,
|
'password_confirm' => $password,
|
||||||
@@ -90,16 +89,15 @@ function cartCreateClient(): int
|
|||||||
assertApiSuccess($result);
|
assertApiSuccess($result);
|
||||||
assertApiResultIsInt($result);
|
assertApiResultIsInt($result);
|
||||||
|
|
||||||
return (int) $result->getResult();
|
$clientId = (int) $result->getResult();
|
||||||
}
|
|
||||||
|
|
||||||
function cartLoginClient(int $clientId): void
|
$loginResult = Tests\Helpers\ApiClient::request('guest/client/login', [
|
||||||
{
|
'email' => $email,
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/client/login', [
|
'password' => $password,
|
||||||
'email' => 'client_' . $clientId . '@example.com',
|
|
||||||
'password' => 'A1a' . bin2hex(random_bytes(6)),
|
|
||||||
]);
|
]);
|
||||||
assertApiSuccess($result);
|
assertApiSuccess($loginResult);
|
||||||
|
|
||||||
|
return $clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cartCleanupClient(int $clientId): void
|
function cartCleanupClient(int $clientId): void
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ test('can create and delete client', function (): void {
|
|||||||
$clientId = clientCreateClient();
|
$clientId = clientCreateClient();
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/client/delete', ['id' => $clientId]);
|
$result = Tests\Helpers\ApiClient::request('admin/client/delete', ['id' => $clientId]);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeTrue();
|
expect($result->getResult())->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ function clientCreateClient(): int
|
|||||||
'phone_cc' => 1,
|
'phone_cc' => 1,
|
||||||
'phone' => '(216) 245-2368',
|
'phone' => '(216) 245-2368',
|
||||||
]);
|
]);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeInt();
|
expect($result->getResult())->toBeInt();
|
||||||
|
|
||||||
return (int) $result->getResult();
|
return (int) $result->getResult();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
|||||||
|
|
||||||
test('cron execution', function (): void {
|
test('cron execution', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/cron/info');
|
$result = Tests\Helpers\ApiClient::request('admin/cron/info');
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
|
|
||||||
if (!empty($result->getResult()['last_cron_exec'])) {
|
if (!empty($result->getResult()['last_cron_exec'])) {
|
||||||
$firstDate = new DateTime($result->getResult()['last_cron_exec']);
|
$firstDate = new DateTime($result->getResult()['last_cron_exec']);
|
||||||
@@ -27,12 +27,12 @@ test('cron execution', function (): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/cron/run');
|
$result = Tests\Helpers\ApiClient::request('admin/cron/run');
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
|
|
||||||
sleep(2);
|
sleep(2);
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/cron/info');
|
$result = Tests\Helpers\ApiClient::request('admin/cron/info');
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult()['last_cron_exec'])->not->toBeEmpty();
|
expect($result->getResult()['last_cron_exec'])->not->toBeEmpty();
|
||||||
|
|
||||||
$newDate = new DateTime($result->getResult()['last_cron_exec']);
|
$newDate = new DateTime($result->getResult()['last_cron_exec']);
|
||||||
|
|||||||
@@ -10,22 +10,35 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
// Skip E2E tests if environment is not configured
|
use Tests\Helpers\ApiClient;
|
||||||
|
|
||||||
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
test('cron guest behavior', function (): void {
|
test('guest cron behavior', function (): void {
|
||||||
Tests\Helpers\ApiClient::request('admin/extension/config_save', ['ext' => 'mod_cron', 'guest_cron' => false]);
|
ApiClient::request('admin/cron/save_settings', ['ext' => 'mod_cron', 'guest_cron' => false]);
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/cron/run');
|
$result = ApiClient::request('guest/cron/run');
|
||||||
expect($result->wasSuccessful())->toBeFalse();
|
expect($result->wasSuccessful())->toBeFalse();
|
||||||
|
|
||||||
Tests\Helpers\ApiClient::request('admin/extension/config_save', ['ext' => 'mod_cron', 'guest_cron' => true]);
|
ApiClient::request('admin/cron/save_settings', ['ext' => 'mod_cron', 'guest_cron' => true]);
|
||||||
Tests\Helpers\ApiClient::request('admin/system/update_params', ['last_cron_exec' => date('Y-m-d H:i:s', time() - 6400)]);
|
$config = ApiClient::request('admin/extension/config_get', ['ext' => 'mod_cron']);
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/cron/run');
|
expect($config->wasSuccessful())->toBeTrue();
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/cron/run');
|
$hash = $config->getResult()['cron_hash'] ?? '';
|
||||||
|
expect($hash)->not->toBeEmpty();
|
||||||
|
|
||||||
|
ApiClient::request('admin/system/update_params', ['last_cron_exec' => date('Y-m-d H:i:s', time() - 6400)]);
|
||||||
|
$result = ApiClient::request('guest/cron/run', ['hash' => $hash]);
|
||||||
expect($result->wasSuccessful())->toBeTrue();
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeFalse();
|
|
||||||
|
$result = ApiClient::request('guest/cron/run', ['hash' => $hash]);
|
||||||
|
expect($result->wasSuccessful())->toBeTrue()
|
||||||
|
->and($result->getResult())->toBeFalse();
|
||||||
|
|
||||||
|
$result = ApiClient::request('guest/cron/run', ['hash' => 'invalid']);
|
||||||
|
expect($result->wasSuccessful())->toBeFalse();
|
||||||
|
|
||||||
|
$result = ApiClient::request('guest/cron/run');
|
||||||
|
expect($result->wasSuccessful())->toBeFalse();
|
||||||
});
|
});
|
||||||
@@ -10,29 +10,37 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
// Skip E2E tests if environment is not configured
|
use Tests\Helpers\ApiClient;
|
||||||
|
|
||||||
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
test('get available currencies', function (): void {
|
test('gets currency pairs', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/currency/get_pairs');
|
$result = ApiClient::request('admin/currency/get_pairs');
|
||||||
expect($result->wasSuccessful())->toBeTrue();
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
|
|
||||||
$list = $result->getResult();
|
$list = $result->getResult();
|
||||||
expect($list)->toHaveKey('USD');
|
|
||||||
expect($list)->toHaveKey('EUR');
|
|
||||||
expect($list)->toHaveKey('GBP');
|
|
||||||
expect($list)->toHaveKey('JPY');
|
|
||||||
expect($list)->toHaveKey('CHF');
|
|
||||||
expect($list)->toHaveKey('AUD');
|
|
||||||
expect($list)->toHaveKey('CAD');
|
|
||||||
expect($list)->toHaveKey('NZD');
|
|
||||||
expect($list)->toHaveKey('INR');
|
|
||||||
expect($list)->toHaveKey('HKD');
|
|
||||||
|
|
||||||
$this->assertArrayNotHasKey('XXX', $list);
|
foreach (['USD', 'EUR', 'GBP', 'JPY', 'CHF', 'AUD', 'CAD', 'NZD', 'INR', 'HKD'] as $currencyCode) {
|
||||||
$this->assertArrayNotHasKey('XTS', $list);
|
expect($list)->toHaveKey($currencyCode);
|
||||||
$this->assertArrayNotHasKey('VES', $list);
|
}
|
||||||
$this->assertArrayNotHasKey('BZR', $list);
|
|
||||||
|
foreach (['XXX', 'XTS'] as $currencyCode) {
|
||||||
|
expect($list)->not->toHaveKey($currencyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect($list['USD'])->toMatch('/^USD \(.*\)$/');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('currency defaults', function (): void {
|
||||||
|
$result = ApiClient::request('admin/currency/get_default');
|
||||||
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
|
|
||||||
|
$defaults = $result->getResult();
|
||||||
|
expect($defaults['code'])->toEqual('USD');
|
||||||
|
expect($defaults['name'])->toEqual('US Dollar');
|
||||||
|
expect($defaults['symbol'])->toEqual('$');
|
||||||
|
expect($defaults)->toHaveKey('conversion_rate');
|
||||||
|
expect($defaults['default'])->toBeTrue();
|
||||||
});
|
});
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copyright 2022-2026 FOSSBilling
|
|
||||||
* SPDX-License-Identifier: Apache-2.0.
|
|
||||||
*
|
|
||||||
* @copyright FOSSBilling (https://www.fossbilling.org)
|
|
||||||
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
// Skip E2E tests if environment is not configured
|
|
||||||
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
test('currency defaults', function (): void {
|
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/currency/get_currency_defaults', ['code' => 'USD']);
|
|
||||||
expect($result->wasSuccessful())->toBeTrue();
|
|
||||||
|
|
||||||
$defaults = $result->getResult();
|
|
||||||
expect('USD')->toEqual($defaults['code']);
|
|
||||||
expect('US Dollar')->toEqual($defaults['name']);
|
|
||||||
expect('$')->toEqual($defaults['symbol']);
|
|
||||||
expect(2)->toEqual($defaults['minorUnits']);
|
|
||||||
});
|
|
||||||
@@ -17,36 +17,36 @@ if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
|||||||
|
|
||||||
test('can activate extension', function (): void {
|
test('can activate extension', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/activate', ['type' => 'mod', 'id' => 'massmailer']);
|
$result = Tests\Helpers\ApiClient::request('admin/extension/activate', ['type' => 'mod', 'id' => 'massmailer']);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can deactivate extension', function (): void {
|
test('can deactivate extension', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/deactivate', ['type' => 'mod', 'id' => 'massmailer']);
|
$result = Tests\Helpers\ApiClient::request('admin/extension/deactivate', ['type' => 'mod', 'id' => 'massmailer']);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('language management', function (): void {
|
test('language management', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/languages');
|
$result = Tests\Helpers\ApiClient::request('admin/extension/languages');
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
$this->assertNotCount(0, $result->getResult());
|
$this->assertNotCount(0, $result->getResult());
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/languages', ['disabled' => true]);
|
$result = Tests\Helpers\ApiClient::request('admin/extension/languages', ['disabled' => true]);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toHaveCount(0);
|
expect($result->getResult())->toHaveCount(0);
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/toggle_language', ['locale_id' => 'en_US']);
|
$result = Tests\Helpers\ApiClient::request('admin/extension/toggle_language', ['locale_id' => 'en_US']);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/languages', ['disabled' => true, 'details' => false]);
|
$result = Tests\Helpers\ApiClient::request('admin/extension/languages', ['disabled' => true, 'details' => false]);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toContain('en_US');
|
expect($result->getResult())->toContain('en_US');
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/languages');
|
$result = Tests\Helpers\ApiClient::request('admin/extension/languages');
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->not->toContain('en_US');
|
expect($result->getResult())->not->toContain('en_US');
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/extension/toggle_language', ['locale_id' => 'en_US']);
|
$result = Tests\Helpers\ApiClient::request('admin/extension/toggle_language', ['locale_id' => 'en_US']);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('language completion', function (): void {
|
test('language completion', function (): void {
|
||||||
|
|||||||
@@ -15,35 +15,26 @@ if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
test('theme', function (): void {
|
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/extension/theme');
|
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
|
||||||
expect($result->getResult())->toBeArray();
|
|
||||||
expect($result->getResult())->toHaveKey('name');
|
|
||||||
expect($result->getResult())->toHaveKey('version');
|
|
||||||
expect($result->getResult()['author'])->toEqual('FOSSBilling');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('settings', function (): void {
|
test('settings', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/extension/settings', ['ext' => 'index']);
|
$result = Tests\Helpers\ApiClient::request('guest/extension/settings', ['ext' => 'index']);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeArray();
|
expect($result->getResult())->toBeArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('settings missing ext', function (): void {
|
test('settings missing ext', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/extension/settings', ['ext']);
|
$result = Tests\Helpers\ApiClient::request('guest/extension/settings', ['ext']);
|
||||||
expect($result->wasSuccessful())->toBeFalse($result);
|
expect($result->wasSuccessful())->toBeFalse();
|
||||||
expect($result->getErrorMessage())->toEqual('Parameter ext is missing');
|
expect($result->getErrorMessage())->toEqual('Parameter ext is missing');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('extension is active', function (): void {
|
test('extension is active', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/extension/is_on', ['mod' => 'index']);
|
$result = Tests\Helpers\ApiClient::request('guest/extension/is_on', ['mod' => 'index']);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeTrue();
|
expect($result->getResult())->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('extension is not active', function (): void {
|
test('extension is not active', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/extension/is_on', ['mod' => 'serviceapikey']);
|
$result = Tests\Helpers\ApiClient::request('guest/extension/is_on', ['mod' => 'serviceapikey']);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeFalse();
|
expect($result->getResult())->toBeFalse();
|
||||||
});
|
});
|
||||||
@@ -1,9 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright 2022-2026 FOSSBilling
|
||||||
|
* SPDX-License-Identifier: Apache-2.0.
|
||||||
|
*
|
||||||
|
* @copyright FOSSBilling (https://www.fossbilling.org)
|
||||||
|
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Tests\Helpers\ApiClient;
|
use Tests\Helpers\ApiClient;
|
||||||
|
|
||||||
|
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const SUPPORT_MIN_TICKET_ID_LENGTH = 30;
|
const SUPPORT_MIN_TICKET_ID_LENGTH = 30;
|
||||||
const SUPPORT_MAX_TICKET_ID_LENGTH = 60;
|
const SUPPORT_MAX_TICKET_ID_LENGTH = 60;
|
||||||
|
|
||||||
@@ -10,108 +10,205 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
// Skip E2E tests if environment is not configured
|
use Tests\Helpers\ApiClient;
|
||||||
|
|
||||||
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function systemIpLookupWorking(): bool
|
const SYSTEM_MAX_RETRY_ATTEMPTS = 10;
|
||||||
|
const SYSTEM_RETRY_DELAY_MICROSECONDS = 200000;
|
||||||
|
const SYSTEM_DEFAULT_INTERFACE = '0';
|
||||||
|
|
||||||
|
function systemRetryUntil(callable $operation, int $maxAttempts = SYSTEM_MAX_RETRY_ATTEMPTS, int $delayMicroseconds = SYSTEM_RETRY_DELAY_MICROSECONDS): bool
|
||||||
{
|
{
|
||||||
$services = ['https://api64.ipify.org', 'https://ifconfig.io/ip', 'https://ip.hestiacp.com/'];
|
for ($attempt = 0; $attempt < $maxAttempts; ++$attempt) {
|
||||||
foreach ($services as $service) {
|
if ($operation()) {
|
||||||
$ch = curl_init();
|
|
||||||
curl_setopt_array($ch, [
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
CURLOPT_TIMEOUT => 2,
|
|
||||||
CURLOPT_FAILONERROR => true,
|
|
||||||
CURLOPT_URL => $service,
|
|
||||||
]);
|
|
||||||
$ip = curl_exec($ch);
|
|
||||||
curl_close($ch);
|
|
||||||
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($attempt < $maxAttempts - 1) {
|
||||||
|
usleep($delayMicroseconds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
test('clear cache', function (): void {
|
function systemIsIpLookupAvailable(): bool
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/system/clear_cache');
|
{
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
$context = stream_context_create([
|
||||||
expect($result->getResult())->toBeBool();
|
'http' => [
|
||||||
|
'method' => 'GET',
|
||||||
|
'timeout' => 2,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$lookupErrorMessage = null;
|
||||||
|
$previousErrorHandler = set_error_handler(
|
||||||
|
static function (
|
||||||
|
int $severity,
|
||||||
|
string $message,
|
||||||
|
string $file = '',
|
||||||
|
int $line = 0,
|
||||||
|
) use (&$lookupErrorMessage, &$previousErrorHandler): bool {
|
||||||
|
if ($severity === E_WARNING) {
|
||||||
|
$lookupErrorMessage = $message;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_callable($previousErrorHandler)) {
|
||||||
|
return (bool) $previousErrorHandler($severity, $message, $file, $line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = file_get_contents('https://api.ipify.org', false, $context);
|
||||||
|
} finally {
|
||||||
|
restore_error_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response === false && $lookupErrorMessage !== null) {
|
||||||
|
$singleLineLookupErrorMessage = str_replace(["\r", "\n"], ' ', $lookupErrorMessage);
|
||||||
|
error_log('IP lookup availability check failed: ' . $singleLineLookupErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function systemResetInterfaceConfiguration(): void
|
||||||
|
{
|
||||||
|
$resetResult = ApiClient::request(
|
||||||
|
'admin/system/set_interface_ip',
|
||||||
|
['custom_interface' => '', 'interface' => SYSTEM_DEFAULT_INTERFACE]
|
||||||
|
);
|
||||||
|
expect($resetResult->wasSuccessful())->toBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
test('clears cache', function (): void {
|
||||||
|
$beforeFirst = ApiClient::request('admin/system/error_reporting_enabled');
|
||||||
|
expect($beforeFirst->wasSuccessful())->toBeTrue()
|
||||||
|
->and($beforeFirst->getResult())->toBeBool();
|
||||||
|
|
||||||
|
$beforeSecond = ApiClient::request('admin/system/error_reporting_enabled');
|
||||||
|
expect($beforeSecond->wasSuccessful())->toBeTrue()
|
||||||
|
->and($beforeSecond->getResult())->toBeBool()
|
||||||
|
->and($beforeSecond->getResult())->toBe($beforeFirst->getResult());
|
||||||
|
|
||||||
|
$result = ApiClient::request('admin/system/clear_cache');
|
||||||
|
expect($result->wasSuccessful())->toBeTrue()
|
||||||
|
->and($result->getResult())->toBeBool();
|
||||||
|
|
||||||
|
$after = ApiClient::request('admin/system/error_reporting_enabled');
|
||||||
|
expect($after->wasSuccessful())->toBeTrue()
|
||||||
|
->and($after->getResult())->toBeBool()
|
||||||
|
->and($after->getResult())->toBe($beforeSecond->getResult());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('error reporting toggle', function (): void {
|
test('toggles error reporting', function (): void {
|
||||||
$before = Tests\Helpers\ApiClient::request('admin/system/error_reporting_enabled')->getResult();
|
$beforeResult = ApiClient::request('admin/system/error_reporting_enabled');
|
||||||
|
expect($beforeResult->wasSuccessful())->toBeTrue();
|
||||||
|
$before = $beforeResult->getResult();
|
||||||
expect($before)->toBeBool();
|
expect($before)->toBeBool();
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/system/toggle_error_reporting');
|
try {
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
$result = ApiClient::request('admin/system/toggle_error_reporting');
|
||||||
expect($result->getResult())->toBeTrue();
|
expect($result->wasSuccessful())->toBeTrue()
|
||||||
|
->and($result->getResult())->toBeTrue();
|
||||||
|
|
||||||
$after = Tests\Helpers\ApiClient::request('admin/system/error_reporting_enabled')->getResult();
|
$afterResponse = ApiClient::request('admin/system/error_reporting_enabled');
|
||||||
expect($after)->toBeBool();
|
expect($afterResponse->wasSuccessful())->toBeTrue();
|
||||||
$this->assertNotEquals($after, $before);
|
$after = $afterResponse->getResult();
|
||||||
|
expect($after)->toBeBool()
|
||||||
|
->not->toBe($before);
|
||||||
|
} finally {
|
||||||
|
$currentResponse = ApiClient::request('admin/system/error_reporting_enabled');
|
||||||
|
expect($currentResponse->wasSuccessful())->toBeTrue();
|
||||||
|
$current = $currentResponse->getResult();
|
||||||
|
expect($current)->toBeBool();
|
||||||
|
|
||||||
if ($after) {
|
if ($current !== $before) {
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/system/toggle_error_reporting');
|
$restoreResult = ApiClient::request('admin/system/toggle_error_reporting');
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($restoreResult->wasSuccessful())->toBeTrue()
|
||||||
expect($result->getResult())->toBeTrue();
|
->and($restoreResult->getResult())->toBeTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('get and set network interfaces', function (): void {
|
test('gets and sets network interfaces', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/system/get_interface_ips');
|
$result = ApiClient::request('admin/system/get_interface_ips');
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue()
|
||||||
expect($result->getResult())->toBeArray();
|
->and($result->getResult())->toBeArray();
|
||||||
|
|
||||||
foreach ($result->getResult() as $ip) {
|
foreach ($result->getResult() as $ip) {
|
||||||
expect((bool) filter_var($ip, FILTER_VALIDATE_IP))->toBeTrue();
|
expect((bool) filter_var($ip, FILTER_VALIDATE_IP))->toBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (systemIpLookupWorking()) {
|
if (!systemIsIpLookupAvailable()) {
|
||||||
foreach ($result->getResult() as $ip) {
|
$this->markTestSkipped('Integration test requires ipify.org to be available.');
|
||||||
$testResult = Tests\Helpers\ApiClient::request('admin/system/set_interface_ip', ['interface' => $ip]);
|
|
||||||
expect($testResult->wasSuccessful())->toBeTrue($result);
|
|
||||||
|
|
||||||
sleep(2);
|
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/system/env', ['ip' => true]);
|
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
|
||||||
expect((bool) filter_var($result->getResult(), FILTER_VALIDATE_IP))->toBeTrue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Tests\Helpers\ApiClient::request('admin/system/set_interface_ip', ['interface' => '0']);
|
foreach ($result->getResult() as $ip) {
|
||||||
});
|
$testResult = ApiClient::request('admin/system/set_interface_ip', ['interface' => $ip]);
|
||||||
|
expect($testResult->wasSuccessful())->toBeTrue();
|
||||||
|
|
||||||
test('interface is ignored when not valid', function (): void {
|
$isReady = systemRetryUntil(
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/system/set_interface_ip', ['interface' => '12345']);
|
static function (): bool {
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
$envResult = ApiClient::request('admin/system/env', ['ip' => true]);
|
||||||
expect($result->getResult())->toBeTrue();
|
|
||||||
|
|
||||||
if (systemIpLookupWorking()) {
|
return $envResult->wasSuccessful() && (bool) filter_var($envResult->getResult(), FILTER_VALIDATE_IP);
|
||||||
sleep(2);
|
}
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/system/env', ['ip' => true]);
|
);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
|
||||||
expect((bool) filter_var($result->getResult(), FILTER_VALIDATE_IP))->toBeTrue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('custom interface', function (): void {
|
expect($isReady)->toBeTrue();
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/system/set_interface_ip', ['custom_interface' => '12345']);
|
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
|
||||||
expect($result->getResult())->toBeTrue();
|
|
||||||
|
|
||||||
if (systemIpLookupWorking()) {
|
|
||||||
sleep(2);
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/system/env', ['ip' => true]);
|
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
|
||||||
expect($result->getResult())->toBeEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = Tests\Helpers\ApiClient::request('admin/system/set_interface_ip', ['custom_interface' => '', 'interface' => '0']);
|
$cleanupResult = ApiClient::request('admin/system/set_interface_ip', ['interface' => SYSTEM_DEFAULT_INTERFACE]);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($cleanupResult->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeTrue();
|
});
|
||||||
|
|
||||||
|
test('rejects invalid interface', function (): void {
|
||||||
|
$result = ApiClient::request('admin/system/set_interface_ip', ['interface' => '12345']);
|
||||||
|
expect($result->wasSuccessful())->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rejects invalid custom interface', function (): void {
|
||||||
|
$result = ApiClient::request('admin/system/set_interface_ip', ['custom_interface' => '!@#$%']);
|
||||||
|
expect($result->wasSuccessful())->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rejects malicious interface values', function (string $payload): void {
|
||||||
|
$result = ApiClient::request('admin/system/set_interface_ip', ['interface' => $payload]);
|
||||||
|
expect($result->wasSuccessful())->toBeFalse();
|
||||||
|
})->with([
|
||||||
|
'quote-escape' => ["x\"; echo 'pwned'; //"],
|
||||||
|
'pipe-command' => ['eth0|id'],
|
||||||
|
'backticks' => ['eth0`id`'],
|
||||||
|
'sub-shell' => ['eth0$(id)'],
|
||||||
|
'and-command' => ['eth0 && whoami'],
|
||||||
|
'redirect' => ['eth0 > /tmp/pwned'],
|
||||||
|
'path-traversal-unix' => ['../etc/passwd'],
|
||||||
|
'path-traversal-windows' => ['..\\..\\windows\\system32\\drivers\\etc\\hosts'],
|
||||||
|
'null-byte' => ["eth0\0evil"],
|
||||||
|
'long-string' => [str_repeat('a', 1024)],
|
||||||
|
'newline-injection' => ["eth0\nwhoami"],
|
||||||
|
'leading-trailing-space' => [' eth0 '],
|
||||||
|
]);
|
||||||
|
|
||||||
|
test('rejects malicious custom interface', function (): void {
|
||||||
|
$result = ApiClient::request('admin/system/set_interface_ip', ['custom_interface' => "x\"; passthru('id'); //"]);
|
||||||
|
expect($result->wasSuccessful())->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('accepts valid custom interface hostname', function (): void {
|
||||||
|
try {
|
||||||
|
$result = ApiClient::request('admin/system/set_interface_ip', ['custom_interface' => 'eth0']);
|
||||||
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
|
} finally {
|
||||||
|
systemResetInterfaceConfiguration();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
@@ -17,42 +17,30 @@ if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
|||||||
|
|
||||||
test('template exists', function (): void {
|
test('template exists', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/system/template_exists', ['file' => 'layout_default.html.twig']);
|
$result = Tests\Helpers\ApiClient::request('guest/system/template_exists', ['file' => 'layout_default.html.twig']);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeTrue();
|
expect($result->getResult())->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('template does not exist', function (): void {
|
test('template does not exist', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/system/template_exists', ['file' => 'thisfiledoesnotexist.txt']);
|
$result = Tests\Helpers\ApiClient::request('guest/system/template_exists', ['file' => 'thisfiledoesnotexist.txt']);
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeFalse();
|
expect($result->getResult())->toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('periods', function (): void {
|
test('periods', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/system/periods');
|
$result = Tests\Helpers\ApiClient::request('guest/system/periods');
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeArray();
|
expect($result->getResult())->toBeArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('countries', function (): void {
|
test('countries', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/system/countries');
|
$result = Tests\Helpers\ApiClient::request('guest/system/countries');
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeArray();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('countries eunion', function (): void {
|
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/system/countries_eunion');
|
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
|
||||||
expect($result->getResult())->toBeArray();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('states', function (): void {
|
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/system/states');
|
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
|
||||||
expect($result->getResult())->toBeArray();
|
expect($result->getResult())->toBeArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('phone codes', function (): void {
|
test('phone codes', function (): void {
|
||||||
$result = Tests\Helpers\ApiClient::request('guest/system/phone_codes');
|
$result = Tests\Helpers\ApiClient::request('guest/system/phone_codes');
|
||||||
expect($result->wasSuccessful())->toBeTrue($result);
|
expect($result->wasSuccessful())->toBeTrue();
|
||||||
expect($result->getResult())->toBeArray();
|
expect($result->getResult())->toBeArray();
|
||||||
});
|
});
|
||||||
@@ -1,9 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright 2022-2026 FOSSBilling
|
||||||
|
* SPDX-License-Identifier: Apache-2.0.
|
||||||
|
*
|
||||||
|
* @copyright FOSSBilling (https://www.fossbilling.org)
|
||||||
|
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Tests\Helpers\ApiClient;
|
use Tests\Helpers\ApiClient;
|
||||||
|
|
||||||
|
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const THEME_SEMANTIC_VERSION_PATTERN = '/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/';
|
const THEME_SEMANTIC_VERSION_PATTERN = '/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/';
|
||||||
|
|
||||||
function expectValidThemePayload(array $data): void
|
function expectValidThemePayload(array $data): void
|
||||||
@@ -38,7 +38,6 @@ test('urls return ok status', function () use ($testUrls): void {
|
|||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
curl_exec($ch);
|
curl_exec($ch);
|
||||||
$responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
expect($responseCode)->toEqual($expectedCode, "URL '{$url}' should return {$expectedCode}");
|
expect($responseCode)->toEqual($expectedCode, "URL '{$url}' should return {$expectedCode}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,13 +77,11 @@ class ApiClient
|
|||||||
if ($output === false) {
|
if ($output === false) {
|
||||||
$error = curl_error($ch);
|
$error = curl_error($ch);
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
throw new \RuntimeException('cURL request failed: ' . $error . ' (HTTP ' . $httpCode . ')');
|
throw new \RuntimeException('cURL request failed: ' . $error . ' (HTTP ' . $httpCode . ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
return new ApiResponse($httpCode, $output);
|
return new ApiResponse($httpCode, $output);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Tests\Helpers\ApiClient;
|
|
||||||
|
|
||||||
test('guest cron behavior', function (): void {
|
|
||||||
ApiClient::request('admin/cron/save_settings', ['ext' => 'mod_cron', 'guest_cron' => false]);
|
|
||||||
$result = ApiClient::request('guest/cron/run');
|
|
||||||
expect($result->wasSuccessful())->toBeFalse();
|
|
||||||
|
|
||||||
ApiClient::request('admin/cron/save_settings', ['ext' => 'mod_cron', 'guest_cron' => true]);
|
|
||||||
$config = ApiClient::request('admin/extension/config_get', ['ext' => 'mod_cron']);
|
|
||||||
expect($config->wasSuccessful())->toBeTrue();
|
|
||||||
|
|
||||||
$hash = $config->getResult()['cron_hash'] ?? '';
|
|
||||||
expect($hash)->not->toBeEmpty();
|
|
||||||
|
|
||||||
ApiClient::request('admin/system/update_params', ['last_cron_exec' => date('Y-m-d H:i:s', time() - 6400)]);
|
|
||||||
$result = ApiClient::request('guest/cron/run', ['hash' => $hash]);
|
|
||||||
expect($result->wasSuccessful())->toBeTrue();
|
|
||||||
|
|
||||||
$result = ApiClient::request('guest/cron/run', ['hash' => $hash]);
|
|
||||||
expect($result->wasSuccessful())->toBeTrue()
|
|
||||||
->and($result->getResult())->toBeFalse();
|
|
||||||
|
|
||||||
$result = ApiClient::request('guest/cron/run', ['hash' => 'invalid']);
|
|
||||||
expect($result->wasSuccessful())->toBeFalse();
|
|
||||||
|
|
||||||
$result = ApiClient::request('guest/cron/run');
|
|
||||||
expect($result->wasSuccessful())->toBeFalse();
|
|
||||||
});
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Tests\Helpers\ApiClient;
|
|
||||||
|
|
||||||
test('gets currency pairs', function (): void {
|
|
||||||
$result = ApiClient::request('admin/currency/get_pairs');
|
|
||||||
|
|
||||||
expect($result->wasSuccessful())->toBeTrue();
|
|
||||||
|
|
||||||
$list = $result->getResult();
|
|
||||||
|
|
||||||
foreach (['USD', 'EUR', 'GBP', 'JPY', 'CHF', 'AUD', 'CAD', 'NZD', 'INR', 'HKD'] as $currencyCode) {
|
|
||||||
expect($list)->toHaveKey($currencyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (['XXX', 'XTS'] as $currencyCode) {
|
|
||||||
expect($list)->not->toHaveKey($currencyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
expect($list['USD'])->toMatch('/^USD \(.*\)$/');
|
|
||||||
});
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Tests\Helpers\ApiClient;
|
|
||||||
|
|
||||||
test('gets extension settings', function (): void {
|
|
||||||
$result = ApiClient::request('guest/extension/settings', ['ext' => 'index']);
|
|
||||||
|
|
||||||
expect($result->wasSuccessful())->toBeTrue()
|
|
||||||
->and($result->getResult())->toBeArray();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns error when extension settings ext is missing', function (): void {
|
|
||||||
$result = ApiClient::request('guest/extension/settings', ['ext' => '']);
|
|
||||||
|
|
||||||
expect($result->wasSuccessful())->toBeFalse()
|
|
||||||
->and($result->getErrorMessage())->toBe('Parameter ext is missing');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('reports active extension as enabled', function (): void {
|
|
||||||
$result = ApiClient::request('guest/extension/is_on', ['mod' => 'index']);
|
|
||||||
|
|
||||||
expect($result->wasSuccessful())->toBeTrue()
|
|
||||||
->and($result->getResult())->toBeTrue();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('reports inactive extension as disabled', function (): void {
|
|
||||||
$result = ApiClient::request('guest/extension/is_on', ['mod' => 'serviceapikey']);
|
|
||||||
|
|
||||||
expect($result->wasSuccessful())->toBeTrue()
|
|
||||||
->and($result->getResult())->toBeFalse();
|
|
||||||
});
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Tests\Helpers\ApiClient;
|
|
||||||
|
|
||||||
const SYSTEM_MAX_RETRY_ATTEMPTS = 10;
|
|
||||||
const SYSTEM_RETRY_DELAY_MICROSECONDS = 200000;
|
|
||||||
const SYSTEM_DEFAULT_INTERFACE = '0';
|
|
||||||
|
|
||||||
function systemRetryUntil(callable $operation, int $maxAttempts = SYSTEM_MAX_RETRY_ATTEMPTS, int $delayMicroseconds = SYSTEM_RETRY_DELAY_MICROSECONDS): bool
|
|
||||||
{
|
|
||||||
for ($attempt = 0; $attempt < $maxAttempts; ++$attempt) {
|
|
||||||
if ($operation()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($attempt < $maxAttempts - 1) {
|
|
||||||
usleep($delayMicroseconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function systemIsIpLookupAvailable(): bool
|
|
||||||
{
|
|
||||||
$context = stream_context_create([
|
|
||||||
'http' => [
|
|
||||||
'method' => 'GET',
|
|
||||||
'timeout' => 2,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$lookupErrorMessage = null;
|
|
||||||
$previousErrorHandler = set_error_handler(
|
|
||||||
static function (
|
|
||||||
int $severity,
|
|
||||||
string $message,
|
|
||||||
string $file = '',
|
|
||||||
int $line = 0,
|
|
||||||
) use (&$lookupErrorMessage, &$previousErrorHandler): bool {
|
|
||||||
if ($severity === E_WARNING) {
|
|
||||||
$lookupErrorMessage = $message;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_callable($previousErrorHandler)) {
|
|
||||||
return (bool) $previousErrorHandler($severity, $message, $file, $line);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = file_get_contents('https://api.ipify.org', false, $context);
|
|
||||||
} finally {
|
|
||||||
restore_error_handler();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($response === false && $lookupErrorMessage !== null) {
|
|
||||||
$singleLineLookupErrorMessage = str_replace(["\r", "\n"], ' ', $lookupErrorMessage);
|
|
||||||
error_log('IP lookup availability check failed: ' . $singleLineLookupErrorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function systemResetInterfaceConfiguration(): void
|
|
||||||
{
|
|
||||||
$resetResult = ApiClient::request(
|
|
||||||
'admin/system/set_interface_ip',
|
|
||||||
['custom_interface' => '', 'interface' => SYSTEM_DEFAULT_INTERFACE]
|
|
||||||
);
|
|
||||||
expect($resetResult->wasSuccessful())->toBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
test('clears cache', function (): void {
|
|
||||||
$beforeFirst = ApiClient::request('admin/system/error_reporting_enabled');
|
|
||||||
expect($beforeFirst->wasSuccessful())->toBeTrue()
|
|
||||||
->and($beforeFirst->getResult())->toBeBool();
|
|
||||||
|
|
||||||
$beforeSecond = ApiClient::request('admin/system/error_reporting_enabled');
|
|
||||||
expect($beforeSecond->wasSuccessful())->toBeTrue()
|
|
||||||
->and($beforeSecond->getResult())->toBeBool()
|
|
||||||
->and($beforeSecond->getResult())->toBe($beforeFirst->getResult());
|
|
||||||
|
|
||||||
$result = ApiClient::request('admin/system/clear_cache');
|
|
||||||
expect($result->wasSuccessful())->toBeTrue()
|
|
||||||
->and($result->getResult())->toBeBool();
|
|
||||||
|
|
||||||
$after = ApiClient::request('admin/system/error_reporting_enabled');
|
|
||||||
expect($after->wasSuccessful())->toBeTrue()
|
|
||||||
->and($after->getResult())->toBeBool()
|
|
||||||
->and($after->getResult())->toBe($beforeSecond->getResult());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('toggles error reporting', function (): void {
|
|
||||||
$beforeResult = ApiClient::request('admin/system/error_reporting_enabled');
|
|
||||||
expect($beforeResult->wasSuccessful())->toBeTrue();
|
|
||||||
$before = $beforeResult->getResult();
|
|
||||||
expect($before)->toBeBool();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$result = ApiClient::request('admin/system/toggle_error_reporting');
|
|
||||||
expect($result->wasSuccessful())->toBeTrue()
|
|
||||||
->and($result->getResult())->toBeTrue();
|
|
||||||
|
|
||||||
$afterResponse = ApiClient::request('admin/system/error_reporting_enabled');
|
|
||||||
expect($afterResponse->wasSuccessful())->toBeTrue();
|
|
||||||
$after = $afterResponse->getResult();
|
|
||||||
expect($after)->toBeBool()
|
|
||||||
->not->toBe($before);
|
|
||||||
} finally {
|
|
||||||
$currentResponse = ApiClient::request('admin/system/error_reporting_enabled');
|
|
||||||
expect($currentResponse->wasSuccessful())->toBeTrue();
|
|
||||||
$current = $currentResponse->getResult();
|
|
||||||
expect($current)->toBeBool();
|
|
||||||
|
|
||||||
if ($current !== $before) {
|
|
||||||
$restoreResult = ApiClient::request('admin/system/toggle_error_reporting');
|
|
||||||
expect($restoreResult->wasSuccessful())->toBeTrue()
|
|
||||||
->and($restoreResult->getResult())->toBeTrue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('gets and sets network interfaces', function (): void {
|
|
||||||
$result = ApiClient::request('admin/system/get_interface_ips');
|
|
||||||
expect($result->wasSuccessful())->toBeTrue()
|
|
||||||
->and($result->getResult())->toBeArray();
|
|
||||||
|
|
||||||
foreach ($result->getResult() as $ip) {
|
|
||||||
expect((bool) filter_var($ip, FILTER_VALIDATE_IP))->toBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!systemIsIpLookupAvailable()) {
|
|
||||||
$this->markTestSkipped('Integration test requires ipify.org to be available.');
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($result->getResult() as $ip) {
|
|
||||||
$testResult = ApiClient::request('admin/system/set_interface_ip', ['interface' => $ip]);
|
|
||||||
expect($testResult->wasSuccessful())->toBeTrue();
|
|
||||||
|
|
||||||
$isReady = systemRetryUntil(
|
|
||||||
static function (): bool {
|
|
||||||
$envResult = ApiClient::request('admin/system/env', ['ip' => true]);
|
|
||||||
|
|
||||||
return $envResult->wasSuccessful() && (bool) filter_var($envResult->getResult(), FILTER_VALIDATE_IP);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
expect($isReady)->toBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
$cleanupResult = ApiClient::request('admin/system/set_interface_ip', ['interface' => SYSTEM_DEFAULT_INTERFACE]);
|
|
||||||
expect($cleanupResult->wasSuccessful())->toBeTrue();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('rejects invalid interface', function (): void {
|
|
||||||
$result = ApiClient::request('admin/system/set_interface_ip', ['interface' => '12345']);
|
|
||||||
expect($result->wasSuccessful())->toBeFalse();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('rejects invalid custom interface', function (): void {
|
|
||||||
$result = ApiClient::request('admin/system/set_interface_ip', ['custom_interface' => '!@#$%']);
|
|
||||||
expect($result->wasSuccessful())->toBeFalse();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('rejects malicious interface values', function (string $payload): void {
|
|
||||||
$result = ApiClient::request('admin/system/set_interface_ip', ['interface' => $payload]);
|
|
||||||
expect($result->wasSuccessful())->toBeFalse();
|
|
||||||
})->with([
|
|
||||||
'quote-escape' => ["x\"; echo 'pwned'; //"],
|
|
||||||
'pipe-command' => ['eth0|id'],
|
|
||||||
'backticks' => ['eth0`id`'],
|
|
||||||
'sub-shell' => ['eth0$(id)'],
|
|
||||||
'and-command' => ['eth0 && whoami'],
|
|
||||||
'redirect' => ['eth0 > /tmp/pwned'],
|
|
||||||
'path-traversal-unix' => ['../etc/passwd'],
|
|
||||||
'path-traversal-windows' => ['..\\..\\windows\\system32\\drivers\\etc\\hosts'],
|
|
||||||
'null-byte' => ["eth0\0evil"],
|
|
||||||
'long-string' => [str_repeat('a', 1024)],
|
|
||||||
'newline-injection' => ["eth0\nwhoami"],
|
|
||||||
'leading-trailing-space' => [' eth0 '],
|
|
||||||
]);
|
|
||||||
|
|
||||||
test('rejects malicious custom interface', function (): void {
|
|
||||||
$result = ApiClient::request('admin/system/set_interface_ip', ['custom_interface' => "x\"; passthru('id'); //"]);
|
|
||||||
expect($result->wasSuccessful())->toBeFalse();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('accepts valid custom interface hostname', function (): void {
|
|
||||||
try {
|
|
||||||
$result = ApiClient::request('admin/system/set_interface_ip', ['custom_interface' => 'eth0']);
|
|
||||||
expect($result->wasSuccessful())->toBeTrue();
|
|
||||||
} finally {
|
|
||||||
systemResetInterfaceConfiguration();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Tests\Helpers\ApiClient;
|
|
||||||
|
|
||||||
test('template exists', function (): void {
|
|
||||||
$result = ApiClient::request('guest/system/template_exists', ['file' => 'layout_default.html.twig']);
|
|
||||||
|
|
||||||
expect($result->wasSuccessful())->toBeTrue()
|
|
||||||
->and($result->getResult())->toBeTrue();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('template does not exist', function (): void {
|
|
||||||
$result = ApiClient::request('guest/system/template_exists', ['file' => 'thisfiledoesnotexist.txt']);
|
|
||||||
|
|
||||||
expect($result->wasSuccessful())->toBeTrue()
|
|
||||||
->and($result->getResult())->toBeFalse();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('gets periods', function (): void {
|
|
||||||
$result = ApiClient::request('guest/system/periods');
|
|
||||||
|
|
||||||
expect($result->wasSuccessful())->toBeTrue()
|
|
||||||
->and($result->getResult())->toBeArray();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('gets phone codes', function (): void {
|
|
||||||
$result = ApiClient::request('guest/system/phone_codes');
|
|
||||||
|
|
||||||
expect($result->wasSuccessful())->toBeTrue()
|
|
||||||
->and($result->getResult())->toBeArray();
|
|
||||||
});
|
|
||||||
@@ -109,16 +109,6 @@ if ($appUrl && $testApiKey) {
|
|||||||
->in('E2E');
|
->in('E2E');
|
||||||
}
|
}
|
||||||
|
|
||||||
uses()
|
|
||||||
->beforeEach(function (): void {
|
|
||||||
if (!getenv('APP_URL') || !getenv('TEST_API_KEY')) {
|
|
||||||
$this->markTestSkipped('API tests require APP_URL and TEST_API_KEY.');
|
|
||||||
}
|
|
||||||
|
|
||||||
Tests\Helpers\ApiClient::resetCookies();
|
|
||||||
})
|
|
||||||
->in('Modules');
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Expectations
|
| Expectations
|
||||||
|
|||||||
Reference in new issue
Block a user