fix test issues
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
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
This commit is contained in:
@@ -98,8 +98,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: mcr.microsoft.com/devcontainers/php:8.4
|
image: mcr.microsoft.com/devcontainers/php:8.4
|
||||||
env:
|
env: {}
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
run: |
|
run: |
|
||||||
@@ -195,8 +194,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: node:22-bookworm-slim
|
image: node:22-bookworm-slim
|
||||||
env:
|
env: {}
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
run: |
|
run: |
|
||||||
@@ -299,8 +297,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container:
|
container:
|
||||||
image: mcr.microsoft.com/devcontainers/php:8.4
|
image: mcr.microsoft.com/devcontainers/php:8.4
|
||||||
env:
|
env: {}
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: "0"
|
|
||||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
|
||||||
needs: [test, build, security]
|
needs: [test, build, security]
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -139,4 +139,12 @@ class SchoolYearController extends BaseApiController
|
|||||||
'data' => $this->service->closingReport($schoolYear),
|
'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()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use Illuminate\Support\Facades\Route;
|
|||||||
/**
|
/**
|
||||||
* Laravel named routes (`route()`), cookie-session URLs, SPA paths, and absolute URLs for `public/` paths.
|
* 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`). */
|
/** Absolute URL for a path under `public/` (e.g. `/docs/openapi_*.yaml`). */
|
||||||
public function absolutePublicPathUrl(string $path): string
|
public function absolutePublicPathUrl(string $path): string
|
||||||
|
|||||||
@@ -1168,6 +1168,34 @@ class SchoolYearClosureService
|
|||||||
return $email !== '' ? $email : $userId;
|
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
|
private function presentSchoolYear(?SchoolYear $year): ?array
|
||||||
{
|
{
|
||||||
if (! $year) {
|
if (! $year) {
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// email_templates: missing created_at
|
||||||
|
$this->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<string, callable(Blueprint): void> $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<string> $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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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.
|
||||||
+7
-1
@@ -509,7 +509,7 @@ Route::prefix('v1')->group(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Route::middleware(['multi.auth', 'account.active'])->prefix('school-years')->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('current', [SchoolYearController::class, 'current']);
|
||||||
Route::get('options', [SchoolYearController::class, 'options']);
|
Route::get('options', [SchoolYearController::class, 'options']);
|
||||||
Route::post('/', [SchoolYearController::class, 'store'])->middleware('perm:school_year.create');
|
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']);
|
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::prefix('system')->group(function () {
|
||||||
Route::get('semester-range/school-year', [SemesterRangeController::class, 'schoolYearRange']);
|
Route::get('semester-range/school-year', [SemesterRangeController::class, 'schoolYearRange']);
|
||||||
Route::get('semester-range/semester', [SemesterRangeController::class, 'semesterRange']);
|
Route::get('semester-range/semester', [SemesterRangeController::class, 'semesterRange']);
|
||||||
|
|||||||
@@ -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)(?:/|$)#',
|
'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)(?:/|$)#',
|
'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)(?:/|$)#',
|
'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)(?:/|$)#',
|
'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)(?:/|$)#',
|
'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)(?:/|$)#',
|
'attendance_and_safety' => '#^api/v1/(?:attendance|attendance-tracking|attendance-comment-templates|attendance-templates|incidents)(?:/|$)#',
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ class AdministratorAbsenceServiceTest extends TestCase
|
|||||||
new User,
|
new User,
|
||||||
new StaffAttendance,
|
new StaffAttendance,
|
||||||
new SemesterRangeService(new SemesterConfigService),
|
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']]);
|
$request = Request::create('/absences', 'POST', ['dates' => ['2025-01-05']]);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class TeacherSubmissionNotificationServiceTest extends TestCase
|
|||||||
$shared = Mockery::mock(AdministratorSharedService::class);
|
$shared = Mockery::mock(AdministratorSharedService::class);
|
||||||
$support = Mockery::mock(TeacherSubmissionSupportService::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', []);
|
$request = Request::create('/notify', 'POST', []);
|
||||||
|
|
||||||
$result = $service->send($request, 1);
|
$result = $service->send($request, 1);
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ use App\Models\ClassProgressReport;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\ClassProgress\ClassProgressAttachmentService;
|
use App\Services\ClassProgress\ClassProgressAttachmentService;
|
||||||
use App\Services\ClassProgress\ClassProgressMutationService;
|
use App\Services\ClassProgress\ClassProgressMutationService;
|
||||||
|
use App\Services\ClassProgress\ClassProgressQueryService;
|
||||||
use App\Services\ClassProgress\ClassProgressRuleService;
|
use App\Services\ClassProgress\ClassProgressRuleService;
|
||||||
|
use App\Services\SchoolYears\SchoolYearContextService;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
@@ -23,7 +25,9 @@ class ClassProgressMutationServiceTest extends TestCase
|
|||||||
|
|
||||||
$service = new ClassProgressMutationService(
|
$service = new ClassProgressMutationService(
|
||||||
new ClassProgressRuleService,
|
new ClassProgressRuleService,
|
||||||
new ClassProgressAttachmentService
|
new ClassProgressAttachmentService,
|
||||||
|
app(ClassProgressQueryService::class),
|
||||||
|
app(SchoolYearContextService::class),
|
||||||
);
|
);
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
@@ -48,7 +52,9 @@ class ClassProgressMutationServiceTest extends TestCase
|
|||||||
|
|
||||||
$service = new ClassProgressMutationService(
|
$service = new ClassProgressMutationService(
|
||||||
new ClassProgressRuleService,
|
new ClassProgressRuleService,
|
||||||
new ClassProgressAttachmentService
|
new ClassProgressAttachmentService,
|
||||||
|
app(ClassProgressQueryService::class),
|
||||||
|
app(SchoolYearContextService::class),
|
||||||
);
|
);
|
||||||
|
|
||||||
$updated = $service->updateReport($report, ['status' => 'behind'], []);
|
$updated = $service->updateReport($report, ['status' => 'behind'], []);
|
||||||
@@ -61,7 +67,9 @@ class ClassProgressMutationServiceTest extends TestCase
|
|||||||
$report = ClassProgressReport::factory()->create();
|
$report = ClassProgressReport::factory()->create();
|
||||||
$service = new ClassProgressMutationService(
|
$service = new ClassProgressMutationService(
|
||||||
new ClassProgressRuleService,
|
new ClassProgressRuleService,
|
||||||
new ClassProgressAttachmentService
|
new ClassProgressAttachmentService,
|
||||||
|
app(ClassProgressQueryService::class),
|
||||||
|
app(SchoolYearContextService::class),
|
||||||
);
|
);
|
||||||
|
|
||||||
$service->deleteReport($report);
|
$service->deleteReport($report);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Services\Discounts;
|
namespace Tests\Unit\Services\Discounts;
|
||||||
|
|
||||||
|
use App\Services\ApplicationUrlService;
|
||||||
use App\Services\Discounts\DiscountApplyService;
|
use App\Services\Discounts\DiscountApplyService;
|
||||||
use App\Services\Discounts\DiscountContextService;
|
use App\Services\Discounts\DiscountContextService;
|
||||||
use App\Services\Discounts\DiscountInvoiceService;
|
use App\Services\Discounts\DiscountInvoiceService;
|
||||||
@@ -30,7 +31,7 @@ class DiscountApplyServiceTest extends TestCase
|
|||||||
'is_active' => 1,
|
'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);
|
$result = $service->applyVoucher(1, [10], true, 1);
|
||||||
|
|
||||||
$this->assertFalse($result['ok']);
|
$this->assertFalse($result['ok']);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Services\Refunds;
|
namespace Tests\Unit\Services\Refunds;
|
||||||
|
|
||||||
|
use App\Services\ApplicationUrlService;
|
||||||
use App\Services\Discounts\DiscountInvoiceService;
|
use App\Services\Discounts\DiscountInvoiceService;
|
||||||
use App\Services\Fees\FeeRefundDetailService;
|
use App\Services\Fees\FeeRefundDetailService;
|
||||||
use App\Services\Refunds\RefundInvoiceAdjustmentService;
|
use App\Services\Refunds\RefundInvoiceAdjustmentService;
|
||||||
@@ -85,7 +86,7 @@ class RefundPolicyServiceTest extends TestCase
|
|||||||
|
|
||||||
$service = new RefundPolicyService(
|
$service = new RefundPolicyService(
|
||||||
new FeeRefundDetailService,
|
new FeeRefundDetailService,
|
||||||
new RefundInvoiceAdjustmentService(new DiscountInvoiceService)
|
new RefundInvoiceAdjustmentService(new DiscountInvoiceService(app(ApplicationUrlService::class)))
|
||||||
);
|
);
|
||||||
|
|
||||||
$result = $service->processWithdrawalRefund(10, 1, '2025-2026', 'Fall', 1);
|
$result = $service->processWithdrawalRefund(10, 1, '2025-2026', 'Fall', 1);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Services\Refunds;
|
namespace Tests\Unit\Services\Refunds;
|
||||||
|
|
||||||
|
use App\Services\ApplicationUrlService;
|
||||||
use App\Services\Discounts\DiscountInvoiceService;
|
use App\Services\Discounts\DiscountInvoiceService;
|
||||||
use App\Services\Fees\FeeRefundDetailService;
|
use App\Services\Fees\FeeRefundDetailService;
|
||||||
use App\Services\Refunds\RefundInvoiceAdjustmentService;
|
use App\Services\Refunds\RefundInvoiceAdjustmentService;
|
||||||
@@ -61,7 +62,7 @@ class RefundRequestServiceTest extends TestCase
|
|||||||
$service = new RefundRequestService(
|
$service = new RefundRequestService(
|
||||||
new RefundPolicyService(
|
new RefundPolicyService(
|
||||||
new FeeRefundDetailService,
|
new FeeRefundDetailService,
|
||||||
new RefundInvoiceAdjustmentService(new DiscountInvoiceService)
|
new RefundInvoiceAdjustmentService(new DiscountInvoiceService(app(ApplicationUrlService::class)))
|
||||||
),
|
),
|
||||||
new RefundSummaryService,
|
new RefundSummaryService,
|
||||||
new RefundNotificationService
|
new RefundNotificationService
|
||||||
|
|||||||
Reference in New Issue
Block a user