artisan('db:seed', ['--class' => 'RoleSeeder']); } protected function createAdminUser(): User { $admin = User::factory()->create(); $admin->assignRole('admin'); return $admin; } public function test_admin_can_create_note_for_member(): void { $admin = $this->createAdminUser(); $member = Member::factory()->create(); $response = $this->actingAs($admin)->postJson( route('admin.members.notes.store', $member), ['content' => 'Test note content'] ); $response->assertStatus(201); $response->assertJsonStructure([ 'note' => ['id', 'content', 'created_at', 'author'], 'message', ]); $response->assertJson([ 'note' => [ 'content' => 'Test note content', 'author' => [ 'id' => $admin->id, 'name' => $admin->name, ], ], 'message' => '備忘錄已新增', ]); $this->assertDatabaseHas('notes', [ 'notable_type' => 'member', 'notable_id' => $member->id, 'content' => 'Test note content', 'author_user_id' => $admin->id, ]); } public function test_admin_can_retrieve_notes_for_member(): void { $admin = $this->createAdminUser(); $member = Member::factory()->create(); // Create 3 notes Note::factory()->forMember($member)->byAuthor($admin)->create(['content' => 'Note 1']); Note::factory()->forMember($member)->byAuthor($admin)->create(['content' => 'Note 2']); Note::factory()->forMember($member)->byAuthor($admin)->create(['content' => 'Note 3']); $response = $this->actingAs($admin)->getJson( route('admin.members.notes.index', $member) ); $response->assertStatus(200); $response->assertJsonStructure([ 'notes' => [ '*' => ['id', 'content', 'created_at', 'updated_at', 'author'], ], ]); $notes = $response->json('notes'); $this->assertCount(3, $notes); // Verify each note has required fields foreach ($notes as $note) { $this->assertArrayHasKey('content', $note); $this->assertArrayHasKey('created_at', $note); $this->assertArrayHasKey('author', $note); $this->assertArrayHasKey('name', $note['author']); } } public function test_note_creation_requires_content(): void { $admin = $this->createAdminUser(); $member = Member::factory()->create(); $response = $this->actingAs($admin)->postJson( route('admin.members.notes.store', $member), ['content' => ''] ); $response->assertStatus(422); $response->assertJsonValidationErrors('content'); } public function test_note_creation_logs_audit_trail(): void { $admin = $this->createAdminUser(); $member = Member::factory()->create(); $this->actingAs($admin)->postJson( route('admin.members.notes.store', $member), ['content' => 'Audit test note'] ); $this->assertDatabaseHas('audit_logs', [ 'action' => 'note.created', ]); // Verify the audit log has the correct metadata $auditLog = \App\Models\AuditLog::where('action', 'note.created')->first(); $this->assertNotNull($auditLog); $this->assertEquals($admin->id, $auditLog->user_id); $this->assertArrayHasKey('member_id', $auditLog->metadata); $this->assertEquals($member->id, $auditLog->metadata['member_id']); $this->assertEquals($member->full_name, $auditLog->metadata['member_name']); } public function test_non_admin_cannot_create_note(): void { $user = User::factory()->create(); // User has no admin role and no permissions $member = Member::factory()->create(); $response = $this->actingAs($user)->postJson( route('admin.members.notes.store', $member), ['content' => 'Should fail'] ); $response->assertStatus(403); } public function test_member_list_includes_notes_count(): void { $admin = $this->createAdminUser(); $member1 = Member::factory()->create(); $member2 = Member::factory()->create(); // Create 3 notes for member1, 0 notes for member2 Note::factory()->forMember($member1)->byAuthor($admin)->count(3)->create(); $response = $this->actingAs($admin)->get(route('admin.members.index')); $response->assertStatus(200); $response->assertViewHas('members'); $members = $response->viewData('members'); // Find the members in the paginated result $foundMember1 = $members->firstWhere('id', $member1->id); $foundMember2 = $members->firstWhere('id', $member2->id); $this->assertNotNull($foundMember1); $this->assertNotNull($foundMember2); $this->assertEquals(3, $foundMember1->notes_count); $this->assertEquals(0, $foundMember2->notes_count); } public function test_notes_returned_newest_first(): void { $admin = $this->createAdminUser(); $member = Member::factory()->create(); // Create notes with different timestamps $oldestNote = Note::factory()->forMember($member)->byAuthor($admin)->create([ 'content' => 'Oldest note', 'created_at' => now()->subDays(3), ]); $middleNote = Note::factory()->forMember($member)->byAuthor($admin)->create([ 'content' => 'Middle note', 'created_at' => now()->subDays(1), ]); $newestNote = Note::factory()->forMember($member)->byAuthor($admin)->create([ 'content' => 'Newest note', 'created_at' => now(), ]); $response = $this->actingAs($admin)->getJson( route('admin.members.notes.index', $member) ); $response->assertStatus(200); $notes = $response->json('notes'); // Verify ordering (newest first) $this->assertEquals('Newest note', $notes[0]['content']); $this->assertEquals('Middle note', $notes[1]['content']); $this->assertEquals('Oldest note', $notes[2]['content']); } }