seedConfigurations(); $this->user = User::factory()->create(); $this->teacherMain = User::factory()->create([ 'firstname' => 'Main', 'lastname' => 'Teacher', ]); $this->teacherAssistant = User::factory()->create([ 'firstname' => 'Assistant', 'lastname' => 'Teacher', ]); $this->sectionA = ClassSection::factory()->create([ 'section_name' => 'Grade 1 - A', ]); $this->sectionB = ClassSection::factory()->create([ 'section_name' => 'Grade 2 - B', ]); $this->studentOne = Student::factory()->create([ 'firstname' => 'Ali', 'lastname' => 'Hassan', 'age' => 8, 'gender' => 'Male', 'registration_grade' => 'Grade 1', 'photo_consent' => 1, 'tuition_paid' => 0, 'school_id' => 'SCH-001', 'is_active' => 1, ]); $this->studentTwo = Student::factory()->create([ 'firstname' => 'Sara', 'lastname' => 'Ibrahim', 'age' => 9, 'gender' => 'Female', 'registration_grade' => 'Grade 2', 'photo_consent' => 0, 'tuition_paid' => 1, 'school_id' => 'SCH-002', 'is_active' => 1, ]); } protected function seedConfigurations(): void { Configuration::query()->create([ 'config_key' => 'semester', 'config_value' => 'Fall', ]); Configuration::query()->create([ 'config_key' => 'school_year', 'config_value' => '2025-2026', ]); } public function test_index_returns_assignment_overview(): void { TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Morning group', ]); TeacherClass::query()->create([ 'teacher_id' => $this->teacherAssistant->id, 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'ta', 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Morning group', ]); StudentClass::query()->create([ 'student_id' => $this->studentOne->id, 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Morning group', 'updated_by' => $this->user->id, ]); $response = $this->actingAs($this->user, 'sanctum') ->getJson('/api/v1/assignments?school_year=2025-2026&semester=Fall'); $response->assertOk() ->assertJsonPath('message', 'Assignments retrieved successfully.') ->assertJsonPath('data.schoolYear', '2025-2026') ->assertJsonPath('data.selectedYear', '2025-2026') ->assertJsonPath('data.selectedSemester', 'Fall') ->assertJsonPath('data.semester', 'Fall') ->assertJsonPath('data.current_school_year', '2025-2026') ->assertJsonCount(1, 'data.classSections') ->assertJsonPath('data.classSections.0.class_section_id', $this->sectionA->class_section_id) ->assertJsonPath('data.classSections.0.class_section_name', 'Grade 1 - A') ->assertJsonPath('data.classSections.0.main_teachers.0', 'Main Teacher') ->assertJsonPath('data.classSections.0.teacher_assistants.0', 'Assistant Teacher') ->assertJsonPath('data.classSections.0.students.0.firstname', 'Ali') ->assertJsonPath('data.classSections.0.students.0.lastname', 'Hassan') ->assertJsonPath('data.classSections.0.students.0.photo_consent', true) ->assertJsonPath('data.classSections.0.students.0.tuition_paid', false) ->assertJsonPath('data.classSections.0.description', 'Morning group'); } public function test_index_filters_by_school_year(): void { TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Included', ]); TeacherClass::query()->create([ 'teacher_id' => $this->teacherAssistant->id, 'class_section_id' => $this->sectionB->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2024-2025', 'description' => 'Excluded', ]); StudentClass::query()->create([ 'student_id' => $this->studentOne->id, 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Included', 'updated_by' => $this->user->id, ]); StudentClass::query()->create([ 'student_id' => $this->studentTwo->id, 'class_section_id' => $this->sectionB->class_section_id, 'semester' => 'Fall', 'school_year' => '2024-2025', 'description' => 'Excluded', 'updated_by' => $this->user->id, ]); $response = $this->actingAs($this->user, 'sanctum') ->getJson('/api/v1/assignments?school_year=2025-2026'); $response->assertOk() ->assertJsonCount(1, 'data.classSections') ->assertJsonPath('data.classSections.0.class_section_name', 'Grade 1 - A'); } public function test_index_sorts_sections_by_name(): void { TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, 'class_section_id' => $this->sectionB->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'B group', ]); TeacherClass::query()->create([ 'teacher_id' => $this->teacherAssistant->id, 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'A group', ]); $response = $this->actingAs($this->user, 'sanctum') ->getJson('/api/v1/assignments'); $response->assertOk(); $sections = $response->json('data.classSections'); $this->assertCount(2, $sections); $this->assertSame('Grade 1 - A', $sections[0]['class_section_name']); $this->assertSame('Grade 2 - B', $sections[1]['class_section_name']); } public function test_index_excludes_inactive_students(): void { $inactiveStudent = Student::factory()->create([ 'firstname' => 'Hidden', 'lastname' => 'Student', 'is_active' => 0, ]); TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Morning group', ]); StudentClass::query()->create([ 'student_id' => $inactiveStudent->id, 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Should not show', 'updated_by' => $this->user->id, ]); $response = $this->actingAs($this->user, 'sanctum') ->getJson('/api/v1/assignments'); $response->assertOk() ->assertJsonPath('data.classSections.0.students', []); } public function test_store_creates_new_assignment(): void { $payload = [ 'student_id' => $this->studentOne->id, 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'New assignment', ]; $response = $this->actingAs($this->user, 'sanctum') ->postJson('/api/v1/assignments', $payload); $response->assertCreated() ->assertJsonPath('message', 'Assignment saved successfully.') ->assertJsonPath('data.student_id', $this->studentOne->id) ->assertJsonPath('data.class_section_id', $this->sectionA->class_section_id) ->assertJsonPath('data.semester', 'Fall') ->assertJsonPath('data.school_year', '2025-2026') ->assertJsonPath('data.description', 'New assignment'); $this->assertDatabaseHas('student_class', [ 'student_id' => $this->studentOne->id, 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'New assignment', 'updated_by' => $this->user->id, ]); } public function test_store_updates_existing_assignment_for_same_unique_keys(): void { StudentClass::query()->create([ 'student_id' => $this->studentOne->id, 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Old description', 'updated_by' => $this->user->id, ]); $payload = [ 'student_id' => $this->studentOne->id, 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Updated description', ]; $response = $this->actingAs($this->user, 'sanctum') ->postJson('/api/v1/assignments', $payload); $response->assertCreated() ->assertJsonPath('data.description', 'Updated description'); $this->assertDatabaseHas('student_class', [ 'student_id' => $this->studentOne->id, 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Updated description', ]); $this->assertSame( 1, StudentClass::query() ->where('student_id', $this->studentOne->id) ->where('class_section_id', $this->sectionA->class_section_id) ->where('semester', 'Fall') ->where('school_year', '2025-2026') ->count() ); } public function test_store_validates_required_fields(): void { $response = $this->actingAs($this->user, 'sanctum') ->postJson('/api/v1/assignments', []); $response->assertUnprocessable() ->assertJsonValidationErrors([ 'student_id', 'class_section_id', 'semester', 'school_year', ]); } public function test_store_validates_foreign_keys(): void { $payload = [ 'student_id' => 999999, 'class_section_id' => 999999, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Bad ids', ]; $response = $this->actingAs($this->user, 'sanctum') ->postJson('/api/v1/assignments', $payload); $response->assertUnprocessable() ->assertJsonValidationErrors([ 'student_id', 'class_section_id', ]); } public function test_class_assignment_data_returns_expected_structure(): void { TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Morning group', ]); StudentClass::query()->create([ 'student_id' => $this->studentOne->id, 'class_section_id' => $this->sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Morning group', 'updated_by' => $this->user->id, ]); $response = $this->actingAs($this->user, 'sanctum') ->getJson('/api/v1/assignments/class-assignment-data'); $response->assertOk() ->assertJsonPath('message', 'Class assignment data retrieved successfully.') ->assertJsonPath('data.semester', 'Fall') ->assertJsonPath('data.school_year', '2025-2026') ->assertJsonCount(1, 'data.classSections') ->assertJsonPath('data.classSections.0.class_section_name', 'Grade 1 - A') ->assertJsonPath('data.classSections.0.main_teachers.0', 'Main Teacher') ->assertJsonPath('data.classSections.0.students.0.firstname', 'Ali'); } public function test_class_assignment_data_deduplicates_teacher_names(): void { TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Morning group', ]); TeacherClass::query()->create([ 'teacher_id' => $this->teacherMain->id, 'class_section_id' => $this->sectionA->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Morning group', ]); $response = $this->actingAs($this->user, 'sanctum') ->getJson('/api/v1/assignments/class-assignment-data'); $response->assertOk(); $mainTeachers = $response->json('data.classSections.0.main_teachers'); $this->assertSame(['Main Teacher'], $mainTeachers); } }