namespace FinalFactory.Rendezvous.TestClient; internal enum TestClientExitCode { Success = 0, Usage = 2, Configuration = 3, ServiceFailure = 10, NoCompatibleSession = 11, TraversalFailed = 12, DirectTrafficFailed = 13, Cancelled = 130, } internal interface ITestClientCommandRunner { Task RunAsync( TestClientOptions options, TestClientOutput output, TextReader input, CancellationToken cancellationToken); } internal sealed class TestClientApplication(ITestClientCommandRunner runner) { private readonly ITestClientCommandRunner _runner = runner ?? throw new ArgumentNullException(nameof(runner)); internal async Task RunAsync( string[] args, TextReader input, TextWriter standardOutput, TextWriter standardError, CancellationToken cancellationToken) { bool jsonRequested = args.Contains("--json", StringComparer.Ordinal); TestClientParseResult parsed = TestClientOptionParser.Parse(args); TestClientOutput output = new(standardOutput, standardError, jsonRequested); if (parsed.ShowHelp) { if (jsonRequested) { output.Write( "cli.help", "complete", phase: "configuration", message: "Run without --json to read the full command reference."); } else { await standardOutput.WriteLineAsync(TestClientOptionParser.Usage).ConfigureAwait(false); } return (int)TestClientExitCode.Success; } if (!parsed.Succeeded || parsed.Options is null) { if (jsonRequested) { output.WriteError( "cli.usage", "failed", parsed.Error ?? "Invalid command line.", phase: "configuration"); } else { await standardError.WriteLineAsync(parsed.Error ?? "Invalid command line.").ConfigureAwait(false); await standardError.WriteLineAsync("Use --help for documented options.").ConfigureAwait(false); } return (int)TestClientExitCode.Usage; } output = new TestClientOutput(standardOutput, standardError, parsed.Options.Json); try { return (int)await _runner.RunAsync( parsed.Options, output, input, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { output.Write("lifecycle.cancelled", "cancelled", phase: "lifecycle"); return (int)TestClientExitCode.Cancelled; } catch (Exception exception) { output.WriteError( "lifecycle.failed", "failed", $"Unexpected {exception.GetType().Name}; credentials remain redacted."); return (int)TestClientExitCode.ServiceFailure; } } }