create(); $user = User::factory()->create(); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()]) ->get(CrossTenantComparePage::getUrl(panel: 'admin')) ->assertNotFound(); }); it('returns 403 for workspace members missing baseline view capability on the compare route', function (): void { $workspace = Workspace::factory()->create(); $viewer = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $viewer->getKey(), 'role' => 'readonly', ]); $resolver = \Mockery::mock(WorkspaceCapabilityResolver::class); $resolver->shouldReceive('isMember')->andReturnTrue(); $resolver->shouldReceive('can')->andReturnFalse(); app()->instance(WorkspaceCapabilityResolver::class, $resolver); $this->actingAs($viewer) ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()]) ->get(CrossTenantComparePage::getUrl(panel: 'admin')) ->assertForbidden(); }); it('returns 404 when the requested target tenant is outside the actor scope', function (): void { $fixture = $this->makeCrossTenantCompareFixture(); $hiddenTarget = Tenant::factory()->create([ 'workspace_id' => (int) $fixture['workspace']->getKey(), 'name' => 'Hidden Target', ]); $session = $this->setAdminWorkspaceContext($fixture['user'], $fixture['workspace']); $this->withSession($session) ->get(CrossTenantComparePage::getUrl(parameters: [ 'source_tenant_id' => (int) $fixture['sourceTenant']->getKey(), 'target_tenant_id' => (int) $hiddenTarget->getKey(), ], panel: 'admin')) ->assertNotFound(); }); it('keeps promotion preflight visible but disabled for readonly members and forbids forced execution', function (): void { $fixture = $this->makeCrossTenantCompareFixture(workspaceRole: 'readonly', tenantRole: 'readonly'); $this->createPortfolioCompareSubject( tenant: $fixture['sourceTenant'], displayName: 'Readonly Policy', snapshot: ['settings' => [['key' => 'readonly', 'value' => 1]]], ); $this->createPortfolioCompareSubject( tenant: $fixture['targetTenant'], displayName: 'Readonly Policy', snapshot: ['settings' => [['key' => 'readonly', 'value' => 1]]], ); $this->setAdminWorkspaceContext($fixture['user'], $fixture['workspace']); $query = [ 'source_tenant_id' => (int) $fixture['sourceTenant']->getKey(), 'target_tenant_id' => (int) $fixture['targetTenant']->getKey(), 'policy_type' => ['deviceConfiguration'], ]; Livewire::withQueryParams($query) ->actingAs($fixture['user']) ->test(CrossTenantComparePage::class) ->assertActionVisible('generatePromotionPreflight') ->assertActionDisabled('generatePromotionPreflight') ->assertActionExists('generatePromotionPreflight', fn (Action $action): bool => $action->getTooltip() === 'You need workspace baseline manage access to generate a promotion preflight.') ->call('generatePromotionPreflight') ->assertForbidden(); });