dc9c0c950c
Apply the ZB.MOM.WW. prefix to all gateway-side projects, folders,
.csproj/.sln contents, C# namespaces, using directives, generated proto
C# (csharp_namespace + checked-in generated files), InternalsVisibleTo
attributes, project-name string literals (LoadProject, .sln lookups,
worker exe paths, staticwebassets manifest), and the install/script/doc
references that point at any of the above. Migrate the solution from
.sln to .slnx via `dotnet sln migrate` and delete the old file.
External-runtime identifiers are intentionally NOT prefixed so external
configuration keeps working:
- GatewayMetrics.cs MeterName ("MxGateway.Server")
- DashboardAuthenticationDefaults Scheme/Policy ("MxGateway.Dashboard")
- GatewayRequestLoggingMiddleware logger category ("MxGateway.Request")
- StaRuntime thread name ("MxGateway.Worker.STA")
- appsettings.json root section "MxGateway" + env-var prefix
MxGateway__... and secret-name MxGateway:ApiKeyPepper
- C:\ProgramData\MxGateway\ data dir paths
Also fixes two tests that were not rename-related but became visible
while validating the rename:
- WorkerLiveMxAccessSmokeTests.ShutDownAsync: cancellation that the
gateway service correctly maps to RpcException(Cancelled) per gRPC
convention was being misclassified as a stream fault. Added a sibling
catch on RpcException with StatusCode.Cancelled.
- IntegrationTestEnvironment.ResolveRepositoryRoot: extracted IsRepositoryRoot
and made it accept either a .git marker OR a .sln/.slnx next to src/
so the worker-exe walker works in non-git working copies.
clients/proto/proto-inputs.json's protoRoot updated to point at
src/ZB.MOM.WW.MxGateway.Contracts/Protos.
Verified by `dotnet build` and a full `dotnet test` of the .slnx with
MXGATEWAY_RUN_LIVE_{MXACCESS,LDAP,GALAXY}_TESTS=1:
Tests: 472/472 pass
Worker.Tests: 280/280 pass (4 dev-rig [Fact(Skip=...)] skipped)
IntegrationTests: 18/18 pass
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
215 lines
9.1 KiB
C#
215 lines
9.1 KiB
C#
using System;
|
|
using ArchestrA.MxAccess;
|
|
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
|
using ZB.MOM.WW.MxGateway.Worker.MxAccess;
|
|
using ComMxDataType = ArchestrA.MxAccess.MxDataType;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Worker.Tests.MxAccess;
|
|
|
|
/// <summary>
|
|
/// Integrated tests for <see cref="MxAccessBaseEventSink"/>: drive an MXAccess COM
|
|
/// event through the real sink → <see cref="MxAccessEventMapper"/> →
|
|
/// <see cref="MxAccessEventQueue"/> pipeline and assert a correctly-converted
|
|
/// protobuf <see cref="WorkerEvent"/> lands in the queue.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Boundary: the COM-side <c>+=</c> subscription performed in
|
|
/// <see cref="MxAccessBaseEventSink.Attach"/> casts the supplied object to the
|
|
/// sealed <c>LMXProxyServerClass</c> RCW and cannot run without a live MXAccess COM
|
|
/// object, so <c>Attach</c>/<c>Detach</c> are not exercised here. The event
|
|
/// handlers themselves (<c>OnDataChange</c>, <c>OnWriteComplete</c>,
|
|
/// <c>OperationComplete</c>, <c>OnBufferedDataChange</c>) are the exact delegate
|
|
/// targets the COM runtime invokes; calling them directly reproduces an STA-thread
|
|
/// COM callback and exercises the genuine conversion + enqueue path. The
|
|
/// <c>sessionId</c> normally set by <c>Attach</c> defaults to empty here, which the
|
|
/// assertions account for. The COM-event-conversion fault branch is left to
|
|
/// <see cref="MxAccessEventMapperTests"/> and the queue's own fault tests.
|
|
/// </remarks>
|
|
public sealed class MxAccessBaseEventSinkTests
|
|
{
|
|
/// <summary>
|
|
/// Verifies that an OnDataChange COM callback converts to a protobuf event and lands in the queue.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OnDataChange_ComCallback_ConvertedEventLandsInQueue()
|
|
{
|
|
MxAccessEventQueue queue = new();
|
|
MxAccessBaseEventSink sink = new(queue, new MxAccessEventMapper());
|
|
DateTime timestamp = new(2026, 5, 18, 9, 15, 0, DateTimeKind.Utc);
|
|
MXSTATUS_PROXY[] statuses = Array.Empty<MXSTATUS_PROXY>();
|
|
|
|
sink.OnDataChange(
|
|
hLMXServerHandle: 7,
|
|
phItemHandle: 21,
|
|
pvItemValue: 1234,
|
|
pwItemQuality: 192,
|
|
pftItemTimeStamp: timestamp,
|
|
ref statuses);
|
|
|
|
Assert.Equal(1, queue.Count);
|
|
Assert.Equal(1UL, queue.LastEventSequence);
|
|
Assert.True(queue.TryDequeue(out WorkerEvent? workerEvent));
|
|
Assert.NotNull(workerEvent);
|
|
|
|
MxEvent mxEvent = workerEvent!.Event;
|
|
Assert.Equal(MxEventFamily.OnDataChange, mxEvent.Family);
|
|
Assert.Equal(MxEvent.BodyOneofCase.OnDataChange, mxEvent.BodyCase);
|
|
Assert.Equal(7, mxEvent.ServerHandle);
|
|
Assert.Equal(21, mxEvent.ItemHandle);
|
|
Assert.Equal(1234, mxEvent.Value.Int32Value);
|
|
Assert.Equal(192, mxEvent.Quality);
|
|
Assert.Equal(timestamp, mxEvent.SourceTimestamp.ToDateTime());
|
|
Assert.Equal(1UL, mxEvent.WorkerSequence);
|
|
Assert.NotNull(mxEvent.WorkerTimestamp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that an OnDataChange COM callback also writes the value into the
|
|
/// per-session value cache, so a later <c>ReadBulk</c> on an already-advised
|
|
/// tag can serve the cached value without re-advising. The cache update must
|
|
/// fire after the event has cleared the outbound queue — verified here by
|
|
/// checking the cache only after the queue confirms the enqueue succeeded.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OnDataChange_ComCallback_PopulatesValueCache()
|
|
{
|
|
MxAccessEventQueue queue = new();
|
|
MxAccessValueCache cache = new();
|
|
MxAccessBaseEventSink sink = new(queue, new MxAccessEventMapper(), cache);
|
|
DateTime timestamp = new(2026, 5, 18, 9, 15, 0, DateTimeKind.Utc);
|
|
MXSTATUS_PROXY[] statuses = Array.Empty<MXSTATUS_PROXY>();
|
|
|
|
sink.OnDataChange(
|
|
hLMXServerHandle: 7,
|
|
phItemHandle: 21,
|
|
pvItemValue: 1234,
|
|
pwItemQuality: 192,
|
|
pftItemTimeStamp: timestamp,
|
|
ref statuses);
|
|
|
|
Assert.Equal(1, queue.Count);
|
|
Assert.True(cache.TryGet(7, 21, out MxAccessValueCache.CachedValue cached));
|
|
Assert.Equal(1UL, cached.Version);
|
|
Assert.Equal(1234, cached.Value.Int32Value);
|
|
Assert.Equal(192, cached.Quality);
|
|
Assert.Equal(timestamp, cached.SourceTimestamp.ToDateTime());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that the sink-bound <c>ValueCache</c> is exposed for sharing with
|
|
/// the owning <see cref="MxAccessSession"/> so writes and reads see the same
|
|
/// instance.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ValueCache_ReturnsTheInstanceBoundAtConstruction()
|
|
{
|
|
MxAccessEventQueue queue = new();
|
|
MxAccessValueCache cache = new();
|
|
MxAccessBaseEventSink sink = new(queue, new MxAccessEventMapper(), cache);
|
|
|
|
Assert.Same(cache, sink.ValueCache);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that consecutive OnDataChange callbacks land in the queue with monotonic sequences.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OnDataChange_MultipleComCallbacks_QueueAssignsMonotonicSequences()
|
|
{
|
|
MxAccessEventQueue queue = new();
|
|
MxAccessBaseEventSink sink = new(queue, new MxAccessEventMapper());
|
|
MXSTATUS_PROXY[] statuses = Array.Empty<MXSTATUS_PROXY>();
|
|
|
|
sink.OnDataChange(1, 10, 100, 192, DateTime.UtcNow, ref statuses);
|
|
sink.OnDataChange(1, 11, 200, 192, DateTime.UtcNow, ref statuses);
|
|
sink.OnDataChange(1, 12, 300, 192, DateTime.UtcNow, ref statuses);
|
|
|
|
Assert.Equal(3, queue.Count);
|
|
Assert.Equal(3UL, queue.LastEventSequence);
|
|
|
|
Assert.True(queue.TryDequeue(out WorkerEvent? first));
|
|
Assert.True(queue.TryDequeue(out WorkerEvent? second));
|
|
Assert.True(queue.TryDequeue(out WorkerEvent? third));
|
|
Assert.Equal(1UL, first!.Event.WorkerSequence);
|
|
Assert.Equal(2UL, second!.Event.WorkerSequence);
|
|
Assert.Equal(3UL, third!.Event.WorkerSequence);
|
|
Assert.Equal(10, first.Event.ItemHandle);
|
|
Assert.Equal(12, third.Event.ItemHandle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that an OnWriteComplete COM callback lands in the queue with the correct family.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OnWriteComplete_ComCallback_ConvertedEventLandsInQueue()
|
|
{
|
|
MxAccessEventQueue queue = new();
|
|
MxAccessBaseEventSink sink = new(queue, new MxAccessEventMapper());
|
|
MXSTATUS_PROXY[] statuses = Array.Empty<MXSTATUS_PROXY>();
|
|
|
|
sink.OnWriteComplete(hLMXServerHandle: 3, phItemHandle: 9, ref statuses);
|
|
|
|
Assert.Equal(1, queue.Count);
|
|
Assert.True(queue.TryDequeue(out WorkerEvent? workerEvent));
|
|
MxEvent mxEvent = workerEvent!.Event;
|
|
Assert.Equal(MxEventFamily.OnWriteComplete, mxEvent.Family);
|
|
Assert.Equal(MxEvent.BodyOneofCase.OnWriteComplete, mxEvent.BodyCase);
|
|
Assert.Equal(3, mxEvent.ServerHandle);
|
|
Assert.Equal(9, mxEvent.ItemHandle);
|
|
Assert.Equal(1UL, mxEvent.WorkerSequence);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that an OperationComplete COM callback lands in the queue with the correct family.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OperationComplete_ComCallback_ConvertedEventLandsInQueue()
|
|
{
|
|
MxAccessEventQueue queue = new();
|
|
MxAccessBaseEventSink sink = new(queue, new MxAccessEventMapper());
|
|
MXSTATUS_PROXY[] statuses = Array.Empty<MXSTATUS_PROXY>();
|
|
|
|
sink.OperationComplete(hLMXServerHandle: 4, phItemHandle: 8, ref statuses);
|
|
|
|
Assert.Equal(1, queue.Count);
|
|
Assert.True(queue.TryDequeue(out WorkerEvent? workerEvent));
|
|
MxEvent mxEvent = workerEvent!.Event;
|
|
Assert.Equal(MxEventFamily.OperationComplete, mxEvent.Family);
|
|
Assert.Equal(MxEvent.BodyOneofCase.OperationComplete, mxEvent.BodyCase);
|
|
Assert.Equal(4, mxEvent.ServerHandle);
|
|
Assert.Equal(8, mxEvent.ItemHandle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that an OnBufferedDataChange COM callback converts the value and lands in the queue.
|
|
/// </summary>
|
|
[Fact]
|
|
public void OnBufferedDataChange_ComCallback_ConvertedEventLandsInQueue()
|
|
{
|
|
MxAccessEventQueue queue = new();
|
|
MxAccessBaseEventSink sink = new(queue, new MxAccessEventMapper());
|
|
MXSTATUS_PROXY[] statuses = Array.Empty<MXSTATUS_PROXY>();
|
|
|
|
// Raw MXAccess data-type code 2 == Integer (see MxAccessEventMapper.MapMxDataType).
|
|
const int integerDataTypeCode = 2;
|
|
|
|
sink.OnBufferedDataChange(
|
|
hLMXServerHandle: 5,
|
|
phItemHandle: 13,
|
|
dtDataType: (ComMxDataType)integerDataTypeCode,
|
|
pvItemValue: 77,
|
|
pwItemQuality: 192,
|
|
pftItemTimeStamp: DateTime.UtcNow,
|
|
ref statuses);
|
|
|
|
Assert.Equal(1, queue.Count);
|
|
Assert.True(queue.TryDequeue(out WorkerEvent? workerEvent));
|
|
MxEvent mxEvent = workerEvent!.Event;
|
|
Assert.Equal(MxEventFamily.OnBufferedDataChange, mxEvent.Family);
|
|
Assert.Equal(MxEvent.BodyOneofCase.OnBufferedDataChange, mxEvent.BodyCase);
|
|
Assert.Equal(5, mxEvent.ServerHandle);
|
|
Assert.Equal(13, mxEvent.ItemHandle);
|
|
Assert.Equal(integerDataTypeCode, mxEvent.OnBufferedDataChange.RawDataType);
|
|
}
|
|
}
|