TenantAtlas/apps/platform/tests/Unit/Baselines/CompareStrategyRegistryTest.php
ahmido e64bae9cfc feat: cut over tenant core to managed environments (#335)
## Summary
- replace the legacy Tenant and TenantMembership core models with ManagedEnvironment and ManagedEnvironmentMembership
- propagate the managed environment naming and key changes across Filament resources, pages, controllers, jobs, models, and supporting runtime paths
- add feature 279 spec artifacts and focused managed-environment test coverage for model behavior, route binding, panel context, authorization, and legacy guardrails

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ManagedEnvironment/LegacyTenantCoreGuardTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentAuthorizationTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentPanelContextTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentRouteBindingTest.php tests/Unit/ManagedEnvironment/ManagedEnvironmentContextResolverTest.php tests/Unit/ManagedEnvironment/ManagedEnvironmentModelTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`

## Notes
- branch pushed from commit `1123b122`
- browser smoke test file was added but not run in this pass

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #335
2026-05-07 06:38:14 +00:00

217 lines
7.6 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\ManagedEnvironment;
use App\Support\Baselines\BaselineScope;
use App\Support\Baselines\Compare\CompareOrchestrationContext;
use App\Support\Baselines\Compare\CompareStrategy;
use App\Support\Baselines\Compare\CompareStrategyCapability;
use App\Support\Baselines\Compare\CompareStrategyKey;
use App\Support\Baselines\Compare\CompareStrategyRegistry;
use App\Support\Governance\GovernanceDomainKey;
use App\Support\Governance\GovernanceSubjectClass;
it('selects a single compatible strategy family for a canonical scope entry', function (): void {
$registry = new CompareStrategyRegistry([
compareStrategyStub(
key: 'intune_policy',
capabilities: [
new CompareStrategyCapability(
strategyKey: CompareStrategyKey::intunePolicy(),
domainKeys: [GovernanceDomainKey::Intune->value, GovernanceDomainKey::PlatformFoundation->value],
subjectClasses: [
GovernanceSubjectClass::Policy->value,
GovernanceSubjectClass::ConfigurationResource->value,
],
),
],
),
]);
$scope = BaselineScope::fromJsonb([
'version' => 2,
'entries' => [
[
'domain_key' => GovernanceDomainKey::Intune->value,
'subject_class' => GovernanceSubjectClass::Policy->value,
'subject_type_keys' => ['deviceConfiguration'],
'filters' => [],
],
[
'domain_key' => GovernanceDomainKey::PlatformFoundation->value,
'subject_class' => GovernanceSubjectClass::ConfigurationResource->value,
'subject_type_keys' => ['assignmentFilter'],
'filters' => [],
],
],
]);
$selection = $registry->select($scope);
expect($selection->isSupported())->toBeTrue()
->and($selection->strategyKey?->value)->toBe('intune_policy')
->and($selection->matchedScopeEntries)->toHaveCount(2)
->and($selection->rejectedScopeEntries)->toBe([]);
});
it('rejects canonical scope entries when no strategy supports them', function (): void {
$registry = new CompareStrategyRegistry([
compareStrategyStub(
key: 'intune_policy',
capabilities: [
new CompareStrategyCapability(
strategyKey: CompareStrategyKey::intunePolicy(),
domainKeys: [GovernanceDomainKey::Intune->value],
subjectClasses: [GovernanceSubjectClass::Policy->value],
),
],
),
]);
$scope = new BaselineScope(
entries: [[
'domain_key' => GovernanceDomainKey::Entra->value,
'subject_class' => GovernanceSubjectClass::Control->value,
'subject_type_keys' => ['conditionalAccessPolicy'],
'filters' => [],
]],
version: 2,
);
$selection = $registry->select($scope);
expect($selection->isUnsupported())->toBeTrue()
->and($selection->strategyKey)->toBeNull()
->and($selection->matchedScopeEntries)->toBe([])
->and($selection->rejectedScopeEntries)->toHaveCount(1);
});
it('marks scope as mixed when multiple strategy families are required', function (): void {
$registry = new CompareStrategyRegistry([
compareStrategyStub(
key: 'intune_policy',
capabilities: [
new CompareStrategyCapability(
strategyKey: CompareStrategyKey::intunePolicy(),
domainKeys: [GovernanceDomainKey::Intune->value],
subjectClasses: [GovernanceSubjectClass::Policy->value],
),
],
),
compareStrategyStub(
key: 'future_control',
capabilities: [
new CompareStrategyCapability(
strategyKey: CompareStrategyKey::from('future_control'),
domainKeys: [GovernanceDomainKey::Entra->value],
subjectClasses: [GovernanceSubjectClass::Control->value],
),
],
),
]);
$scope = new BaselineScope(
entries: [
[
'domain_key' => GovernanceDomainKey::Intune->value,
'subject_class' => GovernanceSubjectClass::Policy->value,
'subject_type_keys' => ['deviceConfiguration'],
'filters' => [],
],
[
'domain_key' => GovernanceDomainKey::Entra->value,
'subject_class' => GovernanceSubjectClass::Control->value,
'subject_type_keys' => ['conditionalAccessPolicy'],
'filters' => [],
],
],
version: 2,
);
$selection = $registry->select($scope);
expect($selection->isMixed())->toBeTrue()
->and($selection->strategyKey)->toBeNull()
->and($selection->matchedScopeEntries)->toHaveCount(2)
->and($selection->diagnostics['matched_strategy_keys'] ?? [])->toEqual(['future_control', 'intune_policy']);
});
it('supports deterministic future-domain selection without implicit intune fallback', function (): void {
$registry = new CompareStrategyRegistry([
compareStrategyStub(
key: 'future_control',
capabilities: [
new CompareStrategyCapability(
strategyKey: CompareStrategyKey::from('future_control'),
domainKeys: [GovernanceDomainKey::Entra->value],
subjectClasses: [GovernanceSubjectClass::Control->value],
),
],
),
]);
$scope = new BaselineScope(
entries: [[
'domain_key' => GovernanceDomainKey::Entra->value,
'subject_class' => GovernanceSubjectClass::Control->value,
'subject_type_keys' => ['conditionalAccessPolicy'],
'filters' => [],
]],
version: 2,
);
$selection = $registry->select($scope);
expect($selection->isSupported())->toBeTrue()
->and($selection->strategyKey?->value)->toBe('future_control')
->and($registry->resolve('future_control'))->toBeInstanceOf(CompareStrategy::class);
});
it('throws when resolving an unknown strategy key', function (): void {
$registry = new CompareStrategyRegistry([]);
expect(fn (): CompareStrategy => $registry->resolve('missing_strategy'))
->toThrow(InvalidArgumentException::class, 'Unknown compare strategy');
});
/**
* @param list<CompareStrategyCapability> $capabilities
*/
function compareStrategyStub(string $key, array $capabilities): CompareStrategy
{
return new class($key, $capabilities) implements CompareStrategy
{
/**
* @param list<CompareStrategyCapability> $capabilities
*/
public function __construct(
private readonly string $keyValue,
private readonly array $capabilities,
) {}
public function key(): CompareStrategyKey
{
return CompareStrategyKey::from($this->keyValue);
}
public function capabilities(): array
{
return $this->capabilities;
}
public function compare(
CompareOrchestrationContext $context,
ManagedEnvironment $tenant,
array $baselineItems,
array $currentItems,
array $resolvedCurrentEvidence,
array $severityMapping,
): array {
return [
'subject_results' => [],
'diagnostics' => [],
];
}
};
}