400 lines
14 KiB
C#
400 lines
14 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: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!;
|
|
}
|
|
}
|