diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 3432aa80..826d86b5 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -98,8 +98,7 @@ jobs: runs-on: ubuntu-latest container: image: mcr.microsoft.com/devcontainers/php:8.4 - env: - NODE_TLS_REJECT_UNAUTHORIZED: "0" + env: {} steps: - name: Checkout repository run: | @@ -195,8 +194,7 @@ jobs: runs-on: ubuntu-latest container: image: node:22-bookworm-slim - env: - NODE_TLS_REJECT_UNAUTHORIZED: "0" + env: {} steps: - name: Checkout repository run: | @@ -299,8 +297,7 @@ jobs: runs-on: ubuntu-latest container: image: mcr.microsoft.com/devcontainers/php:8.4 - env: - NODE_TLS_REJECT_UNAUTHORIZED: "0" + env: {} if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' needs: [test, build, security] environment: diff --git a/app/Http/Controllers/Api/SchoolYears/SchoolYearController.php b/app/Http/Controllers/Api/SchoolYears/SchoolYearController.php index 34aad5d7..7dbb9c3a 100644 --- a/app/Http/Controllers/Api/SchoolYears/SchoolYearController.php +++ b/app/Http/Controllers/Api/SchoolYears/SchoolYearController.php @@ -139,4 +139,12 @@ class SchoolYearController extends BaseApiController 'data' => $this->service->closingReport($schoolYear), ]); } + + public function archive(Request $request, int $schoolYear): JsonResponse + { + return response()->json([ + 'ok' => true, + 'data' => $this->service->archive($schoolYear, $this->getCurrentUserId()), + ]); + } } diff --git a/app/Services/ApplicationUrlService.php b/app/Services/ApplicationUrlService.php index a175d085..32216cc0 100644 --- a/app/Services/ApplicationUrlService.php +++ b/app/Services/ApplicationUrlService.php @@ -8,7 +8,7 @@ use Illuminate\Support\Facades\Route; /** * Laravel named routes (`route()`), cookie-session URLs, SPA paths, and absolute URLs for `public/` paths. */ -final class ApplicationUrlService +class ApplicationUrlService { /** Absolute URL for a path under `public/` (e.g. `/docs/openapi_*.yaml`). */ public function absolutePublicPathUrl(string $path): string diff --git a/app/Services/SchoolYears/SchoolYearClosureService.php b/app/Services/SchoolYears/SchoolYearClosureService.php index 6c0e805c..d5e98726 100644 --- a/app/Services/SchoolYears/SchoolYearClosureService.php +++ b/app/Services/SchoolYears/SchoolYearClosureService.php @@ -1168,6 +1168,34 @@ class SchoolYearClosureService return $email !== '' ? $email : $userId; } + public function archive(int $id, ?int $actorId): array + { + $year = $this->findOrFail($id); + + if ($year->status === SchoolYear::STATUS_ARCHIVED) { + throw ValidationException::withMessages([ + 'school_year' => ['This school year is already archived.'], + ]); + } + + $oldState = $year->toArray(); + $year->status = SchoolYear::STATUS_ARCHIVED; + $year->is_current = false; + $year->save(); + + $this->logAudit( + $actorId, + 'school_year_archived', + 'school_years', + (int) $year->id, + $oldState, + $year->fresh()?->toArray(), + null + ); + + return $this->presentSchoolYear($year->fresh()); + } + private function presentSchoolYear(?SchoolYear $year): ?array { if (! $year) { diff --git a/database/migrations/2026_07_06_000000_fix_schema_drift_for_testing.php b/database/migrations/2026_07_06_000000_fix_schema_drift_for_testing.php new file mode 100644 index 00000000..420d981b --- /dev/null +++ b/database/migrations/2026_07_06_000000_fix_schema_drift_for_testing.php @@ -0,0 +1,114 @@ +addColumns('email_templates', [ + 'created_at' => fn (Blueprint $table) => $table->timestamp('created_at')->nullable(), + ]); + + // class_prep_adjustments: missing updated_at + $this->addColumns('class_prep_adjustments', [ + 'updated_at' => fn (Blueprint $table) => $table->timestamp('updated_at')->nullable(), + ]); + + // late_slip_logs: printed_at should be nullable + if (Schema::hasTable('late_slip_logs') && Schema::hasColumn('late_slip_logs', 'printed_at')) { + Schema::table('late_slip_logs', function (Blueprint $table) { + $table->timestamp('printed_at')->nullable()->default(null)->change(); + }); + } + + // payments: number_of_installments needs default + if (Schema::hasTable('payments') && Schema::hasColumn('payments', 'number_of_installments')) { + Schema::table('payments', function (Blueprint $table) { + $table->unsignedInteger('number_of_installments')->default(1)->change(); + }); + } + + // ip_attempts: last_attempt_at needs default + if (Schema::hasTable('ip_attempts') && Schema::hasColumn('ip_attempts', 'last_attempt_at')) { + Schema::table('ip_attempts', function (Blueprint $table) { + $table->timestamp('last_attempt_at')->useCurrent()->change(); + }); + } + } + + public function down(): void + { + $this->dropColumns('email_templates', ['created_at']); + $this->dropColumns('class_prep_adjustments', ['updated_at']); + + if (Schema::hasTable('late_slip_logs') && Schema::hasColumn('late_slip_logs', 'printed_at')) { + Schema::table('late_slip_logs', function (Blueprint $table) { + $table->timestamp('printed_at')->nullable(false)->change(); + }); + } + + if (Schema::hasTable('payments') && Schema::hasColumn('payments', 'number_of_installments')) { + Schema::table('payments', function (Blueprint $table) { + $table->unsignedInteger('number_of_installments')->default(null)->change(); + }); + } + + if (Schema::hasTable('ip_attempts') && Schema::hasColumn('ip_attempts', 'last_attempt_at')) { + Schema::table('ip_attempts', function (Blueprint $table) { + $table->timestamp('last_attempt_at')->useCurrent(false)->change(); + }); + } + } + + /** + * @param array $columns + */ + private function addColumns(string $table, array $columns): void + { + if (! Schema::hasTable($table)) { + return; + } + + $missing = array_filter( + array_keys($columns), + static fn (string $column): bool => ! Schema::hasColumn($table, $column) + ); + + if ($missing === []) { + return; + } + + Schema::table($table, function (Blueprint $blueprint) use ($columns, $missing): void { + foreach ($missing as $column) { + $columns[$column]($blueprint); + } + }); + } + + /** + * @param list $columns + */ + private function dropColumns(string $table, array $columns): void + { + if (! Schema::hasTable($table)) { + return; + } + + $existing = array_filter( + $columns, + static fn (string $column): bool => Schema::hasColumn($table, $column) + ); + + if ($existing === []) { + return; + } + + Schema::table($table, function (Blueprint $blueprint) use ($existing): void { + $blueprint->dropColumn($existing); + }); + } +}; diff --git a/docs/phpunit_failure_fix_summary.md b/docs/phpunit_failure_fix_summary.md new file mode 100644 index 00000000..e9d85bb0 --- /dev/null +++ b/docs/phpunit_failure_fix_summary.md @@ -0,0 +1,836 @@ +# 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: + +```php +$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: + +```php +$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: + +```http +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: + +```text +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: + +```text +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: + +```http +POST /api/v1/school-years/{schoolYear}/archive +``` + +But the controller method does not exist. + +### Required Fix + +Add the method to: + +```text +app/Http/Controllers/Api/SchoolYears/SchoolYearController.php +``` + +Required method: + +```php +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: + +```http +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: + +```text +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: + +```text +table email_templates has no column named created_at +``` + +Fix: + +```php +$table->timestamps(); +``` + +Or set `$timestamps = false` on the model if timestamps are not intended. + +Preferred fix: **add timestamps**. + +--- + +#### `class_prep_adjustments` + +Error: + +```text +table class_prep_adjustments has no column named updated_at +``` + +Fix: + +```php +$table->timestamps(); +``` + +--- + +#### `late_slip_logs` + +Error: + +```text +NOT NULL constraint failed: late_slip_logs.printed_at +``` + +Fix one: + +```php +$table->timestamp('printed_at')->nullable(); +``` + +or ensure every insert provides `printed_at`. + +Preferred fix: **make nullable**. + +--- + +#### `additional_charges` + +Error: + +```text +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: + +```text +NOT NULL constraint failed: payments.number_of_installments +``` + +Fix: + +```php +$table->unsignedInteger('number_of_installments')->default(1); +``` + +Also ensure payment creation services pass a value when appropriate. + +--- + +#### `ip_attempts` + +Error: + +```text +NOT NULL constraint failed: ip_attempts.last_attempt_at +``` + +Fix: + +```php +$table->timestamp('last_attempt_at')->useCurrent(); +``` + +or update all insert paths. + +--- + +#### `student_class` + +Error: + +```text +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: + +```text +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: + +```text +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: + +```env +DB_CONNECTION=sqlite +DB_DATABASE=/tmp/alrahma_testing.sqlite +``` + +Before tests: + +```bash +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: + +```text +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: + +```php +$service = new SomeService($oldArg1, $oldArg2); +``` + +with container resolution: + +```php +$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: + +```text +App\Services\ApplicationUrlService +``` + +because it is declared `final`. + +### Required Fix + +Choose one: + +1. Remove `final` +2. Extract an interface, for example: + +```php +interface ApplicationUrlProvider +``` + +3. 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: + +```json +{ + "ok": true +} +``` + +```json +{ + "status": true, + "message": "...", + "data": {} +} +``` + +Raw arrays are also returned in some places. + +### Required Fix + +Standardize API v1 responses. + +Recommended shape: + +```json +{ + "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: + +```text +min_score +max_score +template_text +``` + +Old callers send: + +```text +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: + +```text +comments +``` + +Old callers send: + +```text +comment +``` + +Fix: + +```php +$comments = $request->input('comments', $request->input('comment')); +``` + +--- + +#### Below-60 grading + +Current required field: + +```text +semester +``` + +Some callers only send: + +```text +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: + +```text +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: + +```text +permissions +``` + +Old callers send: + +```text +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: + +```http +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: + +```php +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: + +```http +400 Bad Request +``` + +when tests expect: + +```http +422 Unprocessable Entity +``` + +### Required Fix + +Use Laravel validation responses consistently. + +Expected status rules: + +```text +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: + +```http +207 Multi-Status +``` + +where tests expect: + +```http +200 OK +``` + +Affected endpoint: + +```http +/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: + +```text +3000 users +``` + +but creates: + +```text +3001 users +``` + +### Required Fix + +Find the extra user: + +```php +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: + +```text +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. diff --git a/routes/api.php b/routes/api.php index ecc94b25..88166e5d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -509,7 +509,7 @@ Route::prefix('v1')->group(function () { }); Route::middleware(['multi.auth', 'account.active'])->prefix('school-years')->group(function () { - Route::get('/', [SchoolYearController::class, 'index']); + Route::get('/', [SchoolYearController::class, 'index'])->middleware('perm:school_year.view'); Route::get('current', [SchoolYearController::class, 'current']); Route::get('options', [SchoolYearController::class, 'options']); Route::post('/', [SchoolYearController::class, 'store'])->middleware('perm:school_year.create'); @@ -1256,6 +1256,12 @@ Route::prefix('v1')->group(function () { Route::delete('{id}', [ConfigurationAdminController::class, 'destroy']); }); + // Legacy alias: /configuration-admin -> /configuration + Route::middleware('admin.access')->prefix('configuration-admin')->group(function () { + Route::get('/', [ConfigurationAdminController::class, 'index']); + Route::post('/', [ConfigurationAdminController::class, 'store']); + }); + Route::prefix('system')->group(function () { Route::get('semester-range/school-year', [SemesterRangeController::class, 'schoolYearRange']); Route::get('semester-range/semester', [SemesterRangeController::class, 'semesterRange']); diff --git a/tests/Feature/Api/ApiUseCaseCoverageMatrixTest.php b/tests/Feature/Api/ApiUseCaseCoverageMatrixTest.php index 77511206..b61786bb 100644 --- a/tests/Feature/Api/ApiUseCaseCoverageMatrixTest.php +++ b/tests/Feature/Api/ApiUseCaseCoverageMatrixTest.php @@ -89,7 +89,7 @@ class ApiUseCaseCoverageMatrixTest extends TestCase 'public_content_and_support' => '#^api/(?:v1/)?(?:login|register|auth|documentation|docs|docs/public|policies|pages|contact|frontend|health|system/db-check|access_denied|certificates/verify|timeoff/notify|winners/competitions|confirm_authorized_user|set_authorized_user_password|badge_scan|scanner)(?:/|$)#', 'legacy_compatibility' => '#^api/(?:proofread|emails|compare|attendance-templates|attendance-comment-templates|administrator/attendance-templates)(?:/|$)#', 'preferences_navigation_and_ui' => '#^api/v1/(?:preferences|nav-builder|landing|info-icon|dashboard|utilities|ui|settings|configuration|system|stats|role-switcher)(?:/|$)#', - 'administration_enrollment_and_school_years' => '#^api/v1/(?:administrator|school-years|class-sections|users|role-permissions|ip-bans|notifications)(?:/|$)#', + 'administration_enrollment_and_school_years' => '#^api/v1/(?:admin|administrator|school-years|class-sections|users|role-permissions|ip-bans|notifications)(?:/|$)#', '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)(?:/|$)#', diff --git a/tests/Unit/Services/Administrator/AdministratorAbsenceServiceTest.php b/tests/Unit/Services/Administrator/AdministratorAbsenceServiceTest.php index 32586771..3d467528 100644 --- a/tests/Unit/Services/Administrator/AdministratorAbsenceServiceTest.php +++ b/tests/Unit/Services/Administrator/AdministratorAbsenceServiceTest.php @@ -30,7 +30,8 @@ class AdministratorAbsenceServiceTest extends TestCase new User, new StaffAttendance, new SemesterRangeService(new SemesterConfigService), - Mockery::mock(StaffTimeOffLinkService::class) + Mockery::mock(StaffTimeOffLinkService::class), + app(\App\Services\ApplicationUrlService::class), ); $request = Request::create('/absences', 'POST', ['dates' => ['2025-01-05']]); diff --git a/tests/Unit/Services/Administrator/TeacherSubmissionNotificationServiceTest.php b/tests/Unit/Services/Administrator/TeacherSubmissionNotificationServiceTest.php index 539a53e4..2e0a0eac 100644 --- a/tests/Unit/Services/Administrator/TeacherSubmissionNotificationServiceTest.php +++ b/tests/Unit/Services/Administrator/TeacherSubmissionNotificationServiceTest.php @@ -19,7 +19,7 @@ class TeacherSubmissionNotificationServiceTest extends TestCase $shared = Mockery::mock(AdministratorSharedService::class); $support = Mockery::mock(TeacherSubmissionSupportService::class); - $service = new TeacherSubmissionNotificationService($shared, $support); + $service = new TeacherSubmissionNotificationService($shared, $support, app(\App\Services\ApplicationUrlService::class)); $request = Request::create('/notify', 'POST', []); $result = $service->send($request, 1); diff --git a/tests/Unit/Services/ClassProgress/ClassProgressMutationServiceTest.php b/tests/Unit/Services/ClassProgress/ClassProgressMutationServiceTest.php index faaa3be4..542ad3fe 100644 --- a/tests/Unit/Services/ClassProgress/ClassProgressMutationServiceTest.php +++ b/tests/Unit/Services/ClassProgress/ClassProgressMutationServiceTest.php @@ -6,7 +6,9 @@ use App\Models\ClassProgressReport; use App\Models\User; use App\Services\ClassProgress\ClassProgressAttachmentService; use App\Services\ClassProgress\ClassProgressMutationService; +use App\Services\ClassProgress\ClassProgressQueryService; use App\Services\ClassProgress\ClassProgressRuleService; +use App\Services\SchoolYears\SchoolYearContextService; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; use Tests\TestCase; @@ -23,7 +25,9 @@ class ClassProgressMutationServiceTest extends TestCase $service = new ClassProgressMutationService( new ClassProgressRuleService, - new ClassProgressAttachmentService + new ClassProgressAttachmentService, + app(ClassProgressQueryService::class), + app(SchoolYearContextService::class), ); $payload = [ @@ -48,7 +52,9 @@ class ClassProgressMutationServiceTest extends TestCase $service = new ClassProgressMutationService( new ClassProgressRuleService, - new ClassProgressAttachmentService + new ClassProgressAttachmentService, + app(ClassProgressQueryService::class), + app(SchoolYearContextService::class), ); $updated = $service->updateReport($report, ['status' => 'behind'], []); @@ -61,7 +67,9 @@ class ClassProgressMutationServiceTest extends TestCase $report = ClassProgressReport::factory()->create(); $service = new ClassProgressMutationService( new ClassProgressRuleService, - new ClassProgressAttachmentService + new ClassProgressAttachmentService, + app(ClassProgressQueryService::class), + app(SchoolYearContextService::class), ); $service->deleteReport($report); diff --git a/tests/Unit/Services/Discounts/DiscountApplyServiceTest.php b/tests/Unit/Services/Discounts/DiscountApplyServiceTest.php index 21ea1a47..5ea545c4 100644 --- a/tests/Unit/Services/Discounts/DiscountApplyServiceTest.php +++ b/tests/Unit/Services/Discounts/DiscountApplyServiceTest.php @@ -2,6 +2,7 @@ namespace Tests\Unit\Services\Discounts; +use App\Services\ApplicationUrlService; use App\Services\Discounts\DiscountApplyService; use App\Services\Discounts\DiscountContextService; use App\Services\Discounts\DiscountInvoiceService; @@ -30,7 +31,7 @@ class DiscountApplyServiceTest extends TestCase 'is_active' => 1, ]); - $service = new DiscountApplyService(new DiscountContextService, new DiscountInvoiceService); + $service = new DiscountApplyService(new DiscountContextService, new DiscountInvoiceService(app(ApplicationUrlService::class))); $result = $service->applyVoucher(1, [10], true, 1); $this->assertFalse($result['ok']); diff --git a/tests/Unit/Services/Refunds/RefundPolicyServiceTest.php b/tests/Unit/Services/Refunds/RefundPolicyServiceTest.php index 6d4ecd1a..c7acb608 100644 --- a/tests/Unit/Services/Refunds/RefundPolicyServiceTest.php +++ b/tests/Unit/Services/Refunds/RefundPolicyServiceTest.php @@ -2,6 +2,7 @@ namespace Tests\Unit\Services\Refunds; +use App\Services\ApplicationUrlService; use App\Services\Discounts\DiscountInvoiceService; use App\Services\Fees\FeeRefundDetailService; use App\Services\Refunds\RefundInvoiceAdjustmentService; @@ -85,7 +86,7 @@ class RefundPolicyServiceTest extends TestCase $service = new RefundPolicyService( new FeeRefundDetailService, - new RefundInvoiceAdjustmentService(new DiscountInvoiceService) + new RefundInvoiceAdjustmentService(new DiscountInvoiceService(app(ApplicationUrlService::class))) ); $result = $service->processWithdrawalRefund(10, 1, '2025-2026', 'Fall', 1); diff --git a/tests/Unit/Services/Refunds/RefundRequestServiceTest.php b/tests/Unit/Services/Refunds/RefundRequestServiceTest.php index 12f97147..f3c7ff50 100644 --- a/tests/Unit/Services/Refunds/RefundRequestServiceTest.php +++ b/tests/Unit/Services/Refunds/RefundRequestServiceTest.php @@ -2,6 +2,7 @@ namespace Tests\Unit\Services\Refunds; +use App\Services\ApplicationUrlService; use App\Services\Discounts\DiscountInvoiceService; use App\Services\Fees\FeeRefundDetailService; use App\Services\Refunds\RefundInvoiceAdjustmentService; @@ -61,7 +62,7 @@ class RefundRequestServiceTest extends TestCase $service = new RefundRequestService( new RefundPolicyService( new FeeRefundDetailService, - new RefundInvoiceAdjustmentService(new DiscountInvoiceService) + new RefundInvoiceAdjustmentService(new DiscountInvoiceService(app(ApplicationUrlService::class))) ), new RefundSummaryService, new RefundNotificationService