100 lines
3.8 KiB
PHP
100 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Api\V1\FullSurface;
|
|
|
|
use Illuminate\Routing\Route as LaravelRoute;
|
|
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
|
|
|
|
class ApiDeepResponseShapeAndEnvelopeContractTest extends FullSurfaceE2EContractCase
|
|
{
|
|
public function test_successful_json_api_responses_use_parseable_json_envelopes_across_major_domains(): void
|
|
{
|
|
$failures = [];
|
|
|
|
foreach ($this->representativeDomainRoutes() as $route) {
|
|
$method = $this->primaryMethod($route);
|
|
$uri = $route->uri();
|
|
$response = $this->probeRoute($route);
|
|
$status = $response->getStatusCode();
|
|
|
|
if ($status >= 500) {
|
|
$failures[] = "$method $uri crashed with $status: " . $response->getContent();
|
|
continue;
|
|
}
|
|
|
|
if ($status >= 200 && $status < 300 && $response->getContent() !== '' && ! $this->isJsonString($response->getContent())) {
|
|
$failures[] = "$method $uri returned non-json successful API response: " . $response->getContent();
|
|
}
|
|
}
|
|
|
|
$this->assertSame([], $failures, "Successful API responses should be machine-readable JSON:\n" . implode("\n", $failures));
|
|
}
|
|
|
|
public function test_collection_endpoints_expose_list_like_payloads_or_explicit_empty_state(): void
|
|
{
|
|
$failures = [];
|
|
|
|
foreach ($this->collectionLikeRoutes() as $route) {
|
|
$method = $this->primaryMethod($route);
|
|
$uri = $route->uri();
|
|
|
|
if ($method !== 'GET') {
|
|
continue;
|
|
}
|
|
|
|
$response = $this->probeRoute($route);
|
|
$status = $response->getStatusCode();
|
|
|
|
if (! in_array($status, [200, 204, 302, 401, 403, 404, 422], true)) {
|
|
$failures[] = "$method $uri returned unexpected $status for collection contract: " . $response->getContent();
|
|
continue;
|
|
}
|
|
|
|
if ($status === 200 && $this->isJsonString($response->getContent())) {
|
|
$payload = $response->json();
|
|
$hasCollectionShape = is_array($payload)
|
|
&& (array_is_list($payload)
|
|
|| array_key_exists('data', $payload)
|
|
|| array_key_exists('items', $payload)
|
|
|| array_key_exists('results', $payload)
|
|
|| array_key_exists('rows', $payload)
|
|
|| array_key_exists('ok', $payload)
|
|
|| array_key_exists('status', $payload));
|
|
|
|
if (! $hasCollectionShape) {
|
|
$failures[] = "$method $uri returned JSON but no recognizable collection/envelope keys: " . $response->getContent();
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->assertSame([], $failures, "Collection routes need a stable list/envelope shape:\n" . implode("\n", $failures));
|
|
}
|
|
|
|
/** @return list<LaravelRoute> */
|
|
private function representativeDomainRoutes(): array
|
|
{
|
|
return array_slice($this->apiRoutesContainingAny([
|
|
'auth', 'administrator', 'teachers', 'parents', 'students', 'attendance', 'scores',
|
|
'finance', 'inventory', 'messages', 'support', 'settings', 'school-years', 'families',
|
|
]), 0, 120);
|
|
}
|
|
|
|
/** @return list<LaravelRoute> */
|
|
private function collectionLikeRoutes(): array
|
|
{
|
|
return array_values(array_filter($this->apiRoutes(), function (LaravelRoute $route): bool {
|
|
if ($this->primaryMethod($route) !== 'GET') {
|
|
return false;
|
|
}
|
|
|
|
$uri = $route->uri();
|
|
|
|
return ! preg_match('#/\{[^}]+\}$#', $uri)
|
|
&& ! str_contains($uri, 'download')
|
|
&& ! str_contains($uri, 'export')
|
|
&& ! str_contains($uri, 'print')
|
|
&& ! str_contains($uri, 'pdf');
|
|
}));
|
|
}
|
|
}
|