373 lines
13 KiB
PHP
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);
|
|
}
|
|
} |