seed(DatabaseSeeder::class); $admin = User::query()->where('email', 'admin@prn.local')->firstOrFail(); $this->actingAs($admin) ->get(route('admin.documents.download', $this->document('ic_document'))) ->assertOk(); } public function test_admin_can_download_bank_statement(): void { Storage::fake('local'); $this->seed(DatabaseSeeder::class); $admin = User::query()->where('email', 'admin@prn.local')->firstOrFail(); $this->actingAs($admin) ->get(route('admin.documents.download', $this->document('bank_statement'))) ->assertOk(); } public function test_unauthorized_role_cannot_access_admin_download_route(): void { Storage::fake('local'); $this->seed(DatabaseSeeder::class); $ktm = User::query()->where('email', 'ktm@prn.local')->firstOrFail(); $this->actingAs($ktm) ->get(route('admin.documents.download', $this->document('bank_statement'))) ->assertForbidden(); } // ── Finance path ───────────────────────────────────────────────────── public function test_finance_can_download_bank_statement(): void { Storage::fake('local'); $this->seed(DatabaseSeeder::class); $finance = User::query()->where('email', 'kewangan@prn.local')->firstOrFail(); $this->actingAs($finance) ->get(route('kewangan.documents.download', $this->document('bank_statement'))) ->assertOk(); } public function test_finance_cannot_download_ic_document_via_finance_route(): void { Storage::fake('local'); $this->seed(DatabaseSeeder::class); $finance = User::query()->where('email', 'kewangan@prn.local')->firstOrFail(); $this->actingAs($finance) ->get(route('kewangan.documents.download', $this->document('ic_document'))) ->assertForbidden(); } // ── PPM path ────────────────────────────────────────────────────────── public function test_ppm_can_download_ic_document_from_own_pusat(): void { Storage::fake('local'); $this->seed(DatabaseSeeder::class); $ppm = User::query()->where('email', 'ppm@prn.local')->firstOrFail(); $pusat = $this->ppmPusat($ppm); $document = $this->documentForPusat($pusat, 'ic_document'); $this->actingAs($ppm) ->get(route('ppm.applications.documents.download', [ 'application' => $document->application->public_uuid, 'document' => $document, ])) ->assertOk(); } public function test_ppm_can_download_bank_statement_from_own_pusat(): void { Storage::fake('local'); $this->seed(DatabaseSeeder::class); $ppm = User::query()->where('email', 'ppm@prn.local')->firstOrFail(); $pusat = $this->ppmPusat($ppm); $document = $this->documentForPusat($pusat, 'bank_statement'); $this->actingAs($ppm) ->get(route('ppm.applications.documents.download', [ 'application' => $document->application->public_uuid, 'document' => $document, ])) ->assertOk(); } public function test_ppm_cannot_download_document_from_another_pusat(): void { Storage::fake('local'); $this->seed(DatabaseSeeder::class); $ppm = User::query()->where('email', 'ppm@prn.local')->firstOrFail(); // Create a document belonging to a DIFFERENT pusat than PPM's assignment $otherPusat = PusatMengundi::factory()->create([ 'election_id' => PusatMengundi::query()->where('code', 'PM001')->value('election_id'), 'daerah_mengundi_id' => PusatMengundi::query()->where('code', 'PM001')->value('daerah_mengundi_id'), ]); $document = $this->documentForPusat($otherPusat, 'ic_document'); $this->actingAs($ppm) ->get(route('ppm.applications.documents.download', [ 'application' => $document->application->public_uuid, 'document' => $document, ])) ->assertForbidden(); } public function test_ppm_download_records_activity_log(): void { Storage::fake('local'); $this->seed(DatabaseSeeder::class); $ppm = User::query()->where('email', 'ppm@prn.local')->firstOrFail(); $pusat = $this->ppmPusat($ppm); $document = $this->documentForPusat($pusat, 'ic_document'); $this->actingAs($ppm) ->get(route('ppm.applications.documents.download', [ 'application' => $document->application->public_uuid, 'document' => $document, ])); $this->assertDatabaseHas('activity_log', [ 'log_name' => 'documents', 'description' => 'document_downloaded_by_ppm', 'causer_id' => $ppm->id, ]); } // ── Helpers ─────────────────────────────────────────────────────────── private function document(string $type, string $fileName = 'file.pdf'): ApplicationDocument { $pusat = PusatMengundi::query()->where('code', 'PM001')->firstOrFail(); return $this->documentForPusat($pusat, $type, $fileName); } private function documentForPusat(PusatMengundi $pusat, string $type, string $fileName = 'file.pdf'): ApplicationDocument { $position = Position::query()->where('code', 'PAPM')->firstOrFail(); $application = Application::factory()->create([ 'election_id' => $pusat->election_id, 'pusat_mengundi_id' => $pusat->id, 'requested_position_id' => $position->id, ]); $path = 'applications/'.$application->public_uuid.'/'.$fileName; Storage::disk('local')->put($path, 'test content'); $document = ApplicationDocument::query()->create([ 'application_id' => $application->id, 'document_type' => $type, 'disk' => 'local', 'path' => $path, 'original_name' => $fileName, 'mime_type' => 'application/pdf', 'size' => 12, ]); $document->setRelation('application', $application); return $document; } private function ppmPusat(User $ppm): PusatMengundi { $ppmPosition = Position::query()->where('code', 'PPM')->firstOrFail(); $assignment = StaffAssignment::query() ->where('user_id', $ppm->id) ->where('position_id', $ppmPosition->id) ->where('status', 'active') ->firstOrFail(); return PusatMengundi::query()->findOrFail($assignment->pusat_mengundi_id); } }