Compare commits

..

No commits in common. "444b3520b0b235ce20803e947ccb56d9d224c30f" and "eb85b76eede1e85d3236dee6c7dcbf21642d13f1" have entirely different histories.

297 changed files with 1373 additions and 4437 deletions

View File

@ -137,14 +137,11 @@ public static function canAccess(): bool
return true;
}
$counts = app(GovernanceDecisionRegisterBuilder::class)->build(
return (int) (app(GovernanceDecisionRegisterBuilder::class)->build(
workspace: $workspace,
visibleTenants: $visibleTenants,
registerState: 'open',
)['counts'] ?? [];
return (int) ($counts['open'] ?? 0) > 0
|| (int) ($counts['recently_closed'] ?? 0) > 0;
)['counts']['open'] ?? 0) > 0;
}
public function mount(): void
@ -419,19 +416,7 @@ private function ensureRegisterIsVisible(): void
return;
}
$counts = $this->unfilteredRegisterPayload()['counts'] ?? [];
if ((int) ($counts['open'] ?? 0) > 0) {
return;
}
if ((int) ($counts['recently_closed'] ?? 0) > 0) {
$this->redirect($this->pageUrl(['register_state' => 'recently_closed']), navigate: true);
return;
}
if ((int) ($counts['open'] ?? 0) === 0) {
if ((int) ($this->registerPayload()['counts']['open'] ?? 0) === 0) {
abort(403);
}
}

View File

