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,122 @@
using FluentAssertions;
using Xunit;
namespace ZB.MOM.WW.LmxProxy.Client.Tests;
public class ClientMetricsTests
{
private static LmxProxyClient.ClientMetrics CreateMetrics() => new();
[Fact]
public void IncrementOperationCount_Increments()
{
var metrics = CreateMetrics();
metrics.IncrementOperationCount("Read");
metrics.IncrementOperationCount("Read");
metrics.IncrementOperationCount("Read");
var snapshot = metrics.GetSnapshot();
snapshot["Read_count"].Should().Be(3L);
}
[Fact]
public void IncrementErrorCount_Increments()
{
var metrics = CreateMetrics();
metrics.IncrementErrorCount("Write");
metrics.IncrementErrorCount("Write");
var snapshot = metrics.GetSnapshot();
snapshot["Write_errors"].Should().Be(2L);
}
[Fact]
public void RecordLatency_StoresValues()
{
var metrics = CreateMetrics();
metrics.RecordLatency("Read", 10);
metrics.RecordLatency("Read", 20);
metrics.RecordLatency("Read", 30);
var snapshot = metrics.GetSnapshot();
snapshot.Should().ContainKey("Read_avg_latency_ms");
snapshot.Should().ContainKey("Read_p95_latency_ms");
snapshot.Should().ContainKey("Read_p99_latency_ms");
var avg = (double)snapshot["Read_avg_latency_ms"];
avg.Should().BeApproximately(20.0, 0.1);
}
[Fact]
public void RollingBuffer_CapsAt1000()
{
var metrics = CreateMetrics();
for (int i = 0; i < 1100; i++)
{
metrics.RecordLatency("Read", i);
}
var snapshot = metrics.GetSnapshot();
// After 1100 entries, the buffer should have capped at 1000 (oldest removed)
// The earliest remaining value should be 100 (entries 0-99 were evicted)
var p95 = (long)snapshot["Read_p95_latency_ms"];
// p95 of values 100-1099 should be around 1050
p95.Should().BeGreaterThan(900);
}
[Fact]
public void GetSnapshot_IncludesP95AndP99()
{
var metrics = CreateMetrics();
// Add 100 values: 1, 2, 3, ..., 100
for (int i = 1; i <= 100; i++)
{
metrics.RecordLatency("Op", i);
}
var snapshot = metrics.GetSnapshot();
var p95 = (long)snapshot["Op_p95_latency_ms"];
var p99 = (long)snapshot["Op_p99_latency_ms"];
// P95 of 1..100 should be 95
p95.Should().Be(95);
// P99 of 1..100 should be 99
p99.Should().Be(99);
}
[Fact]
public void GetSnapshot_ReturnsEmptyForNoData()
{
var metrics = CreateMetrics();
var snapshot = metrics.GetSnapshot();
snapshot.Should().BeEmpty();
}
[Fact]
public void GetSnapshot_TracksMultipleOperations()
{
var metrics = CreateMetrics();
metrics.IncrementOperationCount("Read");
metrics.IncrementOperationCount("Write");
metrics.IncrementErrorCount("Read");
metrics.RecordLatency("Read", 10);
metrics.RecordLatency("Write", 20);
var snapshot = metrics.GetSnapshot();
snapshot["Read_count"].Should().Be(1L);
snapshot["Write_count"].Should().Be(1L);
snapshot["Read_errors"].Should().Be(1L);
snapshot.Should().ContainKey("Read_avg_latency_ms");
snapshot.Should().ContainKey("Write_avg_latency_ms");
}
}