TenantAtlas/apps/platform/app/Services/TenantConfiguration/FakeExchangePowerShellCommandRunner.php
2026-07-07 15:54:34 +02:00

119 lines
5.0 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\TenantConfiguration;
use App\Support\Providers\ProviderReasonCodes;
use RuntimeException;
final class FakeExchangePowerShellCommandRunner implements ExchangePowerShellCommandRunner
{
public const string SCENARIO_SUCCESS = 'success';
public const string SCENARIO_EMPTY_SUCCESS = 'empty_success';
public const string SCENARIO_AUTH_FAILURE = 'auth_failure';
public const string SCENARIO_AUTHORIZATION_FAILURE = 'authorization_failure';
public const string SCENARIO_TIMEOUT = 'timeout';
public const string SCENARIO_THROTTLED = 'throttled';
public const string SCENARIO_MODULE_UNAVAILABLE = 'module_unavailable';
public const string SCENARIO_COMMAND_UNAVAILABLE = 'command_unavailable';
public const string SCENARIO_MALFORMED_SCALAR = 'malformed_scalar';
public const string SCENARIO_MALFORMED_TEXT = 'malformed_text';
public const string SCENARIO_UNEXPECTED_EXCEPTION = 'unexpected_exception';
/**
* @var list<array<string, mixed>>
*/
public array $calls = [];
/**
* @param list<array<string, mixed>>|null $items
*/
public function __construct(
private readonly string $scenario = self::SCENARIO_SUCCESS,
private readonly ?array $items = null,
) {}
public function run(
ExchangePowerShellCommandContract $commandContract,
ExchangePowerShellInvocationContext $context,
): ExchangePowerShellInvocationResult {
$this->calls[] = [
'operation_run_id' => $context->operationRunId,
'canonical_type' => $context->canonicalType,
'command_name' => $context->commandName,
'runner_mode' => $context->runnerMode,
'parameter_names' => $commandContract->parameterNames(),
'context' => $context->toArray(),
];
return match ($this->scenario) {
self::SCENARIO_EMPTY_SUCCESS => ExchangePowerShellInvocationResult::succeeded([]),
self::SCENARIO_AUTH_FAILURE => ExchangePowerShellInvocationResult::failed(
reasonCode: ProviderReasonCodes::ProviderAuthFailed,
failureCode: 'execution_failed_authentication',
message: 'Exchange PowerShell authentication failed.',
),
self::SCENARIO_AUTHORIZATION_FAILURE => ExchangePowerShellInvocationResult::failed(
reasonCode: ProviderReasonCodes::ProviderPermissionDenied,
failureCode: 'execution_failed_authorization',
message: 'Exchange PowerShell authorization failed.',
),
self::SCENARIO_TIMEOUT => ExchangePowerShellInvocationResult::failed(
reasonCode: ProviderReasonCodes::NetworkUnreachable,
failureCode: 'execution_failed_timeout',
message: 'Exchange PowerShell invocation timed out.',
),
self::SCENARIO_THROTTLED => ExchangePowerShellInvocationResult::failed(
reasonCode: ProviderReasonCodes::RateLimited,
failureCode: 'execution_failed_throttled',
message: 'Exchange PowerShell invocation was rate limited.',
),
self::SCENARIO_MODULE_UNAVAILABLE => ExchangePowerShellInvocationResult::blocked(
reasonCode: ProviderReasonCodes::ProviderBindingUnsupported,
failureCode: 'execution_blocked_module_unavailable',
message: 'Exchange PowerShell module is unavailable.',
),
self::SCENARIO_COMMAND_UNAVAILABLE => ExchangePowerShellInvocationResult::blocked(
reasonCode: ProviderReasonCodes::ProviderBindingUnsupported,
failureCode: 'execution_blocked_command_unavailable',
message: 'Exchange PowerShell command is unavailable.',
),
self::SCENARIO_MALFORMED_SCALAR => ExchangePowerShellInvocationResult::succeededPayload(42),
self::SCENARIO_MALFORMED_TEXT => ExchangePowerShellInvocationResult::succeededPayload('raw transcript rejected by shape gate'),
self::SCENARIO_UNEXPECTED_EXCEPTION => throw new RuntimeException('Simulated runner exception with token and secret redaction pressure.'),
default => ExchangePowerShellInvocationResult::succeeded($this->items ?? $this->defaultItems($commandContract->toArray())),
};
}
/**
* @param array<string, mixed> $commandContract
* @return list<array<string, mixed>>
*/
private function defaultItems(array $commandContract): array
{
$canonicalType = (string) ($commandContract['canonical_type'] ?? 'exchange');
$label = (string) ($commandContract['human_label'] ?? 'Exchange object');
return [
[
'id' => 'spec431-'.$canonicalType.'-1',
'sourceId' => 'spec431-'.$canonicalType.'-source-1',
'Guid' => '00000000-0000-4000-8000-000000000431',
'Name' => 'Spec 431 '.$label,
'DisplayName' => 'Spec 431 '.$label,
],
];
}
}