Files
alrahma_sunday_school_api/docs/phpunit_failure_fix_summary.md
T
root 304fa6bfd0
API CI/CD / Validate (composer + pint) (push) Successful in 3m5s
API CI/CD / Test (PHPUnit) (push) Failing after 7m12s
API CI/CD / Build frontend assets (push) Successful in 1m2s
API CI/CD / Security audit (push) Failing after 49s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
fix test issues
2026-07-06 02:39:13 -04:00

13 KiB

PHPUnit Failure Fix Summary

Overview

The PHPUnit run failed with a large number of cascading failures, but the issues are concentrated in a few root causes.

Result summary:

  • 679 tests passed
  • 352 tests failed
  • 2 tests risky
  • Most failures are caused by authorization changes, missing routes, schema drift, stale test setup, and inconsistent API contracts.

Do not fix the 352 failures one by one. Fix the root causes below in order.


1. Fix Authorization and Permission Test Setup

Problem

Many tests now fail with:

  • 403 Forbidden
  • 401 Unauthorized

Most protected routes are correctly blocking users because the tests authenticate users without assigning required roles or permissions.

Example bad pattern:

$user = User::factory()->create();
Sanctum::actingAs($user);

This creates an authenticated user, but not an authorized admin.

Required Fix

Update test helpers to create proper users with roles and permissions.

Recommended helpers:

$this->actingAsApiAdministrator();
$this->actingAsApiUserWithPermissions(['permission.name']);
$this->actingAsParent();
$this->actingAsTeacher();

Then update affected feature tests to use the correct helper.

Priority

Critical

This is the biggest failure cluster and must be fixed before chasing controller-level test failures.


2. Protect School Year Admin Routes Correctly

Problem

The test expects non-admin users to be rejected from:

GET /api/v1/school-years

But the route currently returns 200 for a non-admin user.

Required Fix

Separate public/current school-year routes from admin-only routes.

Recommended access model:

GET /api/v1/school-years              -> admin only
GET /api/v1/school-years/current      -> authenticated users
GET /api/v1/school-years/options      -> authenticated users if frontend needs it

Use a stricter permission for the index route, such as:

school_year.manage
school_year.admin.view

Do not let parent users list all school years unless that is intentional.

Priority

Critical


3. Add Missing SchoolYearController@archive

Problem

The route exists:

POST /api/v1/school-years/{schoolYear}/archive

But the controller method does not exist.

Required Fix

Add the method to:

app/Http/Controllers/Api/SchoolYears/SchoolYearController.php

Required method:

public function archive(int $schoolYear): JsonResponse

Then either:

  • implement real archive behavior, or
  • remove the route if archiving is no longer supported.

Priority

Critical


4. Update API Route Coverage Matrix

Problem

The route coverage tests report missing mapping for:

GET /api/v1/admin/progress
GET /api/v1/admin/progress/meta

Required Fix

Add both routes to the API use-case coverage matrix.

Assign them to the correct domain, likely:

Academic Progress
Administrator Reporting

Also update E2E workflow coverage if required by the test suite.

Priority

High


5. Fix Database Schema Drift

Problem

Several tests fail because models/services expect columns that migrations do not provide, or migrations require fields that services/tests do not send.

Required Fixes

email_templates

Error:

table email_templates has no column named created_at

Fix:

$table->timestamps();

Or set $timestamps = false on the model if timestamps are not intended.

Preferred fix: add timestamps.


class_prep_adjustments

Error:

table class_prep_adjustments has no column named updated_at

Fix:

$table->timestamps();

late_slip_logs

Error:

NOT NULL constraint failed: late_slip_logs.printed_at

Fix one:

$table->timestamp('printed_at')->nullable();

or ensure every insert provides printed_at.

Preferred fix: make nullable.


additional_charges

Error:

NOT NULL constraint failed: additional_charges.title

Fix one:

  • provide title in every creation path
  • or make title nullable/defaulted

Preferred fix: update service/test payloads to always provide title unless legacy data requires nullable.


payments

Error:

NOT NULL constraint failed: payments.number_of_installments

Fix:

$table->unsignedInteger('number_of_installments')->default(1);

Also ensure payment creation services pass a value when appropriate.


ip_attempts

Error:

NOT NULL constraint failed: ip_attempts.last_attempt_at

Fix:

$table->timestamp('last_attempt_at')->useCurrent();

or update all insert paths.


student_class

Error:

table student_class has no column named school_id

Fix one:

  • add school_id to the pivot table
  • or remove references to school_id

Preferred fix: add school_id if school isolation is required.


semester_scores

Error:

NOT NULL constraint failed: semester_scores.school_id

Fix:

  • provide school_id in all score creation/update paths
  • or allow nullable for legacy records

Preferred fix: provide school_id consistently.

Priority

High


6. Stabilize SQLite Test Database

Problem

The log contains repeated:

SQLSTATE[HY000]: General error: 5 database is locked

Required Fix

Do not share one SQLite file across many concurrent or transaction-heavy tests.

Recommended CI setup:

DB_CONNECTION=sqlite
DB_DATABASE=/tmp/alrahma_testing.sqlite

Before tests:

rm -f /tmp/alrahma_testing.sqlite
touch /tmp/alrahma_testing.sqlite
php artisan migrate:fresh --env=testing

Also check:

  • no parallel tests share the same SQLite file
  • long transactions are not left open
  • RefreshDatabase is used consistently

Priority

High


7. Update Unit Tests for Service Constructor Changes

Problem

Many unit tests manually instantiate services with outdated constructor arguments.

Examples:

AdministratorAbsenceService expects 6 arguments, test passes 5
TeacherSubmissionNotificationService expects 3 arguments, test passes 2
ClassProgressMutationService expects 4 arguments, test passes 2
DiscountInvoiceService expects 1 argument, test passes 0

