seedRolesAndPermissions(); } /** * Test membership expiry on boundary date */ public function test_membership_expiry_on_boundary_date(): void { // Member expires today $memberExpiringToday = $this->createMember([ 'membership_status' => Member::STATUS_ACTIVE, 'membership_started_at' => now()->subYear(), 'membership_expires_at' => now()->endOfDay(), ]); // Should still be considered active if expires at end of today $this->assertTrue( $memberExpiringToday->membership_expires_at->isToday() || $memberExpiringToday->membership_expires_at->isFuture() ); } /** * Test membership renewal before expiry */ public function test_membership_renewal_before_expiry(): void { $member = $this->createActiveMember([ 'membership_expires_at' => now()->addMonth(), ]); $originalExpiry = $member->membership_expires_at->copy(); // Simulate renewal - extend by one year $member->membership_expires_at = $member->membership_expires_at->addYear(); $member->save(); $this->assertTrue($member->membership_expires_at->gt($originalExpiry)); $this->assertTrue($member->hasPaidMembership()); } /** * Test membership renewal after expiry */ public function test_membership_renewal_after_expiry(): void { $member = $this->createExpiredMember(); $this->assertFalse($member->hasPaidMembership()); // Reactivate membership $member->membership_status = Member::STATUS_ACTIVE; $member->membership_started_at = now(); $member->membership_expires_at = now()->addYear(); $member->save(); $this->assertTrue($member->hasPaidMembership()); } /** * Test duplicate national ID detection */ public function test_duplicate_national_id_detection(): void { // Create first member with national ID $member1 = $this->createMember(['national_id' => 'A123456789']); // Attempt to create second member with same national ID // This should be handled by validation or unique constraint $this->assertDatabaseHas('members', ['id' => $member1->id]); } /** * Test unicode characters in name */ public function test_unicode_characters_in_name(): void { $member = $this->createMember([ 'full_name' => '張三李四 王五', ]); $this->assertEquals('張三李四 王五', $member->full_name); } /** * Test very long address handling */ public function test_very_long_address_handling(): void { $longAddress = str_repeat('台北市信義區信義路五段7號 ', 10); $member = $this->createMember([ 'address_line_1' => $longAddress, ]); // Address should be stored (possibly truncated depending on DB column size) $this->assertNotEmpty($member->address_line_1); } /** * Test special characters in fields */ public function test_special_characters_in_fields(): void { $member = $this->createMember([ 'full_name' => "O'Connor-Smith", 'address_line_1' => '123 Main St. #456 & Co.', ]); $this->assertEquals("O'Connor-Smith", $member->full_name); $this->assertStringContainsString('&', $member->address_line_1); } /** * Test null optional fields */ public function test_null_optional_fields(): void { $member = $this->createMember([ 'address_line_2' => null, 'emergency_contact_name' => null, 'emergency_contact_phone' => null, ]); $this->assertNull($member->address_line_2); $this->assertNull($member->emergency_contact_name); $this->assertNull($member->emergency_contact_phone); } /** * Test concurrent status updates */ public function test_concurrent_status_updates(): void { $member = $this->createPendingMember(); // Simulate two concurrent updates $member1 = Member::find($member->id); $member2 = Member::find($member->id); $member1->membership_status = Member::STATUS_ACTIVE; $member1->save(); // Second update should still work $member2->refresh(); $member2->membership_status = Member::STATUS_SUSPENDED; $member2->save(); $member->refresh(); $this->assertEquals(Member::STATUS_SUSPENDED, $member->membership_status); } /** * Test orphaned member without user */ public function test_orphaned_member_without_user(): void { $user = User::factory()->create(); $member = Member::factory()->create(['user_id' => $user->id]); // Delete user (cascade should handle this, or soft delete) $userId = $user->id; // Check member still exists and relationship handling $this->assertDatabaseHas('members', ['user_id' => $userId]); } }