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(); });