Features: - Implement two fee types: entrance fee and annual fee (both NT$1,000) - Add 50% discount for disability certificate holders - Add disability certificate upload in member profile - Integrate disability verification into cashier approval workflow - Add membership fee settings in system admin Document permissions: - Fix hard-coded role logic in Document model - Use permission-based authorization instead of role checks Additional features: - Add announcements, general ledger, and trial balance modules - Add income management and accounting entries - Add comprehensive test suite with factories - Update UI translations to Traditional Chinese 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
193 lines
5.6 KiB
PHP
193 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\EdgeCases;
|
|
|
|
use App\Models\Member;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Tests\TestCase;
|
|
use Tests\Traits\CreatesMemberData;
|
|
use Tests\Traits\SeedsRolesAndPermissions;
|
|
|
|
/**
|
|
* Member Edge Cases Tests
|
|
*
|
|
* Tests boundary values and edge cases for member operations.
|
|
*/
|
|
class MemberEdgeCasesTest extends TestCase
|
|
{
|
|
use RefreshDatabase, SeedsRolesAndPermissions, CreatesMemberData;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Storage::fake('private');
|
|
$this->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]);
|
|
}
|
|
}
|