f82017cb91
API CI/CD / Validate (composer + pint) (push) Successful in 2m6s
API CI/CD / Test (PHPUnit) (push) Failing after 2m33s
API CI/CD / Build frontend assets (push) Successful in 2m20s
API CI/CD / Security audit (push) Successful in 32s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
428 lines
15 KiB
PHP
428 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/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);
|
|
}
|
|
}
|