browser()->timeout(10_000); it('keeps stale verification warnings and the selected provider connection stable after refresh', function (): void { $workspace = Workspace::factory()->create(); $tenant = Tenant::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => '30303030-3030-3030-3030-303030303030', 'name' => 'Stale Verification Tenant', 'status' => Tenant::STATUS_ONBOARDING, ]); $user = User::factory()->create(['name' => 'Verification Owner']); createUserWithTenant( tenant: $tenant, user: $user, role: 'owner', workspaceRole: 'owner', ensureDefaultMicrosoftProviderConnection: false, ); $verifiedConnection = ProviderConnection::factory()->platform()->consentGranted()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $tenant->getKey(), 'provider' => 'microsoft', 'entra_tenant_id' => (string) $tenant->tenant_id, 'display_name' => 'Previously verified connection', 'is_default' => true, 'status' => 'connected', ]); $selectedConnection = ProviderConnection::factory()->platform()->consentGranted()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $tenant->getKey(), 'provider' => 'dummy', 'entra_tenant_id' => (string) $tenant->tenant_id, 'display_name' => 'Current selected connection', 'is_default' => false, 'status' => 'connected', ]); $run = OperationRun::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $tenant->getKey(), 'type' => 'provider.connection.check', 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, 'context' => [ 'provider_connection_id' => (int) $verifiedConnection->getKey(), 'target_scope' => [ 'entra_tenant_id' => (string) $tenant->tenant_id, ], ], ]); $draft = createOnboardingDraft([ 'workspace' => $workspace, 'tenant' => $tenant, 'started_by' => $user, 'updated_by' => $user, 'current_step' => 'verify', 'state' => [ 'entra_tenant_id' => (string) $tenant->tenant_id, 'tenant_name' => (string) $tenant->name, 'provider_connection_id' => (int) $selectedConnection->getKey(), 'verification_operation_run_id' => (int) $run->getKey(), ], ]); $this->actingAs($user)->withSession([ WorkspaceContext::SESSION_KEY => (int) $workspace->getKey(), ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey()); $visibleSelectValue = <<<'JS' (() => { const select = [...document.querySelectorAll('select')].find((element) => { const style = window.getComputedStyle(element); return style.display !== 'none' && style.visibility !== 'hidden'; }); return select?.value ?? null; })() JS; $page = visit(route('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()])); $page ->assertNoJavaScriptErrors() ->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()]) ->assertSee('Verify access') ->assertSee('Status: Needs attention') ->assertSee('The selected provider connection has changed since this verification run. Start verification again to validate the current connection.') ->assertSee('Start verification') ->refresh() ->waitForText('The selected provider connection has changed since this verification run. Start verification again to validate the current connection.') ->assertNoJavaScriptErrors() ->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()]) ->assertSee('Status: Needs attention') ->assertSee('Start verification') ->click('Provider connection') ->assertScript($visibleSelectValue, (string) $selectedConnection->getKey()); }); it('preserves bootstrap revisit state and blocked activation guards after refresh', function (): void { $workspace = Workspace::factory()->create(); $tenant = Tenant::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => '40404040-4040-4040-4040-404040404040', 'name' => 'Blocked Review Tenant', 'status' => Tenant::STATUS_ONBOARDING, ]); $user = User::factory()->create(['name' => 'Review Owner']); createUserWithTenant( tenant: $tenant, user: $user, role: 'owner', workspaceRole: 'owner', ensureDefaultMicrosoftProviderConnection: false, ); $connection = ProviderConnection::factory()->platform()->consentGranted()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $tenant->getKey(), 'provider' => 'microsoft', 'entra_tenant_id' => (string) $tenant->tenant_id, 'display_name' => 'Blocked review connection', 'is_default' => true, 'status' => 'connected', ]); $verificationRun = OperationRun::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $tenant->getKey(), 'type' => 'provider.connection.check', 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Failed->value, 'context' => [ 'provider_connection_id' => (int) $connection->getKey(), 'target_scope' => [ 'entra_tenant_id' => (string) $tenant->tenant_id, 'entra_tenant_name' => (string) $tenant->name, ], 'verification_report' => VerificationReportWriter::build('provider.connection.check', [ [ 'key' => 'permissions.admin_consent', 'title' => 'Required application permissions', 'status' => 'fail', 'severity' => 'critical', 'blocking' => true, 'reason_code' => 'permission_denied', 'message' => 'Missing required Graph permissions.', 'evidence' => [], 'next_steps' => [], ], ]), ], ]); $bootstrapRun = createInventorySyncOperationRun($tenant, [ 'workspace_id' => (int) $workspace->getKey(), 'status' => 'success', ]); $draft = createOnboardingDraft([ 'workspace' => $workspace, 'tenant' => $tenant, 'started_by' => $user, 'updated_by' => $user, 'current_step' => 'complete', 'state' => [ 'entra_tenant_id' => (string) $tenant->tenant_id, 'tenant_name' => (string) $tenant->name, 'provider_connection_id' => (int) $connection->getKey(), 'verification_operation_run_id' => (int) $verificationRun->getKey(), 'bootstrap_operation_types' => ['inventory_sync'], 'bootstrap_operation_runs' => [ 'inventory_sync' => (int) $bootstrapRun->getKey(), ], ], ]); $this->actingAs($user)->withSession([ WorkspaceContext::SESSION_KEY => (int) $workspace->getKey(), ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey()); $page = visit(route('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()])); $page ->assertNoJavaScriptErrors() ->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()]) ->assertSee('Verify access') ->assertSee('Status: Blocked') ->assertSee('View required permissions') ->refresh() ->waitForText('View required permissions') ->assertNoJavaScriptErrors() ->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()]) ->assertSee('Status: Blocked') ->assertSee('View required permissions'); }); it('opens the full-page permissions deep dive in a new tab without replacing onboarding', function (): void { [$user, $tenant] = createUserWithTenant( role: 'owner', ensureDefaultMicrosoftProviderConnection: false, ); $workspace = $tenant->workspace()->firstOrFail(); $configured = array_merge( config('intune_permissions.permissions', []), config('entra_permissions.permissions', []), ); $permissionKeys = array_values(array_filter(array_map(static function (mixed $permission): ?string { if (! is_array($permission)) { return null; } $key = $permission['key'] ?? null; return is_string($key) && trim($key) !== '' ? trim($key) : null; }, $configured))); if ($permissionKeys === []) { test()->markTestSkipped('No configured required permissions found.'); } $missingKey = $permissionKeys[0]; foreach (array_slice($permissionKeys, 1) as $key) { TenantPermission::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $workspace->getKey(), 'permission_key' => $key, 'status' => 'granted', 'details' => ['source' => 'db'], 'last_checked_at' => now(), ]); } $connection = ProviderConnection::factory()->platform()->consentGranted()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $tenant->getKey(), 'provider' => 'microsoft', 'entra_tenant_id' => (string) $tenant->tenant_id, 'display_name' => 'Browser assist connection', 'is_default' => true, 'status' => 'connected', ]); $run = OperationRun::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $tenant->getKey(), 'type' => 'provider.connection.check', 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Blocked->value, 'context' => [ 'provider_connection_id' => (int) $connection->getKey(), 'target_scope' => [ 'entra_tenant_id' => (string) $tenant->tenant_id, 'entra_tenant_name' => (string) $tenant->name, ], 'verification_report' => VerificationReportWriter::build('provider.connection.check', [ [ 'key' => 'permissions.admin_consent', 'title' => 'Required application permissions', 'status' => 'fail', 'severity' => 'critical', 'blocking' => true, 'reason_code' => 'provider_permission_missing', 'message' => "Missing required application permission: {$missingKey}", 'evidence' => [], 'next_steps' => [], ], ]), ], ]); $draft = createOnboardingDraft([ 'workspace' => $workspace, 'tenant' => $tenant, 'started_by' => $user, 'updated_by' => $user, 'current_step' => 'verify', 'state' => [ 'entra_tenant_id' => (string) $tenant->tenant_id, 'tenant_name' => (string) $tenant->name, 'provider_connection_id' => (int) $connection->getKey(), 'verification_operation_run_id' => (int) $run->getKey(), ], ]); $this->actingAs($user)->withSession([ WorkspaceContext::SESSION_KEY => (int) $workspace->getKey(), ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey()); $page = visit(route('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()])); $page ->assertNoJavaScriptErrors() ->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()]) ->waitForText('View required permissions') ->click('View required permissions') ->waitForText('Open full page'); $page->script(<<<'JS' Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText: async () => Promise.resolve(), }, }); document.querySelector('[data-testid="verification-assist-copy-application"]')?.click(); JS); $page ->waitForText('Copied') ->assertAttribute('[data-testid="verification-assist-full-page"]', 'target', '_blank') ->assertAttribute('[data-testid="verification-assist-full-page"]', 'rel', 'noopener noreferrer') ->click('Open full page') ->wait(1) ->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()]) ->assertSee('Open full page') ->click('Close') ->click('Provider connection') ->assertSee('Select an existing connection or create a new one.'); }); it('opens the permissions assist from report remediation steps without leaving onboarding', function (): void { [$user, $tenant] = createUserWithTenant( role: 'owner', ensureDefaultMicrosoftProviderConnection: false, ); $workspace = $tenant->workspace()->firstOrFail(); $configured = array_merge( config('intune_permissions.permissions', []), config('entra_permissions.permissions', []), ); $permissionKeys = array_values(array_filter(array_map(static function (mixed $permission): ?string { if (! is_array($permission)) { return null; } $key = $permission['key'] ?? null; return is_string($key) && trim($key) !== '' ? trim($key) : null; }, $configured))); if ($permissionKeys === []) { test()->markTestSkipped('No configured required permissions found.'); } foreach (array_slice($permissionKeys, 1) as $key) { TenantPermission::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $workspace->getKey(), 'permission_key' => $key, 'status' => 'granted', 'details' => ['source' => 'db'], 'last_checked_at' => now(), ]); } $connection = ProviderConnection::factory()->platform()->consentGranted()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $tenant->getKey(), 'provider' => 'microsoft', 'entra_tenant_id' => (string) $tenant->tenant_id, 'display_name' => 'Browser next-step connection', 'is_default' => true, 'status' => 'connected', ]); $run = OperationRun::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $tenant->getKey(), 'type' => 'provider.connection.check', 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Blocked->value, 'context' => [ 'provider_connection_id' => (int) $connection->getKey(), 'target_scope' => [ 'entra_tenant_id' => (string) $tenant->tenant_id, 'entra_tenant_name' => (string) $tenant->name, ], 'verification_report' => VerificationReportWriter::build('provider.connection.check', [ [ 'key' => 'provider.connection.preflight', 'title' => 'Provider connection preflight', 'status' => 'fail', 'severity' => 'critical', 'blocking' => true, 'reason_code' => 'provider_permission_missing', 'message' => 'Provider connection requires admin consent before use.', 'evidence' => [], 'next_steps' => [ [ 'label' => 'Grant admin consent', 'url' => 'https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/grant-admin-consent', ], [ 'label' => 'Review platform connection', 'url' => route('filament.admin.resources.provider-connections.edit', ['tenant' => $tenant->external_id, 'record' => (int) $connection->getKey()]), ], ], ], ]), ], ]); $draft = createOnboardingDraft([ 'workspace' => $workspace, 'tenant' => $tenant, 'started_by' => $user, 'updated_by' => $user, 'current_step' => 'verify', 'state' => [ 'entra_tenant_id' => (string) $tenant->tenant_id, 'tenant_name' => (string) $tenant->name, 'provider_connection_id' => (int) $connection->getKey(), 'verification_operation_run_id' => (int) $run->getKey(), ], ]); $this->actingAs($user)->withSession([ WorkspaceContext::SESSION_KEY => (int) $workspace->getKey(), ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey()); visit(route('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()])) ->assertNoJavaScriptErrors() ->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()]) ->waitForText('Grant admin consent') ->click('Grant admin consent') ->waitForText('Required permissions assist') ->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()]) ->assertSee('Open full page') ->assertSee('Review platform connection'); });