TenantAtlas/apps/platform/tests/Unit/Support/TenantConfiguration/Spec421EntraComparableDiffTest.php
ahmido 69d4ecbbd2 feat: complete spec 421 Entra comparable/renderable pack (#488)
Implements the bounded Spec 421 Entra comparable/renderable pack on the existing Coverage v2 operator surface.

- Adds typed Conditional Access normalization, comparison, and render summaries
- Keeps Security Defaults and other optional Entra types deferred until evidence-backed
- Preserves the existing Coverage v2 surface with claim-guard and redaction hardening
- Includes focused unit, feature, and browser coverage already recorded in the implementation report

Validation is documented in `specs/421-entra-core-comparable-renderable-pack/implementation-report.md`.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #488
2026-06-27 22:12:01 +00:00

70 lines
3.0 KiB
PHP

<?php
declare(strict_types=1);
use App\Services\TenantConfiguration\EntraCoverageComparator;
it('Spec421 treats volatile-only Conditional Access differences as unchanged', function (): void {
$result = app(EntraCoverageComparator::class)->compare(
'conditionalAccessPolicy',
spec421ComparablePayload(['modifiedDateTime' => '2026-06-27T10:00:00Z']),
spec421ComparablePayload(['modifiedDateTime' => '2026-06-27T11:00:00Z']),
);
expect($result['changed'])->toBeFalse()
->and($result['classification'])->toBe('unchanged')
->and(collect($result['changes'])->pluck('classification'))->toContain('ignored_volatile');
});
it('Spec421 detects material Conditional Access changes with bounded importance', function (array $after, string $field, string $importance): void {
$result = app(EntraCoverageComparator::class)->compare(
'conditionalAccessPolicy',
spec421ComparablePayload(),
spec421ComparablePayload($after),
);
$change = collect($result['changes'])->firstWhere('field', $field);
expect($result['changed'])->toBeTrue()
->and($result['classification'])->toBe('changed')
->and($change)->not->toBeNull()
->and($change['classification'])->toBe('changed')
->and($change['importance'])->toBe($importance);
})->with([
'state' => [['state' => 'disabled'], 'state', 'critical'],
'target users' => [['conditions' => ['users' => ['includeUsers' => ['All', 'group-a']]]], 'targets.users.include_users', 'important'],
'grant controls' => [['grantControls' => ['operator' => 'OR', 'builtInControls' => ['mfa', 'compliantDevice']]], 'grant_controls.built_in_controls', 'important'],
'session controls' => [['sessionControls' => ['signInFrequency' => ['value' => 4, 'type' => 'hours', 'isEnabled' => true]]], 'session_controls.signInFrequency.value', 'important'],
]);
it('Spec421 records redacted and unsupported fields as non-material diagnostics', function (): void {
$result = app(EntraCoverageComparator::class)->compare(
'conditionalAccessPolicy',
spec421ComparablePayload(),
spec421ComparablePayload(['clientSecret' => 'spec421-client-secret']),
);
expect($result['changed'])->toBeFalse()
->and(collect($result['changes'])->pluck('classification'))->toContain('redacted', 'unsupported_field')
->and(json_encode($result, JSON_THROW_ON_ERROR))->not->toContain('spec421-client-secret');
});
function spec421ComparablePayload(array $overrides = []): array
{
return array_replace_recursive([
'id' => 'cap-1',
'displayName' => 'Require MFA',
'state' => 'enabled',
'conditions' => [
'users' => ['includeUsers' => ['All']],
'applications' => ['includeApplications' => ['Office365']],
],
'grantControls' => [
'operator' => 'OR',
'builtInControls' => ['mfa'],
],
'sessionControls' => [
'signInFrequency' => ['value' => 8, 'type' => 'hours', 'isEnabled' => true],
],
], $overrides);
}