ids['schoolYear']; $checks = [ ['POST', "/api/v1/school-years/$schoolYearId/validate-close", []], ['POST', "/api/v1/school-years/$schoolYearId/preview-close", []], ['POST', "/api/v1/school-years/$schoolYearId/close", ['confirm' => true]], ['POST', "/api/v1/school-years/$schoolYearId/reopen", ['reason' => 'E2E invariant probe']], ['GET', '/api/v1/administrator/promotions'], ['POST', '/api/v1/administrator/promotions/preview', ['school_year' => self::E2E_SCHOOL_YEAR]], ]; $this->assertTransitionChecksAreControlled($checks, 'school year and promotion'); } public function test_finance_transition_routes_do_not_allow_negative_balance_crashes_or_untracked_state(): void { $invoiceId = (int) $this->ids['invoiceId']; $checks = [ ['POST', '/api/v1/finance/payments', ['invoice_id' => $invoiceId, 'amount' => -100, 'payment_method' => 'cash']], ['POST', '/api/v1/finance/payments', ['invoice_id' => $invoiceId, 'amount' => 25, 'payment_method' => 'cash']], ['POST', '/api/v1/finance/refunds', ['invoice_id' => $invoiceId, 'amount' => 999999, 'reason' => 'Over-refund probe']], ['POST', '/api/v1/finance/installments', ['invoice_id' => $invoiceId, 'amount' => 25, 'due_date' => '2025-11-01']], ['GET', '/api/v1/finance/payment-followups'], ]; $this->assertTransitionChecksAreControlled($checks, 'finance'); $invoice = DB::table('invoices')->where('id', $invoiceId)->first(); $this->assertNotNull($invoice, 'Finance probes must not delete the invoice fixture just because math got spicy.'); } public function test_attendance_transition_routes_keep_single_day_student_context_controlled(): void { $studentId = (int) $this->ids['studentId']; $classSectionId = (int) $this->ids['classSectionId']; $checks = [ ['POST', '/api/v1/teacher/attendance', ['student_id' => $studentId, 'class_section_id' => $classSectionId, 'date' => '2025-10-05', 'status' => 'present']], ['POST', '/api/v1/teacher/attendance', ['student_id' => $studentId, 'class_section_id' => $classSectionId, 'date' => '2025-10-05', 'status' => 'absent']], ['POST', '/api/v1/attendance/late-slips', ['student_id' => $studentId, 'class_section_id' => $classSectionId, 'date' => '2025-10-05', 'arrival_time' => '10:45']], ['POST', '/api/v1/attendance/early-dismissals', ['student_id' => $studentId, 'class_section_id' => $classSectionId, 'date' => '2025-10-05', 'dismissal_time' => '12:30']], ]; $this->assertTransitionChecksAreControlled($checks, 'attendance'); } /** @param list}> $checks */ private function assertTransitionChecksAreControlled(array $checks, string $domain): void { $failures = []; foreach ($checks as $check) { $method = $check[0]; $uri = $check[1]; $payload = $check[2] ?? null; $payload ??= $this->payloadFor($method, ltrim($uri, '/')); $response = $this->requestAs($this->actorFor($uri), $method, $uri, $payload); if ($response->getStatusCode() >= 500) { $failures[] = "$method $uri crashed during $domain transition probe: ".$response->getContent(); continue; } if (! in_array($response->getStatusCode(), $this->controlledStatuses($method), true)) { $failures[] = "$method $uri returned unexpected {$response->getStatusCode()} during $domain transition probe: ".$response->getContent(); } } $this->assertSame([], $failures, ucfirst($domain)." state transitions must be controlled:\n".implode("\n", $failures)); } }