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 */ 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 */ 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(); if (str_contains($uri, 'documentation') || str_contains($uri, 'docs/') || str_contains($uri, 'confirm_authorized_user')) { return false; } return ! preg_match('#/\{[^}]+\}$#', $uri) && ! str_contains($uri, 'download') && ! str_contains($uri, 'export') && ! str_contains($uri, 'print') && ! str_contains($uri, 'pdf'); })); } }