Files
natsnet/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/AccountTests.cs
2026-02-28 20:28:13 -05:00

456 lines
16 KiB
C#

using System.Text;
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 AccountTests
{
[Fact] // T:80
public void AccountMultipleServiceImportsWithSameSubjectFromDifferentAccounts_ShouldSucceed()
{
var importer = Account.NewAccount("CLIENTS");
var svcE = Account.NewAccount("SVC-E");
var svcW = Account.NewAccount("SVC-W");
importer.Imports.Services = new Dictionary<string, List<ServiceImportEntry>>
{
["SvcReq.>"] =
[
new ServiceImportEntry { Account = svcE, From = "SvcReq.>", To = "SvcReq.>" },
new ServiceImportEntry { Account = svcW, From = "SvcReq.>", To = "SvcReq.>" },
],
};
var copied = Account.NewAccount("CLIENTS");
importer.ShallowCopy(copied);
copied.Imports.Services.ShouldNotBeNull();
copied.Imports.Services!.ShouldContainKey("SvcReq.>");
copied.Imports.Services["SvcReq.>"].Count.ShouldBe(2);
var accounts = copied.Imports.Services["SvcReq.>"]
.Select(static si => si.Account?.Name)
.OrderBy(static n => n)
.ToArray();
accounts.ShouldBe(["SVC-E", "SVC-W"]);
}
[Fact] // T:83
public void AccountBasicRouteMapping_ShouldSucceed()
{
var acc = Account.NewAccount("global");
acc.AddMapping("foo", "bar").ShouldBeNull();
var (dest, mapped) = acc.SelectMappedSubject("foo");
mapped.ShouldBeTrue();
dest.ShouldBe("bar");
acc.RemoveMapping("foo").ShouldBeTrue();
var (destAfterRemove, mappedAfterRemove) = acc.SelectMappedSubject("foo");
mappedAfterRemove.ShouldBeFalse();
destAfterRemove.ShouldBe("foo");
}
[Fact] // T:84
public void AccountWildcardRouteMapping_ShouldSucceed()
{
var acc = Account.NewAccount("global");
acc.AddMapping("foo.*.*", "bar.$2.$1").ShouldBeNull();
acc.AddMapping("bar.*.>", "baz.$1.>").ShouldBeNull();
var (mappedDest, mapped) = acc.SelectMappedSubject("foo.1.2");
mapped.ShouldBeTrue();
mappedDest.ShouldBe("bar.2.1");
var (remappedDest, remapped) = acc.SelectMappedSubject("bar.2.1");
remapped.ShouldBeTrue();
remappedDest.ShouldBe("baz.2.1");
}
[Fact] // T:85
public void AccountRouteMappingChangesAfterClientStart_ShouldSucceed()
{
var acc = Account.NewAccount("global");
var (beforeDest, beforeMapped) = acc.SelectMappedSubject("foo");
beforeMapped.ShouldBeFalse();
beforeDest.ShouldBe("foo");
acc.AddMapping("foo", "bar").ShouldBeNull();
var (afterAddDest, afterAddMapped) = acc.SelectMappedSubject("foo");
afterAddMapped.ShouldBeTrue();
afterAddDest.ShouldBe("bar");
acc.RemoveMapping("foo").ShouldBeTrue();
var (afterRemoveDest, afterRemoveMapped) = acc.SelectMappedSubject("foo");
afterRemoveMapped.ShouldBeFalse();
afterRemoveDest.ShouldBe("foo");
}
[Fact] // T:88
public void GlobalAccountRouteMappingsConfiguration_ShouldSucceed()
{
var acc = Account.NewAccount("global");
acc.AddMapping("foo", "bar").ShouldBeNull();
acc.AddWeightedMappings(
"foo.*",
MapDest.New("bar.v1.$1", 40),
MapDest.New("baz.v2.$1", 20)).ShouldBeNull();
acc.AddMapping("bar.*.*", "RAB.$2.$1").ShouldBeNull();
var (simpleDest, simpleMapped) = acc.SelectMappedSubject("foo");
simpleMapped.ShouldBeTrue();
simpleDest.ShouldBe("bar");
var (crossDest, crossMapped) = acc.SelectMappedSubject("bar.11.22");
crossMapped.ShouldBeTrue();
crossDest.ShouldBe("RAB.22.11");
var counts = new Dictionary<string, int>(StringComparer.Ordinal);
for (var i = 0; i < 400; i++)
{
var (dest, mapped) = acc.SelectMappedSubject("foo.22");
mapped.ShouldBeTrue();
counts.TryGetValue(dest, out var current);
counts[dest] = current + 1;
}
counts.ShouldContainKey("bar.v1.22");
counts.ShouldContainKey("baz.v2.22");
counts.ShouldContainKey("foo.22");
}
[Fact] // T:90
public void AccountRouteMappingsWithLossInjection_ShouldSucceed()
{
var acc = Account.NewAccount("global");
acc.AddWeightedMappings("foo", MapDest.New("foo", 80)).ShouldBeNull();
acc.AddWeightedMappings("bar", MapDest.New("bar", 0)).ShouldBeNull();
var fooMapped = 0;
var fooUnmapped = 0;
for (var i = 0; i < 2000; i++)
{
var (_, mapped) = acc.SelectMappedSubject("foo");
if (mapped) fooMapped++;
else fooUnmapped++;
}
fooMapped.ShouldBeGreaterThan(0);
fooUnmapped.ShouldBeGreaterThan(0);
for (var i = 0; i < 200; i++)
{
var (dest, mapped) = acc.SelectMappedSubject("bar");
mapped.ShouldBeFalse();
dest.ShouldBe("bar");
}
}
[Fact] // T:91
public void AccountRouteMappingsWithOriginClusterFilter_ShouldSucceed()
{
var acc = Account.NewAccount("global");
acc.AddWeightedMappings("foo", new MapDest { Subject = "bar", Weight = 100, Cluster = "SYN" })
.ShouldBeNull();
var (dest, mapped) = acc.SelectMappedSubject("foo");
mapped.ShouldBeTrue();
dest.ShouldBe("foo");
}
[Fact] // T:92
public void AccountServiceImportWithRouteMappings_ShouldSucceed()
{
var exporter = Account.NewAccount("foo");
var importer = Account.NewAccount("bar");
exporter.AddMapping("request", "request.v2").ShouldBeNull();
importer.Imports.Services = new Dictionary<string, List<ServiceImportEntry>>
{
["request"] = [new ServiceImportEntry { Account = exporter, From = "request", To = "request" }],
};
var (mappedSubject, mapped) = exporter.SelectMappedSubject("request");
mapped.ShouldBeTrue();
mappedSubject.ShouldBe("request.v2");
importer.Imports.Services.ShouldContainKey("request");
importer.Imports.Services["request"].Count.ShouldBe(1);
importer.Imports.Services["request"][0].To.ShouldBe("request");
}
[Fact] // T:93
public void AccountImportsWithWildcardSupport_ShouldSucceed()
{
var acc = Account.NewAccount("bar");
acc.AddMapping("request.*", "my.request.$1").ShouldBeNull();
acc.AddMapping("events.*", "foo.events.$1").ShouldBeNull();
acc.AddMapping("info.*.*.>", "foo.info.$2.$1.>").ShouldBeNull();
acc.SelectMappedSubject("request.22").ShouldBe(("my.request.22", true));
acc.SelectMappedSubject("events.22").ShouldBe(("foo.events.22", true));
acc.SelectMappedSubject("info.11.22.bar").ShouldBe(("foo.info.22.11.bar", true));
}
[Fact] // T:94
public void AccountImportsWithWildcardSupportStreamAndService_ShouldSucceed()
{
var source = Account.NewAccount("foo");
var target = Account.NewAccount("bar");
target.Imports.Services = new Dictionary<string, List<ServiceImportEntry>>
{
["request.*"] = [new ServiceImportEntry
{
Account = source,
From = "request.*",
To = "my.request.$1",
Transform = RequireTransform("request.*", "my.request.$1"),
}],
};
target.Imports.Streams =
[
new StreamImportEntry
{
Account = source,
From = "events.*",
To = "foo.events.$1",
Transform = RequireTransform("events.*", "foo.events.$1"),
},
];
target.Imports.Services["request.*"].Single().Transform!.TransformSubject("request.22")
.ShouldBe("my.request.22");
target.Imports.Streams.Single().Transform!.TransformSubject("events.22")
.ShouldBe("foo.events.22");
}
[Fact] // T:97
public void AccountSystemPermsWithGlobalAccess_ShouldSucceed()
{
var global = Account.NewAccount("$G");
var system = Account.NewAccount("$SYS");
system.Exports.Services = new Dictionary<string, ServiceExportEntry>
{
["$SYS.REQ.>"] = new ServiceExportEntry { Account = system },
};
global.IsExportService("$SYS.REQ.INFO").ShouldBeFalse();
system.IsExportService("$SYS.REQ.INFO").ShouldBeTrue();
system.CheckServiceExportApproved(global, "$SYS.REQ.INFO", null).ShouldBeTrue();
}
[Fact] // T:81
public void MultipleStreamImportsWithSameSubjectDifferentPrefix_ShouldSucceed()
{
var fooAccount = Account.NewAccount("foo");
var barAccount = Account.NewAccount("bar");
var importAccount = Account.NewAccount("import");
fooAccount.AddStreamExport("test", null).ShouldBeNull();
barAccount.AddStreamExport("test", null).ShouldBeNull();
importAccount.AddStreamImport(fooAccount, "test", "foo").ShouldBeNull();
importAccount.AddStreamImport(barAccount, "test", "bar").ShouldBeNull();
importAccount.Imports.Streams.ShouldNotBeNull();
importAccount.Imports.Streams!.Count.ShouldBe(2);
importAccount.Imports.Streams.Select(si => si.To).OrderBy(static t => t).ToArray()
.ShouldBe(["bar.test", "foo.test"]);
}
[Fact] // T:82
public void MultipleStreamImportsWithSameSubject_ShouldSucceed()
{
var fooAccount = Account.NewAccount("foo");
var barAccount = Account.NewAccount("bar");
var importAccount = Account.NewAccount("import");
fooAccount.AddStreamExport("test", null).ShouldBeNull();
barAccount.AddStreamExport("test", null).ShouldBeNull();
importAccount.AddStreamImport(fooAccount, "test", string.Empty).ShouldBeNull();
importAccount.AddStreamImport(fooAccount, "test", string.Empty).ShouldBe(ServerErrors.ErrStreamImportDuplicate);
importAccount.AddStreamImport(barAccount, "test", string.Empty).ShouldBeNull();
importAccount.Imports.Streams.ShouldNotBeNull();
importAccount.Imports.Streams!.Count.ShouldBe(2);
importAccount.Imports.Streams.Select(si => si.Account?.Name).OrderBy(static n => n).ToArray()
.ShouldBe(["bar", "foo"]);
}
[Fact] // T:95
public void BenchmarkNewRouteReply()
{
var globalAccount = Account.NewAccount("$G");
var first = globalAccount.NewServiceReply(tracking: false);
var second = globalAccount.NewServiceReply(tracking: false);
var tracked = globalAccount.NewServiceReply(tracking: true);
first.Length.ShouldBeGreaterThan(20);
second.Length.ShouldBeGreaterThan(20);
first.SequenceEqual(second).ShouldBeFalse();
var trackedText = Encoding.ASCII.GetString(tracked);
trackedText.EndsWith(".T", StringComparison.Ordinal).ShouldBeTrue();
}
[Fact] // T:98
public void ImportSubscriptionPartialOverlapWithPrefix_ShouldSucceed()
{
var transform = RequireTransform(">", "myprefix.>");
var mapped = transform.TransformSubject("test");
mapped.ShouldBe("myprefix.test");
foreach (var filter in new[] { ">", "myprefix.*", "myprefix.>", "myprefix.test", "*.>", "*.*", "*.test" })
SubscriptionIndex.SubjectIsSubsetMatch(mapped, filter).ShouldBeTrue();
}
[Fact] // T:99
public void ImportSubscriptionPartialOverlapWithTransform_ShouldSucceed()
{
var transform = RequireTransform("*.*.>", "myprefix.$2.$1.>");
var mapped = transform.TransformSubject("1.2.test");
mapped.ShouldBe("myprefix.2.1.test");
foreach (var filter in new[]
{
">", "*.*.*.>", "*.2.*.>", "*.*.1.>", "*.2.1.>", "*.*.*.*", "*.2.1.*", "*.*.*.test",
"*.*.1.test", "*.2.*.test", "*.2.1.test", "myprefix.*.*.*", "myprefix.>", "myprefix.*.>",
"myprefix.*.*.>", "myprefix.2.>", "myprefix.2.1.>", "myprefix.*.1.>", "myprefix.2.*.>",
"myprefix.2.1.*", "myprefix.*.*.test", "myprefix.2.1.test",
})
{
SubscriptionIndex.SubjectIsSubsetMatch(mapped, filter).ShouldBeTrue();
}
}
[Fact] // T:104
public void AccountUserSubPermsWithQueueGroups_ShouldSucceed()
{
var c = new ClientConnection(ClientKind.Client);
c.RegisterUser(new User
{
Username = "user",
Password = "pass",
Permissions = new Permissions
{
Publish = new SubjectPermission { Allow = ["foo.restricted"] },
Subscribe = new SubjectPermission
{
Allow = ["foo.>"],
Deny = ["foo.restricted"],
},
Response = new ResponsePermission { MaxMsgs = 1, Expires = TimeSpan.Zero },
},
});
c.Perms.ShouldNotBeNull();
c.Perms!.Sub.Allow.ShouldNotBeNull();
c.Perms.Sub.Deny.ShouldNotBeNull();
c.Perms.Sub.Allow!.Match("foo.restricted").PSubs.Count.ShouldBeGreaterThan(0);
c.Perms.Sub.Deny!.Match("foo.restricted").PSubs.Count.ShouldBeGreaterThan(0);
var (_, queue) = ClientConnection.SplitSubjectQueue("foo.> qg");
queue.ShouldNotBeNull();
Encoding.ASCII.GetString(queue!).ShouldBe("qg");
}
[Fact] // T:106
public void AccountImportOwnExport_ShouldSucceed()
{
var a = Account.NewAccount("A");
a.Exports.Services = new Dictionary<string, ServiceExportEntry>
{
["echo"] = new ServiceExportEntry
{
Account = a,
Latency = new InternalServiceLatency { Subject = "latency.echo", Sampling = 100 },
},
};
a.Imports.Services = new Dictionary<string, List<ServiceImportEntry>>
{
["echo"] = [new ServiceImportEntry { Account = a, From = "echo", To = "echo" }],
};
a.IsExportService("echo").ShouldBeTrue();
a.CheckServiceExportApproved(a, "echo", null).ShouldBeTrue();
a.Imports.Services["echo"].Count.ShouldBe(1);
}
[Fact] // T:107
public void AccountImportDuplicateResponseDeliveryWithLeafnodes_ShouldSucceed()
{
var exporter = Account.NewAccount("A");
var importer = Account.NewAccount("B");
exporter.Exports.Services = new Dictionary<string, ServiceExportEntry>
{
["foo"] = new ServiceExportEntry { Account = exporter, ResponseType = ServiceRespType.Streamed },
};
importer.Imports.Services = new Dictionary<string, List<ServiceImportEntry>>
{
["foo"] = [new ServiceImportEntry
{
Account = exporter,
From = "foo",
To = "foo",
ResponseType = ServiceRespType.Streamed,
}],
};
importer.Imports.Services["foo"].Count.ShouldBe(1);
importer.Imports.Services["foo"][0].DidDeliver.ShouldBeFalse();
exporter.CheckServiceExportApproved(importer, "foo", null).ShouldBeTrue();
}
[Fact] // T:109
public void AccountServiceAndStreamExportDoubleDelivery_ShouldSucceed()
{
var tenant = Account.NewAccount("tenant1");
tenant.Exports.Streams = new Dictionary<string, StreamExport>
{
["DW.>"] = new StreamExport(),
};
tenant.Exports.Services = new Dictionary<string, ServiceExportEntry>
{
["DW.>"] = new ServiceExportEntry { Account = tenant },
};
tenant.CheckStreamExportApproved(tenant, "DW.test.123", null).ShouldBeTrue();
tenant.CheckServiceExportApproved(tenant, "DW.test.123", null).ShouldBeTrue();
tenant.IsExportService("DW.test.123").ShouldBeTrue();
}
[Fact] // T:110
public void AccountServiceImportNoResponders_ShouldSucceed()
{
var exporter = Account.NewAccount("accExp");
var importer = Account.NewAccount("accImp");
importer.Imports.Services = new Dictionary<string, List<ServiceImportEntry>>
{
["foo"] = [new ServiceImportEntry { Account = exporter, From = "foo", To = "foo" }],
};
importer.Imports.Services["foo"].Count.ShouldBe(1);
exporter.CheckServiceExportApproved(importer, "foo", null).ShouldBeFalse();
}
private static SubjectTransform RequireTransform(string src, string dest)
{
var (transform, err) = SubjectTransform.New(src, dest);
err.ShouldBeNull();
transform.ShouldNotBeNull();
return transform!;
}
}