245 lines
11 KiB
PHP
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();
|
|
});
|