5e35fefd69
API CI/CD / Validate (composer + pint) (push) Successful in 2m47s
API CI/CD / Test (PHPUnit) (push) Failing after 3m8s
API CI/CD / Build frontend assets (push) Failing after 5m22s
API CI/CD / Security audit (push) Failing after 34s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
295 lines
9.9 KiB
PHP
295 lines
9.9 KiB
PHP
<?php
|
|
|
|
namespace Tests\Concerns;
|
|
|
|
use App\Models\Role;
|
|
use App\Models\SchoolYear;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Testing\TestResponse;
|
|
|
|
/**
|
|
* Reusable deterministic fixtures for end-to-end API scenario tests.
|
|
*
|
|
* @see docs/alrahma_end_to_end_use_case_test_plan.md Part III
|
|
*/
|
|
trait SeedsE2ETestFixtures
|
|
{
|
|
use CreatesApiTestUsers;
|
|
|
|
protected const E2E_SCHOOL_YEAR = '2025-2026';
|
|
|
|
protected const E2E_PREV_SCHOOL_YEAR = '2024-2025';
|
|
|
|
protected const E2E_SEMESTER = 'Fall';
|
|
|
|
protected function seedE2EConfiguration(): void
|
|
{
|
|
// Pin calendar context so semester-aware services resolve to Fall 2025-2026.
|
|
$this->travelTo('2025-10-05 10:00:00');
|
|
|
|
$this->seedApiTestConfig(self::E2E_SCHOOL_YEAR, self::E2E_SEMESTER);
|
|
|
|
DB::table('configuration')->updateOrInsert(
|
|
['config_key' => 'fall_semester_start'],
|
|
['config_value' => '2025-09-01']
|
|
);
|
|
DB::table('configuration')->updateOrInsert(
|
|
['config_key' => 'spring_semester_start'],
|
|
['config_value' => '2026-01-15']
|
|
);
|
|
DB::table('configuration')->updateOrInsert(
|
|
['config_key' => 'last_school_day'],
|
|
['config_value' => '2026-06-15']
|
|
);
|
|
}
|
|
|
|
protected function seedClosedSchoolYear(): void
|
|
{
|
|
SchoolYear::query()->updateOrCreate(
|
|
['name' => self::E2E_PREV_SCHOOL_YEAR],
|
|
[
|
|
'start_date' => '2024-09-01',
|
|
'end_date' => '2025-06-30',
|
|
'status' => SchoolYear::STATUS_CLOSED,
|
|
'is_current' => false,
|
|
]
|
|
);
|
|
}
|
|
|
|
protected function seedClassSection(
|
|
int $classSectionId = 701,
|
|
string $name = '5A',
|
|
int $classId = 5
|
|
): int {
|
|
DB::table('classSection')->insert([
|
|
'class_id' => $classId,
|
|
'class_section_id' => $classSectionId,
|
|
'class_section_name' => $name,
|
|
'semester' => self::E2E_SEMESTER,
|
|
'school_year' => self::E2E_SCHOOL_YEAR,
|
|
]);
|
|
|
|
return $classSectionId;
|
|
}
|
|
|
|
protected function seedTeacherClassAssignment(int $teacherId, int $classSectionId, string $position = 'main'): void
|
|
{
|
|
DB::table('teacher_class')->insert([
|
|
'teacher_id' => $teacherId,
|
|
'class_section_id' => $classSectionId,
|
|
'position' => $position,
|
|
'school_year' => self::E2E_SCHOOL_YEAR,
|
|
'semester' => self::E2E_SEMESTER,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
protected function seedStudentClassAssignment(int $studentId, int $classSectionId): void
|
|
{
|
|
DB::table('student_class')->insert([
|
|
'student_id' => $studentId,
|
|
'class_section_id' => $classSectionId,
|
|
'school_year' => self::E2E_SCHOOL_YEAR,
|
|
'semester' => self::E2E_SEMESTER,
|
|
'is_event_only' => 0,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return array{teacher: User, class_section_id: int, student_id: int, parent_id: int}
|
|
*/
|
|
protected function seedTeacherClassWithStudent(?int $classSectionId = null): array
|
|
{
|
|
$this->seedE2EConfiguration();
|
|
$classSectionId ??= $this->seedClassSection(801, '3A', 3);
|
|
|
|
$teacher = $this->createApiUserWithRole('teacher', [
|
|
'firstname' => 'Tara',
|
|
'lastname' => 'Teacher',
|
|
'email' => 'e2e.teacher.'.uniqid('', true).'@example.test',
|
|
]);
|
|
$this->seedTeacherClassAssignment($teacher->id, $classSectionId);
|
|
|
|
$parent = $this->createApiUserWithRole('parent', [
|
|
'firstname' => 'Parent',
|
|
'lastname' => 'Fixture',
|
|
'email' => 'e2e.parent.'.uniqid('', true).'@example.test',
|
|
]);
|
|
|
|
$studentId = DB::table('students')->insertGetId([
|
|
'school_id' => 'S-E2E-'.random_int(1000, 9999),
|
|
'firstname' => 'Kai',
|
|
'lastname' => 'Student',
|
|
'dob' => '2014-09-01',
|
|
'age' => 11,
|
|
'gender' => 'Male',
|
|
'photo_consent' => 1,
|
|
'parent_id' => $parent->id,
|
|
'year_of_registration' => '2025',
|
|
'school_year' => self::E2E_SCHOOL_YEAR,
|
|
'semester' => self::E2E_SEMESTER,
|
|
'is_active' => 1,
|
|
]);
|
|
$this->seedStudentClassAssignment($studentId, $classSectionId);
|
|
DB::table('enrollments')->insert([
|
|
'student_id' => $studentId,
|
|
'class_section_id' => $classSectionId,
|
|
'parent_id' => $parent->id,
|
|
'enrollment_date' => '2025-09-01',
|
|
'enrollment_status' => 'enrolled',
|
|
'admission_status' => 'accepted',
|
|
'is_withdrawn' => 0,
|
|
'school_year' => self::E2E_SCHOOL_YEAR,
|
|
'semester' => self::E2E_SEMESTER,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
return [
|
|
'teacher' => $teacher,
|
|
'class_section_id' => $classSectionId,
|
|
'student_id' => $studentId,
|
|
'parent_id' => $parent->id,
|
|
'parent' => $parent,
|
|
];
|
|
}
|
|
|
|
protected function createParentAccountViaApi(array $overrides = []): int
|
|
{
|
|
$parentRoleId = (int) Role::query()->where('name', 'parent')->value('id');
|
|
$unique = str_replace('.', '', uniqid('', true));
|
|
|
|
$payload = array_merge([
|
|
'firstname' => 'E2E',
|
|
'lastname' => 'Parent',
|
|
'gender' => 'Female',
|
|
'cellphone' => '5551234567',
|
|
'email' => "e2e.parent.$unique@example.test",
|
|
'address_street' => '42 Cedar Lane',
|
|
'city' => 'Brooklyn',
|
|
'state' => 'NY',
|
|
'zip' => '11201',
|
|
'accept_school_policy' => true,
|
|
'role_id' => $parentRoleId,
|
|
'password' => 'secret123',
|
|
'semester' => self::E2E_SEMESTER,
|
|
'school_year' => self::E2E_SCHOOL_YEAR,
|
|
'user_type' => 'primary',
|
|
'status' => 'Active',
|
|
'is_verified' => true,
|
|
], $overrides);
|
|
|
|
$response = $this->postJson('/api/v1/users', $payload);
|
|
$response->assertStatus(201)->assertJsonPath('ok', true);
|
|
|
|
return (int) $response->json('user_id');
|
|
}
|
|
|
|
protected function createStudentViaApi(int $parentId, array $overrides = [], bool $firstChild = true): int
|
|
{
|
|
$unique = str_replace('.', '', uniqid('', true));
|
|
$payload = array_merge([
|
|
'firstname' => 'E2E',
|
|
'lastname' => 'Student',
|
|
'dob' => '2015-05-10',
|
|
'age' => 10,
|
|
'gender' => 'Male',
|
|
'is_active' => true,
|
|
'registration_grade' => '5',
|
|
'is_new' => true,
|
|
'photo_consent' => true,
|
|
'parent_id' => $parentId,
|
|
'registration_date' => '2025-09-01',
|
|
'tuition_paid' => false,
|
|
'semester' => self::E2E_SEMESTER,
|
|
'year_of_registration' => 2025,
|
|
'school_year' => self::E2E_SCHOOL_YEAR,
|
|
], $overrides);
|
|
|
|
if ($firstChild) {
|
|
$payload = array_merge($payload, [
|
|
'name' => $overrides['name'] ?? 'E2E Parent',
|
|
'cellphone' => $overrides['cellphone'] ?? '5551234567',
|
|
'email' => $overrides['email'] ?? "e2e.contact.$unique@example.test",
|
|
'relation' => $overrides['relation'] ?? 'Mother',
|
|
]);
|
|
}
|
|
|
|
$response = $this->postJson('/api/v1/students', $payload);
|
|
$response->assertStatus(201)->assertJsonPath('ok', true);
|
|
|
|
return (int) $response->json('student.id');
|
|
}
|
|
|
|
protected function assignStudentToClassViaApi(int $studentId, int $classSectionId): TestResponse
|
|
{
|
|
return $this->postJson('/api/v1/students/assign-class', [
|
|
'student_id' => $studentId,
|
|
'class_section_id' => [$classSectionId],
|
|
]);
|
|
}
|
|
|
|
protected function postEnrollmentStatuses(array $studentStatusMap): TestResponse
|
|
{
|
|
return $this->post(
|
|
'/api/v1/administrator/enrollment-withdrawal/update-statuses?'.
|
|
http_build_query(['enrollment_status' => $studentStatusMap]),
|
|
[]
|
|
);
|
|
}
|
|
|
|
protected function seedInvoiceForParent(int $parentId, array $overrides = []): int
|
|
{
|
|
return DB::table('invoices')->insertGetId(array_merge([
|
|
'parent_id' => $parentId,
|
|
'invoice_number' => 'INV-E2E-'.random_int(1000, 9999),
|
|
'total_amount' => 500.00,
|
|
'balance' => 500.00,
|
|
'paid_amount' => 0,
|
|
'has_discount' => 0,
|
|
'issue_date' => '2025-09-01',
|
|
'due_date' => '2025-10-01',
|
|
'status' => 'unpaid',
|
|
'school_year' => self::E2E_SCHOOL_YEAR,
|
|
'semester' => self::E2E_SEMESTER,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
], $overrides));
|
|
}
|
|
|
|
protected function seedPriorYearUnpaidInvoice(int $parentId): int
|
|
{
|
|
return DB::table('invoices')->insertGetId([
|
|
'parent_id' => $parentId,
|
|
'invoice_number' => 'INV-PRIOR-'.random_int(1000, 9999),
|
|
'total_amount' => 200.00,
|
|
'balance' => 150.00,
|
|
'paid_amount' => 50.00,
|
|
'has_discount' => 0,
|
|
'issue_date' => '2024-09-01',
|
|
'due_date' => '2024-10-01',
|
|
'status' => 'unpaid',
|
|
'school_year' => self::E2E_PREV_SCHOOL_YEAR,
|
|
'semester' => self::E2E_SEMESTER,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
protected function seedDiscountVoucher(): int
|
|
{
|
|
return DB::table('discount_vouchers')->insertGetId([
|
|
'code' => 'E2E10-'.random_int(100, 999),
|
|
'discount_type' => 'fixed',
|
|
'discount_value' => 50,
|
|
'max_uses' => 100,
|
|
'times_used' => 0,
|
|
'is_active' => 1,
|
|
]);
|
|
}
|
|
}
|