@ -12,7 +12,6 @@
use App\Support\Filament\CanonicalAdminTenantFilterState;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\OperateHub\OperateHubShell;
use App\Support\OperationRunLinks;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Operations\OperationLifecyclePolicy;
@ -24,7 +23,6 @@
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceType;
use App\Support\Workspaces\WorkspaceContext;
use App\Models\User;
use App\Services\Auth\ManagedEnvironmentAccessScopeResolver;
use App\Services\Auth\WorkspaceCapabilityResolver;
use BackedEnum;
use Filament\Actions\Action;
@ -349,7 +347,6 @@ public function table(Table $table): Table
->query(function (): Builder {
$workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());
$tenantFilter = $this->currentTenantFilterId();
$allowedTenantIds = $this->allowedTenantIdsForWorkspaceScope($workspaceId);
$query = OperationRun::query()
->with('user')
@ -362,18 +359,6 @@ public function table(Table $table): Table
! $workspaceId,
fn (Builder $query): Builder => $query->whereRaw('1 = 0'),
)
->when(
$workspaceId && $allowedTenantIds !== null,
function (Builder $query) use ($allowedTenantIds): Builder {
return $query->where(function (Builder $query) use ($allowedTenantIds): void {
$query->whereNull('managed_environment_id');
if ($allowedTenantIds !== []) {
$query->orWhereIn('managed_environment_id', $allowedTenantIds);
}
});
},
)
->when(
$tenantFilter !== null,
fn (Builder $query): Builder => $query->where('managed_environment_id', $tenantFilter),
@ -452,22 +437,9 @@ private function scopedSummaryQuery(): ?Builder
}
$tenantFilter = $this->currentTenantFilterId();
$allowedTenantIds = $this->allowedTenantIdsForWorkspaceScope($workspaceId);
return OperationRun::query()
->where('workspace_id', (int) $workspaceId)
->when(
$allowedTenantIds !== null,
function (Builder $query) use ($allowedTenantIds): Builder {
return $query->where(function (Builder $query) use ($allowedTenantIds): void {
$query->whereNull('managed_environment_id');
if ($allowedTenantIds !== []) {
$query->orWhereIn('managed_environment_id', $allowedTenantIds);
}
});
},
)
->when(
$tenantFilter !== null,
fn (Builder $query): Builder => $query->where('managed_environment_id', $tenantFilter),
@ -537,29 +509,6 @@ private function currentTenantFilterId(): ?int
return $this->normalizeEntitledTenantFilter($tenantFilter);
}
/**
* Null means inherited access to all environments in the workspace.
*
* @return list<int>|null
*/
private function allowedTenantIdsForWorkspaceScope(mixed $workspaceId): ?array
{
$user = auth()->user();
if (! $user instanceof User || ! is_int($workspaceId)) {
return [];
}
$allowedIds = app(ManagedEnvironmentAccessScopeResolver::class)
->allowedManagedEnvironmentIdsForWorkspace($user, $workspaceId);
if ($allowedIds === null) {
return null;
}
return array_values(array_unique(array_map('intval', $allowedIds)));
}
private function normalizeEntitledTenantFilter(mixed $value): ?int
{
if (! is_numeric($value)) {

View File

@ -20,14 +20,6 @@ public static function getLabel(): string
return 'Register tenant';
}
/**
* @return class-string<Model>
*/
public function getModel(): string
{
return ManagedEnvironment::class;
}
public static function canView(): bool
{
$user = auth()->user();

View File

@ -111,12 +111,7 @@ public static function getUrl(array $parameters = [], bool $isAbsolute = true, ?
return url('/admin');
}
$url = url('/admin/workspaces/'.($workspace->slug ?? $workspace->getKey()).'/environments/'.$resolvedTenant->getRouteKey());
$query = array_diff_key($parameters, array_flip(['tenant', 'environment', 'workspace']));
return $query === []
? $url
: $url.'?'.http_build_query($query);
return url('/admin/workspaces/'.($workspace->slug ?? $workspace->getKey()).'/environments/'.$resolvedTenant->getRouteKey());
}
/**

View File

@ -96,7 +96,7 @@ public function bootstrapOwner(): void
abort(403, 'Not allowed');
}
app(TenantMembershipManager::class)->grantScope($tenant, $user, $user, sourceRef: 'diagnostic');
app(TenantMembershipManager::class)->grantScope($tenant, $user, $user, source: 'diagnostic');
$this->mount();
}

View File

@ -4844,9 +4844,7 @@ private function completionSummaryBootstrapLabel(): string
$runs = is_array($runs) ? $runs : [];
if ($runs !== []) {
return $this->completionSummaryBootstrapCompleted()
? 'Completed'
: 'Started';
return 'Started';
}
return $this->completionSummarySelectedBootstrapTypes() === []
@ -4881,10 +4879,6 @@ private function completionSummaryBootstrapDetail(): string
return sprintf('%d action(s) selected', count($selectedTypes));
}
if ($this->completionSummaryBootstrapCompleted()) {
return sprintf('%d action(s) completed', count($runs));
}
if (count($runs) < count($selectedTypes)) {
return sprintf('%d of %d action(s) started', count($runs), count($selectedTypes));
}
@ -4901,41 +4895,6 @@ private function completionSummaryBootstrapSummary(): string
);
}
private function completionSummaryBootstrapCompleted(): bool
{
if (! $this->onboardingSession instanceof TenantOnboardingSession) {
return false;
}
$selectedTypes = $this->completionSummarySelectedBootstrapTypes();
if ($selectedTypes === []) {
return false;
}
$runs = $this->onboardingSession->state['bootstrap_operation_runs'] ?? null;
$runs = is_array($runs) ? $runs : [];
if ($runs === [] || count($runs) < count($selectedTypes)) {
return false;
}
$runIds = array_values(array_filter(array_map(
static fn (mixed $value): ?int => is_numeric($value) ? (int) $value : null,
$runs,
)));
if (count($runIds) < count($selectedTypes)) {
return false;
}
return OperationRun::query()
->whereIn('id', $runIds)
->where('status', OperationRunStatus::Completed->value)
->where('outcome', OperationRunOutcome::Succeeded->value)
->count() >= count($selectedTypes);
}
private function showCompletionSummaryBootstrapRecovery(): bool
{
return $this->completionSummaryBootstrapActionRequiredDetail() !== null;
@ -4951,7 +4910,6 @@ private function completionSummaryBootstrapColor(): string
return match ($this->completionSummaryBootstrapLabel()) {
'Action required' => 'warning',
'Started' => 'info',
'Completed' => 'success',
'Selected' => 'warning',
default => 'gray',
};

View File

@ -55,8 +55,6 @@ protected function getViewData(): array
->limit(5)
->get([
'id',
'workspace_id',
'managed_environment_id',
'type',
'status',
'outcome',

View File

@ -146,11 +146,6 @@ public function panel(Panel $panel): Panel
->icon('heroicon-o-queue-list')
->group(fn (): string => __('localization.navigation.monitoring'))
->sort(10),
NavigationItem::make('Alerts')
->url(fn (): string => route('filament.admin.alerts'))
->icon('heroicon-o-bell-alert')
->group(fn (): string => __('localization.navigation.monitoring'))
->sort(23),
NavigationItem::make(fn (): string => __('localization.navigation.audit_log'))
->url(fn (): string => route('admin.monitoring.audit-log'))
->icon('heroicon-o-clipboard-document-list')
@ -166,7 +161,7 @@ public function panel(Panel $panel): Panel
fn () => view('filament.partials.context-bar')->render()
)
->renderHook(
PanelsRenderHook::PAGE_START,
PanelsRenderHook::BODY_END,
fn (): string => request()->routeIs('admin.workspace.managed-tenants.index', 'admin.onboarding', 'admin.onboarding.draft', 'filament.admin.pages.choose-tenant')
? ''
: ((bool) config('tenantpilot.bulk_operations.progress_widget_enabled', true)
@ -229,11 +224,13 @@ public function panel(Panel $panel): Panel
Authenticate::class,
]);
if (! app()->runningUnitTests()) {
$theme = PanelThemeAsset::resolve('resources/css/filament/admin/theme.css');
if (is_string($theme)) {
$panel->theme($theme);
}
}
return $panel;
}

View File

@ -51,7 +51,7 @@ public function panel(Panel $panel): Panel
])
->navigationItems([
NavigationItem::make(fn (): string => __('localization.navigation.operations'))
->url(fn (): string => OperationRunLinks::index())
->url(fn (): string => route('admin.operations.index'))
->icon('heroicon-o-queue-list')
->group(fn (): string => __('localization.navigation.monitoring'))
->sort(10),

View File

@ -18,11 +18,6 @@ final class ManagedEnvironmentAccessScopeResolver
*/
private array $scopeIdsByUserWorkspace = [];
/**
* @var array<int, Workspace|null>
*/
private array $workspaceById = [];
public function __construct(
private readonly WorkspaceCapabilityResolver $workspaceCapabilityResolver,
) {}
@ -49,7 +44,7 @@ public function decision(User $user, ManagedEnvironment $tenant, ?string $requir
);
}
$workspace = $this->workspaceForId($workspaceId);
$workspace = Workspace::query()->whereKey($workspaceId)->first();
if (! $workspace instanceof Workspace) {
return new ManagedEnvironmentAccessDecision(
@ -144,7 +139,7 @@ public function canAccess(User $user, ManagedEnvironment $tenant): bool
*/
public function allowedManagedEnvironmentIdsForWorkspace(User $user, int $workspaceId): ?array
{
$workspace = $this->workspaceForId($workspaceId);
$workspace = Workspace::query()->whereKey($workspaceId)->first();
if (! $workspace instanceof Workspace || $this->workspaceCapabilityResolver->getRole($user, $workspace) === null) {
return [];
@ -192,7 +187,6 @@ public function prime(User $user, array $tenantIds): void
public function clearCache(): void
{
$this->scopeIdsByUserWorkspace = [];
$this->workspaceById = [];
}
public function applyWorkspaceScopeToQuery(Builder $query, User $user, int $workspaceId, string $qualifiedEnvironmentColumn): Builder
@ -239,15 +233,6 @@ private function scopeIdsForWorkspace(User $user, int $workspaceId): ?array
return $this->scopeIdsByUserWorkspace[$cacheKey];
}
private function workspaceForId(int $workspaceId): ?Workspace
{
if (! array_key_exists($workspaceId, $this->workspaceById)) {
$this->workspaceById[$workspaceId] = Workspace::query()->whereKey($workspaceId)->first();
}
return $this->workspaceById[$workspaceId];
}
private function hydrateTenantBoundary(ManagedEnvironment $tenant): ?ManagedEnvironment
{
if ($tenant->exists && $tenant->workspace_id !== null) {

View File

@ -199,15 +199,6 @@ public function startCompareForVisibleAssignments(BaselineProfile $profile, User
$blockedCount = 0;
$targets = [];
$this->capabilityResolver->primeMemberships(
$initiator,
$assignments
->pluck('managed_environment_id')
->filter(static fn (mixed $id): bool => is_numeric($id))
->map(static fn (mixed $id): int => (int) $id)
->all(),
);
foreach ($assignments as $assignment) {
$tenant = $assignment->tenant;

View File

@ -46,15 +46,6 @@ public function build(BaselineProfile $profile, User $user, array $filters = [])
->with('tenant')
->get();
$this->capabilityResolver->primeMemberships(
$user,
$assignments
->pluck('managed_environment_id')
->filter(static fn (mixed $id): bool => is_numeric($id))
->map(static fn (mixed $id): int => (int) $id)
->all(),
);
$visibleTenants = $this->visibleTenants($assignments, $user);
$referenceResolution = $this->snapshotTruthResolver->resolveCompareSnapshot($profile);
$referenceSnapshot = $this->resolvedSnapshot($referenceResolution);

View File

@ -405,7 +405,6 @@ private function operationsSection(
tenant: $selectedTenant,
context: $navigationContext,
problemClass: $dominantProblemClass,
workspace: $workspace,
),
'entries' => $entries,
'empty_state' => $selectedTenant instanceof ManagedEnvironment

View File

@ -84,11 +84,8 @@ public static function index(
bool $allTenants = false,
?string $problemClass = null,
?string $operationType = null,
?Workspace $workspace = null,
): string {
$workspace = $tenant instanceof ManagedEnvironment
? self::resolveWorkspace($tenant)
: ($workspace ?? self::resolveWorkspace());
$workspace = self::resolveWorkspace($tenant);
if (! $workspace instanceof Workspace) {
return url('/admin');

View File

@ -13,7 +13,7 @@ class="flex w-full flex-col items-start gap-3 sm:flex-row sm:flex-wrap sm:items-
@if (filled($context['provider'] ?? null))
<div data-testid="tenant-dashboard-context-chip-provider" data-provider-key="{{ $context['providerKey'] ?? '' }}" class="inline-flex items-center gap-2 whitespace-nowrap rounded-2xl border border-gray-200 bg-white/80 px-4 py-2 text-sm font-medium text-gray-700 shadow-sm backdrop-blur dark:border-white/10 dark:bg-white/5 dark:text-gray-200">
@if (($context['providerKey'] ?? null) === 'microsoft')
<svg data-testid="tenant-dashboard-context-chip-provider-microsoft-logo" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" class="h-4 w-4 shrink-0">
<svg data-testid="tenant-dashboard-context-chip-provider-microsoft-logo" viewBox="0 0 16 16" aria-hidden="true" class="h-4 w-4 shrink-0">
<rect x="1" y="1" width="6" height="6" fill="#f25022" />
<rect x="9" y="1" width="6" height="6" fill="#7fba00" />
<rect x="1" y="9" width="6" height="6" fill="#00a4ef" />

View File

@ -1,3 +1 @@
@if (\Filament\Facades\Filament::getCurrentPanel()?->getId() === 'admin' && auth()->user() instanceof \App\Models\User)
<livewire:bulk-operation-progress />
@endif

View File

@ -386,18 +386,10 @@
abort_unless($allowed, 404);
};
Route::middleware([
'web',
'panel:admin',
'ensure-correct-guard:web',
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
FilamentAuthenticate::class,
'ensure-workspace-member',
])
Route::middleware(['web', 'auth', 'ensure-correct-guard:web', 'ensure-workspace-member'])
->prefix('/admin/workspaces/{workspace}')
->group(function (): void {
Route::get('/overview', WorkspaceOverview::class)
Route::get('/', WorkspaceOverview::class)
->name('admin.workspace.home');
Route::get('/ping', fn () => response()->noContent())->name('admin.workspace.ping');

View File

@ -8,7 +8,6 @@
use App\Models\Finding;
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Support\OperationRunLinks;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Workspaces\WorkspaceContext;
@ -34,7 +33,6 @@
'outcome' => OperationRunOutcome::Failed->value,
'completed_at' => now()->subHour(),
]);
$operationPath = (string) parse_url(OperationRunLinks::tenantlessView($operation), PHP_URL_PATH);
$backupSet = BackupSet::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
@ -64,18 +62,17 @@
],
]);
$page = visit(TenantDashboard::getUrl(panel: 'admin', tenant: $tenant))
$page = visit(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
->waitForText($tenant->name)
->waitForText('Backup posture')
->assertScript("document.querySelector('[data-testid=\"tenant-dashboard-posture-pill\"]') !== null", true)
->assertScript("document.querySelector('[data-testid=\"tenant-dashboard-context-chip-workspace\"]') !== null", true)
->assertScript("document.querySelector('[data-testid=\"tenant-dashboard-context-chip-provider\"][data-provider-key=\"microsoft\"]') !== null", true)
->assertScript("document.querySelector('[data-testid=\"tenant-dashboard-context-chip-provider-microsoft-logo\"]') !== null", true)
->assertScript("(() => { const logo = document.querySelector('[data-testid=\"tenant-dashboard-context-chip-provider-microsoft-logo\"]'); if (! logo) return false; const rect = logo.getBoundingClientRect(); return rect.width <= 20 && rect.height <= 20; })()", true)
->assertScript("document.querySelector('[data-testid=\"tenant-dashboard-context-chip-latest-activity\"]') !== null", true)
->assertScript("document.querySelector('[data-testid=\"tenant-dashboard-context-chip-latest-activity-icon\"]') !== null", true)
->assertScript("(() => { const chips = document.querySelector('[data-testid=\"tenant-dashboard-context-chips\"]'); const firstKpi = document.querySelector('[data-testid=\"tenant-dashboard-kpi\"]'); if (! chips || ! firstKpi) return false; return chips.getBoundingClientRect().top < firstKpi.getBoundingClientRect().top; })()", true)
->assertScript("(() => { const subtitle = Array.from(document.querySelectorAll('p')).find((node) => node.textContent?.includes('governance overview')); const chips = document.querySelector('[data-testid=\"tenant-dashboard-context-chips\"]'); if (! subtitle || ! chips) return false; return chips.getBoundingClientRect().top - subtitle.getBoundingClientRect().bottom <= 64; })()", true)
->assertScript("(() => { const subtitle = Array.from(document.querySelectorAll('p')).find((node) => node.textContent?.includes('Tenant governance overview')); const chips = document.querySelector('[data-testid=\"tenant-dashboard-context-chips\"]'); if (! subtitle || ! chips) return false; return chips.getBoundingClientRect().top - subtitle.getBoundingClientRect().bottom <= 40; })()", true)
->assertScript("(() => { const workspace = document.querySelector('[data-testid=\"tenant-dashboard-context-chip-workspace\"]'); const provider = document.querySelector('[data-testid=\"tenant-dashboard-context-chip-provider\"]'); const activity = document.querySelector('[data-testid=\"tenant-dashboard-context-chip-latest-activity\"]'); if (! workspace || ! provider || ! activity) return false; const tops = [workspace, provider, activity].map((element) => Math.round(element.getBoundingClientRect().top)); return Math.max(...tops) - Math.min(...tops) <= 2; })()", true)
->assertSee('Recommended next actions')
->assertSee('Operations needing attention')
@ -111,10 +108,9 @@
->assertScript("! document.body.innerHTML.includes('fixed bottom-4 right-4 z-[999999] w-96 space-y-2')", true)
->assertScript("(() => { const overview = document.querySelector('[data-testid=\"tenant-dashboard-overview\"]'); const main = document.querySelector('[data-testid=\"tenant-dashboard-overview-main\"]'); if (! overview || ! main) return false; const overviewWidth = overview.getBoundingClientRect().width; const mainWidth = main.getBoundingClientRect().width; return overviewWidth >= 600 && mainWidth >= 400; })()", true)
->assertScript("document.querySelectorAll('[data-testid=\"tenant-dashboard-overview\"] table').length === 0", true)
->assertSeeIn('[data-testid="ops-ux-activity-feedback-primary-action"]', 'View operation')
->click('[data-testid="ops-ux-activity-feedback-primary-action"]')
->click('Review operation')
->waitForText('Show all operations')
->assertScript("window.location.pathname === '{$operationPath}'", true)
->assertScript("window.location.pathname.includes('/admin/operations/{$operation->getKey()}')", true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();

View File

@ -52,6 +52,7 @@
'entra_tenant_id' => (string) $tenant->managed_environment_id,
'tenant_name' => (string) $tenant->name,
'environment' => 'prod',
'provider_connection_id' => (int) $connection->getKey(),
],
]);
@ -66,24 +67,28 @@
->assertNoJavaScriptErrors()
->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()])
->assertSee('Onboarding draft')
->waitForText('Use existing connection')
->assertSee('Verify access')
->assertSee('Status: Not started')
->refresh()
->waitForText('Use existing connection')
->waitForText('Status: Not started')
->assertNoJavaScriptErrors()
->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()])
->check('Create new connection')
->waitForText('Dedicated override')
->check('Dedicated override')
->waitForText('Dedicated client secret')
->assertSee('Verify access')
->assertSee('Status: Not started')
->click('Select an existing connection or create a new one.')
->assertSee('Edit selected connection')
->click('Create new connection')
->check('internal:label="Dedicated override"s')
->fill('[type="password"]', 'browser-only-secret')
->assertValue('[type="password"]', 'browser-only-secret')
->refresh()
->waitForText('Use existing connection')
->waitForText('Status: Not started')
->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()])
->check('Create new connection')
->waitForText('Dedicated override')
->check('Dedicated override')
->waitForText('Dedicated client secret')
->assertSee('Verify access')
->click('Select an existing connection or create a new one.')
->assertSee('Edit selected connection')
->click('Create new connection')
->check('internal:label="Dedicated override"s')
->assertValue('[type="password"]', '');
});
@ -280,6 +285,5 @@
$page
->wait(7)
->assertNoJavaScriptErrors()
->assertSee('Completed - 1 action(s) completed')
->assertSee('Complete onboarding');
->assertSee('Bootstrap completed across 1 operation(s).');
});

View File

