create(); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); session()->forget(WorkspaceContext::SESSION_KEY); $run = OperationRun::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => null, 'type' => 'provider.connection.check', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, ]); $this->actingAs($user) ->get("/admin/operations/{$run->getKey()}") ->assertSuccessful(); expect(session()->get(WorkspaceContext::SESSION_KEY))->toBeNull(); }); it('returns 404 for non-members when viewing an operation run without a selected workspace', function (): void { $workspace = Workspace::factory()->create(); $user = User::factory()->create(); session()->forget(WorkspaceContext::SESSION_KEY); $run = OperationRun::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => null, 'type' => 'provider.connection.check', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, ]); $this->actingAs($user) ->get("/admin/operations/{$run->getKey()}") ->assertNotFound(); }); it('returns 403 for members missing the required capability for the operation type', function (): void { $workspace = Workspace::factory()->create(); $tenant = Tenant::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), ]); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); $tenant->users()->attach((int) $user->getKey(), [ 'role' => TenantRole::Readonly->value, 'source' => 'manual', 'source_ref' => null, 'created_by_user_id' => null, ]); session()->forget(WorkspaceContext::SESSION_KEY); $run = OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $workspace->getKey(), 'type' => 'inventory_sync', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, ]); $this->actingAs($user) ->get("/admin/operations/{$run->getKey()}") ->assertForbidden(); }); it('keeps a canonical run viewer accessible when the remembered tenant differs from the run tenant', function (): void { $workspace = Workspace::factory()->create(); $tenantA = Tenant::factory()->for($workspace)->create(); $tenantB = Tenant::factory()->for($workspace)->create(); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); foreach ([$tenantA, $tenantB] as $tenant) { $tenant->users()->attach((int) $user->getKey(), [ 'role' => TenantRole::Owner->value, 'source' => 'manual', 'source_ref' => null, 'created_by_user_id' => null, ]); } $run = OperationRun::factory()->create([ 'tenant_id' => (int) $tenantA->getKey(), 'workspace_id' => (int) $workspace->getKey(), 'type' => 'inventory_sync', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, ]); Filament::setTenant($tenantB, true); $this->actingAs($user) ->withSession([ WorkspaceContext::SESSION_KEY => (int) $workspace->getKey(), WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [ (string) $workspace->getKey() => (int) $tenantB->getKey(), ], ]) ->get("/admin/operations/{$run->getKey()}") ->assertSuccessful() ->assertSee('Operation run') ->assertSee('Back to Operations'); }); it('keeps tenantless run viewing accessible while another tenant is selected', function (): void { $selectedTenant = Tenant::factory()->create(); [$user, $selectedTenant] = createUserWithTenant(tenant: $selectedTenant, role: 'owner'); $run = OperationRun::factory()->create([ 'workspace_id' => (int) $selectedTenant->workspace_id, 'tenant_id' => null, 'type' => 'provider.connection.check', 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, ]); Filament::setTenant($selectedTenant, true); $this->actingAs($user) ->withSession([ WorkspaceContext::SESSION_KEY => (int) $selectedTenant->workspace_id, WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [ (string) $selectedTenant->workspace_id => (int) $selectedTenant->getKey(), ], ]) ->get(route('admin.operations.view', ['run' => (int) $run->getKey()])) ->assertSuccessful() ->assertSee('Workspace-level run') ->assertSee('This canonical workspace view is not tied to the current tenant context'); }); it('keeps a canonical run viewer accessible when remembered tenant context is cleared as stale', function (): void { $runTenant = Tenant::factory()->active()->create([ 'name' => 'Viewer Run Tenant', ]); [$user, $runTenant] = createUserWithTenant(tenant: $runTenant, role: 'owner'); $rememberedTenant = Tenant::factory()->onboarding()->create([ 'workspace_id' => (int) $runTenant->workspace_id, 'name' => 'Viewer Onboarding Tenant', ]); createUserWithTenant( tenant: $rememberedTenant, user: $user, role: 'owner', workspaceRole: 'owner', ensureDefaultMicrosoftProviderConnection: false, ); $run = OperationRun::factory()->create([ 'tenant_id' => (int) $runTenant->getKey(), 'workspace_id' => (int) $runTenant->workspace_id, 'type' => 'inventory_sync', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, ]); Filament::setTenant(null, true); $this->actingAs($user) ->withSession([ WorkspaceContext::SESSION_KEY => (int) $runTenant->workspace_id, WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [ (string) $runTenant->workspace_id => (int) $rememberedTenant->getKey(), ], ]) ->get(route('admin.operations.view', ['run' => (int) $run->getKey()])) ->assertSuccessful() ->assertSee('All tenants') ->assertSee('Canonical workspace view') ->assertSee('No tenant context is currently selected.'); }); it('keeps a canonical run viewer accessible when the run tenant is selector-ineligible but the remembered context is valid', function (): void { $rememberedTenant = Tenant::factory()->active()->create([ 'name' => 'Viewer Active Tenant', ]); [$user, $rememberedTenant] = createUserWithTenant(tenant: $rememberedTenant, role: 'owner'); $runTenant = Tenant::factory()->onboarding()->create([ 'workspace_id' => (int) $rememberedTenant->workspace_id, 'name' => 'Viewer Onboarding Tenant', ]); createUserWithTenant( tenant: $runTenant, user: $user, role: 'owner', workspaceRole: 'owner', ensureDefaultMicrosoftProviderConnection: false, ); $run = OperationRun::factory()->create([ 'tenant_id' => (int) $runTenant->getKey(), 'workspace_id' => (int) $rememberedTenant->workspace_id, 'type' => 'inventory_sync', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, ]); Filament::setTenant(null, true); $this->actingAs($user) ->withSession([ WorkspaceContext::SESSION_KEY => (int) $rememberedTenant->workspace_id, WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [ (string) $rememberedTenant->workspace_id => (int) $rememberedTenant->getKey(), ], ]) ->get(route('admin.operations.view', ['run' => (int) $run->getKey()])) ->assertSuccessful() ->assertSee('Current tenant context differs from this run') ->assertSee('Run tenant: '.$runTenant->name.'.') ->assertSee('Back to Operations'); }); it('renders stored target scope and failure details for a completed run', function (): void { $workspace = Workspace::factory()->create(); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); session()->forget(WorkspaceContext::SESSION_KEY); $entraTenantId = '11111111-1111-1111-1111-111111111111'; $failureMessage = 'Missing required Graph permissions.'; $run = OperationRun::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => null, 'type' => 'provider.connection.check', 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Failed->value, 'context' => [ 'target_scope' => [ 'entra_tenant_id' => $entraTenantId, 'entra_tenant_name' => 'Contoso', ], ], 'failure_summary' => [ [ 'code' => 'provider.connection.check.failed', 'reason_code' => 'permission_denied', 'message' => $failureMessage, ], ], ]); $this->actingAs($user) ->get("/admin/operations/{$run->getKey()}") ->assertSuccessful() ->assertSee($entraTenantId) ->assertSee('permission_denied') ->assertSee($failureMessage); }); it('renders explicit back-link lineage when opened from a canonical source context', function (): void { $workspace = Workspace::factory()->create(); $tenant = Tenant::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), ]); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); $tenant->users()->attach((int) $user->getKey(), [ 'role' => TenantRole::Owner->value, 'source' => 'manual', 'source_ref' => null, 'created_by_user_id' => null, ]); $run = OperationRun::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $tenant->getKey(), 'type' => 'backup_set.add_policies', 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, ]); $context = new CanonicalNavigationContext( sourceSurface: 'backup_set.detail_section', canonicalRouteName: 'admin.operations.view', tenantId: (int) $tenant->getKey(), backLinkLabel: 'Back to backup set', backLinkUrl: '/admin/tenant/backup-sets/1', ); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()]) ->get(OperationRunLinks::tenantlessView($run, $context)) ->assertSuccessful() ->assertSee('Back to backup set') ->assertSee('/admin/tenant/backup-sets/1', false); }); it('keeps the explicit back-link action after Livewire hydration', function (): void { $workspace = Workspace::factory()->create(); $tenant = Tenant::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), ]); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); $tenant->users()->attach((int) $user->getKey(), [ 'role' => TenantRole::Owner->value, 'source' => 'manual', 'source_ref' => null, 'created_by_user_id' => null, ]); $run = OperationRun::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $tenant->getKey(), 'type' => 'baseline_compare', 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, ]); $this->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()]); session([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()]); Livewire::withQueryParams([ 'nav' => [ 'source_surface' => 'finding.detail_section', 'canonical_route_name' => 'admin.operations.view', 'tenant_id' => (int) $tenant->getKey(), 'back_label' => 'Back to finding', 'back_url' => '/admin/findings/42?tenant='.$tenant->external_id, ], ]) ->actingAs($user) ->test(\App\Filament\Pages\Operations\TenantlessOperationRunViewer::class, ['run' => $run]) ->assertActionVisible('operate_hub_back_to_origin_run_detail'); }); it('renders shared polling markup for active tenantless runs', function (string $status, int $ageSeconds): void { $workspace = Workspace::factory()->create(); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); session()->forget(WorkspaceContext::SESSION_KEY); $run = OperationRun::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => null, 'type' => 'provider.connection.check', 'status' => $status, 'outcome' => OperationRunOutcome::Pending->value, 'created_at' => now()->subSeconds($ageSeconds), ]); $expectedInterval = RunDetailPolling::interval($run); expect($expectedInterval)->not->toBeNull(); $this->actingAs($user) ->get("/admin/operations/{$run->getKey()}") ->assertSuccessful() ->assertSee("wire:poll.{$expectedInterval}", escape: false); })->with([ 'queued runs poll every second at startup' => [ OperationRunStatus::Queued->value, 5, ], 'running runs slow to five seconds after startup' => [ OperationRunStatus::Running->value, 30, ], ]); it('does not render polling markup for terminal tenantless runs', function (string $outcome): void { $workspace = Workspace::factory()->create(); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); session()->forget(WorkspaceContext::SESSION_KEY); $run = OperationRun::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => null, 'type' => 'provider.connection.check', 'status' => OperationRunStatus::Completed->value, 'outcome' => $outcome, 'created_at' => now()->subMinutes(2), ]); $this->actingAs($user) ->get("/admin/operations/{$run->getKey()}") ->assertSuccessful() ->assertDontSee('wire:poll.1s', escape: false) ->assertDontSee('wire:poll.5s', escape: false) ->assertDontSee('wire:poll.10s', escape: false); })->with([ 'succeeded runs stay stable' => OperationRunOutcome::Succeeded->value, 'failed runs stay stable' => OperationRunOutcome::Failed->value, ]);