feat(lmxproxy): phase 5 — client core (ILmxProxyClient, connection, read/write/subscribe)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-22 00:22:29 -04:00
parent 9eb81180c0
commit 8ba75b50e8
19 changed files with 1819 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
using FluentAssertions;
using Xunit;
using ZB.MOM.WW.LmxProxy.Client.Domain;
using ZB.MOM.WW.LmxProxy.Client.Tests.Fakes;
namespace ZB.MOM.WW.LmxProxy.Client.Tests;
public class LmxProxyClientConnectionTests
{
[Fact]
public async Task IsConnectedAsync_ReturnsFalseBeforeConnect()
{
var client = new LmxProxyClient("localhost", 50051, null, null);
var result = await client.IsConnectedAsync();
result.Should().BeFalse();
client.Dispose();
}
[Fact]
public async Task IsConnectedAsync_ReturnsTrueAfterInjection()
{
var (client, _) = TestableClient.CreateConnected();
var result = await client.IsConnectedAsync();
result.Should().BeTrue();
client.Dispose();
}
[Fact]
public async Task DisconnectAsync_SendsDisconnectAndClearsState()
{
var (client, fake) = TestableClient.CreateConnected();
await client.DisconnectAsync();
fake.DisconnectCalls.Should().HaveCount(1);
fake.DisconnectCalls[0].SessionId.Should().Be("test-session-123");
client.IsConnected.Should().BeFalse();
client.Dispose();
}
[Fact]
public async Task DisconnectAsync_SwallowsExceptions()
{
var (client, fake) = TestableClient.CreateConnected();
fake.DisconnectResponseToReturn = null!; // Force an error path
// Should not throw
var act = () => client.DisconnectAsync();
await act.Should().NotThrowAsync();
client.Dispose();
}
[Fact]
public void IsConnected_ReturnsFalseAfterDispose()
{
var (client, _) = TestableClient.CreateConnected();
client.Dispose();
client.IsConnected.Should().BeFalse();
}
[Fact]
public async Task MarkDisconnectedAsync_ClearsConnectionState()
{
var (client, _) = TestableClient.CreateConnected();
await client.MarkDisconnectedAsync(new Exception("connection lost"));
client.IsConnected.Should().BeFalse();
client.Dispose();
}
[Fact]
public void DefaultTimeout_RejectsOutOfRange()
{
var client = new LmxProxyClient("localhost", 50051, null, null);
var act = () => client.DefaultTimeout = TimeSpan.FromMilliseconds(500);
act.Should().Throw<ArgumentOutOfRangeException>();
var act2 = () => client.DefaultTimeout = TimeSpan.FromMinutes(11);
act2.Should().Throw<ArgumentOutOfRangeException>();
client.Dispose();
}
[Fact]
public void DefaultTimeout_AcceptsValidRange()
{
var client = new LmxProxyClient("localhost", 50051, null, null);
client.DefaultTimeout = TimeSpan.FromSeconds(5);
client.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(5));
client.Dispose();
}
}