docs(src): add missing XML docs and strip tracking-ID comments
Sweep of 203 source files resolving CommentChecker findings: add <summary>/<param>/<returns>/<inheritdoc> where missing, and remove resolved task/issue tracking markers (Tests-NNN, Worker-NNN, Server-NNN, Task N) from code comments. Comment/doc-only — no logic changes. Server+Tests build clean under TreatWarningsAsErrors.
This commit is contained in:
@@ -143,10 +143,7 @@ public sealed class WorkerApplicationTests
|
||||
|
||||
private sealed class SucceedingPipeClient : IWorkerPipeClient
|
||||
{
|
||||
/// <summary>Runs the worker pipe client successfully.</summary>
|
||||
/// <param name="options">Worker options.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Completed task.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync(
|
||||
WorkerOptions options,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -166,10 +163,7 @@ public sealed class WorkerApplicationTests
|
||||
_exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>Runs the worker pipe client and throws configured exception.</summary>
|
||||
/// <param name="options">Worker options.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Never completes; always throws.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync(
|
||||
WorkerOptions options,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -132,7 +132,7 @@ public sealed class VariantConverterTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-010 regression: a 32-bit <see cref="uint"/> with an expected
|
||||
/// A 32-bit <see cref="uint"/> with an expected
|
||||
/// data type of <see cref="MxDataType.Time"/> must not be projected as a
|
||||
/// Windows FILETIME. A uint can only hold the low 32 bits of a FILETIME,
|
||||
/// which would silently render as a near-1601 timestamp; the converter
|
||||
|
||||
@@ -14,6 +14,7 @@ public sealed class WorkerFrameProtocolTests
|
||||
private const string Nonce = "nonce-secret";
|
||||
|
||||
/// <summary>Verifies that valid envelopes round-trip through write and read.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WriteAndReadAsync_WithValidEnvelope_RoundTripsFrame()
|
||||
{
|
||||
@@ -32,6 +33,7 @@ public sealed class WorkerFrameProtocolTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that wrong protocol version throws mismatch error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WithWrongProtocolVersion_ThrowsProtocolVersionMismatch()
|
||||
{
|
||||
@@ -49,6 +51,7 @@ public sealed class WorkerFrameProtocolTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that wrong session ID throws mismatch error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WithWrongSessionId_ThrowsSessionMismatch()
|
||||
{
|
||||
@@ -72,6 +75,7 @@ public sealed class WorkerFrameProtocolTests
|
||||
/// length prefix is the leading four bytes of the stream, so a four-zero-byte
|
||||
/// stream is exactly a frame declaring a zero-length payload.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WithZeroLengthPayload_ThrowsMalformedLength()
|
||||
{
|
||||
@@ -93,6 +97,7 @@ public sealed class WorkerFrameProtocolTests
|
||||
/// A small maximum is configured so the rejection is asserted without
|
||||
/// allocating a multi-megabyte buffer.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WithPayloadAboveConfiguredMaximum_ThrowsMessageTooLarge()
|
||||
{
|
||||
@@ -115,6 +120,7 @@ public sealed class WorkerFrameProtocolTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that malformed payload throws invalid envelope error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WithMalformedPayload_ThrowsInvalidEnvelope()
|
||||
{
|
||||
@@ -130,7 +136,7 @@ public sealed class WorkerFrameProtocolTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-021 (a): pins the <c>EndOfStream</c> branch of
|
||||
/// Pins the <c>EndOfStream</c> branch of
|
||||
/// <c>WorkerFrameReader.ReadExactlyOrThrowAsync</c>. The gateway
|
||||
/// closing its end of the pipe during a partial-frame read is the
|
||||
/// most common production transport failure; the reader must
|
||||
@@ -140,6 +146,7 @@ public sealed class WorkerFrameProtocolTests
|
||||
/// payload but only supplies 50 bytes, so the inner read loop sees
|
||||
/// <c>bytesRead == 0</c> mid-frame.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WhenStreamEndsMidFrame_ThrowsEndOfStream()
|
||||
{
|
||||
@@ -157,16 +164,16 @@ public sealed class WorkerFrameProtocolTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-021 (b): pins the writer-side
|
||||
/// <c>MessageTooLarge</c> branch. A session that constructs an
|
||||
/// envelope whose serialised size exceeds <c>MaxMessageBytes</c>
|
||||
/// must be rejected by the writer before any bytes are sent down
|
||||
/// the pipe, so a misbehaving producer cannot push the receiver
|
||||
/// past its bounds. A small <c>MaxMessageBytes</c> is configured
|
||||
/// so a modest <c>GatewayHello</c> payload — with its nonce
|
||||
/// padded out to several hundred bytes — exceeds the limit
|
||||
/// Pins the writer-side <c>MessageTooLarge</c> branch. A session that
|
||||
/// constructs an envelope whose serialised size exceeds
|
||||
/// <c>MaxMessageBytes</c> must be rejected by the writer before any
|
||||
/// bytes are sent down the pipe, so a misbehaving producer cannot
|
||||
/// push the receiver past its bounds. A small <c>MaxMessageBytes</c>
|
||||
/// is configured so a modest <c>GatewayHello</c> payload — with its
|
||||
/// nonce padded out to several hundred bytes — exceeds the limit
|
||||
/// without allocating anything large.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WriteAsync_WithEnvelopeAboveConfiguredMaximum_ThrowsMessageTooLarge()
|
||||
{
|
||||
@@ -191,11 +198,10 @@ public sealed class WorkerFrameProtocolTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-021 (c): documents that the writer-side
|
||||
/// <c>InvalidEnvelope</c> branch (raised when
|
||||
/// <c>WorkerEnvelope.CalculateSize()</c> returns 0) is unreachable
|
||||
/// through public API. <c>WorkerEnvelopeValidator.Validate</c> (run
|
||||
/// before the size check in <c>WorkerFrameWriter.WriteAsync</c>)
|
||||
/// Documents that the writer-side <c>InvalidEnvelope</c> branch
|
||||
/// (raised when <c>WorkerEnvelope.CalculateSize()</c> returns 0) is
|
||||
/// unreachable through public API. <c>WorkerEnvelopeValidator.Validate</c>
|
||||
/// (run before the size check in <c>WorkerFrameWriter.WriteAsync</c>)
|
||||
/// rejects any envelope whose <c>BodyCase</c> is <c>None</c> with
|
||||
/// <c>InvalidEnvelope</c>; a body-less envelope is therefore
|
||||
/// intercepted before the empty-payload branch can fire. Any
|
||||
@@ -209,6 +215,7 @@ public sealed class WorkerFrameProtocolTests
|
||||
/// would weaken the writer against future serialisation
|
||||
/// regressions; this test makes its rationale visible.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WriteAsync_WithEmptyEnvelope_ThrowsInvalidEnvelopeFromValidator()
|
||||
{
|
||||
@@ -233,6 +240,7 @@ public sealed class WorkerFrameProtocolTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that concurrent writes produce complete serialized frames.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WriteAsync_WithConcurrentCalls_SerializesCompleteFrames()
|
||||
{
|
||||
@@ -256,12 +264,13 @@ public sealed class WorkerFrameProtocolTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-009 regression: the reader rents its payload buffer from a
|
||||
/// shared pool, so a rented buffer can be larger than the current frame
|
||||
/// and may carry bytes from a previous, larger frame. Reading frames of
|
||||
/// differing sizes back-to-back through one reader must parse each frame
|
||||
/// using only its own payload length, never trailing pooled bytes.
|
||||
/// The reader rents its payload buffer from a shared pool, so a rented
|
||||
/// buffer can be larger than the current frame and may carry bytes from
|
||||
/// a previous, larger frame. Reading frames of differing sizes
|
||||
/// back-to-back through one reader must parse each frame using only its
|
||||
/// own payload length, never trailing pooled bytes.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WithVaryingFrameSizes_ParsesEachFrameExactly()
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace ZB.MOM.WW.MxGateway.Worker.Tests.Ipc;
|
||||
public sealed class WorkerPipeClientTests
|
||||
{
|
||||
/// <summary>Verifies that worker client connects and completes handshake.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_ConnectsToPipeAndCompletesHandshake()
|
||||
{
|
||||
@@ -83,6 +84,7 @@ public sealed class WorkerPipeClientTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that worker client retries until pipe server becomes available.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_RetriesUntilPipeServerAppears()
|
||||
{
|
||||
@@ -127,6 +129,7 @@ public sealed class WorkerPipeClientTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that worker client throws timeout if pipe never appears.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenPipeNeverAppears_ThrowsTimeoutException()
|
||||
{
|
||||
@@ -148,7 +151,7 @@ public sealed class WorkerPipeClientTests
|
||||
/// <summary>
|
||||
/// Reads frames until one matching the expected body case is found,
|
||||
/// skipping interleaved heartbeats (the first heartbeat is emitted
|
||||
/// immediately on entering the heartbeat loop — see Worker-002).
|
||||
/// immediately on entering the heartbeat loop).
|
||||
/// </summary>
|
||||
private static async Task<WorkerEnvelope> ReadUntilAsync(
|
||||
WorkerFrameReader reader,
|
||||
|
||||
@@ -20,6 +20,7 @@ public sealed class WorkerPipeSessionTests
|
||||
private const string Nonce = "nonce-secret";
|
||||
|
||||
/// <summary>Verifies that valid gateway hello triggers worker hello and ready responses.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CompleteStartupHandshakeAsync_WithValidGatewayHello_SendsHelloThenReady()
|
||||
{
|
||||
@@ -51,6 +52,7 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that wrong nonce causes protocol violation fault before initialization.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CompleteStartupHandshakeAsync_WithWrongNonce_FaultsBeforeInitialization()
|
||||
{
|
||||
@@ -79,6 +81,7 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that unsupported protocol version causes mismatch fault before initialization.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CompleteStartupHandshakeAsync_WithWrongProtocol_FaultsBeforeInitialization()
|
||||
{
|
||||
@@ -106,6 +109,7 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that malformed frame causes protocol violation fault.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CompleteStartupHandshakeAsync_WithMalformedFrame_WritesWorkerFault()
|
||||
{
|
||||
@@ -132,6 +136,7 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that MXAccess COM creation failure produces fault instead of ready.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CompleteStartupHandshakeAsync_WhenMxAccessCreationFails_WritesFaultInsteadOfReady()
|
||||
{
|
||||
@@ -158,6 +163,7 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that heartbeat payload reflects current runtime snapshot.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_SendsHeartbeatPayloadFromRuntimeSnapshot()
|
||||
{
|
||||
@@ -211,6 +217,7 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that heartbeat reports current command correlation during execution.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenCommandIsExecuting_HeartbeatReportsCurrentCorrelation()
|
||||
{
|
||||
@@ -258,6 +265,7 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that worker events are written to the pipe.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenRuntimeHasEvents_WritesWorkerEventEnvelope()
|
||||
{
|
||||
@@ -293,6 +301,7 @@ public sealed class WorkerPipeSessionTests
|
||||
/// (not dispatched to the STA) with an OK reply that echoes the ping
|
||||
/// message into the reply's diagnostic field.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_PingControlCommand_RepliesOkAndEchoesMessage()
|
||||
{
|
||||
@@ -324,6 +333,7 @@ public sealed class WorkerPipeSessionTests
|
||||
/// Verifies that GetSessionState reports the worker's lifecycle as the
|
||||
/// proto SessionState — READY while the message loop is serving.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_GetSessionStateControlCommand_RepliesReady()
|
||||
{
|
||||
@@ -359,6 +369,7 @@ public sealed class WorkerPipeSessionTests
|
||||
/// Verifies that GetWorkerInfo populates the worker process id, version,
|
||||
/// and MXAccess ProgID/CLSID from the worker's own metadata.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_GetWorkerInfoControlCommand_PopulatesWorkerInfoFields()
|
||||
{
|
||||
@@ -398,6 +409,7 @@ public sealed class WorkerPipeSessionTests
|
||||
/// Verifies that DrainEvents drains the runtime session's queued events
|
||||
/// into the reply rather than streaming them as WorkerEvent envelopes.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_DrainEventsControlCommand_ReturnsQueuedEvents()
|
||||
{
|
||||
@@ -442,6 +454,7 @@ public sealed class WorkerPipeSessionTests
|
||||
/// shutdown runs and disposes the runtime session, and that the message
|
||||
/// loop then stops.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_ShutdownWorkerControlCommand_RepliesOkThenShutsDown()
|
||||
{
|
||||
@@ -491,12 +504,12 @@ public sealed class WorkerPipeSessionTests
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that stale STA activity with no command in flight triggers
|
||||
/// the watchdog StaHung fault. Worker-017 changed the watchdog to skip
|
||||
/// the fault while a command is in flight (the worker is busy
|
||||
/// executing it, not hung), so this test deliberately leaves the
|
||||
/// current-command correlation id empty to assert the genuine-hung
|
||||
/// path still fires.
|
||||
/// the watchdog StaHung fault. The watchdog skips the fault while a
|
||||
/// command is in flight (the worker is busy executing it, not hung),
|
||||
/// so this test deliberately leaves the current-command correlation
|
||||
/// id empty to assert the genuine-hung path still fires.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenStaActivityIsStale_WritesWatchdogFault()
|
||||
{
|
||||
@@ -532,17 +545,18 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-017 regression: while a command is in flight (snapshot's
|
||||
/// current command correlation id is non-empty), stale STA activity
|
||||
/// must NOT trigger the watchdog StaHung fault. The STA is busy
|
||||
/// executing the command, not hung; <c>StaRuntime.ProcessQueuedCommands</c>
|
||||
/// only calls <c>MarkActivity()</c> before and after each work item,
|
||||
/// so a synchronously long-running command (e.g. <c>ReadBulk</c>
|
||||
/// waiting <c>timeout_ms</c> for OnDataChange) legitimately freezes
|
||||
/// While a command is in flight (snapshot's current command
|
||||
/// correlation id is non-empty), stale STA activity must NOT trigger
|
||||
/// the watchdog StaHung fault. The STA is busy executing the command,
|
||||
/// not hung; <c>StaRuntime.ProcessQueuedCommands</c> only calls
|
||||
/// <c>MarkActivity()</c> before and after each work item, so a
|
||||
/// synchronously long-running command (e.g. <c>ReadBulk</c> waiting
|
||||
/// <c>timeout_ms</c> for OnDataChange) legitimately freezes
|
||||
/// <c>LastActivityUtc</c>. The heartbeat already advertises the
|
||||
/// in-flight correlation id so the gateway can apply its own per-command
|
||||
/// timeout.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenStaActivityIsStaleWithCommandInFlight_DoesNotWriteWatchdogFault()
|
||||
{
|
||||
@@ -596,13 +610,13 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-004 regression: once the watchdog reports an StaHung fault,
|
||||
/// subsequent heartbeats must report <see cref="WorkerState.Faulted"/>
|
||||
/// rather than a non-faulted state that contradicts the fault. The
|
||||
/// snapshot uses an empty current-command correlation id so the
|
||||
/// heartbeat State is derived from the session state, not forced to
|
||||
/// ExecutingCommand.
|
||||
/// Once the watchdog reports an StaHung fault, subsequent heartbeats
|
||||
/// must report <see cref="WorkerState.Faulted"/> rather than a
|
||||
/// non-faulted state that contradicts the fault. The snapshot uses an
|
||||
/// empty current-command correlation id so the heartbeat State is
|
||||
/// derived from the session state, not forced to ExecutingCommand.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_AfterWatchdogFault_HeartbeatReportsFaultedState()
|
||||
{
|
||||
@@ -643,15 +657,16 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-023 regression: the in-flight-command suppression on the
|
||||
/// <c>StaHung</c> watchdog (Worker-017) is bounded by
|
||||
/// <c>WorkerPipeSessionOptions.HeartbeatStuckCeiling</c>. A truly
|
||||
/// stuck synchronous STA command (e.g. a dead MXAccess provider) would
|
||||
/// otherwise keep <c>CurrentCommandCorrelationId</c> non-empty forever
|
||||
/// and permanently defeat the watchdog. Once <c>LastStaActivityUtc</c>
|
||||
/// has been stale for longer than <c>HeartbeatStuckCeiling</c> the
|
||||
/// watchdog DOES fire <c>StaHung</c> even with a command in flight.
|
||||
/// The in-flight-command suppression on the <c>StaHung</c> watchdog is
|
||||
/// bounded by <c>WorkerPipeSessionOptions.HeartbeatStuckCeiling</c>. A
|
||||
/// truly stuck synchronous STA command (e.g. a dead MXAccess provider)
|
||||
/// would otherwise keep <c>CurrentCommandCorrelationId</c> non-empty
|
||||
/// forever and permanently defeat the watchdog. Once
|
||||
/// <c>LastStaActivityUtc</c> has been stale for longer than
|
||||
/// <c>HeartbeatStuckCeiling</c> the watchdog DOES fire <c>StaHung</c>
|
||||
/// even with a command in flight.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenStaActivityIsStaleBeyondCeilingWithCommandInFlight_WritesWatchdogFault()
|
||||
{
|
||||
@@ -690,10 +705,11 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-025 regression: <c>RunAsync</c> must throw a diagnostic
|
||||
/// exception if the runtime-session factory returns null, rather than
|
||||
/// deferring the failure to an NRE on the next dereference.
|
||||
/// <c>RunAsync</c> must throw a diagnostic exception if the
|
||||
/// runtime-session factory returns null, rather than deferring the
|
||||
/// failure to an NRE on the next dereference.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenRuntimeSessionFactoryReturnsNull_ThrowsDiagnosticException()
|
||||
{
|
||||
@@ -715,11 +731,11 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-006 regression: when graceful shutdown times out, RunAsync
|
||||
/// must still dispose the runtime session in its finally block.
|
||||
/// Skipping disposal on the timed-out path leaked the STA thread and
|
||||
/// the MXAccess COM object.
|
||||
/// When graceful shutdown times out, RunAsync must still dispose the
|
||||
/// runtime session in its finally block. Skipping disposal on the
|
||||
/// timed-out path leaked the STA thread and the MXAccess COM object.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenShutdownTimesOut_StillDisposesRuntimeSession()
|
||||
{
|
||||
@@ -783,6 +799,7 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that shutdown drops late replies and sends shutdown ack.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenShutdownArrivesDuringCommand_DropsLateReplyAndWritesShutdownAck()
|
||||
{
|
||||
@@ -823,6 +840,7 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that command exceptions after shutdown are dropped before ack.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenCommandThrowsAfterShutdown_DropsLateFaultAndWritesShutdownAck()
|
||||
{
|
||||
@@ -852,9 +870,9 @@ public sealed class WorkerPipeSessionTests
|
||||
await pipePair.GatewayWriter
|
||||
.WriteAsync(CreateShutdownEnvelope(), cancellation.Token);
|
||||
|
||||
// The first heartbeat is emitted immediately on entering the loop
|
||||
// (Worker-002), so skip any interleaved heartbeats; the late fault
|
||||
// must still be dropped — no WorkerFault may precede the ack.
|
||||
// The first heartbeat is emitted immediately on entering the loop,
|
||||
// so skip any interleaved heartbeats; the late fault must still be
|
||||
// dropped — no WorkerFault may precede the ack.
|
||||
WorkerEnvelope envelopeAfterShutdown;
|
||||
do
|
||||
{
|
||||
@@ -873,7 +891,7 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-017 regression: the <c>WorkerCancel</c> branch of
|
||||
/// The <c>WorkerCancel</c> branch of
|
||||
/// <see cref="WorkerPipeSession.DispatchGatewayEnvelopeAsync"/> must
|
||||
/// forward the envelope's correlation id to the runtime session via
|
||||
/// <see cref="IWorkerRuntimeSession.CancelCommand"/> and keep the
|
||||
@@ -881,6 +899,7 @@ public sealed class WorkerPipeSessionTests
|
||||
/// returns <c>true</c> (keep reading), so a subsequent
|
||||
/// <c>WorkerShutdown</c> still produces the normal shutdown ack.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenGatewaySendsWorkerCancel_ForwardsCorrelationIdToRuntimeSession()
|
||||
{
|
||||
@@ -910,7 +929,7 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-017 regression: the <c>default:</c> arm of
|
||||
/// The <c>default:</c> arm of
|
||||
/// <see cref="WorkerPipeSession.DispatchGatewayEnvelopeAsync"/> must
|
||||
/// throw <see cref="WorkerFrameProtocolException"/> with
|
||||
/// <see cref="WorkerFrameProtocolErrorCode.UnexpectedEnvelopeBody"/>
|
||||
@@ -923,6 +942,7 @@ public sealed class WorkerPipeSessionTests
|
||||
/// pre-handshake protocol violations); the contract this test pins
|
||||
/// is the exception type/error-code and message-loop exit.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenGatewaySendsUnexpectedEnvelopeBodyAfterHandshake_ThrowsAndExitsMessageLoop()
|
||||
{
|
||||
@@ -956,11 +976,12 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-002 regression: the first heartbeat must be emitted
|
||||
/// immediately on entering the heartbeat loop, not after a full
|
||||
/// HeartbeatInterval. A long interval is configured so a delay-first
|
||||
/// loop would fail to deliver a heartbeat inside the assertion window.
|
||||
/// The first heartbeat must be emitted immediately on entering the
|
||||
/// heartbeat loop, not after a full HeartbeatInterval. A long interval
|
||||
/// is configured so a delay-first loop would fail to deliver a
|
||||
/// heartbeat inside the assertion window.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_SendsFirstHeartbeatImmediatelyOnEnteringLoop()
|
||||
{
|
||||
@@ -984,7 +1005,7 @@ public sealed class WorkerPipeSessionTests
|
||||
// if the first heartbeat is not received within 5s, ReadUntilAsync throws
|
||||
// OperationCanceledException and the test fails. A redundant wall-clock
|
||||
// elapsed < 5s assertion would add the same class of flakiness
|
||||
// Workers.Tests-003/004/013/020 corrected elsewhere, so it is omitted here.
|
||||
// corrected elsewhere, so it is omitted here.
|
||||
using CancellationTokenSource heartbeatWait = CancellationTokenSource
|
||||
.CreateLinkedTokenSource(cancellation.Token);
|
||||
heartbeatWait.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
@@ -999,11 +1020,12 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-003 regression: when a command completes after the worker
|
||||
/// has transitioned out of a command-serving state, the dropped
|
||||
/// reply must be logged with a diagnostic rather than discarded
|
||||
/// silently, so a stuck gateway correlation wait can be traced.
|
||||
/// When a command completes after the worker has transitioned out of
|
||||
/// a command-serving state, the dropped reply must be logged with a
|
||||
/// diagnostic rather than discarded silently, so a stuck gateway
|
||||
/// correlation wait can be traced.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenReplyIsDroppedAfterShutdown_LogsDiagnostic()
|
||||
{
|
||||
@@ -1110,7 +1132,7 @@ public sealed class WorkerPipeSessionTests
|
||||
// (default = position in the typical Hello/Command/Cancel/Shutdown
|
||||
// ordering) so a multi-frame test that interleaves the helpers can
|
||||
// assign monotonically increasing values and produce a wire trace
|
||||
// that reads in ascending order — see Worker.Tests-030.
|
||||
// that reads in ascending order.
|
||||
private static WorkerEnvelope CreateGatewayHelloEnvelope(
|
||||
string nonce = Nonce,
|
||||
uint supportedProtocolVersion = GatewayContractInfo.WorkerProtocolVersion,
|
||||
@@ -1346,17 +1368,13 @@ public sealed class WorkerPipeSessionTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Records an informational log event.</summary>
|
||||
/// <param name="eventName">The event name.</param>
|
||||
/// <param name="fields">The event fields.</param>
|
||||
/// <inheritdoc />
|
||||
public void Information(string eventName, IReadOnlyDictionary<string, object?> fields)
|
||||
{
|
||||
Record(eventName, fields);
|
||||
}
|
||||
|
||||
/// <summary>Records an error log event.</summary>
|
||||
/// <param name="eventName">The event name.</param>
|
||||
/// <param name="fields">The event fields.</param>
|
||||
/// <inheritdoc />
|
||||
public void Error(string eventName, IReadOnlyDictionary<string, object?> fields)
|
||||
{
|
||||
Record(eventName, fields);
|
||||
@@ -1447,7 +1465,7 @@ public sealed class WorkerPipeSessionTests
|
||||
return new PipePair(gatewayStream, workerStream);
|
||||
}
|
||||
|
||||
/// <summary>Disposes pipe resources.</summary>
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
WorkerStream.Dispose();
|
||||
|
||||
@@ -371,28 +371,20 @@ public sealed class AlarmCommandExecutorTests
|
||||
/// <summary>Gets the last alarm filter prefix.</summary>
|
||||
public string? LastFilterPrefix { get; private set; }
|
||||
|
||||
/// <summary>Records a subscription.</summary>
|
||||
/// <param name="command">The subscribe-alarms command.</param>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <inheritdoc />
|
||||
public void Subscribe(SubscribeAlarmsCommand command, string sessionId)
|
||||
{
|
||||
LastSubscription = command.SubscriptionExpression;
|
||||
LastSessionId = sessionId;
|
||||
}
|
||||
|
||||
/// <summary>Records an unsubscribe request.</summary>
|
||||
/// <inheritdoc />
|
||||
public void Unsubscribe()
|
||||
{
|
||||
UnsubscribeCalled = true;
|
||||
}
|
||||
|
||||
/// <summary>Records an acknowledge request.</summary>
|
||||
/// <param name="alarmGuid">The alarm identifier.</param>
|
||||
/// <param name="comment">The acknowledge comment.</param>
|
||||
/// <param name="operatorUser">The operator user name.</param>
|
||||
/// <param name="operatorNode">The operator node name.</param>
|
||||
/// <param name="operatorDomain">The operator domain.</param>
|
||||
/// <param name="operatorFullName">The operator full name.</param>
|
||||
/// <inheritdoc />
|
||||
public int Acknowledge(
|
||||
Guid alarmGuid, string comment, string operatorUser,
|
||||
string operatorNode, string operatorDomain, string operatorFullName)
|
||||
@@ -406,15 +398,7 @@ public sealed class AlarmCommandExecutorTests
|
||||
return AcknowledgeReturn;
|
||||
}
|
||||
|
||||
/// <summary>Records an acknowledge by name request.</summary>
|
||||
/// <param name="alarmName">The alarm name.</param>
|
||||
/// <param name="providerName">The provider name.</param>
|
||||
/// <param name="groupName">The group name.</param>
|
||||
/// <param name="comment">The acknowledge comment.</param>
|
||||
/// <param name="operatorUser">The operator user name.</param>
|
||||
/// <param name="operatorNode">The operator node name.</param>
|
||||
/// <param name="operatorDomain">The operator domain.</param>
|
||||
/// <param name="operatorFullName">The operator full name.</param>
|
||||
/// <inheritdoc />
|
||||
public int AcknowledgeByName(
|
||||
string alarmName, string providerName, string groupName,
|
||||
string comment, string operatorUser, string operatorNode,
|
||||
@@ -428,8 +412,7 @@ public sealed class AlarmCommandExecutorTests
|
||||
/// <summary>Gets the last acknowledge by name tuple.</summary>
|
||||
public (string Name, string Provider, string Group)? LastAckByNameTuple { get; private set; }
|
||||
|
||||
/// <summary>Queries the active alarms with the given filter prefix.</summary>
|
||||
/// <param name="alarmFilterPrefix">The alarm filter prefix for the query.</param>
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<ActiveAlarmSnapshot> QueryActive(string? alarmFilterPrefix)
|
||||
{
|
||||
LastFilterPrefix = alarmFilterPrefix;
|
||||
@@ -439,7 +422,7 @@ public sealed class AlarmCommandExecutorTests
|
||||
/// <summary>Gets the number of poll calls.</summary>
|
||||
public int PollCount { get; private set; }
|
||||
|
||||
/// <summary>Increments the poll count.</summary>
|
||||
/// <inheritdoc />
|
||||
public void PollOnce()
|
||||
{
|
||||
PollCount++;
|
||||
|
||||
@@ -42,7 +42,7 @@ public sealed class AlarmCommandHandlerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-024: pins both the disposal contract and the
|
||||
/// Pins both the disposal contract and the
|
||||
/// origin of the propagated exception. The fake throws
|
||||
/// <c>InvalidOperationException("simulated wnwrap subscribe failure")</c>
|
||||
/// from <c>Subscribe</c>; the handler must propagate that exact
|
||||
@@ -199,7 +199,7 @@ public sealed class AlarmCommandHandlerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-024 regression: every method that touches the underlying
|
||||
/// Every method that touches the underlying
|
||||
/// <see cref="IMxAccessAlarmConsumer"/> must invoke the configured
|
||||
/// STA-affinity guard. A guard that throws (simulating an off-STA
|
||||
/// call) must propagate from every command-path entry point.
|
||||
@@ -238,7 +238,7 @@ public sealed class AlarmCommandHandlerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-024 regression: a guard that throws must propagate from
|
||||
/// A guard that throws must propagate from
|
||||
/// every command-path entry point — proving the guard is not
|
||||
/// swallowed by an inner try/catch.
|
||||
/// </summary>
|
||||
@@ -274,7 +274,7 @@ public sealed class AlarmCommandHandlerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-9: ForcedMode=Subtag builds a subtag consumer (via the
|
||||
/// ForcedMode=Subtag builds a subtag consumer (via the
|
||||
/// injected standby factory) and advises it — the primary
|
||||
/// (alarmmgr) consumer is NOT created.
|
||||
/// </summary>
|
||||
@@ -312,7 +312,7 @@ public sealed class AlarmCommandHandlerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-9: ForcedMode=Unspecified + a non-empty watch list builds a
|
||||
/// ForcedMode=Unspecified + a non-empty watch list builds a
|
||||
/// failover composite (primary + subtag standby). Forcing the primary
|
||||
/// to fail on subscribe with a threshold of 1 drives the composite to
|
||||
/// switch to the subtag provider, which must enqueue an
|
||||
@@ -359,7 +359,7 @@ public sealed class AlarmCommandHandlerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-9: a non-failover subscribe (alarmmgr-only) never enqueues a
|
||||
/// A non-failover subscribe (alarmmgr-only) never enqueues a
|
||||
/// provider-mode-changed event, and a subsequent Unsubscribe detaches
|
||||
/// the handler so no event leaks.
|
||||
/// </summary>
|
||||
@@ -378,7 +378,7 @@ public sealed class AlarmCommandHandlerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-9: the mapper builds a well-formed OnAlarmProviderModeChanged
|
||||
/// The mapper builds a well-formed OnAlarmProviderModeChanged
|
||||
/// MxEvent — correct family and populated body fields.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
@@ -436,8 +436,7 @@ public sealed class AlarmCommandHandlerTests
|
||||
/// <summary>Gets a value indicating whether the consumer has been disposed.</summary>
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
/// <summary>Subscribes to alarms with the given subscription string.</summary>
|
||||
/// <param name="subscription">The subscription reference.</param>
|
||||
/// <inheritdoc />
|
||||
public void Subscribe(string subscription)
|
||||
{
|
||||
LastSubscription = subscription;
|
||||
@@ -447,13 +446,7 @@ public sealed class AlarmCommandHandlerTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Acknowledges an alarm by GUID.</summary>
|
||||
/// <param name="alarmGuid">The alarm GUID.</param>
|
||||
/// <param name="ackComment">The acknowledgment comment.</param>
|
||||
/// <param name="ackOperatorName">The operator name.</param>
|
||||
/// <param name="ackOperatorNode">The operator node.</param>
|
||||
/// <param name="ackOperatorDomain">The operator domain.</param>
|
||||
/// <param name="ackOperatorFullName">The operator full name.</param>
|
||||
/// <inheritdoc />
|
||||
public int AcknowledgeByGuid(
|
||||
Guid alarmGuid, string ackComment, string ackOperatorName,
|
||||
string ackOperatorNode, string ackOperatorDomain, string ackOperatorFullName)
|
||||
@@ -463,15 +456,7 @@ public sealed class AlarmCommandHandlerTests
|
||||
return AcknowledgeReturn;
|
||||
}
|
||||
|
||||
/// <summary>Acknowledges an alarm by name.</summary>
|
||||
/// <param name="alarmName">The alarm name.</param>
|
||||
/// <param name="providerName">The provider name.</param>
|
||||
/// <param name="groupName">The alarm group name.</param>
|
||||
/// <param name="ackComment">The acknowledgment comment.</param>
|
||||
/// <param name="ackOperatorName">The operator name.</param>
|
||||
/// <param name="ackOperatorNode">The operator node.</param>
|
||||
/// <param name="ackOperatorDomain">The operator domain.</param>
|
||||
/// <param name="ackOperatorFullName">The operator full name.</param>
|
||||
/// <inheritdoc />
|
||||
public int AcknowledgeByName(
|
||||
string alarmName, string providerName, string groupName,
|
||||
string ackComment, string ackOperatorName, string ackOperatorNode,
|
||||
@@ -485,13 +470,13 @@ public sealed class AlarmCommandHandlerTests
|
||||
/// <summary>Gets the last acknowledge-by-name parameters.</summary>
|
||||
public (string Name, string Provider, string Group)? LastAckByNameTuple { get; private set; }
|
||||
|
||||
/// <summary>Returns a snapshot of active alarms.</summary>
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<MxAlarmSnapshotRecord> SnapshotActiveAlarms() => SnapshotResult;
|
||||
|
||||
/// <summary>Gets the number of times polled.</summary>
|
||||
public int PollCount { get; private set; }
|
||||
|
||||
/// <summary>Polls once for alarm updates.</summary>
|
||||
/// <inheritdoc />
|
||||
public void PollOnce()
|
||||
{
|
||||
PollCount++;
|
||||
|
||||
@@ -394,20 +394,13 @@ public sealed class AlarmDispatcherTests
|
||||
AlarmTransitionEmitted?.Invoke(this, transition);
|
||||
}
|
||||
|
||||
/// <summary>Records the subscription reference.</summary>
|
||||
/// <param name="subscription">The subscription reference.</param>
|
||||
/// <inheritdoc />
|
||||
public void Subscribe(string subscription)
|
||||
{
|
||||
LastSubscription = subscription;
|
||||
}
|
||||
|
||||
/// <summary>Records an acknowledge-by-GUID call with operator identity.</summary>
|
||||
/// <param name="alarmGuid">The alarm GUID.</param>
|
||||
/// <param name="ackComment">The acknowledgment comment.</param>
|
||||
/// <param name="ackOperatorName">The operator name.</param>
|
||||
/// <param name="ackOperatorNode">The operator node.</param>
|
||||
/// <param name="ackOperatorDomain">The operator domain.</param>
|
||||
/// <param name="ackOperatorFullName">The operator full name.</param>
|
||||
/// <inheritdoc />
|
||||
public int AcknowledgeByGuid(
|
||||
Guid alarmGuid,
|
||||
string ackComment,
|
||||
@@ -425,15 +418,7 @@ public sealed class AlarmDispatcherTests
|
||||
return AcknowledgeReturn;
|
||||
}
|
||||
|
||||
/// <summary>Records an acknowledge-by-name call with alarm name, provider, and group.</summary>
|
||||
/// <param name="alarmName">The alarm name.</param>
|
||||
/// <param name="providerName">The provider name.</param>
|
||||
/// <param name="groupName">The alarm group name.</param>
|
||||
/// <param name="ackComment">The acknowledgment comment.</param>
|
||||
/// <param name="ackOperatorName">The operator name.</param>
|
||||
/// <param name="ackOperatorNode">The operator node.</param>
|
||||
/// <param name="ackOperatorDomain">The operator domain.</param>
|
||||
/// <param name="ackOperatorFullName">The operator full name.</param>
|
||||
/// <inheritdoc />
|
||||
public int AcknowledgeByName(
|
||||
string alarmName, string providerName, string groupName,
|
||||
string ackComment, string ackOperatorName, string ackOperatorNode,
|
||||
@@ -447,7 +432,7 @@ public sealed class AlarmDispatcherTests
|
||||
/// <summary>Gets the last acknowledge-by-name tuple (alarm name, provider, group).</summary>
|
||||
public (string Name, string Provider, string Group)? LastAckByNameTuple { get; private set; }
|
||||
|
||||
/// <summary>Returns the current snapshot result collection.</summary>
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<MxAlarmSnapshotRecord> SnapshotActiveAlarms()
|
||||
{
|
||||
return SnapshotResult;
|
||||
@@ -456,7 +441,7 @@ public sealed class AlarmDispatcherTests
|
||||
/// <summary>Gets the count of poll operations.</summary>
|
||||
public int PollCount { get; private set; }
|
||||
|
||||
/// <summary>Increments the poll count.</summary>
|
||||
/// <inheritdoc />
|
||||
public void PollOnce()
|
||||
{
|
||||
PollCount++;
|
||||
|
||||
@@ -24,6 +24,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
/// </summary>
|
||||
private sealed class FlakyPrimary : IMxAccessAlarmConsumer
|
||||
{
|
||||
/// <summary>Raised when the fake forwards a simulated alarm transition via <see cref="Raise"/>.</summary>
|
||||
public event EventHandler<MxAlarmTransitionEvent>? AlarmTransitionEmitted;
|
||||
|
||||
public bool ThrowOnPoll = true;
|
||||
@@ -32,7 +33,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
/// When set, <see cref="PollOnce"/> throws
|
||||
/// <see cref="OutOfMemoryException"/> instead of a
|
||||
/// <see cref="System.Runtime.InteropServices.COMException"/>, to
|
||||
/// exercise the OOM-safe exception filter (Worker.Tests-032).
|
||||
/// exercise the OOM-safe exception filter.
|
||||
/// </summary>
|
||||
public bool ThrowOutOfMemoryOnPoll;
|
||||
|
||||
@@ -45,6 +46,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
/// </summary>
|
||||
public int SubscribeCount;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Subscribe(string s)
|
||||
{
|
||||
SubscribeCount++;
|
||||
@@ -54,6 +56,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PollOnce()
|
||||
{
|
||||
Polls++;
|
||||
@@ -68,14 +71,20 @@ public sealed class FailoverAlarmConsumerTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AcknowledgeByGuid(Guid g, string c, string a, string b, string d, string e) => 11;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AcknowledgeByName(string n, string p, string gr, string c, string a, string b, string d, string e) => 11;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<MxAlarmSnapshotRecord> SnapshotActiveAlarms() => Array.Empty<MxAlarmSnapshotRecord>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
|
||||
/// <summary>Raises <see cref="AlarmTransitionEmitted"/> with the given event, simulating a COM-forwarded transition.</summary>
|
||||
/// <param name="e">The transition event to forward.</param>
|
||||
public void Raise(MxAlarmTransitionEvent e) => AlarmTransitionEmitted?.Invoke(this, e);
|
||||
}
|
||||
|
||||
@@ -85,27 +94,33 @@ public sealed class FailoverAlarmConsumerTests
|
||||
/// </summary>
|
||||
private sealed class StubStandby : IMxAccessAlarmConsumer
|
||||
{
|
||||
/// <summary>Raised when the fake forwards a simulated alarm transition via <see cref="Raise"/>.</summary>
|
||||
public event EventHandler<MxAlarmTransitionEvent>? AlarmTransitionEmitted;
|
||||
|
||||
public bool Subscribed;
|
||||
|
||||
/// <summary>
|
||||
/// When set, <see cref="SnapshotActiveAlarms"/> throws — modeling a
|
||||
/// priming-snapshot failure during failover (Worker-026).
|
||||
/// priming-snapshot failure during failover.
|
||||
/// </summary>
|
||||
public bool ThrowOnSnapshot;
|
||||
|
||||
/// <summary>Number of <see cref="SnapshotActiveAlarms"/> calls.</summary>
|
||||
public int SnapshotCalls;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Subscribe(string s) => Subscribed = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void PollOnce() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AcknowledgeByGuid(Guid g, string c, string a, string b, string d, string e) => 22;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AcknowledgeByName(string n, string p, string gr, string c, string a, string b, string d, string e) => 22;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<MxAlarmSnapshotRecord> SnapshotActiveAlarms()
|
||||
{
|
||||
SnapshotCalls++;
|
||||
@@ -117,8 +132,11 @@ public sealed class FailoverAlarmConsumerTests
|
||||
return Array.Empty<MxAlarmSnapshotRecord>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
|
||||
/// <summary>Raises <see cref="AlarmTransitionEmitted"/> with the given event, simulating a COM-forwarded transition.</summary>
|
||||
/// <param name="e">The transition event to forward.</param>
|
||||
public void Raise(MxAlarmTransitionEvent e) => AlarmTransitionEmitted?.Invoke(this, e);
|
||||
}
|
||||
|
||||
@@ -128,6 +146,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
PreviousState = MxAlarmStateKind.Unspecified,
|
||||
};
|
||||
|
||||
/// <summary>Proves that the consumer switches to the subtag standby after the primary fails the configured threshold of consecutive times.</summary>
|
||||
[Fact]
|
||||
public void Primary_FailsThresholdTimes_SwitchesToSubtag()
|
||||
{
|
||||
@@ -154,6 +173,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
Assert.Equal(unchecked((int)0x80004005), changes[0].HResult);
|
||||
}
|
||||
|
||||
/// <summary>Proves that once failed over, transitions raised by the standby are forwarded through the consumer's <c>AlarmTransitionEmitted</c> event.</summary>
|
||||
[Fact]
|
||||
public void AfterSwitch_StandbyTransitionsAreForwarded()
|
||||
{
|
||||
@@ -174,6 +194,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
Assert.Same(transition, forwarded);
|
||||
}
|
||||
|
||||
/// <summary>Proves that once the primary heals, the consumer fails back to it only after the configured number of consecutive clean probes.</summary>
|
||||
[Fact]
|
||||
public void WhileDegraded_PrimaryHeals_FailsBackAfterStableProbes()
|
||||
{
|
||||
@@ -212,6 +233,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
Assert.Equal(subscribeCountAfterFailover, primary.SubscribeCount);
|
||||
}
|
||||
|
||||
/// <summary>Proves that before any failover, transitions raised by the primary are forwarded but transitions from the inactive standby are suppressed.</summary>
|
||||
[Fact]
|
||||
public void BeforeFailover_PrimaryTransitionsAreForwarded()
|
||||
{
|
||||
@@ -252,7 +274,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
FailoverSettings settings = new FailoverSettings(threshold: 1, probeIntervalSeconds: 0, stableProbes: 3);
|
||||
using FailoverAlarmConsumer sut = new FailoverAlarmConsumer(primary, standby, settings);
|
||||
|
||||
sut.Subscribe(@"\\HOST\Galaxy!Area"); // Subscribe attempt #1 (throws) → Subtag
|
||||
sut.Subscribe(@"\\HOST\Galaxy!Area"); // Subscribe attempt (throws) → Subtag
|
||||
|
||||
// Capture how many Subscribe calls the initial setup caused (exactly 1:
|
||||
// the attempt that threw and triggered failover).
|
||||
@@ -272,6 +294,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
Assert.Equal(subscribeCountAfterSetup, primary.SubscribeCount);
|
||||
}
|
||||
|
||||
/// <summary>Proves that acknowledgment calls delegate to whichever child (primary or standby) is currently active.</summary>
|
||||
[Fact]
|
||||
public void Acknowledge_DelegatesToActiveChild()
|
||||
{
|
||||
@@ -325,7 +348,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-026 regression: when the standby's priming
|
||||
/// When the standby's priming
|
||||
/// <c>SnapshotActiveAlarms</c> throws during failover, the switch must
|
||||
/// still (a) fire <c>ProviderModeChanged</c> so the gateway learns the
|
||||
/// feed went degraded, (b) leave <see cref="FailoverAlarmConsumer.Mode"/>
|
||||
@@ -361,7 +384,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-026 regression: when a <c>ProviderModeChanged</c> subscriber's
|
||||
/// When a <c>ProviderModeChanged</c> subscriber's
|
||||
/// handler throws (modeling the AlarmCommandHandler's event-queue enqueue
|
||||
/// overflowing at capacity), the switch must still take effect and the
|
||||
/// exception must not escape the switch path into the poll loop.
|
||||
@@ -389,7 +412,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-031 regression: with a non-zero
|
||||
/// With a non-zero
|
||||
/// <see cref="FailoverSettings.ProbeIntervalSeconds"/>, two back-to-back
|
||||
/// <c>ProbeOnce</c> calls must throttle — the second falls inside the
|
||||
/// interval and must NOT re-poll the primary. Two consecutive calls
|
||||
@@ -422,7 +445,7 @@ public sealed class FailoverAlarmConsumerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-032 regression: <c>RunPrimary</c>'s
|
||||
/// <c>RunPrimary</c>'s
|
||||
/// <c>when (ex is not OutOfMemoryException)</c> filter must let an
|
||||
/// <see cref="OutOfMemoryException"/> propagate rather than swallowing it
|
||||
/// and counting it toward the failover threshold. No mode change must
|
||||
@@ -447,11 +470,17 @@ public sealed class FailoverAlarmConsumerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-032 regression: <see cref="FailoverSettings"/> clamps
|
||||
/// sub-1 <c>threshold</c> and <c>stableProbes</c> (and sub-0
|
||||
/// Verifies that <see cref="FailoverSettings"/> clamps out-of-range
|
||||
/// <c>threshold</c> and <c>stableProbes</c> values (and negative
|
||||
/// <c>probeIntervalSeconds</c>) to their safe minimums so a misconfigured
|
||||
/// bind cannot change failover semantics.
|
||||
/// </summary>
|
||||
/// <param name="threshold">The raw, possibly out-of-range threshold to construct with.</param>
|
||||
/// <param name="probeInterval">The raw, possibly negative probe interval (seconds) to construct with.</param>
|
||||
/// <param name="stableProbes">The raw, possibly out-of-range stable-probe count to construct with.</param>
|
||||
/// <param name="expectedThreshold">The expected clamped <see cref="FailoverSettings.Threshold"/>.</param>
|
||||
/// <param name="expectedProbeInterval">The expected clamped <see cref="FailoverSettings.ProbeIntervalSeconds"/>.</param>
|
||||
/// <param name="expectedStableProbes">The expected clamped <see cref="FailoverSettings.StableProbes"/>.</param>
|
||||
[Theory]
|
||||
[InlineData(0, 0, 0, 1, 0, 1)]
|
||||
[InlineData(-5, -5, -5, 1, 0, 1)]
|
||||
|
||||
@@ -199,26 +199,39 @@ public sealed class LmxSubtagAlarmSourceTests
|
||||
private readonly Dictionary<string, int> handlesByAddress = new(StringComparer.Ordinal);
|
||||
private int nextItemHandle = 100;
|
||||
|
||||
/// <summary>Item definitions passed to <see cref="AddItem"/> or <see cref="AddItem2"/>, in call order.</summary>
|
||||
public List<string> AddedItems { get; } = new();
|
||||
|
||||
/// <summary>Number of times <see cref="Advise"/> has been called.</summary>
|
||||
public int AdviseCount { get; private set; }
|
||||
|
||||
/// <summary>Server handles passed to <see cref="Advise"/>, in call order.</summary>
|
||||
public List<int> AdvisedServerHandles { get; } = new();
|
||||
|
||||
/// <summary>Recorded <see cref="Write"/> calls, in call order.</summary>
|
||||
public List<WriteRecord> Writes { get; } = new();
|
||||
|
||||
/// <summary>Item handles passed to <see cref="UnAdvise"/>, in call order.</summary>
|
||||
public List<int> UnAdvisedItemHandles { get; } = new();
|
||||
|
||||
/// <summary>Item handles passed to <see cref="RemoveItem"/>, in call order.</summary>
|
||||
public List<int> RemovedItemHandles { get; } = new();
|
||||
|
||||
/// <summary>Number of times <see cref="Unregister"/> has been called.</summary>
|
||||
public int UnregisterCount { get; private set; }
|
||||
|
||||
/// <summary>Returns the item handle most recently assigned to the given item address.</summary>
|
||||
/// <param name="itemAddress">The item definition/address to look up.</param>
|
||||
/// <returns>The item handle assigned by <see cref="AddItem"/>.</returns>
|
||||
public int LastItemHandleFor(string itemAddress) => handlesByAddress[itemAddress];
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Register(string clientName) => FakeServerHandle;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Unregister(int serverHandle) => UnregisterCount++;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AddItem(int serverHandle, string itemDefinition)
|
||||
{
|
||||
AddedItems.Add(itemDefinition);
|
||||
@@ -227,55 +240,76 @@ public sealed class LmxSubtagAlarmSourceTests
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AddItem2(int serverHandle, string itemDefinition, string itemContext)
|
||||
=> AddItem(serverHandle, itemDefinition);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveItem(int serverHandle, int itemHandle) => RemovedItemHandles.Add(itemHandle);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Advise(int serverHandle, int itemHandle)
|
||||
{
|
||||
AdviseCount++;
|
||||
AdvisedServerHandles.Add(serverHandle);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UnAdvise(int serverHandle, int itemHandle) => UnAdvisedItemHandles.Add(itemHandle);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AdviseSupervisory(int serverHandle, int itemHandle)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Write(int serverHandle, int itemHandle, object? value, int userId)
|
||||
=> Writes.Add(new WriteRecord(serverHandle, itemHandle, value, userId));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Write2(int serverHandle, int itemHandle, object? value, object? timestamp, int userId)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteSecured(int serverHandle, int itemHandle, int currentUserId, int verifierUserId, object? value)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteSecured2(int serverHandle, int itemHandle, int currentUserId, int verifierUserId, object? value, object? timestamp)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Suspend(int serverHandle, int itemHandle) => new object();
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Activate(int serverHandle, int itemHandle) => new object();
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AuthenticateUser(int serverHandle, string verifyUser, string verifyUserPassword) => 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int ArchestrAUserToId(int serverHandle, string userIdGuid) => 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int AddBufferedItem(int serverHandle, string itemDefinition, string itemContext)
|
||||
=> AddItem(serverHandle, itemDefinition);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetBufferedUpdateInterval(int serverHandle, int updateIntervalMilliseconds)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Immutable record of a single <see cref="Write"/> call captured by <see cref="Writes"/>.</summary>
|
||||
internal sealed class WriteRecord
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="WriteRecord"/> class from a recorded write.</summary>
|
||||
/// <param name="serverHandle">The server handle the write targeted.</param>
|
||||
/// <param name="itemHandle">The item handle the write targeted.</param>
|
||||
/// <param name="value">The written value.</param>
|
||||
/// <param name="userId">The writing user's identifier.</param>
|
||||
public WriteRecord(int serverHandle, int itemHandle, object? value, int userId)
|
||||
{
|
||||
ServerHandle = serverHandle;
|
||||
@@ -284,12 +318,16 @@ public sealed class LmxSubtagAlarmSourceTests
|
||||
UserId = userId;
|
||||
}
|
||||
|
||||
/// <summary>The server handle the write targeted.</summary>
|
||||
public int ServerHandle { get; }
|
||||
|
||||
/// <summary>The item handle the write targeted.</summary>
|
||||
public int ItemHandle { get; }
|
||||
|
||||
/// <summary>The written value.</summary>
|
||||
public object? Value { get; }
|
||||
|
||||
/// <summary>The writing user's identifier.</summary>
|
||||
public int UserId { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using ZB.MOM.WW.MxGateway.Worker.MxAccess;
|
||||
namespace ZB.MOM.WW.MxGateway.Worker.Tests.MxAccess;
|
||||
|
||||
/// <summary>
|
||||
/// Worker-007 regression tests for <see cref="MxAccessComServer"/>. The
|
||||
/// Regression tests for <see cref="MxAccessComServer"/>. The
|
||||
/// adapter no longer falls back to late-bound <c>Type.InvokeMember</c>
|
||||
/// reflection: a COM object must implement either the typed
|
||||
/// <c>ILMXProxyServer</c> COM interface family (production) or
|
||||
@@ -124,8 +124,7 @@ public sealed class MxAccessComServerTests
|
||||
/// <summary>Gets the recorded method calls as strings.</summary>
|
||||
public IReadOnlyList<string> Calls => calls.ToArray();
|
||||
|
||||
/// <summary>Records a Register call and returns the configured handle.</summary>
|
||||
/// <param name="clientName">The client name to record.</param>
|
||||
/// <inheritdoc />
|
||||
public int Register(string clientName)
|
||||
{
|
||||
calls.Add($"Register:{clientName}");
|
||||
@@ -138,159 +137,111 @@ public sealed class MxAccessComServerTests
|
||||
return registerHandle;
|
||||
}
|
||||
|
||||
/// <summary>Records an Unregister call.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <inheritdoc />
|
||||
public void Unregister(int serverHandle)
|
||||
{
|
||||
calls.Add($"Unregister:{serverHandle}");
|
||||
}
|
||||
|
||||
/// <summary>Records an AddItem call and returns zero.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemDefinition">The item definition string to record.</param>
|
||||
/// <inheritdoc />
|
||||
public int AddItem(int serverHandle, string itemDefinition)
|
||||
{
|
||||
calls.Add($"AddItem:{serverHandle}:{itemDefinition}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Records an AddItem2 call and returns zero.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemDefinition">The item definition string to record.</param>
|
||||
/// <param name="itemContext">The item context string to record.</param>
|
||||
/// <inheritdoc />
|
||||
public int AddItem2(int serverHandle, string itemDefinition, string itemContext)
|
||||
{
|
||||
calls.Add($"AddItem2:{serverHandle}:{itemDefinition}:{itemContext}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Records a RemoveItem call.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemHandle">The MXAccess item handle.</param>
|
||||
/// <inheritdoc />
|
||||
public void RemoveItem(int serverHandle, int itemHandle)
|
||||
{
|
||||
calls.Add($"RemoveItem:{serverHandle}:{itemHandle}");
|
||||
}
|
||||
|
||||
/// <summary>Records an Advise call.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemHandle">The MXAccess item handle.</param>
|
||||
/// <inheritdoc />
|
||||
public void Advise(int serverHandle, int itemHandle)
|
||||
{
|
||||
calls.Add($"Advise:{serverHandle}:{itemHandle}");
|
||||
}
|
||||
|
||||
/// <summary>Records an UnAdvise call.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemHandle">The MXAccess item handle.</param>
|
||||
/// <inheritdoc />
|
||||
public void UnAdvise(int serverHandle, int itemHandle)
|
||||
{
|
||||
calls.Add($"UnAdvise:{serverHandle}:{itemHandle}");
|
||||
}
|
||||
|
||||
/// <summary>Records an AdviseSupervisory call.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemHandle">The MXAccess item handle.</param>
|
||||
/// <inheritdoc />
|
||||
public void AdviseSupervisory(int serverHandle, int itemHandle)
|
||||
{
|
||||
calls.Add($"AdviseSupervisory:{serverHandle}:{itemHandle}");
|
||||
}
|
||||
|
||||
/// <summary>Records a Write call.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemHandle">The MXAccess item handle.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
/// <param name="userId">The user identifier.</param>
|
||||
/// <inheritdoc />
|
||||
public void Write(int serverHandle, int itemHandle, object? value, int userId)
|
||||
{
|
||||
calls.Add($"Write:{serverHandle}:{itemHandle}:{value}:{userId}");
|
||||
}
|
||||
|
||||
/// <summary>Records a Write2 call.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemHandle">The MXAccess item handle.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
/// <param name="timestamp">The timestamp value.</param>
|
||||
/// <param name="userId">The user identifier.</param>
|
||||
/// <inheritdoc />
|
||||
public void Write2(int serverHandle, int itemHandle, object? value, object? timestamp, int userId)
|
||||
{
|
||||
calls.Add($"Write2:{serverHandle}:{itemHandle}:{value}:{timestamp}:{userId}");
|
||||
}
|
||||
|
||||
/// <summary>Records a WriteSecured call.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemHandle">The MXAccess item handle.</param>
|
||||
/// <param name="currentUserId">The current user identifier.</param>
|
||||
/// <param name="verifierUserId">The verifier user identifier.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
/// <inheritdoc />
|
||||
public void WriteSecured(int serverHandle, int itemHandle, int currentUserId, int verifierUserId, object? value)
|
||||
{
|
||||
calls.Add($"WriteSecured:{serverHandle}:{itemHandle}:{currentUserId}:{verifierUserId}:{value}");
|
||||
}
|
||||
|
||||
/// <summary>Records a WriteSecured2 call.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemHandle">The MXAccess item handle.</param>
|
||||
/// <param name="currentUserId">The current user identifier.</param>
|
||||
/// <param name="verifierUserId">The verifier user identifier.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
/// <param name="timestamp">The timestamp value.</param>
|
||||
/// <inheritdoc />
|
||||
public void WriteSecured2(
|
||||
int serverHandle, int itemHandle, int currentUserId, int verifierUserId, object? value, object? timestamp)
|
||||
{
|
||||
calls.Add($"WriteSecured2:{serverHandle}:{itemHandle}:{currentUserId}:{verifierUserId}:{value}:{timestamp}");
|
||||
}
|
||||
|
||||
/// <summary>Records a Suspend call and returns a canned status.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemHandle">The MXAccess item handle.</param>
|
||||
/// <inheritdoc />
|
||||
public object Suspend(int serverHandle, int itemHandle)
|
||||
{
|
||||
calls.Add($"Suspend:{serverHandle}:{itemHandle}");
|
||||
return new object();
|
||||
}
|
||||
|
||||
/// <summary>Records an Activate call and returns a canned status.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemHandle">The MXAccess item handle.</param>
|
||||
/// <inheritdoc />
|
||||
public object Activate(int serverHandle, int itemHandle)
|
||||
{
|
||||
calls.Add($"Activate:{serverHandle}:{itemHandle}");
|
||||
return new object();
|
||||
}
|
||||
|
||||
/// <summary>Records an AuthenticateUser call and returns zero.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="verifyUser">The user name to authenticate.</param>
|
||||
/// <param name="verifyUserPassword">The credential; recorded only as a fixed marker, never echoed.</param>
|
||||
/// <inheritdoc />
|
||||
public int AuthenticateUser(int serverHandle, string verifyUser, string verifyUserPassword)
|
||||
{
|
||||
calls.Add($"AuthenticateUser:{serverHandle}:{verifyUser}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Records an ArchestrAUserToId call and returns zero.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="userIdGuid">The ArchestrA user GUID to resolve.</param>
|
||||
/// <inheritdoc />
|
||||
public int ArchestrAUserToId(int serverHandle, string userIdGuid)
|
||||
{
|
||||
calls.Add($"ArchestrAUserToId:{serverHandle}:{userIdGuid}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Records an AddBufferedItem call and returns zero.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="itemDefinition">The item definition string to record.</param>
|
||||
/// <param name="itemContext">The item context string to record.</param>
|
||||
/// <inheritdoc />
|
||||
public int AddBufferedItem(int serverHandle, string itemDefinition, string itemContext)
|
||||
{
|
||||
calls.Add($"AddBufferedItem:{serverHandle}:{itemDefinition}:{itemContext}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Records a SetBufferedUpdateInterval call.</summary>
|
||||
/// <param name="serverHandle">The MXAccess server handle.</param>
|
||||
/// <param name="updateIntervalMilliseconds">The buffered update interval in milliseconds.</param>
|
||||
/// <inheritdoc />
|
||||
public void SetBufferedUpdateInterval(int serverHandle, int updateIntervalMilliseconds)
|
||||
{
|
||||
calls.Add($"SetBufferedUpdateInterval:{serverHandle}:{updateIntervalMilliseconds}");
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace ZB.MOM.WW.MxGateway.Worker.Tests.MxAccess;
|
||||
public sealed class MxAccessCommandExecutorTests
|
||||
{
|
||||
/// <summary>Verifies that Register command calls MXAccess on the STA thread and preserves the server handle.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_Register_CallsMxAccessOnStaAndPreservesServerHandle()
|
||||
{
|
||||
@@ -40,6 +41,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Unregister command calls MXAccess on the STA thread and removes the tracked server handle.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_Unregister_CallsMxAccessOnStaAndRemovesTrackedServerHandle()
|
||||
{
|
||||
@@ -59,6 +61,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Unregister preserves the HResult when MXAccess throws and does not rewrite the failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_UnregisterWhenMxAccessThrows_PreservesHResultAndDoesNotRewriteFailure()
|
||||
{
|
||||
@@ -86,6 +89,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddItem command calls MXAccess on the STA thread and tracks the item handle.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_AddItem_CallsMxAccessOnStaAndTracksItemHandle()
|
||||
{
|
||||
@@ -123,6 +127,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddItem2 command passes the context exactly and tracks the item handle.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_AddItem2_PassesContextExactlyAndTracksItemHandle()
|
||||
{
|
||||
@@ -160,6 +165,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that RemoveItem command calls MXAccess on the STA thread and removes the tracked item handle.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_RemoveItem_CallsMxAccessOnStaAndRemovesTrackedItemHandle()
|
||||
{
|
||||
@@ -188,6 +194,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that RemoveItem removes tracked advice after MXAccess succeeds on an advised handle.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_RemoveItemWithAdvisedHandle_RemovesTrackedAdviceAfterMxAccessSucceeds()
|
||||
{
|
||||
@@ -213,6 +220,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that RemoveItem preserves the HResult and keeps the tracked item handle when using a cross-server handle.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_RemoveItemWithCrossServerHandle_PreservesHResultAndKeepsTrackedItemHandle()
|
||||
{
|
||||
@@ -247,6 +255,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddItem2 preserves the HResult when MXAccess throws and does not track the item handle.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_AddItem2WhenMxAccessThrows_PreservesHResultAndDoesNotTrackItemHandle()
|
||||
{
|
||||
@@ -276,6 +285,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Advise command calls MXAccess on the STA thread and tracks plain advice.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_Advise_CallsMxAccessOnStaAndTracksPlainAdvice()
|
||||
{
|
||||
@@ -309,6 +319,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AdviseSupervisory calls a distinct MXAccess method and tracks supervisory advice.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_AdviseSupervisory_CallsDistinctMxAccessMethodAndTracksSupervisoryAdvice()
|
||||
{
|
||||
@@ -341,6 +352,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that UnAdvise command calls MXAccess on the STA thread and removes the tracked advice.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_UnAdvise_CallsMxAccessOnStaAndRemovesTrackedAdvice()
|
||||
{
|
||||
@@ -369,6 +381,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Advise preserves the HResult when MXAccess throws and does not track the advice.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_AdviseWhenMxAccessThrows_PreservesHResultAndDoesNotTrackAdvice()
|
||||
{
|
||||
@@ -399,6 +412,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that UnAdvise preserves the HResult when MXAccess throws and keeps the tracked advice.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_UnAdviseWhenMxAccessThrows_PreservesHResultAndKeepsTrackedAdvice()
|
||||
{
|
||||
@@ -433,6 +447,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that SubscribeBulk runs sequential MXAccess calls and returns per-item results.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_SubscribeBulk_RunsSequentialMxAccessCallsAndReturnsPerItemResults()
|
||||
{
|
||||
@@ -478,6 +493,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
/// one BulkWriteResult per entry in input order, including a per-entry COM
|
||||
/// failure surfaced as <c>WasSuccessful=false</c> with the underlying HRESULT.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WriteBulk_RunsSequentialWritesAndReturnsPerEntryResults()
|
||||
{
|
||||
@@ -530,6 +546,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Write2Bulk forwards value AND timestamp to each per-entry Write2.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_Write2Bulk_ForwardsValueAndTimestampPerEntry()
|
||||
{
|
||||
@@ -556,6 +573,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that WriteSecuredBulk forwards both user ids per entry.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WriteSecuredBulk_ForwardsUserIdsPerEntry()
|
||||
{
|
||||
@@ -580,6 +598,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that WriteSecured2Bulk forwards user ids, value, and timestamp per entry.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WriteSecured2Bulk_ForwardsUserIdsValueAndTimestampPerEntry()
|
||||
{
|
||||
@@ -612,6 +631,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
/// The fake COM object never fires events so the wait always times out — but
|
||||
/// the lifecycle calls must still happen, in order, on the STA.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_ReadBulk_WhenTagNotCached_TakesSnapshotLifecycleAndTimesOut()
|
||||
{
|
||||
@@ -650,6 +670,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReadBulk with no payload returns an invalid request error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_ReadBulkWithoutPayload_ReturnsInvalidRequest()
|
||||
{
|
||||
@@ -671,6 +692,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that UnsubscribeBulk removes items after UnAdvise failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_UnsubscribeBulk_RemovesItemAfterUnAdviseFailure()
|
||||
{
|
||||
@@ -700,6 +722,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ShutdownGracefullyAsync cleans up handles in advice, item, server order.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ShutdownGracefullyAsync_CleansHandlesInAdviceItemServerOrder()
|
||||
{
|
||||
@@ -725,6 +748,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ShutdownGracefullyAsync records cleanup failures and continues.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ShutdownGracefullyAsync_RecordsCleanupFailuresAndContinues()
|
||||
{
|
||||
@@ -753,6 +777,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Register without payload returns an invalid request error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_RegisterWithoutPayload_ReturnsInvalidRequest()
|
||||
{
|
||||
@@ -774,6 +799,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddItem without payload returns an invalid request error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_AddItemWithoutPayload_ReturnsInvalidRequest()
|
||||
{
|
||||
@@ -795,6 +821,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Advise without payload returns an invalid request error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_AdviseWithoutPayload_ReturnsInvalidRequest()
|
||||
{
|
||||
@@ -816,6 +843,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Write dispatches the converted value to MXAccess on the STA thread.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_Write_CallsMxAccessOnStaWithConvertedValue()
|
||||
{
|
||||
@@ -838,6 +866,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Write2 forwards the converted value and timestamp to MXAccess.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_Write2_ForwardsValueAndTimestamp()
|
||||
{
|
||||
@@ -860,6 +889,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that WriteSecured forwards the operator and verifier user ids to MXAccess.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WriteSecured_ForwardsUserIds()
|
||||
{
|
||||
@@ -881,6 +911,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that WriteSecured2 forwards user ids, value, and timestamp to MXAccess.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WriteSecured2_ForwardsUserIdsValueAndTimestamp()
|
||||
{
|
||||
@@ -904,6 +935,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Write without a payload returns an invalid request error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WriteWithoutPayload_ReturnsInvalidRequest()
|
||||
{
|
||||
@@ -926,6 +958,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Write without a value returns an invalid request error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WriteWithoutValue_ReturnsInvalidRequest()
|
||||
{
|
||||
@@ -953,6 +986,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies Suspend calls MXAccess on the STA and maps the native status to MxStatusProxy.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_Suspend_CallsMxAccessOnStaAndMapsStatus()
|
||||
{
|
||||
@@ -977,6 +1011,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies Activate calls MXAccess on the STA and maps the native status to MxStatusProxy.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_Activate_CallsMxAccessOnStaAndMapsStatus()
|
||||
{
|
||||
@@ -1000,6 +1035,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies AuthenticateUser passes credentials to MXAccess on the STA and returns the user id.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_AuthenticateUser_CallsMxAccessOnStaAndReturnsUserId()
|
||||
{
|
||||
@@ -1026,6 +1062,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
/// command reply or any recorded diagnostic — the password is only ever
|
||||
/// handed straight to the MXAccess wrapper.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_AuthenticateUser_DoesNotLeakPassword()
|
||||
{
|
||||
@@ -1051,6 +1088,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies ArchestrAUserToId calls MXAccess on the STA and returns the resolved user id.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_ArchestrAUserToId_CallsMxAccessOnStaAndReturnsUserId()
|
||||
{
|
||||
@@ -1072,6 +1110,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies AddBufferedItem calls MXAccess on the STA and tracks the buffered item handle.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_AddBufferedItem_CallsMxAccessOnStaAndTracksItemHandle()
|
||||
{
|
||||
@@ -1104,6 +1143,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies SetBufferedUpdateInterval calls MXAccess on the STA and returns a base OK reply.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_SetBufferedUpdateInterval_CallsMxAccessOnStaAndReturnsOk()
|
||||
{
|
||||
@@ -1131,6 +1171,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
/// <c>throw</c> would propagate an unhandled exception through <c>WorkerPipeSession</c>
|
||||
/// and no other test would catch it.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WithUnknownCommandKind_ReturnsInvalidRequestWithUnsupportedDiagnostic()
|
||||
{
|
||||
@@ -1860,9 +1901,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
/// <summary>Gets the list of operations performed on this fake object.</summary>
|
||||
public IReadOnlyList<string> OperationNames => operationNames.ToArray();
|
||||
|
||||
/// <summary>Registers a client and returns a server handle.</summary>
|
||||
/// <param name="clientName">Name of the client to register.</param>
|
||||
/// <returns>The server handle for the registered client.</returns>
|
||||
/// <inheritdoc />
|
||||
public int Register(string clientName)
|
||||
{
|
||||
operationNames.Add($"Register:{clientName}");
|
||||
@@ -1872,8 +1911,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
return registerHandle;
|
||||
}
|
||||
|
||||
/// <summary>Unregisters a server and tracks the operation.</summary>
|
||||
/// <param name="serverHandle">Server handle to unregister.</param>
|
||||
/// <inheritdoc />
|
||||
public void Unregister(int serverHandle)
|
||||
{
|
||||
operationNames.Add($"Unregister:{serverHandle}");
|
||||
@@ -1886,10 +1924,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the server and returns its item handle.</summary>
|
||||
/// <param name="serverHandle">Server handle to add the item to.</param>
|
||||
/// <param name="itemDefinition">Item definition string.</param>
|
||||
/// <returns>The item handle for the added item.</returns>
|
||||
/// <inheritdoc />
|
||||
public int AddItem(
|
||||
int serverHandle,
|
||||
string itemDefinition)
|
||||
@@ -1907,11 +1942,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
return addItemHandle;
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the server with context and returns its item handle.</summary>
|
||||
/// <param name="serverHandle">Server handle to add the item to.</param>
|
||||
/// <param name="itemDefinition">Item definition string.</param>
|
||||
/// <param name="itemContext">Item context string.</param>
|
||||
/// <returns>The item handle for the added item.</returns>
|
||||
/// <inheritdoc />
|
||||
public int AddItem2(
|
||||
int serverHandle,
|
||||
string itemDefinition,
|
||||
@@ -1931,9 +1962,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
return addItem2Handle;
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the server and tracks the operation.</summary>
|
||||
/// <param name="serverHandle">Server handle from which to remove the item.</param>
|
||||
/// <param name="itemHandle">Item handle to remove.</param>
|
||||
/// <inheritdoc />
|
||||
public void RemoveItem(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -1949,9 +1978,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Advises on item changes and tracks the operation.</summary>
|
||||
/// <param name="serverHandle">Server handle for the advisory subscription.</param>
|
||||
/// <param name="itemHandle">Item handle to advise on.</param>
|
||||
/// <inheritdoc />
|
||||
public void Advise(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -1967,9 +1994,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes an item advice subscription and tracks the operation.</summary>
|
||||
/// <param name="serverHandle">Server handle from which to remove the subscription.</param>
|
||||
/// <param name="itemHandle">Item handle to remove advice from.</param>
|
||||
/// <inheritdoc />
|
||||
public void UnAdvise(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -1985,9 +2010,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Advises supervisory on item changes and tracks the operation.</summary>
|
||||
/// <param name="serverHandle">Server handle for the supervisory subscription.</param>
|
||||
/// <param name="itemHandle">Item handle to advise supervisory on.</param>
|
||||
/// <inheritdoc />
|
||||
public void AdviseSupervisory(
|
||||
int serverHandle,
|
||||
int itemHandle)
|
||||
@@ -2027,11 +2050,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
/// <summary>Gets the thread ID on which the most recent write was called.</summary>
|
||||
public int? WriteThreadId { get; private set; }
|
||||
|
||||
/// <summary>Writes a value to an item and tracks the operation.</summary>
|
||||
/// <param name="serverHandle">Server handle for the write.</param>
|
||||
/// <param name="itemHandle">Item handle to write to.</param>
|
||||
/// <param name="value">Value to write.</param>
|
||||
/// <param name="userId">MXAccess user id for the write.</param>
|
||||
/// <inheritdoc />
|
||||
public void Write(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
@@ -2047,12 +2066,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
ThrowIfWriteFailureConfigured(itemHandle);
|
||||
}
|
||||
|
||||
/// <summary>Writes a timestamped value to an item and tracks the operation.</summary>
|
||||
/// <param name="serverHandle">Server handle for the write.</param>
|
||||
/// <param name="itemHandle">Item handle to write to.</param>
|
||||
/// <param name="value">Value to write.</param>
|
||||
/// <param name="timestamp">Source timestamp for the write.</param>
|
||||
/// <param name="userId">MXAccess user id for the write.</param>
|
||||
/// <inheritdoc />
|
||||
public void Write2(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
@@ -2070,12 +2084,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
ThrowIfWriteFailureConfigured(itemHandle);
|
||||
}
|
||||
|
||||
/// <summary>Performs a secured write to an item and tracks the operation.</summary>
|
||||
/// <param name="serverHandle">Server handle for the write.</param>
|
||||
/// <param name="itemHandle">Item handle to write to.</param>
|
||||
/// <param name="currentUserId">Operator user id.</param>
|
||||
/// <param name="verifierUserId">Verifier user id.</param>
|
||||
/// <param name="value">Value to write.</param>
|
||||
/// <inheritdoc />
|
||||
public void WriteSecured(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
@@ -2093,13 +2102,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
ThrowIfWriteFailureConfigured(itemHandle);
|
||||
}
|
||||
|
||||
/// <summary>Performs a secured timestamped write to an item and tracks the operation.</summary>
|
||||
/// <param name="serverHandle">Server handle for the write.</param>
|
||||
/// <param name="itemHandle">Item handle to write to.</param>
|
||||
/// <param name="currentUserId">Operator user id.</param>
|
||||
/// <param name="verifierUserId">Verifier user id.</param>
|
||||
/// <param name="value">Value to write.</param>
|
||||
/// <param name="timestamp">Source timestamp for the write.</param>
|
||||
/// <inheritdoc />
|
||||
public void WriteSecured2(
|
||||
int serverHandle,
|
||||
int itemHandle,
|
||||
@@ -2181,10 +2184,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
/// <summary>Gets the interval passed to SetBufferedUpdateInterval, if called.</summary>
|
||||
public int? SetBufferedUpdateIntervalValue { get; private set; }
|
||||
|
||||
/// <summary>Suspends an item and returns a canned status whose fields drive MxStatusProxy conversion.</summary>
|
||||
/// <param name="serverHandle">Server handle for the suspend.</param>
|
||||
/// <param name="itemHandle">Item handle to suspend.</param>
|
||||
/// <returns>A status stand-in with all-OK fields.</returns>
|
||||
/// <inheritdoc />
|
||||
public object Suspend(int serverHandle, int itemHandle)
|
||||
{
|
||||
operationNames.Add($"Suspend:{serverHandle}:{itemHandle}");
|
||||
@@ -2194,10 +2194,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
return new FakeMxStatus { success = 1, category = 0, detectedBy = 0, detail = 0 };
|
||||
}
|
||||
|
||||
/// <summary>Activates an item and returns a canned status whose fields drive MxStatusProxy conversion.</summary>
|
||||
/// <param name="serverHandle">Server handle for the activate.</param>
|
||||
/// <param name="itemHandle">Item handle to activate.</param>
|
||||
/// <returns>A status stand-in with all-OK fields.</returns>
|
||||
/// <inheritdoc />
|
||||
public object Activate(int serverHandle, int itemHandle)
|
||||
{
|
||||
operationNames.Add($"Activate:{serverHandle}:{itemHandle}");
|
||||
@@ -2207,11 +2204,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
return new FakeMxStatus { success = 1, category = 0, detectedBy = 0, detail = 0 };
|
||||
}
|
||||
|
||||
/// <summary>Authenticates a user and returns a canned user id.</summary>
|
||||
/// <param name="serverHandle">Server handle for the authentication.</param>
|
||||
/// <param name="verifyUser">User name to authenticate.</param>
|
||||
/// <param name="verifyUserPassword">Credential; recorded only to assert it is never logged.</param>
|
||||
/// <returns>The canned MXAccess user id (1).</returns>
|
||||
/// <inheritdoc />
|
||||
public int AuthenticateUser(int serverHandle, string verifyUser, string verifyUserPassword)
|
||||
{
|
||||
// Deliberately does NOT include the password in the operation log.
|
||||
@@ -2223,10 +2216,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>Resolves an ArchestrA user GUID and returns a canned user id.</summary>
|
||||
/// <param name="serverHandle">Server handle for the resolution.</param>
|
||||
/// <param name="userIdGuid">ArchestrA user GUID to resolve.</param>
|
||||
/// <returns>The canned MXAccess user id (7).</returns>
|
||||
/// <inheritdoc />
|
||||
public int ArchestrAUserToId(int serverHandle, string userIdGuid)
|
||||
{
|
||||
operationNames.Add($"ArchestrAUserToId:{serverHandle}:{userIdGuid}");
|
||||
@@ -2235,11 +2225,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
return 7;
|
||||
}
|
||||
|
||||
/// <summary>Adds a buffered item and returns a canned item handle.</summary>
|
||||
/// <param name="serverHandle">Server handle to add the item to.</param>
|
||||
/// <param name="itemDefinition">Item definition string.</param>
|
||||
/// <param name="itemContext">Item context string.</param>
|
||||
/// <returns>The canned buffered item handle (1).</returns>
|
||||
/// <inheritdoc />
|
||||
public int AddBufferedItem(int serverHandle, string itemDefinition, string itemContext)
|
||||
{
|
||||
operationNames.Add($"AddBufferedItem:{serverHandle}:{itemDefinition}:{itemContext}");
|
||||
@@ -2249,9 +2235,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// <summary>Sets the buffered update interval and tracks the operation.</summary>
|
||||
/// <param name="serverHandle">Server handle for the interval change.</param>
|
||||
/// <param name="updateIntervalMilliseconds">Buffered update interval in milliseconds.</param>
|
||||
/// <inheritdoc />
|
||||
public void SetBufferedUpdateInterval(int serverHandle, int updateIntervalMilliseconds)
|
||||
{
|
||||
operationNames.Add($"SetBufferedUpdateInterval:{serverHandle}:{updateIntervalMilliseconds}");
|
||||
@@ -2274,8 +2258,7 @@ public sealed class MxAccessCommandExecutorTests
|
||||
/// <summary>Gets the fake COM object.</summary>
|
||||
public FakeMxAccessComObject FakeComObject { get; }
|
||||
|
||||
/// <summary>Creates and returns the fake MXAccess COM object.</summary>
|
||||
/// <returns>The fake COM object.</returns>
|
||||
/// <inheritdoc />
|
||||
public object Create()
|
||||
{
|
||||
return FakeComObject;
|
||||
|
||||
@@ -15,6 +15,7 @@ public sealed class MxAccessLiveComCreationTests
|
||||
private const string DefaultLiveAddItem2Context = "TestChildObject";
|
||||
|
||||
/// <summary>Verifies that StartAsync creates the installed MXAccess COM object on the STA thread when opted in.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[LiveMxAccessFact]
|
||||
public async Task StartAsync_WhenOptedIn_CreatesInstalledMxAccessComObjectOnSta()
|
||||
{
|
||||
@@ -24,6 +25,7 @@ public sealed class MxAccessLiveComCreationTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Register and Unregister round-trip server handles with installed MXAccess.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[LiveMxAccessFact]
|
||||
public async Task RegisterAndUnregister_WhenOptedIn_RoundTripsInstalledMxAccessServerHandle()
|
||||
{
|
||||
@@ -61,6 +63,7 @@ public sealed class MxAccessLiveComCreationTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddItem and RemoveItem round-trip item handles with installed MXAccess.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[LiveMxAccessFact]
|
||||
public async Task AddItemAndRemoveItem_WhenOptedIn_RoundTripsInstalledMxAccessItemHandle()
|
||||
{
|
||||
@@ -129,6 +132,7 @@ public sealed class MxAccessLiveComCreationTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AddItem2 and RemoveItem preserve item context with installed MXAccess.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[LiveMxAccessFact]
|
||||
public async Task AddItem2AndRemoveItem_WhenOptedIn_PreservesContextForInstalledMxAccess()
|
||||
{
|
||||
@@ -198,6 +202,7 @@ public sealed class MxAccessLiveComCreationTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that Advise and UnAdvise round-trip subscriptions with installed MXAccess.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[LiveMxAccessFact]
|
||||
public async Task AdviseAndUnAdvise_WhenOptedIn_RoundTripsInstalledMxAccessSubscription()
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ public sealed class MxAccessStaSessionTests
|
||||
/// <summary>
|
||||
/// Verifies that StartAsync creates the MXAccess COM object and attaches the event sink on the STA thread.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task StartAsync_CreatesComObjectAndAttachesEventSinkOnStaThread()
|
||||
{
|
||||
@@ -42,6 +43,7 @@ public sealed class MxAccessStaSessionTests
|
||||
/// <summary>
|
||||
/// Verifies that StartAsync maps creation exceptions with HResult when the factory fails.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task StartAsync_WhenFactoryFails_MapsCreationExceptionWithHResult()
|
||||
{
|
||||
@@ -63,6 +65,7 @@ public sealed class MxAccessStaSessionTests
|
||||
/// <summary>
|
||||
/// Verifies that Dispose detaches the event sink on the STA thread.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Dispose_DetachesEventSinkOnStaThread()
|
||||
{
|
||||
@@ -116,9 +119,7 @@ public sealed class MxAccessStaSessionTests
|
||||
/// </summary>
|
||||
public ApartmentState? CreateApartmentState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates the COM object or throws the configured exception.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public object Create()
|
||||
{
|
||||
CreateThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
@@ -158,11 +159,7 @@ public sealed class MxAccessStaSessionTests
|
||||
/// </summary>
|
||||
public string? SessionId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the MXAccess COM object and records thread context.
|
||||
/// </summary>
|
||||
/// <param name="mxAccessComObject">MXAccess COM object to attach.</param>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
/// <inheritdoc />
|
||||
public void Attach(
|
||||
object mxAccessComObject,
|
||||
string sessionId)
|
||||
@@ -172,9 +169,7 @@ public sealed class MxAccessStaSessionTests
|
||||
SessionId = sessionId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detaches the MXAccess COM object and records thread context.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public void Detach()
|
||||
{
|
||||
DetachThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
@@ -188,6 +183,7 @@ public sealed class MxAccessStaSessionTests
|
||||
/// This proves the fix in WorkerPipeSession (and the new internal constructor) correctly
|
||||
/// wires the factory rather than leaving alarmCommandHandler null.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task StartAsync_WithAlarmCommandHandlerFactory_SubscribeAlarmsCommandReachesHandler()
|
||||
{
|
||||
@@ -231,6 +227,7 @@ public sealed class MxAccessStaSessionTests
|
||||
/// test fails if the diagnostic regresses to a misleading message that still
|
||||
/// happens to contain the word "alarm".
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task StartAsync_WithoutAlarmCommandHandlerFactory_SubscribeAlarmsReturnsInvalidRequest()
|
||||
{
|
||||
@@ -267,6 +264,7 @@ public sealed class MxAccessStaSessionTests
|
||||
/// loop calls PollOnce on the handler via the STA within a reasonable timeout.
|
||||
/// This proves polling is driven by the STA rather than the consumer's internal timer.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task StartAsync_WithAlarmCommandHandlerFactory_PollOnceCalledViaSta()
|
||||
{
|
||||
@@ -304,6 +302,7 @@ public sealed class MxAccessStaSessionTests
|
||||
/// immediately after Dispose and stays frozen — deterministic, with no
|
||||
/// elapsed-time "no further polls" window that a slow agent could race.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Dispose_StopsAlarmPollLoop()
|
||||
{
|
||||
@@ -346,12 +345,13 @@ public sealed class MxAccessStaSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-005 regression: when the alarm poll loop's PollOnce throws a
|
||||
/// When the alarm poll loop's PollOnce throws a
|
||||
/// real failure (e.g. a COMException from GetXmlCurrentAlarms2), the
|
||||
/// failure must be recorded as a fault on the event queue so a broken
|
||||
/// alarm subscription becomes observable on the IPC fault path instead
|
||||
/// of silently faulting the never-awaited poll task.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAlarmPollLoop_WhenPollOnceThrows_RecordsFaultOnEventQueue()
|
||||
{
|
||||
@@ -389,7 +389,7 @@ public sealed class MxAccessStaSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-016 regression: the alarm poll loop's catch for the graceful
|
||||
/// The alarm poll loop's catch for the graceful
|
||||
/// STA-runtime-shutdown signal must NOT also swallow a vanilla
|
||||
/// <see cref="InvalidOperationException"/> raised from inside the marshalled
|
||||
/// poll lambda — for example the STA-affinity assertion thrown by
|
||||
@@ -399,6 +399,7 @@ public sealed class MxAccessStaSessionTests
|
||||
/// <see cref="InvalidOperationException"/> from <c>PollOnce</c> must reach
|
||||
/// the fault-recording arm and become observable on the event queue.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunAlarmPollLoop_WhenPollOnceThrowsInvalidOperation_RecordsFaultOnEventQueue()
|
||||
{
|
||||
@@ -438,7 +439,7 @@ public sealed class MxAccessStaSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-008 regression: the STA-affinity guard throws when an
|
||||
/// The STA-affinity guard throws when an
|
||||
/// IMxAccessAlarmConsumer call is attempted off the thread that created
|
||||
/// the consumer, mirroring the MxAccessSession.CreationThreadId invariant.
|
||||
/// </summary>
|
||||
@@ -455,7 +456,7 @@ public sealed class MxAccessStaSessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-008: the STA-affinity guard is a no-op on the owning thread and
|
||||
/// The STA-affinity guard is a no-op on the owning thread and
|
||||
/// when no alarm consumer is configured (expected thread id null).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
@@ -495,52 +496,35 @@ public sealed class MxAccessStaSessionTests
|
||||
get { lock (gate) return lastPollThreadId; }
|
||||
}
|
||||
|
||||
/// <summary>Subscribes to alarm events.</summary>
|
||||
/// <param name="command">The subscribe-alarms command.</param>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <inheritdoc />
|
||||
public void Subscribe(SubscribeAlarmsCommand command, string sessionId)
|
||||
{
|
||||
IsSubscribed = true;
|
||||
LastSubscription = command.SubscriptionExpression;
|
||||
}
|
||||
|
||||
/// <summary>Unsubscribes from alarm events.</summary>
|
||||
/// <inheritdoc />
|
||||
public void Unsubscribe()
|
||||
{
|
||||
IsSubscribed = false;
|
||||
}
|
||||
|
||||
/// <summary>Acknowledges an alarm by guid.</summary>
|
||||
/// <param name="alarmGuid">The alarm GUID.</param>
|
||||
/// <param name="comment">The acknowledgment comment.</param>
|
||||
/// <param name="operatorUser">The operator user name.</param>
|
||||
/// <param name="operatorNode">The operator node name.</param>
|
||||
/// <param name="operatorDomain">The operator domain.</param>
|
||||
/// <param name="operatorFullName">The operator full name.</param>
|
||||
/// <inheritdoc />
|
||||
public int Acknowledge(Guid alarmGuid, string comment, string operatorUser,
|
||||
string operatorNode, string operatorDomain, string operatorFullName)
|
||||
=> 0;
|
||||
|
||||
/// <summary>Acknowledges an alarm by name.</summary>
|
||||
/// <param name="alarmName">The alarm name.</param>
|
||||
/// <param name="providerName">The provider name.</param>
|
||||
/// <param name="groupName">The alarm group name.</param>
|
||||
/// <param name="comment">The acknowledgment comment.</param>
|
||||
/// <param name="operatorUser">The operator user name.</param>
|
||||
/// <param name="operatorNode">The operator node name.</param>
|
||||
/// <param name="operatorDomain">The operator domain.</param>
|
||||
/// <param name="operatorFullName">The operator full name.</param>
|
||||
/// <inheritdoc />
|
||||
public int AcknowledgeByName(string alarmName, string providerName, string groupName,
|
||||
string comment, string operatorUser, string operatorNode,
|
||||
string operatorDomain, string operatorFullName)
|
||||
=> 0;
|
||||
|
||||
/// <summary>Queries active alarms.</summary>
|
||||
/// <param name="alarmFilterPrefix">Optional alarm name filter prefix.</param>
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<ActiveAlarmSnapshot> QueryActive(string? alarmFilterPrefix)
|
||||
=> Array.Empty<ActiveAlarmSnapshot>();
|
||||
|
||||
/// <summary>Polls for alarm events once.</summary>
|
||||
/// <inheritdoc />
|
||||
public void PollOnce()
|
||||
{
|
||||
lock (gate)
|
||||
|
||||
@@ -115,6 +115,7 @@ public sealed class MxAccessValueCacheTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that TryWaitForUpdate returns true when the cache is updated after the baseline.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task TryWaitForUpdate_ReturnsTrue_WhenSetFiresAfterBaselineVersion()
|
||||
{
|
||||
|
||||
@@ -294,16 +294,13 @@ public sealed class SubtagAlarmConsumerTests
|
||||
/// <summary>Gets the most recent (address, value) pair passed to <see cref="Write"/>.</summary>
|
||||
public (string Address, object? Value)? LastWrite { get; private set; }
|
||||
|
||||
/// <summary>Records the advised subtag addresses.</summary>
|
||||
/// <param name="itemAddresses">The subtag references to advise.</param>
|
||||
/// <inheritdoc />
|
||||
public void Advise(IReadOnlyCollection<string> itemAddresses)
|
||||
{
|
||||
Advised.AddRange(itemAddresses);
|
||||
}
|
||||
|
||||
/// <summary>Records the most recent write.</summary>
|
||||
/// <param name="itemAddress">The subtag reference to write.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
/// <inheritdoc />
|
||||
public void Write(string itemAddress, object? value)
|
||||
{
|
||||
LastWrite = (itemAddress, value);
|
||||
|
||||
@@ -23,6 +23,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
AckCommentSubtag = "Tank01.Level.HiHi.ackmsg",
|
||||
};
|
||||
|
||||
/// <summary>Verifies that an active transition from false to true emits an unacked raise.</summary>
|
||||
[Fact]
|
||||
public void ActiveFalseToTrue_EmitsRaise()
|
||||
{
|
||||
@@ -37,6 +38,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
Assert.Equal("Area", e.Record.Group);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a subtag target built from a real Galaxy area round-trips to native alarmmgr provider/group/tag-name fields.</summary>
|
||||
[Fact]
|
||||
public void ActiveFalseToTrue_AlarmMgrShape_EmitsNativeProviderGroupTagName()
|
||||
{
|
||||
@@ -59,6 +61,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
Assert.Equal("TestMachine_001.TestAlarm001", e.Record.TagName);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that when the alarm reference has no provider "!" separator, the whole reference is used as the tag name.</summary>
|
||||
[Fact]
|
||||
public void ActiveFalseToTrue_NoProviderBang_UsesWholeReferenceAsTagName()
|
||||
{
|
||||
@@ -77,6 +80,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
Assert.Equal(string.Empty, e.Record.Group);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an out-of-order un-ack arriving before the active-false clear still results in an AckRtn transition.</summary>
|
||||
[Fact]
|
||||
public void OutOfOrderAckThenClear_StillEmitsAckRtn()
|
||||
{
|
||||
@@ -91,6 +95,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
Assert.Equal(MxAlarmStateKind.AckRtn, e.Record.State);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that constructing the state machine with two targets sharing an active subtag address throws.</summary>
|
||||
[Fact]
|
||||
public void DuplicateActiveSubtag_Throws()
|
||||
{
|
||||
@@ -110,7 +115,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-028 regression: two watch-list entries sharing an
|
||||
/// Two watch-list entries sharing an
|
||||
/// <see cref="AlarmSubtagTarget.AlarmFullReference"/> (but using distinct
|
||||
/// subtag addresses) must throw at construction, symmetric with the
|
||||
/// duplicate-address guard, rather than silently overwriting the earlier
|
||||
@@ -134,6 +139,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
Assert.Throws<ArgumentException>(() => new SubtagAlarmStateMachine(new[] { first, second }));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that acking an active, unacked alarm emits an AckAlm transition from UnackAlm.</summary>
|
||||
[Fact]
|
||||
public void AckedTrueWhileActive_EmitsAck()
|
||||
{
|
||||
@@ -146,6 +152,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
Assert.Equal(MxAlarmStateKind.UnackAlm, e.PreviousState);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that clearing an active, unacked alarm emits an UnackRtn transition.</summary>
|
||||
[Fact]
|
||||
public void ActiveTrueToFalse_WhileUnacked_EmitsUnackRtn()
|
||||
{
|
||||
@@ -157,6 +164,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
Assert.Equal(MxAlarmStateKind.UnackRtn, e.Record.State);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that clearing an active, acked alarm emits an AckRtn transition.</summary>
|
||||
[Fact]
|
||||
public void ActiveTrueToFalse_WhileAcked_EmitsAckRtn()
|
||||
{
|
||||
@@ -169,6 +177,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
Assert.Equal(MxAlarmStateKind.AckRtn, e.Record.State);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that <c>SnapshotActive</c> reflects the current active and acked state.</summary>
|
||||
[Fact]
|
||||
public void Snapshot_ReflectsActiveAndAckedState()
|
||||
{
|
||||
@@ -180,6 +189,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
Assert.Equal(MxAlarmStateKind.AckAlm, snap.State);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a value change on an address not bound to any target produces no events.</summary>
|
||||
[Fact]
|
||||
public void UnknownAddress_NoEvents()
|
||||
{
|
||||
@@ -189,7 +199,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-033 regression: an ack arriving while the alarm is NOT
|
||||
/// An ack arriving while the alarm is NOT
|
||||
/// active must emit nothing and must NOT latch
|
||||
/// <c>AckedDuringEpisode</c> — otherwise a stale ack from a prior episode
|
||||
/// would mis-latch the next raise into a spurious ACK_RTN on clear. The
|
||||
@@ -214,7 +224,7 @@ public sealed class SubtagAlarmStateMachineTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-033 regression: a priority-subtag value change must flow
|
||||
/// A priority-subtag value change must flow
|
||||
/// through <c>CoerceInt</c> into the emitted record's
|
||||
/// <see cref="MxAlarmSnapshotRecord.Priority"/>. A non-numeric value must
|
||||
/// leave the prior priority unchanged (the CoerceInt fallback path).
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed class SyntheticAlarmGuidTests
|
||||
Assert.NotEqual(Guid.Empty, SyntheticAlarmGuid.ForReference(string.Empty));
|
||||
|
||||
/// <summary>
|
||||
/// Worker-027 regression: <see cref="SyntheticAlarmGuid.ForReference"/>
|
||||
/// Regression test: <see cref="SyntheticAlarmGuid.ForReference"/>
|
||||
/// must derive its GUID without routing through
|
||||
/// <see cref="System.Security.Cryptography"/>, because on net48
|
||||
/// <c>MD5.Create()</c> throws under the Windows FIPS-compliance policy.
|
||||
|
||||
@@ -125,7 +125,7 @@ public sealed class WnWrapAlarmConsumerXmlTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-001 regression: the consumer must own no internal
|
||||
/// The consumer must own no internal
|
||||
/// <see cref="Timer"/>. A thread-pool timer calling the
|
||||
/// apartment-threaded wnwrap COM object off its owning STA can
|
||||
/// deadlock on cross-apartment marshaling, so the timer field and
|
||||
@@ -144,7 +144,7 @@ public sealed class WnWrapAlarmConsumerXmlTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker-001 regression: no public constructor may accept a
|
||||
/// No public constructor may accept a
|
||||
/// poll-interval parameter. A non-zero poll interval was the only
|
||||
/// way to arm the off-STA timer; removing the parameter makes the
|
||||
/// footgun structurally unreachable.
|
||||
@@ -163,7 +163,7 @@ public sealed class WnWrapAlarmConsumerXmlTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-022: pins the "new alarm sighting" branch of
|
||||
/// Pins the "new alarm sighting" branch of
|
||||
/// <see cref="WnWrapAlarmConsumer.ComputeTransitions"/>. A GUID
|
||||
/// that appears in <c>next</c> but not in <c>previous</c> must
|
||||
/// produce exactly one transition with
|
||||
@@ -191,7 +191,7 @@ public sealed class WnWrapAlarmConsumerXmlTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-022: pins the "state unchanged" branch. A GUID
|
||||
/// Pins the "state unchanged" branch. A GUID
|
||||
/// present in both snapshots with identical
|
||||
/// <see cref="MxAlarmSnapshotRecord.State"/> must produce no
|
||||
/// transition — a regression that emits a transition every poll
|
||||
@@ -218,7 +218,7 @@ public sealed class WnWrapAlarmConsumerXmlTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-022: pins the "state changed" branch. A GUID
|
||||
/// Pins the "state changed" branch. A GUID
|
||||
/// present in both snapshots with a different state must produce
|
||||
/// one transition carrying the prior state so the proto layer
|
||||
/// can distinguish e.g. <c>UnackAlm</c>→<c>AckAlm</c>
|
||||
@@ -247,7 +247,7 @@ public sealed class WnWrapAlarmConsumerXmlTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-022: pins the "alarm cleared from the active set"
|
||||
/// Pins the "alarm cleared from the active set"
|
||||
/// branch. AVEVA drops cleared alarms from
|
||||
/// <c>GetXmlCurrentAlarms2</c>'s active set rather than emitting a
|
||||
/// transition record. A GUID present in
|
||||
@@ -273,7 +273,7 @@ public sealed class WnWrapAlarmConsumerXmlTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Worker.Tests-022: pins the multi-alarm fan-out. Multiple
|
||||
/// Pins the multi-alarm fan-out. Multiple
|
||||
/// simultaneous transitions (new + changed + unchanged + dropped)
|
||||
/// in one snapshot must produce exactly the changed and new
|
||||
/// entries — not the unchanged and not the dropped.
|
||||
|
||||
@@ -555,6 +555,13 @@ public sealed class AlarmSubtagLiveSmokeTests
|
||||
internal int ptY;
|
||||
}
|
||||
|
||||
/// <summary>Checks the thread message queue for a message, optionally removing it.</summary>
|
||||
/// <param name="lpMsg">Receives the retrieved message.</param>
|
||||
/// <param name="hWnd">Window handle to filter messages for, or <see cref="IntPtr.Zero"/> for any window owned by the calling thread.</param>
|
||||
/// <param name="wMsgFilterMin">Lowest message value to examine.</param>
|
||||
/// <param name="wMsgFilterMax">Highest message value to examine.</param>
|
||||
/// <param name="wRemoveMsg">Specifies how the message is handled (e.g. <see cref="PM_REMOVE"/>).</param>
|
||||
/// <returns><see langword="true"/> if a message is available; otherwise <see langword="false"/>.</returns>
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool PeekMessage(
|
||||
@@ -564,10 +571,16 @@ public sealed class AlarmSubtagLiveSmokeTests
|
||||
uint wMsgFilterMax,
|
||||
uint wRemoveMsg);
|
||||
|
||||
/// <summary>Translates virtual-key messages into character messages.</summary>
|
||||
/// <param name="lpMsg">The message to translate.</param>
|
||||
/// <returns><see langword="true"/> if the message was translated; otherwise <see langword="false"/>.</returns>
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool TranslateMessage(ref MSG lpMsg);
|
||||
|
||||
/// <summary>Dispatches a message to the window procedure.</summary>
|
||||
/// <param name="lpmsg">The message to dispatch.</param>
|
||||
/// <returns>The value returned by the window procedure.</returns>
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern IntPtr DispatchMessage(ref MSG lpmsg);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class WnWrapConsumerProbeTests
|
||||
private static readonly string SubscriptionExpression =
|
||||
$@"\\{MachineName}\Galaxy!DEV";
|
||||
|
||||
// XML query form — per WIN-911 / ArchestrA reference. NODE is the
|
||||
// XML query form — per ArchestrA reference. NODE is the
|
||||
// machine, PROVIDER is the literal "Galaxy", GROUP is the area.
|
||||
private static readonly string XmlAlarmQuery =
|
||||
"<QUERIES FROM_PRIORITY=\"1\" TO_PRIORITY=\"999\" ALARM_STATE=\"ALL\" DISPLAY_MODE=\"Summary\">" +
|
||||
|
||||
@@ -18,6 +18,7 @@ public sealed class StaCommandDispatcherTests
|
||||
/// <summary>
|
||||
/// Verifies commands execute on the STA thread in queue order.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_ExecutesCommandsOnStaInQueueOrder()
|
||||
{
|
||||
@@ -41,6 +42,7 @@ public sealed class StaCommandDispatcherTests
|
||||
/// <summary>
|
||||
/// Verifies executor exceptions are captured as HResult in the reply without exposing message details.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WhenExecutorThrows_ReturnsFailureReplyWithHResult()
|
||||
{
|
||||
@@ -64,6 +66,7 @@ public sealed class StaCommandDispatcherTests
|
||||
/// <summary>
|
||||
/// Verifies cancellation before execution prevents the command from running.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WhenCanceledBeforeExecution_ReturnsCanceledReplyWithoutExecuting()
|
||||
{
|
||||
@@ -96,6 +99,7 @@ public sealed class StaCommandDispatcherTests
|
||||
/// observed and ignored" from "cancel never checked"; it only proves the
|
||||
/// in-flight command is not aborted.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WhenCanceledWhileExecuting_DoesNotAbortInFlightCommand()
|
||||
{
|
||||
@@ -121,6 +125,7 @@ public sealed class StaCommandDispatcherTests
|
||||
/// <summary>
|
||||
/// Verifies shutdown rejects new dispatch attempts.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DispatchAsync_WhenShutdownRequested_RejectsNewCommands()
|
||||
{
|
||||
@@ -138,6 +143,7 @@ public sealed class StaCommandDispatcherTests
|
||||
/// <summary>
|
||||
/// Verifies shutdown allows the current command to complete but rejects queued commands.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RequestShutdown_RejectsQueuedCommandButLetsCurrentCommandFinish()
|
||||
{
|
||||
@@ -162,6 +168,7 @@ public sealed class StaCommandDispatcherTests
|
||||
/// <summary>
|
||||
/// Verifies heartbeat reports current command correlation ID and pending command count.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task PopulateHeartbeat_ReportsCurrentCorrelationAndPendingCount()
|
||||
{
|
||||
|
||||
@@ -37,6 +37,7 @@ public sealed class StaMessagePumpTests
|
||||
/// <summary>
|
||||
/// Verifies that WaitForWorkOrMessages returns promptly when the wake event is already signalled.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WaitForWorkOrMessages_WakeEventAlreadySignalled_ReturnsImmediately()
|
||||
{
|
||||
@@ -60,6 +61,7 @@ public sealed class StaMessagePumpTests
|
||||
/// <summary>
|
||||
/// Verifies that WaitForWorkOrMessages wakes when the wake event is signalled from another thread.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WaitForWorkOrMessages_WakeEventSignalledDuringWait_Returns()
|
||||
{
|
||||
@@ -89,6 +91,7 @@ public sealed class StaMessagePumpTests
|
||||
/// <summary>
|
||||
/// Verifies that WaitForWorkOrMessages returns on timeout when the wake event is never signalled.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WaitForWorkOrMessages_WakeEventNeverSignalled_ReturnsAfterTimeout()
|
||||
{
|
||||
@@ -113,6 +116,7 @@ public sealed class StaMessagePumpTests
|
||||
/// <summary>
|
||||
/// Verifies that a zero timeout (the TimeSpan.Zero conversion branch) returns without blocking.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WaitForWorkOrMessages_ZeroTimeout_ReturnsWithoutBlocking()
|
||||
{
|
||||
@@ -137,6 +141,7 @@ public sealed class StaMessagePumpTests
|
||||
/// <summary>
|
||||
/// Verifies that PumpPendingMessages returns zero when the STA thread message queue is empty.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task PumpPendingMessages_NoMessagesPosted_ReturnsZero()
|
||||
{
|
||||
@@ -155,6 +160,7 @@ public sealed class StaMessagePumpTests
|
||||
/// <summary>
|
||||
/// Verifies that PumpPendingMessages dispatches and counts messages posted to the STA thread.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task PumpPendingMessages_MessagesPostedToStaThread_ReturnsCountProcessed()
|
||||
{
|
||||
@@ -179,6 +185,7 @@ public sealed class StaMessagePumpTests
|
||||
/// <summary>
|
||||
/// Verifies that WaitForWorkOrMessages returns once a Windows message is posted to the STA thread.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WaitForWorkOrMessages_WindowsMessagePosted_ReturnsForInputAvailable()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace ZB.MOM.WW.MxGateway.Worker.Tests.Sta;
|
||||
public sealed class StaRuntimeTests
|
||||
{
|
||||
/// <summary>Verifies that InvokeAsync executes commands on the STA thread.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InvokeAsync_ExecutesCommandOnStaThread()
|
||||
{
|
||||
@@ -35,6 +36,7 @@ public sealed class StaRuntimeTests
|
||||
/// correct dispatch past an arbitrary millisecond budget, which would be a
|
||||
/// false failure.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InvokeAsync_WakesIdlePumpForQueuedCommand()
|
||||
{
|
||||
@@ -84,6 +86,7 @@ public sealed class StaRuntimeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that InvokeAsync faults the returned task when a command raises an exception without stopping the runtime.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InvokeAsync_CommandException_FaultsReturnedTaskWithoutStoppingRuntime()
|
||||
{
|
||||
@@ -101,14 +104,15 @@ public sealed class StaRuntimeTests
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that InvokeAsync returns a faulted task when called after
|
||||
/// Shutdown. Worker-016 introduced <see cref="StaRuntimeShutdownException"/>
|
||||
/// (a dedicated subtype of <see cref="InvalidOperationException"/>) so
|
||||
/// Shutdown. <see cref="StaRuntimeShutdownException"/> is
|
||||
/// a dedicated subtype of <see cref="InvalidOperationException"/> so
|
||||
/// callers — notably <c>MxAccessStaSession.RunAlarmPollLoopAsync</c> —
|
||||
/// can distinguish the graceful shutdown signal from a vanilla
|
||||
/// <see cref="InvalidOperationException"/> such as an STA-affinity
|
||||
/// assertion. The test pins the exact type so a regression that
|
||||
/// reverts to a plain <c>InvalidOperationException</c> fails here.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InvokeAsync_AfterShutdown_ReturnsFaultedTask()
|
||||
{
|
||||
@@ -164,14 +168,14 @@ public sealed class StaRuntimeTests
|
||||
/// <summary>The thread ID where Uninitialize was called.</summary>
|
||||
public int? UninitializeThreadId { get; private set; }
|
||||
|
||||
/// <summary>Initializes the COM apartment and records the calling thread.</summary>
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
InitializeCount++;
|
||||
InitializeThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
}
|
||||
|
||||
/// <summary>Uninitializes the COM apartment and records the calling thread.</summary>
|
||||
/// <inheritdoc />
|
||||
public void Uninitialize()
|
||||
{
|
||||
UninitializeCount++;
|
||||
|
||||
@@ -46,11 +46,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
|
||||
/// <summary>Gets a value indicating whether Dispose was called.</summary>
|
||||
public bool Disposed { get; private set; }
|
||||
|
||||
/// <summary>Starts the worker session with the given session ID and process ID.</summary>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="workerProcessId">The worker process ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Worker ready response.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<WorkerReady> StartAsync(
|
||||
string sessionId,
|
||||
int workerProcessId,
|
||||
@@ -65,9 +61,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Dispatches a command to the STA thread.</summary>
|
||||
/// <param name="command">The command to dispatch.</param>
|
||||
/// <returns>The command reply.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<MxCommandReply> DispatchAsync(StaCommand command)
|
||||
{
|
||||
return Task.Run(
|
||||
@@ -112,8 +106,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Captures current heartbeat snapshot.</summary>
|
||||
/// <returns>Current runtime heartbeat snapshot.</returns>
|
||||
/// <inheritdoc />
|
||||
public WorkerRuntimeHeartbeatSnapshot CaptureHeartbeat()
|
||||
{
|
||||
lock (gate)
|
||||
@@ -132,9 +125,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
|
||||
/// </summary>
|
||||
public uint? SuppressDrainForBatchSize { get; set; }
|
||||
|
||||
/// <summary>Drains queued events up to the specified limit.</summary>
|
||||
/// <param name="maxEvents">Maximum events to drain; 0 drains all.</param>
|
||||
/// <returns>The drained events.</returns>
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<WorkerEvent> DrainEvents(uint maxEvents)
|
||||
{
|
||||
if (SuppressDrainForBatchSize is uint suppressed && maxEvents == suppressed)
|
||||
@@ -157,8 +148,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Drains a pending fault if any.</summary>
|
||||
/// <returns>Pending fault or null.</returns>
|
||||
/// <inheritdoc />
|
||||
public WorkerFault? DrainFault()
|
||||
{
|
||||
return null;
|
||||
@@ -168,7 +158,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
|
||||
/// Gets a snapshot of every correlation id passed to
|
||||
/// <see cref="CancelCommand"/>. Recording lets the IPC tests
|
||||
/// assert that a <c>WorkerCancel</c> envelope dispatched on the
|
||||
/// gateway side reaches the runtime session — see Worker.Tests-017.
|
||||
/// gateway side reaches the runtime session.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> CancelledCorrelationIds
|
||||
{
|
||||
@@ -189,7 +179,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
|
||||
/// command), matching the previous test-double behaviour. Mutated
|
||||
/// and read under <c>lock(gate)</c> to match the locking convention
|
||||
/// the rest of this fake uses for <c>cancelledCorrelationIds</c>,
|
||||
/// <c>snapshot</c>, and <c>events</c> (Worker.Tests-027).
|
||||
/// <c>snapshot</c>, and <c>events</c>.
|
||||
/// </summary>
|
||||
public bool CancelCommandReturnValue
|
||||
{
|
||||
@@ -210,9 +200,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Cancels command by correlation ID.</summary>
|
||||
/// <param name="correlationId">The command correlation ID.</param>
|
||||
/// <returns>True if cancelled; false otherwise.</returns>
|
||||
/// <inheritdoc />
|
||||
public bool CancelCommand(string correlationId)
|
||||
{
|
||||
lock (gate)
|
||||
@@ -222,16 +210,13 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Requests graceful shutdown.</summary>
|
||||
/// <inheritdoc />
|
||||
public void RequestShutdown()
|
||||
{
|
||||
releaseDispatch.Set();
|
||||
}
|
||||
|
||||
/// <summary>Shuts down gracefully within the specified timeout.</summary>
|
||||
/// <param name="timeout">Shutdown timeout period.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Shutdown result.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<MxAccessShutdownResult> ShutdownGracefullyAsync(
|
||||
TimeSpan timeout,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -272,7 +257,7 @@ internal sealed class FakeRuntimeSession : IWorkerRuntimeSession
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Disposes resources.</summary>
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Disposed = true;
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace ZB.MOM.WW.MxGateway.Worker.Tests.TestSupport;
|
||||
/// otherwise report as <c>Passed</c>). Mirrors
|
||||
/// <c>ZB.MOM.WW.MxGateway.IntegrationTests.LiveMxAccessFactAttribute</c>; both
|
||||
/// copies bind to the same <c>GatewayContractInfo</c> constant so the
|
||||
/// env-var name has a single literal source of truth (Worker.Tests-025).
|
||||
/// env-var name has a single literal source of truth.
|
||||
/// </summary>
|
||||
public sealed class LiveMxAccessFactAttribute : FactAttribute
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace ZB.MOM.WW.MxGateway.Worker.Tests.TestSupport;
|
||||
/// <see cref="MxAccessSession.CreateForTesting"/> but do not exercise any
|
||||
/// MXAccess COM call. Replaces the per-file <c>NullMxAccessServer</c> copy
|
||||
/// that previously lived inside <c>AlarmCommandExecutorTests</c> and was
|
||||
/// constructed via reflection — see Worker.Tests-016 for the rationale.
|
||||
/// constructed via reflection.
|
||||
/// </summary>
|
||||
internal sealed class NoopMxAccessServer : IMxAccessServer
|
||||
{
|
||||
@@ -96,7 +96,7 @@ internal sealed class NoopMxAccessServer : IMxAccessServer
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Previously duplicated as a nested class in <c>MxAccessCommandExecutorTests.FakeMxAccessComObject</c>.
|
||||
/// Consolidated here per Worker.Tests-034 so a future field change to the real COM struct only
|
||||
/// Consolidated here so a future field change to the real COM struct only
|
||||
/// requires updating one place.
|
||||
/// </remarks>
|
||||
internal sealed class FakeMxStatus
|
||||
|
||||
@@ -12,6 +12,7 @@ internal static class WorkerFrameTestHelpers
|
||||
{
|
||||
/// <summary>Builds a length-prefixed frame from a protobuf message.</summary>
|
||||
/// <param name="message">Message to serialize into the frame payload.</param>
|
||||
/// <returns>The length-prefixed frame bytes.</returns>
|
||||
public static byte[] CreateFrame(IMessage message)
|
||||
{
|
||||
return CreateFrame(message.ToByteArray());
|
||||
@@ -19,6 +20,7 @@ internal static class WorkerFrameTestHelpers
|
||||
|
||||
/// <summary>Builds a length-prefixed frame from a raw payload.</summary>
|
||||
/// <param name="payload">Payload bytes to wrap in a frame.</param>
|
||||
/// <returns>The length-prefixed frame bytes.</returns>
|
||||
public static byte[] CreateFrame(byte[] payload)
|
||||
{
|
||||
byte[] frame = new byte[sizeof(uint) + payload.Length];
|
||||
|
||||
Reference in New Issue
Block a user