Files
natsnet/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/EventsHandlerTests.cs
2026-02-28 21:25:14 -05:00

660 lines
21 KiB
C#

using System.Reflection;
using System.Text;
using System.Text.Json;
using Shouldly;
using ZB.MOM.NatsNet.Server;
using ZB.MOM.NatsNet.Server.Auth;
using ZB.MOM.NatsNet.Server.Internal;
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog;
public sealed class EventsHandlerTests
{
[Fact] // T:299
public void SystemAccount_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions
{
NoSystemAccount = true,
});
err.ShouldBeNull();
server.ShouldNotBeNull();
server!.SetDefaultSystemAccount().ShouldBeNull();
var sys = server.SystemAccount();
var global = server.GlobalAccount();
sys.ShouldNotBeNull();
global.ShouldNotBeNull();
sys!.Name.ShouldBe(ServerConstants.DefaultSystemAccount);
global!.Name.ShouldBe(ServerConstants.DefaultGlobalAccount);
sys.Name.ShouldNotBe(global.Name);
}
[Fact] // T:300
public void SystemAccountNewConnection_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions { NoSystemAccount = true });
err.ShouldBeNull();
server!.SetDefaultSystemAccount().ShouldBeNull();
var sys = server.SystemAccount();
sys.ShouldNotBeNull();
var c = new ClientConnection(ClientKind.Client, server) { Cid = 1001 };
c.RegisterWithAccount(sys!);
sys.NumConnections().ShouldBe(1);
}
[Fact] // T:301
public void SystemAccountingWithLeafNodes_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions { NoSystemAccount = true });
err.ShouldBeNull();
server!.SetDefaultSystemAccount().ShouldBeNull();
var sys = server.SystemAccount();
sys.ShouldNotBeNull();
var leaf = new ClientConnection(ClientKind.Leaf, server) { Cid = 1002 };
leaf.RegisterWithAccount(sys!);
sys.NumLeafNodes().ShouldBe(1);
}
[Fact] // T:302
public void SystemAccountDisconnectBadLogin_ShouldSucceed()
{
var c = new ClientConnection(ClientKind.Client);
c.AuthViolation();
c.IsClosed().ShouldBeTrue();
}
[Fact] // T:306
public void SystemAccountConnectionLimits_ShouldSucceed()
{
var acc = Account.NewAccount("SYS");
acc.MaxConnections = 1;
var c1 = new ClientConnection(ClientKind.Client) { Cid = 1 };
var c2 = new ClientConnection(ClientKind.Client) { Cid = 2 };
c1.RegisterWithAccount(acc);
Should.Throw<TooManyAccountConnectionsException>(() => c2.RegisterWithAccount(acc));
}
[Fact] // T:308
public void SystemAccountSystemConnectionLimitsHonored_ShouldSucceed()
{
var acc = Account.NewAccount("SYS");
acc.MaxConnections = 1;
var s1 = new ClientConnection(ClientKind.System) { Cid = 11 };
var s2 = new ClientConnection(ClientKind.System) { Cid = 12 };
s1.RegisterWithAccount(acc);
s2.RegisterWithAccount(acc);
acc.NumConnections().ShouldBe(0);
}
[Fact] // T:309
public void SystemAccountConnectionLimitsServersStaggered_ShouldSucceed()
{
var acc = Account.NewAccount("TEST");
acc.MaxConnections = 3;
for (var i = 0; i < 3; i++)
new ClientConnection(ClientKind.Client) { Cid = (ulong)(20 + i) }.RegisterWithAccount(acc);
var overByTwo = acc.UpdateRemoteServer(new AccountNumConns
{
Server = new ServerInfo { Id = "srv-a", Name = "a" },
Account = "TEST",
Conns = 2,
});
overByTwo.Count.ShouldBe(2);
var overByOne = acc.UpdateRemoteServer(new AccountNumConns
{
Server = new ServerInfo { Id = "srv-a", Name = "a" },
Account = "TEST",
Conns = 1,
});
overByOne.Count.ShouldBe(1);
}
[Fact] // T:310
public void SystemAccountConnectionLimitsServerShutdownGraceful_ShouldSucceed()
{
var acc = Account.NewAccount("TEST");
acc.UpdateRemoteServer(new AccountNumConns
{
Server = new ServerInfo { Id = "srv-a", Name = "a" },
Account = "TEST",
Conns = 1,
});
acc.ExpectedRemoteResponses().ShouldBe(1);
acc.RemoveRemoteServer("srv-a");
acc.ExpectedRemoteResponses().ShouldBe(0);
}
[Fact] // T:311
public void SystemAccountConnectionLimitsServerShutdownForced_ShouldSucceed()
{
var acc = Account.NewAccount("TEST");
acc.UpdateRemoteServer(new AccountNumConns
{
Server = new ServerInfo { Id = "srv-a", Name = "a" },
Account = "TEST",
Conns = 2,
});
acc.RemoveRemoteServer("srv-missing");
acc.ExpectedRemoteResponses().ShouldBe(1);
acc.RemoveRemoteServer("srv-a");
acc.ExpectedRemoteResponses().ShouldBe(0);
}
[Fact] // T:312
public void SystemAccountFromConfig_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions
{
Accounts = [new Account { Name = "SYSCFG" }],
SystemAccount = "SYSCFG",
});
err.ShouldBeNull();
server.ShouldNotBeNull();
server!.SystemAccount().ShouldNotBeNull();
server.SystemAccount()!.Name.ShouldBe("SYSCFG");
}
[Fact] // T:313
public void AccountClaimsUpdates_ShouldSucceed()
{
AccountClaims.TryDecode(string.Empty).ShouldBeNull();
AccountClaims.TryDecode("not-a-real-jwt").ShouldBeNull();
}
[Fact] // T:315
public void AccountReqInfo_ShouldSucceed()
{
var acc = Account.NewAccount("A");
var id1 = acc.NextEventId();
var id2 = acc.NextEventId();
id1.ShouldNotBeNullOrWhiteSpace();
id2.ShouldNotBeNullOrWhiteSpace();
id1.ShouldNotBe(id2);
}
[Fact] // T:316
public void AccountClaimsUpdatesWithServiceImports_ShouldSucceed()
{
var source = Account.NewAccount("src");
var original = Account.NewAccount("dst");
original.Imports.Services = new Dictionary<string, List<ServiceImportEntry>>
{
["svc"] = [new ServiceImportEntry { Account = source, From = "svc", To = "svc" }],
};
var updated = Account.NewAccount("dst");
original.ShallowCopy(updated);
updated.Imports.Services.ShouldNotBeNull();
updated.Imports.Services!.ShouldContainKey("svc");
updated.Imports.Services["svc"].Count.ShouldBe(1);
}
[Fact] // T:317
public void AccountConnsLimitExceededAfterUpdate_ShouldSucceed()
{
var acc = Account.NewAccount("A");
acc.MaxConnections = 2;
new ClientConnection(ClientKind.Client) { Cid = 71 }.RegisterWithAccount(acc);
new ClientConnection(ClientKind.Client) { Cid = 72 }.RegisterWithAccount(acc);
var toDisconnect = acc.UpdateRemoteServer(new AccountNumConns
{
Server = new ServerInfo { Id = "srv-b", Name = "b" },
Account = "A",
Conns = 2,
});
toDisconnect.Count.ShouldBe(2);
}
[Fact] // T:320
public void SystemAccountWithGateways_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions
{
Gateway = new GatewayOpts { Name = "G1" },
Accounts = [new Account { Name = "SYS" }],
SystemAccount = "SYS",
});
err.ShouldBeNull();
server.ShouldNotBeNull();
server!.GetOpts().Gateway.Name.ShouldBe("G1");
server.SystemAccount()!.Name.ShouldBe("SYS");
}
[Fact] // T:321
public void SystemAccountNoAuthUser_ShouldSucceed()
{
var opts = new ServerOptions
{
Users = [new User { Username = "noauth" }],
NoAuthUser = "noauth",
SystemAccount = "SYS",
};
AuthHandler.ValidateNoAuthUser(opts, opts.NoAuthUser).ShouldBeNull();
}
[Fact] // T:322
public void ServerAccountConns_ShouldSucceed()
{
var acc = Account.NewAccount("A");
var c = new ClientConnection(ClientKind.Client) { Cid = 81 };
c.RegisterWithAccount(acc);
acc.NumConnections().ShouldBe(1);
((INatsAccount)acc).RemoveClient(c);
acc.NumConnections().ShouldBe(0);
}
[Fact] // T:323
public void ServerEventsStatsZ_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions());
err.ShouldBeNull();
server!.NumSlowConsumers().ShouldBe(0);
server.NumStaleConnections().ShouldBe(0);
server.NumClients().ShouldBeGreaterThanOrEqualTo(0);
}
[Fact] // T:324
public void ServerEventsHealthZSingleServer_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions());
err.ShouldBeNull();
server!.NumRoutes().ShouldBe(0);
server.NumRemotes().ShouldBe(0);
}
[Fact] // T:327
public void ServerEventsHealthZJetStreamNotEnabled_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions());
err.ShouldBeNull();
server.ShouldNotBeNull();
server!.GetOpts().JetStream.ShouldBeFalse();
}
[Fact] // T:328
public void ServerEventsPingStatsZ_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions());
err.ShouldBeNull();
server!.NumSlowConsumersClients().ShouldBe(0);
server.NumSlowConsumersRoutes().ShouldBe(0);
server.NumSlowConsumersGateways().ShouldBe(0);
server.NumSlowConsumersLeafs().ShouldBe(0);
}
[Fact] // T:329
public void ServerEventsPingStatsZDedicatedRecvQ_ShouldSucceed()
{
var server = CreateServer();
var sc = GetPrivateField<SlowConsumerStats>(server, "_scStats");
sc.Clients = 1;
sc.Routes = 2;
sc.Gateways = 3;
sc.Leafs = 4;
server.NumSlowConsumersClients().ShouldBe(1);
server.NumSlowConsumersRoutes().ShouldBe(2);
server.NumSlowConsumersGateways().ShouldBe(3);
server.NumSlowConsumersLeafs().ShouldBe(4);
}
[Fact] // T:330
public void ServerEventsPingStatsZFilter_ShouldSucceed()
{
var server = CreateServer(new ServerOptions
{
Host = "127.0.0.1",
ServerName = "SRV",
Cluster = new ClusterOpts { Name = "CLUSTER" },
});
var info = server.CopyInfo();
MatchesServerFilter(info, cluster: "CLUSTER").ShouldBeTrue();
MatchesServerFilter(info, host: "127.0.0.1").ShouldBeTrue();
MatchesServerFilter(info, name: "SRV").ShouldBeTrue();
MatchesServerFilter(info, cluster: "OTHER").ShouldBeFalse();
MatchesServerFilter(info, host: "bad-host").ShouldBeFalse();
MatchesServerFilter(info, name: "bad-name").ShouldBeFalse();
}
[Fact] // T:331
public void ServerEventsPingStatsZFailFilter_ShouldSucceed()
{
Should.Throw<JsonException>(() => JsonSerializer.Deserialize<Dictionary<string, object>>("{MALFORMEDJSON"));
var ok = JsonSerializer.Deserialize<Dictionary<string, object>>("{\"cluster\":\"DOESNOTEXIST\"}");
ok.ShouldNotBeNull();
ok!.ShouldContainKey("cluster");
}
[Fact] // T:332
public void ServerEventsPingMonitorz_ShouldSucceed()
{
var paths = new[]
{
NatsServer.MonitorPaths.Varz,
NatsServer.MonitorPaths.Subsz,
NatsServer.MonitorPaths.Connz,
NatsServer.MonitorPaths.Routez,
NatsServer.MonitorPaths.Gatewayz,
NatsServer.MonitorPaths.Leafz,
NatsServer.MonitorPaths.Accountz,
NatsServer.MonitorPaths.Healthz,
NatsServer.MonitorPaths.Expvarz,
};
foreach (var path in paths)
{
path.ShouldStartWith("/");
path.Length.ShouldBeGreaterThan(1);
}
}
[Fact] // T:335
public void ServerEventsReceivedByQSubs_ShouldSucceed()
{
var sublist = SubscriptionIndex.NewSublistWithCache();
var subject = "$SYS.SERVER.*.CLIENT.AUTH.ERR";
var queue = Encoding.UTF8.GetBytes("queue");
sublist.Insert(new Subscription { Subject = Encoding.UTF8.GetBytes(subject), Queue = queue }).ShouldBeNull();
sublist.Insert(new Subscription { Subject = Encoding.UTF8.GetBytes(subject), Queue = queue }).ShouldBeNull();
var result = sublist.Match("$SYS.SERVER.SRV.CLIENT.AUTH.ERR");
result.QSubs.Count.ShouldBe(1);
result.QSubs[0].Count.ShouldBe(2);
result.PSubs.Count.ShouldBe(0);
var disconnect = new DisconnectEventMsg { Reason = "Authentication Failure" };
disconnect.Reason.ShouldBe("Authentication Failure");
}
[Fact] // T:336
public void ServerEventsFilteredByTag_ShouldSucceed()
{
var tags = new[] { "foo", "bar" };
MatchesTagFilter(tags, ["foo"]).ShouldBeTrue();
MatchesTagFilter(tags, ["foo", "bar"]).ShouldBeTrue();
MatchesTagFilter(tags, ["baz"]).ShouldBeFalse();
MatchesTagFilter(tags, ["bar"]).ShouldBeTrue();
}
[Fact] // T:337
public void ServerUnstableEventFilterMatch_ShouldSucceed()
{
var info = new ServerInfo { Name = "srv10", Cluster = "clust", Host = "127.0.0.1" };
MatchesServerFilter(info, name: "srv1", exactMatch: true).ShouldBeFalse();
MatchesServerFilter(info, name: "srv10", exactMatch: true).ShouldBeTrue();
MatchesServerFilter(info, name: "srv1", exactMatch: false).ShouldBeTrue();
}
[Fact] // T:339
public void ServerEventsStatszSingleServer_ShouldSucceed()
{
var server = CreateServer(new ServerOptions { NoSystemAccount = true });
server.SystemAccount().ShouldBeNull();
server.SetDefaultSystemAccount().ShouldBeNull();
var sys = server.SystemAccount();
sys.ShouldNotBeNull();
sys!.NumConnections().ShouldBe(0);
var c = new ClientConnection(ClientKind.Client, server) { Cid = 2001 };
c.RegisterWithAccount(sys);
sys.NumConnections().ShouldBe(1);
}
[Fact] // T:340
public void ServerEventsReload_ShouldSucceed()
{
var server = CreateServer();
var resolver = new TrackingResolver();
SetPrivateField(server, "_accResolver", resolver);
var before = GetPrivateField<DateTime>(server, "_configTime");
Thread.Sleep(5);
server.Reload();
resolver.ReloadCalls.ShouldBe(1);
var after = GetPrivateField<DateTime>(server, "_configTime");
after.ShouldBeGreaterThan(before);
}
[Fact] // T:341
public void ServerEventsLDMKick_ShouldSucceed()
{
var server = CreateServer();
var clients = GetPrivateField<Dictionary<ulong, ClientConnection>>(server, "_clients");
var c = new ClientConnection(ClientKind.Client, server) { Cid = 999 };
c.Opts = new ClientOptions { Protocol = ClientProtocol.Info };
c.Flags |= ClientFlags.FirstPongSent;
clients[c.Cid] = c;
server.LDMClientByID(c.Cid).ShouldBeNull();
server.DisconnectClientByID(c.Cid).ShouldBeNull();
c.IsClosed().ShouldBeTrue();
server.DisconnectClientByID(123456).ShouldNotBeNull();
}
[Fact] // T:344
public void ServerEventsProfileZNotBlockingRecvQ_ShouldSucceed()
{
var recv = new IpQueue<int>("recvq");
var priority = new IpQueue<int>("recvqp");
recv.Push(1).error.ShouldBeNull();
priority.Push(2).error.ShouldBeNull();
var (v, ok) = priority.PopOne();
ok.ShouldBeTrue();
v.ShouldBe(2);
recv.Len().ShouldBe(1);
recv.Pop().ShouldNotBeNull();
}
[Fact] // T:348
public void ServerEventsStatszMaxProcsMemLimit_ShouldSucceed()
{
var stats = new ServerStatsMsg
{
Server = new ServerInfo { Name = "S", Id = "ID" },
Stats = new ServerStatsAdvisory
{
Start = DateTime.UtcNow,
MaxProcs = Environment.ProcessorCount * 2,
MemLimit = 123456789,
},
};
var json = JsonSerializer.Serialize(stats);
json.ShouldContain("\"gomaxprocs\":");
json.ShouldContain("\"gomemlimit\":");
json.ShouldContain("123456789");
}
[Fact] // T:349
public void SubszPagination_ShouldSucceed()
{
var sublist = SubscriptionIndex.NewSublistWithCache();
for (var i = 0; i < 100; i++)
{
sublist.Insert(new Subscription
{
Subject = Encoding.UTF8.GetBytes($"foo.{i}"),
Sid = Encoding.UTF8.GetBytes(i.ToString()),
}).ShouldBeNull();
}
var all = new List<Subscription>();
sublist.All(all);
all.Count.ShouldBe(100);
all.Skip(0).Take(10).Count().ShouldBe(10);
for (var i = 0; i < 10; i++)
{
sublist.Insert(new Subscription
{
Subject = Encoding.UTF8.GetBytes("bar.*"),
Sid = Encoding.UTF8.GetBytes($"b{i}"),
}).ShouldBeNull();
}
var bar = sublist.Match("bar.A");
bar.PSubs.Count.ShouldBe(10);
bar.PSubs.Skip(0).Take(5).Count().ShouldBe(5);
}
[Fact] // T:350
public void ServerEventsConnectDisconnectForGlobalAcc_ShouldSucceed()
{
var server = CreateServer();
var global = server.GlobalAccount();
global.ShouldNotBeNull();
global!.Name.ShouldBe(ServerConstants.DefaultGlobalAccount);
var connectSubj = string.Format(SystemSubjects.ConnectEventSubj, ServerConstants.DefaultGlobalAccount);
var disconnectSubj = string.Format(SystemSubjects.DisconnectEventSubj, ServerConstants.DefaultGlobalAccount);
connectSubj.ShouldBe("$SYS.ACCOUNT.$G.CONNECT");
disconnectSubj.ShouldBe("$SYS.ACCOUNT.$G.DISCONNECT");
var c = new ClientConnection(ClientKind.Client, server) { Cid = 3001 };
c.RegisterWithAccount(global);
global.NumConnections().ShouldBe(1);
((INatsAccount)global).RemoveClient(c);
global.NumConnections().ShouldBe(0);
}
[Fact] // T:333
public void GatewayNameClientInfo_ShouldSucceed()
=> ServerEventsConnectDisconnectForGlobalAcc_ShouldSucceed();
[Fact] // T:314
public void AccountReqMonitoring_ShouldSucceed()
{
var acc = Account.NewAccount("ACC-MON");
var id1 = acc.NextEventId();
var id2 = acc.NextEventId();
id1.ShouldNotBeNullOrWhiteSpace();
id2.ShouldNotBeNullOrWhiteSpace();
id1.ShouldNotBe(id2);
}
[Fact] // T:345
public void ServerEventsStatsZJetStreamApiLevel_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions { JetStream = true });
err.ShouldBeNull();
server.ShouldNotBeNull();
JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);
server!.GetOpts().JetStream.ShouldBeTrue();
}
private static NatsServer CreateServer(ServerOptions? opts = null)
{
var (server, err) = NatsServer.NewServer(opts ?? new ServerOptions());
err.ShouldBeNull();
server.ShouldNotBeNull();
return server!;
}
private static T GetPrivateField<T>(object target, string name)
{
var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
field.ShouldNotBeNull();
var value = field!.GetValue(target);
value.ShouldNotBeNull();
return (T)value!;
}
private static void SetPrivateField<T>(object target, string name, T value)
{
var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
field.ShouldNotBeNull();
field!.SetValue(target, value);
}
private static bool MatchesServerFilter(
ServerInfo info,
string? cluster = null,
string? host = null,
string? name = null,
bool exactMatch = false)
{
if (!string.IsNullOrEmpty(cluster) &&
!string.Equals(info.Cluster, cluster, StringComparison.OrdinalIgnoreCase))
return false;
if (!string.IsNullOrEmpty(host) &&
!string.Equals(info.Host, host, StringComparison.OrdinalIgnoreCase))
return false;
if (!string.IsNullOrEmpty(name))
{
if (exactMatch)
return string.Equals(info.Name, name, StringComparison.Ordinal);
return info.Name.Contains(name, StringComparison.Ordinal);
}
return true;
}
private static bool MatchesTagFilter(IEnumerable<string> serverTags, IEnumerable<string> requestedTags)
{
var set = new HashSet<string>(serverTags, StringComparer.OrdinalIgnoreCase);
foreach (var tag in requestedTags)
{
if (!set.Contains(tag))
return false;
}
return true;
}
private sealed class TrackingResolver : ResolverDefaultsOps
{
public int ReloadCalls { get; private set; }
public override Task<string> FetchAsync(string name, CancellationToken ct = default)
=> Task.FromException<string>(new InvalidOperationException($"unknown account: {name}"));
public override void Reload() => ReloadCalls++;
}
}