fix(driver-historian-wonderware-client): resolve Medium code-review finding (Driver.Historian.Wonderware.Client-009)
Add six previously-missing edge-case tests to WonderwareHistorianClientTests: (2) WriteBatchAsync transport-drop catch path returns RetryPlease for all events; (3) InvokeAsync second-attempt-also-fails propagates the exception; (4) stalled sidecar fires OperationCanceledException within CallTimeout; (5) HistoryAggregateType.Total throws NotSupportedException via ReadProcessedAsync; (6) sidecar wrong-MessageKind reply throws InvalidDataException. Extend FakeSidecarServer with DisconnectBeforeReply, ReplyWithWrongKind, and StallAfterRequest test knobs to support these scenarios. Add ContractsWireParityTests.cs (11 tests) to pin the MessagePack byte layout, round-trip correctness, MessageKind enum values, and Framing constants — catching silent [Key] index drift between the client and sidecar mirror copies without requiring a cross-TFM (net10 vs net48) project reference. Test count grew from 11 to 27; all 27 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,26 @@ internal sealed class FakeSidecarServer : IAsyncDisposable
|
||||
/// <summary>Force-disconnect the next accepted client mid-call to exercise reconnect.</summary>
|
||||
public bool DisconnectAfterHandshake { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Drop the connection after the handshake but before replying to any non-Hello request.
|
||||
/// Armed for every connection until reset. Used to exercise the WriteBatchAsync catch
|
||||
/// path and the second-attempt-also-fails propagation path.
|
||||
/// </summary>
|
||||
public bool DisconnectBeforeReply { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reply to the first non-Hello request with this kind instead of the expected kind,
|
||||
/// to exercise <see cref="System.IO.InvalidDataException"/> detection in ExchangeAsync.
|
||||
/// Reset to null after the first mis-routed reply.
|
||||
/// </summary>
|
||||
public MessageKind? ReplyWithWrongKind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stall indefinitely after receiving a request before sending any reply, so the client's
|
||||
/// call-timeout token fires. Used to test the CallTimeout path.
|
||||
/// </summary>
|
||||
public bool StallAfterRequest { get; set; }
|
||||
|
||||
public FakeSidecarServer(string pipeName, string expectedSecret)
|
||||
{
|
||||
_pipeName = pipeName;
|
||||
@@ -83,6 +103,32 @@ internal sealed class FakeSidecarServer : IAsyncDisposable
|
||||
var frame = await reader.ReadFrameAsync(ct).ConfigureAwait(false);
|
||||
if (frame is null) break;
|
||||
|
||||
// Drop before sending any reply — lets the client fall into its catch /
|
||||
// retry path or propagate on second failure.
|
||||
if (DisconnectBeforeReply)
|
||||
{
|
||||
pipe.Disconnect();
|
||||
break;
|
||||
}
|
||||
|
||||
// Stall indefinitely to let the client's call-timeout token fire.
|
||||
if (StallAfterRequest)
|
||||
{
|
||||
await Task.Delay(Timeout.Infinite, ct).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Optionally send a deliberately wrong kind back to exercise
|
||||
// InvalidDataException detection in the client's ExchangeAsync.
|
||||
if (ReplyWithWrongKind.HasValue)
|
||||
{
|
||||
var wrongKind = ReplyWithWrongKind.Value;
|
||||
ReplyWithWrongKind = null; // arm once
|
||||
// Send an empty body with the wrong kind so the client can parse it.
|
||||
await writer.WriteAsync(wrongKind, new ReadRawReply { Success = false }, ct);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (frame.Value.Kind)
|
||||
{
|
||||
case MessageKind.ReadRawRequest:
|
||||
|
||||
Reference in New Issue
Block a user