Files
alrahma_sunday_school_api/tests/Concerns/SeedsE2ETestFixtures.php
T
2026-06-11 11:46:12 -04:00

282 lines
9.4 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);
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,
]);
}
}