Files
natsdotnet/tests/NATS.Server.Tests/Auth/ResponseThresholdTests.cs
Joseph Doherty 3107615885 feat: add service export latency tracking with p50/p90/p99 (Gap 9.1)
Add ServiceLatencyTracker with sorted-sample histogram, percentile getters (p50/p90/p99), average/min/max, reset, and immutable snapshot. Wire LatencyTracker and RecordServiceLatency onto Account. Cover with 11 xUnit tests.
2026-02-25 12:52:05 -05:00

138 lines
5.3 KiB
C#

// Tests for Account.SetServiceResponseThreshold / GetServiceResponseThreshold /
// IsServiceResponseOverdue / CheckServiceResponse.
// Go reference: server/accounts.go — SetServiceExportResponseThreshold (~line 2522),
// ServiceExportResponseThreshold (~line 2510).
using NATS.Server.Auth;
namespace NATS.Server.Tests.Auth;
public class ResponseThresholdTests
{
// ---------------------------------------------------------------------------
// SetServiceResponseThreshold / GetServiceResponseThreshold
// ---------------------------------------------------------------------------
[Fact]
public void SetServiceResponseThreshold_StoresThreshold()
{
// Go ref: accounts.go SetServiceExportResponseThreshold (~line 2522)
var account = new Account("test");
account.SetServiceResponseThreshold("svc.foo", TimeSpan.FromSeconds(5));
account.ServiceResponseThresholds.ContainsKey("svc.foo").ShouldBeTrue();
account.ServiceResponseThresholds["svc.foo"].ShouldBe(TimeSpan.FromSeconds(5));
}
[Fact]
public void GetServiceResponseThreshold_ReturnsStored()
{
// Go ref: accounts.go ServiceExportResponseThreshold (~line 2510)
var account = new Account("test");
account.SetServiceResponseThreshold("svc.bar", TimeSpan.FromMilliseconds(200));
account.GetServiceResponseThreshold("svc.bar").ShouldBe(TimeSpan.FromMilliseconds(200));
}
[Fact]
public void GetServiceResponseThreshold_NotSet_ReturnsNull()
{
// Go ref: accounts.go ServiceExportResponseThreshold — returns error when export not found
var account = new Account("test");
account.GetServiceResponseThreshold("svc.unknown").ShouldBeNull();
}
// ---------------------------------------------------------------------------
// IsServiceResponseOverdue
// ---------------------------------------------------------------------------
[Fact]
public void IsServiceResponseOverdue_WithinThreshold_ReturnsFalse()
{
// Go ref: accounts.go respThresh check — elapsed < threshold ⇒ not overdue
var account = new Account("test");
account.SetServiceResponseThreshold("svc.a", TimeSpan.FromSeconds(10));
account.IsServiceResponseOverdue("svc.a", TimeSpan.FromSeconds(9)).ShouldBeFalse();
}
[Fact]
public void IsServiceResponseOverdue_ExceedsThreshold_ReturnsTrue()
{
// Go ref: accounts.go respThresh check — elapsed > threshold ⇒ overdue
var account = new Account("test");
account.SetServiceResponseThreshold("svc.b", TimeSpan.FromSeconds(1));
account.IsServiceResponseOverdue("svc.b", TimeSpan.FromSeconds(2)).ShouldBeTrue();
}
[Fact]
public void IsServiceResponseOverdue_NoThreshold_ReturnsFalse()
{
// Go ref: accounts.go — when no respThresh is set the timer never fires (never overdue)
var account = new Account("test");
account.IsServiceResponseOverdue("svc.unregistered", TimeSpan.FromHours(1)).ShouldBeFalse();
}
[Fact]
public void SetServiceResponseThreshold_OverwritesPrevious()
{
// Go ref: accounts.go SetServiceExportResponseThreshold — se.respThresh = maxTime overwrites
var account = new Account("test");
account.SetServiceResponseThreshold("svc.c", TimeSpan.FromSeconds(5));
account.SetServiceResponseThreshold("svc.c", TimeSpan.FromSeconds(30));
account.GetServiceResponseThreshold("svc.c").ShouldBe(TimeSpan.FromSeconds(30));
}
// ---------------------------------------------------------------------------
// CheckServiceResponse
// ---------------------------------------------------------------------------
[Fact]
public void CheckServiceResponse_Found_NotOverdue()
{
// Go ref: accounts.go ServiceExportResponseThreshold + respThresh timer — within window
var account = new Account("test");
account.SetServiceResponseThreshold("svc.d", TimeSpan.FromSeconds(10));
var result = account.CheckServiceResponse("svc.d", TimeSpan.FromSeconds(5));
result.Found.ShouldBeTrue();
result.IsOverdue.ShouldBeFalse();
result.Threshold.ShouldBe(TimeSpan.FromSeconds(10));
result.Elapsed.ShouldBe(TimeSpan.FromSeconds(5));
}
[Fact]
public void CheckServiceResponse_Found_Overdue()
{
// Go ref: accounts.go respThresh timer fires — elapsed exceeded threshold
var account = new Account("test");
account.SetServiceResponseThreshold("svc.e", TimeSpan.FromSeconds(2));
var result = account.CheckServiceResponse("svc.e", TimeSpan.FromSeconds(5));
result.Found.ShouldBeTrue();
result.IsOverdue.ShouldBeTrue();
result.Threshold.ShouldBe(TimeSpan.FromSeconds(2));
result.Elapsed.ShouldBe(TimeSpan.FromSeconds(5));
}
[Fact]
public void CheckServiceResponse_NotFound()
{
// Go ref: accounts.go — no export defined, returns error; here Found=false
var account = new Account("test");
var result = account.CheckServiceResponse("svc.none", TimeSpan.FromSeconds(1));
result.Found.ShouldBeFalse();
result.IsOverdue.ShouldBeFalse();
result.Threshold.ShouldBeNull();
result.Elapsed.ShouldBe(TimeSpan.FromSeconds(1));
}
}