feat: add account-scoped filtering to /connz (Gap 10.2)
Add ConnzConnectionInfo, ConnzFilterResult, ConnzFilterOptions, and ConnzFilter static class to Connz.cs, providing a pure unit-testable layer for account-scoped filtering and pagination that mirrors the Go server's /connz ?acc= query-parameter behaviour. Ten new tests in ConnzAccountFilterTests.cs cover FilterByAccount (match, no-match, case-insensitive), ConnzFilterOptions.Parse (acc param, defaults, offset/limit), and ApplyFilters (account filter, offset, limit, no-filter pass-through).
This commit is contained in:
190
tests/NATS.Server.Tests/Monitoring/ConnzAccountFilterTests.cs
Normal file
190
tests/NATS.Server.Tests/Monitoring/ConnzAccountFilterTests.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using NATS.Server.Monitoring;
|
||||
|
||||
namespace NATS.Server.Tests.Monitoring;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for account-scoped /connz filtering (Gap 10.2).
|
||||
/// Exercises ConnzFilterOptions.Parse, ConnzFilter.FilterByAccount, and
|
||||
/// ConnzFilter.ApplyFilters in isolation — no running server required.
|
||||
/// Go reference: monitor_test.go — TestConnzFilterByAccount, TestConnzWithAccount.
|
||||
/// </summary>
|
||||
public class ConnzAccountFilterTests
|
||||
{
|
||||
// -----------------------------------------------------------------------
|
||||
// Helper — build a ConnzConnectionInfo with sensible defaults
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private static ConnzConnectionInfo MakeConn(
|
||||
ulong clientId,
|
||||
string? accountName,
|
||||
string remoteAddress = "127.0.0.1:1234",
|
||||
string? name = null,
|
||||
long inMsgs = 0,
|
||||
long outMsgs = 0,
|
||||
long inBytes = 0,
|
||||
long outBytes = 0) =>
|
||||
new(clientId, remoteAddress, accountName, name, DateTime.UtcNow,
|
||||
inMsgs, outMsgs, inBytes, outBytes);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// FilterByAccount tests
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void FilterByAccount_MatchingAccount_ReturnsFiltered()
|
||||
{
|
||||
// Go reference: monitor.go Connz() — "if opts.Account != "" { ... }" filter
|
||||
var connections = new[]
|
||||
{
|
||||
MakeConn(1, "acctA"),
|
||||
MakeConn(2, "acctB"),
|
||||
MakeConn(3, "acctA"),
|
||||
};
|
||||
|
||||
var result = ConnzFilter.FilterByAccount(connections, "acctA");
|
||||
|
||||
result.Count.ShouldBe(2);
|
||||
result.ShouldAllBe(c => c.AccountName == "acctA");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilterByAccount_NoMatch_ReturnsEmpty()
|
||||
{
|
||||
var connections = new[]
|
||||
{
|
||||
MakeConn(1, "acctA"),
|
||||
MakeConn(2, "acctB"),
|
||||
};
|
||||
|
||||
var result = ConnzFilter.FilterByAccount(connections, "acctC");
|
||||
|
||||
result.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilterByAccount_CaseInsensitive()
|
||||
{
|
||||
// Go reference: monitor.go — account name comparison is case-insensitive
|
||||
var connections = new[]
|
||||
{
|
||||
MakeConn(1, "AcctA"),
|
||||
MakeConn(2, "acctb"),
|
||||
};
|
||||
|
||||
// Upper-case query against lower-case stored name
|
||||
var lowerResult = ConnzFilter.FilterByAccount(connections, "accta");
|
||||
lowerResult.Count.ShouldBe(1);
|
||||
lowerResult[0].ClientId.ShouldBe(1UL);
|
||||
|
||||
// Lower-case query against mixed-case stored name
|
||||
var upperResult = ConnzFilter.FilterByAccount(connections, "ACCTB");
|
||||
upperResult.Count.ShouldBe(1);
|
||||
upperResult[0].ClientId.ShouldBe(2UL);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ConnzFilterOptions.Parse tests
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Parse_WithAccParam_SetsAccountFilter()
|
||||
{
|
||||
var opts = ConnzFilterOptions.Parse("?acc=myAccount");
|
||||
|
||||
opts.AccountFilter.ShouldBe("myAccount");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_WithoutParams_DefaultValues()
|
||||
{
|
||||
var opts = ConnzFilterOptions.Parse(null);
|
||||
|
||||
opts.AccountFilter.ShouldBeNull();
|
||||
opts.StateFilter.ShouldBeNull();
|
||||
opts.Offset.ShouldBe(0);
|
||||
opts.Limit.ShouldBe(1024);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_WithOffsetAndLimit_ParsesCorrectly()
|
||||
{
|
||||
var opts = ConnzFilterOptions.Parse("?acc=acctA&state=open&offset=10&limit=50");
|
||||
|
||||
opts.AccountFilter.ShouldBe("acctA");
|
||||
opts.StateFilter.ShouldBe("open");
|
||||
opts.Offset.ShouldBe(10);
|
||||
opts.Limit.ShouldBe(50);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ApplyFilters tests
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ApplyFilters_WithAccountFilter_FiltersCorrectly()
|
||||
{
|
||||
var connections = new[]
|
||||
{
|
||||
MakeConn(1, "acctA"),
|
||||
MakeConn(2, "acctB"),
|
||||
MakeConn(3, "acctA"),
|
||||
};
|
||||
|
||||
var opts = new ConnzFilterOptions { AccountFilter = "acctA" };
|
||||
var result = ConnzFilter.ApplyFilters(connections, opts);
|
||||
|
||||
result.Total.ShouldBe(2);
|
||||
result.Connections.Count.ShouldBe(2);
|
||||
result.Connections.ShouldAllBe(c => c.AccountName == "acctA");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyFilters_WithOffset_SkipsEntries()
|
||||
{
|
||||
var connections = Enumerable.Range(1, 5)
|
||||
.Select(i => MakeConn((ulong)i, "acctA"))
|
||||
.ToList();
|
||||
|
||||
var opts = new ConnzFilterOptions { Offset = 3, Limit = 10 };
|
||||
var result = ConnzFilter.ApplyFilters(connections, opts);
|
||||
|
||||
result.Total.ShouldBe(5);
|
||||
result.Connections.Count.ShouldBe(2);
|
||||
result.Offset.ShouldBe(3);
|
||||
// The paged items should be the last two (clientId 4 and 5)
|
||||
result.Connections[0].ClientId.ShouldBe(4UL);
|
||||
result.Connections[1].ClientId.ShouldBe(5UL);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyFilters_WithLimit_CapsResults()
|
||||
{
|
||||
var connections = Enumerable.Range(1, 10)
|
||||
.Select(i => MakeConn((ulong)i, "acctA"))
|
||||
.ToList();
|
||||
|
||||
var opts = new ConnzFilterOptions { Limit = 3 };
|
||||
var result = ConnzFilter.ApplyFilters(connections, opts);
|
||||
|
||||
result.Total.ShouldBe(10);
|
||||
result.Connections.Count.ShouldBe(3);
|
||||
result.Limit.ShouldBe(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyFilters_NoFilters_ReturnsAll()
|
||||
{
|
||||
var connections = new[]
|
||||
{
|
||||
MakeConn(1, "acctA"),
|
||||
MakeConn(2, "acctB"),
|
||||
MakeConn(3, null),
|
||||
};
|
||||
|
||||
var opts = new ConnzFilterOptions();
|
||||
var result = ConnzFilter.ApplyFilters(connections, opts);
|
||||
|
||||
result.Total.ShouldBe(3);
|
||||
result.Connections.Count.ShouldBe(3);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user