@ -53,11 +53,11 @@ function operationActivityFeedbackSmokeLoginUrl(User $user, ManagedEnvironment $
]);
visit(operationActivityFeedbackSmokeLoginUrl($user, $tenant))
->waitForText($tenant->name)
->waitForText('Dashboard')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$inventoryPage = visit(InventoryItemResource::getUrl('index', panel: 'admin', tenant: $tenant))
$inventoryPage = visit(InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $tenant))
->resize(1440, 1200)
->assertScript('window.innerWidth >= 1400', true)
->waitForText('Inventory Items')
@ -183,7 +183,7 @@ function operationActivityFeedbackSmokeLoginUrl(User $user, ManagedEnvironment $
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page = visit(FindingResource::getUrl('index', panel: 'admin', tenant: $tenant))
$page = visit(FindingResource::getUrl('index', panel: 'tenant', tenant: $tenant))
->resize(1440, 1200);
$page
@ -252,11 +252,11 @@ function operationActivityFeedbackSmokeLoginUrl(User $user, ManagedEnvironment $
]);
visit(operationActivityFeedbackSmokeLoginUrl($user, $tenant))
->waitForText($tenant->name)
->waitForText('Dashboard')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
visit(InventoryItemResource::getUrl('index', panel: 'admin', tenant: $tenant))
visit(InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $tenant))
->resize(1440, 1200)
->waitForText('Inventory Items')
->waitForText('Capturing evidence.')
@ -285,11 +285,11 @@ function operationActivityFeedbackSmokeLoginUrl(User $user, ManagedEnvironment $
]);
visit(operationActivityFeedbackSmokeLoginUrl($user, $tenant))
->waitForText($tenant->name)
->waitForText('Dashboard')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page = visit(InventoryItemResource::getUrl('index', panel: 'admin', tenant: $tenant))
$page = visit(InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $tenant))
->resize(1440, 1200)
->waitForText('Inventory Items')
->waitForText('Acknowledge')

View File

@ -48,10 +48,7 @@
visit(OperationRunLinks::tenantlessView($run))
->waitForText(OperationRunLinks::identifier((int) $run->getKey()))
->assertRoute('admin.operations.view', [
'workspace' => (int) $run->workspace_id,
'run' => (int) $run->getKey(),
])
->assertRoute('admin.operations.view', ['run' => (int) $run->getKey()])
->assertNoJavaScriptErrors()
->assertSee(OperationRunLinks::identifier((int) $run->getKey()));
});

View File

@ -3,8 +3,8 @@
declare(strict_types=1);
use App\Filament\Resources\TenantReviewResource;
use App\Models\ManagedEnvironment;
use App\Models\ReviewPack;
use App\Models\ManagedEnvironment;
use App\Support\TenantReviewStatus;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Foundation\Testing\RefreshDatabase;
@ -106,8 +106,7 @@
->waitForText('Published ManagedEnvironment')
->assertDontSee('No Published ManagedEnvironment')
->assertDontSee('No published review available yet')
->assertSeeIn('tbody tr.fi-ta-row:first-of-type td:last-child', 'Review öffnen')
->click('tbody tr.fi-ta-row:first-of-type td:last-child a')
->click('Review öffnen')
->waitForText('Ergebniszusammenfassung')
->assertSee('Governance-Paket herunterladen')
->assertSee('Governance-Paket')

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

View File

@ -72,16 +72,11 @@
visit($operationsIndexUrl)
->assertNoJavaScriptErrors()
->assertRoute('admin.operations.index', [
'workspace' => (int) $tenant->workspace_id,
]);
->assertRoute('admin.operations.index');
visit(OperationRunLinks::tenantlessView($run))
->assertNoJavaScriptErrors()
->assertRoute('admin.operations.view', [
'workspace' => (int) $run->workspace_id,
'run' => (int) $run->getKey(),
])
->assertRoute('admin.operations.view', ['run' => (int) $run->getKey()])
->assertSee(OperationRunLinks::identifier((int) $run->getKey()));
});

View File

@ -84,7 +84,7 @@
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $staleTenant->workspace_id);
visit(EvidenceSnapshotResource::getUrl('view', ['record' => $staleSnapshot], tenant: $staleTenant, panel: 'admin'))
visit(EvidenceSnapshotResource::getUrl('view', ['record' => $staleSnapshot], tenant: $staleTenant, panel: 'tenant'))
->waitForText('Outcome summary')
->assertNoJavaScriptErrors()
->assertSee('Stale')
@ -103,7 +103,7 @@
->assertSee('Refresh the source review before sharing this pack')
->assertSee('Download');
visit(ReviewPackResource::getUrl('view', ['record' => $stalePack], tenant: $staleTenant, panel: 'admin'))
visit(ReviewPackResource::getUrl('view', ['record' => $stalePack], tenant: $staleTenant, panel: 'tenant'))
->waitForText('Outcome summary')
->assertNoJavaScriptErrors()
->assertSee('Internal only')

View File

@ -123,11 +123,11 @@ function seedSpec177InventoryItemFilterPaginationFixtures(ManagedEnvironment $te
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
$coverageUrl = InventoryCoverage::getUrl(panel: 'admin', tenant: $tenant);
$coverageUrl = InventoryCoverage::getUrl(panel: 'tenant', tenant: $tenant);
$basisRunUrl = OperationRunLinks::view($run, $tenant);
$inventoryItemsUrl = InventoryItemResource::getUrl('index', panel: 'admin', tenant: $tenant);
$inventoryItemsUrl = InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $tenant);
$searchPage = visit(InventoryItemResource::getUrl('index', panel: 'admin', tenant: $tenant));
$searchPage = visit(InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $tenant));
$searchPage
->waitForText('Inventory Items')
@ -162,10 +162,7 @@ function seedSpec177InventoryItemFilterPaginationFixtures(ManagedEnvironment $te
visit($basisRunUrl)
->waitForText('Operation #'.(int) $run->getKey())
->assertNoJavaScriptErrors()
->assertRoute('admin.operations.view', [
'workspace' => (int) $run->workspace_id,
'run' => (int) $run->getKey(),
])
->assertRoute('admin.operations.view', ['run' => (int) $run->getKey()])
->assertSee('Inventory sync coverage')
->assertSee('Need follow-up');

View File

@ -11,7 +11,7 @@
uses(BuildsBaselineCompareMatrixFixtures::class);
pest()->browser()->timeout(20_000);
pest()->browser()->timeout(15_000);
it('smokes dense multi-tenant scanning and finding drilldown continuity', function (): void {
$fixture = $this->makeBaselineCompareMatrixFixture();

View File

@ -143,7 +143,7 @@ function spec192ApprovedFindingException(ManagedEnvironment $tenant, User $reque
->assertSee('Review compare matrix')
->assertSee('Compare now');
visit(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin'))
visit(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'tenant'))
->waitForText('Related context')
->assertNoJavaScriptErrors()
->assertScript("document.querySelectorAll('[data-supporting-group-kind]').length === 0", true)
@ -266,7 +266,7 @@ function spec192ApprovedFindingException(ManagedEnvironment $tenant, User $reque
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
visit(ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant, panel: 'admin'))
visit(ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant, panel: 'tenant'))
->waitForText('Download')
->assertNoJavaScriptErrors()
->assertSee('Regenerate');

View File

@ -73,7 +73,7 @@
->assertNoConsoleLogs()
->assertSee('Quiet monitoring mode');
visit(\App\Support\OperationRunLinks::tenantlessView($run))
visit(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->assertSee('Monitoring detail')
@ -84,10 +84,8 @@
->assertNoConsoleLogs()
->assertSee('Alert deliveries');
visit(\App\Filament\Pages\TenantDashboard::getUrl(tenant: $diagnosticsTenant))
visit('/admin/t/'.$diagnosticsTenant->external_id.'/diagnostics')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->assertSee($diagnosticsTenant->name)
->click('[aria-label="More"]')
->assertSee('Open support diagnostics');
->assertSee('Missing owner');
});

View File

@ -10,20 +10,20 @@
use App\Models\EvidenceSnapshot;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\PlatformUser;
use App\Models\ReviewPack;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Findings\FindingExceptionService;
use App\Support\Auth\PlatformCapabilities;
use App\Support\Evidence\EvidenceCompletenessState;
use App\Support\Evidence\EvidenceSnapshotStatus;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\System\SystemOperationRunLinks;
use App\Support\TenantReviewCompletenessState;
use App\Support\TenantReviewStatus;
use App\Support\Auth\PlatformCapabilities;
use App\Support\System\SystemOperationRunLinks;
use Illuminate\Foundation\Testing\RefreshDatabase;
pest()->browser()->timeout(20_000);
@ -65,8 +65,6 @@ function spec194ApprovedFindingException(ManagedEnvironment $tenant, User $reque
function spec194SmokeLoginUrl(User $user, ManagedEnvironment $tenant, string $redirect = ''): string
{
$redirect = spec194RelativeRedirect($redirect);
return route('admin.local.smoke-login', array_filter([
'email' => $user->email,
'tenant' => $tenant->external_id,
@ -75,31 +73,11 @@ function spec194SmokeLoginUrl(User $user, ManagedEnvironment $tenant, string $re
], static fn (?string $value): bool => filled($value)));
}
function spec194RelativeRedirect(string $redirect): string
{
$redirect = trim($redirect);
if ($redirect === '') {
return '';
}
$parts = parse_url($redirect);
if ($parts === false || ! isset($parts['path'])) {
return '';
}
return $parts['path']
.(isset($parts['query']) ? '?'.$parts['query'] : '')
.(isset($parts['fragment']) ? '#'.$parts['fragment'] : '');
}
it('smokes tenant and admin governance semantics through modal entry points', function (): void {
[$user, $tenant] = createUserWithTenant(
role: 'owner',
workspaceRole: 'owner',
workspaceRole: 'manager',
ensureDefaultMicrosoftProviderConnection: false,
clearCapabilityCaches: true,
);
$finding = Finding::factory()->for($tenant)->create();
@ -169,13 +147,12 @@ function spec194RelativeRedirect(string $redirect): string
tenant: $archivedTenant,
user: $user,
role: 'owner',
workspaceRole: 'owner',
workspaceRole: 'manager',
ensureDefaultMicrosoftProviderConnection: false,
clearCapabilityCaches: true,
);
visit(spec194SmokeLoginUrl($user, $tenant))
->waitForText($tenant->name)
->waitForText('Dashboard')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
@ -200,14 +177,14 @@ function spec194RelativeRedirect(string $redirect): string
->assertNoConsoleLogs()
->click('Publish review')
->waitForText('Publication reason')
->click('button:has-text("Cancel")')
->click('Cancel')
->click('[aria-label="More"]')
->assertSee('Refresh review')
->assertSee('Export executive pack')
->click('[aria-label="Danger"]')
->click('Archive review')
->waitForText('Archive reason')
->click('button:has-text("Cancel")')
->click('Cancel')
->assertSee('Publish review')
->assertSee('Evidence snapshot');
@ -217,10 +194,10 @@ function spec194RelativeRedirect(string $redirect): string
->assertNoConsoleLogs()
->click('Refresh evidence')
->waitForText('Confirm')
->click('button:has-text("Cancel")')
->click('Cancel')
->click('Expire snapshot')
->waitForText('Expiry reason')
->click('button:has-text("Cancel")')
->click('Cancel')
->assertSee('Refresh evidence')
->assertSee('Expire snapshot');
@ -229,10 +206,9 @@ function spec194RelativeRedirect(string $redirect): string
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->click('[aria-label="Lifecycle"]')
->waitForText('Archive')
->click('button:has-text("Archive")')
->click('Archive')
->waitForText('Archive reason')
->click('button:has-text("Cancel")')
->click('Cancel')
->assertSee('Lifecycle');
visit(TenantResource::getUrl('edit', ['record' => $tenant], panel: 'admin'))
@ -241,21 +217,13 @@ function spec194RelativeRedirect(string $redirect): string
->assertNoConsoleLogs()
->assertSee('Lifecycle');
visit(spec194SmokeLoginUrl(
$user,
$archivedTenant,
TenantResource::getUrl('view', ['record' => $archivedTenant], panel: 'admin'),
))
visit(TenantResource::getUrl('view', ['record' => $archivedTenant], panel: 'admin'))
->waitForText('Related context')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->assertSee('Lifecycle');
visit(spec194SmokeLoginUrl(
$user,
$archivedTenant,
TenantResource::getUrl('edit', ['record' => $archivedTenant], panel: 'admin'),
))
visit(TenantResource::getUrl('edit', ['record' => $archivedTenant], panel: 'admin'))
->waitForText('Related context')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()

View File

@ -95,7 +95,6 @@
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
visit(route('admin.operations.index', [
'workspace' => $tenant->workspace,
'managed_environment_id' => (int) $tenant->getKey(),
'activeTab' => 'active',
]))
@ -152,14 +151,8 @@
'baseline_profile_id' => (int) $profile->getKey(),
]);
$this->actingAs($user)->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
(string) $tenant->workspace_id => (int) $tenant->getKey(),
],
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
setAdminPanelContext($tenant);
$this->actingAs($user);
$tenant->makeCurrent();
$matrixUrl = BaselineProfileResource::compareMatrixUrl($profile).'?subject_key='.urlencode($subjectKey);
@ -168,11 +161,11 @@
'baseline_profile_id' => (int) $profile->getKey(),
'subject_key' => $subjectKey,
],
panel: 'admin',
panel: 'tenant',
tenant: $tenant,
))
->waitForText('Open compare matrix')
->assertSee('Launch the compare matrix with the currently known baseline profile and any carried subject focus from this environment landing.');
->assertSee('Launch the compare matrix with the currently known baseline profile and any carried subject focus from this tenant landing.');
visit($matrixUrl)
->waitForText('Focused subject')

