Files
alrahma_sunday_school_api/tests/Feature/Api/V1/Admin/AdminProgressApiTest.php
T
2026-03-05 12:29:37 -05:00

373 lines
13 KiB
PHP

<?php
namespace Tests\Feature\Api\Admin;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Tests\TestCase;
class AdminProgressApiTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->createProgressTestTablesIfMissing();
config()->set('progress.status_options', [
'draft' => 'Draft',
'submitted' => 'Submitted',
'reviewed' => 'Reviewed',
'approved' => 'Approved',
'rejected' => 'Rejected',
]);
config()->set('progress.subject_sections', [
'quran' => ['Reading', 'Memorization'],
'arabic' => ['Reading', 'Writing'],
]);
}
/** @test */
public function it_lists_grouped_progress_reports(): void
{
$sectionId = $this->insertClassSection('Grade 5 - A');
$this->insertStudentClass($sectionId);
$this->insertProgressReport([
'class_section_id' => $sectionId,
'subject' => 'quran',
'status' => 'submitted',
'week_start' => '2026-02-09',
'week_end' => '2026-02-15',
]);
$this->insertProgressReport([
'class_section_id' => $sectionId,
'subject' => 'arabic',
'status' => 'submitted',
'week_start' => '2026-02-09',
'week_end' => '2026-02-15',
]);
$response = $this->getJson('/api/admin/progress');
$response->assertOk()
->assertJsonStructure([
'data' => [
'*' => [
'week_start',
'week_end',
'class_section_id',
'class_section_name',
'reports',
],
],
'meta' => [
'filters',
'class_sections',
'status_options',
'subject_sections',
],
]);
$data = $response->json('data');
$this->assertCount(1, $data);
$this->assertEquals('2026-02-09', $data[0]['week_start']);
$this->assertEquals($sectionId, $data[0]['class_section_id']);
$this->assertArrayHasKey('quran', $data[0]['reports']);
$this->assertArrayHasKey('arabic', $data[0]['reports']);
}
/** @test */
public function it_filters_progress_reports_by_status_and_class_section(): void
{
$sectionA = $this->insertClassSection('Grade 4 - A');
$sectionB = $this->insertClassSection('Grade 6 - B');
$this->insertStudentClass($sectionA);
$this->insertStudentClass($sectionB);
$this->insertProgressReport([
'class_section_id' => $sectionA,
'subject' => 'quran',
'status' => 'approved',
'week_start' => '2026-02-09',
'week_end' => '2026-02-15',
]);
$this->insertProgressReport([
'class_section_id' => $sectionB,
'subject' => 'arabic',
'status' => 'draft',
'week_start' => '2026-02-09',
'week_end' => '2026-02-15',
]);
$response = $this->getJson('/api/admin/progress?status=approved&class_section_id=' . $sectionA);
$response->assertOk();
$data = $response->json('data');
$this->assertCount(1, $data);
$this->assertEquals($sectionA, $data[0]['class_section_id']);
$this->assertArrayHasKey('quran', $data[0]['reports']);
$this->assertArrayNotHasKey('arabic', $data[0]['reports']);
}
/** @test */
public function it_shows_progress_report_with_weekly_reports_and_attachments(): void
{
$sectionId = $this->insertClassSection('Grade 5 - A');
$report1 = $this->insertProgressReport([
'class_section_id' => $sectionId,
'subject' => 'quran',
'status' => 'submitted',
'week_start' => '2026-02-09',
'week_end' => '2026-02-15',
'flags_json' => json_encode(['needs_followup' => true]),
]);
$report2 = $this->insertProgressReport([
'class_section_id' => $sectionId,
'subject' => 'arabic',
'status' => 'submitted',
'week_start' => '2026-02-09',
'week_end' => '2026-02-15',
]);
$this->insertAttachment($report1, [
'original_name' => 'quran-notes.pdf',
'file_path' => 'uploads/progress/quran-notes.pdf',
]);
$response = $this->getJson('/api/admin/progress/reports/' . $report1);
$response->assertOk()
->assertJsonStructure([
'data' => [
'report',
'weekly_reports',
'subject_sections',
],
]);
$this->assertEquals($report1, $response->json('data.report.id'));
$this->assertEquals('Submitted', $response->json('data.report.status_label'));
$this->assertTrue($response->json('data.report.flags.needs_followup'));
$weekly = $response->json('data.weekly_reports');
$this->assertCount(2, $weekly);
$quranReport = collect($weekly)->firstWhere('subject', 'quran');
$this->assertNotNull($quranReport);
$this->assertNotEmpty($quranReport['attachments']);
$this->assertEquals('quran-notes.pdf', $quranReport['attachments'][0]['name']);
$arabicReport = collect($weekly)->firstWhere('subject', 'arabic');
$this->assertNotNull($arabicReport);
$this->assertIsArray($arabicReport['attachments']);
}
/** @test */
public function it_returns_404_for_missing_progress_report(): void
{
$this->getJson('/api/admin/progress/reports/999999')
->assertStatus(404)
->assertJson([
'message' => 'Progress report not found.',
]);
}
/** @test */
public function it_downloads_legacy_attachment_from_report_attachment_path(): void
{
File::ensureDirectoryExists(storage_path('app/uploads/progress'));
$fullPath = storage_path('app/uploads/progress/legacy-file.txt');
File::put($fullPath, 'legacy attachment content');
$reportId = $this->insertProgressReport([
'class_section_id' => 1,
'subject' => 'quran',
'status' => 'submitted',
'week_start' => '2026-02-09',
'week_end' => '2026-02-15',
'attachment_path' => 'writable/uploads/progress/legacy-file.txt',
]);
$response = $this->get('/api/admin/progress/reports/' . $reportId . '/attachment');
$response->assertOk();
$response->assertHeader('content-disposition');
$this->assertStringContainsString('legacy-file.txt', $response->headers->get('content-disposition', ''));
}
/** @test */
public function it_returns_404_when_legacy_attachment_is_missing(): void
{
$reportId = $this->insertProgressReport([
'class_section_id' => 1,
'subject' => 'quran',
'status' => 'submitted',
'week_start' => '2026-02-09',
'week_end' => '2026-02-15',
'attachment_path' => 'writable/uploads/progress/missing.txt',
]);
$this->get('/api/admin/progress/reports/' . $reportId . '/attachment')
->assertStatus(404);
}
/** @test */
public function it_downloads_normalized_attachment_by_attachment_id(): void
{
File::ensureDirectoryExists(storage_path('app/uploads/progress'));
$fullPath = storage_path('app/uploads/progress/normalized-file.txt');
File::put($fullPath, 'normalized attachment content');
$reportId = $this->insertProgressReport([
'class_section_id' => 1,
'subject' => 'arabic',
'status' => 'approved',
'week_start' => '2026-02-09',
'week_end' => '2026-02-15',
]);
$attachmentId = $this->insertAttachment($reportId, [
'original_name' => 'Teacher Notes.txt',
'file_path' => 'writable/uploads/progress/normalized-file.txt',
]);
$response = $this->get('/api/admin/progress/attachments/' . $attachmentId);
$response->assertOk();
$this->assertStringContainsString('Teacher Notes.txt', $response->headers->get('content-disposition', ''));
}
/** @test */
public function it_returns_404_for_missing_normalized_attachment(): void
{
$this->get('/api/admin/progress/attachments/999999')
->assertStatus(404);
}
// ------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------
private function createProgressTestTablesIfMissing(): void
{
if (!Schema::hasTable('classSection')) {
Schema::create('classSection', function (Blueprint $table) {
$table->increments('class_section_id');
$table->string('class_section_name')->nullable();
});
}
if (!Schema::hasTable('student_class')) {
Schema::create('student_class', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('class_section_id');
$table->unsignedInteger('student_id')->nullable();
});
}
if (!Schema::hasTable('users')) {
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('firstname')->nullable();
$table->string('lastname')->nullable();
$table->string('email')->nullable();
$table->string('password')->nullable();
$table->timestamps();
});
} else {
// Add columns if test app users table exists but lacks firstname/lastname
if (!Schema::hasColumn('users', 'firstname')) {
Schema::table('users', function (Blueprint $table) {
$table->string('firstname')->nullable();
});
}
if (!Schema::hasColumn('users', 'lastname')) {
Schema::table('users', function (Blueprint $table) {
$table->string('lastname')->nullable();
});
}
}
if (!Schema::hasTable('class_progress_reports')) {
Schema::create('class_progress_reports', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('class_section_id')->nullable();
$table->unsignedBigInteger('teacher_id')->nullable();
$table->string('subject')->nullable();
$table->date('week_start')->nullable();
$table->date('week_end')->nullable();
$table->string('status')->nullable();
$table->text('flags_json')->nullable();
$table->string('attachment_path')->nullable();
$table->text('notes')->nullable();
$table->timestamps();
});
}
if (!Schema::hasTable('class_progress_attachments')) {
Schema::create('class_progress_attachments', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('report_id');
$table->string('original_name')->nullable();
$table->string('file_path')->nullable();
$table->timestamps();
});
}
}
private function insertClassSection(string $name): int
{
return (int) \DB::table('classSection')->insertGetId([
'class_section_name' => $name,
], 'class_section_id');
}
private function insertStudentClass(int $classSectionId): int
{
return (int) \DB::table('student_class')->insertGetId([
'class_section_id' => $classSectionId,
'student_id' => rand(1000, 9999),
]);
}
private function insertProgressReport(array $override = []): int
{
$data = array_merge([
'class_section_id' => 1,
'teacher_id' => null,
'subject' => 'quran',
'week_start' => '2026-02-09',
'week_end' => '2026-02-15',
'status' => 'draft',
'flags_json' => null,
'attachment_path' => null,
'notes' => 'Test report',
'created_at' => now(),
'updated_at' => now(),
], $override);
return (int) \DB::table('class_progress_reports')->insertGetId($data);
}
private function insertAttachment(int $reportId, array $override = []): int
{
$data = array_merge([
'report_id' => $reportId,
'original_name' => 'Attachment.pdf',
'file_path' => 'writable/uploads/progress/attachment.pdf',
'created_at' => now(),
'updated_at' => now(),
], $override);
return (int) \DB::table('class_progress_attachments')->insertGetId($data);
}
}