Files
FOSSBilling/tests/APIHelper.php
T
Copilotandadmdly 230b285dd7 Fix SSL typo, invalid transaction status, and unsafe array access in APIHelper (#3399)
* Initial plan

* Fix typos, invalid transaction status, and unsafe array access in APIHelper

Agent-Logs-Url: https://github.com/FOSSBilling/FOSSBilling/sessions/5ec74911-4de8-4620-a3e3-cd6e36a975fb

Co-authored-by: admdly <2807620+admdly@users.noreply.github.com>

* Fix getHasSll typo in Directadmin.php to match baseline suppression

Agent-Logs-Url: https://github.com/FOSSBilling/FOSSBilling/sessions/e74d2b4a-d375-4c99-b557-9050dc9f0c55

Co-authored-by: admdly <2807620+admdly@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>
2026-04-08 07:46:23 +01:00

125 lines
3.9 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);
$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 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'] ?? '';
}
}