Files
api/services/nginx/app/tests/Support/Api/ApiClient.php
T

185 lines
6.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Support\Api;
use RuntimeException;
final class ApiClient
{
private const DEFAULT_TIMEOUT_SECONDS = 30;
public function __construct(private readonly string $baseUrl)
{
}
public function get(string $path, array $headers = []): ApiResponse
{
return $this->request('GET', $path, null, $headers);
}
public function post(string $path, ?array $payload = null, array $headers = []): ApiResponse
{
return $this->request('POST', $path, $payload, $headers);
}
/**
* @param array<string, scalar|null> $fields
* @param array<string, string|array{path:string,mime?:string,name?:string}> $files
* @param array<string, string> $headers
*/
public function postMultipart(string $path, array $fields = [], array $files = [], array $headers = []): ApiResponse
{
$postFields = [];
foreach ($fields as $name => $value) {
$postFields[$name] = $value === null ? '' : (string)$value;
}
foreach ($files as $name => $file) {
$filePath = is_array($file) ? (string)$file['path'] : (string)$file;
$mime = is_array($file) ? (string)($file['mime'] ?? 'application/octet-stream') : 'application/octet-stream';
$filename = is_array($file) ? (string)($file['name'] ?? basename($filePath)) : basename($filePath);
$postFields[$name] = new \CURLFile($filePath, $mime, $filename);
}
return $this->requestMultipart('POST', $path, $postFields, $headers);
}
public function put(string $path, ?array $payload = null, array $headers = []): ApiResponse
{
return $this->request('PUT', $path, $payload, $headers);
}
public function delete(string $path, ?array $payload = null, array $headers = []): ApiResponse
{
return $this->request('DELETE', $path, $payload, $headers);
}
public function request(string $method, string $path, ?array $payload = null, array $headers = []): ApiResponse
{
$curl = curl_init();
if ($curl === false) {
throw new RuntimeException('Unable to initialize cURL for API tests.');
}
$timeoutSeconds = (int)(getenv('API_TEST_REQUEST_TIMEOUT') ?: self::DEFAULT_TIMEOUT_SECONDS);
if ($timeoutSeconds <= 0) {
$timeoutSeconds = self::DEFAULT_TIMEOUT_SECONDS;
}
$responseHeaders = [];
$normalizedHeaders = [];
foreach ($headers as $name => $value) {
$normalizedHeaders[] = $name . ': ' . $value;
}
if ($payload !== null) {
$normalizedHeaders[] = 'Content-Type: application/json';
}
curl_setopt_array($curl, [
CURLOPT_URL => rtrim($this->baseUrl, '/') . $path,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_HTTPHEADER => $normalizedHeaders,
CURLOPT_CONNECTTIMEOUT => $timeoutSeconds,
CURLOPT_TIMEOUT => $timeoutSeconds,
CURLOPT_HEADERFUNCTION => static function ($curlHandle, string $headerLine) use (&$responseHeaders): int {
$length = strlen($headerLine);
$parts = explode(':', $headerLine, 2);
if (count($parts) === 2) {
$responseHeaders[strtolower(trim($parts[0]))] = trim($parts[1]);
}
return $length;
},
]);
if ($payload !== null) {
$encodedPayload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (!is_string($encodedPayload)) {
throw new RuntimeException('Unable to encode the API request payload.');
}
curl_setopt($curl, CURLOPT_POSTFIELDS, $encodedPayload);
}
$body = curl_exec($curl);
if ($body === false) {
$error = curl_error($curl);
curl_close($curl);
throw new RuntimeException('API request failed: ' . $error);
}
$status = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$decoded = json_decode($body, true);
if (getenv('EMAIL_FAKE_MODE') === '1' && class_exists(\classes\email::class)) {
\classes\email::syncFakeDeliveries();
}
return new ApiResponse($status, $responseHeaders, $decoded, (string)$body);
}
/**
* @param array<string, string|\CURLFile> $postFields
* @param array<string, string> $headers
*/
private function requestMultipart(string $method, string $path, array $postFields, array $headers = []): ApiResponse
{
$curl = curl_init();
if ($curl === false) {
throw new RuntimeException('Unable to initialize cURL for API tests.');
}
$timeoutSeconds = (int)(getenv('API_TEST_REQUEST_TIMEOUT') ?: self::DEFAULT_TIMEOUT_SECONDS);
if ($timeoutSeconds <= 0) {
$timeoutSeconds = self::DEFAULT_TIMEOUT_SECONDS;
}
$responseHeaders = [];
$normalizedHeaders = [];
foreach ($headers as $name => $value) {
$normalizedHeaders[] = $name . ': ' . $value;
}
curl_setopt_array($curl, [
CURLOPT_URL => rtrim($this->baseUrl, '/') . $path,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false,
CURLOPT_HTTPHEADER => $normalizedHeaders,
CURLOPT_CONNECTTIMEOUT => $timeoutSeconds,
CURLOPT_TIMEOUT => $timeoutSeconds,
CURLOPT_POSTFIELDS => $postFields,
CURLOPT_HEADERFUNCTION => static function ($curlHandle, string $headerLine) use (&$responseHeaders): int {
$length = strlen($headerLine);
$parts = explode(':', $headerLine, 2);
if (count($parts) === 2) {
$responseHeaders[strtolower(trim($parts[0]))] = trim($parts[1]);
}
return $length;
},
]);
$body = curl_exec($curl);
if ($body === false) {
$error = curl_error($curl);
curl_close($curl);
throw new RuntimeException('API multipart request failed: ' . $error);
}
$status = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$decoded = json_decode($body, true);
return new ApiResponse($status, $responseHeaders, $decoded, (string)$body);
}
}