View File

@ -73,7 +73,7 @@ function spec202SmokeLoginUrl(User $user, ManagedEnvironment $tenant, string $re
]);
visit(spec202SmokeLoginUrl($user, $tenant))
->waitForText($tenant->name)
->waitForText('Dashboard')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
@ -148,7 +148,7 @@ function spec202SmokeLoginUrl(User $user, ManagedEnvironment $tenant, string $re
]);
visit(spec202SmokeLoginUrl($user, $tenant))
->waitForText($tenant->name)
->waitForText('Dashboard')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();

View File

@ -71,7 +71,7 @@ function spec265SmokeLoginUrl(User $user, ManagedEnvironment $tenant, string $re
]);
visit(spec265SmokeLoginUrl($user, $tenant))
->waitForText($tenant->name)
->waitForText('Dashboard')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
@ -82,8 +82,7 @@ function spec265SmokeLoginUrl(User $user, ManagedEnvironment $tenant, string $re
->assertSee('The register is currently filtered to one tenant.')
->assertSee($tenant->name)
->assertSee('Showing 1 result')
->assertSeeIn('tbody tr.fi-ta-row:first-of-type', $tenant->name)
->click('tbody tr.fi-ta-row:first-of-type')
->click('tbody tr.fi-ta-row')
->waitForText('Opened from the workspace decision register')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()

View File

@ -41,7 +41,7 @@
],
]);
visit(StoredReportResource::getUrl('index', tenant: $tenant, panel: 'admin'))
visit(StoredReportResource::getUrl('index', tenant: $tenant, panel: 'tenant'))
->waitForText('Stored reports')
->assertSee('Permission posture report')
->assertSee('Current')

View File

@ -35,7 +35,7 @@
->assertSee('Active')
->click('Spec 279 Production')
->waitForText('Spec 279 Production')
->assertPathContains('/admin/workspaces/'.$environment->workspace->slug.'/environments/spec-279-production')
->assertPathContains('/admin/t/spec-279-production')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
});

View File

@ -14,7 +14,9 @@
$member = User::factory()->create([
'email' => 'browser-tenant-member@example.test',
]);
createUserWithTenant(tenant: $tenant, user: $member, role: 'readonly');
$member->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'readonly'],
]);
$this->actingAs($owner)->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
@ -26,27 +28,28 @@
$viewPage
->assertNoJavaScriptErrors()
->assertSee((string) $tenant->name)
->assertSee('Manage access scope')
->assertSee('Manage memberships')
->assertScript("document.body.innerText.includes('Add member')", false)
->assertScript("document.body.innerText.includes('browser-tenant-member@example.test')", false);
$membershipsPage = $viewPage->click('Manage access scope');
$membershipsPage = $viewPage->click('Manage memberships');
$membershipsPage
->assertNoJavaScriptErrors()
->assertSee('Manage environment access scope')
->assertSee('Back to environment overview')
->assertSee('Workspace membership defines the role. Explicit environment scopes only narrow which workspace members can see this environment.');
->assertSee('Manage tenant memberships')
->assertSee('Back to tenant overview')
->assertSee('ManagedEnvironment access is managed here. Use the tenant overview for provider state, verification, and operational context.');
$membershipsPage->script(<<<'JS'
window.scrollTo(0, document.body.scrollHeight);
JS);
$membershipsPage
->waitForText('Add explicit access scope')
->waitForText('Add member')
->assertNoJavaScriptErrors()
->assertSee('Manage environment access scope')
->assertSee('Add explicit access scope')
->assertSee('Manage tenant memberships')
->assertSee('Add member')
->assertSee('browser-tenant-member@example.test')
->assertSee('Remove explicit scope');
->assertSee('Change role')
->assertSee('Remove');
});

View File

@ -44,7 +44,7 @@ public function test_renders_canonical_detail_for_a_workspace_member_when_tenant
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee(\App\Support\OperationRunLinks::identifier($run))
->assertSee('Policy sync')
@ -70,7 +70,7 @@ public function test_renders_canonical_detail_gracefully_when_tenant_id_is_null(
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('No target scope details were recorded for this operation.');
}
@ -89,7 +89,7 @@ public function test_returns_404_on_canonical_detail_for_non_members(): void
]);
$this->actingAs($otherUser)
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertNotFound();
}
@ -118,7 +118,7 @@ public function test_renders_canonical_detail_db_only_with_no_job_dispatch(): vo
assertNoOutboundHttp(function () use ($user, $run): void {
$this->actingAs($user)
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('Verification report');
});

View File

@ -25,7 +25,7 @@ public function test_hides_operations_kpi_stats_when_tenant_context_is_absent():
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(\App\Support\OperationRunLinks::index())
->get(route('admin.operations.index'))
->assertOk()
->assertDontSee('Total Operations (30 days)')
->assertDontSee('Active Operations')

View File

@ -38,7 +38,7 @@ public function test_renders_workspace_operations_list_with_tenantless_runs_when
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(\App\Support\OperationRunLinks::index())
->get(route('admin.operations.index'))
->assertOk()
->assertSee('Tenantless run')
->assertSee('ManagedEnvironment run');
@ -70,7 +70,7 @@ public function test_renders_workspace_operations_list_workspace_wide_even_with_
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(\App\Support\OperationRunLinks::index())
->get(route('admin.operations.index'))
->assertOk()
->assertSee('ManagedEnvironment run')
->assertSee('Tenantless run');

View File

@ -38,7 +38,7 @@ public function test_shows_restore_related_links_on_canonical_detail_for_restore
$response = $this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('Open')
->assertSee('View restore run');
@ -61,7 +61,7 @@ public function test_shows_only_generic_links_for_tenantless_runs_on_canonical_d
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('Operations')
->assertSee(OperationRunLinks::index(), false)
@ -80,7 +80,7 @@ public function test_does_not_show_legacy_admin_details_cta_and_keeps_canonical_
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertDontSee('Admin details')
->assertDontSee('/admin/t/'.$tenant->external_id.'/operations/r/'.$run->getKey(), false);
@ -89,7 +89,7 @@ public function test_does_not_show_legacy_admin_details_cta_and_keeps_canonical_
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(OperationRunLinks::index())
->get(route('admin.operations.index'))
->assertOk()
->assertSee('Open run detail');
}
@ -122,11 +122,11 @@ public function test_hides_tenant_scoped_follow_up_links_when_the_run_tenant_is_
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $activeTenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('Open')
->assertSee('Operations')
->assertDontSee(InventoryItemResource::getUrl('index', panel: 'admin', tenant: $onboardingTenant), false)
->assertDontSee(InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $onboardingTenant), false)
->assertSee('Some tenant follow-up actions may be unavailable from this canonical workspace view.');
}
}

View File

@ -53,12 +53,12 @@ public function test_renders_verification_report_on_canonical_detail_without_fil
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('Verification report')
->assertDontSee('Verification report unavailable')
->assertSee('Open previous operation')
->assertSee(\App\Support\OperationRunLinks::tenantlessView($previousRun), false)
->assertSee('/admin/operations/'.((int) $previousRun->getKey()), false)
->assertSee('Token acquisition works');
}
}

View File

@ -7,6 +7,7 @@
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
@ -37,15 +38,15 @@ public function test_shows_non_blocking_mismatch_context_when_the_selected_tenan
'outcome' => OperationRunOutcome::Succeeded->value,
]);
setAdminPanelContext($currentTenant);
Filament::setTenant($currentTenant, true);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $runTenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('Current environment context differs from this operation')
->assertSee('Current environment context: Current ManagedEnvironment.')
->assertSee('Operation environment: Run ManagedEnvironment.')
->assertSee('Current tenant context differs from this operation')
->assertSee('Current tenant context: Current ManagedEnvironment.')
->assertSee('Operation tenant: Run ManagedEnvironment.')
->assertSee('canonical workspace view');
}
@ -64,14 +65,14 @@ public function test_frames_tenantless_runs_as_workspace_level_even_when_tenant_
'outcome' => OperationRunOutcome::Succeeded->value,
]);
setAdminPanelContext($selectedTenant);
Filament::setTenant($selectedTenant, true);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $selectedTenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('Workspace-level operation')
->assertSee('This canonical workspace view is not tied to the current environment context (Selected ManagedEnvironment).');
->assertSee('This canonical workspace view is not tied to the current tenant context (Selected ManagedEnvironment).');
}
public function test_keeps_onboarding_tenant_runs_viewable_with_lifecycle_aware_context(): void
@ -90,14 +91,14 @@ public function test_keeps_onboarding_tenant_runs_viewable_with_lifecycle_aware_
'outcome' => OperationRunOutcome::Succeeded->value,
]);
setAdminPanelContext();
Filament::setTenant(null, true);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('Operation environment is not available in the current environment selector')
->assertSee('Operation environment: Onboarding ManagedEnvironment.')
->assertSee('Operation tenant is not available in the current tenant selector')
->assertSee('Operation tenant: Onboarding ManagedEnvironment.')
->assertSee('This tenant is currently onboarding')
->assertSee('Back to Operations')
->assertDontSee('This tenant is currently active')
@ -128,14 +129,14 @@ public function test_keeps_archived_tenant_runs_viewable_with_lifecycle_aware_co
'outcome' => OperationRunOutcome::Succeeded->value,
]);
setAdminPanelContext();
Filament::setTenant(null, true);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $activeTenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('Operation environment is not available in the current environment selector')
->assertSee('Operation environment: Archived ManagedEnvironment.')
->assertSee('Operation tenant is not available in the current tenant selector')
->assertSee('Operation tenant: Archived ManagedEnvironment.')
->assertSee('This tenant is currently archived')
->assertSee('Back to Operations')
->assertDontSee('deactivated')
@ -158,14 +159,14 @@ public function test_keeps_selector_excluded_draft_tenant_runs_viewable_with_lif
'outcome' => OperationRunOutcome::Succeeded->value,
]);
setAdminPanelContext();
Filament::setTenant(null, true);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('Operation environment is not available in the current environment selector')
->assertSee('Operation environment: Draft ManagedEnvironment.')
->assertSee('Operation tenant is not available in the current tenant selector')
->assertSee('Operation tenant: Draft ManagedEnvironment.')
->assertSee('This tenant is currently draft')
->assertDontSee('Resume onboarding');
}

