apiRoutes() as $route) { $uri = $route->uri(); if ($route->getName() !== null) { continue; } if ($this->isAllowedUnnamedRoute($uri)) { continue; } $unnamed[] = $this->primaryMethod($route) . ' ' . $uri; } $this->assertSame([], $unnamed, "API routes should be nameable for docs, client generation, and redirects. Unnamed routes:\n" . implode("\n", $unnamed)); } public function test_openapi_or_docs_catalog_endpoint_is_reachable_and_route_table_remains_documentable(): void { $docsChecks = [ ['GET', '/api/documentation'], ['GET', '/api/documentation/swagger'], ['GET', '/api/documentation/swagger.json'], ['GET', '/api/docs/public'], ]; $failures = []; foreach ($docsChecks as [$method, $uri]) { $response = $this->requestAs($this->admin, $method, $uri); if ($response->getStatusCode() >= 500) { $failures[] = "$method $uri docs endpoint crashed: " . $response->getContent(); } } $this->assertSame([], $failures, "Documentation endpoints must not be decorative rubble:\n" . implode("\n", $failures)); } public function test_route_use_case_catalog_covers_new_deep_contract_domains(): void { $coverageNeedles = [ 'import' => ['upload', 'import', 'attachment', 'document'], 'export' => ['download', 'export', 'pdf', 'print'], 'pagination' => ['students', 'users', 'invoices', 'inventory', 'messages'], 'state_transition' => ['close', 'reopen', 'approve', 'archive', 'resolve', 'lock', 'unlock'], 'identity' => ['auth', 'login', 'logout', 'me', 'roles'], ]; foreach ($coverageNeedles as $label => $needles) { $this->assertNotSame([], $this->apiRoutesContainingAny($needles), "The $label contract bucket should map to at least one route."); } } private function isAllowedUnnamedRoute(string $uri): bool { return str_contains($uri, 'api/v1') || str_contains($uri, 'login') || str_contains($uri, 'register') || str_contains($uri, 'proofread') || str_contains($uri, 'emails') || str_contains($uri, 'compare'); } }