using System; using System.Threading.Tasks; using FluentAssertions; using Xunit; using ZB.MOM.WW.LmxProxy.Host.Metrics; namespace ZB.MOM.WW.LmxProxy.Host.Tests.Metrics { public class PerformanceMetricsTests { [Fact] public void RecordOperation_TracksCountAndDuration() { using var metrics = new PerformanceMetrics(); for (int i = 0; i < 5; i++) { metrics.RecordOperation("TestOp", TimeSpan.FromMilliseconds(10), true); } var stats = metrics.GetStatistics(); stats.Should().ContainKey("TestOp"); stats["TestOp"].TotalCount.Should().Be(5); } [Fact] public void RecordOperation_TracksSuccessAndFailure() { using var metrics = new PerformanceMetrics(); for (int i = 0; i < 3; i++) { metrics.RecordOperation("TestOp", TimeSpan.FromMilliseconds(10), true); } for (int i = 0; i < 2; i++) { metrics.RecordOperation("TestOp", TimeSpan.FromMilliseconds(10), false); } var stats = metrics.GetStatistics(); stats["TestOp"].SuccessRate.Should().BeApproximately(0.6, 0.001); } [Fact] public void GetStatistics_CalculatesP95Correctly() { using var metrics = new PerformanceMetrics(); for (int i = 1; i <= 100; i++) { metrics.RecordOperation("TestOp", TimeSpan.FromMilliseconds(i), true); } var stats = metrics.GetStatistics(); stats["TestOp"].Percentile95Milliseconds.Should().BeApproximately(95.0, 1.0); } [Fact] public void RollingBuffer_CapsAt1000Samples() { using var metrics = new PerformanceMetrics(); for (int i = 0; i < 1500; i++) { metrics.RecordOperation("TestOp", TimeSpan.FromMilliseconds(i), true); } var stats = metrics.GetStatistics(); // TotalCount tracks all 1500 but percentile is computed from the last 1000 stats["TestOp"].TotalCount.Should().Be(1500); // The rolling buffer should have entries from 500-1499 // P95 of 500..1499 should be around 1449 stats["TestOp"].Percentile95Milliseconds.Should().BeGreaterThan(1000); } [Fact] public void BeginOperation_RecordsDurationOnDispose() { using var metrics = new PerformanceMetrics(); using (var scope = metrics.BeginOperation("TestOp")) { System.Threading.Thread.Sleep(50); } var stats = metrics.GetStatistics(); stats.Should().ContainKey("TestOp"); stats["TestOp"].TotalCount.Should().Be(1); stats["TestOp"].AverageMilliseconds.Should().BeGreaterOrEqualTo(40); } [Fact] public void TimingScope_DefaultsToSuccess() { using var metrics = new PerformanceMetrics(); using (metrics.BeginOperation("TestOp")) { // Do nothing — default is success } var stats = metrics.GetStatistics(); stats["TestOp"].SuccessCount.Should().Be(1); } [Fact] public void TimingScope_RespectsSetSuccessFalse() { using var metrics = new PerformanceMetrics(); using (var scope = metrics.BeginOperation("TestOp")) { scope.SetSuccess(false); } var stats = metrics.GetStatistics(); stats["TestOp"].SuccessCount.Should().Be(0); stats["TestOp"].TotalCount.Should().Be(1); } [Fact] public void GetMetrics_ReturnsNullForUnknownOperation() { using var metrics = new PerformanceMetrics(); var result = metrics.GetMetrics("DoesNotExist"); result.Should().BeNull(); } [Fact] public void GetAllMetrics_ReturnsAllTrackedOperations() { using var metrics = new PerformanceMetrics(); metrics.RecordOperation("Read", TimeSpan.FromMilliseconds(10), true); metrics.RecordOperation("Write", TimeSpan.FromMilliseconds(20), true); metrics.RecordOperation("Subscribe", TimeSpan.FromMilliseconds(5), true); var all = metrics.GetAllMetrics(); all.Should().ContainKey("Read"); all.Should().ContainKey("Write"); all.Should().ContainKey("Subscribe"); all.Count.Should().Be(3); } } }