View File

@ -7,6 +7,7 @@
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\OperationRunLinks;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
@ -42,7 +43,7 @@ public function test_trusts_canonical_run_links_opened_from_a_tenant_surface_aft
backLinkUrl: route('filament.admin.resources.tenants.view', ['record' => $runTenant]),
);
setAdminPanelContext($otherTenant);
Filament::setTenant($otherTenant, true);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $runTenant->workspace_id])
@ -50,7 +51,7 @@ public function test_trusts_canonical_run_links_opened_from_a_tenant_surface_aft
->assertOk()
->assertSee('Back to tenant')
->assertSee(route('filament.admin.resources.tenants.view', ['record' => $runTenant]), false)
->assertSee('Current environment context differs from this operation');
->assertSee('Current tenant context differs from this operation');
}
public function test_trusts_notification_style_run_links_with_no_selected_tenant_context(): void
@ -64,7 +65,7 @@ public function test_trusts_notification_style_run_links_with_no_selected_tenant
'type' => 'inventory_sync',
]);
setAdminPanelContext();
Filament::setTenant(null, true);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
@ -91,7 +92,7 @@ public function test_uses_canonical_collection_link_for_default_back_and_show_al
'type' => 'inventory_sync',
]);
setAdminPanelContext($otherTenant);
Filament::setTenant($otherTenant, true);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $runTenant->workspace_id])
@ -129,7 +130,7 @@ public function test_trusts_verification_surface_run_links_with_no_selected_tena
backLinkUrl: '/admin/verification/report',
);
setAdminPanelContext();
Filament::setTenant(null, true);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])

View File

@ -27,11 +27,6 @@
'user_id' => $nonMember->getKey(),
'role' => 'owner',
]);
createUserWithTenant(
tenant: ManagedEnvironment::factory()->create(['workspace_id' => (int) $workspace->getKey()]),
user: $nonMember,
role: 'owner',
);
$this->actingAs($nonMember)
->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])

View File

@ -61,14 +61,6 @@
'role' => 'owner',
]);
createUserWithTenant(
tenant: ManagedEnvironment::factory()->archived()->create([
'workspace_id' => (int) $workspace->getKey(),
]),
user: $user,
role: 'owner',
);
ManagedEnvironment::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'status' => 'active',
@ -112,7 +104,7 @@
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $visibleTenant->workspace_id])
->get(FindingResource::getUrl('view', ['record' => $hiddenFinding], panel: 'admin', tenant: $hiddenTenant))
->get(FindingResource::getUrl('view', ['record' => $hiddenFinding], panel: 'tenant', tenant: $hiddenTenant))
->assertNotFound();
});
@ -141,6 +133,6 @@
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(FindingResource::getUrl('view', ['record' => $finding], panel: 'admin', tenant: $tenant))
->get(FindingResource::getUrl('view', ['record' => $finding], panel: 'tenant', tenant: $tenant))
->assertOk();
});

View File

@ -126,7 +126,7 @@
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $visibleTenant->workspace_id])
->get(FindingResource::getUrl('view', ['record' => $hiddenFinding], panel: 'admin', tenant: $hiddenTenant))
->get(FindingResource::getUrl('view', ['record' => $hiddenFinding], panel: 'tenant', tenant: $hiddenTenant))
->assertNotFound();
});
@ -166,6 +166,6 @@
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(FindingResource::getUrl('view', ['record' => $finding], panel: 'admin', tenant: $tenant))
->get(FindingResource::getUrl('view', ['record' => $finding], panel: 'tenant', tenant: $tenant))
->assertForbidden();
});

View File

@ -25,7 +25,7 @@
$nonMember = User::factory()->create();
$this->actingAs($nonMember)
->get(BaselineCompareLanding::getUrl(tenant: $tenant, panel: 'admin'))
->get(BaselineCompareLanding::getUrl(tenant: $tenant, panel: 'tenant'))
->assertNotFound();
});
@ -42,10 +42,7 @@
$this->actingAs($readonly)
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(route('admin.operations.view', [
'workspace' => $tenant->workspace,
'run' => (int) $run->getKey(),
]))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertForbidden();
});
@ -183,15 +180,6 @@
'role' => 'owner',
]);
$visibleTenant = ManagedEnvironment::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'status' => 'active',
]);
$user->tenants()->syncWithoutDetaching([
$visibleTenant->getKey() => ['role' => 'owner'],
]);
$run = OperationRun::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $workspace->getKey(),
@ -203,9 +191,6 @@
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
->get(route('admin.operations.view', [
'workspace' => $workspace,
'run' => (int) $run->getKey(),
]))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertNotFound();
});

View File

@ -38,7 +38,7 @@
->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertSuccessful()
->assertSee('Permission required')
->assertSee('The initiating actor no longer has the capability required for this queued run.')
@ -77,6 +77,6 @@
->withSession([
WorkspaceContext::SESSION_KEY => (int) $hiddenTenant->workspace_id,
])
->get(\App\Support\OperationRunLinks::tenantlessView($run))
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertNotFound();
});

View File

@ -56,10 +56,10 @@
];
$this->withSession($session)
->get(BackupScheduleResource::getUrl('edit', ['record' => $allowed], panel: 'admin', tenant: $tenantA))
->get(BackupScheduleResource::getUrl('edit', ['record' => $allowed], panel: 'admin'))
->assertOk();
$this->withSession($session)
->get(BackupScheduleResource::getUrl('edit', ['record' => $blocked], panel: 'admin', tenant: $tenantA))
->get(BackupScheduleResource::getUrl('edit', ['record' => $blocked], panel: 'admin'))
->assertNotFound();
});

View File

@ -2,7 +2,6 @@
use App\Filament\Resources\BackupScheduleResource\Pages\CreateBackupSchedule;
use App\Filament\Resources\BackupScheduleResource\Pages\EditBackupSchedule;
use App\Filament\Resources\BackupScheduleResource;
use App\Models\BackupSchedule;
use App\Models\ManagedEnvironment;
use Carbon\CarbonImmutable;
@ -53,7 +52,7 @@
// workspace matches the tenant we are about to access.
session()->put(\App\Support\Workspaces\WorkspaceContext::SESSION_KEY, (int) $tenantA->workspace_id);
$this->get(BackupScheduleResource::getUrl('index', tenant: $tenantA))
$this->get(route('filament.admin.resources.backup-schedules.index', filamentTenantRouteParams($tenantA)))
->assertOk()
->assertSee('ManagedEnvironment A schedule')
->assertSee('Device Configuration')
@ -80,7 +79,7 @@
$this->actingAs($user);
$this->get(BackupScheduleResource::getUrl('index', tenant: $tenant))
$this->get(route('filament.admin.resources.backup-schedules.index', filamentTenantRouteParams($tenant)))
->assertOk()
->assertSee('Jan 5, 2026 10:17:00');
});
@ -90,7 +89,7 @@
$unauthorizedTenant = ManagedEnvironment::factory()->create();
$this->actingAs($user)
->get(BackupScheduleResource::getUrl('index', tenant: $unauthorizedTenant))
->get(route('filament.admin.resources.backup-schedules.index', filamentTenantRouteParams($unauthorizedTenant)))
->assertNotFound();
});
@ -168,7 +167,7 @@
$this->actingAs($user);
$this->get(BackupScheduleResource::getUrl('index', tenant: $tenant))
$this->get(route('filament.admin.resources.backup-schedules.index', filamentTenantRouteParams($tenant)))
->assertOk()
->assertSee('Active schedule')
->assertDontSee('Archived schedule');

View File

@ -359,7 +359,7 @@ function makeBackupScheduleForLifecycle(\App\Models\ManagedEnvironment $tenant,
$this->get(BackupScheduleResource::getUrl('index', [
'backup_health_reason' => TenantBackupHealthAssessment::REASON_SCHEDULE_FOLLOW_UP,
], panel: 'admin', tenant: $tenant))
], panel: 'tenant', tenant: $tenant))
->assertOk()
->assertSee('not produced a successful run yet')
->assertSee($schedule->name)

View File

@ -54,9 +54,9 @@
$result = $service->startCompareForVisibleAssignments($fixture['profile'], $fixture['user']);
expect($result['visibleAssignedTenantCount'])->toBe(3)
->and($result['queuedCount'])->toBe(2)
->and($result['queuedCount'])->toBe(1)
->and($result['alreadyQueuedCount'])->toBe(1)
->and($result['blockedCount'])->toBe(0);
->and($result['blockedCount'])->toBe(1);
$launchStates = collect($result['targets'])
->mapWithKeys(static fn (array $target): array => [(int) $target['tenantId'] => (string) $target['launchState']])
@ -64,7 +64,7 @@
expect($launchStates[(int) $fixture['visibleTenant']->getKey()] ?? null)->toBe('queued')
->and($launchStates[(int) $fixture['visibleTenantTwo']->getKey()] ?? null)->toBe('already_queued')
->and($launchStates[(int) $readonlyTenant->getKey()] ?? null)->toBe('queued');
->and($launchStates[(int) $readonlyTenant->getKey()] ?? null)->toBe('blocked');
Queue::assertPushed(CompareBaselineToTenantJob::class);
@ -73,7 +73,7 @@
->where('type', OperationRunType::BaselineCompare->value)
->get();
expect($activeRuns)->toHaveCount(3)
expect($activeRuns)->toHaveCount(2)
->and($activeRuns->every(static fn (OperationRun $run): bool => $run->managed_environment_id !== null))->toBeTrue()
->and($activeRuns->every(static fn (OperationRun $run): bool => (string) $run->status === OperationRunStatus::Queued->value))->toBeTrue()
->and($activeRuns->every(static fn (OperationRun $run): bool => (string) $run->outcome === OperationRunOutcome::Pending->value))->toBeTrue()

View File

