TenantAtlas/tests/Feature/Filament/InventoryCoverageTableTest.php
2026-03-09 11:39:36 +01:00

245 lines
11 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\InventoryCoverage;
use App\Models\Tenant;
use App\Models\User;
use App\Models\WorkspaceMembership;
use App\Support\Badges\BadgeCatalog;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\TagBadgeCatalog;
use App\Support\Badges\TagBadgeDomain;
use Filament\Facades\Filament;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Livewire\Features\SupportTesting\Testable;
use Livewire\Livewire;
function inventoryCoverageRecordKey(string $segment, string $type): string
{
return "{$segment}:{$type}";
}
function inventoryCoverageComponent(User $user, Tenant $tenant): Testable
{
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
test()->actingAs($user);
return Livewire::actingAs($user)->test(InventoryCoverage::class);
}
function removeInventoryCoverageRestoreMetadata(): void
{
config()->set(
'tenantpilot.supported_policy_types',
collect(config('tenantpilot.supported_policy_types', []))
->map(function (array $row): array {
unset($row['restore']);
return $row;
})
->all(),
);
config()->set(
'tenantpilot.foundation_types',
collect(config('tenantpilot.foundation_types', []))
->map(function (array $row): array {
unset($row['restore']);
return $row;
})
->all(),
);
}
it('renders searchable coverage rows for policy and foundation metadata', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$conditionalAccessKey = inventoryCoverageRecordKey('policy', 'conditionalAccessPolicy');
$deviceConfigurationKey = inventoryCoverageRecordKey('policy', 'deviceConfiguration');
$scopeTagKey = inventoryCoverageRecordKey('foundation', 'roleScopeTag');
inventoryCoverageComponent($user, $tenant)
->assertOk()
->assertTableColumnExists('type')
->assertTableColumnExists('label')
->assertTableColumnExists('category')
->assertTableColumnExists('dependencies')
->assertCountTableRecords(
count(config('tenantpilot.supported_policy_types', [])) + count(config('tenantpilot.foundation_types', [])),
)
->assertCanSeeTableRecords([$conditionalAccessKey, $scopeTagKey])
->searchTable('conditional')
->assertCanSeeTableRecords([$conditionalAccessKey])
->assertCanNotSeeTableRecords([$deviceConfigurationKey, $scopeTagKey])
->searchTable('Scope Tag')
->assertCanSeeTableRecords([$scopeTagKey])
->assertCanNotSeeTableRecords([$conditionalAccessKey])
->searchTable(null)
->assertCanSeeTableRecords([$conditionalAccessKey, $deviceConfigurationKey, $scopeTagKey]);
});
it('sorts coverage rows by type and label deterministically', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$appProtectionKey = inventoryCoverageRecordKey('policy', 'appProtectionPolicy');
$conditionalAccessKey = inventoryCoverageRecordKey('policy', 'conditionalAccessPolicy');
$deviceComplianceKey = inventoryCoverageRecordKey('policy', 'deviceCompliancePolicy');
$adminTemplatesKey = inventoryCoverageRecordKey('policy', 'groupPolicyConfiguration');
$appConfigDeviceKey = inventoryCoverageRecordKey('policy', 'managedDeviceAppConfiguration');
$appConfigMamKey = inventoryCoverageRecordKey('policy', 'mamAppConfiguration');
inventoryCoverageComponent($user, $tenant)
->sortTable('type')
->assertCanSeeTableRecords([$appProtectionKey, $conditionalAccessKey, $deviceComplianceKey], inOrder: true)
->sortTable('label')
->assertCanSeeTableRecords([$adminTemplatesKey, $appConfigDeviceKey, $appConfigMamKey], inOrder: true);
});
it('filters coverage rows by category and restore mode when restore metadata exists', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$assignmentFilterKey = inventoryCoverageRecordKey('foundation', 'assignmentFilter');
$scopeTagKey = inventoryCoverageRecordKey('foundation', 'roleScopeTag');
$roleDefinitionKey = inventoryCoverageRecordKey('foundation', 'intuneRoleDefinition');
$roleAssignmentKey = inventoryCoverageRecordKey('foundation', 'intuneRoleAssignment');
$deviceConfigurationKey = inventoryCoverageRecordKey('policy', 'deviceConfiguration');
$conditionalAccessKey = inventoryCoverageRecordKey('policy', 'conditionalAccessPolicy');
$securityBaselineKey = inventoryCoverageRecordKey('policy', 'securityBaselinePolicy');
inventoryCoverageComponent($user, $tenant)
->assertTableFilterExists('category')
->assertTableFilterExists('restore')
->filterTable('category', 'Foundations')
->assertCanSeeTableRecords([$assignmentFilterKey, $scopeTagKey])
->assertCanNotSeeTableRecords([$deviceConfigurationKey, $conditionalAccessKey, $roleDefinitionKey, $roleAssignmentKey])
->removeTableFilters()
->filterTable('category', 'RBAC')
->assertCanSeeTableRecords([$roleDefinitionKey, $roleAssignmentKey])
->assertCanNotSeeTableRecords([$assignmentFilterKey, $scopeTagKey, $deviceConfigurationKey])
->removeTableFilters()
->filterTable('restore', 'preview-only')
->assertCanSeeTableRecords([$conditionalAccessKey, $securityBaselineKey, $roleDefinitionKey, $roleAssignmentKey])
->assertCanNotSeeTableRecords([$deviceConfigurationKey, $assignmentFilterKey]);
});
it('omits the restore filter when the runtime dataset has no restore metadata', function (): void {
removeInventoryCoverageRestoreMetadata();
[$user, $tenant] = createUserWithTenant(role: 'owner');
$component = inventoryCoverageComponent($user, $tenant)
->assertTableFilterExists('category');
expect($component->instance()->getTable()->getFilter('restore'))->toBeNull();
});
it('shows a single clear-filters empty state action and can reset back to a populated dataset', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$conditionalAccessKey = inventoryCoverageRecordKey('policy', 'conditionalAccessPolicy');
inventoryCoverageComponent($user, $tenant)
->assertTableEmptyStateActionsExistInOrder(['clear_filters'])
->searchTable('no-such-coverage-entry')
->assertCountTableRecords(0)
->assertSee('No coverage entries match this view')
->assertSee('Clear filters')
->searchTable(null)
->assertCountTableRecords(
count(config('tenantpilot.supported_policy_types', [])) + count(config('tenantpilot.foundation_types', [])),
)
->assertCanSeeTableRecords([$conditionalAccessKey]);
});
it('preserves badge semantics and dependency indicators in the interactive table columns', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$conditionalAccessKey = inventoryCoverageRecordKey('policy', 'conditionalAccessPolicy');
$deviceConfigurationKey = inventoryCoverageRecordKey('policy', 'deviceConfiguration');
$assignmentFilterKey = inventoryCoverageRecordKey('foundation', 'assignmentFilter');
$typeSpec = TagBadgeCatalog::spec(TagBadgeDomain::PolicyType, 'conditionalAccessPolicy');
$categorySpec = TagBadgeCatalog::spec(TagBadgeDomain::PolicyCategory, 'Conditional Access');
$restoreSpec = BadgeCatalog::spec(BadgeDomain::PolicyRestoreMode, 'preview-only');
$riskSpec = BadgeCatalog::spec(BadgeDomain::PolicyRisk, 'high');
inventoryCoverageComponent($user, $tenant)
->assertTableColumnFormattedStateSet('label', $typeSpec->label, $conditionalAccessKey)
->assertTableColumnFormattedStateSet('category', $categorySpec->label, $conditionalAccessKey)
->assertTableColumnFormattedStateSet('restore', $restoreSpec->label, $conditionalAccessKey)
->assertTableColumnFormattedStateSet('risk', $riskSpec->label, $conditionalAccessKey)
->assertTableColumnFormattedStateSet('segment', 'Policy', $conditionalAccessKey)
->assertTableColumnFormattedStateSet('segment', 'Foundation', $assignmentFilterKey)
->assertTableColumnStateSet('dependencies', true, $deviceConfigurationKey)
->assertTableColumnStateSet('dependencies', false, $assignmentFilterKey)
->assertTableColumnExists('label', function (TextColumn $column) use ($typeSpec): bool {
$state = $column->getState();
return $column->isBadge()
&& $column->getColor($state) === $typeSpec->color
&& $column->getIcon($state) === $typeSpec->icon;
}, $conditionalAccessKey)
->assertTableColumnExists('category', function (TextColumn $column) use ($categorySpec): bool {
$state = $column->getState();
return $column->isBadge()
&& $column->getColor($state) === $categorySpec->color
&& $column->getIcon($state) === $categorySpec->icon;
}, $conditionalAccessKey)
->assertTableColumnExists('restore', function (TextColumn $column) use ($restoreSpec): bool {
$state = $column->getState();
return $column->isBadge()
&& $column->getColor($state) === $restoreSpec->color
&& $column->getIcon($state) === $restoreSpec->icon;
}, $conditionalAccessKey)
->assertTableColumnExists('risk', function (TextColumn $column) use ($riskSpec): bool {
$state = $column->getState();
return $column->isBadge()
&& $column->getColor($state) === $riskSpec->color
&& $column->getIcon($state) === $riskSpec->icon;
}, $conditionalAccessKey)
->assertTableColumnExists('dependencies', function (IconColumn $column): bool {
$state = $column->getState();
return $state === true
&& $column->getColor($state) === 'success'
&& (string) $column->getIcon($state) === 'heroicon-m-check-circle';
}, $deviceConfigurationKey)
->assertTableColumnExists('dependencies', function (IconColumn $column): bool {
$state = $column->getState();
return $state === false
&& $column->getColor($state) === 'gray'
&& (string) $column->getIcon($state) === 'heroicon-m-minus-circle';
}, $assignmentFilterKey);
});
it('returns 404 for non-members on the inventory coverage page even when RBAC foundations exist', function (): void {
[$owner, $tenant] = createUserWithTenant(role: 'owner');
$this->actingAs($owner);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$outsider = User::factory()->create();
WorkspaceMembership::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'user_id' => (int) $outsider->getKey(),
'role' => 'owner',
]);
$this->actingAs($outsider);
$tenant->makeCurrent();
Filament::setTenant($tenant, true);
$this->get(InventoryCoverage::getUrl(tenant: $tenant))
->assertNotFound();
});