931 lines
37 KiB
C#
931 lines
37 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading.Tasks;
|
|
using MxGateway.Contracts.Proto;
|
|
using MxGateway.Worker.MxAccess;
|
|
using MxGateway.Worker.Sta;
|
|
|
|
namespace MxGateway.Worker.Tests.MxAccess;
|
|
|
|
public sealed class MxAccessCommandExecutorTests
|
|
{
|
|
[Fact]
|
|
public async Task DispatchAsync_Register_CallsMxAccessOnStaAndPreservesServerHandle()
|
|
{
|
|
FakeMxAccessComObjectFactory factory = new(new FakeMxAccessComObject(registerHandle: 42));
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateRegisterCommand("correlation-1", "client-a"));
|
|
|
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
|
Assert.True(reply.HasHresult);
|
|
Assert.Equal(0, reply.Hresult);
|
|
Assert.Equal(42, reply.Register.ServerHandle);
|
|
Assert.Equal(MxDataType.Integer, reply.ReturnValue.DataType);
|
|
Assert.Equal(42, reply.ReturnValue.Int32Value);
|
|
Assert.Equal(runtime.StaThreadId, factory.FakeComObject.RegisterThreadId);
|
|
Assert.Equal("client-a", factory.FakeComObject.RegisteredClientName);
|
|
|
|
RegisteredServerHandle registeredServerHandle = Assert.Single(
|
|
await session.GetRegisteredServerHandlesAsync());
|
|
Assert.Equal(42, registeredServerHandle.ServerHandle);
|
|
Assert.Equal("client-a", registeredServerHandle.ClientName);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_Unregister_CallsMxAccessOnStaAndRemovesTrackedServerHandle()
|
|
{
|
|
FakeMxAccessComObject fakeComObject = new(registerHandle: 43);
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register", "client-a"));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateUnregisterCommand("unregister", 43));
|
|
|
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
|
Assert.Equal(43, fakeComObject.UnregisteredServerHandle);
|
|
Assert.Equal(runtime.StaThreadId, fakeComObject.UnregisterThreadId);
|
|
Assert.Empty(await session.GetRegisteredServerHandlesAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_UnregisterWhenMxAccessThrows_PreservesHResultAndDoesNotRewriteFailure()
|
|
{
|
|
const int hresult = unchecked((int)0x80070057);
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 44,
|
|
unregisterException: new COMException("Invalid handle.", hresult));
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-failure", "client-a"));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateUnregisterCommand("invalid-unregister", 44));
|
|
|
|
Assert.Equal(ProtocolStatusCode.MxaccessFailure, reply.ProtocolStatus.Code);
|
|
Assert.True(reply.HasHresult);
|
|
Assert.Equal(hresult, reply.Hresult);
|
|
Assert.Contains("0x80070057", reply.DiagnosticMessage);
|
|
Assert.Equal(44, fakeComObject.UnregisteredServerHandle);
|
|
|
|
RegisteredServerHandle registeredServerHandle = Assert.Single(
|
|
await session.GetRegisteredServerHandlesAsync());
|
|
Assert.Equal(44, registeredServerHandle.ServerHandle);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_AddItem_CallsMxAccessOnStaAndTracksItemHandle()
|
|
{
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 46,
|
|
addItemHandle: 501);
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-add", "client-a"));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateAddItemCommand(
|
|
"add-item",
|
|
46,
|
|
"Galaxy.Tag.Value"));
|
|
|
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
|
Assert.True(reply.HasHresult);
|
|
Assert.Equal(0, reply.Hresult);
|
|
Assert.Equal(501, reply.AddItem.ItemHandle);
|
|
Assert.Equal(MxDataType.Integer, reply.ReturnValue.DataType);
|
|
Assert.Equal(501, reply.ReturnValue.Int32Value);
|
|
Assert.Equal(46, fakeComObject.AddItemServerHandle);
|
|
Assert.Equal("Galaxy.Tag.Value", fakeComObject.AddItemDefinition);
|
|
Assert.Equal(runtime.StaThreadId, fakeComObject.AddItemThreadId);
|
|
|
|
RegisteredItemHandle registeredItemHandle = Assert.Single(
|
|
await session.GetRegisteredItemHandlesAsync());
|
|
Assert.Equal(46, registeredItemHandle.ServerHandle);
|
|
Assert.Equal(501, registeredItemHandle.ItemHandle);
|
|
Assert.Equal("Galaxy.Tag.Value", registeredItemHandle.ItemDefinition);
|
|
Assert.Equal(string.Empty, registeredItemHandle.ItemContext);
|
|
Assert.False(registeredItemHandle.HasItemContext);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_AddItem2_PassesContextExactlyAndTracksItemHandle()
|
|
{
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 47,
|
|
addItem2Handle: 502);
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-add2", "client-a"));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateAddItem2Command(
|
|
"add-item2",
|
|
47,
|
|
"TestInt",
|
|
"TestChildObject"));
|
|
|
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
|
Assert.Equal(502, reply.AddItem2.ItemHandle);
|
|
Assert.Equal(MxDataType.Integer, reply.ReturnValue.DataType);
|
|
Assert.Equal(502, reply.ReturnValue.Int32Value);
|
|
Assert.Equal(47, fakeComObject.AddItem2ServerHandle);
|
|
Assert.Equal("TestInt", fakeComObject.AddItem2Definition);
|
|
Assert.Equal("TestChildObject", fakeComObject.AddItem2Context);
|
|
Assert.Equal(runtime.StaThreadId, fakeComObject.AddItem2ThreadId);
|
|
|
|
RegisteredItemHandle registeredItemHandle = Assert.Single(
|
|
await session.GetRegisteredItemHandlesAsync());
|
|
Assert.Equal(47, registeredItemHandle.ServerHandle);
|
|
Assert.Equal(502, registeredItemHandle.ItemHandle);
|
|
Assert.Equal("TestInt", registeredItemHandle.ItemDefinition);
|
|
Assert.Equal("TestChildObject", registeredItemHandle.ItemContext);
|
|
Assert.True(registeredItemHandle.HasItemContext);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_RemoveItem_CallsMxAccessOnStaAndRemovesTrackedItemHandle()
|
|
{
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 48,
|
|
addItemHandle: 503);
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-remove", "client-a"));
|
|
await session.DispatchAsync(CreateAddItemCommand("add-before-remove", 48, "Galaxy.Tag.Value"));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateRemoveItemCommand(
|
|
"remove-item",
|
|
48,
|
|
503));
|
|
|
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
|
Assert.True(reply.HasHresult);
|
|
Assert.Equal(0, reply.Hresult);
|
|
Assert.Equal(48, fakeComObject.RemoveItemServerHandle);
|
|
Assert.Equal(503, fakeComObject.RemovedItemHandle);
|
|
Assert.Equal(runtime.StaThreadId, fakeComObject.RemoveItemThreadId);
|
|
Assert.Empty(await session.GetRegisteredItemHandlesAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_RemoveItemWithAdvisedHandle_RemovesTrackedAdviceAfterMxAccessSucceeds()
|
|
{
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 148,
|
|
addItemHandle: 603);
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-advised-remove", "client-a"));
|
|
await session.DispatchAsync(CreateAddItemCommand("add-before-advised-remove", 148, "Galaxy.Tag.Value"));
|
|
await session.DispatchAsync(CreateAdviseCommand("advise-before-remove", 148, 603));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateRemoveItemCommand(
|
|
"remove-advised-item",
|
|
148,
|
|
603));
|
|
|
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
|
Assert.Empty(await session.GetRegisteredItemHandlesAsync());
|
|
Assert.Empty(await session.GetRegisteredAdviceHandlesAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_RemoveItemWithCrossServerHandle_PreservesHResultAndKeepsTrackedItemHandle()
|
|
{
|
|
const int hresult = unchecked((int)0x80070057);
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 49,
|
|
addItemHandle: 504,
|
|
removeItemException: new COMException("Invalid item handle.", hresult));
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-remove-failure", "client-a"));
|
|
await session.DispatchAsync(CreateAddItemCommand("add-before-remove-failure", 49, "Galaxy.Tag.Value"));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateRemoveItemCommand(
|
|
"remove-item-failure",
|
|
999,
|
|
504));
|
|
|
|
Assert.Equal(ProtocolStatusCode.MxaccessFailure, reply.ProtocolStatus.Code);
|
|
Assert.True(reply.HasHresult);
|
|
Assert.Equal(hresult, reply.Hresult);
|
|
Assert.Contains("0x80070057", reply.DiagnosticMessage);
|
|
Assert.Equal(999, fakeComObject.RemoveItemServerHandle);
|
|
Assert.Equal(504, fakeComObject.RemovedItemHandle);
|
|
|
|
RegisteredItemHandle registeredItemHandle = Assert.Single(
|
|
await session.GetRegisteredItemHandlesAsync());
|
|
Assert.Equal(49, registeredItemHandle.ServerHandle);
|
|
Assert.Equal(504, registeredItemHandle.ItemHandle);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_AddItem2WhenMxAccessThrows_PreservesHResultAndDoesNotTrackItemHandle()
|
|
{
|
|
const int hresult = unchecked((int)0x80070057);
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 50,
|
|
addItem2Exception: new COMException("Invalid server handle.", hresult));
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateAddItem2Command(
|
|
"add-item2-failure",
|
|
9001,
|
|
"TestInt",
|
|
"TestChildObject"));
|
|
|
|
Assert.Equal(ProtocolStatusCode.MxaccessFailure, reply.ProtocolStatus.Code);
|
|
Assert.True(reply.HasHresult);
|
|
Assert.Equal(hresult, reply.Hresult);
|
|
Assert.Contains("0x80070057", reply.DiagnosticMessage);
|
|
Assert.Equal(9001, fakeComObject.AddItem2ServerHandle);
|
|
Assert.Equal("TestInt", fakeComObject.AddItem2Definition);
|
|
Assert.Equal("TestChildObject", fakeComObject.AddItem2Context);
|
|
Assert.Empty(await session.GetRegisteredItemHandlesAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_Advise_CallsMxAccessOnStaAndTracksPlainAdvice()
|
|
{
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 52,
|
|
addItemHandle: 505);
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-advise", "client-a"));
|
|
await session.DispatchAsync(CreateAddItemCommand("add-before-advise", 52, "Galaxy.Tag.Value"));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateAdviseCommand(
|
|
"advise",
|
|
52,
|
|
505));
|
|
|
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
|
Assert.True(reply.HasHresult);
|
|
Assert.Equal(0, reply.Hresult);
|
|
Assert.Equal(52, fakeComObject.AdviseServerHandle);
|
|
Assert.Equal(505, fakeComObject.AdvisedItemHandle);
|
|
Assert.Equal(runtime.StaThreadId, fakeComObject.AdviseThreadId);
|
|
|
|
RegisteredAdviceHandle adviceHandle = Assert.Single(
|
|
await session.GetRegisteredAdviceHandlesAsync());
|
|
Assert.Equal(52, adviceHandle.ServerHandle);
|
|
Assert.Equal(505, adviceHandle.ItemHandle);
|
|
Assert.Equal(MxAccessAdviceKind.Plain, adviceHandle.AdviceKind);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_AdviseSupervisory_CallsDistinctMxAccessMethodAndTracksSupervisoryAdvice()
|
|
{
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 53,
|
|
addItemHandle: 506);
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-supervisory", "client-a"));
|
|
await session.DispatchAsync(CreateAddItemCommand("add-before-supervisory", 53, "Galaxy.Tag.Value"));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateAdviseSupervisoryCommand(
|
|
"advise-supervisory",
|
|
53,
|
|
506));
|
|
|
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
|
Assert.Equal(53, fakeComObject.AdviseSupervisoryServerHandle);
|
|
Assert.Equal(506, fakeComObject.AdviseSupervisoryItemHandle);
|
|
Assert.Equal(runtime.StaThreadId, fakeComObject.AdviseSupervisoryThreadId);
|
|
Assert.Null(fakeComObject.AdviseServerHandle);
|
|
|
|
RegisteredAdviceHandle adviceHandle = Assert.Single(
|
|
await session.GetRegisteredAdviceHandlesAsync());
|
|
Assert.Equal(53, adviceHandle.ServerHandle);
|
|
Assert.Equal(506, adviceHandle.ItemHandle);
|
|
Assert.Equal(MxAccessAdviceKind.Supervisory, adviceHandle.AdviceKind);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_UnAdvise_CallsMxAccessOnStaAndRemovesTrackedAdvice()
|
|
{
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 54,
|
|
addItemHandle: 507);
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-unadvise", "client-a"));
|
|
await session.DispatchAsync(CreateAddItemCommand("add-before-unadvise", 54, "Galaxy.Tag.Value"));
|
|
await session.DispatchAsync(CreateAdviseCommand("advise-before-unadvise", 54, 507));
|
|
await session.DispatchAsync(CreateAdviseSupervisoryCommand("supervisory-before-unadvise", 54, 507));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateUnAdviseCommand(
|
|
"unadvise",
|
|
54,
|
|
507));
|
|
|
|
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
|
Assert.Equal(54, fakeComObject.UnAdviseServerHandle);
|
|
Assert.Equal(507, fakeComObject.UnAdvisedItemHandle);
|
|
Assert.Equal(runtime.StaThreadId, fakeComObject.UnAdviseThreadId);
|
|
Assert.Empty(await session.GetRegisteredAdviceHandlesAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_AdviseWhenMxAccessThrows_PreservesHResultAndDoesNotTrackAdvice()
|
|
{
|
|
const int hresult = unchecked((int)0x80070057);
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 55,
|
|
addItemHandle: 508,
|
|
adviseException: new COMException("Invalid item handle.", hresult));
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-advise-failure", "client-a"));
|
|
await session.DispatchAsync(CreateAddItemCommand("add-before-advise-failure", 55, "Galaxy.Tag.Value"));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateAdviseCommand(
|
|
"advise-failure",
|
|
55,
|
|
999));
|
|
|
|
Assert.Equal(ProtocolStatusCode.MxaccessFailure, reply.ProtocolStatus.Code);
|
|
Assert.True(reply.HasHresult);
|
|
Assert.Equal(hresult, reply.Hresult);
|
|
Assert.Contains("0x80070057", reply.DiagnosticMessage);
|
|
Assert.Equal(55, fakeComObject.AdviseServerHandle);
|
|
Assert.Equal(999, fakeComObject.AdvisedItemHandle);
|
|
Assert.Empty(await session.GetRegisteredAdviceHandlesAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_UnAdviseWhenMxAccessThrows_PreservesHResultAndKeepsTrackedAdvice()
|
|
{
|
|
const int hresult = unchecked((int)0x80070057);
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 56,
|
|
addItemHandle: 509,
|
|
unAdviseException: new COMException("Invalid item handle.", hresult));
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-unadvise-failure", "client-a"));
|
|
await session.DispatchAsync(CreateAddItemCommand("add-before-unadvise-failure", 56, "Galaxy.Tag.Value"));
|
|
await session.DispatchAsync(CreateAdviseCommand("advise-before-unadvise-failure", 56, 509));
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(CreateUnAdviseCommand(
|
|
"unadvise-failure",
|
|
56,
|
|
509));
|
|
|
|
Assert.Equal(ProtocolStatusCode.MxaccessFailure, reply.ProtocolStatus.Code);
|
|
Assert.True(reply.HasHresult);
|
|
Assert.Equal(hresult, reply.Hresult);
|
|
Assert.Contains("0x80070057", reply.DiagnosticMessage);
|
|
Assert.Equal(56, fakeComObject.UnAdviseServerHandle);
|
|
Assert.Equal(509, fakeComObject.UnAdvisedItemHandle);
|
|
|
|
RegisteredAdviceHandle adviceHandle = Assert.Single(
|
|
await session.GetRegisteredAdviceHandlesAsync());
|
|
Assert.Equal(MxAccessAdviceKind.Plain, adviceHandle.AdviceKind);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ShutdownGracefullyAsync_CleansHandlesInAdviceItemServerOrder()
|
|
{
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 58,
|
|
addItemHandle: 510);
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-shutdown", "client-a"));
|
|
await session.DispatchAsync(CreateAddItemCommand("add-before-shutdown", 58, "Galaxy.Tag.Value"));
|
|
await session.DispatchAsync(CreateAdviseCommand("advise-before-shutdown", 58, 510));
|
|
await session.DispatchAsync(CreateAdviseSupervisoryCommand("supervisory-before-shutdown", 58, 510));
|
|
|
|
MxAccessShutdownResult result = await session.ShutdownGracefullyAsync(TimeSpan.FromSeconds(2));
|
|
|
|
Assert.True(result.Succeeded);
|
|
Assert.Equal(
|
|
new[] { "UnAdvise:58:510", "RemoveItem:58:510", "Unregister:58" },
|
|
fakeComObject.OperationNames.Where(name => name.StartsWith("Un", StringComparison.Ordinal)
|
|
|| name.StartsWith("Remove", StringComparison.Ordinal)));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ShutdownGracefullyAsync_RecordsCleanupFailuresAndContinues()
|
|
{
|
|
const int hresult = unchecked((int)0x80070057);
|
|
COMException cleanupException = new("Invalid handle.", hresult);
|
|
FakeMxAccessComObject fakeComObject = new(
|
|
registerHandle: 59,
|
|
addItemHandle: 511,
|
|
unregisterException: cleanupException,
|
|
removeItemException: cleanupException,
|
|
unAdviseException: cleanupException);
|
|
FakeMxAccessComObjectFactory factory = new(fakeComObject);
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
await session.DispatchAsync(CreateRegisterCommand("register-before-shutdown-failure", "client-a"));
|
|
await session.DispatchAsync(CreateAddItemCommand("add-before-shutdown-failure", 59, "Galaxy.Tag.Value"));
|
|
await session.DispatchAsync(CreateAdviseCommand("advise-before-shutdown-failure", 59, 511));
|
|
|
|
MxAccessShutdownResult result = await session.ShutdownGracefullyAsync(TimeSpan.FromSeconds(2));
|
|
|
|
Assert.False(result.Succeeded);
|
|
Assert.Equal(new[] { "UnAdvise", "RemoveItem", "Unregister" }, result.Failures.Select(failure => failure.Operation));
|
|
Assert.All(result.Failures, failure => Assert.Equal(hresult, failure.HResult));
|
|
Assert.Contains("Unregister:59", fakeComObject.OperationNames);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_RegisterWithoutPayload_ReturnsInvalidRequest()
|
|
{
|
|
FakeMxAccessComObjectFactory factory = new(new FakeMxAccessComObject(registerHandle: 45));
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(new StaCommand(
|
|
"session-1",
|
|
"missing-payload",
|
|
new MxCommand
|
|
{
|
|
Kind = MxCommandKind.Register,
|
|
}));
|
|
|
|
Assert.Equal(ProtocolStatusCode.InvalidRequest, reply.ProtocolStatus.Code);
|
|
Assert.Null(factory.FakeComObject.RegisteredClientName);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_AddItemWithoutPayload_ReturnsInvalidRequest()
|
|
{
|
|
FakeMxAccessComObjectFactory factory = new(new FakeMxAccessComObject(registerHandle: 51));
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(new StaCommand(
|
|
"session-1",
|
|
"missing-add-payload",
|
|
new MxCommand
|
|
{
|
|
Kind = MxCommandKind.AddItem,
|
|
}));
|
|
|
|
Assert.Equal(ProtocolStatusCode.InvalidRequest, reply.ProtocolStatus.Code);
|
|
Assert.Null(factory.FakeComObject.AddItemDefinition);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DispatchAsync_AdviseWithoutPayload_ReturnsInvalidRequest()
|
|
{
|
|
FakeMxAccessComObjectFactory factory = new(new FakeMxAccessComObject(registerHandle: 57));
|
|
using StaRuntime runtime = CreateRuntime();
|
|
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
|
|
await session.StartAsync(workerProcessId: 1234);
|
|
|
|
MxCommandReply reply = await session.DispatchAsync(new StaCommand(
|
|
"session-1",
|
|
"missing-advise-payload",
|
|
new MxCommand
|
|
{
|
|
Kind = MxCommandKind.Advise,
|
|
}));
|
|
|
|
Assert.Equal(ProtocolStatusCode.InvalidRequest, reply.ProtocolStatus.Code);
|
|
Assert.Null(factory.FakeComObject.AdviseServerHandle);
|
|
}
|
|
|
|
private static StaCommand CreateRegisterCommand(
|
|
string correlationId,
|
|
string clientName)
|
|
{
|
|
return new StaCommand(
|
|
"session-1",
|
|
correlationId,
|
|
new MxCommand
|
|
{
|
|
Kind = MxCommandKind.Register,
|
|
Register = new RegisterCommand
|
|
{
|
|
ClientName = clientName,
|
|
},
|
|
});
|
|
}
|
|
|
|
private static StaCommand CreateUnregisterCommand(
|
|
string correlationId,
|
|
int serverHandle)
|
|
{
|
|
return new StaCommand(
|
|
"session-1",
|
|
correlationId,
|
|
new MxCommand
|
|
{
|
|
Kind = MxCommandKind.Unregister,
|
|
Unregister = new UnregisterCommand
|
|
{
|
|
ServerHandle = serverHandle,
|
|
},
|
|
});
|
|
}
|
|
|
|
private static StaCommand CreateAddItemCommand(
|
|
string correlationId,
|
|
int serverHandle,
|
|
string itemDefinition)
|
|
{
|
|
return new StaCommand(
|
|
"session-1",
|
|
correlationId,
|
|
new MxCommand
|
|
{
|
|
Kind = MxCommandKind.AddItem,
|
|
AddItem = new AddItemCommand
|
|
{
|
|
ServerHandle = serverHandle,
|
|
ItemDefinition = itemDefinition,
|
|
},
|
|
});
|
|
}
|
|
|
|
private static StaCommand CreateAddItem2Command(
|
|
string correlationId,
|
|
int serverHandle,
|
|
string itemDefinition,
|
|
string itemContext)
|
|
{
|
|
return new StaCommand(
|
|
"session-1",
|
|
correlationId,
|
|
new MxCommand
|
|
{
|
|
Kind = MxCommandKind.AddItem2,
|
|
AddItem2 = new AddItem2Command
|
|
{
|
|
ServerHandle = serverHandle,
|
|
ItemDefinition = itemDefinition,
|
|
ItemContext = itemContext,
|
|
},
|
|
});
|
|
}
|
|
|
|
private static StaCommand CreateRemoveItemCommand(
|
|
string correlationId,
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
return new StaCommand(
|
|
"session-1",
|
|
correlationId,
|
|
new MxCommand
|
|
{
|
|
Kind = MxCommandKind.RemoveItem,
|
|
RemoveItem = new RemoveItemCommand
|
|
{
|
|
ServerHandle = serverHandle,
|
|
ItemHandle = itemHandle,
|
|
},
|
|
});
|
|
}
|
|
|
|
private static StaCommand CreateAdviseCommand(
|
|
string correlationId,
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
return new StaCommand(
|
|
"session-1",
|
|
correlationId,
|
|
new MxCommand
|
|
{
|
|
Kind = MxCommandKind.Advise,
|
|
Advise = new AdviseCommand
|
|
{
|
|
ServerHandle = serverHandle,
|
|
ItemHandle = itemHandle,
|
|
},
|
|
});
|
|
}
|
|
|
|
private static StaCommand CreateUnAdviseCommand(
|
|
string correlationId,
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
return new StaCommand(
|
|
"session-1",
|
|
correlationId,
|
|
new MxCommand
|
|
{
|
|
Kind = MxCommandKind.UnAdvise,
|
|
UnAdvise = new UnAdviseCommand
|
|
{
|
|
ServerHandle = serverHandle,
|
|
ItemHandle = itemHandle,
|
|
},
|
|
});
|
|
}
|
|
|
|
private static StaCommand CreateAdviseSupervisoryCommand(
|
|
string correlationId,
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
return new StaCommand(
|
|
"session-1",
|
|
correlationId,
|
|
new MxCommand
|
|
{
|
|
Kind = MxCommandKind.AdviseSupervisory,
|
|
AdviseSupervisory = new AdviseSupervisoryCommand
|
|
{
|
|
ServerHandle = serverHandle,
|
|
ItemHandle = itemHandle,
|
|
},
|
|
});
|
|
}
|
|
|
|
private static StaRuntime CreateRuntime()
|
|
{
|
|
return new StaRuntime(
|
|
new NoopComApartmentInitializer(),
|
|
new StaMessagePump(),
|
|
TimeSpan.FromMilliseconds(25));
|
|
}
|
|
|
|
private sealed class FakeMxAccessComObject
|
|
{
|
|
private readonly int registerHandle;
|
|
private readonly int addItemHandle;
|
|
private readonly int addItem2Handle;
|
|
private readonly Exception? unregisterException;
|
|
private readonly Exception? addItemException;
|
|
private readonly Exception? addItem2Exception;
|
|
private readonly Exception? removeItemException;
|
|
private readonly Exception? adviseException;
|
|
private readonly Exception? unAdviseException;
|
|
private readonly Exception? adviseSupervisoryException;
|
|
private readonly List<string> operationNames = new();
|
|
|
|
public FakeMxAccessComObject(
|
|
int registerHandle,
|
|
int addItemHandle = 0,
|
|
int addItem2Handle = 0,
|
|
Exception? unregisterException = null,
|
|
Exception? addItemException = null,
|
|
Exception? addItem2Exception = null,
|
|
Exception? removeItemException = null,
|
|
Exception? adviseException = null,
|
|
Exception? unAdviseException = null,
|
|
Exception? adviseSupervisoryException = null)
|
|
{
|
|
this.registerHandle = registerHandle;
|
|
this.addItemHandle = addItemHandle;
|
|
this.addItem2Handle = addItem2Handle;
|
|
this.unregisterException = unregisterException;
|
|
this.addItemException = addItemException;
|
|
this.addItem2Exception = addItem2Exception;
|
|
this.removeItemException = removeItemException;
|
|
this.adviseException = adviseException;
|
|
this.unAdviseException = unAdviseException;
|
|
this.adviseSupervisoryException = adviseSupervisoryException;
|
|
}
|
|
|
|
public string? RegisteredClientName { get; private set; }
|
|
|
|
public int? RegisterThreadId { get; private set; }
|
|
|
|
public int? UnregisteredServerHandle { get; private set; }
|
|
|
|
public int? UnregisterThreadId { get; private set; }
|
|
|
|
public int? AddItemServerHandle { get; private set; }
|
|
|
|
public string? AddItemDefinition { get; private set; }
|
|
|
|
public int? AddItemThreadId { get; private set; }
|
|
|
|
public int? AddItem2ServerHandle { get; private set; }
|
|
|
|
public string? AddItem2Definition { get; private set; }
|
|
|
|
public string? AddItem2Context { get; private set; }
|
|
|
|
public int? AddItem2ThreadId { get; private set; }
|
|
|
|
public int? RemoveItemServerHandle { get; private set; }
|
|
|
|
public int? RemovedItemHandle { get; private set; }
|
|
|
|
public int? RemoveItemThreadId { get; private set; }
|
|
|
|
public int? AdviseServerHandle { get; private set; }
|
|
|
|
public int? AdvisedItemHandle { get; private set; }
|
|
|
|
public int? AdviseThreadId { get; private set; }
|
|
|
|
public int? UnAdviseServerHandle { get; private set; }
|
|
|
|
public int? UnAdvisedItemHandle { get; private set; }
|
|
|
|
public int? UnAdviseThreadId { get; private set; }
|
|
|
|
public int? AdviseSupervisoryServerHandle { get; private set; }
|
|
|
|
public int? AdviseSupervisoryItemHandle { get; private set; }
|
|
|
|
public int? AdviseSupervisoryThreadId { get; private set; }
|
|
|
|
public IReadOnlyList<string> OperationNames => operationNames.ToArray();
|
|
|
|
public int Register(string clientName)
|
|
{
|
|
operationNames.Add($"Register:{clientName}");
|
|
RegisteredClientName = clientName;
|
|
RegisterThreadId = Environment.CurrentManagedThreadId;
|
|
|
|
return registerHandle;
|
|
}
|
|
|
|
public void Unregister(int serverHandle)
|
|
{
|
|
operationNames.Add($"Unregister:{serverHandle}");
|
|
UnregisteredServerHandle = serverHandle;
|
|
UnregisterThreadId = Environment.CurrentManagedThreadId;
|
|
|
|
if (unregisterException is not null)
|
|
{
|
|
throw unregisterException;
|
|
}
|
|
}
|
|
|
|
public int AddItem(
|
|
int serverHandle,
|
|
string itemDefinition)
|
|
{
|
|
operationNames.Add($"AddItem:{serverHandle}:{itemDefinition}");
|
|
AddItemServerHandle = serverHandle;
|
|
AddItemDefinition = itemDefinition;
|
|
AddItemThreadId = Environment.CurrentManagedThreadId;
|
|
|
|
if (addItemException is not null)
|
|
{
|
|
throw addItemException;
|
|
}
|
|
|
|
return addItemHandle;
|
|
}
|
|
|
|
public int AddItem2(
|
|
int serverHandle,
|
|
string itemDefinition,
|
|
string itemContext)
|
|
{
|
|
operationNames.Add($"AddItem2:{serverHandle}:{itemDefinition}:{itemContext}");
|
|
AddItem2ServerHandle = serverHandle;
|
|
AddItem2Definition = itemDefinition;
|
|
AddItem2Context = itemContext;
|
|
AddItem2ThreadId = Environment.CurrentManagedThreadId;
|
|
|
|
if (addItem2Exception is not null)
|
|
{
|
|
throw addItem2Exception;
|
|
}
|
|
|
|
return addItem2Handle;
|
|
}
|
|
|
|
public void RemoveItem(
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
operationNames.Add($"RemoveItem:{serverHandle}:{itemHandle}");
|
|
RemoveItemServerHandle = serverHandle;
|
|
RemovedItemHandle = itemHandle;
|
|
RemoveItemThreadId = Environment.CurrentManagedThreadId;
|
|
|
|
if (removeItemException is not null)
|
|
{
|
|
throw removeItemException;
|
|
}
|
|
}
|
|
|
|
public void Advise(
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
operationNames.Add($"Advise:{serverHandle}:{itemHandle}");
|
|
AdviseServerHandle = serverHandle;
|
|
AdvisedItemHandle = itemHandle;
|
|
AdviseThreadId = Environment.CurrentManagedThreadId;
|
|
|
|
if (adviseException is not null)
|
|
{
|
|
throw adviseException;
|
|
}
|
|
}
|
|
|
|
public void UnAdvise(
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
operationNames.Add($"UnAdvise:{serverHandle}:{itemHandle}");
|
|
UnAdviseServerHandle = serverHandle;
|
|
UnAdvisedItemHandle = itemHandle;
|
|
UnAdviseThreadId = Environment.CurrentManagedThreadId;
|
|
|
|
if (unAdviseException is not null)
|
|
{
|
|
throw unAdviseException;
|
|
}
|
|
}
|
|
|
|
public void AdviseSupervisory(
|
|
int serverHandle,
|
|
int itemHandle)
|
|
{
|
|
operationNames.Add($"AdviseSupervisory:{serverHandle}:{itemHandle}");
|
|
AdviseSupervisoryServerHandle = serverHandle;
|
|
AdviseSupervisoryItemHandle = itemHandle;
|
|
AdviseSupervisoryThreadId = Environment.CurrentManagedThreadId;
|
|
|
|
if (adviseSupervisoryException is not null)
|
|
{
|
|
throw adviseSupervisoryException;
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class FakeMxAccessComObjectFactory : IMxAccessComObjectFactory
|
|
{
|
|
public FakeMxAccessComObjectFactory(FakeMxAccessComObject fakeComObject)
|
|
{
|
|
FakeComObject = fakeComObject;
|
|
}
|
|
|
|
public FakeMxAccessComObject FakeComObject { get; }
|
|
|
|
public object Create()
|
|
{
|
|
return FakeComObject;
|
|
}
|
|
}
|
|
|
|
private sealed class NoopEventSink : IMxAccessEventSink
|
|
{
|
|
public void Attach(
|
|
object mxAccessComObject,
|
|
string sessionId)
|
|
{
|
|
}
|
|
|
|
public void Detach()
|
|
{
|
|
}
|
|
}
|
|
|
|
private sealed class NoopComApartmentInitializer : IStaComApartmentInitializer
|
|
{
|
|
public void Initialize()
|
|
{
|
|
}
|
|
|
|
public void Uninitialize()
|
|
{
|
|
}
|
|
}
|
|
}
|