@ -7,17 +7,19 @@
use App\Support\Baselines\BaselineProfileStatus;
use Filament\Facades\Filament;
it('keeps baseline profiles workspace-owned while retired tenant navigation URLs stay unavailable', function (): void {
Filament::setCurrentPanel('admin');
it('keeps baseline profiles out of tenant panel registration and tenant navigation URLs', function (): void {
$tenantPanelResources = Filament::getPanel('tenant')->getResources();
expect($tenantPanelResources)->not->toContain(BaselineProfileResource::class);
[$user, $tenant] = createUserWithTenant(role: 'owner');
expect(BaselineProfileResource::shouldRegisterNavigation())->toBeTrue();
$this->actingAs($user)
->withSession([\App\Support\Workspaces\WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get("/admin/t/{$tenant->external_id}")
->assertNotFound();
->assertOk()
->assertDontSee("/admin/t/{$tenant->external_id}/baseline-profiles", false)
->assertDontSee('>Baselines</span>', false);
});
it('keeps baseline profile urls workspace-owned even when a tenant context exists', function (): void {

View File

@ -5,14 +5,22 @@
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\RestoreRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('backup sets table bulk archive creates a run and archives selected sets', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$sets = collect(range(1, 3))->map(function (int $i) use ($tenant) {
return BackupSet::create([
@ -55,8 +63,13 @@
});
test('backup sets can be archived even when referenced by restore runs', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$set = BackupSet::create([
'managed_environment_id' => $tenant->id,
@ -83,8 +96,13 @@
});
test('backup sets table bulk archive requires type-to-confirm for 10+ sets', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$sets = collect(range(1, 10))->map(function (int $i) use ($tenant) {
return BackupSet::create([

View File

@ -4,14 +4,22 @@
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\RestoreRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('bulk delete restore runs skips running items', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$backupSet = BackupSet::create([
'managed_environment_id' => $tenant->id,

View File

@ -4,14 +4,22 @@
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\RestoreRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('bulk delete restore runs soft deletes selected runs', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$backupSet = BackupSet::create([
'managed_environment_id' => $tenant->id,

View File

@ -4,14 +4,22 @@
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('backup sets table bulk force delete permanently deletes archived sets and their items', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$set = BackupSet::create([
'managed_environment_id' => $tenant->id,

View File

@ -4,15 +4,22 @@
use App\Models\OperationRun;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('policy versions table bulk force delete creates a run and skips non-archived records', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$tenant->forceFill(['is_current' => true])->save();
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create(['is_current' => true]);
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$policy = Policy::factory()->create(['managed_environment_id' => $tenant->id]);
$version = PolicyVersion::factory()->create([

View File

@ -4,14 +4,22 @@
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\RestoreRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('bulk force delete restore runs permanently deletes archived runs', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$backupSet = BackupSet::create([
'managed_environment_id' => $tenant->id,

View File

@ -2,6 +2,7 @@
use App\Livewire\BulkOperationProgress;
use App\Models\OperationRun;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
@ -10,7 +11,7 @@
test('progress widget shows running operations for current tenant and user', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$this->actingAs($user);
setAdminPanelContext($tenant);
Filament::setTenant($tenant, true);
// Active op
OperationRun::factory()->create([
@ -43,7 +44,7 @@
test('progress widget shows queued backup schedule runs as operation runs', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$this->actingAs($user);
setAdminPanelContext($tenant);
Filament::setTenant($tenant, true);
OperationRun::factory()->create([
'managed_environment_id' => $tenant->id,

View File

@ -4,14 +4,22 @@
use App\Models\OperationRun;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('bulk prune records skip reasons', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$policyA = Policy::factory()->create(['managed_environment_id' => $tenant->id]);
$current = PolicyVersion::factory()->create([

View File

@ -3,14 +3,22 @@
use App\Filament\Resources\PolicyVersionResource;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('bulk prune archives eligible policy versions', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$policy = Policy::factory()->create(['managed_environment_id' => $tenant->id]);

View File

@ -4,14 +4,22 @@
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('backup sets table bulk restore restores archived sets and their items', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$set = BackupSet::create([
'managed_environment_id' => $tenant->id,

View File

@ -4,15 +4,22 @@
use App\Models\OperationRun;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('policy versions table bulk restore creates a run and restores archived records', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$tenant->forceFill(['is_current' => true])->save();
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create(['is_current' => true]);
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$policy = Policy::factory()->create(['managed_environment_id' => $tenant->id]);
$version = PolicyVersion::factory()->create([

View File

@ -4,14 +4,22 @@
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\RestoreRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('restore runs table bulk restore creates a run and restores archived records', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$backupSet = BackupSet::create([
'managed_environment_id' => $tenant->id,

View File

@ -2,14 +2,22 @@
use App\Filament\Resources\PolicyResource;
use App\Models\Policy;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
test('bulk delete requires confirmation string for large batches', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$policies = Policy::factory()->count(20)->create(['managed_environment_id' => $tenant->id]);
Livewire::actingAs($user)
@ -23,8 +31,13 @@
});
test('bulk delete fails with incorrect confirmation string', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$policies = Policy::factory()->count(20)->create(['managed_environment_id' => $tenant->id]);
Livewire::actingAs($user)
@ -38,8 +51,13 @@
});
test('bulk delete does not require confirmation string for small batches', function () {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$tenant = ManagedEnvironment::factory()->create();
$user = User::factory()->create();
$user->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'owner'],
]);
Filament::setTenant($tenant, true);
$policies = Policy::factory()->count(10)->create(['managed_environment_id' => $tenant->id]);
Livewire::actingAs($user)

View File

@ -57,7 +57,8 @@
$this->get(TenantDashboard::getUrl(tenant: $tenant))
->assertOk();
setAdminPanelContext($tenant);
Filament::setCurrentPanel(Filament::getPanel('tenant'));
Filament::setTenant($tenant, true);
Livewire::test(RecoveryReadiness::class)
->assertSee('Backup posture')

View File

@ -64,11 +64,9 @@ function tenantDashboardButtonClassesForXPath(string $content, string $xpathExpr
it('builds the canonical operations follow-up baseline with tenant continuity', function (): void {
$tenant = ManagedEnvironment::factory()->create();
[, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
expect(OperationRunLinks::index($tenant, activeTab: 'active'))
->toBe(route('admin.operations.index', [
'workspace' => $tenant->workspace,
'managed_environment_id' => (int) $tenant->getKey(),
'activeTab' => 'active',
]))
@ -78,7 +76,6 @@ function tenantDashboardButtonClassesForXPath(string $content, string $xpathExpr
problemClass: OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP,
))
->toBe(route('admin.operations.index', [
'workspace' => $tenant->workspace,
'managed_environment_id' => (int) $tenant->getKey(),
'activeTab' => OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP,
'problemClass' => OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP,
@ -89,10 +86,9 @@ function tenantDashboardButtonClassesForXPath(string $content, string $xpathExpr
$tenant = ManagedEnvironment::factory()->create([
'external_id' => 'tenant-dashboard-productization',
]);
[, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
expect(RequiredPermissionsLinks::requiredPermissions($tenant, ['source' => 'tenant_dashboard']))
->toBe(url('/admin/workspaces/'.urlencode((string) $tenant->workspace->slug).'/environments/'.urlencode((string) $tenant->getRouteKey()).'/required-permissions?source=tenant_dashboard'));
->toBe('/admin/tenants/'.urlencode((string) $tenant->external_id).'/required-permissions?source=tenant_dashboard');
});
it('prioritizes operations requiring attention below permissions and high severity findings and keeps canonical hub links', function (): void {
@ -243,13 +239,13 @@ function tenantDashboardButtonClassesForXPath(string $content, string $xpathExpr
->and($actions[1]['actionUrl'])->toBe(FindingResource::getUrl('index', [
'tab' => 'needs_action',
'high_severity' => 1,
], panel: 'admin', tenant: $tenant))
->and($actions[2]['actionUrl'])->toBe(FindingExceptionResource::getUrl('index', panel: 'admin', tenant: $tenant));
], panel: 'tenant', tenant: $tenant))
->and($actions[2]['actionUrl'])->toBe(FindingExceptionResource::getUrl('index', panel: 'tenant', tenant: $tenant));
$this->actingAs($user);
setTenantPanelContext($tenant);
$content = $this->get(TenantDashboard::getUrl(panel: 'admin', tenant: $tenant))
$content = $this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
->assertSuccessful()
->getContent();

View File

@ -117,7 +117,7 @@ function tenantDashboardProductizationHeaderMoreActionNames(Testable $component)
$outsider = User::factory()->create();
$this->actingAs($outsider)
->get(TenantDashboard::getUrl(panel: 'admin', tenant: $tenant))
->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
->assertNotFound();
});
@ -127,7 +127,7 @@ function tenantDashboardProductizationHeaderMoreActionNames(Testable $component)
$this->actingAs($user);
setTenantPanelContext($tenant);
$this->get(TenantDashboard::getUrl(panel: 'admin', tenant: $tenant))
$this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
->assertSuccessful();
});
@ -261,7 +261,7 @@ function tenantDashboardProductizationHeaderMoreActionNames(Testable $component)
$this->actingAs($user);
setTenantPanelContext($tenant);
$content = $this->get(TenantDashboard::getUrl(panel: 'admin', tenant: $tenant))
$content = $this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
->assertSuccessful()
->getContent();
@ -313,7 +313,7 @@ function tenantDashboardProductizationHeaderMoreActionNames(Testable $component)
$this->actingAs($user);
setTenantPanelContext($tenant);
$content = $this->get(TenantDashboard::getUrl(panel: 'admin', tenant: $tenant))
$content = $this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
->assertSuccessful()
->getContent();
@ -383,5 +383,5 @@ function tenantDashboardProductizationHeaderMoreActionNames(Testable $component)
$this->get(FindingResource::getUrl('index', [
'tab' => 'needs_action',
'high_severity' => 1,
], panel: 'admin', tenant: $tenant))->assertForbidden();
], panel: 'tenant', tenant: $tenant))->assertForbidden();
});

View File

@ -109,7 +109,7 @@ function mockTenantDashboardReadinessPermissions(array $overview = []): void
expect($outputCard)
->not->toBeNull()
->and($outputCard['actionLabel'])->toBe('Open review pack')
->and($outputCard['actionUrl'])->toBe(ReviewPackResource::getUrl('view', ['record' => $pack], panel: 'admin', tenant: $tenant))
->and($outputCard['actionUrl'])->toBe(ReviewPackResource::getUrl('view', ['record' => $pack], panel: 'tenant', tenant: $tenant))
->and($outputCard['helperText'])->toBeNull();
});
@ -180,7 +180,7 @@ function mockTenantDashboardReadinessPermissions(array $overview = []): void
->and($currentReview['actionUrl'])->toBe(TenantReviewResource::tenantScopedUrl('view', ['record' => $review], $tenant))
->and($evidenceCoverage)
->not->toBeNull()
->and($evidenceCoverage['actionUrl'])->toBe(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], panel: 'admin', tenant: $tenant))
->and($evidenceCoverage['actionUrl'])->toBe(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], panel: 'tenant', tenant: $tenant))
->and($outputCard)
->not->toBeNull()
->and($outputCard['actionUrl'])->toBe(CustomerReviewWorkspace::tenantPrefilterUrl($tenant))
@ -264,7 +264,7 @@ function mockTenantDashboardReadinessPermissions(array $overview = []): void
$this->actingAs($user);
setTenantPanelContext($tenant);
$content = $this->get(TenantDashboard::getUrl(panel: 'admin', tenant: $tenant))
$content = $this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
->assertSuccessful()
->getContent();
@ -331,5 +331,5 @@ function mockTenantDashboardReadinessPermissions(array $overview = []): void
->not->toBeNull()
->and($evidenceCoverage['value'])->toBe('Unavailable')
->and($evidenceCoverage['description'])->toBe('No evidence snapshot is currently available for customer-safe output.')
->and($evidenceCoverage['actionUrl'])->toBe(EvidenceSnapshotResource::getUrl('index', panel: 'admin', tenant: $tenant));
->and($evidenceCoverage['actionUrl'])->toBe(EvidenceSnapshotResource::getUrl('index', panel: 'tenant', tenant: $tenant));
});

View File

@ -74,7 +74,7 @@ function mockTenantDashboardSummaryPermissions(array $overview = []): void
$this->actingAs($user);
setTenantPanelContext($tenant);
$response = $this->get(TenantDashboard::getUrl(panel: 'admin', tenant: $tenant))
$response = $this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
->assertSuccessful()
->assertSee($tenant->name)
->assertSee('Recommended next actions')
@ -106,7 +106,7 @@ function mockTenantDashboardSummaryPermissions(array $overview = []): void
->and($content)->toContain('data-testid="tenant-dashboard-context-chip-provider"')
->and($content)->toContain('data-testid="tenant-dashboard-context-chip-provider-microsoft-logo"')
->and($content)->toContain('data-provider-key="microsoft"')
->and($content)->toContain('Microsoft environment')
->and($content)->toContain('Microsoft tenant')
->and($content)->toContain('data-testid="tenant-dashboard-context-chip-latest-activity" class="inline-flex items-center gap-2 whitespace-nowrap')
->and($content)->toContain('data-testid="tenant-dashboard-context-chip-latest-activity-icon"')
->and($content)->toContain('Latest activity:')
@ -294,7 +294,7 @@ function mockTenantDashboardSummaryPermissions(array $overview = []): void
$this->actingAs($user);
setTenantPanelContext($tenant);
$response = $this->get(TenantDashboard::getUrl(panel: 'admin', tenant: $tenant))
$response = $this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
->assertSuccessful()
->assertSee('No immediate action is waiting.')
->assertDontSee('Recent operations')
@ -381,7 +381,7 @@ function mockTenantDashboardSummaryPermissions(array $overview = []): void
$this->actingAs($user);
setTenantPanelContext($tenant);
$this->get(TenantDashboard::getUrl(panel: 'admin', tenant: $tenant))
$this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
->assertSuccessful()
->assertSee('data-testid="tenant-dashboard-operations-attention-summary"', false)
->assertSee('Review operation')
@ -417,7 +417,7 @@ function mockTenantDashboardSummaryPermissions(array $overview = []): void
$this->actingAs($user);
setTenantPanelContext($tenant);
$this->get(TenantDashboard::getUrl(panel: 'admin', tenant: $tenant))
$this->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant))
->assertSuccessful()
->assertDontSee('data-testid="tenant-dashboard-operations-attention-summary"', false)
->assertDontSee('Review operation')

View File

@ -29,7 +29,6 @@
});
[$user, $tenant] = createUserWithTenant(role: 'owner', fixtureProfile: 'credential-enabled');
spec283SeedRequirementRows($tenant, ['permissions.directory_groups']);
$this->actingAs($user);
$tenant->makeCurrent();

View File

@ -124,14 +124,14 @@
->assertNotFound();
});
test('keeps Entra groups out of admin sidebar navigation after tenant-panel retirement', function () {
test('keeps Entra groups out of admin sidebar navigation while preserving tenant-panel navigation', function () {
Filament::setCurrentPanel(Filament::getPanel('admin'));
expect(EntraGroupResource::shouldRegisterNavigation())->toBeFalse();
Filament::setCurrentPanel(Filament::getPanel('admin'));
Filament::setCurrentPanel(Filament::getPanel('tenant'));
expect(EntraGroupResource::shouldRegisterNavigation())->toBeFalse();
expect(EntraGroupResource::shouldRegisterNavigation())->toBeTrue();
Filament::setCurrentPanel(null);
});

View File

@ -1,12 +1,10 @@
<?php
use App\Filament\Resources\PolicyVersionResource;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Graph\GraphClientInterface;
use App\Support\Workspaces\WorkspaceContext;
use function Pest\Laravel\mock;
@ -50,14 +48,10 @@
$this->actingAs($this->user);
$response = $this
->withSession([
WorkspaceContext::SESSION_KEY => (int) $this->tenant->workspace_id,
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
(string) $this->tenant->workspace_id => (int) $this->tenant->getKey(),
],
])
->get(PolicyVersionResource::getUrl('view', ['record' => $version], panel: 'admin', tenant: $this->tenant));
$response = $this->get(route('filament.tenant.resources.policy-versions.view', array_merge(
filamentTenantRouteParams($this->tenant),
['record' => $version],
)));
$response->assertOk();
});

View File

@ -21,7 +21,6 @@
});
[$user, $tenant] = createUserWithTenant(role: 'owner', fixtureProfile: 'credential-enabled');
spec283SeedRequirementRows($tenant, ['permissions.directory_groups']);
$this->actingAs($user);
$tenant->makeCurrent();

View File

@ -10,7 +10,6 @@
Queue::fake();
[$user, $tenant] = createUserWithTenant(role: 'owner', fixtureProfile: 'credential-enabled');
spec283SeedRequirementRows($tenant, ['permissions.directory_groups']);
$service = app(EntraGroupSyncService::class);

View File

@ -1,10 +1,8 @@
<?php
use App\Filament\Resources\FindingResource;
use App\Models\Finding;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Support\Workspaces\WorkspaceContext;
it('shows an explicit diff unavailable message when policy version references are missing', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
@ -40,13 +38,10 @@
]);
$response = $this->actingAs($user)
->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
(string) $tenant->workspace_id => (int) $tenant->getKey(),
],
])
->get(FindingResource::getUrl('view', ['record' => $finding], panel: 'admin', tenant: $tenant))
->get(route('filament.tenant.resources.findings.view', array_merge(
filamentTenantRouteParams($tenant),
['record' => $finding],
)))
->assertOk()
->assertSee('Diff unavailable')
->assertDontSee('No normalized changes were found');
@ -112,13 +107,10 @@
]);
$response = $this->actingAs($user)
->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
(string) $tenant->workspace_id => (int) $tenant->getKey(),
],
])
->get(FindingResource::getUrl('view', ['record' => $finding], panel: 'admin', tenant: $tenant))
->get(route('filament.tenant.resources.findings.view', array_merge(
filamentTenantRouteParams($tenant),
['record' => $finding],
)))
->assertOk()
->assertDontSee('Diff unavailable')
->assertSee('1 added')
@ -185,13 +177,10 @@
]);
$response = $this->actingAs($user)
->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY => [
(string) $tenant->workspace_id => (int) $tenant->getKey(),
],
])
->get(FindingResource::getUrl('view', ['record' => $finding], panel: 'admin', tenant: $tenant))
->get(route('filament.tenant.resources.findings.view', array_merge(
filamentTenantRouteParams($tenant),
['record' => $finding],
)))
->assertOk()
->assertDontSee('Diff unavailable')
->assertSee('1 removed')

View File

@ -85,7 +85,7 @@ function createAdminRolesReport(ManagedEnvironment $tenant, ?array $summaryOverr
'high_privilege_assignments' => 7,
]);
$expectedUrl = StoredReportResource::getUrl('view', ['record' => $report], panel: 'admin', tenant: $tenant);
$expectedUrl = StoredReportResource::getUrl('view', ['record' => $report], panel: 'tenant', tenant: $tenant);
Livewire::actingAs($user)
->test(AdminRolesSummaryWidget::class, ['record' => $tenant])

View File

@ -94,8 +94,8 @@
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenantA->workspace_id])
->get(route('admin.evidence.overview', ['managed_environment_id' => (int) $tenantB->getKey()]))
->assertOk()
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshots[(int) $tenantB->getKey()]], tenant: $tenantB, panel: 'admin'), false)
->assertDontSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshots[(int) $tenantA->getKey()]], tenant: $tenantA, panel: 'admin'), false);
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshots[(int) $tenantB->getKey()]], tenant: $tenantB, panel: 'tenant'), false)
->assertDontSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshots[(int) $tenantA->getKey()]], tenant: $tenantA, panel: 'tenant'), false);
});
it('shows stale evidence burden and a create-review next step on the overview', function (): void {
@ -121,8 +121,8 @@
->assertSee($freshTenant->name)
->assertSee('Refresh the stale evidence before relying on this snapshot')
->assertSee('Create a current review from this evidence snapshot')
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $staleSnapshot], tenant: $staleTenant, panel: 'admin'), false)
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $freshSnapshot], tenant: $freshTenant, panel: 'admin'), false);
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $staleSnapshot], tenant: $staleTenant, panel: 'tenant'), false)
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $freshSnapshot], tenant: $freshTenant, panel: 'tenant'), false);
});
it('seeds the native entitled-tenant prefilter once and clears it through the page action', function (): void {
@ -174,6 +174,6 @@
$this->get(route('admin.evidence.overview'))
->assertOk()
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshotA], tenant: $tenantA, panel: 'admin'), false)
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshotB], tenant: $tenantB, panel: 'admin'), false);
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshotA], tenant: $tenantA, panel: 'tenant'), false)
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshotB], tenant: $tenantB, panel: 'tenant'), false);
});

