using MBN_STOCK_WEBVIEW.PlayoutSmoke; namespace MBN_STOCK_WEBVIEW.Playout.Tests; public sealed class SmokeCommandLineTests { [Theory] [InlineData("Tornado2 TEST", false)] [InlineData("TEST-1", false)] [InlineData("Tornado2", true)] [InlineData("PGM", true)] [InlineData("CONTEST", true)] [InlineData("", true)] [InlineData(null, true)] public void TornadoWindowTitleWithoutExplicitTestMarkerIsUnsafe( string? title, bool expected) { Assert.Equal(expected, SmokeCommandLine.IsUnsafeTornadoWindowTitle(title)); } [Fact] public void Parse_NoArguments_PreservesReadOnlyProbeDefault() { var parsed = SmokeCommandLine.Parse([]); Assert.True(parsed.IsValid); Assert.Equal(SmokeCommandKind.Probe, parsed.Invocation.Kind); } [Theory] [InlineData("--probe", "Probe")] [InlineData("--PROBE", "Probe")] [InlineData("--dry-run", "DryRun")] public void Parse_ExistingComFreeCommandsRemainAvailable( string argument, string expected) { var parsed = SmokeCommandLine.Parse([argument]); Assert.True(parsed.IsValid); Assert.Equal(expected, parsed.Invocation.Kind.ToString()); } [Fact] public void Parse_TestConnectRequiresExplicitAcknowledgementAndConfigArgument() { var withoutAcknowledgement = SmokeCommandLine.Parse( ["--test-connect", "--config", "C:\\safe\\playout.local.json"]); var complete = SmokeCommandLine.Parse( [ "--test-connect", SmokeCommandLine.AcknowledgementFlag, "--config", "C:\\safe\\playout.local.json" ]); Assert.False(withoutAcknowledgement.IsValid); Assert.Equal("missing-acknowledgement", withoutAcknowledgement.ErrorCode); Assert.True(complete.IsValid); Assert.Equal(SmokeCommandKind.TestConnect, complete.Invocation.Kind); } [Fact] public void Parse_TestSequenceRequiresBothSceneCodesAndBoundedObservation() { var missingObservation = SmokeCommandLine.Parse( [ "--test-sequence", SmokeCommandLine.AcknowledgementFlag, "--config", "C:\\safe\\playout.local.json", "--prepare", "5001", "--next", "5006" ]); var complete = SmokeCommandLine.Parse( [ "--test-sequence", "--prepare", "5001", "--observe-ms", "1000", SmokeCommandLine.AcknowledgementFlag, "--next", "5006", "--config", "C:\\safe\\playout.local.json" ]); Assert.False(missingObservation.IsValid); Assert.Equal("missing-required-option", missingObservation.ErrorCode); Assert.True(complete.IsValid); Assert.Equal(1000, complete.Invocation.ObservationMilliseconds); } [Theory] [InlineData("999")] [InlineData("30001")] [InlineData("1.5")] [InlineData("secret")] public void Parse_RejectsUnboundedOrNonIntegralObservation(string value) { var parsed = ValidSequenceArguments(observation: value); Assert.False(parsed.IsValid); Assert.Equal("invalid-observation-duration", parsed.ErrorCode); } [Theory] [InlineData("../5001")] [InlineData("5001.t2s")] [InlineData("장면")] [InlineData("SCENE A")] public void Parse_RejectsUnsafeSceneCodesWithoutReflectingThem(string sceneCode) { var parsed = ValidSequenceArguments(prepare: sceneCode); Assert.False(parsed.IsValid); Assert.Equal("unsafe-scene-code", parsed.ErrorCode); Assert.DoesNotContain(sceneCode, parsed.ErrorCode, StringComparison.OrdinalIgnoreCase); } [Fact] public void Parse_TestConnectRejectsMutationOptions() { var parsed = SmokeCommandLine.Parse( [ "--test-connect", SmokeCommandLine.AcknowledgementFlag, "--config", "C:\\safe\\playout.local.json", "--prepare", "5001" ]); Assert.False(parsed.IsValid); Assert.Equal("unknown-option", parsed.ErrorCode); } [Fact] public void Parse_TestSequenceRequiresTwoDistinctSceneCodes() { var parsed = SmokeCommandLine.Parse( [ "--test-sequence", SmokeCommandLine.AcknowledgementFlag, "--config", "C:\\safe\\playout.local.json", "--prepare", "5001", "--next", "5001", "--observe-ms", "1000" ]); Assert.False(parsed.IsValid); Assert.Equal("duplicate-scene-code", parsed.ErrorCode); } [Fact] public void Parse_TestPlanUsesTheSameSequenceInputsButNeverImpliesExecution() { var parsed = SmokeCommandLine.Parse( [ "--test-plan", SmokeCommandLine.AcknowledgementFlag, "--config", "C:\\safe\\playout.local.json", "--prepare", "5001", "--next", "5006", "--observe-ms", "5000" ]); Assert.True(parsed.IsValid); Assert.Equal(SmokeCommandKind.TestPlan, parsed.Invocation.Kind); } [Fact] public void Parse_PgmConnectDiagnosticRequiresDistinctAcknowledgementAndExactInputs() { string[] complete = [ "--pgm-connect-diagnostic", SmokeCommandLine.PgmConnectAcknowledgementFlag, "--host", "127.0.0.1", "--port", "30001", "--expected-pgm-window-title", "PGM" ]; var missingAcknowledgement = SmokeCommandLine.Parse(complete[0..1] .Concat(complete[2..]) .ToArray()); var wrongAcknowledgement = SmokeCommandLine.Parse( complete.Select(value => value == SmokeCommandLine.PgmConnectAcknowledgementFlag ? SmokeCommandLine.AcknowledgementFlag : value).ToArray()); var parsed = SmokeCommandLine.Parse(complete); Assert.False(missingAcknowledgement.IsValid); Assert.Equal("missing-acknowledgement", missingAcknowledgement.ErrorCode); Assert.False(wrongAcknowledgement.IsValid); Assert.Equal("unknown-option", wrongAcknowledgement.ErrorCode); Assert.True(parsed.IsValid); Assert.Equal(SmokeCommandKind.PgmConnectDiagnostic, parsed.Invocation.Kind); Assert.Equal("127.0.0.1", parsed.Invocation.Host); Assert.Equal(30001, parsed.Invocation.Port); Assert.Equal("PGM", parsed.Invocation.ExpectedPgmWindowTitle); Assert.Null(parsed.Invocation.ConfigPath); } [Theory] [InlineData("--config")] [InlineData("--prepare")] [InlineData("--next")] [InlineData("--observe-ms")] public void Parse_PgmConnectDiagnosticRejectsEveryConfigOrRenderOption(string option) { var parsed = SmokeCommandLine.Parse( [ "--pgm-connect-diagnostic", SmokeCommandLine.PgmConnectAcknowledgementFlag, "--host", "127.0.0.1", "--port", "30001", "--expected-pgm-window-title", "PGM", option, "5001" ]); Assert.False(parsed.IsValid); Assert.Equal("unknown-option", parsed.ErrorCode); } [Theory] [InlineData("0")] [InlineData("65536")] [InlineData("secret")] public void Parse_PgmConnectDiagnosticRejectsInvalidPort(string port) { var parsed = SmokeCommandLine.Parse( [ "--pgm-connect-diagnostic", SmokeCommandLine.PgmConnectAcknowledgementFlag, "--host", "127.0.0.1", "--port", port, "--expected-pgm-window-title", "PGM" ]); Assert.False(parsed.IsValid); Assert.Equal("invalid-port", parsed.ErrorCode); } private static SmokeCommandParseResult ValidSequenceArguments( string prepare = "5001", string observation = "1000") => SmokeCommandLine.Parse( [ "--test-sequence", SmokeCommandLine.AcknowledgementFlag, "--config", "C:\\safe\\playout.local.json", "--prepare", prepare, "--next", "5006", "--observe-ms", observation ]); }