Automated PR for spec 426 exchange teams core evidence identity readiness. Includes service changes and coverage/requirement/spec updates from commit fb4dc20c.
Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #493
112 lines
5.8 KiB
PHP
112 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\TenantConfigurationResourceType;
|
|
use App\Services\TenantConfiguration\CanonicalIdentityResolver;
|
|
use App\Services\TenantConfiguration\CoverageIdentityStrategyRegistry;
|
|
use App\Services\TenantConfiguration\ResourceTypeRegistry;
|
|
use App\Support\TenantConfiguration\CanonicalKeyKind;
|
|
use App\Support\TenantConfiguration\IdentityState;
|
|
|
|
it('Spec426 registers stable identity strategies for the four selected Exchange and Teams types', function (): void {
|
|
$strategies = app(CoverageIdentityStrategyRegistry::class)->strategies();
|
|
|
|
foreach (['transportRule', 'acceptedDomain', 'appPermissionPolicy', 'meetingPolicy'] as $canonicalType) {
|
|
$strategy = $strategies[$canonicalType] ?? null;
|
|
|
|
expect($strategy)->toBeArray()
|
|
->and($strategy['strategy_identifier'])->toStartWith('tcm.')
|
|
->and($strategy['requires_provider_connection_scope'])->toBeTrue()
|
|
->and($strategy['allows_derived_identity'])->toBeFalse()
|
|
->and($strategy['allows_experimental_identity'])->toBeFalse()
|
|
->and($strategy['derived_claims_allowed'])->toBeFalse();
|
|
|
|
$identityFields = [
|
|
...$strategy['preferred_identity_fields'],
|
|
...$strategy['fallback_identity_fields'],
|
|
];
|
|
|
|
expect(array_intersect($identityFields, ['displayName', 'DisplayName', 'name', 'Name', 'Identity']))
|
|
->toBe([], "{$canonicalType} must not treat display-only or name-like Identity fields as stable identity.");
|
|
}
|
|
});
|
|
|
|
it('Spec426 resolves stable source-backed identity from provider or natural keys', function (string $canonicalType, array $payload, CanonicalKeyKind $expectedKeyKind): void {
|
|
$result = app(CanonicalIdentityResolver::class)->resolve(
|
|
spec426IdentityResourceType($canonicalType),
|
|
$payload,
|
|
['source_contract_key' => $canonicalType, 'source_version' => 'v1.0'],
|
|
);
|
|
|
|
expect($result->identityState)->toBe(IdentityState::Stable)
|
|
->and($result->keyKind)->toBe($expectedKeyKind)
|
|
->and($result->sourceResourceId)->not->toStartWith('missing:');
|
|
})->with([
|
|
'transport rule guid' => ['transportRule', ['Guid' => 'b0f47b86-0875-46ba-8753-37d19bb0789a', 'Identity' => 'Transport Rule 1', 'DisplayName' => 'Mail flow rule'], CanonicalKeyKind::TcmResourceIdentifier],
|
|
'accepted domain natural key' => ['acceptedDomain', ['DomainName' => 'contoso.com', 'DisplayName' => 'Contoso'], CanonicalKeyKind::ProviderExternalId],
|
|
'app permission policy id' => ['appPermissionPolicy', ['policyId' => 'app-policy-global', 'Identity' => 'Global', 'DisplayName' => 'Global app policy'], CanonicalKeyKind::TcmResourceIdentifier],
|
|
'meeting policy id' => ['meetingPolicy', ['policyId' => 'meeting-policy-global', 'Identity' => 'Global', 'DisplayName' => 'Global meeting policy'], CanonicalKeyKind::TcmResourceIdentifier],
|
|
]);
|
|
|
|
it('Spec426 prefers stable provider IDs over name-like Identity values', function (string $canonicalType, array $payload, string $expectedField, string $expectedValue): void {
|
|
$result = app(CanonicalIdentityResolver::class)->resolve(
|
|
spec426IdentityResourceType($canonicalType),
|
|
$payload,
|
|
['source_contract_key' => $canonicalType, 'source_version' => 'v1.0'],
|
|
);
|
|
|
|
expect($result->identityState)->toBe(IdentityState::Stable)
|
|
->and($result->sourceIdentity['values']['field'])->toBe($expectedField)
|
|
->and($result->sourceIdentity['values']['value'])->toBe($expectedValue)
|
|
->and($result->sourceResourceId)->toBe($expectedValue);
|
|
})->with([
|
|
'transport rule guid before Identity' => ['transportRule', ['Identity' => 'Transport Rule 1', 'Guid' => 'b0f47b86-0875-46ba-8753-37d19bb0789a'], 'Guid', 'b0f47b86-0875-46ba-8753-37d19bb0789a'],
|
|
'app permission policy policyId before Identity' => ['appPermissionPolicy', ['Identity' => 'Global', 'policyId' => 'app-policy-global'], 'policyId', 'app-policy-global'],
|
|
'meeting policy policyId before Identity' => ['meetingPolicy', ['Identity' => 'Global', 'policyId' => 'meeting-policy-global'], 'policyId', 'meeting-policy-global'],
|
|
]);
|
|
|
|
it('Spec426 blocks display-only payloads from stable readiness identity', function (string $canonicalType): void {
|
|
$result = app(CanonicalIdentityResolver::class)->resolve(
|
|
spec426IdentityResourceType($canonicalType),
|
|
['DisplayName' => 'Shared display name', 'name' => 'Shared display name'],
|
|
['source_contract_key' => $canonicalType, 'source_version' => 'v1.0'],
|
|
);
|
|
|
|
expect($result->identityState)->toBe(IdentityState::MissingExternalId)
|
|
->and($result->sourceResourceId)->toStartWith('missing:')
|
|
->and($result->canonicalResourceKey)->not->toContain('Shared display name');
|
|
})->with([
|
|
'transportRule',
|
|
'acceptedDomain',
|
|
'appPermissionPolicy',
|
|
'meetingPolicy',
|
|
]);
|
|
|
|
it('Spec426 blocks Identity-only payloads from stable readiness identity', function (string $canonicalType): void {
|
|
$result = app(CanonicalIdentityResolver::class)->resolve(
|
|
spec426IdentityResourceType($canonicalType),
|
|
['Identity' => 'Global', 'DisplayName' => 'Global'],
|
|
['source_contract_key' => $canonicalType, 'source_version' => 'v1.0'],
|
|
);
|
|
|
|
expect($result->identityState)->toBe(IdentityState::MissingExternalId)
|
|
->and($result->sourceResourceId)->toStartWith('missing:')
|
|
->and($result->canonicalResourceKey)->not->toContain('Global');
|
|
})->with([
|
|
'transportRule',
|
|
'acceptedDomain',
|
|
'appPermissionPolicy',
|
|
'meetingPolicy',
|
|
]);
|
|
|
|
function spec426IdentityResourceType(string $canonicalType): TenantConfigurationResourceType
|
|
{
|
|
$definition = collect(ResourceTypeRegistry::defaultDefinitions())
|
|
->firstWhere('canonical_type', $canonicalType);
|
|
|
|
expect($definition)->not->toBeNull("Missing default resource type definition for {$canonicalType}.");
|
|
|
|
return new TenantConfigurationResourceType($definition);
|
|
}
|