insert([ 'id' => 1, 'name' => 'worked_hours', 'label' => 'Worked Hours', 'icon' => 'clock', ]); } /** * Test user can view transaction they are involved in (sender) * * @test */ public function user_can_view_transaction_as_sender() { $user = User::factory()->create(); $recipient = User::factory()->create(); $userAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $user->id, ]); $recipientAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $recipient->id, ]); $transaction = Transaction::factory()->create([ 'from_account_id' => $userAccount->id, 'to_account_id' => $recipientAccount->id, 'amount' => 60, ]); $this->actingAs($user, 'web'); session(['activeProfileType' => User::class]); session(['activeProfileId' => $user->id]); $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id])); $response->assertStatus(200); } /** * Test user can view transaction they are involved in (recipient) * * @test */ public function user_can_view_transaction_as_recipient() { $sender = User::factory()->create(); $user = User::factory()->create(); $senderAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $sender->id, ]); $userAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $user->id, ]); $transaction = Transaction::factory()->create([ 'from_account_id' => $senderAccount->id, 'to_account_id' => $userAccount->id, 'amount' => 60, ]); $this->actingAs($user, 'web'); session(['activeProfileType' => User::class]); session(['activeProfileId' => $user->id]); $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id])); $response->assertStatus(200); } /** * Test user cannot view transaction they are not involved in * * @test */ public function user_cannot_view_transaction_they_are_not_involved_in() { $user = User::factory()->create(); $sender = User::factory()->create(); $recipient = User::factory()->create(); $senderAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $sender->id, ]); $recipientAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $recipient->id, ]); $transaction = Transaction::factory()->create([ 'from_account_id' => $senderAccount->id, 'to_account_id' => $recipientAccount->id, 'amount' => 60, ]); // User is NOT involved in this transaction $this->actingAs($user, 'web'); session(['activeProfileType' => User::class]); session(['activeProfileId' => $user->id]); $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id])); $response->assertStatus(403); } /** * Test organization can view transaction they are involved in * * @test */ public function organization_can_view_transaction_they_are_involved_in() { $orgUser = User::factory()->create(); $organization = Organization::factory()->create(); $organization->users()->attach($orgUser->id); $recipient = User::factory()->create(); $orgAccount = Account::factory()->create([ 'accountable_type' => Organization::class, 'accountable_id' => $organization->id, ]); $recipientAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $recipient->id, ]); $transaction = Transaction::factory()->create([ 'from_account_id' => $orgAccount->id, 'to_account_id' => $recipientAccount->id, 'amount' => 120, ]); $this->actingAs($organization, 'organization'); session(['activeProfileType' => Organization::class]); session(['activeProfileId' => $organization->id]); $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id])); $response->assertStatus(200); } /** * Test organization cannot view transaction of another organization * * @test */ public function organization_cannot_view_another_organizations_transaction() { $user = User::factory()->create(); $org1 = Organization::factory()->create(); $org2 = Organization::factory()->create(); $org1->users()->attach($user->id); $recipient = User::factory()->create(); $org2Account = Account::factory()->create([ 'accountable_type' => Organization::class, 'accountable_id' => $org2->id, ]); $recipientAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $recipient->id, ]); $transaction = Transaction::factory()->create([ 'from_account_id' => $org2Account->id, 'to_account_id' => $recipientAccount->id, 'amount' => 120, ]); // Logged in as org1, trying to view org2's transaction $this->actingAs($org1, 'organization'); session(['activeProfileType' => Organization::class]); session(['activeProfileId' => $org1->id]); $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id])); $response->assertStatus(403); } /** * Test bank can view transaction they are involved in * * @test */ public function bank_can_view_transaction_they_are_involved_in() { $bankUser = User::factory()->create(); $bank = Bank::factory()->create(); $bank->managers()->attach($bankUser->id); $recipient = User::factory()->create(); $bankAccount = Account::factory()->create([ 'accountable_type' => Bank::class, 'accountable_id' => $bank->id, ]); $recipientAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $recipient->id, ]); $transaction = Transaction::factory()->create([ 'from_account_id' => $bankAccount->id, 'to_account_id' => $recipientAccount->id, 'amount' => 200, ]); $this->actingAs($bank, 'bank'); session(['activeProfileType' => Bank::class]); session(['activeProfileId' => $bank->id]); $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id])); $response->assertStatus(200); } /** * Test session manipulation to view unauthorized transaction is blocked * * @test */ public function session_manipulation_to_view_transaction_is_blocked() { $user1 = User::factory()->create(); $user2 = User::factory()->create(); $recipient = User::factory()->create(); $user2Account = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $user2->id, ]); $recipientAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $recipient->id, ]); $transaction = Transaction::factory()->create([ 'from_account_id' => $user2Account->id, 'to_account_id' => $recipientAccount->id, 'amount' => 60, ]); // Logged in as user1 $this->actingAs($user1, 'web'); // Malicious: manipulate session to impersonate user2 session(['activeProfileType' => User::class]); session(['activeProfileId' => $user2->id]); // Attacker sets this! $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id])); // Should be blocked by statement() query which checks session against database $response->assertStatus(403); } /** * Test cross-guard attack to view transaction is blocked * * @test */ public function cross_guard_attack_to_view_transaction_is_blocked() { $user = User::factory()->create(); $organization = Organization::factory()->create(); $organization->users()->attach($user->id); $recipient = User::factory()->create(); $orgAccount = Account::factory()->create([ 'accountable_type' => Organization::class, 'accountable_id' => $organization->id, ]); $recipientAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $recipient->id, ]); $transaction = Transaction::factory()->create([ 'from_account_id' => $orgAccount->id, 'to_account_id' => $recipientAccount->id, 'amount' => 120, ]); // Logged in as user (web guard) $this->actingAs($user, 'web'); // Malicious: manipulate session to view org's transaction session(['activeProfileType' => Organization::class]); session(['activeProfileId' => $organization->id]); $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id])); // Should be blocked because auth guard doesn't match session $response->assertStatus(403); } /** * Test unauthenticated user cannot view transactions * * @test */ public function unauthenticated_user_cannot_view_transactions() { $sender = User::factory()->create(); $recipient = User::factory()->create(); $senderAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $sender->id, ]); $recipientAccount = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $recipient->id, ]); $transaction = Transaction::factory()->create([ 'from_account_id' => $senderAccount->id, 'to_account_id' => $recipientAccount->id, 'amount' => 60, ]); // Not authenticated $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id])); $response->assertRedirect(route('login')); } /** * Test user can access transactions list page * * @test */ public function user_can_access_transactions_list_page() { $user = User::factory()->create(); $this->actingAs($user, 'web'); session(['activeProfileType' => User::class]); session(['activeProfileId' => $user->id]); $response = $this->get(route('transactions')); $response->assertStatus(200); } /** * Test organization can access transactions list page * * @test */ public function organization_can_access_transactions_list_page() { $user = User::factory()->create(); $organization = Organization::factory()->create(); $organization->users()->attach($user->id); $this->actingAs($organization, 'organization'); session(['activeProfileType' => Organization::class]); session(['activeProfileId' => $organization->id]); $response = $this->get(route('transactions')); $response->assertStatus(200); } /** * Test bank can access transactions list page * * @test */ public function bank_can_access_transactions_list_page() { $user = User::factory()->create(); $bank = Bank::factory()->create(); $bank->managers()->attach($user->id); $this->actingAs($bank, 'bank'); session(['activeProfileType' => Bank::class]); session(['activeProfileId' => $bank->id]); $response = $this->get(route('transactions')); $response->assertStatus(200); } /** * Test non-existent transaction returns 403 * * @test */ public function non_existent_transaction_returns_403() { $user = User::factory()->create(); $this->actingAs($user, 'web'); session(['activeProfileType' => User::class]); session(['activeProfileId' => $user->id]); $response = $this->get(route('transaction.show', ['transactionId' => 99999])); $response->assertStatus(403); } /** * Test TransactionsTable Livewire component loads for user * * @test */ public function transactions_table_livewire_component_loads_for_user() { $user = User::factory()->create(); Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $user->id, ]); $this->actingAs($user, 'web'); session(['activeProfileType' => User::class]); session(['activeProfileId' => $user->id]); $response = Livewire::test(\App\Http\Livewire\TransactionsTable::class); $response->assertStatus(200); } /** * Test TransactionsTable Livewire component loads for organization * * @test */ public function transactions_table_livewire_component_loads_for_organization() { $user = User::factory()->create(); $organization = Organization::factory()->create(); $organization->users()->attach($user->id); Account::factory()->create([ 'accountable_type' => Organization::class, 'accountable_id' => $organization->id, ]); $this->actingAs($organization, 'organization'); session(['activeProfileType' => Organization::class]); session(['activeProfileId' => $organization->id]); $response = Livewire::test(\App\Http\Livewire\TransactionsTable::class); $response->assertStatus(200); } /** * Test TransactionsTable filters transactions by active profile * * @test */ public function transactions_table_filters_by_active_profile() { $user1 = User::factory()->create(); $user2 = User::factory()->create(); $user1Account = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $user1->id, ]); $user2Account = Account::factory()->create([ 'accountable_type' => User::class, 'accountable_id' => $user2->id, ]); // Transaction involving user1 Transaction::factory()->create([ 'from_account_id' => $user1Account->id, 'to_account_id' => $user2Account->id, 'amount' => 60, 'description' => 'User1 transaction', ]); // Transaction NOT involving user1 Transaction::factory()->create([ 'from_account_id' => $user2Account->id, 'to_account_id' => $user2Account->id, // Self transaction for testing 'amount' => 30, 'description' => 'User2 only transaction', ]); $this->actingAs($user1, 'web'); session(['activeProfileType' => User::class]); session(['activeProfileId' => $user1->id]); $response = Livewire::test(\App\Http\Livewire\TransactionsTable::class); // Should see user1's transaction $response->assertSee('User1 transaction'); // Should NOT see user2's private transaction $response->assertDontSee('User2 only transaction'); } }