mirror of
https://github.com/FOSSBilling/FOSSBilling.git
synced 2026-11-03 02:27:21 +09:00
* Initial plan * Fix duplicate getHttpCode/getStatusCode, improve wasSuccessful result check, and test email validation via Validate class --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Adam Daley <admdly@users.noreply.github.com>
234 lines
6.3 KiB
PHP
234 lines
6.3 KiB
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);
|
|
|
|
namespace Tests\Helpers;
|
|
|
|
use Symfony\Component\Filesystem\Filesystem;
|
|
use Symfony\Component\Filesystem\Path;
|
|
|
|
/**
|
|
* API client for E2E testing.
|
|
*/
|
|
class ApiClient
|
|
{
|
|
private static ?string $baseUrl = null;
|
|
private static ?string $apiKey = null;
|
|
private static string $cookiePath;
|
|
|
|
public static function setBaseUrl(string $url): void
|
|
{
|
|
self::$baseUrl = rtrim($url, '/');
|
|
}
|
|
|
|
public static function setApiKey(string $key): void
|
|
{
|
|
self::$apiKey = $key;
|
|
}
|
|
|
|
private static function getCookiePath(): string
|
|
{
|
|
if (!isset(self::$cookiePath)) {
|
|
self::$cookiePath = Path::join(sys_get_temp_dir(), 'fossbilling_e2e_cookies.txt');
|
|
}
|
|
|
|
return self::$cookiePath;
|
|
}
|
|
|
|
public static function request(
|
|
string $endpoint,
|
|
array $payload = [],
|
|
?string $role = null,
|
|
?string $apiKey = null,
|
|
string $method = 'POST',
|
|
): ApiResponse {
|
|
$baseUrl = self::$baseUrl ?? (getenv('APP_URL') ?: 'http://localhost');
|
|
$baseUrl = rtrim($baseUrl, '/');
|
|
$apiKey ??= self::$apiKey ?? getenv('TEST_API_KEY');
|
|
$role ??= str_starts_with($endpoint, 'admin') ? 'admin' : 'client';
|
|
|
|
$url = rtrim($baseUrl, '/') . '/api/' . ltrim($endpoint, '/');
|
|
|
|
$ch = curl_init();
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_URL => $url,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 30,
|
|
CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
|
|
CURLOPT_COOKIEJAR => self::getCookiePath(),
|
|
CURLOPT_COOKIEFILE => self::getCookiePath(),
|
|
CURLOPT_USERPWD => "$role:$apiKey",
|
|
]);
|
|
|
|
if (strcasecmp($method, 'POST') === 0) {
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload));
|
|
}
|
|
|
|
$output = curl_exec($ch);
|
|
if ($output === false) {
|
|
$error = curl_error($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
throw new \RuntimeException('cURL request failed: ' . $error . ' (HTTP ' . $httpCode . ')');
|
|
}
|
|
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
return new ApiResponse($httpCode, $output);
|
|
}
|
|
|
|
public static function resetCookies(): void
|
|
{
|
|
$filesystem = new Filesystem();
|
|
$filesystem->remove(self::getCookiePath());
|
|
}
|
|
|
|
public static function get(string $endpoint, array $payload = [], ?string $role = null): ApiResponse
|
|
{
|
|
return self::request($endpoint, $payload, $role ?? 'admin', null, 'GET');
|
|
}
|
|
|
|
public static function post(string $endpoint, array $payload = [], ?string $role = null): ApiResponse
|
|
{
|
|
return self::request($endpoint, $payload, $role ?? 'admin', null, 'POST');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* API Response wrapper for E2E tests.
|
|
*/
|
|
class ApiResponse
|
|
{
|
|
private array $decodedResponse = [];
|
|
|
|
public function __construct(
|
|
private readonly int $code,
|
|
private readonly string $rawResponse,
|
|
) {
|
|
$decoded = json_decode($this->rawResponse, true);
|
|
if (json_last_error() !== JSON_ERROR_NONE || !is_array($decoded)) {
|
|
$preview = strlen($this->rawResponse) > 500 ? substr($this->rawResponse, 0, 500) . '...' : $this->rawResponse;
|
|
|
|
throw new \RuntimeException('Invalid JSON response: ' . json_last_error_msg() . ' (HTTP ' . $this->code . '). Response: ' . $preview);
|
|
}
|
|
$this->decodedResponse = $decoded;
|
|
}
|
|
|
|
public function getHttpCode(): int
|
|
{
|
|
return $this->getStatusCode();
|
|
}
|
|
|
|
public function getStatusCode(): int
|
|
{
|
|
return $this->code;
|
|
}
|
|
|
|
public function getResponse(): array
|
|
{
|
|
return $this->decodedResponse;
|
|
}
|
|
|
|
public function getRawResponse(): string
|
|
{
|
|
return $this->rawResponse;
|
|
}
|
|
|
|
public function wasSuccessful(): bool
|
|
{
|
|
return !empty($this->decodedResponse)
|
|
&& array_key_exists('error', $this->decodedResponse)
|
|
&& empty($this->decodedResponse['error'])
|
|
&& array_key_exists('result', $this->decodedResponse);
|
|
}
|
|
|
|
public function getErrorMessage(): string
|
|
{
|
|
return $this->decodedResponse['error']['message'] ?? 'None';
|
|
}
|
|
|
|
public function getErrorCode(): int
|
|
{
|
|
return (int) ($this->decodedResponse['error']['code'] ?? 0);
|
|
}
|
|
|
|
public function getError(): string
|
|
{
|
|
return $this->getErrorMessage() . ' (Code ' . $this->getErrorCode() . ')';
|
|
}
|
|
|
|
public function getResult(): mixed
|
|
{
|
|
return $this->decodedResponse['result'] ?? null;
|
|
}
|
|
|
|
public function hasResult(): bool
|
|
{
|
|
return array_key_exists('result', $this->decodedResponse);
|
|
}
|
|
|
|
public function isArrayResult(): bool
|
|
{
|
|
return is_array($this->decodedResponse['result'] ?? null);
|
|
}
|
|
|
|
public function isIntResult(): bool
|
|
{
|
|
return is_int($this->decodedResponse['result'] ?? null);
|
|
}
|
|
|
|
public function isBoolResult(): bool
|
|
{
|
|
return is_bool($this->decodedResponse['result'] ?? null);
|
|
}
|
|
|
|
public function isStringResult(): bool
|
|
{
|
|
return is_string($this->decodedResponse['result'] ?? null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function for API requests.
|
|
*/
|
|
function api(string $endpoint, array $payload = [], ?string $role = null): ApiResponse
|
|
{
|
|
return ApiClient::request($endpoint, $payload, $role);
|
|
}
|
|
|
|
/**
|
|
* Assert that an API response was successful.
|
|
*/
|
|
function assertApiSuccess(ApiResponse $response): void
|
|
{
|
|
expect($response->wasSuccessful())
|
|
->toBeTrue($response->getError());
|
|
}
|
|
|
|
/**
|
|
* Assert that an API response has an integer result.
|
|
*/
|
|
function assertApiResultIsInt(ApiResponse $response): void
|
|
{
|
|
expect($response->isIntResult())
|
|
->toBeTrue('Expected integer result, got: ' . gettype($response->getResult()));
|
|
}
|
|
|
|
/**
|
|
* Assert that an API response has an array result.
|
|
*/
|
|
function assertApiResultIsArray(ApiResponse $response): void
|
|
{
|
|
expect($response->isArrayResult())
|
|
->toBeTrue('Expected array result, got: ' . gettype($response->getResult()));
|
|
}
|