TenantAtlas/tests/Feature/Guards/FilamentTableStandardsGuardTest.php
ahmido 1f3619bd16 feat: tenant-owned query canon and wrong-tenant guards (#180)
## Summary
- introduce a shared tenant-owned query and record-resolution canon for first-slice Filament resources
- harden direct views, row actions, bulk actions, relation managers, and workspace-admin canonical viewers against wrong-tenant access
- add registry-backed rollout metadata, search posture handling, architectural guards, and focused Pest coverage for scope parity and 404/403 semantics

## Included
- Spec 150 package under `specs/150-tenant-owned-query-canon-and-wrong-tenant-guards/`
- shared support classes: `TenantOwnedModelFamilies`, `TenantOwnedQueryScope`, `TenantOwnedRecordResolver`
- shared Filament concern: `InteractsWithTenantOwnedRecords`
- resource/page/policy hardening across findings, policies, policy versions, backup schedules, backup sets, restore runs, inventory items, and Entra groups
- additional regression coverage for canonical tenant state, wrong-tenant record resolution, relation-manager congruence, and action-surface guardrails

## Validation
- `vendor/bin/sail artisan test --compact` passed
- full suite result: `2733 passed, 8 skipped`
- formatting applied with `vendor/bin/sail bin pint --dirty --format agent`

## Notes
- Livewire v4.0+ compliant via existing Filament v5 stack
- provider registration remains in `bootstrap/providers.php`
- globally searchable first-slice posture: Entra groups scoped; policies and policy versions explicitly disabled
- destructive actions continue to use confirmation and policy authorization
- no new Filament assets added; existing deployment flow remains unchanged, including `php artisan filament:assets` when registered assets are used

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #180
2026-03-18 08:33:13 +00:00

339 lines
16 KiB
PHP

<?php
declare(strict_types=1);
use Illuminate\Support\Collection;
it('declares default sorts for the standardized table surface inventory', function (): void {
$paths = collect([
'app/Filament/Resources/TenantResource.php',
'app/Filament/Resources/PolicyResource.php',
'app/Filament/Resources/BackupSetResource.php',
'app/Filament/Resources/BackupScheduleResource.php',
'app/Filament/Resources/ProviderConnectionResource.php',
'app/Filament/Resources/FindingResource.php',
'app/Filament/Resources/OperationRunResource.php',
'app/Filament/Resources/EntraGroupResource.php',
'app/Filament/Resources/AlertDeliveryResource.php',
'app/Filament/Resources/AlertRuleResource.php',
'app/Filament/Resources/AlertDestinationResource.php',
'app/Filament/Resources/BaselineProfileResource.php',
'app/Filament/Resources/BaselineSnapshotResource.php',
'app/Filament/Resources/InventoryItemResource.php',
'app/Filament/Resources/PolicyVersionResource.php',
'app/Filament/Resources/ReviewPackResource.php',
'app/Filament/Resources/Workspaces/WorkspaceResource.php',
'app/Filament/Resources/TenantResource/RelationManagers/TenantMembershipsRelationManager.php',
'app/Filament/Resources/Workspaces/RelationManagers/WorkspaceMembershipsRelationManager.php',
'app/Filament/Resources/BaselineProfileResource/RelationManagers/BaselineTenantAssignmentsRelationManager.php',
'app/Filament/Resources/BackupSetResource/RelationManagers/BackupItemsRelationManager.php',
'app/Filament/Resources/PolicyResource/RelationManagers/VersionsRelationManager.php',
'app/Filament/Resources/BackupScheduleResource/RelationManagers/BackupScheduleOperationRunsRelationManager.php',
'app/Filament/Pages/InventoryCoverage.php',
'app/Filament/System/Pages/Directory/Tenants.php',
'app/Filament/System/Pages/Directory/Workspaces.php',
'app/Filament/System/Pages/Ops/Runs.php',
'app/Filament/System/Pages/Ops/Failures.php',
'app/Filament/System/Pages/Ops/Stuck.php',
'app/Filament/System/Pages/Security/AccessLogs.php',
'app/Filament/System/Pages/RepairWorkspaceOwners.php',
'app/Filament/Widgets/Dashboard/RecentDriftFindings.php',
'app/Filament/Widgets/Dashboard/RecentOperations.php',
'app/Livewire/BackupSetPolicyPickerTable.php',
'app/Livewire/EntraGroupCachePickerTable.php',
'app/Livewire/SettingsCatalogSettingsTable.php',
]);
/** @var Collection<int, string> $missing */
$missing = $paths
->filter(function (string $relativePath): bool {
$contents = file_get_contents(base_path($relativePath));
return ! is_string($contents) || ! str_contains($contents, '->defaultSort(');
})
->values();
expect($missing)->toBeEmpty('Missing explicit default sort declarations: '.implode(', ', $missing->all()));
});
it('declares domain-specific empty states across the standardized table surface inventory', function (): void {
$patternByPath = [
'app/Filament/Resources/TenantResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/PolicyResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/BackupSetResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/BackupScheduleResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/ProviderConnectionResource/Pages/ListProviderConnections.php' => ['getTableEmptyStateHeading', 'getTableEmptyStateDescription'],
'app/Filament/Resources/FindingResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/OperationRunResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/EntraGroupResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/AlertDeliveryResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/AlertRuleResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/AlertDestinationResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/BaselineProfileResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/BaselineSnapshotResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/InventoryItemResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/PolicyVersionResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/ReviewPackResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/Workspaces/WorkspaceResource.php' => ['->emptyStateHeading('],
'app/Filament/Resources/TenantResource/RelationManagers/TenantMembershipsRelationManager.php' => ['->emptyStateHeading('],
'app/Filament/Resources/Workspaces/RelationManagers/WorkspaceMembershipsRelationManager.php' => ['->emptyStateHeading('],
'app/Filament/Resources/BaselineProfileResource/RelationManagers/BaselineTenantAssignmentsRelationManager.php' => ['->emptyStateHeading('],
'app/Filament/Resources/BackupSetResource/RelationManagers/BackupItemsRelationManager.php' => ['->emptyStateHeading('],
'app/Filament/Resources/PolicyResource/RelationManagers/VersionsRelationManager.php' => ['->emptyStateHeading('],
'app/Filament/Resources/BackupScheduleResource/RelationManagers/BackupScheduleOperationRunsRelationManager.php' => ['->emptyStateHeading('],
'app/Filament/Pages/InventoryCoverage.php' => ['->emptyStateHeading('],
'app/Filament/System/Pages/Directory/Tenants.php' => ['->emptyStateHeading('],
'app/Filament/System/Pages/Directory/Workspaces.php' => ['->emptyStateHeading('],
'app/Filament/System/Pages/Ops/Runs.php' => ['->emptyStateHeading('],
'app/Filament/System/Pages/Ops/Failures.php' => ['->emptyStateHeading('],
'app/Filament/System/Pages/Ops/Stuck.php' => ['->emptyStateHeading('],
'app/Filament/System/Pages/Security/AccessLogs.php' => ['->emptyStateHeading('],
'app/Filament/System/Pages/RepairWorkspaceOwners.php' => ['->emptyStateHeading('],
'app/Filament/Widgets/Dashboard/RecentDriftFindings.php' => ['->emptyStateHeading('],
'app/Filament/Widgets/Dashboard/RecentOperations.php' => ['->emptyStateHeading('],
'app/Livewire/BackupSetPolicyPickerTable.php' => ['->emptyStateHeading('],
'app/Livewire/EntraGroupCachePickerTable.php' => ['->emptyStateHeading('],
'app/Livewire/SettingsCatalogSettingsTable.php' => ['->emptyStateHeading('],
];
$missing = [];
foreach ($patternByPath as $relativePath => $patterns) {
$contents = file_get_contents(base_path($relativePath));
if (! is_string($contents)) {
$missing[] = $relativePath;
continue;
}
foreach ($patterns as $pattern) {
if (! str_contains($contents, $pattern)) {
$missing[] = $relativePath;
break;
}
}
}
expect($missing)->toBeEmpty('Missing standardized empty-state declarations: '.implode(', ', $missing));
});
it('keeps persistence declarations explicit on the designated critical and follow-up filter surfaces', function (): void {
$paths = [
'app/Filament/Resources/TenantResource.php',
'app/Filament/Resources/PolicyResource.php',
'app/Filament/Resources/BackupSetResource.php',
'app/Filament/Resources/BackupScheduleResource.php',
'app/Filament/Resources/ProviderConnectionResource.php',
'app/Filament/Resources/FindingResource.php',
'app/Filament/Resources/InventoryItemResource.php',
'app/Filament/Resources/PolicyVersionResource.php',
'app/Filament/Resources/RestoreRunResource.php',
'app/Filament/Resources/AlertDeliveryResource.php',
'app/Filament/Resources/EntraGroupResource.php',
'app/Filament/Resources/OperationRunResource.php',
'app/Filament/Resources/BaselineSnapshotResource.php',
'app/Filament/Resources/BackupSetResource/RelationManagers/BackupItemsRelationManager.php',
];
$missing = [];
foreach ($paths as $relativePath) {
$contents = file_get_contents(base_path($relativePath));
if (! is_string($contents)) {
$missing[] = $relativePath;
continue;
}
foreach (['->persistSearchInSession()', '->persistSortInSession()', '->persistFiltersInSession()'] as $pattern) {
if (! str_contains($contents, $pattern)) {
$missing[] = "{$relativePath} ({$pattern})";
}
}
}
expect($missing)->toBeEmpty('Missing persistence declarations: '.implode(', ', $missing));
});
it('syncs canonical admin tenant filter state on the persisted admin list surfaces', function (): void {
$patternByPath = [
'app/Filament/Resources/BackupScheduleResource/Pages/ListBackupSchedules.php' => [
'CanonicalAdminTenantFilterState::class',
'->sync(',
],
'app/Filament/Resources/BackupSetResource/Pages/ListBackupSets.php' => [
'CanonicalAdminTenantFilterState::class',
'->sync(',
],
'app/Filament/Resources/FindingResource/Pages/ListFindings.php' => [
'CanonicalAdminTenantFilterState::class',
'->sync(',
],
'app/Filament/Resources/InventoryItemResource/Pages/ListInventoryItems.php' => [
'CanonicalAdminTenantFilterState::class',
'->sync(',
],
'app/Filament/Resources/RestoreRunResource/Pages/ListRestoreRuns.php' => [
'CanonicalAdminTenantFilterState::class',
'->sync(',
],
'app/Filament/Resources/PolicyResource/Pages/ListPolicies.php' => [
'CanonicalAdminTenantFilterState::class',
'->sync(',
],
'app/Filament/Resources/PolicyVersionResource/Pages/ListPolicyVersions.php' => [
'CanonicalAdminTenantFilterState::class',
'->sync(',
],
'app/Filament/Resources/ProviderConnectionResource/Pages/ListProviderConnections.php' => [
'CanonicalAdminTenantFilterState::class',
'->sync(',
],
'app/Filament/Resources/EntraGroupResource/Pages/ListEntraGroups.php' => [
'CanonicalAdminTenantFilterState::class',
'->sync(',
],
'app/Filament/Pages/Monitoring/AuditLog.php' => [
'CanonicalAdminTenantFilterState::class',
'->sync(',
],
];
$missing = [];
foreach ($patternByPath as $relativePath => $patterns) {
$contents = file_get_contents(base_path($relativePath));
if (! is_string($contents)) {
$missing[] = $relativePath;
continue;
}
foreach ($patterns as $pattern) {
if (! str_contains($contents, $pattern)) {
$missing[] = "{$relativePath} ({$pattern})";
}
}
}
expect($missing)->toBeEmpty('Missing canonical admin tenant filter sync coverage: '.implode(', ', $missing));
});
it('uses shared archived and date-range presets on the repeated soft-delete filter surfaces', function (): void {
$patternByPath = [
'app/Filament/Resources/PolicyVersionResource.php' => [
'FilterPresets::dateRange(',
'FilterPresets::archived()',
],
'app/Filament/Resources/RestoreRunResource.php' => [
'FilterPresets::dateRange(',
'FilterPresets::archived()',
],
];
$missing = [];
foreach ($patternByPath as $relativePath => $patterns) {
$contents = file_get_contents(base_path($relativePath));
if (! is_string($contents)) {
$missing[] = $relativePath;
continue;
}
foreach ($patterns as $pattern) {
if (! str_contains($contents, $pattern)) {
$missing[] = "{$relativePath} ({$pattern})";
}
}
}
expect($missing)->toBeEmpty('Missing shared filter presets: '.implode(', ', $missing));
});
it('uses centralized option sources for the prioritized status and operation filters', function (): void {
$patternByPath = [
'app/Filament/Resources/FindingResource.php' => ['FilterOptionCatalog::findingStatuses()'],
'app/Filament/Resources/AlertDeliveryResource.php' => ['FilterOptionCatalog::alertDeliveryStatuses()'],
'app/Filament/Resources/BaselineProfileResource.php' => ['FilterOptionCatalog::baselineProfileStatuses()'],
'app/Filament/Resources/OperationRunResource.php' => ['FilterOptionCatalog::operationTypes('],
];
$missing = [];
foreach ($patternByPath as $relativePath => $patterns) {
$contents = file_get_contents(base_path($relativePath));
if (! is_string($contents)) {
$missing[] = $relativePath;
continue;
}
foreach ($patterns as $pattern) {
if (! str_contains($contents, $pattern)) {
$missing[] = "{$relativePath} ({$pattern})";
}
}
}
expect($missing)->toBeEmpty('Missing centralized filter option sourcing: '.implode(', ', $missing));
});
it('uses the shared pagination profile helper on standardized surfaces', function (): void {
$paths = [
'app/Filament/Resources/TenantResource.php',
'app/Filament/Resources/PolicyResource.php',
'app/Filament/Resources/BackupSetResource.php',
'app/Filament/Resources/BackupScheduleResource.php',
'app/Filament/Resources/ProviderConnectionResource.php',
'app/Filament/Resources/FindingResource.php',
'app/Filament/Resources/OperationRunResource.php',
'app/Filament/Resources/EntraGroupResource.php',
'app/Filament/Resources/AlertDeliveryResource.php',
'app/Filament/Resources/AlertRuleResource.php',
'app/Filament/Resources/AlertDestinationResource.php',
'app/Filament/Resources/BaselineProfileResource.php',
'app/Filament/Resources/BaselineSnapshotResource.php',
'app/Filament/Resources/InventoryItemResource.php',
'app/Filament/Resources/PolicyVersionResource.php',
'app/Filament/Resources/ReviewPackResource.php',
'app/Filament/Resources/Workspaces/WorkspaceResource.php',
'app/Filament/Resources/TenantResource/RelationManagers/TenantMembershipsRelationManager.php',
'app/Filament/Resources/Workspaces/RelationManagers/WorkspaceMembershipsRelationManager.php',
'app/Filament/Resources/BaselineProfileResource/RelationManagers/BaselineTenantAssignmentsRelationManager.php',
'app/Filament/Resources/BackupSetResource/RelationManagers/BackupItemsRelationManager.php',
'app/Filament/Resources/PolicyResource/RelationManagers/VersionsRelationManager.php',
'app/Filament/Resources/BackupScheduleResource/RelationManagers/BackupScheduleOperationRunsRelationManager.php',
'app/Filament/Pages/InventoryCoverage.php',
'app/Filament/System/Pages/Directory/Tenants.php',
'app/Filament/System/Pages/Directory/Workspaces.php',
'app/Filament/System/Pages/Ops/Runs.php',
'app/Filament/System/Pages/Ops/Failures.php',
'app/Filament/System/Pages/Ops/Stuck.php',
'app/Filament/System/Pages/Security/AccessLogs.php',
'app/Filament/System/Pages/RepairWorkspaceOwners.php',
'app/Filament/Widgets/Dashboard/RecentDriftFindings.php',
'app/Filament/Widgets/Dashboard/RecentOperations.php',
'app/Livewire/BackupSetPolicyPickerTable.php',
'app/Livewire/EntraGroupCachePickerTable.php',
'app/Livewire/SettingsCatalogSettingsTable.php',
];
$missing = collect($paths)
->filter(function (string $relativePath): bool {
$contents = file_get_contents(base_path($relativePath));
return ! is_string($contents) || ! str_contains($contents, 'TablePaginationProfiles::');
})
->values()
->all();
expect($missing)->toBeEmpty('Missing pagination profile helper usage: '.implode(', ', $missing));
});