user = User::factory()->create(); // Seed config values used by AssignmentService Configuration::query()->updateOrCreate( ['config_key' => 'semester'], ['config_value' => 'Fall'] ); Configuration::query()->updateOrCreate( ['config_key' => 'school_year'], ['config_value' => '2025-2026'] ); } #[Test] public function it_returns_assignment_sections_json(): void { Sanctum::actingAs($this->user); $section = ClassSection::factory()->create([ 'class_section_name' => 'Grade 5 - A', ]); $teacher = User::factory()->create([ 'firstname' => 'Ahmad', 'lastname' => 'Ali', ]); TeacherClass::query()->create([ 'teacher_id' => $teacher->id, 'class_section_id' => $section->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Main teacher assignment', ]); $student = Student::factory()->create([ 'firstname' => 'Sara', 'lastname' => 'Yousef', 'is_active' => 1, 'gender' => 'F', 'registration_grade' => '5', 'photo_consent' => 1, 'tuition_paid' => 1, 'school_id' => 'SCH-1001', ]); StudentClass::query()->create([ 'student_id' => $student->id, 'class_section_id' => $section->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Student assigned', 'is_active' => 1, // remove if your table doesn’t have this column ]); $response = $this->getJson('/api/assignments'); $response->assertOk() ->assertJsonStructure([ 'message', 'data' => [ 'classSections' => [ '*' => [ 'class_section_id', 'class_section_name', 'main_teachers', 'teacher_assistants', 'students', 'semester', 'school_year', 'description', ], ], 'schoolYears', 'selectedYear', 'selectedSemester', 'semester', 'current_school_year', ], ]); $response->assertJsonPath('data.classSections.0.class_section_id', $section->class_section_id); $response->assertJsonPath('data.classSections.0.main_teachers.0', 'Ahmad Ali'); $response->assertJsonPath('data.classSections.0.students.0.id', $student->id); } #[Test] public function it_filters_assignments_by_school_year(): void { Sanctum::actingAs($this->user); $section1 = ClassSection::factory()->create(['class_section_name' => 'Grade 4']); $section2 = ClassSection::factory()->create(['class_section_name' => 'Grade 6']); $teacher = User::factory()->create(['firstname' => 'Test', 'lastname' => 'Teacher']); TeacherClass::query()->create([ 'teacher_id' => $teacher->id, 'class_section_id' => $section1->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2024-2025', ]); TeacherClass::query()->create([ 'teacher_id' => $teacher->id, 'class_section_id' => $section2->class_section_id, 'position' => 'main', 'semester' => 'Fall', 'school_year' => '2025-2026', ]); $response = $this->getJson('/api/assignments?school_year=2025-2026'); $response->assertOk(); $sections = $response->json('data.classSections'); $ids = collect($sections)->pluck('class_section_id')->all(); $this->assertContains($section2->class_section_id, $ids); $this->assertNotContains($section1->class_section_id, $ids); $response->assertJsonPath('data.selectedYear', '2025-2026'); } #[Test] public function it_creates_or_updates_student_assignment(): void { Sanctum::actingAs($this->user); $student = Student::factory()->create(['is_active' => 1]); $section = ClassSection::factory()->create(); $payload = [ 'student_id' => $student->id, 'class_section_id' => $section->class_section_id, 'semester' => 'Spring', 'school_year' => '2025-2026', 'description' => 'Moved after placement review', ]; $response = $this->postJson('/api/assignments', $payload); $response->assertCreated() ->assertJsonPath('message', 'Assignment saved successfully.'); $this->assertDatabaseHas('student_class', [ 'student_id' => $student->id, 'class_section_id' => $section->class_section_id, 'semester' => 'Spring', 'school_year' => '2025-2026', ]); } #[Test] public function it_validates_required_fields_when_storing_assignment(): void { Sanctum::actingAs($this->user); $response = $this->postJson('/api/assignments', []); $response->assertStatus(422) ->assertJsonValidationErrors([ 'student_id', 'class_section_id', ]); } #[Test] public function store_is_idempotent_for_same_student_semester_and_year(): void { Sanctum::actingAs($this->user); $student = Student::factory()->create(['is_active' => 1]); $sectionA = ClassSection::factory()->create(); $sectionB = ClassSection::factory()->create(); // First create $this->postJson('/api/assignments', [ 'student_id' => $student->id, 'class_section_id' => $sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', ])->assertCreated(); // Second call same unique key should update (per service updateOrCreate) $this->postJson('/api/assignments', [ 'student_id' => $student->id, 'class_section_id' => $sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', 'description' => 'Reassigned', ])->assertCreated(); $this->assertEquals( 1, StudentClass::query() ->where('student_id', $student->id) ->where('semester', 'Fall') ->where('school_year', '2025-2026') ->count() ); $this->assertDatabaseHas('student_class', [ 'student_id' => $student->id, 'class_section_id' => $sectionA->class_section_id, 'semester' => 'Fall', 'school_year' => '2025-2026', ]); } #[Test] public function guest_cannot_access_assignment_api_when_auth_is_required(): void { $response = $this->getJson('/api/assignments'); $response->assertStatus(401); } }