Add XML documentation across gateway, worker, and .NET client

This commit is contained in:
Joseph Doherty
2026-04-30 11:49:58 -04:00
parent 4731ab535c
commit eed1e88a37
269 changed files with 4555 additions and 13 deletions
@@ -3,30 +3,71 @@ using MxGateway.Contracts.Proto.Galaxy;
namespace MxGateway.Client.Tests;
/// <summary>
/// Fake Galaxy Repository client transport for testing.
/// </summary>
internal sealed class FakeGalaxyRepositoryTransport(MxGatewayClientOptions options) : IGalaxyRepositoryClientTransport
{
/// <summary>
/// Gets the gateway client options.
/// </summary>
public MxGatewayClientOptions Options { get; } = options;
/// <summary>
/// Gets the raw gRPC client; always null for the fake.
/// </summary>
public GalaxyRepository.GalaxyRepositoryClient? RawClient => null;
/// <summary>
/// Gets the list of TestConnection RPC calls made by the client.
/// </summary>
public List<(TestConnectionRequest Request, CallOptions CallOptions)> TestConnectionCalls { get; } = [];
/// <summary>
/// Gets the list of GetLastDeployTime RPC calls made by the client.
/// </summary>
public List<(GetLastDeployTimeRequest Request, CallOptions CallOptions)> GetLastDeployTimeCalls { get; } = [];
/// <summary>
/// Gets the list of DiscoverHierarchy RPC calls made by the client.
/// </summary>
public List<(DiscoverHierarchyRequest Request, CallOptions CallOptions)> DiscoverHierarchyCalls { get; } = [];
/// <summary>
/// Gets or sets the reply to return from TestConnection; defaults to successful response.
/// </summary>
public TestConnectionReply TestConnectionReply { get; set; } = new() { Ok = true };
/// <summary>
/// Gets or sets the reply to return from GetLastDeployTime; defaults to no deploy time present.
/// </summary>
public GetLastDeployTimeReply GetLastDeployTimeReply { get; set; } = new() { Present = false };
/// <summary>
/// Gets or sets the reply to return from DiscoverHierarchy; defaults to empty response.
/// </summary>
public DiscoverHierarchyReply DiscoverHierarchyReply { get; set; } = new();
/// <summary>
/// Gets the queue of exceptions to throw from TestConnection; dequeued in FIFO order.
/// </summary>
public Queue<Exception> TestConnectionExceptions { get; } = new();
/// <summary>
/// Gets the queue of exceptions to throw from GetLastDeployTime; dequeued in FIFO order.
/// </summary>
public Queue<Exception> GetLastDeployTimeExceptions { get; } = new();
/// <summary>
/// Gets the queue of exceptions to throw from DiscoverHierarchy; dequeued in FIFO order.
/// </summary>
public Queue<Exception> DiscoverHierarchyExceptions { get; } = new();
/// <summary>
/// Records the request and either throws a queued exception or returns the configured reply.
/// </summary>
/// <param name="request">The TestConnectionRequest to process.</param>
/// <param name="callOptions">Call options specifying RPC behavior.</param>
public Task<TestConnectionReply> TestConnectionAsync(
TestConnectionRequest request,
CallOptions callOptions)
@@ -40,6 +81,11 @@ internal sealed class FakeGalaxyRepositoryTransport(MxGatewayClientOptions optio
return Task.FromResult(TestConnectionReply);
}
/// <summary>
/// Records the request and either throws a queued exception or returns the configured reply.
/// </summary>
/// <param name="request">The GetLastDeployTimeRequest to process.</param>
/// <param name="callOptions">Call options specifying RPC behavior.</param>
public Task<GetLastDeployTimeReply> GetLastDeployTimeAsync(
GetLastDeployTimeRequest request,
CallOptions callOptions)
@@ -53,6 +99,11 @@ internal sealed class FakeGalaxyRepositoryTransport(MxGatewayClientOptions optio
return Task.FromResult(GetLastDeployTimeReply);
}
/// <summary>
/// Records the request and either throws a queued exception or returns the configured reply.
/// </summary>
/// <param name="request">The DiscoverHierarchyRequest to process.</param>
/// <param name="callOptions">Call options specifying RPC behavior.</param>
public Task<DiscoverHierarchyReply> DiscoverHierarchyAsync(
DiscoverHierarchyRequest request,
CallOptions callOptions)
@@ -66,10 +117,19 @@ internal sealed class FakeGalaxyRepositoryTransport(MxGatewayClientOptions optio
return Task.FromResult(DiscoverHierarchyReply);
}
/// <summary>
/// Gets the list of WatchDeployEvents RPC calls made by the client.
/// </summary>
public List<(WatchDeployEventsRequest Request, CallOptions CallOptions)> WatchDeployEventsCalls { get; } = [];
/// <summary>
/// Gets or sets the list of events to stream from WatchDeployEvents.
/// </summary>
public List<DeployEvent> WatchDeployEvents { get; } = [];
/// <summary>
/// Gets or sets the exception to throw from WatchDeployEvents, if any.
/// </summary>
public Exception? WatchDeployEventsException { get; set; }
/// <summary>
@@ -78,6 +138,11 @@ internal sealed class FakeGalaxyRepositoryTransport(MxGatewayClientOptions optio
/// </summary>
public Func<CancellationToken, Task>? WatchDeployEventsBeforeYield { get; set; }
/// <summary>
/// Records the request and streams events, checking for queued exceptions and calling WatchDeployEventsBeforeYield before each event.
/// </summary>
/// <param name="request">The WatchDeployEventsRequest to process.</param>
/// <param name="callOptions">Call options specifying RPC behavior.</param>
public async IAsyncEnumerable<DeployEvent> WatchDeployEventsAsync(
WatchDeployEventsRequest request,
CallOptions callOptions)
@@ -3,23 +3,47 @@ using MxGateway.Contracts.Proto;
namespace MxGateway.Client.Tests;
/// <summary>
/// Fake implementation of IMxGatewayClientTransport for testing.
/// </summary>
internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMxGatewayClientTransport
{
private readonly Queue<MxCommandReply> _invokeReplies = new();
private readonly List<MxEvent> _events = [];
/// <summary>
/// Gets the gateway client options.
/// </summary>
public MxGatewayClientOptions Options { get; } = options;
/// <summary>
/// Gets null, since this is a test fake without a real gRPC client.
/// </summary>
public MxAccessGateway.MxAccessGatewayClient? RawClient => null;
/// <summary>
/// Gets the list of captured OpenSessionAsync calls.
/// </summary>
public List<(OpenSessionRequest Request, CallOptions CallOptions)> OpenSessionCalls { get; } = [];
/// <summary>
/// Gets the list of captured CloseSessionAsync calls.
/// </summary>
public List<(CloseSessionRequest Request, CallOptions CallOptions)> CloseSessionCalls { get; } = [];
/// <summary>
/// Gets the list of captured InvokeAsync calls.
/// </summary>
public List<(MxCommandRequest Request, CallOptions CallOptions)> InvokeCalls { get; } = [];
/// <summary>
/// Gets the list of captured StreamEventsAsync calls.
/// </summary>
public List<(StreamEventsRequest Request, CallOptions CallOptions)> StreamEventsCalls { get; } = [];
/// <summary>
/// Gets or sets the reply to return from OpenSessionAsync.
/// </summary>
public OpenSessionReply OpenSessionReply { get; set; } = new()
{
SessionId = "session-fixture",
@@ -29,6 +53,9 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
};
/// <summary>
/// Gets or sets the reply to return from CloseSessionAsync.
/// </summary>
public CloseSessionReply CloseSessionReply { get; set; } = new()
{
SessionId = "session-fixture",
@@ -36,12 +63,26 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
};
/// <summary>
/// Gets the queue of exceptions to throw from OpenSessionAsync.
/// </summary>
public Queue<Exception> OpenSessionExceptions { get; } = new();
/// <summary>
/// Gets the queue of exceptions to throw from CloseSessionAsync.
/// </summary>
public Queue<Exception> CloseSessionExceptions { get; } = new();
/// <summary>
/// Gets the queue of exceptions to throw from InvokeAsync.
/// </summary>
public Queue<Exception> InvokeExceptions { get; } = new();
/// <summary>
/// Verifies that the OpenSessionAsync call is recorded and returns the configured reply.
/// </summary>
/// <param name="request">The OpenSessionRequest to process.</param>
/// <param name="callOptions">Call options specifying RPC behavior.</param>
public Task<OpenSessionReply> OpenSessionAsync(
OpenSessionRequest request,
CallOptions callOptions)
@@ -55,6 +96,11 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx
return Task.FromResult(OpenSessionReply);
}
/// <summary>
/// Verifies that the CloseSessionAsync call is recorded and returns the configured reply.
/// </summary>
/// <param name="request">The CloseSessionRequest to process.</param>
/// <param name="callOptions">Call options specifying RPC behavior.</param>
public Task<CloseSessionReply> CloseSessionAsync(
CloseSessionRequest request,
CallOptions callOptions)
@@ -68,6 +114,11 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx
return Task.FromResult(CloseSessionReply);
}
/// <summary>
/// Verifies that the InvokeAsync call is recorded and returns the next enqueued reply.
/// </summary>
/// <param name="request">The MxCommandRequest to process.</param>
/// <param name="callOptions">Call options specifying RPC behavior.</param>
public Task<MxCommandReply> InvokeAsync(
MxCommandRequest request,
CallOptions callOptions)
@@ -81,6 +132,11 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx
return Task.FromResult(_invokeReplies.Dequeue());
}
/// <summary>
/// Verifies that the StreamEventsAsync call is recorded and yields all enqueued events.
/// </summary>
/// <param name="request">The StreamEventsRequest to process.</param>
/// <param name="callOptions">Call options specifying RPC behavior.</param>
public async IAsyncEnumerable<MxEvent> StreamEventsAsync(
StreamEventsRequest request,
CallOptions callOptions)
@@ -95,11 +151,19 @@ internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMx
}
}
/// <summary>
/// Enqueues a reply to be returned from the next InvokeAsync call.
/// </summary>
/// <param name="reply">The reply to enqueue.</param>
public void AddInvokeReply(MxCommandReply reply)
{
_invokeReplies.Enqueue(reply);
}
/// <summary>
/// Enqueues an event to be yielded from StreamEventsAsync.
/// </summary>
/// <param name="gatewayEvent">The event to enqueue.</param>
public void AddEvent(MxEvent gatewayEvent)
{
_events.Add(gatewayEvent);
@@ -6,6 +6,9 @@ namespace MxGateway.Client.Tests;
public sealed class GalaxyRepositoryClientTests
{
/// <summary>
/// Verifies that TestConnectionAsync attaches the API key in request metadata and returns the Ok flag.
/// </summary>
[Fact]
public async Task TestConnectionAsync_AttachesApiKeyMetadataAndReturnsOkFlag()
{
@@ -21,6 +24,9 @@ public sealed class GalaxyRepositoryClientTests
Assert.Equal("Bearer test-api-key", call.CallOptions.Headers?.GetValue("authorization"));
}
/// <summary>
/// Verifies that TestConnectionAsync returns false when the server reports NotOk.
/// </summary>
[Fact]
public async Task TestConnectionAsync_ReturnsFalseWhenServerReportsNotOk()
{
@@ -33,6 +39,9 @@ public sealed class GalaxyRepositoryClientTests
Assert.False(ok);
}
/// <summary>
/// Verifies that GetLastDeployTimeAsync returns null when the server reports not present.
/// </summary>
[Fact]
public async Task GetLastDeployTimeAsync_ReturnsNullWhenNotPresent()
{
@@ -46,6 +55,9 @@ public sealed class GalaxyRepositoryClientTests
Assert.Single(transport.GetLastDeployTimeCalls);
}
/// <summary>
/// Verifies that GetLastDeployTimeAsync returns the timestamp when the server reports it present.
/// </summary>
[Fact]
public async Task GetLastDeployTimeAsync_ReturnsTimestampWhenPresent()
{
@@ -64,6 +76,9 @@ public sealed class GalaxyRepositoryClientTests
Assert.Equal(expected, deployTime!.Value);
}
/// <summary>
/// Verifies that DiscoverHierarchyAsync returns the objects from the server reply.
/// </summary>
[Fact]
public async Task DiscoverHierarchyAsync_ReturnsObjectsFromReply()
{
@@ -104,6 +119,9 @@ public sealed class GalaxyRepositoryClientTests
Assert.Equal("DelmiaReceiver_001.DownloadPath", attribute.FullTagReference);
}
/// <summary>
/// Verifies that DiscoverHierarchyAsync propagates cancellation tokens to the transport.
/// </summary>
[Fact]
public async Task DiscoverHierarchyAsync_PropagatesCancellationToTransport()
{
@@ -121,6 +139,9 @@ public sealed class GalaxyRepositoryClientTests
Assert.False(call.CallOptions.CancellationToken.IsCancellationRequested);
}
/// <summary>
/// Verifies that TestConnectionAsync retries on transient gRPC failures.
/// </summary>
[Fact]
public async Task TestConnectionAsync_RetriesOnTransientGrpcFailure()
{
@@ -135,6 +156,9 @@ public sealed class GalaxyRepositoryClientTests
Assert.Equal(2, transport.TestConnectionCalls.Count);
}
/// <summary>
/// Verifies that DiscoverHierarchyAsync retries on transient gRPC failures.
/// </summary>
[Fact]
public async Task DiscoverHierarchyAsync_RetriesOnTransientGrpcFailure()
{
@@ -148,6 +172,9 @@ public sealed class GalaxyRepositoryClientTests
Assert.Equal(2, transport.DiscoverHierarchyCalls.Count);
}
/// <summary>
/// Verifies that WatchDeployEventsAsync delivers the bootstrap event.
/// </summary>
[Fact]
public async Task WatchDeployEventsAsync_DeliversBootstrapEvent()
{
@@ -181,6 +208,9 @@ public sealed class GalaxyRepositoryClientTests
Assert.Null(call.Request.LastSeenDeployTime);
}
/// <summary>
/// Verifies that WatchDeployEventsAsync delivers multiple events in order.
/// </summary>
[Fact]
public async Task WatchDeployEventsAsync_DeliversMultipleEventsInOrder()
{
@@ -216,6 +246,9 @@ public sealed class GalaxyRepositoryClientTests
Assert.Equal(t0, call.Request.LastSeenDeployTime!.ToDateTime());
}
/// <summary>
/// Verifies that WatchDeployEventsAsync stops iteration cleanly when cancelled.
/// </summary>
[Fact]
public async Task WatchDeployEventsAsync_CancellationStopsIterationCleanly()
{
@@ -257,6 +290,9 @@ public sealed class GalaxyRepositoryClientTests
Assert.Equal(1ul, received[0].Sequence);
}
/// <summary>
/// Verifies that WatchDeployEventsAsync throws ObjectDisposedException after the client is disposed.
/// </summary>
[Fact]
public async Task WatchDeployEventsAsync_ThrowsAfterDisposal()
{
@@ -269,6 +305,9 @@ public sealed class GalaxyRepositoryClientTests
client.WatchDeployEventsAsync());
}
/// <summary>
/// Verifies that TestConnectionAsync throws ObjectDisposedException after the client is disposed.
/// </summary>
[Fact]
public async Task TestConnectionAsync_ThrowsAfterDisposal()
{
@@ -6,6 +6,7 @@ namespace MxGateway.Client.Tests;
public sealed class MxCommandReplyExtensionsTests
{
/// <summary>Verifies that successful replies pass both protocol and MxAccess success checks.</summary>
[Fact]
public void EnsureSuccess_WithRegisterFixture_ReturnsReply()
{
@@ -15,6 +16,7 @@ public sealed class MxCommandReplyExtensionsTests
Assert.Same(reply, reply.EnsureMxAccessSuccess());
}
/// <summary>Verifies that MxAccess failures throw with preserved HResult and status details.</summary>
[Fact]
public void EnsureMxAccessSuccess_WithFailureFixture_PreservesHResultAndStatuses()
{
@@ -30,6 +32,7 @@ public sealed class MxCommandReplyExtensionsTests
Assert.Contains("0x80040200", exception.Message);
}
/// <summary>Verifies that session-not-found protocol failures throw the correct gateway exception.</summary>
[Fact]
public void EnsureProtocolSuccess_WithSessionFailure_ThrowsSessionException()
{
@@ -5,8 +5,10 @@ using MxGateway.Contracts.Proto.Galaxy;
namespace MxGateway.Client.Tests;
/// <summary>Tests for the CLI command interface.</summary>
public sealed class MxGatewayClientCliTests
{
/// <summary>Verifies that the version command prints compiled protocol versions.</summary>
[Fact]
public void Run_Version_PrintsCompiledProtocolVersions()
{
@@ -21,6 +23,7 @@ public sealed class MxGatewayClientCliTests
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that the version command with --json flag prints JSON protocol versions.</summary>
[Fact]
public async Task RunAsync_VersionJson_PrintsJsonProtocolVersions()
{
@@ -34,6 +37,7 @@ public sealed class MxGatewayClientCliTests
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that the write command builds a write request and prints JSON reply.</summary>
[Fact]
public async Task RunAsync_Write_BuildsWriteCommandAndPrintsJsonReply()
{
@@ -78,6 +82,7 @@ public sealed class MxGatewayClientCliTests
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that error output redacts sensitive API key values.</summary>
[Fact]
public async Task RunAsync_ErrorOutput_RedactsApiKey()
{
@@ -101,6 +106,7 @@ public sealed class MxGatewayClientCliTests
Assert.Contains("[redacted]", error.ToString());
}
/// <summary>Verifies that stream-events with max-events limit stops output in non-JSON format.</summary>
[Fact]
public async Task RunAsync_StreamEvents_WithMaxEventsStopsNonJsonOutput()
{
@@ -142,6 +148,7 @@ public sealed class MxGatewayClientCliTests
}
/// <summary>Verifies that smoke command closes opened session when a command fails.</summary>
[Fact]
public async Task RunAsync_Smoke_WhenCommandFails_ClosesOpenedSession()
{
@@ -172,6 +179,7 @@ public sealed class MxGatewayClientCliTests
Assert.Equal("session-fixture", closeRequest.SessionId);
}
/// <summary>Verifies that galaxy-test-connection command prints JSON reply.</summary>
[Fact]
public async Task RunAsync_GalaxyTestConnection_PrintsJsonReply()
{
@@ -201,6 +209,7 @@ public sealed class MxGatewayClientCliTests
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that galaxy-discover command prints hierarchy summary.</summary>
[Fact]
public async Task RunAsync_GalaxyDiscover_PrintsHierarchySummary()
{
@@ -250,6 +259,7 @@ public sealed class MxGatewayClientCliTests
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that galaxy-watch command prints text output for deploy events.</summary>
[Fact]
public async Task RunAsync_GalaxyWatch_PrintsTextOutputForEvents()
{
@@ -303,6 +313,7 @@ public sealed class MxGatewayClientCliTests
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that galaxy-watch with --json emits one JSON object per event.</summary>
[Fact]
public async Task RunAsync_GalaxyWatch_JsonEmitsOneObjectPerEvent()
{
@@ -337,23 +348,31 @@ public sealed class MxGatewayClientCliTests
Assert.Contains("\"objectCount\": 99", text);
}
/// <summary>Fake CLI client for testing.</summary>
private sealed class FakeCliClient : IMxGatewayCliClient
{
/// <summary>Queue of invoke replies to return.</summary>
public Queue<MxCommandReply> InvokeReplies { get; } = new();
/// <summary>List of received invoke requests.</summary>
public List<MxCommandRequest> InvokeRequests { get; } = [];
/// <summary>List of received close session requests.</summary>
public List<CloseSessionRequest> CloseSessionRequests { get; } = [];
/// <summary>List of events to yield when streaming.</summary>
public List<MxEvent> Events { get; } = [];
/// <summary>Exception to throw on invoke, if any.</summary>
public Exception? InvokeFailure { get; init; }
/// <inheritdoc />
public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;
}
/// <inheritdoc />
public Task<OpenSessionReply> OpenSessionAsync(
OpenSessionRequest request,
CancellationToken cancellationToken)
@@ -367,6 +386,7 @@ public sealed class MxGatewayClientCliTests
});
}
/// <inheritdoc />
public Task<CloseSessionReply> CloseSessionAsync(
CloseSessionRequest request,
CancellationToken cancellationToken)
@@ -380,6 +400,7 @@ public sealed class MxGatewayClientCliTests
});
}
/// <inheritdoc />
public Task<MxCommandReply> InvokeAsync(
MxCommandRequest request,
CancellationToken cancellationToken)
@@ -393,6 +414,7 @@ public sealed class MxGatewayClientCliTests
return Task.FromResult(InvokeReplies.Dequeue());
}
/// <inheritdoc />
public async IAsyncEnumerable<MxEvent> StreamEventsAsync(
StreamEventsRequest request,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
@@ -405,18 +427,25 @@ public sealed class MxGatewayClientCliTests
}
}
/// <summary>Galaxy test connection reply to return.</summary>
public TestConnectionReply GalaxyTestConnectionReply { get; set; } = new() { Ok = true };
/// <summary>Galaxy get last deploy time reply to return.</summary>
public GetLastDeployTimeReply GalaxyGetLastDeployTimeReply { get; set; } = new() { Present = false };
/// <summary>Galaxy discover hierarchy reply to return.</summary>
public DiscoverHierarchyReply GalaxyDiscoverHierarchyReply { get; set; } = new();
/// <summary>List of received galaxy test connection requests.</summary>
public List<TestConnectionRequest> GalaxyTestConnectionRequests { get; } = [];
/// <summary>List of received galaxy get last deploy time requests.</summary>
public List<GetLastDeployTimeRequest> GalaxyGetLastDeployTimeRequests { get; } = [];
/// <summary>List of received galaxy discover hierarchy requests.</summary>
public List<DiscoverHierarchyRequest> GalaxyDiscoverHierarchyRequests { get; } = [];
/// <inheritdoc />
public Task<TestConnectionReply> GalaxyTestConnectionAsync(
TestConnectionRequest request,
CancellationToken cancellationToken)
@@ -425,6 +454,7 @@ public sealed class MxGatewayClientCliTests
return Task.FromResult(GalaxyTestConnectionReply);
}
/// <inheritdoc />
public Task<GetLastDeployTimeReply> GalaxyGetLastDeployTimeAsync(
GetLastDeployTimeRequest request,
CancellationToken cancellationToken)
@@ -433,6 +463,7 @@ public sealed class MxGatewayClientCliTests
return Task.FromResult(GalaxyGetLastDeployTimeReply);
}
/// <inheritdoc />
public Task<DiscoverHierarchyReply> GalaxyDiscoverHierarchyAsync(
DiscoverHierarchyRequest request,
CancellationToken cancellationToken)
@@ -441,10 +472,13 @@ public sealed class MxGatewayClientCliTests
return Task.FromResult(GalaxyDiscoverHierarchyReply);
}
/// <summary>List of received galaxy watch deploy events requests.</summary>
public List<WatchDeployEventsRequest> GalaxyWatchDeployEventsRequests { get; } = [];
/// <summary>List of deploy events to yield when watching.</summary>
public List<DeployEvent> GalaxyDeployEvents { get; } = [];
/// <inheritdoc />
public async IAsyncEnumerable<DeployEvent> GalaxyWatchDeployEventsAsync(
WatchDeployEventsRequest request,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
@@ -4,6 +4,7 @@ namespace MxGateway.Client.Tests;
public sealed class MxGatewayClientContractInfoTests
{
/// <summary>Verifies that the client's gateway protocol version matches the shared contract definition.</summary>
[Fact]
public void GatewayProtocolVersion_MatchesSharedContract()
{
@@ -12,6 +13,7 @@ public sealed class MxGatewayClientContractInfoTests
MxGatewayClientContractInfo.GatewayProtocolVersion);
}
/// <summary>Verifies that the client's worker protocol version matches the shared contract definition.</summary>
[Fact]
public void WorkerProtocolVersion_MatchesSharedContract()
{
@@ -2,6 +2,7 @@ namespace MxGateway.Client.Tests;
public sealed class MxGatewayClientOptionsTests
{
/// <summary>Verifies that options with valid endpoint and API key pass validation.</summary>
[Fact]
public void Validate_WithAbsoluteEndpointAndApiKey_Succeeds()
{
@@ -14,6 +15,7 @@ public sealed class MxGatewayClientOptionsTests
options.Validate();
}
/// <summary>Verifies that empty API key causes validation to fail.</summary>
[Fact]
public void Validate_WithEmptyApiKey_Throws()
{
@@ -26,6 +28,7 @@ public sealed class MxGatewayClientOptionsTests
Assert.Throws<ArgumentException>(options.Validate);
}
/// <summary>Verifies that invalid retry options cause validation to fail.</summary>
[Fact]
public void Validate_WithInvalidRetryOptions_Throws()
{
@@ -3,8 +3,10 @@ using Grpc.Core;
namespace MxGateway.Client.Tests;
/// <summary>Tests for MxGatewaySession and client command behavior.</summary>
public sealed class MxGatewayClientSessionTests
{
/// <summary>Verifies that open session attaches API key metadata and cancellation token.</summary>
[Fact]
public async Task OpenSessionRawAsync_AttachesApiKeyMetadataAndCancellation()
{
@@ -19,6 +21,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Equal(cancellation.Token, call.CallOptions.CancellationToken);
}
/// <summary>Verifies that open session returns a session with the raw open reply.</summary>
[Fact]
public async Task OpenSessionAsync_ReturnsSessionWithRawOpenReply()
{
@@ -33,6 +36,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Equal(1234, session.OpenSessionReply.WorkerProcessId);
}
/// <summary>Verifies that register builds a register command and returns server handle.</summary>
[Fact]
public async Task RegisterAsync_BuildsRegisterCommandAndReturnsServerHandle()
{
@@ -57,6 +61,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Equal("fixture-client", call.Request.Command.Register.ClientName);
}
/// <summary>Verifies that add item 2 builds a command with the specified context.</summary>
[Fact]
public async Task AddItem2Async_BuildsAddItem2CommandWithContext()
{
@@ -81,6 +86,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Equal("runtime", request.Command.AddItem2.ItemContext);
}
/// <summary>Verifies that write raw builds a write command with the raw value.</summary>
[Fact]
public async Task WriteRawAsync_BuildsWriteCommandWithRawValue()
{
@@ -111,6 +117,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Equal(56, request.Command.Write.UserId);
}
/// <summary>Verifies that write 2 raw builds a write 2 command with value and timestamp.</summary>
[Fact]
public async Task Write2RawAsync_BuildsWrite2CommandWithValueAndTimestamp()
{
@@ -138,6 +145,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Equal(56, request.Command.Write2.UserId);
}
/// <summary>Verifies that subscribe bulk builds one command and returns per-item results.</summary>
[Fact]
public async Task SubscribeBulkAsync_BuildsOneBulkCommandAndReturnsPerItemResults()
{
@@ -176,6 +184,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Equal(["Area001.Pump001.Speed"], request.Command.SubscribeBulk.TagAddresses);
}
/// <summary>Verifies that stream events yields events in the order received from the gateway.</summary>
[Fact]
public async Task StreamEventsAsync_YieldsEventsInGatewayOrder()
{
@@ -206,6 +215,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Equal("session-fixture", request.SessionId);
}
/// <summary>Verifies that close is explicit and idempotent.</summary>
[Fact]
public async Task CloseAsync_IsExplicitAndIdempotent()
{
@@ -221,6 +231,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Equal("session-fixture", call.Request.SessionId);
}
/// <summary>Verifies that invoke retries safe diagnostic commands on transient RPC failure.</summary>
[Fact]
public async Task InvokeAsync_RetriesSafeDiagnosticCommandOnTransientGrpcFailure()
{
@@ -244,6 +255,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Equal(2, transport.InvokeCalls.Count);
}
/// <summary>Verifies that open session does not retry on transient RPC failure.</summary>
[Fact]
public async Task OpenSessionAsync_DoesNotRetryTransientGrpcFailure()
{
@@ -256,6 +268,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Single(transport.OpenSessionCalls);
}
/// <summary>Verifies that invoke does not retry write commands on transient RPC failure.</summary>
[Fact]
public async Task InvokeAsync_DoesNotRetryWriteCommand()
{
@@ -270,6 +283,7 @@ public sealed class MxGatewayClientSessionTests
Assert.Single(transport.InvokeCalls);
}
/// <summary>Verifies that invoke helpers pass cancellation token to the transport.</summary>
[Fact]
public async Task InvokeHelpers_PassCancellationTokenToTransport()
{
@@ -2,6 +2,7 @@ namespace MxGateway.Client.Tests;
public sealed class MxGatewayGeneratedContractTests
{
/// <summary>Verifies that the generated gRPC client can be instantiated from the client factory.</summary>
[Fact]
public async Task GeneratedGrpcClient_CanBeConstructedFromClientFactory()
{
@@ -7,6 +7,7 @@ namespace MxGateway.Client.Tests;
public sealed class MxStatusProxyExtensionsTests
{
/// <summary>Verifies that fixture statuses correctly project success and preserve raw integer fields.</summary>
[Fact]
public void FixtureStatuses_ProjectSuccessAndPreserveRawFields()
{
@@ -7,6 +7,7 @@ namespace MxGateway.Client.Tests;
public sealed class MxValueExtensionsTests
{
/// <summary>Verifies that scalar values are converted to correctly-typed MxValue protobuf messages.</summary>
[Fact]
public void ToMxValue_WithScalarValues_CreatesTypedProtobufValues()
{
@@ -18,6 +19,7 @@ public sealed class MxValueExtensionsTests
Assert.Equal(MxValue.KindOneofCase.StringValue, "alpha".ToMxValue().KindCase);
}
/// <summary>Verifies that array values are converted to array-kind MxValue messages with correct element types and dimensions.</summary>
[Fact]
public void ToMxValue_WithArrays_CreatesTypedArrayProtobufValues()
{
@@ -29,6 +31,7 @@ public sealed class MxValueExtensionsTests
Assert.Equal([2U], value.ArrayValue.Dimensions);
}
/// <summary>Verifies that fixture test cases project to expected MxValue kinds and preserve raw type metadata.</summary>
[Fact]
public void FixtureValues_ProjectExpectedKindsAndPreserveRawMetadata()
{