using System.Collections.Concurrent; using System.Runtime.InteropServices; using MBN_STOCK_WEBVIEW.Playout.Interop; namespace MBN_STOCK_WEBVIEW.Playout.Tests; public sealed class DynamicK3dEventHandlerTests { [Fact] public void GeneratedHandler_ImplementsImportedInterfaceAndForwardsOnlyLifecycleCallbacks() { var sink = new RecordingSink(); var value = DynamicK3dEventHandlerBuilder.Create(typeof(IFakeK3dEventHandler), sink); var handler = Assert.IsAssignableFrom(value); handler.OnHello(); handler.OnScenePlayed(1, 9, 10); handler.OnCutOut(0, 9, 10); handler.OnStopAll(1); handler.OnIgnored("sensitive value"); Assert.Equal( new[] { "OnHello:", "OnScenePlayed:1,9,10", "OnCutOut:0,9,10", "OnStopAll:1" }, sink.Calls); Assert.Equal( ClassInterfaceType.None, value.GetType().GetCustomAttributes(typeof(ClassInterfaceAttribute), inherit: false) .Cast() .Single() .Value); } [Fact] public void EventQueue_ParsesCallbacksWithoutThrowingAcrossNativeBoundary() { var queue = new K3dEventQueue(capacity: 4); queue.OnCallback("OnHello", []); queue.OnCallback("OnScenePlayed", [1, 9, 10]); queue.OnCallback("OnCutOut", [0, 9, 10]); queue.OnCallback("OnStopAll", [1]); queue.OnCallback("OnScenePlayed", ["invalid"]); Assert.True(queue.HelloObserved); Assert.False(queue.HasOverflowed); Assert.Equal(3, queue.PendingCount); Assert.True(queue.TryDequeue(out var played)); Assert.Equal( new K3dLifecycleCallback(K3dLifecycleCallbackKind.ScenePlayed, true, 9, 10), played); Assert.True(queue.TryDequeue(out var cut)); Assert.False(cut.IsSuccess); Assert.True(queue.TryDequeue(out var stopped)); Assert.Equal(K3dLifecycleCallbackKind.StopAll, stopped.Kind); Assert.False(queue.TryDequeue(out _)); } [Fact] public void EventQueue_OverflowLatchesAndNeverExceedsCapacity() { var queue = new K3dEventQueue(capacity: 1); queue.OnCallback("OnStopAll", [1]); queue.OnCallback("OnStopAll", [1]); Assert.True(queue.HasOverflowed); Assert.Equal(1, queue.PendingCount); Assert.True(queue.TryDequeue(out _)); Assert.False(queue.TryDequeue(out _)); } private sealed class RecordingSink : IK3dEventSink { private readonly ConcurrentQueue _calls = new(); public string[] Calls => _calls.ToArray(); public void OnCallback(string methodName, object?[] arguments) => _calls.Enqueue($"{methodName}:{string.Join(',', arguments)}"); } [ComImport] [Guid("C21817DB-C0D3-4647-8D85-027338DCF832")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IFakeK3dEventHandler { void OnHello(); void OnScenePlayed(int success, int outputChannelIndex, int layerIndex); void OnCutOut(int success, int outputChannelIndex, int layerIndex); void OnStopAll(int success); void OnIgnored(string value); } }