create(); $ownedOrganization = Organization::factory()->create(['password' => Hash::make('password')]); $unownedOrganization = Organization::factory()->create(['password' => Hash::make('password')]); // Link user to owned organization only $user->organizations()->attach($ownedOrganization->id); $this->actingAs($user, 'web'); // Set intent for owned organization - should work session([ 'intended_profile_switch_type' => 'Organization', 'intended_profile_switch_id' => $ownedOrganization->id, ]); $response = $this->get(route('organization.login')); $response->assertStatus(200); $response->assertViewIs('profile-organization.login'); // Set intent for unowned organization - should fail session([ 'intended_profile_switch_type' => 'Organization', 'intended_profile_switch_id' => $unownedOrganization->id, ]); $response = $this->get(route('organization.login')); // Should show nothing or error because getTargetProfileByTypeAndId returns null $this->assertNull(session('intended_profile_switch_id') ? Organization::find(session('intended_profile_switch_id')) : null); } /** * Test that user cannot switch to unowned bank profile */ public function test_cannot_switch_to_unowned_bank() { $user = User::factory()->create(); $ownedBank = Bank::factory()->create(['password' => Hash::make('password')]); $unownedBank = Bank::factory()->create(['password' => Hash::make('password')]); // Link user to owned bank only $user->banksManaged()->attach($ownedBank->id); $this->actingAs($user, 'web'); // Try to access unowned bank session([ 'intended_profile_switch_type' => 'Bank', 'intended_profile_switch_id' => $unownedBank->id, ]); $response = $this->get(route('bank.login')); // The view will receive $profile = null from getTargetProfileByTypeAndId // This might cause an error or show an empty form $response->assertStatus(200); // The route exists but profile will be null } /** * Test that user cannot switch to unowned admin profile */ public function test_cannot_switch_to_unowned_admin() { $user = User::factory()->create(); $ownedAdmin = Admin::factory()->create(['password' => Hash::make('password')]); $unownedAdmin = Admin::factory()->create(['password' => Hash::make('password')]); // Link user to owned admin only $user->admins()->attach($ownedAdmin->id); $this->actingAs($user, 'web'); // Try to access unowned admin session([ 'intended_profile_switch_type' => 'Admin', 'intended_profile_switch_id' => $unownedAdmin->id, ]); $response = $this->get(route('admin.login')); $response->assertStatus(200); // Route exists but profile will be null } /** * Test profile switch validates relationship pivot tables */ public function test_profile_switch_validates_relationship_pivot_tables() { $user = User::factory()->create(); $organization = Organization::factory()->create(['password' => Hash::make('password')]); $bank = Bank::factory()->create(['password' => Hash::make('password')]); $admin = Admin::factory()->create(['password' => Hash::make('password')]); $this->actingAs($user, 'web'); // Try organization without pivot entry session([ 'intended_profile_switch_type' => 'Organization', 'intended_profile_switch_id' => $organization->id, ]); $response = $this->get(route('organization.login')); // getTargetProfileByTypeAndId will return null (no relationship) // Try bank without pivot entry session([ 'intended_profile_switch_type' => 'Bank', 'intended_profile_switch_id' => $bank->id, ]); $response = $this->get(route('bank.login')); // getTargetProfileByTypeAndId will return null // Try admin without pivot entry session([ 'intended_profile_switch_type' => 'Admin', 'intended_profile_switch_id' => $admin->id, ]); $response = $this->get(route('admin.login')); // getTargetProfileByTypeAndId will return null // Now add relationships and verify they work $user->organizations()->attach($organization->id); $user->banksManaged()->attach($bank->id); $user->admins()->attach($admin->id); session([ 'intended_profile_switch_type' => 'Organization', 'intended_profile_switch_id' => $organization->id, ]); $response = $this->get(route('organization.login')); $response->assertStatus(200); $response->assertViewHas('profile', $organization); } // ========================================== // PASSWORD REQUIREMENT TESTS // ========================================== /** * Test that organization switch does not require password (via direct login) */ public function test_organization_switch_does_not_require_password_via_direct_login() { $user = User::factory()->create(); $organization = Organization::factory()->create(['password' => Hash::make('password')]); $user->organizations()->attach($organization->id); $this->actingAs($user, 'web'); // Direct login for organization should switch immediately $response = $this->get(route('organization.direct-login', ['organizationId' => $organization->id])); // Should redirect to main page, not to password form $response->assertRedirect(route('main')); // Should be authenticated on organization guard $this->assertTrue(Auth::guard('organization')->check()); $this->assertEquals($organization->id, Auth::guard('organization')->id()); } /** * Test that bank switch requires password */ public function test_bank_switch_requires_password() { $user = User::factory()->create(); $bank = Bank::factory()->create(['password' => Hash::make('bank-password')]); $user->banksManaged()->attach($bank->id); $this->actingAs($user, 'web'); // Direct login for bank should redirect to password form $response = $this->get(route('bank.direct-login', ['bankId' => $bank->id])); $response->assertRedirect(route('bank.login')); // Should NOT be authenticated on bank guard yet $this->assertFalse(Auth::guard('bank')->check()); // Verify session intent was set $this->assertEquals('Bank', session('intended_profile_switch_type')); $this->assertEquals($bank->id, session('intended_profile_switch_id')); } /** * Test that admin switch requires password */ public function test_admin_switch_requires_password() { $user = User::factory()->create(); $admin = Admin::factory()->create(['password' => Hash::make('admin-password')]); $user->admins()->attach($admin->id); $this->actingAs($user, 'web'); // Direct login for admin should redirect to password form $response = $this->get(route('admin.direct-login', ['adminId' => $admin->id])); $response->assertRedirect(route('admin.login')); // Should NOT be authenticated on admin guard yet $this->assertFalse(Auth::guard('admin')->check()); // Verify session intent was set $this->assertEquals('Admin', session('intended_profile_switch_type')); $this->assertEquals($admin->id, session('intended_profile_switch_id')); } /** * Test that invalid password prevents bank profile switch */ public function test_invalid_password_prevents_bank_profile_switch() { $user = User::factory()->create(); $bank = Bank::factory()->create(['password' => Hash::make('correct-password')]); $user->banksManaged()->attach($bank->id); $this->actingAs($user, 'web'); $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class); session([ 'intended_profile_switch_type' => 'Bank', 'intended_profile_switch_id' => $bank->id, ]); $response = $this->post(route('bank.login.post'), [ 'password' => 'wrong-password', ]); $response->assertSessionHasErrors(['password']); $this->assertFalse(Auth::guard('bank')->check()); } /** * Test that valid password allows bank profile switch */ public function test_valid_password_allows_bank_profile_switch() { $user = User::factory()->create(); $bank = Bank::factory()->create(['password' => Hash::make('correct-password')]); $user->banksManaged()->attach($bank->id); $this->actingAs($user, 'web'); $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class); session([ 'intended_profile_switch_type' => 'Bank', 'intended_profile_switch_id' => $bank->id, ]); $response = $this->post(route('bank.login.post'), [ 'password' => 'correct-password', ]); $response->assertRedirect(route('main')); $this->assertTrue(Auth::guard('bank')->check()); $this->assertEquals($bank->id, Auth::guard('bank')->id()); } // ========================================== // SESSION STATE MANAGEMENT TESTS // ========================================== /** * Test that profile switch clears session variables after successful authentication */ public function test_profile_switch_clears_session_variables() { $user = User::factory()->create(); $bank = Bank::factory()->create(['password' => Hash::make('password')]); $user->banksManaged()->attach($bank->id); $this->actingAs($user, 'web'); $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class); session([ 'intended_profile_switch_type' => 'Bank', 'intended_profile_switch_id' => $bank->id, ]); $this->assertNotNull(session('intended_profile_switch_type')); $this->assertNotNull(session('intended_profile_switch_id')); $this->post(route('bank.login.post'), [ 'password' => 'password', ]); // Session variables should be cleared after successful login $this->assertNull(session('intended_profile_switch_type')); $this->assertNull(session('intended_profile_switch_id')); } /** * Test that active profile information is stored in session */ public function test_active_profile_stored_in_session() { $user = User::factory()->create(); $organization = Organization::factory()->create(['password' => Hash::make('password')]); $user->organizations()->attach($organization->id); $this->actingAs($user, 'web'); $response = $this->get(route('organization.direct-login', ['organizationId' => $organization->id])); // Check session contains profile information $this->assertEquals(get_class($organization), session('activeProfileType')); $this->assertEquals($organization->id, session('activeProfileId')); $this->assertEquals($organization->name, session('activeProfileName')); $this->assertNotNull(session('last_activity')); } // ========================================== // EDGE CASES // ========================================== /** * Test that switching to nonexistent profile fails gracefully */ public function test_cannot_switch_to_nonexistent_profile() { $user = User::factory()->create(); $this->actingAs($user, 'web'); // Try to access nonexistent organization $response = $this->get(route('organization.direct-login', ['organizationId' => 99999])); $response->assertStatus(404); } /** * Test that switching to soft-deleted profile fails */ public function test_cannot_switch_to_soft_deleted_profile() { $user = User::factory()->create(); $organization = Organization::factory()->create(['password' => Hash::make('password')]); $user->organizations()->attach($organization->id); // Soft delete the organization $organization->delete(); $this->actingAs($user, 'web'); // Try to access deleted organization $response = $this->get(route('organization.direct-login', ['organizationId' => $organization->id])); $response->assertStatus(404); } /** * Test that profile switch requires user to be authenticated */ public function test_profile_switch_requires_authentication() { $organization = Organization::factory()->create(['password' => Hash::make('password')]); // Try to access organization without being authenticated $response = $this->get(route('organization.direct-login', ['organizationId' => $organization->id])); // Should redirect to login $response->assertRedirect(); $this->assertStringContainsString('login', $response->headers->get('Location')); } }