Files
alrahma_sunday_school_api/tests/Feature/Api/V1/Assignment/AssignmentApiControllerTest.php
T
2026-06-08 23:45:55 -04:00

421 lines
15 KiB
PHP

<?php
namespace Tests\Feature\Api;
use App\Models\ClassSection;
use App\Models\Configuration;
use App\Models\Student;
use App\Models\StudentClass;
use App\Models\TeacherClass;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AssignmentApiControllerTest extends TestCase
{
use RefreshDatabase;
protected User $user;
protected User $teacherMain;
protected User $teacherAssistant;
protected ClassSection $sectionA;
protected ClassSection $sectionB;
protected Student $studentOne;
protected Student $studentTwo;
protected function setUp(): void
{
parent::setUp();
$this->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/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/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/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/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/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/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/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/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/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/assignments/class-assignment-data');
$response->assertOk();
$mainTeachers = $response->json('data.classSections.0.main_teachers');
$this->assertSame(['Main Teacher'], $mainTeachers);
}
}