['auth', 'login', 'logout', 'me', 'password', 'register'], 'users_roles' => ['users', 'roles', 'permissions', 'preferences', 'navigation'], 'students_families' => ['students', 'parents', 'family', 'families', 'guardian', 'authorized'], 'classes_teachers' => ['classes', 'class-sections', 'teachers', 'class-progress', 'assignments'], 'attendance_safety' => ['attendance', 'absence', 'late', 'dismissal', 'incident', 'violation'], 'academics_grades' => ['scores', 'homework', 'quiz', 'project', 'midterm', 'final', 'report-card', 'grading'], 'finance' => ['finance', 'invoice', 'payment', 'refund', 'fee', 'installment', 'charge', 'reimbursement'], 'inventory_procurement' => ['inventory', 'supplier', 'supply', 'procurement', 'purchase'], 'communications' => ['messages', 'support', 'contact', 'email', 'whatsapp', 'notification'], 'documents_print' => ['print', 'certificate', 'badge', 'sticker', 'receipt', 'export', 'download'], 'operations' => ['settings', 'configuration', 'school-years', 'calendar', 'dashboard', 'reports'], ]; $routes = $this->apiRoutes(); foreach ($families as $family => $needles) { $matches = array_filter($routes, fn (LaravelRoute $route): bool => $this->uriContainsAny($route->uri(), $needles)); $this->assertNotEmpty($matches, $family.' must have route-level E2E contract coverage candidates.'); } } public function test_mutating_route_surface_is_not_accidentally_unowned_by_contract_suites(): void { $ownedNeedles = [ 'auth', 'login', 'register', 'proofread', 'compare', 'scanner', 'utilities', 'ui', 'users', 'roles', 'permissions', 'preferences', 'nav-builder', 'role-switcher', 'ip-bans', 'students', 'parents', 'family', 'families', 'guardian', 'authorized', 'classes', 'class-sections', 'class-prep', 'class-progress', 'teacher', 'teachers', 'staff', 'attendance', 'absence', 'flags', 'incident', 'incidents', 'scores', 'homework', 'quiz', 'project', 'midterm', 'final', 'participation', 'grading', 'exams', 'subjects', 'competition', 'discount', 'finance', 'invoice', 'payment', 'refund', 'fee', 'inventory', 'supplier', 'messages', 'support', 'contact', 'email', 'communications', 'whatsapp', 'print', 'certificate', 'badge', 'settings', 'configuration', 'school-years', 'calendar', 'reports', 'promotion', 'withdrawal', 'enrollment', 'assignment', 'expense', 'reimbursement', 'administrator', 'notifications', 'system', 'extra-charges', ]; $unowned = []; foreach ($this->apiRoutes() as $route) { $method = $this->primaryMethod($route); $uri = $route->uri(); if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { continue; } if (! $this->uriContainsAny($uri, $ownedNeedles)) { $unowned[] = $method.' '.$uri; } } $this->assertSame([], $unowned, 'Mutating API routes must be assigned to a full-surface use-case contract bucket: '.implode(', ', $unowned)); } /** @param list $needles */ private function uriContainsAny(string $uri, array $needles): bool { foreach ($needles as $needle) { if (str_contains($uri, $needle)) { return true; } } return false; } }