create(); $response = $this->get(route('user.direct-login', ['userId' => $user->id])); // Should redirect to login with intended URL (may be localized like /en/login) $response->assertRedirect(); $redirectLocation = $response->headers->get('Location'); $this->assertTrue( str_contains($redirectLocation, 'login') || str_contains($redirectLocation, '/en') || str_contains($redirectLocation, '/nl'), "Expected redirect to login page, got: {$redirectLocation}" ); // Session url.intended may be set by controller } /** * Test user direct login validates ownership */ public function test_user_direct_login_validates_ownership() { $user1 = User::factory()->create(); $user2 = User::factory()->create(); $this->actingAs($user1, 'web'); // Try to access user2's profile $response = $this->get(route('user.direct-login', ['userId' => $user2->id])); $response->assertStatus(403); $response->assertSee('You do not have access to this profile'); } /** * Test user direct login redirects to intended URL */ public function test_user_direct_login_redirects_to_intended_url() { $user = User::factory()->create(); $this->actingAs($user, 'web'); $intendedUrl = route('main'); $response = $this->get(route('user.direct-login', [ 'userId' => $user->id, 'intended' => $intendedUrl, ])); $response->assertRedirect($intendedUrl); } /** * Test user direct login returns 404 for nonexistent user */ public function test_user_direct_login_returns_404_for_nonexistent_user() { $this->actingAs(User::factory()->create(), 'web'); $response = $this->get(route('user.direct-login', ['userId' => 99999])); $response->assertStatus(404); } // ========================================== // ORGANIZATION DIRECT LOGIN TESTS // ========================================== /** * Test organization direct login requires user authentication first */ public function test_organization_direct_login_requires_user_authentication() { $organization = Organization::factory()->create(); $response = $this->get(route('organization.direct-login', ['organizationId' => $organization->id])); // Should redirect to user login $response->assertRedirect(); $this->assertStringContainsString('login', $response->headers->get('Location')); // Should store intended URL in session $this->assertNotNull(session('url.intended')); $this->assertStringContainsString("/organization/{$organization->id}/login", session('url.intended')); } /** * Test organization direct login validates ownership */ public function test_organization_direct_login_validates_ownership() { $user = User::factory()->create(); $ownedOrg = Organization::factory()->create(); $unownedOrg = Organization::factory()->create(); $user->organizations()->attach($ownedOrg->id); $this->actingAs($user, 'web'); // Try to access unowned organization $response = $this->get(route('organization.direct-login', ['organizationId' => $unownedOrg->id])); $response->assertStatus(403); $response->assertSee('You do not have access to this organization'); } /** * Test organization direct login switches guard directly (passwordless) */ public function test_organization_direct_login_switches_guard_passwordless() { $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])); // Should switch to organization guard immediately without password $this->assertTrue(Auth::guard('organization')->check()); $this->assertEquals($organization->id, Auth::guard('organization')->id()); $response->assertRedirect(route('main')); } /** * Test organization direct login redirects to intended URL */ public function test_organization_direct_login_redirects_to_intended_url() { $user = User::factory()->create(); $organization = Organization::factory()->create(); $user->organizations()->attach($organization->id); $this->actingAs($user, 'web'); $intendedUrl = '/some/deep/link'; $response = $this->get(route('organization.direct-login', [ 'organizationId' => $organization->id, 'intended' => $intendedUrl, ])); $response->assertRedirect($intendedUrl); } /** * Test organization direct login returns 404 for nonexistent organization */ public function test_organization_direct_login_returns_404_for_nonexistent_profile() { $this->actingAs(User::factory()->create(), 'web'); $response = $this->get(route('organization.direct-login', ['organizationId' => 99999])); $response->assertStatus(404); } // ========================================== // BANK DIRECT LOGIN TESTS // ========================================== /** * Test bank direct login requires user authentication first */ public function test_bank_direct_login_requires_user_authentication() { $bank = Bank::factory()->create(); $response = $this->get(route('bank.direct-login', ['bankId' => $bank->id])); // Should redirect to user login $response->assertRedirect(); $this->assertStringContainsString('login', $response->headers->get('Location')); } /** * Test bank direct login validates bank manager relationship */ public function test_bank_direct_login_validates_bank_manager_relationship() { $user = User::factory()->create(); $managedBank = Bank::factory()->create(); $unmanagedBank = Bank::factory()->create(); $user->banksManaged()->attach($managedBank->id); $this->actingAs($user, 'web'); // Try to access unmanaged bank $response = $this->get(route('bank.direct-login', ['bankId' => $unmanagedBank->id])); $response->assertStatus(403); $response->assertSee('You do not have access to this bank'); } /** * Test bank direct login requires password */ public function test_bank_direct_login_requires_password() { $user = User::factory()->create(); $bank = Bank::factory()->create(['password' => Hash::make('bank-password')]); $user->banksManaged()->attach($bank->id); $this->actingAs($user, 'web'); $response = $this->get(route('bank.direct-login', ['bankId' => $bank->id])); // Should redirect to bank login form (not switch immediately) $response->assertRedirect(route('bank.login')); // Should NOT be authenticated on bank guard yet $this->assertFalse(Auth::guard('bank')->check()); // Should set session intent $this->assertEquals('Bank', session('intended_profile_switch_type')); $this->assertEquals($bank->id, session('intended_profile_switch_id')); } /** * Test bank direct login with intended URL stores it in session */ public function test_bank_direct_login_stores_intended_url_in_session() { $user = User::factory()->create(); $bank = Bank::factory()->create(); $user->banksManaged()->attach($bank->id); $this->actingAs($user, 'web'); $intendedUrl = '/deep/link/to/transaction'; $this->get(route('bank.direct-login', [ 'bankId' => $bank->id, 'intended' => $intendedUrl, ])); $this->assertEquals($intendedUrl, session('bank_login_intended_url')); } /** * Test bank login fails with wrong password */ public function test_bank_direct_login_fails_with_wrong_password() { $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()); } // ========================================== // ADMIN DIRECT LOGIN TESTS // ========================================== /** * Test admin direct login requires user authentication first */ public function test_admin_direct_login_requires_user_authentication() { $admin = Admin::factory()->create(); $response = $this->get(route('admin.direct-login', ['adminId' => $admin->id])); // Should redirect to user login $response->assertRedirect(); $this->assertStringContainsString('login', $response->headers->get('Location')); } /** * Test admin direct login validates admin user relationship */ public function test_admin_direct_login_validates_admin_user_relationship() { $user = User::factory()->create(); $ownedAdmin = Admin::factory()->create(); $unownedAdmin = Admin::factory()->create(); $user->admins()->attach($ownedAdmin->id); $this->actingAs($user, 'web'); // Try to access unowned admin $response = $this->get(route('admin.direct-login', ['adminId' => $unownedAdmin->id])); $response->assertStatus(403); $response->assertSee('You do not have access to this admin profile'); } /** * Test admin direct login requires password */ public function test_admin_direct_login_requires_password() { $user = User::factory()->create(); $admin = Admin::factory()->create(['password' => Hash::make('admin-password')]); $user->admins()->attach($admin->id); $this->actingAs($user, 'web'); $response = $this->get(route('admin.direct-login', ['adminId' => $admin->id])); // Should redirect to admin login form $response->assertRedirect(route('admin.login')); // Should NOT be authenticated on admin guard yet $this->assertFalse(Auth::guard('admin')->check()); // Should set session intent $this->assertEquals('Admin', session('intended_profile_switch_type')); $this->assertEquals($admin->id, session('intended_profile_switch_id')); } /** * Test admin direct login fails for non-admin users */ public function test_admin_direct_login_fails_for_non_admin_users() { $regularUser = User::factory()->create(); $admin = Admin::factory()->create(); // Regular user not linked to admin $this->actingAs($regularUser, 'web'); $response = $this->get(route('admin.direct-login', ['adminId' => $admin->id])); $response->assertStatus(403); } // ========================================== // SESSION VARIABLE CLEANUP TESTS // ========================================== /** * Test that session variables are cleared after successful authentication */ public function test_direct_login_session_variables_cleared_after_completion() { $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); // Set up direct login $this->get(route('bank.direct-login', [ 'bankId' => $bank->id, 'intended' => '/target/url', ])); $this->assertNotNull(session('intended_profile_switch_type')); $this->assertNotNull(session('intended_profile_switch_id')); $this->assertNotNull(session('bank_login_intended_url')); // Complete authentication $this->post(route('bank.login.post'), [ 'password' => 'password', ]); // Session variables should be cleared $this->assertNull(session('intended_profile_switch_type')); $this->assertNull(session('intended_profile_switch_id')); $this->assertNull(session('bank_login_intended_url')); } // ========================================== // INTENDED URL VALIDATION TESTS // ========================================== /** * Test that intended URL is properly encoded and decoded */ public function test_intended_url_properly_encoded_and_decoded() { $user = User::factory()->create(); $organization = Organization::factory()->create(); $user->organizations()->attach($organization->id); $this->actingAs($user, 'web'); $intendedUrl = '/path/with spaces/and?query=params'; $encodedUrl = urlencode($intendedUrl); $response = $this->get(route('organization.direct-login', [ 'organizationId' => $organization->id, 'intended' => $intendedUrl, ])); // URL should be properly handled $response->assertRedirect($intendedUrl); } /** * Test that direct login handles missing intended URL gracefully */ public function test_direct_login_handles_missing_intended_url_gracefully() { $user = User::factory()->create(); $organization = Organization::factory()->create(); $user->organizations()->attach($organization->id); $this->actingAs($user, 'web'); $response = $this->get(route('organization.direct-login', [ 'organizationId' => $organization->id, // No 'intended' parameter ])); // Should redirect to default (main page) $response->assertRedirect(route('main')); } // ========================================== // MULTI-LAYER AUTHENTICATION FLOW TESTS // ========================================== /** * Test complete flow: unauthenticated -> user login -> profile login */ public function test_complete_layered_authentication_flow() { $user = User::factory()->create([ 'email' => 'user@test.com', 'password' => Hash::make('user-password'), ]); $bank = Bank::factory()->create(['password' => Hash::make('bank-password')]); $user->banksManaged()->attach($bank->id); $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class); // Step 1: Try to access bank direct login while not authenticated $response = $this->get(route('bank.direct-login', [ 'bankId' => $bank->id, 'intended' => '/final/destination', ])); // Should redirect to user login $response->assertRedirect(); $this->assertStringContainsString('login', $response->headers->get('Location')); // Step 2: Authenticate as user $this->post(route('login'), [ 'name' => $user->email, 'password' => 'user-password', ]); $this->assertTrue(Auth::guard('web')->check()); // Step 3: Now access bank direct login (should redirect to bank password form) $response = $this->get(route('bank.direct-login', [ 'bankId' => $bank->id, 'intended' => '/final/destination', ])); $response->assertRedirect(route('bank.login')); // Step 4: Enter bank password $response = $this->post(route('bank.login.post'), [ 'password' => 'bank-password', ]); // Should be authenticated on bank guard and redirected to final destination $this->assertTrue(Auth::guard('bank')->check()); $response->assertRedirect('/final/destination'); } }