added unit tests
This commit is contained in:
181
tests/Support/ApiClient.php
Normal file
181
tests/Support/ApiClient.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Support;
|
||||
|
||||
/**
|
||||
* Dispatches requests through api/index.php (same as production routing).
|
||||
*/
|
||||
final class ApiClient
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $apiIndexPath,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $headers
|
||||
* @return array{ok: bool, status: int, data?: mixed, error?: array<string, mixed>}
|
||||
*/
|
||||
public function request(
|
||||
string $method,
|
||||
string $route,
|
||||
?array $body = null,
|
||||
array $headers = [],
|
||||
array $query = [],
|
||||
): array {
|
||||
qdb_test_reset_http_state();
|
||||
|
||||
$_SERVER['REQUEST_METHOD'] = strtoupper($method);
|
||||
$queryString = $query !== [] ? '?' . http_build_query($query) : '';
|
||||
$_SERVER['REQUEST_URI'] = '/api/' . ltrim($route, '/') . $queryString;
|
||||
$_SERVER['SCRIPT_NAME'] = '/api/index.php';
|
||||
$_GET = $query;
|
||||
if ($queryString !== '') {
|
||||
parse_str(ltrim($queryString, '?'), $_GET);
|
||||
}
|
||||
|
||||
unset($_SERVER['HTTP_AUTHORIZATION'], $_SERVER['Authorization']);
|
||||
unset($_SERVER['HTTP_X_QDB_CLIENT']);
|
||||
|
||||
foreach ($headers as $name => $value) {
|
||||
$key = 'HTTP_' . strtoupper(str_replace('-', '_', $name));
|
||||
$_SERVER[$key] = $value;
|
||||
}
|
||||
|
||||
if ($body !== null) {
|
||||
qdb_test_set_request_body(json_encode($body, JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
ob_start();
|
||||
try {
|
||||
require $this->apiIndexPath;
|
||||
ob_end_clean();
|
||||
return ['ok' => false, 'status' => 500, 'error' => ['code' => 'NO_RESPONSE', 'message' => 'Handler did not return JSON']];
|
||||
} catch (JsonSuccessResponse $e) {
|
||||
$output = ob_get_clean();
|
||||
return [
|
||||
'ok' => true,
|
||||
'status' => $e->httpStatus,
|
||||
'data' => $e->data,
|
||||
'_raw' => $output,
|
||||
];
|
||||
} catch (JsonErrorResponse $e) {
|
||||
ob_get_clean();
|
||||
$err = ['code' => $e->errorCode, 'message' => $e->errorMessage];
|
||||
if ($e->details !== null) {
|
||||
$err['details'] = $e->details;
|
||||
}
|
||||
return [
|
||||
'ok' => false,
|
||||
'status' => $e->httpStatus,
|
||||
'error' => $err,
|
||||
];
|
||||
} catch (RawHttpResponse $e) {
|
||||
ob_get_clean();
|
||||
return [
|
||||
'ok' => true,
|
||||
'status' => $e->httpStatus,
|
||||
'raw' => true,
|
||||
'body' => $e->body,
|
||||
'headers' => $e->headers,
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
ob_end_clean();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function loginWeb(string $username, string $password): array
|
||||
{
|
||||
return $this->request('POST', 'auth/login', [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
], ['X-QDB-Client' => 'web']);
|
||||
}
|
||||
|
||||
public function loginMobile(string $username, string $password): array
|
||||
{
|
||||
return $this->request('POST', 'auth/login', [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{token: string, data: array<string, mixed>}
|
||||
*/
|
||||
public function loginMobileAndGetToken(string $username, string $password): array
|
||||
{
|
||||
$res = $this->loginMobile($username, $password);
|
||||
if (!$res['ok']) {
|
||||
throw new \RuntimeException('Login failed: ' . ($res['error']['message'] ?? 'unknown'));
|
||||
}
|
||||
$token = $res['data']['token'] ?? '';
|
||||
if ($token === '') {
|
||||
throw new \RuntimeException('Login response missing token');
|
||||
}
|
||||
return ['token' => $token, 'data' => $res['data']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{token: string, data: array<string, mixed>}
|
||||
*/
|
||||
public function loginWebAndGetToken(string $username, string $password): array
|
||||
{
|
||||
$res = $this->loginWeb($username, $password);
|
||||
if (!$res['ok']) {
|
||||
throw new \RuntimeException('Login failed: ' . ($res['error']['message'] ?? 'unknown'));
|
||||
}
|
||||
$token = $res['data']['token'] ?? '';
|
||||
if ($token === '') {
|
||||
throw new \RuntimeException('Login response missing token');
|
||||
}
|
||||
return ['token' => $token, 'data' => $res['data']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $extraHeaders
|
||||
*/
|
||||
public function withToken(string $token, string $method, string $route, ?array $body = null, array $query = [], array $extraHeaders = []): array
|
||||
{
|
||||
$headers = array_merge([
|
||||
'Authorization' => 'Bearer ' . $token,
|
||||
'X-QDB-Client' => 'web',
|
||||
], $extraHeaders);
|
||||
|
||||
return $this->request($method, $route, $body, $headers, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $extraHeaders
|
||||
*/
|
||||
public function withMobileToken(
|
||||
string $token,
|
||||
string $method,
|
||||
string $route,
|
||||
?array $body = null,
|
||||
array $query = [],
|
||||
array $extraHeaders = [],
|
||||
): array {
|
||||
$headers = array_merge(['Authorization' => 'Bearer ' . $token], $extraHeaders);
|
||||
|
||||
return $this->request($method, $route, $body, $headers, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST an encrypted payload (Android app API).
|
||||
*
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
public function encryptedPost(string $token, string $route, array $payload): array
|
||||
{
|
||||
$envelope = qdb_sensitive_envelope(
|
||||
json_encode($payload, JSON_THROW_ON_ERROR),
|
||||
$token
|
||||
);
|
||||
|
||||
return $this->withMobileToken($token, 'POST', $route, $envelope);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user