TenantAtlas/apps/platform/tests/Feature/Filament/BaselineProfileScopeV2PersistenceTest.php

227 lines
9.1 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Resources\BaselineProfileResource;
use App\Filament\Resources\BaselineProfileResource\Pages\CreateBaselineProfile;
use App\Filament\Resources\BaselineProfileResource\Pages\EditBaselineProfile;
use App\Filament\Resources\BaselineProfileResource\Pages\ViewBaselineProfile;
use App\Models\BaselineProfile;
use App\Support\Governance\GovernanceSubjectTaxonomyRegistry;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\DB;
use Livewire\Livewire;
it('persists canonical v2 scope when creating a baseline profile through the current selectors', function (): void {
config()->set('tenantpilot.supported_policy_types', [
['type' => 'deviceConfiguration', 'label' => 'Device Configuration'],
['type' => 'deviceCompliancePolicy', 'label' => 'Device Compliance'],
]);
config()->set('tenantpilot.foundation_types', [
['type' => 'assignmentFilter', 'label' => 'Assignment Filter', 'baseline_compare' => ['supported' => true]],
['type' => 'intuneRoleAssignment', 'label' => 'Intune RBAC Role Assignment', 'baseline_compare' => ['supported' => false]],
]);
[$user, $tenant] = createUserWithTenant(role: 'owner');
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
Livewire::actingAs($user)
->test(CreateBaselineProfile::class)
->fillForm([
'name' => 'Canonical baseline profile',
'scope_jsonb.policy_types' => ['deviceConfiguration'],
'scope_jsonb.foundation_types' => ['assignmentFilter'],
])
->call('create')
->assertHasNoFormErrors()
->assertNotified();
$profile = BaselineProfile::query()
->where('workspace_id', (int) $tenant->workspace_id)
->where('name', 'Canonical baseline profile')
->sole();
expect($profile->scope_jsonb)->toBe([
'policy_types' => ['deviceConfiguration'],
'foundation_types' => ['assignmentFilter'],
]);
expect($profile->canonicalScopeJsonb())->toBe([
'version' => 2,
'entries' => [
[
'domain_key' => 'intune',
'subject_class' => 'policy',
'subject_type_keys' => ['deviceConfiguration'],
'filters' => [],
],
[
'domain_key' => 'platform_foundation',
'subject_class' => 'configuration_resource',
'subject_type_keys' => ['assignmentFilter'],
'filters' => [],
],
],
]);
});
it('normalizes legacy scope on read and saves it forward as canonical v2 on edit', function (): void {
config()->set('tenantpilot.supported_policy_types', [
['type' => 'deviceConfiguration', 'label' => 'Device Configuration'],
['type' => 'deviceCompliancePolicy', 'label' => 'Device Compliance'],
]);
config()->set('tenantpilot.foundation_types', [
['type' => 'assignmentFilter', 'label' => 'Assignment Filter', 'baseline_compare' => ['supported' => true]],
]);
[$user, $tenant] = createUserWithTenant(role: 'owner');
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
$profileId = BaselineProfile::query()->insertGetId([
'workspace_id' => (int) $tenant->workspace_id,
'name' => 'Legacy baseline profile',
'description' => null,
'version_label' => null,
'status' => 'active',
'capture_mode' => 'opportunistic',
'scope_jsonb' => json_encode([
'policy_types' => [],
'foundation_types' => ['assignmentFilter'],
], JSON_THROW_ON_ERROR),
'active_snapshot_id' => null,
'created_by_user_id' => (int) $user->getKey(),
'created_at' => now(),
'updated_at' => now(),
]);
$profile = BaselineProfile::query()->findOrFail($profileId);
expect($profile->normalizedScope()->normalizationLineage())->toMatchArray([
'source_shape' => 'legacy',
'normalized_on_read' => true,
'save_forward_required' => true,
'legacy_keys_present' => ['policy_types', 'foundation_types'],
])->and($profile->scope_jsonb)->toBe([
'policy_types' => [],
'foundation_types' => ['assignmentFilter'],
]);
Livewire::actingAs($user)
->test(EditBaselineProfile::class, ['record' => $profileId])
->fillForm([
'description' => 'Updated after normalization',
])
->call('save')
->assertHasNoFormErrors()
->assertNotified();
$profile->refresh();
$registry = app(GovernanceSubjectTaxonomyRegistry::class);
expect($profile->canonicalScopeJsonb())->toBe([
'version' => 2,
'entries' => [
[
'domain_key' => 'intune',
'subject_class' => 'policy',
'subject_type_keys' => $registry->activeLegacyBucketKeys('policy_types'),
'filters' => [],
],
[
'domain_key' => 'platform_foundation',
'subject_class' => 'configuration_resource',
'subject_type_keys' => ['assignmentFilter'],
'filters' => [],
],
],
])->and($profile->normalizedScope()->normalizationLineage())->toMatchArray([
'source_shape' => 'canonical_v2',
'normalized_on_read' => false,
'save_forward_required' => false,
]);
});
it('summarizes governed subjects, readiness, and save-forward feedback for current selector payloads', function (): void {
config()->set('tenantpilot.supported_policy_types', [
['type' => 'deviceConfiguration', 'label' => 'Device Configuration'],
['type' => 'deviceCompliancePolicy', 'label' => 'Device Compliance'],
]);
config()->set('tenantpilot.foundation_types', [
['type' => 'assignmentFilter', 'label' => 'Assignment Filter', 'baseline_compare' => ['supported' => true]],
]);
$payload = [
'policy_types' => ['deviceConfiguration'],
'foundation_types' => ['assignmentFilter'],
];
expect(BaselineProfileResource::scopeSummaryText($payload))
->toBe('Intune policies: Device Configuration; Platform foundation configuration resources: Assignment Filter')
->and(BaselineProfileResource::scopeSupportReadinessText($payload))
->toBe('Capture: ready. Compare: ready.')
->and(BaselineProfileResource::scopeSelectionFeedbackText($payload))
->toBe('This Intune-first selection will be saved forward as canonical governed-subject scope V2.');
});
it('shows normalization lineage on the baseline profile detail surface before a legacy row is saved forward', function (): void {
config()->set('tenantpilot.supported_policy_types', [
['type' => 'deviceConfiguration', 'label' => 'Device Configuration'],
['type' => 'deviceCompliancePolicy', 'label' => 'Device Compliance'],
]);
[$user, $tenant] = createUserWithTenant(role: 'owner');
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
$profileId = BaselineProfile::query()->insertGetId([
'workspace_id' => (int) $tenant->workspace_id,
'name' => 'Legacy lineage profile',
'description' => null,
'version_label' => null,
'status' => 'active',
'capture_mode' => 'opportunistic',
'scope_jsonb' => json_encode([
'policy_types' => ['deviceConfiguration'],
'foundation_types' => [],
], JSON_THROW_ON_ERROR),
'active_snapshot_id' => null,
'created_by_user_id' => (int) $user->getKey(),
'created_at' => now(),
'updated_at' => now(),
]);
Livewire::actingAs($user)
->test(ViewBaselineProfile::class, ['record' => $profileId])
->assertSee('Governed subject summary')
->assertSee('Intune policies: Device Configuration')
->assertSee('Legacy Intune buckets are being normalized and will be saved forward as canonical V2 on the next successful save.');
});
it('rejects unsupported canonical filters when creating a baseline profile', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
$component = Livewire::actingAs($user)
->test(CreateBaselineProfile::class);
$page = $component->instance();
$method = new \ReflectionMethod($page, 'mutateFormDataBeforeCreate');
$method->setAccessible(true);
expect(fn () => $method->invoke($page, [
'name' => 'Invalid filtered baseline',
'scope_jsonb' => [
'version' => 2,
'entries' => [
[
'domain_key' => 'intune',
'subject_class' => 'policy',
'subject_type_keys' => ['deviceConfiguration'],
'filters' => ['tenant_ids' => ['tenant-a']],
],
],
],
]))->toThrow(ValidationException::class, 'Filters are not supported');
expect(BaselineProfile::query()->where('name', 'Invalid filtered baseline')->exists())->toBeFalse();
});