request('GET', $path, null, $headers); } public function post(string $path, ?array $payload = null, array $headers = []): ApiResponse { return $this->request('POST', $path, $payload, $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); } }