seedRolesAndPermissions(); } /** * Test concurrent payment approval attempts */ public function test_concurrent_payment_approval_attempts(): void { $cashier1 = $this->createCashier(); $cashier2 = $this->createCashier(['email' => 'cashier2@test.com']); $data = $this->createMemberWithPendingPayment(); $payment = $data['payment']; // First cashier approves $response1 = $this->actingAs($cashier1)->post( route('admin.membership-payments.approve', $payment) ); // Refresh to simulate concurrent access $payment->refresh(); // Second cashier tries to approve (should be blocked) $response2 = $this->actingAs($cashier2)->post( route('admin.membership-payments.approve', $payment) ); // Only one should succeed $this->assertTrue( $response1->isRedirect() || $response2->isRedirect() ); } /** * Test concurrent member status update */ public function test_concurrent_member_status_update(): void { $admin = $this->createAdmin(); $member = $this->createPendingMember(); // Load same member twice $member1 = Member::find($member->id); $member2 = Member::find($member->id); // Update from first instance $member1->membership_status = Member::STATUS_ACTIVE; $member1->save(); // Update from second instance (stale data) $member2->membership_status = Member::STATUS_SUSPENDED; $member2->save(); // Final state should be the last update $member->refresh(); $this->assertEquals(Member::STATUS_SUSPENDED, $member->membership_status); } /** * Test concurrent finance document approval */ public function test_concurrent_finance_document_approval(): void { $cashier = $this->createCashier(); $accountant = $this->createAccountant(); $document = $this->createFinanceDocument([ 'status' => FinanceDocument::STATUS_PENDING, ]); // Cashier approves $this->actingAs($cashier)->post( route('admin.finance-documents.approve', $document) ); $document->refresh(); $this->assertEquals(FinanceDocument::STATUS_APPROVED_CASHIER, $document->status); // Accountant tries to approve same document at pending status // This should work since status has changed $response = $this->actingAs($accountant)->post( route('admin.finance-documents.approve', $document) ); $document->refresh(); $this->assertEquals(FinanceDocument::STATUS_APPROVED_ACCOUNTANT, $document->status); } /** * Test transaction rollback on failure */ public function test_transaction_rollback_on_failure(): void { $admin = $this->createAdmin(); $initialCount = Member::count(); try { DB::transaction(function () use ($admin) { Member::factory()->create(); throw new \Exception('Simulated failure'); }); } catch (\Exception $e) { // Expected } // Count should remain unchanged $this->assertEquals($initialCount, Member::count()); } /** * Test unique constraint handling */ public function test_unique_constraint_handling(): void { $existingUser = User::factory()->create(['email' => 'unique@test.com']); // Attempt to create user with same email $this->expectException(\Illuminate\Database\QueryException::class); User::factory()->create(['email' => 'unique@test.com']); } /** * Test sequential number generation under load */ public function test_sequential_number_generation_under_load(): void { $admin = $this->createAdmin(); // Create multiple documents rapidly $numbers = []; for ($i = 0; $i < 10; $i++) { $document = $this->createFinanceDocument(); $numbers[] = $document->document_number; } // All numbers should be unique $this->assertEquals(count($numbers), count(array_unique($numbers))); } /** * Test member number uniqueness under concurrent creation */ public function test_member_number_uniqueness_under_concurrent_creation(): void { $members = []; for ($i = 0; $i < 10; $i++) { $members[] = $this->createMember([ 'full_name' => 'Test Member '.$i, ]); } $memberNumbers = array_map(fn ($m) => $m->member_number, $members); // All member numbers should be unique $this->assertEquals(count($memberNumbers), count(array_unique($memberNumbers))); } /** * Test optimistic locking for updates */ public function test_optimistic_locking_scenario(): void { $document = $this->createFinanceDocument(); $originalAmount = $document->amount; // Load same document twice $doc1 = FinanceDocument::find($document->id); $doc2 = FinanceDocument::find($document->id); // First update $doc1->amount = $originalAmount + 100; $doc1->save(); // Second update (should overwrite) $doc2->amount = $originalAmount + 200; $doc2->save(); $document->refresh(); $this->assertEquals($originalAmount + 200, $document->amount); } /** * Test deadlock prevention */ public function test_deadlock_prevention(): void { $this->assertTrue(true); // Note: Actual deadlock testing requires specific database conditions // This placeholder confirms the test infrastructure is in place } /** * Test race condition in approval workflow */ public function test_race_condition_in_approval_workflow(): void { $cashier = $this->createCashier(); $document = $this->createFinanceDocument([ 'status' => FinanceDocument::STATUS_PENDING, ]); // Simulate multiple approval attempts $approvalCount = 0; for ($i = 0; $i < 3; $i++) { $doc = FinanceDocument::find($document->id); if ($doc->status === FinanceDocument::STATUS_PENDING) { $this->actingAs($cashier)->post( route('admin.finance-documents.approve', $doc) ); $approvalCount++; } } // Only first approval should change status $document->refresh(); $this->assertEquals(FinanceDocument::STATUS_APPROVED_CASHIER, $document->status); } }