using System.Text.Json; using MBN_STOCK_WEBVIEW.Core.Playout; using MBN_STOCK_WEBVIEW.Core.Playout.Scenes; using MBN_STOCK_WEBVIEW.LegacyApplication; using MBN_STOCK_WEBVIEW.LegacyBridge; namespace MBN_STOCK_WEBVIEW.LegacyBridge.Tests; public sealed class LegacyPlayoutBridgeTests { private const string GateACapability = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; [Fact] public void GateAPrepare_ParsesOnlyExactUppercaseCapabilityShape() { Assert.True(LegacyBridgeProtocol.TryParseIntent( "{\"type\":\"gate-a-prepare\",\"payload\":{\"capability\":\"" + GateACapability + "\"}}", out var intent, out var error), error); Assert.Equal( GateACapability, Assert.IsType(intent).Capability); } [Theory] [InlineData("")] [InlineData("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")] [InlineData("0123456789ABCDEF")] [InlineData("G123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF")] public void GateAPrepare_RejectsInvalidCapabilities(string capability) { Assert.False(LegacyBridgeProtocol.TryParseIntent( "{\"type\":\"gate-a-prepare\",\"payload\":{\"capability\":\"" + capability + "\"}}", out _, out _)); } [Fact] public void GateAPrepare_RejectsExtraProperties() { Assert.False(LegacyBridgeProtocol.TryParseIntent( "{\"type\":\"gate-a-prepare\",\"payload\":{\"capability\":\"" + GateACapability + "\",\"retry\":false}}", out _, out _)); } [Theory] [InlineData("prepare-playout", LegacyOperatorPlayoutCommand.Prepare)] [InlineData("take-in", LegacyOperatorPlayoutCommand.TakeIn)] [InlineData("next-playout", LegacyOperatorPlayoutCommand.Next)] [InlineData("take-out", LegacyOperatorPlayoutCommand.TakeOut)] public void CommandIntents_ParseClosedEmptyPayload( string type, LegacyOperatorPlayoutCommand expected) { var parsed = LegacyBridgeProtocol.TryParseIntent( "{\"type\":\"" + type + "\",\"payload\":{}}", out var intent, out var error); Assert.True(parsed, error); var command = Assert.IsType(intent); Assert.Equal(expected, command.Command); } [Theory] [InlineData("prepare-playout")] [InlineData("take-in")] [InlineData("next-playout")] [InlineData("take-out")] [InlineData("toggle-background")] [InlineData("choose-background")] public void EmptyPlayoutIntents_RejectInjectedProperties(string type) { Assert.False(LegacyBridgeProtocol.TryParseIntent( "{\"type\":\"" + type + "\",\"payload\":{\"scene\":\"5001\"}}", out _, out _)); } [Fact] public void CompositionIntents_ParseOnlyTrustedShape() { Assert.True(LegacyBridgeProtocol.TryParseIntent( """{"type":"set-fade-duration","payload":{"duration":6}}""", out var fade, out var fadeError), fadeError); Assert.Equal(6, Assert.IsType(fade).Duration); Assert.True(LegacyBridgeProtocol.TryParseIntent( """{"type":"toggle-background","payload":{}}""", out var toggle, out var toggleError), toggleError); Assert.IsType(toggle); Assert.True(LegacyBridgeProtocol.TryParseIntent( """{"type":"choose-background","payload":{}}""", out var choose, out var chooseError), chooseError); Assert.IsType(choose); } [Theory] [InlineData(0)] [InlineData(6)] [InlineData(19)] public void FadeDuration_AcceptsOriginalComboIndexRange(int duration) { Assert.True(LegacyBridgeProtocol.TryParseIntent( "{\"type\":\"set-fade-duration\",\"payload\":{\"duration\":" + duration + "}}", out var intent, out var error), error); Assert.Equal(duration, Assert.IsType(intent).Duration); } [Theory] [InlineData("-1")] [InlineData("20")] [InlineData("60")] [InlineData("1.5")] [InlineData("\"6\"")] [InlineData("null")] public void FadeDuration_RejectsNonCanonicalValues(string value) { Assert.False(LegacyBridgeProtocol.TryParseIntent( "{\"type\":\"set-fade-duration\",\"payload\":{\"duration\":" + value + "}}", out _, out _)); } [Fact] public void SerializedState_ProjectsAuthoritativePlayoutState() { var snapshot = new LegacyOperatorSnapshot( 1, string.Empty, [], null, [], [], null, string.Empty, LegacyOperatorStatusKind.Neutral); var playout = new LegacyOperatorPlayoutSnapshot( Mode: PlayoutMode.DryRun, ConnectionState: PlayoutConnectionState.DryRunReady, Phase: LegacyOperatorPlayoutPhase.Prepared, IsProcessRunning: true, IsConnected: true, IsCommandAvailable: true, LiveTakeInAllowed: true, IsPlayCompletionPending: false, OutcomeUnknown: false, PreparedCode: "5001", OnAirCode: null, CurrentCueIndexZeroBased: 0, CurrentEntryId: "entry-1", BuilderKey: "s5001", PageIndexZeroBased: 0, PageCount: 2, PageSize: 5, ItemCount: 8, CurrentPageItemCount: 5, IsLastPage: false, NextKind: LegacyWorkflowNextKind.PageNext, IsBusy: false, RefreshActive: false, RefreshCompletedCount: 0, RefreshMaximumCount: 1, RefreshLimitReached: false, FadeDuration: 6, BackgroundEnabled: true, BackgroundFileName: "기본.vrv", CanChangeBackground: false, Message: "준비 완료") { IsTakeOutCompletionPending = true, RefreshNextAtUtc = new DateTimeOffset( 2026, 7, 17, 2, 3, 4, TimeSpan.Zero), RefreshLastSuccessAtUtc = new DateTimeOffset( 2026, 7, 17, 1, 2, 3, TimeSpan.Zero), RefreshFaultCode = "DATABASE_UNAVAILABLE" }; using var document = JsonDocument.Parse( LegacyBridgeProtocol.SerializeState(snapshot, playout: playout)); var projected = document.RootElement.GetProperty("payload").GetProperty("playout"); Assert.Equal("dryRun", projected.GetProperty("mode").GetString()); Assert.Equal("dryRunReady", projected.GetProperty("connectionState").GetString()); Assert.Equal("prepared", projected.GetProperty("phase").GetString()); Assert.Equal("5001", projected.GetProperty("preparedCode").GetString()); Assert.Equal("pageNext", projected.GetProperty("nextKind").GetString()); Assert.Equal(2, projected.GetProperty("pageCount").GetInt32()); Assert.Equal("기본.vrv", projected.GetProperty("backgroundFileName").GetString()); Assert.Equal("entry-1", projected.GetProperty("currentEntryId").GetString()); Assert.True(projected.GetProperty("isTakeOutCompletionPending").GetBoolean()); Assert.Equal( new DateTimeOffset(2026, 7, 17, 2, 3, 4, TimeSpan.Zero), projected.GetProperty("refreshNextAtUtc").GetDateTimeOffset()); Assert.Equal( new DateTimeOffset(2026, 7, 17, 1, 2, 3, TimeSpan.Zero), projected.GetProperty("refreshLastSuccessAtUtc").GetDateTimeOffset()); Assert.Equal( "DATABASE_UNAVAILABLE", projected.GetProperty("refreshFaultCode").GetString()); Assert.False(projected.TryGetProperty("builderKey", out _)); } }