where('name', 'url')->first(); if (! $protocol) { $protocol = new App\Models\ConnectionProtocol(); $protocol->name = 'url'; $protocol->save(); } $provider = App\Models\ConnectionProvider::query()->where('slug', 'azure-fd')->first(); if (! $provider) { $provider = new App\Models\ConnectionProvider(); $provider->name = 'Azure FD'; $provider->slug = 'azure-fd'; $provider->save(); } $status = App\Models\ConnectionStatus::query()->where('name', 'connected')->first(); if (! $status) { $status = new App\Models\ConnectionStatus(); $status->name = 'connected'; $status->save(); } $app = App\Models\ConnectedApp::query()->create([ 'name' => 'Azure FD Portal', 'slug' => 'azure-fd-portal', 'connection_protocol_id' => $protocol->id, 'connection_provider_id' => $provider->id, 'connection_status_id' => $status->id, 'settings' => [ 'url' => [ 'redirect_url' => 'https://azure.microsoft.com', ], ], ]); $role = App\Models\Role::findOrCreate('Azure User'); $role->update(['priority' => 10]); $role->apps()->attach($app->id, ['duration' => now()->addYear()->toDateTimeString()]); $user->assignRole($role); } it('redirects unauthenticated users to login when connecting', function (): void { $this->get(route('entra.connect')) ->assertRedirect(route('login')); }); it('returns 403 forbidden if user does not have permission for azure-fd app', function (): void { $user = User::factory()->create(); $this->actingAs($user) ->get(route('entra.connect')) ->assertStatus(403); }); it('redirects to Microsoft login on connect when authorized', function (): void { $user = User::factory()->create(); assignAzureFdAccess($user); $response = $this->actingAs($user) ->get(route('entra.connect')); $response->assertRedirectContains('login.microsoftonline.com'); $response->assertRedirectContains('oauth2/v2.0/authorize'); }); it('stores token and redirects to dashboard on successful callback when authorized', function (): void { $user = User::factory()->create(); assignAzureFdAccess($user); Http::fake([ 'login.microsoftonline.com/*' => Http::response([ 'access_token' => 'fake-access-token', 'refresh_token' => 'fake-refresh-token', 'token_type' => 'Bearer', 'expires_in' => 3600, ]), ]); $state = 'test-state-value'; $response = $this->actingAs($user) ->withSession(['oauth_state' => $state]) ->get(route('entra.callback', [ 'code' => 'fake-auth-code', 'state' => $state, ])); $response->assertRedirect(route('dashboard.user')); $response->assertSessionHas('entra_success', 'Microsoft connected successfully!'); $this->assertDatabaseHas('oauth_tokens', [ 'user_id' => $user->id, 'provider' => 'microsoft', 'token_type' => 'Bearer', ]); }); it('redirects to dashboard with error on invalid state when authorized', function (): void { $user = User::factory()->create(); assignAzureFdAccess($user); $response = $this->actingAs($user) ->withSession(['oauth_state' => 'correct-state']) ->get(route('entra.callback', [ 'code' => 'fake-code', 'state' => 'wrong-state', ])); $response->assertRedirect(route('dashboard.user')); $response->assertSessionHas('entra_error'); }); it('redirects to dashboard with error when Microsoft returns error and authorized', function (): void { $user = User::factory()->create(); assignAzureFdAccess($user); $state = 'test-state'; $response = $this->actingAs($user) ->withSession(['oauth_state' => $state]) ->get(route('entra.callback', [ 'error' => 'access_denied', 'error_description' => 'User cancelled the request', 'state' => $state, ])); $response->assertRedirect(route('dashboard.user')); $response->assertSessionHas('entra_error', 'User cancelled the request'); }); it('disconnects and removes the token when authorized', function (): void { $user = User::factory()->create(); assignAzureFdAccess($user); OauthToken::create([ 'user_id' => $user->id, 'provider' => 'microsoft', 'access_token' => 'fake-token', 'refresh_token' => 'fake-refresh', 'token_type' => 'Bearer', 'expires_at' => now()->addHour(), ]); $response = $this->actingAs($user) ->delete(route('entra.disconnect')); $response->assertRedirect(route('dashboard.user')); $response->assertSessionHas('entra_success', 'Microsoft disconnected.'); $this->assertDatabaseMissing('oauth_tokens', [ 'user_id' => $user->id, 'provider' => 'microsoft', ]); }); it('updates existing token on re-connect when authorized', function (): void { $user = User::factory()->create(); assignAzureFdAccess($user); OauthToken::create([ 'user_id' => $user->id, 'provider' => 'microsoft', 'access_token' => 'old-token', 'refresh_token' => 'old-refresh', 'token_type' => 'Bearer', 'expires_at' => now()->subHour(), ]); Http::fake([ 'login.microsoftonline.com/*' => Http::response([ 'access_token' => 'new-access-token', 'refresh_token' => 'new-refresh-token', 'token_type' => 'Bearer', 'expires_in' => 3600, ]), ]); $state = 'reconnect-state'; $this->actingAs($user) ->withSession(['oauth_state' => $state]) ->get(route('entra.callback', [ 'code' => 'new-auth-code', 'state' => $state, ])); expect(OauthToken::where('user_id', $user->id)->where('provider', 'microsoft')->count())->toBe(1); $token = $user->oauthToken('microsoft'); expect($token->access_token)->toBe('new-access-token'); expect($token->refresh_token)->toBe('new-refresh-token'); });