TenantAtlas/apps/platform/tests/Feature/TenantConfiguration/Spec433ExchangePowerShellEvidenceReadinessTest.php
Ahmed Darrazi 158071bfb4
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 5m4s
feat: add Exchange credential permission evidence readiness
2026-07-08 01:53:53 +02:00

544 lines
28 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\ManagedEnvironment;
use App\Models\ManagedEnvironmentPermission;
use App\Models\ProviderConnection;
use App\Models\ProviderCredential;
use App\Models\TenantConfigurationResource;
use App\Models\TenantConfigurationResourceEvidence;
use App\Services\TenantConfiguration\ExchangePowerShellCommandContract;
use App\Services\TenantConfiguration\ExchangePowerShellCommandContracts;
use App\Services\TenantConfiguration\ExchangePowerShellCredentialReferenceResolver;
use App\Services\TenantConfiguration\ExchangePowerShellInvocationContext;
use App\Services\TenantConfiguration\ExchangePowerShellInvocationGate;
use App\Services\TenantConfiguration\ExchangePowerShellInvocationReadinessEvaluator;
use App\Services\TenantConfiguration\ExchangePowerShellPermissionEvidenceEvaluator;
use App\Services\TenantConfiguration\ExchangePowerShellProcessExecutor;
use App\Services\TenantConfiguration\ExchangePowerShellProcessResult;
use App\Services\TenantConfiguration\ExchangePowerShellProductionRunner;
use App\Services\TenantConfiguration\FakeExchangePowerShellProcessExecutor;
use App\Services\TenantConfiguration\ResourceTypeRegistry;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Providers\Capabilities\ProviderCapabilityEvaluator;
use App\Support\Providers\Capabilities\ProviderCapabilityStatus;
use App\Support\Providers\ProviderConsentStatus;
use App\Support\Providers\ProviderCredentialKind;
use App\Support\Providers\ProviderCredentialSource;
use App\Support\Providers\ProviderVerificationStatus;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema;
beforeEach(function (): void {
app(ResourceTypeRegistry::class)->syncDefaults();
config([
ExchangePowerShellInvocationGate::FEATURE_CONFIG_PATH => true,
ExchangePowerShellInvocationGate::PRODUCTION_RUNNER_CONFIG_PATH => true,
'tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => [],
'tenantpilot.exchange_powershell.permission_evidence.currentness_days' => 30,
'tenantpilot.exchange_powershell.runtime.allowed_environments' => [app()->environment()],
'tenantpilot.exchange_powershell.runtime.module_check_enabled' => true,
]);
});
it('Spec433 uses existing provider credential and managed environment permission metadata without schema changes', function (): void {
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = spec433ProviderConnection($environment);
spec433Credential($connection, ProviderCredentialKind::Certificate->value);
spec433SeedExchangePowerShellPermission($environment, $connection);
config(['tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate']]);
$result = app(ExchangePowerShellInvocationReadinessEvaluator::class)->evaluate($environment, $connection);
expect($result->allowed)->toBeTrue()
->and($result->context['readiness_state'])->toBe('ready_for_live_invocation')
->and($result->context['credential_state'])->toBe('certificate_supported')
->and($result->context['permission_evidence_state'])->toBe('verified')
->and(Schema::hasColumn('provider_credentials', 'provider_connection_id'))->toBeTrue()
->and(Schema::hasColumn('provider_credentials', 'credential_kind'))->toBeTrue()
->and(Schema::hasColumn('provider_credentials', 'source'))->toBeTrue()
->and(Schema::hasColumn('provider_credentials', 'last_rotated_at'))->toBeTrue()
->and(Schema::hasColumn('provider_credentials', 'expires_at'))->toBeTrue()
->and(Schema::hasColumn('managed_environment_permissions', 'workspace_id'))->toBeTrue()
->and(Schema::hasColumn('managed_environment_permissions', 'details'))->toBeTrue()
->and(glob(database_path('migrations/*spec433*')) ?: [])->toBe([])
->and(glob(database_path('migrations/*exchange*credential*')) ?: [])->toBe([]);
});
it('Spec433 credential readiness blocks unsafe or unobservable states without leaking material', function (
?string $credentialKind,
array $overrides,
string $expectedFailureCode,
string $expectedState,
): void {
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false);
if ($credentialKind !== null) {
spec433Credential($connection, $credentialKind, [
'payload' => [
'private_key' => 'spec433-private-key',
'certificate_password' => 'spec433-certificate-password',
'client_secret' => 'spec433-client-secret',
'access_token' => 'spec433-access-token',
],
...$overrides,
]);
}
$result = app(ExchangePowerShellCredentialReferenceResolver::class)->resolve($connection);
$payload = json_encode($result, JSON_THROW_ON_ERROR);
expect($result->allowed)->toBeFalse()
->and($result->failureCode)->toBe($expectedFailureCode)
->and($result->context['credential_state'])->toBe($expectedState)
->and($payload)
->not->toContain('spec433-private-key')
->not->toContain('spec433-certificate-password')
->not->toContain('spec433-client-secret')
->not->toContain('spec433-access-token')
->not->toContain('client_secret');
})->with([
'missing credential' => [null, [], 'credential_blocked_missing_reference', 'missing'],
'client secret' => [ProviderCredentialKind::ClientSecret->value, [], 'credential_blocked_secret_based_app', 'secret_based_app_unsupported'],
'certificate missing expiry metadata' => [ProviderCredentialKind::Certificate->value, ['expires_at' => null], 'credential_blocked_certificate_missing', 'certificate_missing'],
'certificate expired' => [ProviderCredentialKind::Certificate->value, ['expires_at' => now()->subDay()], 'credential_blocked_certificate_expired', 'certificate_expired'],
'certificate unsupported' => [ProviderCredentialKind::Certificate->value, [], 'credential_blocked_certificate_unsupported', 'certificate_unsupported'],
'federated unsupported' => [ProviderCredentialKind::Federated->value, [], 'credential_blocked_federated_unsupported', 'federated_unsupported'],
'managed identity unsupported' => ['managed_identity', [], 'credential_blocked_managed_identity_unsupported', 'managed_identity_unsupported'],
'unknown kind' => ['unknown_kind', [], 'credential_blocked_unknown_kind', 'unknown_kind'],
]);
it('Spec433 permission evidence enforces source currentness and provider connection scope', function (
array $detailsOverrides,
array $rowOverrides,
string $expectedFailureCode,
string $expectedState,
): void {
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false);
spec433SeedExchangePowerShellPermission($environment, $connection, [
'details' => [
'source' => 'provider_verification',
'observed_at' => now()->toJSON(),
'verified_at' => now()->toJSON(),
'evaluator' => 'exchange_powershell_permission_evidence',
'evaluator_version' => 'v1',
...$detailsOverrides,
],
...$rowOverrides,
]);
$result = app(ExchangePowerShellPermissionEvidenceEvaluator::class)->evaluate($environment, $connection);
expect($result->allowed)->toBeFalse()
->and($result->failureCode)->toBe($expectedFailureCode)
->and($result->context['permission_evidence_state'])->toBe($expectedState);
})->with([
'absent' => [[], ['status' => 'missing'], 'permission_evidence_blocked_absent', 'absent'],
'unknown' => [[], ['status' => 'error'], 'permission_evidence_blocked_unknown', 'unknown'],
'source missing' => [['source' => null], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'source untrusted' => [['source' => 'manual_note'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'source malformed' => [['source' => 'provider verification raw payload'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'observed timestamp missing' => [['observed_at' => null], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'verified timestamp missing' => [['verified_at' => null], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'observed timestamp relative string' => [['observed_at' => 'now'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'verified timestamp relative string' => [['verified_at' => 'now'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'observed timestamp invalid calendar date' => [['observed_at' => '2026-02-30T10:00:00Z'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'verified timestamp invalid offset' => [['verified_at' => '2026-07-07T22:41:10+24:00'], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'verified before observed' => [['observed_at' => now()->toJSON(), 'verified_at' => now()->subMinute()->toJSON()], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'evaluator missing' => [['evaluator' => null], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'evaluator version missing' => [['evaluator_version' => null], [], 'permission_evidence_blocked_unvalidated', 'unvalidated'],
'observed timestamp stale' => [['observed_at' => now()->subDays(31)->toJSON(), 'verified_at' => now()->toJSON()], [], 'permission_evidence_blocked_stale', 'stale'],
'verified timestamp stale' => [['observed_at' => now()->subDays(31)->subMinute()->toJSON(), 'verified_at' => now()->subDays(31)->toJSON()], [], 'permission_evidence_blocked_stale', 'stale'],
'stale by configurable threshold' => [[], ['last_checked_at' => now()->subDays(31)], 'permission_evidence_blocked_stale', 'stale'],
'wrong workspace' => [['workspace_id' => 999999], [], 'permission_evidence_blocked_wrong_workspace', 'wrong_workspace'],
'wrong environment' => [['managed_environment_id' => 999999], [], 'permission_evidence_blocked_wrong_environment', 'wrong_environment'],
'unsupported provider' => [['provider' => 'other'], [], 'permission_evidence_blocked_unsupported_provider', 'unsupported_provider'],
'wrong provider connection' => [['provider_connection_id' => 999999], [], 'permission_evidence_blocked_wrong_provider_connection', 'wrong_provider_connection'],
]);
it('Spec433 rejects malformed permission evidence scope identifiers before scope matching', function (
string $scopeKey,
string $malformedSuffix,
): void {
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false);
$expectedValue = match ($scopeKey) {
'workspace_id' => (int) $connection->workspace_id,
'managed_environment_id' => (int) $connection->managed_environment_id,
'provider_connection_id' => (int) $connection->getKey(),
default => throw new InvalidArgumentException('Unsupported scope key for Spec433 test.'),
};
spec433SeedExchangePowerShellPermission($environment, $connection, [
'details' => [
$scopeKey => ((string) $expectedValue).$malformedSuffix,
],
]);
$result = app(ExchangePowerShellPermissionEvidenceEvaluator::class)->evaluate($environment, $connection);
expect($result->allowed)->toBeFalse()
->and($result->failureCode)->toBe('permission_evidence_blocked_unvalidated')
->and($result->context['permission_evidence_state'])->toBe('unvalidated');
})->with([
'workspace id numeric prefix' => fn (): array => [
'workspace_id',
'abc',
],
'managed environment id decimal' => fn (): array => [
'managed_environment_id',
'.0',
],
'provider connection id scientific notation' => fn (): array => [
'provider_connection_id',
'e0',
],
'provider connection id whitespace padded' => fn (): array => [
'provider_connection_id',
' ',
],
]);
it('Spec433 honors configured permission evidence currentness without fallback-to-latest behavior', function (): void {
config(['tenantpilot.exchange_powershell.permission_evidence.currentness_days' => 10]);
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false);
spec433SeedExchangePowerShellPermission($environment, $connection, [
'last_checked_at' => now()->subDays(11),
]);
$result = app(ExchangePowerShellPermissionEvidenceEvaluator::class)->evaluate($environment, $connection);
expect($result->allowed)->toBeFalse()
->and($result->failureCode)->toBe('permission_evidence_blocked_stale')
->and($result->context['permission_currentness_days'])->toBe(10)
->and(ManagedEnvironmentPermission::query()
->where('managed_environment_id', (int) $environment->getKey())
->where('permission_key', 'Exchange.ManageAsApp')
->count())->toBe(1);
});
it('Spec433 combined readiness requires both credential and permission evidence and does not promote evidence', function (): void {
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false);
spec433Credential($connection, ProviderCredentialKind::ClientSecret->value);
spec433SeedExchangePowerShellPermission($environment, $connection);
$secretCredentialResult = app(ExchangePowerShellInvocationReadinessEvaluator::class)->evaluate($environment, $connection);
$connection->credential()->delete();
config(['tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate']]);
spec433Credential($connection, ProviderCredentialKind::Certificate->value);
spec433SeedExchangePowerShellPermission($environment, $connection, [
'last_checked_at' => now()->subDays(31),
]);
$stalePermissionResult = app(ExchangePowerShellInvocationReadinessEvaluator::class)->evaluate($environment, $connection);
spec433SeedExchangePowerShellPermission($environment, $connection);
$readyResult = app(ExchangePowerShellInvocationReadinessEvaluator::class)->evaluate($environment, $connection);
expect($secretCredentialResult->allowed)->toBeFalse()
->and($secretCredentialResult->context['readiness_blocker'])->toBe('credential')
->and($stalePermissionResult->allowed)->toBeFalse()
->and($stalePermissionResult->context['readiness_blocker'])->toBe('permission')
->and($readyResult->allowed)->toBeTrue()
->and(TenantConfigurationResource::query()->count())->toBe(0)
->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0);
});
it('Spec433 provider capability is supported only when combined readiness passes', function (
string $case,
array $connectionOverrides,
?string $credentialKind,
array $permissionOverrides,
?ProviderCapabilityStatus $expectedStatus,
): void {
config(['tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate']]);
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = spec433ProviderConnection($environment, $connectionOverrides, seedExchangePowerShellPermission: false);
if ($credentialKind !== null) {
spec433Credential($connection, $credentialKind);
}
if ($permissionOverrides !== ['missing' => true]) {
spec433SeedExchangePowerShellPermission($environment, $connection, $permissionOverrides);
}
$result = app(ProviderCapabilityEvaluator::class)->evaluate($environment, $connection, 'exchange_powershell_invoke');
expect($result->status)->toBe($expectedStatus)
->and($result->status)->not->toBe(
$case === 'ready' ? ProviderCapabilityStatus::Missing : ProviderCapabilityStatus::Supported,
);
})->with([
'admin consent missing' => ['admin_consent_missing', ['consent_status' => ProviderConsentStatus::Required->value, 'consent_granted_at' => null], ProviderCredentialKind::Certificate->value, [], ProviderCapabilityStatus::Missing],
'client secret' => ['client_secret', [], ProviderCredentialKind::ClientSecret->value, [], ProviderCapabilityStatus::Blocked],
'missing credential' => ['missing_credential', [], null, [], ProviderCapabilityStatus::Missing],
'missing permission' => ['missing_permission', [], ProviderCredentialKind::Certificate->value, ['missing' => true], ProviderCapabilityStatus::Missing],
'stale permission' => ['stale_permission', [], ProviderCredentialKind::Certificate->value, ['last_checked_at' => now()->subDays(31)], ProviderCapabilityStatus::Unknown],
'wrong scope permission' => ['wrong_scope_permission', [], ProviderCredentialKind::Certificate->value, ['details' => ['provider_connection_id' => 999999]], ProviderCapabilityStatus::Blocked],
'unsupported provider' => ['unsupported_provider', ['provider' => 'other'], ProviderCredentialKind::Certificate->value, ['details' => ['provider' => 'other']], ProviderCapabilityStatus::NotApplicable],
'ready' => ['ready', [], ProviderCredentialKind::Certificate->value, [], ProviderCapabilityStatus::Supported],
]);
it('Spec433 provider capability uses configured Exchange evidence currentness as the shared readiness truth', function (): void {
config([
'tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate'],
'tenantpilot.exchange_powershell.permission_evidence.currentness_days' => 45,
]);
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false);
spec433Credential($connection, ProviderCredentialKind::Certificate->value);
spec433SeedExchangePowerShellPermission($environment, $connection, [
'details' => [
'observed_at' => now()->subDays(31)->toJSON(),
'verified_at' => now()->subDays(31)->toJSON(),
],
'last_checked_at' => now()->subDays(31),
]);
$capability = app(ProviderCapabilityEvaluator::class)->evaluate($environment, $connection, 'exchange_powershell_invoke');
$readiness = app(ExchangePowerShellInvocationReadinessEvaluator::class)->evaluate($environment, $connection);
expect($readiness->allowed)->toBeTrue()
->and($readiness->context['permission_currentness_days'])->toBe(45)
->and($capability->status)->toBe(ProviderCapabilityStatus::Supported);
});
it('Spec433 production runner consumes combined readiness before runtime or process execution', function (): void {
config(['tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate']]);
[, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = spec433ProviderConnection($environment, seedExchangePowerShellPermission: false);
spec433Credential($connection, ProviderCredentialKind::Certificate->value);
spec433SeedExchangePowerShellPermission($environment, $connection, [
'last_checked_at' => now()->subDays(31),
]);
$executor = spec433FakeExecutor();
$result = app(ExchangePowerShellProductionRunner::class)->run(
spec433VerifiedContract('transportRule'),
new ExchangePowerShellInvocationContext(
operationRunId: 433,
workspaceId: (int) $environment->workspace_id,
managedEnvironmentId: (int) $environment->getKey(),
providerConnectionId: (int) $connection->getKey(),
canonicalType: 'transportRule',
commandName: 'Get-TransportRule',
runnerMode: ExchangePowerShellInvocationGate::RUNNER_MODE_PRODUCTION,
redactionPolicy: ExchangePowerShellInvocationGate::REDACTION_POLICY,
sourceContractState: 'contract_verified_pending_capture',
),
);
expect($executor->calls)->toBe([])
->and($result->blocked)->toBeTrue()
->and($result->failureCode)->toBe('permission_evidence_blocked_stale')
->and($result->context['readiness_state'])->toBe('blocked')
->and($result->context['readiness_blocker'])->toBe('permission');
});
it('Spec433 invocation context persists only safe readiness states and no evidence rows after readiness pass', function (): void {
config(['tenantpilot.exchange_powershell.credentials.supported_reference_kinds' => ['certificate']]);
[$user, $environment] = createMinimalUserWithTenant(role: 'owner');
$connection = spec433ProviderConnection($environment);
spec433Credential($connection, ProviderCredentialKind::Certificate->value, [
'payload' => [
'private_key' => 'spec433-private-key',
'certificate_password' => 'spec433-certificate-password',
],
]);
spec433FakeExecutor([
ExchangePowerShellProcessResult::completed(0, "7.4.0\n"),
ExchangePowerShellProcessResult::completed(0, "ExchangeOnlineManagement\n"),
ExchangePowerShellProcessResult::completed(0, '[{"id":"rule-1","secret":"raw-output-secret"}]', durationMs: 14),
]);
$run = app(ExchangePowerShellInvocationGate::class)->invoke(
tenant: $environment,
providerConnection: $connection,
actor: $user,
canonicalType: 'transportRule',
runnerMode: ExchangePowerShellInvocationGate::RUNNER_MODE_PRODUCTION,
);
$persisted = json_encode([
'context' => $run->context,
'failure_summary' => $run->failure_summary,
'summary_counts' => $run->summary_counts,
], JSON_THROW_ON_ERROR);
expect($run->status)->toBe(OperationRunStatus::Completed->value)
->and($run->outcome)->toBe(OperationRunOutcome::Succeeded->value)
->and(data_get($run->context, 'runner_result.readiness_state'))->toBe('ready_for_live_invocation')
->and(data_get($run->context, 'runner_result.credential_state'))->toBe('certificate_supported')
->and(data_get($run->context, 'runner_result.permission_evidence_state'))->toBe('verified')
->and(TenantConfigurationResource::query()->count())->toBe(0)
->and(TenantConfigurationResourceEvidence::query()->count())->toBe(0)
->and($persisted)
->not->toContain('raw-output-secret')
->not->toContain('spec433-private-key')
->not->toContain('spec433-certificate-password')
->not->toContain('raw_shell_stdout')
->not->toContain('raw_shell_stderr')
->not->toContain('client_secret')
->not->toContain('access_token');
});
it('Spec433 adds no UI route trigger migration or tenant-id ownership surface', function (): void {
$runtimeFiles = [
app_path('Services/TenantConfiguration/ExchangePowerShellCredentialReferenceResolver.php'),
app_path('Services/TenantConfiguration/ExchangePowerShellPermissionEvidenceEvaluator.php'),
app_path('Services/TenantConfiguration/ExchangePowerShellInvocationReadinessEvaluator.php'),
app_path('Services/TenantConfiguration/ExchangePowerShellProductionRunner.php'),
app_path('Support/Providers/Capabilities/ProviderCapabilityEvaluator.php'),
];
$runtimeSource = collect($runtimeFiles)
->map(fn (string $file): string => file_get_contents($file) ?: '')
->implode("\n");
expect(preg_match('/\btenant_id\b/', $runtimeSource))->toBe(0);
expect($runtimeSource)
->not->toContain('Start-')
->not->toContain('Set-')
->not->toContain('New-')
->not->toContain('Remove-')
->and(glob(app_path('Filament/**/*ExchangePowerShell*')) ?: [])->toBe([])
->and(glob(base_path('routes/*exchange*')) ?: [])->toBe([])
->and(glob(resource_path('views/**/*ExchangePowerShell*')) ?: [])->toBe([])
->and(glob(database_path('migrations/*exchange*')) ?: [])->toBe([])
->and(File::exists(app_path('Jobs/TenantConfiguration/InvokeExchangePowerShell.php')))->toBeFalse();
});
/**
* @param array<string, mixed> $overrides
*/
function spec433ProviderConnection(
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) {
spec433SeedExchangePowerShellPermission($environment, $connection);
}
return $connection;
}
/**
* @param array<string, mixed> $overrides
*/
function spec433SeedExchangePowerShellPermission(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<string, mixed> $overrides
*/
function spec433Credential(ProviderConnection $connection, string $kind, array $overrides = []): ProviderCredential
{
$storedKind = in_array($kind, ProviderCredentialKind::values(), true)
? $kind
: ProviderCredentialKind::ClientSecret->value;
$credential = ProviderCredential::factory()->create([
'provider_connection_id' => (int) $connection->getKey(),
'type' => $storedKind,
'credential_kind' => $storedKind,
'source' => ProviderCredentialSource::DedicatedManual->value,
'last_rotated_at' => now(),
'expires_at' => now()->addYear(),
...$overrides,
]);
if ($storedKind !== $kind) {
DB::table('provider_credentials')
->where('id', (int) $credential->getKey())
->update([
'type' => $kind,
'credential_kind' => $kind,
]);
}
return $credential;
}
function spec433FakeExecutor(array $results = []): FakeExchangePowerShellProcessExecutor
{
$executor = new FakeExchangePowerShellProcessExecutor(results: $results);
app()->instance(ExchangePowerShellProcessExecutor::class, $executor);
return $executor;
}
function spec433VerifiedContract(string $canonicalType): ExchangePowerShellCommandContract
{
$contracts = new ExchangePowerShellCommandContracts;
$contract = $contracts->contractForCanonicalType($canonicalType);
if (! is_array($contract)) {
throw new RuntimeException('Missing Spec433 command contract.');
}
return ExchangePowerShellCommandContract::fromVerifiedArray($contract, $contracts, []);
}