using System; using System.Collections.Generic; using ZB.MOM.WW.MxGateway.Worker.MxAccess; namespace ZB.MOM.WW.MxGateway.Worker.Tests.MxAccess; /// /// Worker-007 regression tests for . The /// adapter no longer falls back to late-bound Type.InvokeMember /// reflection: a COM object must implement either the typed /// ILMXProxyServer COM interface family (production) or /// directly (test fakes). /// public sealed class MxAccessComServerTests { /// /// A COM object implementing is routed /// through the typed interface — no reflection — preserving arguments /// and return values. /// [Fact] public void Methods_WithTypedServer_RouteThroughTypedInterface() { RecordingMxAccessServer typed = new(registerHandle: 77); MxAccessComServer adapter = new(typed); int serverHandle = adapter.Register("client-a"); adapter.Advise(serverHandle, itemHandle: 9); adapter.Unregister(serverHandle); Assert.Equal(77, serverHandle); Assert.Equal("client-a", typed.RegisteredClientName); Assert.Equal(new[] { "Register:client-a", "Advise:77:9", "Unregister:77" }, typed.Calls); } /// /// A COM object that implements neither the typed COM interface family /// nor fails fast with a clear /// instead of a late-bound /// reflection call. /// [Fact] public void Methods_WithUntypedObject_ThrowInvalidOperation() { MxAccessComServer adapter = new(new object()); InvalidOperationException exception = Assert.Throws(() => adapter.Register("client")); Assert.Contains("does not implement", exception.Message, StringComparison.Ordinal); Assert.Contains(nameof(IMxAccessServer), exception.Message, StringComparison.Ordinal); } /// /// Exceptions thrown by the typed server propagate unchanged — no /// TargetInvocationException wrapping (reflection is gone). /// [Fact] public void Methods_WhenTypedServerThrows_PropagateOriginalException() { RecordingMxAccessServer typed = new(registerHandle: 1) { ThrowOnRegister = new InvalidOperationException("register failed"), }; MxAccessComServer adapter = new(typed); InvalidOperationException exception = Assert.Throws(() => adapter.Register("client")); Assert.Equal("register failed", exception.Message); } private sealed class RecordingMxAccessServer : IMxAccessServer { private readonly int registerHandle; private readonly List calls = new(); /// Initializes a new instance with the specified register handle. /// The initial server handle value to return from Register. public RecordingMxAccessServer(int registerHandle) { this.registerHandle = registerHandle; } /// Gets the client name passed to the most recent Register call. public string? RegisteredClientName { get; private set; } /// Gets or sets an exception to throw from the Register method. public Exception? ThrowOnRegister { get; set; } /// Gets the recorded method calls as strings. public IReadOnlyList Calls => calls.ToArray(); /// Records a Register call and returns the configured handle. /// The client name to record. public int Register(string clientName) { calls.Add($"Register:{clientName}"); RegisteredClientName = clientName; if (ThrowOnRegister is not null) { throw ThrowOnRegister; } return registerHandle; } /// Records an Unregister call. /// The MXAccess server handle. public void Unregister(int serverHandle) { calls.Add($"Unregister:{serverHandle}"); } /// Records an AddItem call and returns zero. /// The MXAccess server handle. /// The item definition string to record. public int AddItem(int serverHandle, string itemDefinition) { calls.Add($"AddItem:{serverHandle}:{itemDefinition}"); return 0; } /// Records an AddItem2 call and returns zero. /// The MXAccess server handle. /// The item definition string to record. /// The item context string to record. public int AddItem2(int serverHandle, string itemDefinition, string itemContext) { calls.Add($"AddItem2:{serverHandle}:{itemDefinition}:{itemContext}"); return 0; } /// Records a RemoveItem call. /// The MXAccess server handle. /// The MXAccess item handle. public void RemoveItem(int serverHandle, int itemHandle) { calls.Add($"RemoveItem:{serverHandle}:{itemHandle}"); } /// Records an Advise call. /// The MXAccess server handle. /// The MXAccess item handle. public void Advise(int serverHandle, int itemHandle) { calls.Add($"Advise:{serverHandle}:{itemHandle}"); } /// Records an UnAdvise call. /// The MXAccess server handle. /// The MXAccess item handle. public void UnAdvise(int serverHandle, int itemHandle) { calls.Add($"UnAdvise:{serverHandle}:{itemHandle}"); } /// Records an AdviseSupervisory call. /// The MXAccess server handle. /// The MXAccess item handle. public void AdviseSupervisory(int serverHandle, int itemHandle) { calls.Add($"AdviseSupervisory:{serverHandle}:{itemHandle}"); } /// Records a Write call. /// The MXAccess server handle. /// The MXAccess item handle. /// The value to write. /// The user identifier. public void Write(int serverHandle, int itemHandle, object? value, int userId) { calls.Add($"Write:{serverHandle}:{itemHandle}:{value}:{userId}"); } /// Records a Write2 call. /// The MXAccess server handle. /// The MXAccess item handle. /// The value to write. /// The timestamp value. /// The user identifier. public void Write2(int serverHandle, int itemHandle, object? value, object? timestamp, int userId) { calls.Add($"Write2:{serverHandle}:{itemHandle}:{value}:{timestamp}:{userId}"); } /// Records a WriteSecured call. /// The MXAccess server handle. /// The MXAccess item handle. /// The current user identifier. /// The verifier user identifier. /// The value to write. public void WriteSecured(int serverHandle, int itemHandle, int currentUserId, int verifierUserId, object? value) { calls.Add($"WriteSecured:{serverHandle}:{itemHandle}:{currentUserId}:{verifierUserId}:{value}"); } /// Records a WriteSecured2 call. /// The MXAccess server handle. /// The MXAccess item handle. /// The current user identifier. /// The verifier user identifier. /// The value to write. /// The timestamp value. public void WriteSecured2( int serverHandle, int itemHandle, int currentUserId, int verifierUserId, object? value, object? timestamp) { calls.Add($"WriteSecured2:{serverHandle}:{itemHandle}:{currentUserId}:{verifierUserId}:{value}:{timestamp}"); } } }