TenantAtlas/apps/platform/tests/Feature/Guards/ActionSurfaceContractTest.php
Ahmed Darrazi c125fd48fd
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 3m58s
feat(ui): implement diagnostic entry point consolidation
Applied diagnostic surface contract rules to Audit Log inspect modal and Support Diagnostics action context, consolidating raw diagnostic data into safe modals according to Spec 374.
2026-06-13 03:06:33 +02:00

2479 lines
105 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\BaselineCompareLanding;
use App\Filament\Pages\BaselineCompareMatrix;
use App\Filament\Pages\EnvironmentDashboard;
use App\Filament\Pages\EnvironmentDiagnostics;
use App\Filament\Pages\EnvironmentRequiredPermissions;
use App\Filament\Pages\InventoryCoverage;
use App\Filament\Pages\Monitoring\Alerts;
use App\Filament\Pages\Monitoring\AuditLog as AuditLogPage;
use App\Filament\Pages\Monitoring\EvidenceOverview;
use App\Filament\Pages\Monitoring\FindingExceptionsQueue;
use App\Filament\Pages\Monitoring\Operations;
use App\Filament\Pages\NoAccess;
use App\Filament\Pages\Operations\TenantlessOperationRunViewer;
use App\Filament\Pages\Reviews\ReviewRegister;
use App\Filament\Pages\Workspaces\ManagedEnvironmentOnboardingWizard;
use App\Filament\Resources\AlertDeliveryResource;
use App\Filament\Resources\AlertDeliveryResource\Pages\ListAlertDeliveries;
use App\Filament\Resources\AlertDestinationResource;
use App\Filament\Resources\AlertDestinationResource\Pages\ListAlertDestinations;
use App\Filament\Resources\AlertRuleResource;
use App\Filament\Resources\AlertRuleResource\Pages\ListAlertRules;
use App\Filament\Resources\BackupScheduleResource;
use App\Filament\Resources\BackupScheduleResource\Pages\EditBackupSchedule;
use App\Filament\Resources\BackupScheduleResource\Pages\ListBackupSchedules;
use App\Filament\Resources\BackupScheduleResource\RelationManagers\BackupScheduleOperationRunsRelationManager;
use App\Filament\Resources\BackupSetResource;
use App\Filament\Resources\BackupSetResource\Pages\EditBackupSet;
use App\Filament\Resources\BackupSetResource\RelationManagers\BackupItemsRelationManager;
use App\Filament\Resources\BaselineProfileResource;
use App\Filament\Resources\BaselineProfileResource\Pages\ListBaselineProfiles;
use App\Filament\Resources\BaselineSnapshotResource;
use App\Filament\Resources\EntraGroupResource;
use App\Filament\Resources\EnvironmentReviewResource;
use App\Filament\Resources\EnvironmentReviewResource\Pages\ListEnvironmentReviews;
use App\Filament\Resources\EvidenceSnapshotResource;
use App\Filament\Resources\EvidenceSnapshotResource\Pages\ListEvidenceSnapshots;
use App\Filament\Resources\FindingExceptionResource;
use App\Filament\Resources\FindingExceptionResource\Pages\ListFindingExceptions;
use App\Filament\Resources\FindingResource;
use App\Filament\Resources\FindingResource\Pages\ListFindings;
use App\Filament\Resources\InventoryItemResource;
use App\Filament\Resources\InventoryItemResource\Pages\ListInventoryItems;
use App\Filament\Resources\ManagedEnvironmentResource;
use App\Filament\Resources\ManagedEnvironmentResource\Pages\ListManagedEnvironments;
use App\Filament\Resources\ManagedEnvironmentResource\Pages\ManageEnvironmentAccessScopes;
use App\Filament\Resources\ManagedEnvironmentResource\Pages\ViewManagedEnvironment;
use App\Filament\Resources\ManagedEnvironmentResource\RelationManagers\ManagedEnvironmentMembershipsRelationManager;
use App\Filament\Resources\OperationRunResource;
use App\Filament\Resources\PolicyResource;
use App\Filament\Resources\PolicyResource\Pages\ListPolicies;
use App\Filament\Resources\PolicyResource\Pages\ViewPolicy;
use App\Filament\Resources\PolicyResource\RelationManagers\VersionsRelationManager;
use App\Filament\Resources\PolicyVersionResource;
use App\Filament\Resources\ProviderConnectionResource;
use App\Filament\Resources\ProviderConnectionResource\Pages\ListProviderConnections;
use App\Filament\Resources\ProviderConnectionResource\Pages\ViewProviderConnection;
use App\Filament\Resources\RestoreRunResource;
use App\Filament\Resources\RestoreRunResource\Pages\ListRestoreRuns;
use App\Filament\Resources\ReviewPackResource;
use App\Filament\Resources\ReviewPackResource\Pages\ListReviewPacks;
use App\Filament\Resources\Workspaces\Pages\ListWorkspaces;
use App\Filament\Resources\Workspaces\Pages\ViewWorkspace;
use App\Filament\Resources\Workspaces\RelationManagers\WorkspaceMembershipsRelationManager;
use App\Filament\Resources\Workspaces\WorkspaceResource;
use App\Filament\System\Pages\Directory\Tenants as SystemDirectoryTenantsPage;
use App\Filament\System\Pages\Directory\Workspaces as SystemDirectoryWorkspacesPage;
use App\Filament\System\Pages\Ops\Failures as SystemFailuresPage;
use App\Filament\System\Pages\Ops\Runs as SystemRunsPage;
use App\Filament\System\Pages\Ops\Stuck as SystemStuckPage;
use App\Filament\System\Pages\Security\AccessLogs as SystemAccessLogsPage;
use App\Jobs\SyncPoliciesJob;
use App\Models\AlertDelivery;
use App\Models\AlertDestination;
use App\Models\AlertRule;
use App\Models\AuditLog;
use App\Models\BackupItem;
use App\Models\BackupSchedule;
use App\Models\BackupSet;
use App\Models\BaselineProfile;
use App\Models\BaselineSnapshot;
use App\Models\BaselineTenantAssignment;
use App\Models\EvidenceSnapshot;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\InventoryItem;
use App\Models\ManagedEnvironment;
use App\Models\ManagedEnvironmentMembership;
use App\Models\OperationRun;
use App\Models\PlatformUser;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\ProviderConnection;
use App\Models\RestoreRun;
use App\Models\ReviewPack;
use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Support\Auth\PlatformCapabilities;
use App\Support\Evidence\EvidenceCompletenessState;
use App\Support\Evidence\EvidenceSnapshotStatus;
use App\Support\ManagedEnvironmentLinks;
use App\Support\Navigation\RelatedActionLabelCatalog;
use App\Support\OperationRunLinks;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\References\ReferenceClass;
use App\Support\References\ReferenceTypeLabelCatalog;
use App\Support\System\SystemDirectoryLinks;
use App\Support\System\SystemOperationRunLinks;
use App\Support\Ui\ActionSurface\ActionSurfaceExemptions;
use App\Support\Ui\ActionSurface\ActionSurfaceProfileDefinition;
use App\Support\Ui\ActionSurface\ActionSurfaceValidator;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfacePanelScope;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceType;
use App\Support\WorkspaceIsolation\TenantOwnedModelFamilies;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Actions\BulkActionGroup;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;
use Tests\Support\TestLaneManifest;
uses(RefreshDatabase::class);
it('keeps the retained action-surface contract family anchored in heavy-governance inventory', function (): void {
$inventoryRecord = collect(TestLaneManifest::heavyGovernanceHotspotInventory())
->firstWhere('familyId', 'action-surface-contract');
expect($inventoryRecord)->not->toBeNull()
->and($inventoryRecord['classificationId'])->toBe('surface-guard')
->and($inventoryRecord['status'])->toBe('retained');
});
function actionSurfaceSystemPanelContext(array $capabilities): PlatformUser
{
Filament::setCurrentPanel('system');
Filament::setTenant(null, true);
Filament::bootCurrentPanel();
$platformUser = PlatformUser::factory()->create([
'capabilities' => array_values(array_unique(array_merge([
PlatformCapabilities::ACCESS_SYSTEM_PANEL,
], $capabilities))),
'is_active' => true,
]);
test()->actingAs($platformUser, 'platform');
return $platformUser;
}
it('passes the action surface contract guard for current repository state', function (): void {
$result = ActionSurfaceValidator::withBaselineExemptions()->validate();
expect($result->hasIssues())->toBeFalse($result->formatForAssertion());
});
it('excludes widgets from action surface discovery scope', function (): void {
$classes = array_map(
static fn ($component): string => $component->className,
ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents(),
);
$widgetClasses = array_values(array_filter($classes, static function (string $className): bool {
return str_starts_with($className, 'App\\Filament\\Widgets\\');
}));
expect($widgetClasses)->toBeEmpty();
});
it('keeps baseline exemptions explicit and does not auto-exempt unknown classes', function (): void {
$exemptions = ActionSurfaceExemptions::baseline();
expect($exemptions->hasClass('App\\Filament\\Resources\\ActionSurfaceUnknownResource'))->toBeFalse();
});
it('maps managed-environment/admin panel scope metadata from discovery sources', function (): void {
$components = collect(ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents())
->keyBy('className');
$tenantResource = $components->get(\App\Filament\Resources\ManagedEnvironmentResource::class);
$policyResource = $components->get(\App\Filament\Resources\PolicyResource::class);
expect($tenantResource)->not->toBeNull();
expect($tenantResource?->hasPanelScope(ActionSurfacePanelScope::Admin))->toBeFalse();
expect($policyResource)->not->toBeNull();
expect($policyResource?->hasPanelScope(ActionSurfacePanelScope::ManagedEnvironment))->toBeTrue();
});
it('requires non-empty reasons for every baseline exemption', function (): void {
$reasons = ActionSurfaceExemptions::baseline()->all();
foreach ($reasons as $className => $reason) {
expect(trim($reason))->not->toBe('', "Baseline exemption reason is empty for {$className}");
}
});
it('discovers the baseline profile resource and validates its declaration', function (): void {
$components = collect(ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents())
->keyBy('className');
$baselineResource = $components->get(BaselineProfileResource::class);
expect($baselineResource)->not->toBeNull('BaselineProfileResource should be discovered by action surface discovery');
expect($baselineResource?->hasPanelScope(ActionSurfacePanelScope::Admin))->toBeTrue();
$declaration = BaselineProfileResource::actionSurfaceDeclaration();
$profiles = new ActionSurfaceProfileDefinition;
foreach ($profiles->requiredSlots($declaration->profile) as $slot) {
expect($declaration->slot($slot))
->not->toBeNull("Missing required slot {$slot->value} in BaselineProfileResource declaration");
}
});
it('discovers the baseline compare matrix page as an admin-scoped drilldown-only surface', function (): void {
$components = collect(ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents())
->keyBy('className');
$matrixPage = $components->get(\App\Filament\Pages\BaselineCompareMatrix::class);
expect($matrixPage)->not->toBeNull('BaselineCompareMatrix should be discovered by action surface discovery')
->and($matrixPage?->hasPanelScope(ActionSurfacePanelScope::Admin))->toBeTrue();
$declaration = \App\Filament\Pages\BaselineCompareMatrix::actionSurfaceDeclaration();
expect((string) ($declaration->slot(ActionSurfaceSlot::ListHeader)?->details ?? ''))
->toContain('compare fan-out')
->and((string) ($declaration->exemption(ActionSurfaceSlot::InspectAffordance)?->reason ?? ''))
->toContain('forbids row click');
});
it('keeps compare-assigned-tenants visibly disabled with helper text on the matrix for readonly members', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'readonly');
$profile = BaselineProfile::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
]);
$snapshot = BaselineSnapshot::factory()->complete()->create([
'workspace_id' => (int) $tenant->workspace_id,
'baseline_profile_id' => (int) $profile->getKey(),
]);
$profile->update(['active_snapshot_id' => (int) $snapshot->getKey()]);
BaselineTenantAssignment::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'baseline_profile_id' => (int) $profile->getKey(),
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
Filament::setTenant(null, true);
Livewire::actingAs($user)
->test(\App\Filament\Pages\BaselineCompareMatrix::class, ['record' => $profile->getKey()])
->assertActionVisible('backToBaselineProfile')
->assertActionVisible('compareAssignedTenants')
->assertActionDisabled('compareAssignedTenants')
->assertActionExists('compareAssignedTenants', fn (Action $action): bool => $action->getTooltip() === 'You need workspace baseline manage access to compare the visible assigned set.');
});
it('keeps BaselineProfile archive under the More menu and declares it in the action surface slots', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$profile = BaselineProfile::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
]);
$declaration = BaselineProfileResource::actionSurfaceDeclaration();
$details = $declaration->slot(ActionSurfaceSlot::ListRowMoreMenu)?->details;
expect($details)->toBeString();
expect($details)->toContain('archive');
$this->actingAs($user);
$livewire = Livewire::test(ListBaselineProfiles::class)
->assertCanSeeTableRecords([$profile]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
expect($moreGroup)->toBeInstanceOf(ActionGroup::class);
expect($moreGroup?->getLabel())->toBe('More');
$primaryRowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($primaryRowActionNames)->toBe([])
->and($table->getRecordUrl($profile))->toBe(BaselineProfileResource::getUrl('view', ['record' => $profile]));
$primaryRowActionCount = count($primaryRowActionNames);
expect($primaryRowActionCount)->toBeLessThanOrEqual(2);
$moreActionNames = collect($moreGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($moreActionNames)->toBe(['edit', 'archive']);
expect($table->getBulkActions())->toBeEmpty();
});
it('keeps backup schedules on clickable-row edit without duplicate Edit actions in More', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$schedule = BackupSchedule::query()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'name' => 'Nightly backup',
'is_enabled' => true,
'timezone' => 'UTC',
'frequency' => 'daily',
'time_of_day' => '01:00:00',
'days_of_week' => null,
'policy_types' => ['deviceConfiguration'],
'include_foundations' => true,
'retention_keep_last' => 30,
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(ListBackupSchedules::class)
->assertCanSeeTableRecords([$schedule]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
expect($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More');
$primaryRowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($primaryRowActionNames)->toBe([])
->and($table->getRecordUrl($schedule))->toBe(BackupScheduleResource::getUrl('edit', ['record' => $schedule]));
$moreActionNames = collect($moreGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($moreActionNames)->toBe(['runNow', 'retry', 'restore', 'archive', 'forceDelete'])
->and($moreActionNames)->not->toContain('edit');
$bulkActions = $table->getBulkActions();
$bulkGroup = collect($bulkActions)->first(static fn ($action): bool => $action instanceof BulkActionGroup);
expect($bulkGroup)->toBeInstanceOf(BulkActionGroup::class)
->and($bulkGroup?->getLabel())->toBe('More');
$bulkActionNames = collect($bulkGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($bulkActionNames)->toBe(['bulk_run_now', 'bulk_retry']);
});
it('uses clickable rows without extra row actions on backup schedule executions', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$schedule = BackupSchedule::query()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'name' => 'Nightly backup',
'is_enabled' => true,
'timezone' => 'UTC',
'frequency' => 'daily',
'time_of_day' => '01:00:00',
'days_of_week' => null,
'policy_types' => ['deviceConfiguration'],
'include_foundations' => true,
'retention_keep_last' => 30,
]);
$run = OperationRun::factory()->forTenant($tenant)->create([
'type' => 'backup_schedule_run',
'context' => ['backup_schedule_id' => (int) $schedule->getKey()],
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(BackupScheduleOperationRunsRelationManager::class, [
'ownerRecord' => $schedule,
'pageClass' => EditBackupSchedule::class,
])
->assertCanSeeTableRecords([$run]);
$table = $livewire->instance()->getTable();
expect($table->getActions())->toBeEmpty()
->and($table->getBulkActions())->toBeEmpty()
->and($table->getRecordUrl($run))->toBe(OperationRunLinks::view($run, $tenant));
});
it('uses clickable rows while keeping remove grouped under More on backup items', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$policy = Policy::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
]);
$version = PolicyVersion::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'policy_id' => (int) $policy->getKey(),
'metadata' => [],
]);
$backupSet = BackupSet::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
]);
$backupItem = BackupItem::factory()->for($backupSet)->for($tenant)->create([
'policy_id' => (int) $policy->getKey(),
'policy_version_id' => (int) $version->getKey(),
'metadata' => [],
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(BackupItemsRelationManager::class, [
'ownerRecord' => $backupSet,
'pageClass' => EditBackupSet::class,
])
->assertCanSeeTableRecords([$backupItem]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$primaryRowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$bulkActions = $table->getBulkActions();
$bulkGroup = collect($bulkActions)->first(static fn ($action): bool => $action instanceof BulkActionGroup);
$bulkActionNames = collect($bulkGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($primaryRowActionNames)->toBe([])
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toEqualCanonicalizing(['remove'])
->and($bulkGroup)->toBeInstanceOf(BulkActionGroup::class)
->and($bulkGroup?->getLabel())->toBe('More')
->and($bulkActionNames)->toEqualCanonicalizing(['bulk_remove'])
->and($table->getRecordUrl($backupItem))->toBe(PolicyVersionResource::getUrl('view', ['record' => $version], tenant: $tenant));
});
it('keeps tenant memberships inline without a separate inspect affordance', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$member = User::factory()->create();
$member->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'readonly'],
]);
$membership = ManagedEnvironmentMembership::query()
->where('managed_environment_id', (int) $tenant->getKey())
->where('user_id', (int) $member->getKey())
->firstOrFail();
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(ManagedEnvironmentMembershipsRelationManager::class, [
'ownerRecord' => $tenant,
'pageClass' => ViewManagedEnvironment::class,
])
->assertCanSeeTableRecords([$membership]);
$table = $livewire->instance()->getTable();
$rowActionNames = collect($table->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($rowActionNames)->toEqualCanonicalizing(['remove'])
->and($table->getBulkActions())->toBeEmpty()
->and($table->getRecordUrl($membership))->toBeNull();
});
it('keeps workspace memberships inline without a separate inspect affordance', function (): void {
$workspace = Workspace::factory()->create();
$owner = User::factory()->create();
WorkspaceMembership::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'user_id' => (int) $owner->getKey(),
'role' => 'owner',
]);
$member = User::factory()->create();
$membership = WorkspaceMembership::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'user_id' => (int) $member->getKey(),
'role' => 'readonly',
]);
$this->actingAs($owner);
$livewire = Livewire::test(WorkspaceMembershipsRelationManager::class, [
'ownerRecord' => $workspace,
'pageClass' => ViewWorkspace::class,
])
->assertCanSeeTableRecords([$membership]);
$table = $livewire->instance()->getTable();
$rowActionNames = collect($table->getActions())
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$moreGroup = collect($table->getActions())
->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions() ?? [])
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($rowActionNames)->toEqualCanonicalizing(['change_role'])
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toEqualCanonicalizing(['remove'])
->and($table->getBulkActions())->toBeEmpty()
->and($table->getRecordUrl($membership))->toBeNull();
});
it('renders the policy versions relation manager on the policy detail page', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$policy = Policy::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
]);
PolicyVersion::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'policy_id' => (int) $policy->getKey(),
'created_by' => 'versions-surface@example.test',
'metadata' => [],
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$this->get(PolicyResource::getUrl('view', ['record' => $policy], tenant: $tenant))
->assertOk()
->assertSee('Versions');
});
it('renders tenant memberships only on the dedicated memberships page', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$member = User::factory()->create([
'email' => 'tenant-members-surface@example.test',
]);
$member->tenants()->syncWithoutDetaching([
$tenant->getKey() => ['role' => 'readonly'],
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$membershipsUrl = ManagedEnvironmentResource::getUrl('memberships', ['record' => $tenant->getRouteKey()], panel: 'admin');
$this->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(ManagedEnvironmentResource::getUrl('view', ['record' => $tenant->getRouteKey()], panel: 'admin'))
->assertOk()
->assertDontSee('/admin/tenants', false)
->assertDontSee('/admin/t/', false)
->assertDontSeeLivewire(ManagedEnvironmentMembershipsRelationManager::class);
session([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]);
$membershipsPage = Livewire::actingAs($user)
->test(ManageEnvironmentAccessScopes::class, ['environment' => $tenant->getRouteKey()])
->assertActionVisible('back_to_overview')
->assertActionDoesNotExist('memberships')
->assertActionExists('back_to_overview', fn ($action): bool => $action->getLabel() === 'Back to environment overview'
&& $action->getUrl() === ManagedEnvironmentLinks::viewUrl($tenant));
expect($membershipsPage->instance()->getRelationManagers())
->toContain(ManagedEnvironmentMembershipsRelationManager::class);
$this->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
->get(ManagedEnvironmentResource::getUrl('memberships', ['record' => $tenant->getRouteKey()], panel: 'admin'))
->assertOk()
->assertSee('Manage environment access scope')
->assertSee('Workspace membership defines the role. Explicit environment scopes only narrow which workspace members can see this environment.')
->assertSee('Back to environment overview')
->assertDontSeeLivewire(\App\Filament\Widgets\ManagedEnvironment\RecentOperationsSummary::class)
->assertDontSeeLivewire(\App\Filament\Widgets\ManagedEnvironment\ManagedEnvironmentVerificationReport::class)
->assertDontSeeLivewire(\App\Filament\Widgets\ManagedEnvironment\AdminRolesSummaryWidget::class)
->assertSeeLivewire(ManagedEnvironmentMembershipsRelationManager::class);
});
it('keeps the tenant registry action surface on row inspect plus one safe dashboard shortcut for active tenants', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user);
Filament::setCurrentPanel('admin');
Filament::setTenant(null, true);
Filament::bootCurrentPanel();
session([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]);
$list = Livewire::actingAs($user)
->test(ListManagedEnvironments::class)
->assertTableActionVisible('openTenant', $tenant)
->assertTableActionHidden('related_onboarding', $tenant)
->assertTableActionHasUrl('openTenant', EnvironmentDashboard::getUrl(panel: 'admin', tenant: $tenant), $tenant);
expect($list->instance()->getTable()->getRecordUrl($tenant))
->toBe(ManagedEnvironmentResource::getUrl('view', ['record' => $tenant], panel: 'admin'));
});
it('keeps tenant detail header actions aligned with the shared administrative family while preserving workflow-heavy exceptions', function (): void {
$tenant = ManagedEnvironment::factory()->active()->create();
[$user, $tenant] = createMinimalUserWithTenant(
tenant: $tenant,
role: 'owner',
ensureDefaultMicrosoftProviderConnection: false,
);
ProviderConnection::factory()->platform()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'provider' => 'microsoft',
'is_default' => true,
'is_enabled' => true,
]);
$this->actingAs($user);
Filament::setCurrentPanel('admin');
Filament::setTenant(null, true);
Filament::bootCurrentPanel();
session([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]);
$listComponent = Livewire::actingAs($user)
->test(ListManagedEnvironments::class)
->assertTableActionVisible('admin_consent', $tenant)
->assertTableActionVisible('open_in_entra', $tenant)
->assertTableActionVisible('syncTenant', $tenant)
->assertTableActionVisible('verify', $tenant)
->assertTableActionVisible('setup_rbac', $tenant)
->assertTableActionVisible('archive', $tenant);
$markReviewedAction = $listComponent->instance()->getTable()->getAction('markReviewed');
$markFollowUpNeededAction = $listComponent->instance()->getTable()->getAction('markFollowUpNeeded');
$component = Livewire::actingAs($user)
->test(ViewManagedEnvironment::class, ['record' => $tenant->getRouteKey()])
->assertActionVisible('admin_consent')
->assertActionVisible('open_in_entra')
->assertActionVisible('syncTenant')
->assertActionVisible('verify')
->assertActionVisible('setup_rbac')
->assertActionVisible('memberships')
->assertActionVisible('refresh_rbac')
->assertActionVisible('archive');
$instance = $component->instance();
if ($instance->getCachedHeaderActions() === []) {
$instance->cacheInteractsWithHeaderActions();
}
$headerActions = $instance->getCachedHeaderActions();
$primaryHeaderActions = collect($headerActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null)
->filter()
->values()
->all();
$headerGroups = collect($headerActions)
->filter(static fn ($action): bool => $action instanceof ActionGroup && $action->isVisible())
->mapWithKeys(static function (ActionGroup $group): array {
$actionNames = collect($group->getActions())
->filter(static fn ($action): bool => ! method_exists($action, 'isVisible') || $action->isVisible())
->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null)
->filter()
->values()
->all();
return [(string) $group->getLabel() => $actionNames];
});
$visibleHeaderActionNames = $headerGroups
->flatMap(static fn (array $actionNames): array => $actionNames)
->values()
->all();
expect($markReviewedAction)->not->toBeNull()
->and($markReviewedAction?->getName())->toBe('markReviewed')
->and($markReviewedAction?->isConfirmationRequired())->toBeTrue()
->and($markFollowUpNeededAction)->not->toBeNull()
->and($markFollowUpNeededAction?->getName())->toBe('markFollowUpNeeded')
->and($markFollowUpNeededAction?->isConfirmationRequired())->toBeTrue()
->and($primaryHeaderActions)->toEqual(['memberships'])
->and(array_keys($headerGroups->all()))->toBe(['External links', 'Setup', 'Triage', 'Lifecycle'])
->and($headerGroups->get('External links'))->toEqualCanonicalizing(['admin_consent', 'open_in_entra'])
->and($headerGroups->get('Setup'))->toEqualCanonicalizing(['syncTenant', 'verify', 'setup_rbac', 'refresh_rbac'])
->and($headerGroups->get('Triage'))->toEqualCanonicalizing(['markReviewed', 'markFollowUpNeeded'])
->and($headerGroups->get('Lifecycle'))->toEqualCanonicalizing(['archive', 'remove_from_workspace'])
->and($visibleHeaderActionNames)->not->toContain('edit')
->and($visibleHeaderActionNames)->toContain('markReviewed')
->and($visibleHeaderActionNames)->toContain('markFollowUpNeeded')
->and($visibleHeaderActionNames)->not->toContain('forceDelete')
->and(collect(ManagedEnvironmentResource::tenantViewContextEntries($tenant))->pluck('key')->all())->toContain('tenant_edit');
});
it('renders the backup items relation manager on the backup set detail page', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$policy = Policy::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'display_name' => 'Backup Items Surface Policy',
]);
$backupSet = BackupSet::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
]);
BackupItem::factory()->for($backupSet)->for($tenant)->create([
'policy_id' => (int) $policy->getKey(),
'policy_version_id' => null,
'metadata' => [],
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$this->get(BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant))
->assertOk()
->assertSee('Items');
});
it('renders the workspace memberships relation manager on the workspace detail page', function (): void {
$workspace = Workspace::factory()->create();
$owner = User::factory()->create();
WorkspaceMembership::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'user_id' => (int) $owner->getKey(),
'role' => 'owner',
]);
$member = User::factory()->create([
'email' => 'workspace-members-surface@example.test',
]);
WorkspaceMembership::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'user_id' => (int) $member->getKey(),
'role' => 'readonly',
]);
$this->actingAs($owner);
$this->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
->get(WorkspaceResource::getUrl('view', ['record' => $workspace]))
->assertOk()
->assertSee('Memberships');
});
it('keeps inventory coverage as derived metadata without inspect or row action affordances', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$conditionalAccessKey = 'policy:conditionalAccessPolicy';
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(InventoryCoverage::class)
->assertCanSeeTableRecords([$conditionalAccessKey])
->assertTableEmptyStateActionsExistInOrder(['clear_filters']);
$table = $livewire->instance()->getTable();
$declaration = InventoryCoverage::actionSurfaceDeclaration();
expect($table->getActions())->toBeEmpty()
->and($table->getBulkActions())->toBeEmpty()
->and($table->getEmptyStateActions())->toHaveCount(1)
->and((string) ($declaration->exemption(ActionSurfaceSlot::InspectAffordance)?->reason ?? ''))
->toContain('runtime-derived metadata');
});
it('ensures representative declarations satisfy required slots', function (): void {
$profiles = new ActionSurfaceProfileDefinition;
$declarations = [
InventoryCoverage::class => InventoryCoverage::actionSurfaceDeclaration(),
NoAccess::class => NoAccess::actionSurfaceDeclaration(),
TenantlessOperationRunViewer::class => TenantlessOperationRunViewer::actionSurfaceDeclaration(),
EnvironmentDiagnostics::class => EnvironmentDiagnostics::actionSurfaceDeclaration(),
EnvironmentRequiredPermissions::class => EnvironmentRequiredPermissions::actionSurfaceDeclaration(),
AlertDeliveryResource::class => AlertDeliveryResource::actionSurfaceDeclaration(),
BackupScheduleResource::class => BackupScheduleResource::actionSurfaceDeclaration(),
BackupScheduleOperationRunsRelationManager::class => BackupScheduleOperationRunsRelationManager::actionSurfaceDeclaration(),
BackupSetResource::class => BackupSetResource::actionSurfaceDeclaration(),
BackupItemsRelationManager::class => BackupItemsRelationManager::actionSurfaceDeclaration(),
BaselineSnapshotResource::class => BaselineSnapshotResource::actionSurfaceDeclaration(),
EntraGroupResource::class => EntraGroupResource::actionSurfaceDeclaration(),
EvidenceSnapshotResource::class => EvidenceSnapshotResource::actionSurfaceDeclaration(),
FindingExceptionResource::class => FindingExceptionResource::actionSurfaceDeclaration(),
Operations::class => Operations::actionSurfaceDeclaration(),
PolicyResource::class => PolicyResource::actionSurfaceDeclaration(),
OperationRunResource::class => OperationRunResource::actionSurfaceDeclaration(),
ReviewPackResource::class => ReviewPackResource::actionSurfaceDeclaration(),
RestoreRunResource::class => RestoreRunResource::actionSurfaceDeclaration(),
ManagedEnvironmentMembershipsRelationManager::class => ManagedEnvironmentMembershipsRelationManager::actionSurfaceDeclaration(),
VersionsRelationManager::class => VersionsRelationManager::actionSurfaceDeclaration(),
BaselineProfileResource::class => BaselineProfileResource::actionSurfaceDeclaration(),
WorkspaceMembershipsRelationManager::class => WorkspaceMembershipsRelationManager::actionSurfaceDeclaration(),
WorkspaceResource::class => WorkspaceResource::actionSurfaceDeclaration(),
SystemRunsPage::class => SystemRunsPage::actionSurfaceDeclaration(),
SystemFailuresPage::class => SystemFailuresPage::actionSurfaceDeclaration(),
SystemStuckPage::class => SystemStuckPage::actionSurfaceDeclaration(),
SystemDirectoryTenantsPage::class => SystemDirectoryTenantsPage::actionSurfaceDeclaration(),
SystemDirectoryWorkspacesPage::class => SystemDirectoryWorkspacesPage::actionSurfaceDeclaration(),
SystemAccessLogsPage::class => SystemAccessLogsPage::actionSurfaceDeclaration(),
];
foreach ($declarations as $className => $declaration) {
foreach ($profiles->requiredSlots($declaration->profile) as $slot) {
expect($declaration->slot($slot))
->not->toBeNull("Missing required slot {$slot->value} in declaration for {$className}");
}
}
});
it('requires every first-slice tenant-owned resource to be discovered without relying on baseline action-surface exemptions', function (): void {
$components = collect(ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents())
->keyBy('className');
$baselineExemptions = ActionSurfaceExemptions::baseline();
foreach (TenantOwnedModelFamilies::firstSlice() as $familyName => $family) {
$resourceClass = $family['resource'];
expect($components->has($resourceClass))
->toBeTrue("{$familyName} resource should be discoverable by the action-surface validator.");
$hasDeclaration = method_exists($resourceClass, 'actionSurfaceDeclaration');
$hasBaselineExemption = $baselineExemptions->hasClass($resourceClass);
expect($hasDeclaration || $hasBaselineExemption)
->toBeTrue("{$familyName} resource must either define actionSurfaceDeclaration() or carry an explicit baseline exemption.");
if ($hasDeclaration) {
expect($hasBaselineExemption)
->toBeFalse("{$familyName} resource should not keep a stale baseline exemption once actionSurfaceDeclaration() exists.");
continue;
}
expect(trim((string) $baselineExemptions->reasonForClass($resourceClass)))
->not->toBe('', "{$familyName} resource baseline exemption reason must stay explicit.");
}
});
it('keeps first-slice tenant-owned action-surface exemptions registry-backed and explicit', function (): void {
$baselineExemptions = ActionSurfaceExemptions::baseline();
$registeredExemptions = TenantOwnedModelFamilies::actionSurfaceBaselineExemptions();
$declaredExemptions = collect(TenantOwnedModelFamilies::firstSlice())
->filter(static fn (array $family): bool => $family['action_surface'] === 'baseline_exemption')
->mapWithKeys(static fn (array $family): array => [$family['resource'] => $family['action_surface_reason']])
->all();
expect($registeredExemptions)->toBe($declaredExemptions);
foreach ($registeredExemptions as $className => $reason) {
expect($baselineExemptions->reasonForClass($className))
->toBe($reason);
}
foreach (TenantOwnedModelFamilies::firstSlice() as $familyName => $family) {
if ($family['action_surface'] !== 'baseline_exemption') {
continue;
}
expect(trim($family['action_surface_reason']))
->not->toBe('', "{$familyName} baseline exemption reason must stay explicit in the registry.");
}
});
it('keeps first-slice trusted-state page action-surface status explicit', function (): void {
$baselineExemptions = ActionSurfaceExemptions::baseline();
expect(method_exists(EnvironmentRequiredPermissions::class, 'actionSurfaceDeclaration'))->toBeTrue()
->and($baselineExemptions->hasClass(EnvironmentRequiredPermissions::class))->toBeFalse();
expect(method_exists(Alerts::class, 'actionSurfaceDeclaration'))->toBeTrue()
->and($baselineExemptions->hasClass(Alerts::class))->toBeFalse();
expect($baselineExemptions->hasClass(ManagedEnvironmentOnboardingWizard::class))->toBeTrue()
->and((string) $baselineExemptions->reasonForClass(ManagedEnvironmentOnboardingWizard::class))->toContain('dedicated conformance tests')
->toContain('spec 172');
expect(method_exists(\App\Filament\System\Pages\Ops\Runbooks::class, 'actionSurfaceDeclaration'))->toBeFalse()
->and($baselineExemptions->hasClass(\App\Filament\System\Pages\Ops\Runbooks::class))->toBeFalse();
expect(method_exists(\App\Filament\System\Pages\RepairWorkspaceOwners::class, 'actionSurfaceDeclaration'))->toBeFalse()
->and($baselineExemptions->hasClass(\App\Filament\System\Pages\RepairWorkspaceOwners::class))->toBeFalse();
});
it('keeps spec 172 retrofit surfaces covered without broad baseline exemptions', function (): void {
$baselineExemptions = ActionSurfaceExemptions::baseline();
expect($baselineExemptions->hasClass(ViewManagedEnvironment::class))->toBeFalse()
->and($baselineExemptions->hasClass(ManagedEnvironmentOnboardingWizard::class))->toBeTrue()
->and((string) $baselineExemptions->reasonForClass(ManagedEnvironmentOnboardingWizard::class))
->toContain('spec 172')
->toContain('OnboardingVerificationTest')
->toContain('OnboardingVerificationClustersTest')
->toContain('OnboardingVerificationV1_5UxTest');
});
it('documents the spec 195 residual inventory, human-readable names, and baseline alignment', function (): void {
$inventory = ActionSurfaceExemptions::spec195ResidualSurfaceInventory();
$baselineExemptions = ActionSurfaceExemptions::baseline()->all();
expect(array_keys($inventory))->toEqualCanonicalizing([
\App\Filament\System\Pages\Dashboard::class,
\App\Filament\System\Pages\Ops\ViewRun::class,
\App\Filament\System\Pages\Ops\Runbooks::class,
\App\Filament\System\Pages\Ops\Controls::class,
\App\Filament\System\Pages\RepairWorkspaceOwners::class,
\App\Filament\System\Pages\Directory\ViewTenant::class,
\App\Filament\System\Pages\Directory\ViewWorkspace::class,
\App\Filament\Pages\BreakGlassRecovery::class,
\App\Filament\Pages\ChooseWorkspace::class,
\App\Filament\Pages\ChooseEnvironment::class,
\App\Filament\Pages\Tenancy\RegisterTenant::class,
\App\Filament\Pages\Workspaces\ManagedEnvironmentOnboardingWizard::class,
\App\Filament\Pages\Workspaces\ManagedEnvironmentsLanding::class,
\App\Filament\Pages\EnvironmentDashboard::class,
]);
foreach ($inventory as $className => $surface) {
expect(trim((string) ($surface['surfaceName'] ?? '')))
->not->toBe('', "{$className} must keep a human-readable surfaceName in Spec 195.")
->and($surface['pageClass'] ?? null)->toBe($className)
->and($surface['evidence'] ?? [])->not->toBeEmpty("{$className} must keep structured Spec 195 evidence.");
}
$mustRemainBaselineExempt = collect($inventory)
->filter(fn (array $surface): bool => ($surface['mustRemainBaselineExempt'] ?? false) === true)
->keys()
->values()
->all();
$mustNotRemainBaselineExempt = collect($inventory)
->filter(fn (array $surface): bool => ($surface['mustNotRemainBaselineExempt'] ?? false) === true)
->keys()
->values()
->all();
expect($mustRemainBaselineExempt)->toEqualCanonicalizing([
\App\Filament\Pages\ChooseWorkspace::class,
\App\Filament\Pages\ChooseEnvironment::class,
\App\Filament\Pages\Tenancy\RegisterTenant::class,
\App\Filament\Pages\Workspaces\ManagedEnvironmentOnboardingWizard::class,
\App\Filament\Pages\Workspaces\ManagedEnvironmentsLanding::class,
\App\Filament\Pages\EnvironmentDashboard::class,
]);
foreach ($mustRemainBaselineExempt as $className) {
expect(array_key_exists($className, $baselineExemptions))
->toBeTrue("{$className} should stay aligned between baseline() and Spec 195.");
}
foreach ($mustNotRemainBaselineExempt as $className) {
expect(array_key_exists($className, $baselineExemptions))
->toBeFalse("{$className} must not keep a stale baseline exemption under Spec 195.");
}
});
it('keeps enrolled system panel pages declaration-backed without stale baseline exemptions', function (): void {
$baselineExemptions = ActionSurfaceExemptions::baseline();
foreach ([
SystemRunsPage::class,
SystemFailuresPage::class,
SystemStuckPage::class,
SystemDirectoryTenantsPage::class,
SystemDirectoryWorkspacesPage::class,
SystemAccessLogsPage::class,
] as $className) {
expect(method_exists($className, 'actionSurfaceDeclaration'))
->toBeTrue("{$className} should declare its action surface once enrolled.");
expect($baselineExemptions->hasClass($className))
->toBeFalse("{$className} should not keep a stale baseline exemption after enrollment.");
}
});
it('discovers only the enrolled system table pages in the primary validator pass', function (): void {
$components = collect(ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents())
->keyBy('className');
foreach ([
SystemRunsPage::class,
SystemFailuresPage::class,
SystemStuckPage::class,
SystemDirectoryTenantsPage::class,
SystemDirectoryWorkspacesPage::class,
SystemAccessLogsPage::class,
] as $className) {
expect($components->has($className))
->toBeTrue("{$className} should be discovered by the primary validator.");
}
foreach ([
\App\Filament\System\Pages\Ops\Runbooks::class,
\App\Filament\System\Pages\RepairWorkspaceOwners::class,
] as $className) {
expect($components->has($className))
->toBeFalse("{$className} should stay out of the primary validator discovery scope.");
}
});
it('keeps residual system pages outside primary discovery but inside the spec 195 closure inventory', function (): void {
$components = collect(ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents())
->keyBy('className');
foreach ([
\App\Filament\System\Pages\Dashboard::class,
\App\Filament\System\Pages\Ops\ViewRun::class,
\App\Filament\System\Pages\Ops\Runbooks::class,
\App\Filament\System\Pages\RepairWorkspaceOwners::class,
\App\Filament\System\Pages\Directory\ViewTenant::class,
\App\Filament\System\Pages\Directory\ViewWorkspace::class,
] as $className) {
expect($components->has($className))
->toBeFalse("{$className} should stay outside the primary declaration-backed discovery scope.")
->and(ActionSurfaceExemptions::spec195ResidualSurface($className))
->not->toBeNull("{$className} must still carry an explicit Spec 195 closure entry.");
}
});
it('reports actionable file context when a residual surface is missing from the spec 195 inventory', function (): void {
$inventory = ActionSurfaceExemptions::spec195ResidualSurfaceInventory();
unset($inventory[\App\Filament\System\Pages\Dashboard::class]);
$issues = ActionSurfaceValidator::validateSpec195ResidualInventoryFixture(
inventory: $inventory,
discoveredClasses: array_map(
static fn ($component): string => $component->className,
ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents(),
),
baselineExemptions: ActionSurfaceExemptions::baseline()->all(),
residualCandidateClasses: [\App\Filament\System\Pages\Dashboard::class],
);
$formattedIssues = implode("\n", array_map(
static fn ($issue): string => $issue->format(),
$issues,
));
expect($formattedIssues)
->toContain(\App\Filament\System\Pages\Dashboard::class)
->toContain('Residual action surface is missing a Spec 195 closure entry')
->toContain('Dashboard.php');
});
it('keeps enrolled relation managers declaration-backed without stale baseline exemptions', function (): void {
$baselineExemptions = ActionSurfaceExemptions::baseline();
foreach ([
BackupItemsRelationManager::class,
ManagedEnvironmentMembershipsRelationManager::class,
WorkspaceMembershipsRelationManager::class,
] as $className) {
expect(method_exists($className, 'actionSurfaceDeclaration'))
->toBeTrue("{$className} should declare its action surface once enrolled.");
expect($baselineExemptions->hasClass($className))
->toBeFalse("{$className} should not keep a stale baseline exemption after enrollment.");
}
});
it('keeps enrolled monitoring pages declaration-backed without stale baseline exemptions', function (): void {
$baselineExemptions = ActionSurfaceExemptions::baseline();
foreach ([
Operations::class,
] as $className) {
expect(method_exists($className, 'actionSurfaceDeclaration'))
->toBeTrue("{$className} should declare its action surface once enrolled.");
expect($baselineExemptions->hasClass($className))
->toBeFalse("{$className} should not keep a stale baseline exemption after enrollment.");
}
});
it('keeps enrolled tenant table pages declaration-backed without stale baseline exemptions', function (): void {
$baselineExemptions = ActionSurfaceExemptions::baseline();
foreach ([
InventoryCoverage::class,
] as $className) {
expect(method_exists($className, 'actionSurfaceDeclaration'))
->toBeTrue("{$className} should declare its action surface once enrolled.");
expect($baselineExemptions->hasClass($className))
->toBeFalse("{$className} should not keep a stale baseline exemption after enrollment.");
}
});
it('keeps enrolled canonical detail pages declaration-backed without stale baseline exemptions', function (): void {
$baselineExemptions = ActionSurfaceExemptions::baseline();
foreach ([
TenantlessOperationRunViewer::class,
] as $className) {
expect(method_exists($className, 'actionSurfaceDeclaration'))
->toBeTrue("{$className} should declare its action surface once enrolled.");
expect($baselineExemptions->hasClass($className))
->toBeFalse("{$className} should not keep a stale baseline exemption after enrollment.");
}
});
it('keeps enrolled singleton tenant pages declaration-backed without stale baseline exemptions', function (): void {
$baselineExemptions = ActionSurfaceExemptions::baseline();
foreach ([
NoAccess::class,
EnvironmentDiagnostics::class,
] as $className) {
expect(method_exists($className, 'actionSurfaceDeclaration'))
->toBeTrue("{$className} should declare its action surface once enrolled.");
expect($baselineExemptions->hasClass($className))
->toBeFalse("{$className} should not keep a stale baseline exemption after enrollment.");
}
});
it('keeps enrolled guided workspace diagnostic pages declaration-backed without stale baseline exemptions', function (): void {
$baselineExemptions = ActionSurfaceExemptions::baseline();
foreach ([
EnvironmentRequiredPermissions::class,
] as $className) {
expect(method_exists($className, 'actionSurfaceDeclaration'))
->toBeTrue("{$className} should declare its action surface once enrolled.");
expect($baselineExemptions->hasClass($className))
->toBeFalse("{$className} should not keep a stale baseline exemption after enrollment.");
}
});
it('keeps finding exception v1 list exemptions explicit and omits grouped or bulk mutations', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$declaration = FindingExceptionResource::actionSurfaceDeclaration();
expect((string) ($declaration->exemption(ActionSurfaceSlot::ListRowMoreMenu)?->reason ?? ''))
->toContain('avoids a More menu');
expect((string) ($declaration->exemption(ActionSurfaceSlot::ListBulkMoreGroup)?->reason ?? ''))
->toContain('omit bulk actions');
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(ListFindingExceptions::class)
->assertTableEmptyStateActionsExistInOrder(['open_findings']);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
expect(collect($rowActions)->contains(static fn ($action): bool => $action instanceof ActionGroup))->toBeFalse();
expect(collect($rowActions)->map(static fn ($action): ?string => $action->getName())->filter()->values()->all())
->toEqualCanonicalizing(['renew_exception', 'revoke_exception']);
expect($table->getBulkActions())->toBeEmpty();
});
it('documents the guided alert delivery empty state without introducing a list-header CTA', function (): void {
$declaration = AlertDeliveryResource::actionSurfaceDeclaration();
expect((string) ($declaration->slot(ActionSurfaceSlot::ListEmptyState)?->details ?? ''))
->toContain('View alert rules');
});
it('uses More grouping conventions and exposes empty-state CTA on representative CRUD list', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(ListPolicies::class)
->assertTableEmptyStateActionsExistInOrder(['sync']);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$rowGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
expect($rowGroup)->toBeInstanceOf(ActionGroup::class);
expect($rowGroup?->getLabel())->toBe('More');
$primaryRowActionCount = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->count();
expect($primaryRowActionCount)->toBeLessThanOrEqual(2);
$bulkActions = $table->getBulkActions();
$bulkGroup = collect($bulkActions)->first(static fn ($action): bool => $action instanceof BulkActionGroup);
expect($bulkGroup)->toBeInstanceOf(BulkActionGroup::class);
expect($bulkGroup?->getLabel())->toBe('More');
});
it('keeps evidence snapshots on the declared clickable-row, two-action surface', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
Livewire::test(ListEvidenceSnapshots::class)
->assertTableEmptyStateActionsExistInOrder(['create_first_snapshot']);
$snapshot = EvidenceSnapshot::query()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'status' => EvidenceSnapshotStatus::Active->value,
'completeness_state' => EvidenceCompletenessState::Complete->value,
'summary' => ['finding_count' => 1, 'missing_dimensions' => 0],
'generated_at' => now(),
]);
$livewire = Livewire::test(ListEvidenceSnapshots::class)
->assertCanSeeTableRecords([$snapshot]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$primaryRowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($primaryRowActionNames)->toBe([])
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toEqualCanonicalizing(['expire'])
->and($table->getBulkActions())->toBeEmpty()
->and($table->getRecordUrl($snapshot))->toBe(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot]));
});
it('uses clickable rows without a duplicate View action on the environment reviews list', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$review = composeEnvironmentReviewForTest($tenant, $user);
$this->actingAs($user);
setAdminEnvironmentContext($tenant);
$livewire = Livewire::test(ListEnvironmentReviews::class)
->assertCanSeeTableRecords([$review]);
$table = $livewire->instance()->getTable();
$rowActionNames = collect($table->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($rowActionNames)->toEqualCanonicalizing(['export_executive_pack'])
->and($table->getBulkActions())->toBeEmpty()
->and($table->getRecordUrl($review))->toBe(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant));
});
it('uses clickable rows while keeping download direct and grouping expire under More on the review packs list', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$pack = ReviewPack::factory()->ready()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'initiated_by_user_id' => (int) $user->getKey(),
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(ListReviewPacks::class)
->assertCanSeeTableRecords([$pack]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$primaryRowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($primaryRowActionNames)->toBe(['download'])
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toEqualCanonicalizing(['expire'])
->and($table->getBulkActions())->toBeEmpty()
->and($table->getRecordUrl($pack))->toBe(ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant));
});
it('uses clickable rows while grouping restore-run maintenance actions under More', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$backupSet = BackupSet::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'status' => 'completed',
]);
$restoreRun = RestoreRun::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'backup_set_id' => (int) $backupSet->getKey(),
'status' => 'completed',
'deleted_at' => null,
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(ListRestoreRuns::class)
->assertCanSeeTableRecords([$restoreRun]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$primaryRowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$bulkActions = $table->getBulkActions();
$bulkGroup = collect($bulkActions)->first(static fn ($action): bool => $action instanceof BulkActionGroup);
$bulkActionNames = collect($bulkGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($primaryRowActionNames)->toBe([])
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toEqualCanonicalizing(['rerun', 'restore', 'archive', 'forceDelete'])
->and($bulkGroup)->toBeInstanceOf(BulkActionGroup::class)
->and($bulkGroup?->getLabel())->toBe('More')
->and($bulkActionNames)->toEqualCanonicalizing(['bulk_delete', 'bulk_restore', 'bulk_force_delete'])
->and($table->getRecordUrl($restoreRun))->toBe(RestoreRunResource::getUrl('view', ['record' => $restoreRun], tenant: $tenant));
});
it('keeps findings on clickable-row inspection with a single related drill-down and grouped workflow actions', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$finding = Finding::factory()->for($tenant)->create([
'status' => Finding::STATUS_NEW,
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(ListFindings::class)
->assertCanSeeTableRecords([$finding]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$primaryRowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$bulkActions = $table->getBulkActions();
$bulkGroup = collect($bulkActions)->first(static fn ($action): bool => $action instanceof BulkActionGroup);
$bulkActionNames = collect($bulkGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($primaryRowActionNames)->toEqualCanonicalizing(['primary_drill_down'])
->and($table->getRecordUrl($finding))->toBe(FindingResource::getUrl('view', ['record' => $finding]))
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toEqualCanonicalizing([
'triage',
'start_progress',
'assign',
'resolve',
'close',
'request_exception',
'renew_exception',
'revoke_exception',
'reopen',
])
->and($bulkGroup)->toBeInstanceOf(BulkActionGroup::class)
->and($bulkGroup?->getLabel())->toBe('More')
->and($bulkActionNames)->toEqualCanonicalizing([
'triage_selected',
'assign_selected',
'resolve_selected',
'close_selected',
]);
});
it('uses clickable rows with restore as the only inline shortcut on the policy versions relation manager', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$policy = Policy::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
]);
$version = PolicyVersion::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'policy_id' => (int) $policy->getKey(),
'metadata' => [],
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(VersionsRelationManager::class, [
'ownerRecord' => $policy,
'pageClass' => ViewPolicy::class,
])
->assertCanSeeTableRecords([$version]);
$table = $livewire->instance()->getTable();
$rowActionNames = collect($table->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($rowActionNames)->toEqualCanonicalizing(['restore_to_intune'])
->and($table->getBulkActions())->toBeEmpty()
->and($table->getRecordUrl($version))->toBe(PolicyVersionResource::getUrl('view', ['record' => $version], panel: 'admin'));
});
it('uses canonical tenantless View run links on representative operation links', function (): void {
$tenant = ManagedEnvironment::factory()->create();
$run = OperationRun::factory()->create([
'managed_environment_id' => $tenant->getKey(),
'workspace_id' => $tenant->workspace_id,
]);
expect(OperationRunLinks::view($run, $tenant))
->toBe(route('admin.operations.view', [
'workspace' => (int) $tenant->workspace_id,
'run' => (int) $run->getKey(),
]));
});
it('keeps canonical operation labels on shared representative links', function (): void {
$labels = app(RelatedActionLabelCatalog::class);
$typeLabels = new ReferenceTypeLabelCatalog;
expect($labels->entryLabel('source_run'))->toBe('Operation')
->and($labels->actionLabel('source_run'))->toBe('Open operation')
->and($typeLabels->label(ReferenceClass::OperationRun))->toBe('Operation');
});
it('uses clickable rows without a lone View action on the monitoring operations list', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$run = OperationRun::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'type' => 'policy.sync',
'status' => 'queued',
'outcome' => 'pending',
]);
$this->actingAs($user);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
setAdminPanelContext();
$livewire = Livewire::test(Operations::class)
->assertCanSeeTableRecords([$run]);
$table = $livewire->instance()->getTable();
$operationsDeclaration = Operations::actionSurfaceDeclaration();
$operationRunDeclaration = OperationRunResource::actionSurfaceDeclaration();
expect($operationsDeclaration->surfaceType)->toBe(ActionSurfaceType::ReadOnlyRegistryReport)
->and($operationRunDeclaration->surfaceType)->toBe(ActionSurfaceType::ReadOnlyRegistryReport)
->and($operationsDeclaration->slot(ActionSurfaceSlot::InspectAffordance)?->details)->toBe(ActionSurfaceInspectAffordance::ClickableRow->value)
->and($operationRunDeclaration->slot(ActionSurfaceSlot::InspectAffordance)?->details)->toBe(ActionSurfaceInspectAffordance::ClickableRow->value)
->and($table->getActions())->toBeEmpty()
->and($table->getRecordUrl($run))->toBe(OperationRunLinks::tenantlessView($run));
});
it('keeps review and evidence references on clickable-row open without duplicate inspect actions', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$review = composeEnvironmentReviewForTest($tenant, $user)->load('evidenceSnapshot');
$snapshot = $review->evidenceSnapshot;
$this->actingAs($user);
setAdminPanelContext();
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
$reviewComponent = Livewire::actingAs($user)
->test(ReviewRegister::class)
->assertCanSeeTableRecords([$review]);
$reviewTable = $reviewComponent->instance()->getTable();
$reviewActionNames = collect($reviewTable->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$evidenceComponent = Livewire::actingAs($user)->test(EvidenceOverview::class);
$evidenceRows = collect($evidenceComponent->instance()->rows);
$evidenceRow = $evidenceRows->firstWhere('snapshot_id', (int) $snapshot->getKey());
expect(ReviewRegister::actionSurfaceDeclaration()->surfaceType)->toBe(ActionSurfaceType::ReadOnlyRegistryReport)
->and(ReviewRegister::actionSurfaceDeclaration()->slot(ActionSurfaceSlot::InspectAffordance)?->details)->toBe(ActionSurfaceInspectAffordance::ClickableRow->value)
->and($reviewActionNames)->not->toContain('view_review')
->and($reviewActionNames)->toContain('export_executive_pack')
->and($reviewTable->getRecordUrl($review))->toBe(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $review->tenant))
->and(EvidenceOverview::actionSurfaceDeclaration()->surfaceType)->toBe(ActionSurfaceType::ReadOnlyRegistryReport)
->and(EvidenceOverview::actionSurfaceDeclaration()->slot(ActionSurfaceSlot::InspectAffordance)?->details)->toBe(ActionSurfaceInspectAffordance::ClickableRow->value)
->and($snapshot)->toBeInstanceOf(EvidenceSnapshot::class)
->and(is_array($evidenceRow))->toBeTrue()
->and($evidenceRow['view_url'] ?? null)->toBe(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin'));
});
it('keeps audit and queue references on explicit inspect without row-click navigation', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner', workspaceRole: 'manager');
$audit = AuditLog::query()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'actor_email' => 'owner@example.com',
'actor_name' => 'Owner',
'actor_type' => 'human',
'action' => 'workspace.selected',
'status' => 'success',
'resource_type' => 'workspace',
'resource_id' => (string) $tenant->workspace_id,
'target_label' => 'Workspace '.$tenant->workspace_id,
'summary' => 'Workspace selected for Workspace '.$tenant->workspace_id,
'metadata' => ['reason' => 'guard-test'],
'recorded_at' => now(),
]);
$finding = Finding::factory()->for($tenant)->create();
$exception = FindingException::query()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'finding_id' => (int) $finding->getKey(),
'requested_by_user_id' => (int) $user->getKey(),
'owner_user_id' => (int) $user->getKey(),
'status' => FindingException::STATUS_PENDING,
'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT,
'request_reason' => 'Guard test exception review',
'requested_at' => now()->subDay(),
'review_due_at' => now()->addDay(),
'evidence_summary' => ['reference_count' => 0],
]);
$this->actingAs($user);
setAdminPanelContext();
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
$auditComponent = Livewire::actingAs($user)
->test(AuditLogPage::class)
->assertCanSeeTableRecords([$audit]);
$auditTable = $auditComponent->instance()->getTable();
$auditActionNames = collect($auditTable->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$queueComponent = Livewire::actingAs($user)
->test(FindingExceptionsQueue::class)
->assertCanSeeTableRecords([$exception]);
$queueTable = $queueComponent->instance()->getTable();
$queueActionNames = collect($queueTable->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect(AuditLogPage::actionSurfaceDeclaration()->surfaceType)->toBe(ActionSurfaceType::HistoryAudit)
->and(AuditLogPage::actionSurfaceDeclaration()->slot(ActionSurfaceSlot::InspectAffordance)?->details)->toBe(ActionSurfaceInspectAffordance::ViewAction->value)
->and($auditActionNames)->toEqualCanonicalizing(['inspect'])
->and($auditTable->getRecordUrl($audit))->toBeNull()
->and(FindingExceptionsQueue::actionSurfaceDeclaration()->surfaceType)->toBe(ActionSurfaceType::QueueReview)
->and(FindingExceptionsQueue::actionSurfaceDeclaration()->slot(ActionSurfaceSlot::InspectAffordance)?->details)->toBe(ActionSurfaceInspectAffordance::ViewAction->value)
->and($queueActionNames)->toEqualCanonicalizing(['inspect_exception'])
->and($queueTable->getRecordUrl($exception))->toBeNull();
});
it('keeps tenantless run detail header actions on the canonical viewer without list affordances', function (): void {
$workspace = Workspace::factory()->create();
$user = User::factory()->create();
WorkspaceMembership::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'user_id' => (int) $user->getKey(),
'role' => 'owner',
]);
$run = OperationRun::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'managed_environment_id' => null,
'type' => 'provider.connection.check',
'status' => OperationRunStatus::Queued->value,
'outcome' => OperationRunOutcome::Pending->value,
]);
session()->forget(WorkspaceContext::SESSION_KEY);
Livewire::actingAs($user)->test(TenantlessOperationRunViewer::class, ['run' => $run])
->assertActionDoesNotExist('operate_hub_scope_run_detail')
->assertActionVisible('operate_hub_back_to_operations')
->assertActionVisible('refresh');
$declaration = TenantlessOperationRunViewer::actionSurfaceDeclaration();
expect((string) ($declaration->exemption(ActionSurfaceSlot::InspectAffordance)?->reason ?? ''))
->toContain('canonical detail destination')
->and((string) ($declaration->slot(ActionSurfaceSlot::DetailHeader)?->details ?? ''))
->toContain('refresh');
});
it('keeps tenant diagnostics as a singleton repair surface with header actions only', function (): void {
[$manager, $tenant] = createUserWithTenant(role: 'manager');
$this->actingAs($manager);
setAdminEnvironmentContext($tenant);
Livewire::test(EnvironmentDiagnostics::class)
->assertActionExists('bootstrapOwner');
$declaration = EnvironmentDiagnostics::actionSurfaceDeclaration();
expect((string) ($declaration->slot(ActionSurfaceSlot::ListHeader)?->details ?? ''))
->toContain('repair actions')
->and((string) ($declaration->exemption(ActionSurfaceSlot::InspectAffordance)?->reason ?? ''))
->toContain('singleton repair diagnostics surface');
});
it('keeps the no-access page as a singleton recovery surface with a header action', function (): void {
$user = User::factory()->create();
$this->actingAs($user);
Livewire::test(NoAccess::class)
->assertActionVisible('createWorkspace')
->assertActionEnabled('createWorkspace');
$declaration = NoAccess::actionSurfaceDeclaration();
expect((string) ($declaration->slot(ActionSurfaceSlot::ListHeader)?->details ?? ''))
->toContain('create-workspace recovery action')
->and((string) ($declaration->exemption(ActionSurfaceSlot::InspectAffordance)?->reason ?? ''))
->toContain('singleton recovery surface');
});
it('keeps required permissions as a guided diagnostic page with inline filters and empty-state guidance', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'readonly');
$response = $this->actingAs($user)
->get(EnvironmentRequiredPermissions::getUrl([
'workspace' => (int) $tenant->workspace_id,
'environment' => $tenant,
], panel: 'admin'));
$response->assertOk()
->assertSee('Copy missing application permissions')
->assertSee('Copy missing delegated permissions')
->assertSee('Permission handoff')
->assertSee('Start verification');
$declaration = EnvironmentRequiredPermissions::actionSurfaceDeclaration();
expect((string) ($declaration->exemption(ActionSurfaceSlot::ListHeader)?->reason ?? ''))
->toContain('body sections')
->and((string) ($declaration->slot(ActionSurfaceSlot::ListEmptyState)?->details ?? ''))
->toContain('no-data');
});
it('uses clickable rows without row triage on the system runs list', function (): void {
$run = OperationRun::factory()->create([
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Failed->value,
'type' => 'inventory_sync',
]);
actionSurfaceSystemPanelContext([
PlatformCapabilities::OPERATIONS_VIEW,
PlatformCapabilities::OPERATIONS_MANAGE,
]);
$livewire = Livewire::test(SystemRunsPage::class)
->assertCanSeeTableRecords([$run])
->assertActionVisible('go_to_runbooks')
->assertActionExists('go_to_runbooks', fn (Action $action): bool => $action->getLabel() === 'Go to runbooks' && $action->getUrl() === \App\Filament\System\Pages\Ops\Runbooks::getUrl(panel: 'system'));
$table = $livewire->instance()->getTable();
$emptyStateActions = collect($table->getEmptyStateActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($livewire->instance()->getTitle())->toBe('Operations')
->and($table->getActions())->toBeEmpty()
->and($table->getBulkActions())->toBeEmpty()
->and($emptyStateActions)->toBe(['go_to_runbooks_empty'])
->and($table->getRecordUrl($run))->toBe(SystemOperationRunLinks::view($run));
});
it('uses clickable rows without row triage on the system failures list', function (): void {
$run = OperationRun::factory()->create([
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Failed->value,
'type' => 'inventory_sync',
]);
actionSurfaceSystemPanelContext([
PlatformCapabilities::OPERATIONS_VIEW,
PlatformCapabilities::OPERATIONS_MANAGE,
]);
$livewire = Livewire::test(SystemFailuresPage::class)
->assertCanSeeTableRecords([$run])
->assertActionVisible('show_all_operations')
->assertActionExists('show_all_operations', fn (Action $action): bool => $action->getLabel() === 'Show all operations' && $action->getUrl() === SystemOperationRunLinks::index());
$table = $livewire->instance()->getTable();
$emptyStateActions = collect($table->getEmptyStateActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($livewire->instance()->getTitle())->toBe('Failed operations')
->and($table->getActions())->toBeEmpty()
->and($table->getBulkActions())->toBeEmpty()
->and($emptyStateActions)->toBe(['show_all_operations_empty'])
->and($table->getRecordUrl($run))->toBe(SystemOperationRunLinks::view($run));
});
it('uses clickable rows without row triage on the system stuck list', function (): void {
$run = OperationRun::factory()->create([
'status' => OperationRunStatus::Queued->value,
'outcome' => OperationRunOutcome::Pending->value,
'created_at' => now()->subHours(2),
'started_at' => null,
'type' => 'inventory_sync',
]);
actionSurfaceSystemPanelContext([
PlatformCapabilities::OPERATIONS_VIEW,
PlatformCapabilities::OPERATIONS_MANAGE,
]);
$livewire = Livewire::test(SystemStuckPage::class)
->assertCanSeeTableRecords([$run])
->assertActionVisible('show_all_operations')
->assertActionExists('show_all_operations', fn (Action $action): bool => $action->getLabel() === 'Show all operations' && $action->getUrl() === SystemOperationRunLinks::index());
$table = $livewire->instance()->getTable();
$emptyStateActions = collect($table->getEmptyStateActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($livewire->instance()->getTitle())->toBe('Stuck operations')
->and($table->getActions())->toBeEmpty()
->and($table->getBulkActions())->toBeEmpty()
->and($emptyStateActions)->toBe(['show_all_operations_empty'])
->and($table->getRecordUrl($run))->toBe(SystemOperationRunLinks::view($run));
});
it('uses clickable rows without extra row actions on the system tenants directory', function (): void {
$workspace = Workspace::factory()->create([
'name' => 'System Directory Workspace',
]);
$tenant = ManagedEnvironment::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'name' => 'System Directory ManagedEnvironment',
]);
actionSurfaceSystemPanelContext([
PlatformCapabilities::DIRECTORY_VIEW,
]);
$livewire = Livewire::test(SystemDirectoryTenantsPage::class)
->assertCanSeeTableRecords([$tenant]);
$table = $livewire->instance()->getTable();
expect($table->getActions())->toBeEmpty()
->and($table->getBulkActions())->toBeEmpty()
->and($table->getRecordUrl($tenant))->toBe(SystemDirectoryLinks::tenantDetail($tenant));
});
it('uses clickable rows without extra row actions on the system workspaces directory', function (): void {
$workspace = Workspace::factory()->create([
'name' => 'System Directory Workspace',
]);
actionSurfaceSystemPanelContext([
PlatformCapabilities::DIRECTORY_VIEW,
]);
$livewire = Livewire::test(SystemDirectoryWorkspacesPage::class)
->assertCanSeeTableRecords([$workspace]);
$table = $livewire->instance()->getTable();
expect($table->getActions())->toBeEmpty()
->and($table->getBulkActions())->toBeEmpty()
->and($table->getRecordUrl($workspace))->toBe(SystemDirectoryLinks::workspaceDetail($workspace));
});
it('keeps system access logs scan-only without row or bulk actions', function (): void {
$tenant = ManagedEnvironment::factory()->create();
$log = AuditLog::query()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'action' => 'platform.auth.login',
'status' => 'success',
'metadata' => ['attempted_email' => 'operator@tenantpilot.test'],
'recorded_at' => now(),
]);
actionSurfaceSystemPanelContext([
PlatformCapabilities::CONSOLE_VIEW,
]);
$livewire = Livewire::test(SystemAccessLogsPage::class)
->assertCanSeeTableRecords([$log]);
$table = $livewire->instance()->getTable();
expect($table->getActions())->toBeEmpty()
->and($table->getBulkActions())->toBeEmpty()
->and($table->getRecordUrl($log))->toBeNull();
});
it('removes lone View buttons and uses clickable rows on the inventory items list', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$item = InventoryItem::factory()->create([
'managed_environment_id' => $tenant->getKey(),
]);
$livewire = Livewire::test(ListInventoryItems::class);
$table = $livewire->instance()->getTable();
expect($table->getActions())->toBeEmpty();
$recordUrl = $table->getRecordUrl($item);
expect($recordUrl)->not->toBeNull();
expect($recordUrl)->toBe(InventoryItemResource::getUrl('view', ['record' => $item]));
});
it('uses clickable rows without a lone View action on the workspaces list', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
Filament::setTenant(null, true);
$workspace = $tenant->workspace;
$livewire = Livewire::test(ListWorkspaces::class)
->assertCanSeeTableRecords([$workspace]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$rowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions() ?? [])
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($rowActionNames)->toBe([])
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toBe(['edit'])
->and($rowActionNames)->not->toContain('view')
->and($table->getRecordUrl($workspace))->toBe(WorkspaceResource::getUrl('view', ['record' => $workspace]));
});
it('uses clickable rows without a lone View action on the policies list', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$policy = Policy::query()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'external_id' => 'policy-action-surface-1',
'policy_type' => 'deviceConfiguration',
'display_name' => 'Policy Action Surface',
'platform' => 'windows',
'last_synced_at' => now(),
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(ListPolicies::class)
->assertCanSeeTableRecords([$policy]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$rowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions() ?? [])
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$bulkGroup = collect($table->getBulkActions())->first(static fn ($action): bool => $action instanceof BulkActionGroup);
$bulkActionNames = collect($bulkGroup?->getActions() ?? [])
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($rowActionNames)->toBe([])
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toBe(['export', 'sync', 'restore', 'ignore'])
->and($bulkGroup)->toBeInstanceOf(BulkActionGroup::class)
->and($bulkGroup?->getLabel())->toBe('More')
->and($bulkActionNames)->toBe(['bulk_export', 'bulk_sync', 'bulk_restore', 'bulk_delete'])
->and($rowActionNames)->not->toContain('view')
->and($table->getRecordUrl($policy))->toBe(PolicyResource::getUrl('view', ['record' => $policy]));
});
it('uses clickable rows without a duplicate Edit action on the alert rules list', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$workspaceId = (int) $tenant->workspace_id;
$rule = AlertRule::factory()->create([
'workspace_id' => $workspaceId,
]);
$this->actingAs($user);
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
Filament::setTenant(null, true);
$livewire = Livewire::test(ListAlertRules::class)
->assertCanSeeTableRecords([$rule]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$rowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($rowActionNames)->not->toContain('edit')
->and($table->getRecordUrl($rule))->toBe(AlertRuleResource::getUrl('edit', ['record' => $rule]))
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toEqualCanonicalizing(['toggle_enabled', 'delete'])
->and($table->getBulkActions())->toBeEmpty();
});
it('uses clickable rows without a duplicate Edit action on the alert destinations list', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$workspaceId = (int) $tenant->workspace_id;
$destination = AlertDestination::factory()->create([
'workspace_id' => $workspaceId,
]);
$this->actingAs($user);
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
Filament::setTenant(null, true);
$livewire = Livewire::test(ListAlertDestinations::class)
->assertCanSeeTableRecords([$destination]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$rowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($rowActionNames)->not->toContain('edit')
->and($table->getRecordUrl($destination))->toBe(AlertDestinationResource::getUrl('edit', ['record' => $destination]))
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toEqualCanonicalizing(['toggle_enabled', 'delete'])
->and($table->getBulkActions())->toBeEmpty();
});
it('uses clickable-row view with all secondary provider connection actions grouped under More', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner', ensureDefaultMicrosoftProviderConnection: false);
$connection = ProviderConnection::factory()->platform()->consentGranted()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'provider' => 'microsoft',
'is_enabled' => true,
'is_default' => false,
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$livewire = Livewire::test(ListProviderConnections::class)
->assertCanSeeTableRecords([$connection]);
$table = $livewire->instance()->getTable();
$rowActions = $table->getActions();
$rowActionNames = collect($rowActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
$moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($rowActionNames)->toBeEmpty()
->and($table->getRecordUrl($connection))->toBe(ProviderConnectionResource::getUrl('view', ['record' => $connection], tenant: $tenant))
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toEqualCanonicalizing([
'edit',
'check_connection',
'inventory_sync',
'compliance_snapshot',
'set_default',
'enable_dedicated_override',
'rotate_dedicated_credential',
'delete_dedicated_credential',
'revert_to_platform',
'enable_connection',
'disable_connection',
])
->and($table->getBulkActions())->toBeEmpty();
});
it('keeps provider connection detail secondary actions aligned under More', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner', ensureDefaultMicrosoftProviderConnection: false);
$connection = ProviderConnection::factory()->platform()->consentGranted()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'provider' => 'microsoft',
'is_enabled' => true,
'is_default' => false,
]);
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$component = Livewire::test(ViewProviderConnection::class, ['record' => $connection->getKey()])
->assertActionVisible('open_required_permissions');
$instance = $component->instance();
if ($instance->getCachedHeaderActions() === []) {
$instance->cacheInteractsWithHeaderActions();
}
$headerActions = $instance->getCachedHeaderActions();
$primaryHeaderActions = collect($headerActions)
->reject(static fn ($action): bool => $action instanceof ActionGroup)
->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null)
->filter()
->values()
->all();
$moreGroup = collect($headerActions)->first(static fn ($action): bool => $action instanceof ActionGroup);
$moreActionNames = collect($moreGroup?->getActions())
->map(static fn ($action): ?string => $action->getName())
->filter()
->values()
->all();
expect($primaryHeaderActions)->toEqual(['open_required_permissions'])
->and($moreGroup)->toBeInstanceOf(ActionGroup::class)
->and($moreGroup?->getLabel())->toBe('More')
->and($moreActionNames)->toEqualCanonicalizing([
'edit',
'check_connection',
'inventory_sync',
'compliance_snapshot',
'set_default',
'enable_dedicated_override',
'rotate_dedicated_credential',
'delete_dedicated_credential',
'revert_to_platform',
'enable_connection',
'disable_connection',
]);
});
it('uses clickable rows without extra row actions on the alert deliveries list', function (): void {
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$workspaceId = (int) $tenant->workspace_id;
$rule = AlertRule::factory()->create([
'workspace_id' => $workspaceId,
]);
$destination = AlertDestination::factory()->create([
'workspace_id' => $workspaceId,
]);
$delivery = AlertDelivery::factory()->create([
'workspace_id' => $workspaceId,
'managed_environment_id' => (int) $tenant->getKey(),
'alert_rule_id' => (int) $rule->getKey(),
'alert_destination_id' => (int) $destination->getKey(),
]);
$this->actingAs($user);
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
Filament::setTenant(null, true);
$livewire = Livewire::test(ListAlertDeliveries::class)
->assertCanSeeTableRecords([$delivery]);
$table = $livewire->instance()->getTable();
expect($table->getActions())->toBeEmpty()
->and($table->getRecordUrl($delivery))->toBe(AlertDeliveryResource::getUrl('view', ['record' => $delivery], panel: 'admin'));
});
it('keeps representative operation-start actions observable with actor and scope metadata', function (): void {
Queue::fake();
bindFailHardGraphClient();
[$user, $tenant] = createMinimalUserWithTenant(role: 'owner');
$this->actingAs($user);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
Livewire::test(ListPolicies::class)
->mountAction('sync')
->callMountedAction()
->assertHasNoActionErrors();
Queue::assertPushed(SyncPoliciesJob::class);
$run = OperationRun::query()
->where('managed_environment_id', $tenant->getKey())
->where('type', 'policy.sync')
->latest('id')
->first();
expect($run)->not->toBeNull();
expect((int) $run?->managed_environment_id)->toBe((int) $tenant->getKey());
expect((int) $run?->workspace_id)->toBe((int) $tenant->workspace_id);
expect((string) $run?->initiator_name)->toBe((string) $user->name);
});
it('documents the spec 192 workflow-heavy exception and reference inventory', function (): void {
$inventory = ActionSurfaceExemptions::spec192RecordPageInventory();
$workflowHeavy = collect($inventory)
->filter(fn (array $surface): bool => $surface['classification'] === 'workflow_heavy_special_type')
->keys()
->values()
->all();
$referencePages = collect($inventory)
->filter(fn (array $surface): bool => $surface['classification'] === 'compliant_reference')
->keys()
->values()
->all();
expect($workflowHeavy)->toBe([\App\Filament\Resources\ManagedEnvironmentResource\Pages\ViewManagedEnvironment::class])
->and($referencePages)->toEqualCanonicalizing([
\App\Filament\Resources\ReviewPackResource\Pages\ViewReviewPack::class,
\App\Filament\Resources\AlertDestinationResource\Pages\ViewAlertDestination::class,
\App\Filament\Resources\PolicyVersionResource\Pages\ViewPolicyVersion::class,
\App\Filament\Resources\Workspaces\Pages\ViewWorkspace::class,
\App\Filament\Resources\BaselineSnapshotResource\Pages\ViewBaselineSnapshot::class,
\App\Filament\Resources\BackupSetResource\Pages\ViewBackupSet::class,
]);
});
it('documents the spec 193 monitoring hierarchy inventory and explicit exception', function (): void {
$inventory = ActionSurfaceExemptions::spec193MonitoringSurfaceInventory();
$baselineExemptions = ActionSurfaceExemptions::baseline();
$remediationRequired = collect($inventory)
->filter(fn (array $surface): bool => $surface['classification'] === 'remediation_required')
->keys()
->values()
->all();
$calmReferences = collect($inventory)
->filter(fn (array $surface): bool => $surface['classification'] === 'compliant_no_op')
->keys()
->values()
->all();
expect(array_keys($inventory))->toEqualCanonicalizing([
FindingExceptionsQueue::class,
TenantlessOperationRunViewer::class,
Operations::class,
Alerts::class,
AuditLogPage::class,
ListAlertDeliveries::class,
EvidenceOverview::class,
BaselineCompareLanding::class,
BaselineCompareMatrix::class,
ReviewRegister::class,
EnvironmentDiagnostics::class,
])
->and($baselineExemptions->hasClass(Alerts::class))->toBeFalse()
->and(method_exists(Alerts::class, 'actionSurfaceDeclaration'))->toBeTrue()
->and($remediationRequired)->toEqualCanonicalizing([
FindingExceptionsQueue::class,
TenantlessOperationRunViewer::class,
Operations::class,
])
->and($calmReferences)->toEqualCanonicalizing([
EvidenceOverview::class,
BaselineCompareLanding::class,
BaselineCompareMatrix::class,
ReviewRegister::class,
])
->and(ActionSurfaceExemptions::spec193MonitoringSurface(EnvironmentDiagnostics::class)['classification'] ?? null)->toBe('special_type_acceptable')
->and(ActionSurfaceExemptions::spec193MonitoringSurface(EnvironmentDiagnostics::class)['exceptionReason'] ?? null)->toContain('diagnostic');
});
it('keeps spec 193 hierarchy work from expanding confirmation, reason capture, or compare-start semantics', function (): void {
[$approver, $tenant] = createMinimalUserWithTenant(role: 'owner', workspaceRole: 'manager');
$mountedActionFieldNames = static function (mixed $component): array {
$method = new \ReflectionMethod($component->instance(), 'getMountedActionForm');
$method->setAccessible(true);
$form = $method->invoke($component->instance());
return collect($form?->getFlatFields(withHidden: true) ?? [])
->map(static fn (mixed $field): ?string => method_exists($field, 'getName') ? $field->getName() : null)
->filter()
->values()
->all();
};
$finding = Finding::factory()->for($tenant)->create();
$exception = FindingException::query()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'finding_id' => (int) $finding->getKey(),
'requested_by_user_id' => (int) $approver->getKey(),
'owner_user_id' => (int) $approver->getKey(),
'status' => FindingException::STATUS_PENDING,
'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT,
'request_reason' => 'Guarded spec 193 review',
'requested_at' => now()->subDay(),
'review_due_at' => now()->addDay(),
'evidence_summary' => ['reference_count' => 0],
]);
$this->actingAs($approver);
setAdminPanelContext();
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
$approveComponent = Livewire::withQueryParams([
'exception' => (int) $exception->getKey(),
])
->actingAs($approver)
->test(FindingExceptionsQueue::class)
->assertActionExists('approve_selected_exception', function (Action $action): bool {
return $action->isConfirmationRequired();
})
->mountAction('approve_selected_exception');
expect($mountedActionFieldNames($approveComponent))->toBe([
'effective_from',
'expires_at',
'approval_reason',
]);
$rejectComponent = Livewire::withQueryParams([
'exception' => (int) $exception->getKey(),
])
->actingAs($approver)
->test(FindingExceptionsQueue::class)
->assertActionExists('reject_selected_exception', function (Action $action): bool {
return $action->isConfirmationRequired();
})
->mountAction('reject_selected_exception');
expect($mountedActionFieldNames($rejectComponent))->toBe([
'rejection_reason',
]);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$profile = BaselineProfile::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
'capture_mode' => \App\Support\Baselines\BaselineCaptureMode::FullContent->value,
]);
$snapshot = BaselineSnapshot::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'baseline_profile_id' => (int) $profile->getKey(),
]);
$profile->update(['active_snapshot_id' => (int) $snapshot->getKey()]);
BaselineTenantAssignment::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'baseline_profile_id' => (int) $profile->getKey(),
]);
baselineCompareLandingLivewire($tenant, user: $approver)
->assertActionExists('compareNow', function (Action $action): bool {
return $action->isConfirmationRequired()
&& $action->getModalDescription() === 'This will refresh content evidence on demand (redacted) before comparing the current tenant inventory against the assigned baseline snapshot.';
});
});
it('keeps spec 192 remediated pages out of the enterprise-detail layout rollout', function (): void {
foreach ([
\App\Filament\Resources\BaselineProfileResource::class,
\App\Filament\Resources\EvidenceSnapshotResource::class,
\App\Filament\Resources\FindingExceptionResource::class,
\App\Filament\Resources\EnvironmentReviewResource::class,
\App\Filament\Resources\ManagedEnvironmentResource::class,
\App\Filament\Resources\ManagedEnvironmentResource\Pages\EditManagedEnvironment::class,
\App\Filament\Resources\ManagedEnvironmentResource\Pages\ViewManagedEnvironment::class,
] as $className) {
$source = file_get_contents((string) (new ReflectionClass($className))->getFileName()) ?: '';
expect($source)
->not->toContain('EnterpriseDetail')
->not->toContain('enterprise-detail/header');
}
});