TenantAtlas/apps/platform/tests/Unit/Support/TenantConfiguration/Spec430ExchangePowerShellResolverTest.php
ahmido 9b58a5696d feat: add Exchange PowerShell adapter contract slice 1 (#497)
Spec 430: Exchange PowerShell adapter contract slice 1. Validation was not rerun during PR creation handoff; branch contains implementation report and tests.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #497
2026-07-05 12:08:16 +00:00

122 lines
6.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\TenantConfigurationResourceType;
use App\Services\Graph\GraphContractRegistry;
use App\Services\TenantConfiguration\CoverageSourceContractDecision;
use App\Services\TenantConfiguration\CoverageSourceContractResolver;
use App\Services\TenantConfiguration\ResourceTypeRegistry;
use App\Support\TenantConfiguration\CaptureOutcome;
use App\Support\TenantConfiguration\ClaimState;
use App\Support\TenantConfiguration\CoverageLevel;
use App\Support\TenantConfiguration\EvidenceState;
use App\Support\TenantConfiguration\IdentityState;
use App\Support\TenantConfiguration\ResourceClass;
use App\Support\TenantConfiguration\RestoreTier;
use App\Support\TenantConfiguration\SourceClass;
use App\Support\TenantConfiguration\SupportState;
use App\Support\TenantConfiguration\Workload;
it('Spec430 resolves included Exchange PowerShell types as verified pending-capture adapter contracts', function (string $canonicalType, string $commandName): void {
$decision = (new CoverageSourceContractResolver(new GraphContractRegistry))
->resolve(spec430ResolverResourceType($canonicalType));
expect($decision->outcome)->toBe(CaptureOutcome::BlockedMissingContract)
->and($decision->reasonCode)->toBe(CoverageSourceContractDecision::CONTRACT_VERIFIED_PENDING_CAPTURE)
->and($decision->sourceContractState)->toBe(CoverageSourceContractDecision::CONTRACT_VERIFIED_PENDING_CAPTURE)
->and($decision->contractKey)->toBe('exchange_powershell.'.$canonicalType)
->and($decision->sourceEndpoint)->toBeNull()
->and($decision->capturable())->toBeFalse()
->and($decision->sourceMetadata['source_contract_state'])->toBe(CoverageSourceContractDecision::CONTRACT_VERIFIED_PENDING_CAPTURE)
->and($decision->sourceMetadata['provider_adapter_state'])->toBe('adapter_contract_available')
->and($decision->sourceMetadata['capture_eligibility_state'])->toBe('pending_capture')
->and($decision->sourceMetadata['source_surface'])->toBe('exchange_online_powershell_rest')
->and($decision->sourceMetadata['adapter_pattern'])->toBe('new_exchange_powershell_adapter')
->and($decision->sourceMetadata['provider_calls_allowed'])->toBeFalse()
->and($decision->sourceMetadata['execution_enabled'])->toBeFalse()
->and($decision->sourceMetadata['evidence_promotion_allowed'])->toBeFalse()
->and($decision->sourceMetadata['command_contract']['command_name'])->toBe($commandName)
->and(config("graph_contracts.types.{$canonicalType}", []))->toBe([]);
})->with([
'transportRule' => ['transportRule', 'Get-TransportRule'],
'remoteDomain' => ['remoteDomain', 'Get-RemoteDomain'],
'inboundConnector' => ['inboundConnector', 'Get-InboundConnector'],
]);
it('Spec430 preserves blocked or deferred states for excluded Exchange and Teams types', function (string $canonicalType, Workload $workload): void {
$decision = (new CoverageSourceContractResolver(new GraphContractRegistry))
->resolve(spec430AdHocResolverResourceType($canonicalType, $workload));
expect($decision->sourceContractState)->not->toBe(CoverageSourceContractDecision::CONTRACT_VERIFIED_PENDING_CAPTURE)
->and($decision->sourceMetadata['provider_adapter_state'] ?? null)->not->toBe('adapter_contract_available')
->and($decision->capturable())->toBeFalse()
->and(config("graph_contracts.types.{$canonicalType}", []))->toBe([]);
})->with([
'acceptedDomain' => ['acceptedDomain', Workload::Exchange],
'organizationConfig' => ['organizationConfig', Workload::Exchange],
'mailboxPlan' => ['mailboxPlan', Workload::Exchange],
'outboundConnector' => ['outboundConnector', Workload::Exchange],
'sharingPolicy' => ['sharingPolicy', Workload::Exchange],
'appPermissionPolicy' => ['appPermissionPolicy', Workload::Teams],
'appSetupPolicy' => ['appSetupPolicy', Workload::Teams],
'meetingPolicy' => ['meetingPolicy', Workload::Teams],
'messagingPolicy' => ['messagingPolicy', Workload::Teams],
'teamsUpdateManagementPolicy' => ['teamsUpdateManagementPolicy', Workload::Teams],
'teamsChannelsPolicy' => ['teamsChannelsPolicy', Workload::Teams],
'externalAccessPolicy' => ['externalAccessPolicy', Workload::Teams],
]);
it('Spec430 adds inboundConnector only to the inactive Exchange planning registry denominator', function (): void {
$definitions = collect(ResourceTypeRegistry::defaultDefinitions());
$inbound = $definitions->firstWhere('canonical_type', 'inboundConnector');
expect($inbound)->not->toBeNull()
->and($inbound['workload'])->toBe(Workload::Exchange->value)
->and($inbound['source_class'])->toBe(SourceClass::Tcm->value)
->and($inbound['support_state'])->toBe(SupportState::OutOfScope->value)
->and($inbound['default_coverage_level'])->toBe(CoverageLevel::Detected->value)
->and($inbound['default_evidence_state'])->toBe(EvidenceState::NotCaptured->value)
->and($inbound['default_claim_state'])->toBe(ClaimState::InternalOnly->value)
->and($inbound['restore_tier'])->toBe(RestoreTier::NotRestorable->value)
->and($definitions->firstWhere('canonical_type', 'outboundConnector'))->toBeNull()
->and($definitions->firstWhere('canonical_type', 'sharingPolicy'))->toBeNull()
->and($definitions->firstWhere('canonical_type', 'externalAccessPolicy'))->toBeNull();
});
function spec430ResolverResourceType(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);
}
function spec430AdHocResolverResourceType(string $canonicalType, Workload $workload): TenantConfigurationResourceType
{
$definition = collect(ResourceTypeRegistry::defaultDefinitions())
->firstWhere('canonical_type', $canonicalType);
if (is_array($definition)) {
return new TenantConfigurationResourceType($definition);
}
return new TenantConfigurationResourceType([
'canonical_type' => $canonicalType,
'display_name' => $canonicalType,
'source_class' => SourceClass::Tcm->value,
'workload' => $workload->value,
'resource_class' => ResourceClass::Configuration->value,
'support_state' => SupportState::OutOfScope->value,
'default_coverage_level' => CoverageLevel::Detected->value,
'default_evidence_state' => EvidenceState::NotCaptured->value,
'default_identity_state' => IdentityState::Derived->value,
'default_claim_state' => ClaimState::InternalOnly->value,
'restore_tier' => RestoreTier::NotRestorable->value,
'is_active' => true,
'metadata' => ['kernel' => 'coverage_v2'],
]);
}