Required Fix

Replace manual construction:

$service = new SomeService($oldArg1, $oldArg2);

with container resolution:

$service = app(SomeService::class);

If mocks are needed, bind them into the container before resolving the service.

Priority

High


8. Fix Final Class Mocking

Problem

This class cannot be mocked:

App\Services\ApplicationUrlService

because it is declared final.

Required Fix

Choose one:

  1. Remove final
  2. Extract an interface, for example:
interface ApplicationUrlProvider
  1. Use a real instance in tests instead of mocking it

Preferred fix: extract an interface if the service should remain final.

Priority

Medium


9. Standardize API Response Shape

Problem

Some tests expect one response shape while controllers return another.

Examples of inconsistent shapes:

{
  "ok": true
}
{
  "status": true,
  "message": "...",
  "data": {}
}

Raw arrays are also returned in some places.

Required Fix

Standardize API v1 responses.

Recommended shape:

{
  "status": true,
  "message": "Success",
  "data": {}
}

Then update:

  • controllers
  • API resources
  • frontend API clients
  • tests

Priority

Medium


10. Fix Validation Contract Drift

Problem

Several controllers now require fields that old tests/frontend callers do not send.

Required Fixes

Attendance comment templates

Current required fields:

min_score
max_score
template_text

Old callers send:

comment
type
is_active

Fix:

  • support legacy aliases, or
  • update callers/tests to the new payload

Preferred fix: support aliases temporarily and migrate frontend/tests.


Score comments

Current required field:

comments

Old callers send:

comment

Fix:

$comments = $request->input('comments', $request->input('comment'));

Below-60 grading

Current required field:

semester

Some callers only send:

school_year

Fix one:

  • make semester optional and infer current semester
  • or update callers/tests to always send semester

Preferred fix: frontend/tests should send semester explicitly.


Print request status

Current validation rejects:

processing

Fix one:

  • add processing to allowed statuses
  • or update callers/tests to use the valid status value

Preferred fix: allow processing if it exists in the real workflow.


Role permissions

Current required field:

permissions

Old callers send:

permission_ids

Fix:

  • accept permission_ids as alias
  • or update frontend/tests

Preferred fix: accept both.

Priority

Medium


11. Add Missing Legacy Route Aliases

Problem

Some tests/frontend paths call legacy endpoints that now return 404.

Examples:

GET /api/v1/configuration-admin
POST /api/v1/configuration-admin
GET /api/v1/attendance-tracking/compose

Required Fix

Either:

  • add compatibility aliases to the current controllers, or
  • update frontend/tests to the new endpoints

Recommended short-term fix:

Route::get('/configuration-admin', ...);
Route::post('/configuration-admin', ...);
Route::get('/attendance-tracking/compose', ...);

Long-term fix:

  • remove legacy aliases only after frontend and tests fully migrate.

Priority

Medium


12. Normalize Validation Status Codes

Problem

Some validation failures return:

400 Bad Request

when tests expect:

422 Unprocessable Entity

Required Fix

Use Laravel validation responses consistently.

Expected status rules:

400 -> malformed request
401 -> unauthenticated
403 -> authenticated but forbidden
404 -> missing resource
422 -> validation error

Priority

Medium


13. Review Enrollment Bulk Status Endpoint

Problem

Endpoint returns:

207 Multi-Status

where tests expect:

200 OK

Affected endpoint:

/api/v1/administrator/enrollment-withdrawal/update-statuses

Required Fix

Decide the correct contract:

  • return 207 only for partial success/failure
  • return 200 if the operation is all-or-nothing

Then update either the controller or the tests.

Priority

Low to Medium


14. Fix Bulk User Creation Count

Problem

Bulk user creation expects:

3000 users

but creates:

3001 users

Required Fix

Find the extra user:

User::where('email', 'like', 'bulk_api_%')
    ->pluck('email')
    ->countBy();

Check whether the extra user comes from:

  • test setup
  • authenticated admin fixture
  • duplicate creation retry
  • linked/secondary user creation
  • email prefix overlap

Priority

Low


15. Fix Public Token / Time-Off Route Tests

Problem

Invalid token route returns a 400 HTML response.

Required Fix

Review the public time-off token contract:

  • expected token format
  • expiry behavior
  • invalid token response
  • whether response should be HTML, JSON, redirect, 400, or 404

Then update controller/tests to match the intended behavior.

Priority

Low


16. Remove Insecure TLS Setting from CI

Problem

The log warns:

NODE_TLS_REJECT_UNAUTHORIZED=0 makes TLS connections insecure

Required Fix

Remove this setting from CI.

If the runner needs to trust an internal/self-signed certificate, install the internal CA certificate properly instead.

Priority

Low but important for security


Recommended Fix Order

Fix in this order:

  1. Authorization test helpers and permission contracts
  2. School-year admin route protection
  3. Missing SchoolYearController@archive
  4. Route coverage matrix for admin progress routes
  5. Database schema drift
  6. SQLite test database locking
  7. Service constructor test updates
  8. Final class mocking issue
  9. API response shape consistency
  10. Validation payload aliases and status codes
  11. Missing legacy route aliases
  12. Remaining workflow-specific failures

Main Root Causes

The test suite is failing because of these root causes:

  1. Authorization hardening changed route behavior
  2. Tests still create users without proper roles/permissions
  3. Backend routes and frontend/test expectations drifted apart
  4. Migrations and models are no longer aligned
  5. Service constructors changed but unit tests were not updated
  6. API response and validation contracts are inconsistent
  7. SQLite test database setup is unstable

Fixing those root causes should collapse most of the 352 failures without manually touching every failing test.