TenantAtlas/apps/platform/tests/Feature/Navigation/Spec322LegacyQueryAliasGuardTest.php
ahmido ec4ff1074c Spec 322: add browser no-drift regression guards (#379)
## Summary
- add the Spec 322 artifact set for the browser no-drift regression guard
- add Feature navigation guards for admin surface scope, environment CTA URLs, and legacy alias rejection
- add Browser smoke coverage for workspace hubs, environment-owned surfaces, workspace-owned analysis surfaces, and alerts/audit flows
- add the Spec 322 browser support harness used by the new smoke coverage

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Navigation/Spec322AdminSurfaceScopeContractTest.php tests/Feature/Navigation/Spec322LegacyQueryAliasGuardTest.php tests/Feature/Navigation/Spec322EnvironmentCtaUrlContractTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Browser/Spec322WorkspaceHubNoDriftSmokeTest.php tests/Browser/Spec322EnvironmentOwnedSurfaceSmokeTest.php tests/Browser/Spec322WorkspaceOwnedAnalysisSmokeTest.php tests/Browser/Spec322AlertsAuditNoDriftSmokeTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail pint --dirty`
- `git diff --check`

## Notes
- a broader filtered regression run still reports existing Baseline Compare feature-test failures outside this diff

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #379
2026-05-17 11:34:31 +00:00

268 lines
9.7 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\Monitoring\AuditLog as AuditLogPage;
use App\Filament\Pages\Monitoring\Operations;
use App\Filament\Resources\AlertDeliveryResource\Pages\ListAlertDeliveries;
use App\Models\AlertDelivery;
use App\Models\AlertDestination;
use App\Models\AlertRule;
use App\Models\AuditLog as AuditLogModel;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Illuminate\Support\Facades\Route;
use Livewire\Livewire;
it('legacy_environment_query_aliases_do_not_create_filter_or_shell_state', function (): void {
[$user, $environmentA, $environmentB, $records] = spec322LegacyAliasFixture();
$this->actingAs($user);
setAdminPanelContext();
session()->put(WorkspaceContext::SESSION_KEY, (int) $environmentA->workspace_id);
$legacyQueries = [
'tenant' => ['tenant' => (string) $environmentA->getKey()],
'tenant_id' => ['tenant_id' => (int) $environmentA->getKey()],
'managed_environment_id' => ['managed_environment_id' => (int) $environmentA->getKey()],
'environment' => ['environment' => (string) $environmentA->getRouteKey()],
'tenant_scope' => ['tenant_scope' => 'environment'],
'tableFilters' => [
'tableFilters' => [
'managed_environment_id' => ['value' => (string) $environmentA->getKey()],
],
],
];
foreach ($legacyQueries as $query) {
Livewire::withQueryParams($query)
->actingAs($user)
->test(Operations::class)
->assertSet('tableFilters.managed_environment_id.value', null)
->assertDontSee('Environment filter:')
->assertCanSeeTableRecords([$records['runA'], $records['runB']]);
Livewire::withQueryParams($query)
->actingAs($user)
->test(ListAlertDeliveries::class)
->assertSet('tableFilters.managed_environment_id.value', null)
->assertDontSee('Environment filter:')
->assertCanSeeTableRecords([$records['deliveryA'], $records['deliveryB']]);
Livewire::withQueryParams($query)
->actingAs($user)
->test(AuditLogPage::class)
->assertSet('tableFilters.managed_environment_id.value', null)
->assertDontSee('Environment filter:')
->assertCanSeeTableRecords([$records['auditA'], $records['auditB'], $records['auditWorkspace']]);
}
});
it('has_no_active_legacy_tenant_panel_routes', function (): void {
$legacyRouteUris = collect(Route::getRoutes())
->map(fn ($route): string => ltrim((string) $route->uri(), '/'))
->filter(fn (string $uri): bool => preg_match('#^admin/t(?:/|$)#', $uri) === 1)
->values();
$registeredProviders = require base_path('bootstrap/providers.php');
$tenantPanelProviders = collect($registeredProviders)
->filter(fn (string $provider): bool => str_contains($provider, 'TenantPanelProvider'))
->values();
expect($legacyRouteUris)->toBeEmpty()
->and($tenantPanelProviders)->toBeEmpty()
->and(file_exists(app_path('Providers/Filament/TenantPanelProvider.php')))->toBeFalse()
->and(file_exists(app_path('Filament/Providers/TenantPanelProvider.php')))->toBeFalse()
->and(Filament::getPanel('tenant'))->toBeNull();
$this->get('/admin/t/example')->assertNotFound();
});
it('allows_tenant_terms_only_in_provider_boundary_contexts', function (): void {
$files = spec322LegacyGuardFiles([
base_path('app/Support/Navigation'),
base_path('app/Filament/Pages/Monitoring/Operations.php'),
base_path('app/Filament/Pages/Monitoring/FindingExceptionsQueue.php'),
base_path('app/Filament/Pages/Governance/GovernanceInbox.php'),
base_path('app/Filament/Pages/Governance/DecisionRegister.php'),
base_path('app/Filament/Pages/Monitoring/EvidenceOverview.php'),
base_path('app/Filament/Pages/Reviews/ReviewRegister.php'),
base_path('app/Filament/Pages/Reviews/CustomerReviewWorkspace.php'),
base_path('app/Filament/Resources/ProviderConnectionResource.php'),
base_path('app/Filament/Resources/ProviderConnectionResource/Pages/ListProviderConnections.php'),
base_path('routes/web.php'),
]);
$hits = spec322LegacyPatternHits($files, [
'/\btenantPrefilterUrl\s*\(/',
'/\bCanonicalAdminTenantFilterState\b/',
'/\bWorkspaceScopedTenantRoutes\b/',
'/\bTenantPageCategory\b/',
'/\bEnsureFilamentTenantSelected\b/',
'/'.'ensure-filament-'.'tenant-selected'.'/',
'/\blastTenantId\s*\(/',
'/\brememberedTenant\s*\(/',
'/\brememberTenantContext\s*\(/',
'/\bLAST_TENANT_IDS_SESSION_KEY\b/',
'/\bTenantBound\b/',
'/\bTenantScopedEvidence\b/',
]);
expect($hits)->toBeEmpty("Retired Tenant platform-context terms remain:\n".implode("\n", $hits));
});
/**
* @return array{0: \App\Models\User, 1: ManagedEnvironment, 2: ManagedEnvironment, 3: array<string, mixed>}
*/
function spec322LegacyAliasFixture(): array
{
$environmentA = ManagedEnvironment::factory()->active()->create([
'name' => 'Spec322 Alias Environment A',
'external_id' => 'spec322-alias-environment-a',
]);
[$user, $environmentA] = createUserWithTenant(tenant: $environmentA, role: 'owner', workspaceRole: 'owner');
$environmentB = ManagedEnvironment::factory()->active()->create([
'workspace_id' => (int) $environmentA->workspace_id,
'name' => 'Spec322 Alias Environment B',
'external_id' => 'spec322-alias-environment-b',
]);
createUserWithTenant(tenant: $environmentB, user: $user, role: 'owner', workspaceRole: 'owner');
$runA = OperationRun::factory()->forTenant($environmentA)->create(['type' => 'policy.sync']);
$runB = OperationRun::factory()->forTenant($environmentB)->create(['type' => 'inventory_sync']);
$rule = AlertRule::factory()->create(['workspace_id' => (int) $environmentA->workspace_id]);
$destination = AlertDestination::factory()->create(['workspace_id' => (int) $environmentA->workspace_id]);
$deliveryA = AlertDelivery::factory()->create([
'workspace_id' => (int) $environmentA->workspace_id,
'managed_environment_id' => (int) $environmentA->getKey(),
'alert_rule_id' => (int) $rule->getKey(),
'alert_destination_id' => (int) $destination->getKey(),
'status' => AlertDelivery::STATUS_FAILED,
]);
$deliveryB = AlertDelivery::factory()->create([
'workspace_id' => (int) $environmentA->workspace_id,
'managed_environment_id' => (int) $environmentB->getKey(),
'alert_rule_id' => (int) $rule->getKey(),
'alert_destination_id' => (int) $destination->getKey(),
'status' => AlertDelivery::STATUS_SENT,
]);
$auditA = spec322LegacyAuditRecord($environmentA, 'Spec322 alias audit A');
$auditB = spec322LegacyAuditRecord($environmentB, 'Spec322 alias audit B');
$auditWorkspace = spec322LegacyAuditRecord(null, 'Spec322 workspace audit', [
'workspace_id' => (int) $environmentA->workspace_id,
]);
return [$user, $environmentA, $environmentB, compact(
'runA',
'runB',
'deliveryA',
'deliveryB',
'auditA',
'auditB',
'auditWorkspace',
)];
}
/**
* @param array<string, mixed> $attributes
*/
function spec322LegacyAuditRecord(?ManagedEnvironment $environment, string $summary, array $attributes = []): AuditLogModel
{
$workspaceId = array_key_exists('workspace_id', $attributes)
? (int) $attributes['workspace_id']
: (int) ($environment?->workspace_id);
return AuditLogModel::query()->create(array_merge([
'workspace_id' => $workspaceId,
'managed_environment_id' => $environment?->getKey(),
'actor_email' => 'spec322@example.test',
'actor_name' => 'Spec322 Operator',
'action' => 'operation.completed',
'status' => 'success',
'resource_type' => 'operation_run',
'resource_id' => '322',
'summary' => $summary,
'metadata' => [],
'recorded_at' => now(),
], $attributes));
}
/**
* @param list<string> $roots
* @return list<string>
*/
function spec322LegacyGuardFiles(array $roots): array
{
$files = [];
foreach ($roots as $root) {
if (is_file($root)) {
$files[] = $root;
continue;
}
if (! is_dir($root)) {
continue;
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS),
);
foreach ($iterator as $file) {
if (! $file instanceof SplFileInfo || ! $file->isFile()) {
continue;
}
if (! in_array($file->getExtension(), ['php', 'md'], true)) {
continue;
}
$files[] = $file->getPathname();
}
}
sort($files);
return array_values(array_unique($files));
}
/**
* @param list<string> $files
* @param list<string> $patterns
* @return list<string>
*/
function spec322LegacyPatternHits(array $files, array $patterns): array
{
$hits = [];
foreach ($files as $path) {
$contents = file_get_contents($path);
if (! is_string($contents)) {
continue;
}
$lines = preg_split('/\R/', $contents) ?: [];
foreach ($patterns as $pattern) {
foreach ($lines as $lineNumber => $line) {
if (preg_match($pattern, $line) !== 1) {
continue;
}
$hits[] = str_replace(repo_path().'/', '', $path).':'.($lineNumber + 1).' -> '.trim($line);
}
}
}
return $hits;
}