mirror of
https://github.com/FOSSBilling/FOSSBilling.git
synced 2026-11-08 21:15:33 +09:00
* Bump Dockerfile syntax to 1.18 * Add getStatusCode to Response and update test * Expect error code 740 for invalid theme action * Use Docker-based CI/test image and live test script * Add PHP 8.5 to CI matrix * Add Buildx caching and optimize Docker build * Add CI timeouts, quoting fixes, and release tree build * Add concurrency, caching, and cleanup to CI * Grant permissions for preview-build job * Map error code 740 to HTTP 404 and update test * Update cache-scope names in GitHub workflows * Remove redundant status guard in theme admin test Agent-Logs-Url: https://github.com/FOSSBilling/FOSSBilling/sessions/b20f484f-0aaa-4afc-ae43-adb264bb05aa Co-authored-by: admdly <2807620+admdly@users.noreply.github.com> * Grant cache export permissions to CI build job Agent-Logs-Url: https://github.com/FOSSBilling/FOSSBilling/sessions/a088c94f-a176-40fb-9e0b-ee3cac2aeb5b Co-authored-by: admdly <2807620+admdly@users.noreply.github.com> * Disable cache for release artifact build * Fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: admdly <2807620+admdly@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
137 lines
4.2 KiB
PHP
137 lines
4.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace APIHelper;
|
|
|
|
use Symfony\Component\Filesystem\Filesystem;
|
|
use Symfony\Component\Filesystem\Path;
|
|
|
|
class Request
|
|
{
|
|
/**
|
|
* Handles making a request to the FOSSBilling server.
|
|
*
|
|
* @param string $endpoint the API endpoint to call (Example: `guest/system/company`)
|
|
* @param string $method set to POST to have cURL make a POST request to the server
|
|
* @param array $payload when using POST, this array will be sent as the POST fields
|
|
* @param string|null $apiKey (optional) the API key to authenticate with
|
|
* @param string|null $baseUrl (optional) the base instance URL to make requests against (Example: `http://localhost/`)
|
|
*/
|
|
public static function makeRequest(string $endpoint, array $payload = [], ?string $role = null, ?string $apiKey = null, string $method = 'POST', ?string $baseUrl = null): Response
|
|
{
|
|
$cookie = Path::join(sys_get_temp_dir(), 'cookie.txt');
|
|
if (!$role) {
|
|
$role = str_starts_with($endpoint, 'admin') ? 'admin' : 'client';
|
|
}
|
|
|
|
if (!$apiKey) {
|
|
$apiKey = getenv('TEST_API_KEY');
|
|
}
|
|
|
|
if (!$baseUrl) {
|
|
$baseUrl = getenv('APP_URL');
|
|
}
|
|
|
|
$ch = curl_init($baseUrl . 'api/' . $endpoint);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
|
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
|
|
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie);
|
|
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie);
|
|
curl_setopt($ch, 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);
|
|
curl_close($ch);
|
|
|
|
throw new \RuntimeException('cURL request failed: ' . $error . ' (HTTP ' . $httpCode . ')');
|
|
}
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
return new Response($httpCode, $output);
|
|
}
|
|
|
|
public static function resetCookies(): void
|
|
{
|
|
$filesystem = new Filesystem();
|
|
$filesystem->remove(Path::join(sys_get_temp_dir(), 'cookie.txt'));
|
|
}
|
|
}
|
|
|
|
class Response
|
|
{
|
|
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->code;
|
|
}
|
|
|
|
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']);
|
|
}
|
|
|
|
public function getErrorMessage(): string
|
|
{
|
|
return $this->decodedResponse['error']['message'] ?? 'None';
|
|
}
|
|
|
|
public function getErrorCode(): int
|
|
{
|
|
return intval($this->decodedResponse['error']['code'] ?? 0);
|
|
}
|
|
|
|
public function getError(): string
|
|
{
|
|
return $this->getErrorMessage() . ' (Code ' . $this->getErrorCode() . ')';
|
|
}
|
|
|
|
public function generatePHPUnitMessage(): string
|
|
{
|
|
return 'The API request failed with the following message: ' . $this->getError();
|
|
}
|
|
|
|
public function getResult(): mixed
|
|
{
|
|
return $this->decodedResponse['result'] ?? '';
|
|
}
|
|
}
|