syncDefaults(); config([ ExchangePowerShellInvocationGate::FEATURE_CONFIG_PATH => true, ExchangePowerShellInvocationGate::PRODUCTION_RUNNER_CONFIG_PATH => true, 'tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate'], 'tenantpilot.exchange_powershell.permission_evidence.currentness_days' => 30, ]); }); it('Spec436 appends content-backed Exchange PowerShell evidence through target normalizers and hash builder', function ( string $canonicalType, string $commandName, string $normalizerVersion, array $payload, ): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec436ReadyProviderConnection($environment); spec436Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec436CaptureRun($environment, $user, $connection, resourceTypes: [$canonicalType]); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: $canonicalType, runnerResult: ExchangePowerShellInvocationResult::succeeded( [$payload], context: ['output_state' => 'structured_collection', 'runner_mode' => 'fake'], ), ); $resource = TenantConfigurationResource::query()->sole(); $evidence = TenantConfigurationResourceEvidence::query()->sole(); $expectedHash = app(ExchangePowerShellHashInputBuilder::class)->hash( app(ExchangePowerShellHashInputBuilder::class)->build( resourceType: $canonicalType, sourceSurface: ExchangePowerShellCommandContracts::SOURCE_SURFACE, commandContractName: $commandName, commandContractVersion: ExchangePowerShellCommandContracts::COMMAND_CONTRACT_VERSION, payloadShapeVersion: ExchangePowerShellTargetEvidenceNormalizer::PAYLOAD_SHAPE_VERSION, normalizerVersion: $normalizerVersion, normalizedPayload: $evidence->normalized_payload, ), ); expect($result) ->toMatchArray([ 'canonical_type' => $canonicalType, 'outcome' => CaptureOutcome::Captured->value, 'adapter_outcome' => CaptureOutcome::Captured->value, 'item_count' => 1, 'reason_code' => null, 'source_contract_key' => 'exchange_powershell.'.$canonicalType, ]) ->and($result['evidence_ids'])->toBe([(int) $evidence->getKey()]) ->and($resource->workspace_id)->toBe((int) $environment->workspace_id) ->and($resource->managed_environment_id)->toBe((int) $environment->getKey()) ->and($resource->provider_connection_id)->toBe((int) $connection->getKey()) ->and($resource->latest_evidence_id)->toBe((int) $evidence->getKey()) ->and($resource->latest_evidence_state)->toBe(EvidenceState::ContentBacked) ->and($resource->latest_claim_state)->toBe(ClaimState::InternalOnly) ->and($evidence->operation_run_id)->toBe((int) $run->getKey()) ->and($evidence->workspace_id)->toBe((int) $environment->workspace_id) ->and($evidence->managed_environment_id)->toBe((int) $environment->getKey()) ->and($evidence->provider_connection_id)->toBe((int) $connection->getKey()) ->and($evidence->coverage_level)->toBe(CoverageLevel::ContentBacked) ->and($evidence->evidence_state)->toBe(EvidenceState::ContentBacked) ->and($evidence->capture_outcome)->toBe(CaptureOutcome::Captured) ->and($evidence->source_endpoint)->toBe(ExchangePowerShellCommandContracts::SOURCE_SURFACE.':'.$commandName) ->and($evidence->raw_payload)->toMatchArray($payload) ->and($evidence->normalized_payload['canonical_type'])->toBe($canonicalType) ->and($evidence->normalized_payload['source_surface'])->toBe(ExchangePowerShellCommandContracts::SOURCE_SURFACE) ->and($evidence->normalized_payload['payload_shape_version'])->toBe(ExchangePowerShellTargetEvidenceNormalizer::PAYLOAD_SHAPE_VERSION) ->and($evidence->normalized_payload['normalizer_version'])->toBe($normalizerVersion) ->and($evidence->normalized_payload['source']['source_contract_key'])->toBe('exchange_powershell.'.$canonicalType) ->and($evidence->normalized_payload['source']['command_contract_name'])->toBe($commandName) ->and($evidence->payload_hash)->toBe($expectedHash) ->and($evidence->permission_context['provider_connection_id'])->toBe((int) $connection->getKey()); $runJson = json_encode([ 'context' => $run->refresh()->context, 'summary_counts' => $run->summary_counts, 'failure_summary' => $run->failure_summary, ], JSON_THROW_ON_ERROR); expect($runJson) ->not->toContain('raw_stdout') ->not->toContain('raw_stderr') ->not->toContain('normalizer_preview') ->not->toContain('hash_preview') ->not->toContain('client_secret') ->not->toContain('access_token'); })->with([ 'transportRule' => [ 'transportRule', 'Get-TransportRule', 'exchange-transport-rule-readiness-v1', spec436TransportRulePayload(), ], 'remoteDomain' => [ 'remoteDomain', 'Get-RemoteDomain', 'exchange-remote-domain-readiness-v1', spec436RemoteDomainPayload(), ], 'inboundConnector' => [ 'inboundConnector', 'Get-InboundConnector', 'exchange-inbound-connector-readiness-v1', spec436InboundConnectorPayload(), ], ]); it('Spec436 blocks unsafe target-normalizer identity states before writer use', function ( string $canonicalType, array $items, string $expectedReasonCode, ): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec436ReadyProviderConnection($environment); spec436Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec436CaptureRun($environment, $user, $connection, resourceTypes: [$canonicalType]); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: $canonicalType, runnerResult: ExchangePowerShellInvocationResult::succeeded( $items, context: ['output_state' => 'structured_collection'], ), ); expect($result['adapter_outcome'])->toBe(ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_IDENTITY_BLOCKED) ->and($result['reason_code'])->toBe($expectedReasonCode) ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); })->with([ 'remoteDomain domain-only' => [ 'remoteDomain', [['DomainName' => 'contoso.example']], 'derived_identity_blocked', ], 'remoteDomain display-only' => [ 'remoteDomain', [['DisplayName' => 'Contoso remote domain']], 'display_name_only', ], 'remoteDomain duplicate identity' => [ 'remoteDomain', [ spec436RemoteDomainPayload(['Guid' => 'duplicate', 'RemoteDomainId' => 'duplicate', 'Identity' => 'duplicate']), spec436RemoteDomainPayload(['Guid' => 'duplicate', 'RemoteDomainId' => 'duplicate', 'Identity' => 'duplicate']), ], 'duplicate_stable_identity', ], 'remoteDomain conflicting aliases' => [ 'remoteDomain', [spec436RemoteDomainPayload(['Guid' => 'guid-a', 'RemoteDomainId' => 'guid-b', 'Identity' => 'guid-a'])], 'identity_conflict', ], 'inboundConnector missing identity' => [ 'inboundConnector', [['ConnectorType' => 'Partner', 'Enabled' => true]], 'missing_stable_external_id', ], ]); it('Spec436 keeps protected inbound connector configuration out of OperationRun while persisting raw payload only in evidence', function (): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec436ReadyProviderConnection($environment); spec436Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec436CaptureRun($environment, $user, $connection, resourceTypes: ['inboundConnector']); app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: 'inboundConnector', runnerResult: ExchangePowerShellInvocationResult::succeeded( [spec436InboundConnectorPayload([ 'SenderIPAddresses' => ['203.0.113.10'], 'SmartHosts' => ['mail.contoso.example'], 'TlsSenderCertificateName' => 'CN=secret-cert', 'Comment' => 'sensitive routing note', ])], context: ['output_state' => 'structured_collection'], ), ); $evidence = TenantConfigurationResourceEvidence::query()->sole(); $runJson = json_encode([ 'context' => $run->refresh()->context, 'summary_counts' => $run->summary_counts, 'failure_summary' => $run->failure_summary, ], JSON_THROW_ON_ERROR); $normalizedJson = json_encode($evidence->normalized_payload, JSON_THROW_ON_ERROR); expect($evidence->raw_payload['SenderIPAddresses'])->toBe(['203.0.113.10']) ->and(data_get($evidence->normalized_payload, 'routing.sender_ip_addresses.value'))->toBe('[redacted]') ->and(data_get($evidence->normalized_payload, 'routing.smart_hosts.value'))->toBe('[redacted]') ->and(data_get($evidence->normalized_payload, 'routing.tls_sender_certificate_name.value'))->toBe('[redacted]') ->and(data_get($evidence->normalized_payload, 'routing.comment.value'))->toBe('[redacted]') ->and($runJson) ->not->toContain('203.0.113.10') ->not->toContain('mail.contoso.example') ->not->toContain('secret-cert') ->not->toContain('sensitive routing note') ->and($normalizedJson) ->not->toContain('203.0.113.10') ->not->toContain('mail.contoso.example') ->not->toContain('secret-cert') ->not->toContain('sensitive routing note'); }); it('Spec436 appends evidence without mutating prior payloads and updates the latest pointer', function (): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec436ReadyProviderConnection($environment); spec436Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec436CaptureRun($environment, $user, $connection); $adapter = app(ExchangePowerShellEvidenceCaptureAdapter::class); $adapter->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: 'transportRule', runnerResult: ExchangePowerShellInvocationResult::succeeded( [spec436TransportRulePayload(['Enabled' => true, 'WhenChanged' => '2026-07-08T10:00:00Z'])], context: ['output_state' => 'structured_collection'], ), ); $firstEvidence = TenantConfigurationResourceEvidence::query()->sole(); $firstHash = $firstEvidence->payload_hash; $firstPayload = $firstEvidence->normalized_payload; $adapter->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: 'transportRule', runnerResult: ExchangePowerShellInvocationResult::succeeded( [spec436TransportRulePayload(['Enabled' => false, 'WhenChanged' => '2026-07-09T10:00:00Z'])], context: ['output_state' => 'structured_collection'], ), ); $resource = TenantConfigurationResource::query()->sole(); $evidenceRows = TenantConfigurationResourceEvidence::query()->orderBy('id')->get(); expect($evidenceRows)->toHaveCount(2) ->and($resource->latest_evidence_id)->toBe((int) $evidenceRows[1]->getKey()) ->and($evidenceRows[0]->payload_hash)->toBe($firstHash) ->and($evidenceRows[0]->normalized_payload)->toBe($firstPayload) ->and($evidenceRows[1]->payload_hash)->not->toBe($firstHash); }); it('Spec436 treats empty collections as zero item summaries without fake evidence', function (): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec436ReadyProviderConnection($environment); spec436Credential($connection, ProviderCredentialKind::Certificate->value); $run = spec436CaptureRun($environment, $user, $connection); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: 'transportRule', runnerResult: ExchangePowerShellInvocationResult::succeeded( [], context: ['output_state' => 'empty_collection'], ), ); expect($result['adapter_outcome'])->toBe(ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_EMPTY_COLLECTION) ->and($result['outcome'])->toBe(CaptureOutcome::Captured->value) ->and($result['item_count'])->toBe(0) ->and($result['summary_counts']['total'])->toBe(0) ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); }); it('Spec436 readiness and output failures fail closed with no evidence', function ( string $case, callable $arrange, ExchangePowerShellInvocationResult $runnerResult, ): void { [$user, $environment] = createMinimalUserWithTenant(role: 'owner'); $connection = spec436ReadyProviderConnection($environment); $arrange($environment, $connection); $run = spec436CaptureRun($environment, $user, $connection); $result = app(ExchangePowerShellEvidenceCaptureAdapter::class)->capture( tenant: $environment, providerConnection: $connection, operationRun: $run, canonicalType: 'transportRule', runnerResult: $runnerResult, ); expect($case)->not->toBe('') ->and($result['adapter_outcome'])->toBe(ExchangePowerShellEvidenceCaptureAdapter::OUTCOME_PREREQUISITE_BLOCKED) ->and(TenantConfigurationResource::query()->count())->toBe(0) ->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0); })->with([ 'missing credential' => [ 'missing credential', function (ManagedEnvironment $environment, ProviderConnection $connection): void {}, ExchangePowerShellInvocationResult::succeeded([spec436TransportRulePayload()], context: ['output_state' => 'structured_collection']), ], 'client secret credential' => [ 'client secret credential', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::ClientSecret->value); }, ExchangePowerShellInvocationResult::succeeded([spec436TransportRulePayload()], context: ['output_state' => 'structured_collection']), ], 'stale permission evidence' => [ 'stale permission evidence', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); spec436SeedExchangePowerShellPermission($environment, $connection, [ 'last_checked_at' => now()->subDays(31), 'details' => [ 'observed_at' => now()->subDays(31)->toJSON(), 'verified_at' => now()->subDays(31)->toJSON(), ], ]); }, ExchangePowerShellInvocationResult::succeeded([spec436TransportRulePayload()], context: ['output_state' => 'structured_collection']), ], 'missing permission evidence' => [ 'missing permission evidence', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); ManagedEnvironmentPermission::query() ->where('managed_environment_id', $environment->getKey()) ->where('permission_key', 'Exchange.ManageAsApp') ->delete(); }, ExchangePowerShellInvocationResult::succeeded([spec436TransportRulePayload()], context: ['output_state' => 'structured_collection']), ], 'wrong-scope permission evidence' => [ 'wrong-scope permission evidence', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); spec436SeedExchangePowerShellPermission($environment, $connection, [ 'details' => [ 'provider_connection_id' => 999999, ], ]); }, ExchangePowerShellInvocationResult::succeeded([spec436TransportRulePayload()], context: ['output_state' => 'structured_collection']), ], 'warning-prefixed output' => [ 'warning-prefixed output', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); }, ExchangePowerShellInvocationResult::succeeded([spec436TransportRulePayload()], context: ['output_state' => 'warning_prefixed']), ], 'authentication failure' => [ 'authentication failure', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); }, ExchangePowerShellInvocationResult::failed( reasonCode: 'authentication_failed', failureCode: 'authentication_failed', message: 'Authentication failed safely.', context: ['output_state' => 'authentication_failed'], ), ], 'authorization permission failure' => [ 'authorization permission failure', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); }, ExchangePowerShellInvocationResult::failed( reasonCode: 'permission_denied', failureCode: 'permission_denied', message: 'Permission denied safely.', context: ['output_state' => 'permission_denied'], ), ], 'source unavailable' => [ 'source unavailable', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); }, ExchangePowerShellInvocationResult::failed( reasonCode: 'source_unavailable', failureCode: 'source_unavailable', message: 'Source unavailable safely.', context: ['output_state' => 'source_unavailable'], ), ], 'malformed output' => [ 'malformed output', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); }, ExchangePowerShellInvocationResult::succeededPayload('not-json', context: ['output_state' => 'malformed_json_collection']), ], 'scalar output' => [ 'scalar output', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); }, ExchangePowerShellInvocationResult::succeededPayload(42, context: ['output_state' => 'scalar_output']), ], 'binary output' => [ 'binary output', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); }, ExchangePowerShellInvocationResult::succeededPayload([], context: ['output_state' => 'non_utf8_or_binary']), ], 'oversized output' => [ 'oversized output', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); }, ExchangePowerShellInvocationResult::succeededPayload([], context: ['output_state' => 'stdout_oversized']), ], 'non-zero exit' => [ 'non-zero exit', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); }, ExchangePowerShellInvocationResult::failed( reasonCode: 'provider_failed', failureCode: 'nonzero_exit', message: 'Non-zero exit safely blocked.', context: ['output_state' => 'nonzero_exit'], ), ], 'partial output' => [ 'partial output', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); }, ExchangePowerShellInvocationResult::failed( reasonCode: 'partial_output', failureCode: 'partial_output', message: 'Partial output safely blocked.', context: ['output_state' => 'partial_output'], ), ], 'runner timeout' => [ 'runner timeout', function (ManagedEnvironment $environment, ProviderConnection $connection): void { spec436Credential($connection, ProviderCredentialKind::Certificate->value); }, ExchangePowerShellInvocationResult::failed( reasonCode: 'provider_failed', failureCode: 'timeout', message: 'Timed out safely.', context: ['output_state' => 'timeout'], ), ], ]); it('Spec436 introduces no generic promotion path product surface trigger tenant-id ownership or mini-platform', function (): void { $adapterSource = file_get_contents(app_path('Services/TenantConfiguration/ExchangePowerShellEvidenceCaptureAdapter.php')) ?: ''; $runtimeFiles = [ app_path('Services/TenantConfiguration/ExchangePowerShellEvidenceCaptureAdapter.php'), app_path('Services/TenantConfiguration/ExchangePowerShellEvidenceNormalizer.php'), app_path('Services/TenantConfiguration/ExchangePowerShellTargetEvidenceNormalizer.php'), app_path('Services/TenantConfiguration/ExchangeTransportRuleEvidenceNormalizer.php'), app_path('Services/TenantConfiguration/ExchangeRemoteDomainEvidenceNormalizer.php'), app_path('Services/TenantConfiguration/ExchangeInboundConnectorEvidenceNormalizer.php'), app_path('Services/TenantConfiguration/ExchangePowerShellHashInputBuilder.php'), app_path('Services/TenantConfiguration/ExchangePowerShellStructuredOutputEnvelope.php'), ]; $runtimeSource = collect($runtimeFiles) ->map(fn (string $file): string => file_get_contents($file) ?: '') ->implode("\n"); $captureOutcomeValues = array_map( static fn (CaptureOutcome $outcome): string => $outcome->value, CaptureOutcome::cases(), ); expect($adapterSource) ->not->toContain('GenericPayloadNormalizer') ->not->toContain('ExchangeTeamsComparablePayloadNormalizer') ->and($runtimeSource) ->not->toContain('ProviderGateway') ->not->toContain('fake_empty_success') ->not->toContain('customer_ready') ->not->toContain('restore_ready') ->and(preg_match('/\btenant_id\b/', $runtimeSource))->toBe(0) ->and(Schema::hasColumn('tenant_configuration_resources', 'tenant_id'))->toBeFalse() ->and(Schema::hasColumn('tenant_configuration_resource_evidence', 'tenant_id'))->toBeFalse() ->and(glob(app_path('Filament/**/*ExchangePowerShell*')) ?: [])->toBe([]) ->and(glob(app_path('Livewire/**/*ExchangePowerShell*')) ?: [])->toBe([]) ->and(glob(base_path('routes/*exchange*')) ?: [])->toBe([]) ->and(glob(resource_path('views/**/*ExchangePowerShell*')) ?: [])->toBe([]) ->and(glob(database_path('migrations/*exchange*evidence*')) ?: [])->toBe([]) ->and(glob(database_path('migrations/*tenant*configuration*exchange*')) ?: [])->toBe([]) ->and(File::exists(app_path('Jobs/TenantConfiguration/CaptureExchangePowerShellEvidenceJob.php')))->toBeFalse() ->and(File::exists(app_path('Console/Commands/CaptureExchangePowerShellEvidence.php')))->toBeFalse() ->and($captureOutcomeValues) ->toContain(CaptureOutcome::Captured->value) ->not->toContain('customer_ready') ->not->toContain('certified') ->not->toContain('renderable') ->not->toContain('comparable'); }); /** * @param array $overrides */ function spec436ReadyProviderConnection( ManagedEnvironment $environment, array $overrides = [], bool $seedExchangePowerShellPermission = true, ): ProviderConnection { $connection = ProviderConnection::factory() ->dedicated() ->consentGranted() ->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'entra_tenant_id' => $environment->providerTenantContext(), 'is_default' => true, 'verification_status' => ProviderVerificationStatus::Healthy->value, 'last_health_check_at' => now(), ...$overrides, ]); if ($seedExchangePowerShellPermission) { spec436SeedExchangePowerShellPermission($environment, $connection); } return $connection; } /** * @param array $overrides */ function spec436SeedExchangePowerShellPermission( ManagedEnvironment $environment, ProviderConnection $connection, array $overrides = [], ): ManagedEnvironmentPermission { $details = [ 'source' => 'provider_verification', 'observed_at' => now()->toJSON(), 'verified_at' => now()->toJSON(), 'evaluator' => 'exchange_powershell_permission_evidence', 'evaluator_version' => 'v1', 'workspace_id' => (int) $connection->workspace_id, 'managed_environment_id' => (int) $connection->managed_environment_id, 'provider' => (string) $connection->provider, 'provider_connection_id' => (int) $connection->getKey(), ]; if (is_array($overrides['details'] ?? null)) { $details = array_replace($details, $overrides['details']); } unset($overrides['details']); return ManagedEnvironmentPermission::query()->updateOrCreate( [ 'managed_environment_id' => (int) $environment->getKey(), 'permission_key' => 'Exchange.ManageAsApp', 'workspace_id' => (int) $environment->workspace_id, ], [ 'status' => 'granted', 'details' => $details, 'last_checked_at' => now(), ...$overrides, ], ); } /** * @param array $overrides */ function spec436Credential(ProviderConnection $connection, string $kind, array $overrides = []): ProviderCredential { return ProviderCredential::factory()->create([ 'provider_connection_id' => (int) $connection->getKey(), 'type' => $kind, 'credential_kind' => $kind, 'source' => ProviderCredentialSource::DedicatedManual->value, 'last_rotated_at' => now(), 'expires_at' => now()->addYear(), ...$overrides, ]); } /** * @param array $overrides * @param list $resourceTypes */ function spec436CaptureRun( ManagedEnvironment $environment, App\Models\User $user, ProviderConnection $connection, array $overrides = [], array $resourceTypes = ['transportRule'], ): OperationRun { $context = [ 'target_scope' => [ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'provider_connection_id' => (int) $connection->getKey(), ], 'resource_types' => $resourceTypes, 'required_capability' => 'exchange_powershell_invoke', 'runner_mode' => 'fake', ]; if (is_array($overrides['context'] ?? null)) { $context = array_replace_recursive($context, $overrides['context']); } unset($overrides['context']); return OperationRun::factory()->withUser($user)->forTenant($environment)->create([ 'type' => OperationRunType::TenantConfigurationCapture->value, 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, 'context' => $context, ...$overrides, ]); } /** * @param array $overrides * @return array */ function spec436TransportRulePayload(array $overrides = []): array { return [ 'RuleId' => 'spec436-rule-1', 'Guid' => 'spec436-rule-1', 'DisplayName' => 'Spec 436 transport rule', 'Name' => 'Spec 436 transport rule', 'Priority' => 0, 'Mode' => 'Enforce', 'State' => 'Enabled', 'Enabled' => true, 'Conditions' => ['SenderDomainIs' => ['contoso.example']], 'Actions' => ['RedirectMessageTo' => ['security@contoso.example']], 'WhenChanged' => 'volatile', ...$overrides, ]; } /** * @param array $overrides * @return array */ function spec436RemoteDomainPayload(array $overrides = []): array { return [ 'RemoteDomainId' => 'spec436-remote-domain-1', 'Guid' => 'spec436-remote-domain-1', 'Identity' => 'spec436-remote-domain-1', 'DomainName' => 'contoso.example', 'DisplayName' => 'Contoso remote domain', 'IsDefault' => false, 'AllowedOOFType' => 'External', 'AutoReplyEnabled' => true, ...$overrides, ]; } /** * @param array $overrides * @return array */ function spec436InboundConnectorPayload(array $overrides = []): array { return [ 'ConnectorId' => 'spec436-inbound-connector-1', 'Guid' => 'spec436-inbound-connector-1', 'Identity' => 'spec436-inbound-connector-1', 'DisplayName' => 'Spec 436 inbound connector', 'Name' => 'Spec 436 inbound connector', 'ConnectorType' => 'Partner', 'Enabled' => true, 'RequireTls' => true, ...$overrides, ]; }