View File

@ -48,7 +48,7 @@
]);
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin').'?'.http_build_query([
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'tenant').'?'.http_build_query([
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
'review_id' => '123',
'tenant_filter_id' => (string) $tenant->getKey(),

View File

@ -95,7 +95,7 @@ function suspendEvidenceSnapshotWorkspace(ManagedEnvironment $tenant): void
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('index', tenant: $tenant, panel: 'admin'))
->get(EvidenceSnapshotResource::getUrl('index', tenant: $tenant, panel: 'tenant'))
->assertOk();
});
@ -111,7 +111,7 @@ function suspendEvidenceSnapshotWorkspace(ManagedEnvironment $tenant): void
[$user] = createUserWithTenant(role: 'owner');
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('index', tenant: $tenant, panel: 'admin'))
->get(EvidenceSnapshotResource::getUrl('index', tenant: $tenant, panel: 'tenant'))
->assertNotFound();
});
@ -122,7 +122,7 @@ function suspendEvidenceSnapshotWorkspace(ManagedEnvironment $tenant): void
Gate::define(Capabilities::EVIDENCE_VIEW, fn (): bool => false);
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('index', tenant: $tenant, panel: 'admin'))
->get(EvidenceSnapshotResource::getUrl('index', tenant: $tenant, panel: 'tenant'))
->assertForbidden();
});
@ -182,7 +182,7 @@ function suspendEvidenceSnapshotWorkspace(ManagedEnvironment $tenant): void
]);
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin'))
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'tenant'))
->assertOk()
->assertSee('Related context')
->assertSee('Review pack');
@ -245,7 +245,7 @@ function suspendEvidenceSnapshotWorkspace(ManagedEnvironment $tenant): void
suspendEvidenceSnapshotWorkspace($tenant);
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin'))
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'tenant'))
->assertOk();
$tenant->makeCurrent();
@ -277,7 +277,7 @@ function suspendEvidenceSnapshotWorkspace(ManagedEnvironment $tenant): void
]);
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin'))
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'tenant'))
->assertOk()
->assertSee('Outcome summary')
->assertDontSee('Artifact truth')
@ -303,13 +303,13 @@ function suspendEvidenceSnapshotWorkspace(ManagedEnvironment $tenant): void
$staleSnapshot = $this->makeStaleArtifactTruthEvidenceSnapshot($staleTenant);
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $freshSnapshot], tenant: $freshTenant, panel: 'admin'))
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $freshSnapshot], tenant: $freshTenant, panel: 'tenant'))
->assertOk()
->assertSee('No action needed')
->assertDontSee('Refresh the stale evidence before relying on this snapshot');
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $staleSnapshot], tenant: $staleTenant, panel: 'admin'))
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $staleSnapshot], tenant: $staleTenant, panel: 'tenant'))
->assertOk()
->assertSee('Refresh recommended')
->assertSee('Refresh the stale evidence before relying on this snapshot');
@ -412,7 +412,7 @@ function suspendEvidenceSnapshotWorkspace(ManagedEnvironment $tenant): void
]);
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin'))
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'tenant'))
->assertOk()
->assertSeeText('3 findings, 2 open.')
->assertSeeText('Open findings')
@ -455,7 +455,7 @@ function suspendEvidenceSnapshotWorkspace(ManagedEnvironment $tenant): void
]);
$this->actingAs($user)
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin').'?'.http_build_query([
->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'tenant').'?'.http_build_query([
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,
'review_id' => '456',
'tenant_filter_id' => (string) $tenant->getKey(),

View File

@ -2,10 +2,6 @@
declare(strict_types=1);
use App\Filament\Pages\InventoryCoverage;
use App\Filament\Resources\BackupSetResource;
use App\Filament\Resources\PolicyResource;
use App\Filament\Resources\PolicyVersionResource;
use App\Models\ManagedEnvironment;
use App\Models\ManagedEnvironmentMembership;
use App\Models\User;
@ -24,7 +20,7 @@
'/admin/inventory/inventory-coverage',
]);
it('keeps retired flat tenant-scoped admin surfaces unavailable when no tenant is selected', function (): void {
it('redirects tenant-scoped admin surfaces to choose-tenant when no tenant is selected', function (): void {
$user = User::factory()->create();
$workspace = Workspace::factory()->create();
@ -55,25 +51,25 @@
->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
->get('/admin/policies')
->assertNotFound();
->assertRedirect('/admin/choose-tenant');
$this
->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
->get('/admin/policy-versions')
->assertNotFound();
->assertRedirect('/admin/choose-tenant');
$this
->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
->get('/admin/backup-sets')
->assertNotFound();
->assertRedirect('/admin/choose-tenant');
$this
->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
->get('/admin/inventory')
->assertRedirect('/admin/workspaces/'.$workspace->getKey().'/environments');
->assertRedirect('/admin/choose-tenant');
});
it('allows tenant-scoped admin surfaces to load from the remembered canonical tenant', function (string $path): void {
@ -94,12 +90,7 @@
(string) $tenantA->workspace_id => (int) $tenantA->getKey(),
],
])
->get(match ($path) {
'/admin/policies' => PolicyResource::getUrl(panel: 'admin', tenant: $tenantA),
'/admin/policy-versions' => PolicyVersionResource::getUrl(panel: 'admin', tenant: $tenantA),
'/admin/backup-sets' => BackupSetResource::getUrl(panel: 'admin', tenant: $tenantA),
'/admin/inventory', '/admin/inventory/inventory-coverage' => InventoryCoverage::getUrl(panel: 'admin', tenant: $tenantA),
});
->get($path);
expect($response->getStatusCode())->toBeIn([200, 302]);
expect($response->headers->get('Location'))->not->toBe('/admin/choose-tenant');

View File

@ -7,7 +7,6 @@
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\OperationRunLinks;
use Carbon\CarbonImmutable;
use Filament\Facades\Filament;
@ -60,7 +59,7 @@
->assertSee('Timing')
->assertSee('Archive')
->assertSee('More')
->assertSee(OperationRunLinks::tenantlessView($run), false)
->assertSee('/admin/operations/'.$run->getKey(), false)
->assertDontSee('Related record')
->assertDontSee('>Completed</span>', false)
->assertSeeInOrder(['Nightly backup', 'Backup quality', 'Lifecycle overview', 'Related context', 'Technical detail']);
@ -141,7 +140,7 @@
$this->get(BackupSetResource::getUrl('view', [
'record' => $backupSet,
'backup_health_reason' => TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE,
], panel: 'admin', tenant: $tenant))
], panel: 'tenant', tenant: $tenant))
->assertOk()
->assertSee('Backup posture')
->assertSee('Latest backup is stale')
@ -175,7 +174,7 @@
$this->get(BackupSetResource::getUrl('view', [
'record' => $backupSet,
'backup_health_reason' => TenantBackupHealthAssessment::REASON_LATEST_BACKUP_DEGRADED,
], panel: 'admin', tenant: $tenant))
], panel: 'tenant', tenant: $tenant))
->assertOk()
->assertSee('Backup posture')
->assertSee('Latest backup is degraded')

View File

@ -17,7 +17,7 @@
$this->get(BackupSetResource::getUrl('index', [
'backup_health_reason' => TenantBackupHealthAssessment::REASON_NO_BACKUP_BASIS,
], panel: 'admin', tenant: $tenant))
], panel: 'tenant', tenant: $tenant))
->assertOk()
->assertSee('No usable completed backup basis is currently available for this tenant.')
->assertSee('No backup sets');
@ -31,7 +31,7 @@
$this->get(BackupSetResource::getUrl('index', [
'backup_health_reason' => TenantBackupHealthAssessment::REASON_LATEST_BACKUP_STALE,
], panel: 'admin', tenant: $tenant))
], panel: 'tenant', tenant: $tenant))
->assertOk()
->assertSee('The latest backup detail is no longer available, so this view stays on the backup-set list.');
});

View File

@ -5,7 +5,6 @@
use App\Filament\Resources\BackupSetResource;
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Support\OperationRunLinks;
use Filament\Facades\Filament;
it('links backup sets to their canonical operations context', function (): void {
@ -30,10 +29,10 @@
->assertOk()
->assertSee('Related context')
->assertSee('Operations')
->assertSee(OperationRunLinks::tenantlessView($run), false);
->assertSee('/admin/operations/'.$run->getKey(), false);
$this->get(BackupSetResource::getUrl('index', tenant: $tenant))
->assertOk()
->assertSee('Open operation')
->assertSee(OperationRunLinks::tenantlessView($run), false);
->assertSee('/admin/operations/'.$run->getKey(), false);
});

View File

@ -34,7 +34,7 @@ function getTableEmptyStateAction($component, string $name): ?\Filament\Actions\
[$user] = createUserWithTenant($otherTenant, role: 'owner');
$this->actingAs($user)
->get(BackupSetResource::getUrl('index', panel: 'admin', tenant: $tenant))
->get(BackupSetResource::getUrl('index', panel: 'tenant', tenant: $tenant))
->assertStatus(404);
});

View File

@ -65,7 +65,8 @@ function createCoverageBannerTenant(): array
],
]);
setAdminPanelContext($tenant);
Filament::setCurrentPanel(Filament::getPanel('tenant'));
Filament::setTenant($tenant, true);
Livewire::test(BaselineCompareCoverageBanner::class)
->assertSee('The last compare finished, but normal result output was suppressed.')
@ -87,7 +88,8 @@ function createCoverageBannerTenant(): array
'baseline_profile_id' => (int) $profile->getKey(),
]);
setAdminPanelContext($tenant);
Filament::setCurrentPanel(Filament::getPanel('tenant'));
Filament::setTenant($tenant, true);
Livewire::test(BaselineCompareCoverageBanner::class)
->assertSee('The current baseline snapshot is not available for compare.')
@ -120,7 +122,8 @@ function createCoverageBannerTenant(): array
],
]);
setAdminPanelContext($tenant);
Filament::setCurrentPanel(Filament::getPanel('tenant'));
Filament::setTenant($tenant, true);
Livewire::test(BaselineCompareCoverageBanner::class)
->assertDontSee('No confirmed drift in the latest baseline compare.')
@ -159,7 +162,8 @@ function createCoverageBannerTenant(): array
'due_at' => now()->subDay(),
]);
setAdminPanelContext($tenant);
Filament::setCurrentPanel(Filament::getPanel('tenant'));
Filament::setTenant($tenant, true);
Livewire::test(BaselineCompareCoverageBanner::class)
->assertSee('overdue finding')

View File

@ -160,6 +160,6 @@ function seedBaselineCompareLandingGapRun(\App\Models\ManagedEnvironment $tenant
seedBaselineCompareLandingGapRun($tenant);
$this->actingAs($nonMember)
->get(BaselineCompareLanding::getUrl(tenant: $tenant, panel: 'admin'))
->get(BaselineCompareLanding::getUrl(tenant: $tenant, panel: 'tenant'))
->assertNotFound();
});

View File

@ -28,7 +28,7 @@
it('redirects unauthenticated users (302)', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$this->get(BaselineCompareLanding::getUrl(tenant: $tenant, panel: 'admin'))
$this->get(BaselineCompareLanding::getUrl(tenant: $tenant, panel: 'tenant'))
->assertStatus(302);
});
@ -37,7 +37,7 @@
$nonMember = \App\Models\User::factory()->create();
$this->actingAs($nonMember)
->get(BaselineCompareLanding::getUrl(tenant: $tenant, panel: 'admin'))
->get(BaselineCompareLanding::getUrl(tenant: $tenant, panel: 'tenant'))
->assertNotFound();
});

View File

@ -6,7 +6,6 @@
use App\Filament\Pages\BaselineCompareMatrix;
use App\Filament\Resources\BaselineProfileResource;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\WorkspaceMembership;
use App\Support\Baselines\Compare\CompareStrategyRegistry;
@ -172,11 +171,6 @@
'user_id' => (int) $viewer->getKey(),
'role' => 'owner',
]);
createUserWithTenant(
tenant: ManagedEnvironment::factory()->create(['workspace_id' => (int) $fixture['workspace']->getKey()]),
user: $viewer,
role: 'owner',
);
$viewer->tenants()->syncWithoutDetaching([
(int) $fixture['visibleTenant']->getKey() => ['role' => 'owner'],
@ -268,13 +262,12 @@
$fixture = $this->makeBaselineCompareMatrixFixture();
$viewer = User::factory()->create();
$scopedTenant = ManagedEnvironment::factory()->create([
WorkspaceMembership::factory()->create([
'workspace_id' => (int) $fixture['workspace']->getKey(),
'status' => 'active',
'user_id' => (int) $viewer->getKey(),
'role' => 'owner',
]);
createUserWithTenant(tenant: $scopedTenant, user: $viewer, role: 'owner');
$session = $this->setAdminWorkspaceContext($viewer, $fixture['workspace']);
$this->withSession($session)

View File

@ -66,7 +66,8 @@ function createBaselineCompareWidgetTenant(): array
$this->actingAs($user);
setAdminPanelContext($tenant);
Filament::setCurrentPanel(Filament::getPanel('tenant'));
Filament::setTenant($tenant, true);
Livewire::test(BaselineCompareNow::class)
->assertSee('Baseline Governance')
@ -110,7 +111,8 @@ function createBaselineCompareWidgetTenant(): array
$this->actingAs($user);
setAdminPanelContext($tenant);
Filament::setCurrentPanel(Filament::getPanel('tenant'));
Filament::setTenant($tenant, true);
Livewire::test(BaselineCompareNow::class)
->assertSee('Needs review')
@ -139,7 +141,8 @@ function createBaselineCompareWidgetTenant(): array
$this->actingAs($user);
setAdminPanelContext($tenant);
Filament::setCurrentPanel(Filament::getPanel('tenant'));
Filament::setTenant($tenant, true);
Livewire::test(BaselineCompareNow::class)
->assertSee('Action required')
@ -165,7 +168,8 @@ function createBaselineCompareWidgetTenant(): array
$this->actingAs($user);
setAdminPanelContext($tenant);
Filament::setCurrentPanel(Filament::getPanel('tenant'));
Filament::setTenant($tenant, true);
Livewire::test(BaselineCompareNow::class)
->assertSee('In progress')
@ -191,7 +195,8 @@ function createBaselineCompareWidgetTenant(): array
$this->actingAs($user);
setAdminPanelContext($tenant);
Filament::setCurrentPanel(Filament::getPanel('tenant'));
Filament::setTenant($tenant, true);
Livewire::test(BaselineCompareNow::class)
->assertSee('Unavailable')

Some files were not shown because too many files have changed in this diff Show More