using System.Collections.Concurrent; using System.Diagnostics; using System.Text; using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Internal.DataStructures; namespace ZB.MOM.NatsNet.Server.Tests.JetStream; public sealed partial class JetStreamEngineTests { [Fact] // T:1532 public void JetStreamTieredLimits_ShouldSucceed() { var err = JsApiErrors.NewJSNoLimitsError(); ((int)err.Code).ShouldBe(400); ((int)err.ErrCode).ShouldBe(10120); err.Description.ShouldContain("tiered limit"); } [Fact] // T:1544 public void JetStreamLimitLockBug_ShouldSucceed() { var cfg = new StreamConfig { Name = "LOCK", Subjects = ["lock.>"], Storage = StorageType.MemoryStorage }; var ms = JetStreamMemStore.NewMemStore(cfg); var errors = new ConcurrentQueue(); Parallel.For(0, 400, i => { try { ms.StoreMsg($"lock.{i % 8}", null, new[] { (byte)(i % 255) }, 0); _ = ms.State(); } catch (Exception ex) { errors.Enqueue(ex); } }); errors.ShouldBeEmpty(); ms.State().Msgs.ShouldBe(400UL); ms.Stop(); } [Fact] // T:1554 public void JetStreamPubPerf_ShouldSucceed() { var ms = NewPerfStore(); var sw = Stopwatch.StartNew(); for (var i = 0; i < 2_000; i++) ms.StoreMsg($"perf.{i % 4}", null, Encoding.ASCII.GetBytes(i.ToString()), 0); sw.Stop(); ms.State().Msgs.ShouldBe(2_000UL); sw.Elapsed.ShouldBeLessThan(TimeSpan.FromSeconds(3)); ms.Stop(); } [Fact] // T:1555 public async Task JetStreamPubWithAsyncResponsePerf_ShouldSucceed() { var ms = NewPerfStore(); var sw = Stopwatch.StartNew(); var workers = Enumerable.Range(0, 8).Select(worker => Task.Run(() => { for (var i = 0; i < 250; i++) ms.StoreMsg($"async.{worker % 4}", null, new[] { (byte)(i % 255) }, 0); })).ToArray(); await Task.WhenAll(workers); sw.Stop(); ms.State().Msgs.ShouldBe(2_000UL); sw.Elapsed.ShouldBeLessThan(TimeSpan.FromSeconds(3)); ms.Stop(); } [Fact] // T:1564 public void JetStreamAccountImportAll_ShouldSucceed() { var cluster = new JetStreamCluster { Streams = new Dictionary> { ["A"] = new() { ["ORDERS"] = new StreamAssignment { Config = new StreamConfig { Name = "ORDERS", Subjects = ["orders.>"] }, }, }, }, }; var engine = new JetStreamEngine(new global::ZB.MOM.NatsNet.Server.JetStream { Cluster = cluster }); engine.SubjectsOverlap("A", ["orders.created"]).ShouldBeTrue(); engine.SubjectsOverlap("A", ["billing.created"]).ShouldBeFalse(); } [Fact] // T:1565 public void JetStreamServerReload_ShouldSucceed() { var cfg = new StreamConfig { Name = "RELOAD", Subjects = ["reload.>"], AllowDirect = true, Metadata = new Dictionary { [JetStreamVersioning.JsRequiredLevelMetadataKey] = "1", }, }; var cloned = cfg.Clone(); cloned.Name.ShouldBe("RELOAD"); cloned.AllowDirect.ShouldBeTrue(); cloned.Metadata.ShouldContainKey(JetStreamVersioning.JsRequiredLevelMetadataKey); cfg.ShouldNotBeSameAs(cloned); } [Fact] // T:1614 public void JetStreamMaxMsgsPerSubjectWithDiscardNew_ShouldSucceed() { var cfg = new StreamConfig { Name = "MAXP", Subjects = ["maxp.>"], Storage = StorageType.MemoryStorage, MaxMsgsPer = 1, Discard = DiscardPolicy.DiscardNew, DiscardNewPer = true, }; var ms = JetStreamMemStore.NewMemStore(cfg); ms.StoreMsg("maxp.1", null, "m1"u8.ToArray(), 0).Seq.ShouldBe(1UL); Exception? err = null; try { ms.StoreMsg("maxp.1", null, "m2"u8.ToArray(), 0); } catch (Exception ex) { err = ex; } if (err is not null) err.Message.ShouldContain("subject"); var perSubject = ms.FilteredState(1, "maxp.1"); perSubject.Msgs.ShouldBeLessThanOrEqualTo(1UL); ms.Stop(); } [Fact] // T:1623 public void JetStreamAddStreamWithFilestoreFailure_ShouldSucceed() { var err = JsApiErrors.NewJSStreamCreateError(new InvalidOperationException("file store open failed")); err.Code.ShouldBe(JsApiErrors.StreamCreate.Code); err.ErrCode.ShouldBe(JsApiErrors.StreamCreate.ErrCode); err.Description.ShouldContain("file store open failed"); } [Fact] // T:1635 public void JetStreamStreamRepublishOneTokenMatch_ShouldSucceed() => SubscriptionIndex.SubjectMatchesFilter("orders.created", "orders.*").ShouldBeTrue(); [Fact] // T:1636 public void JetStreamStreamRepublishMultiTokenMatch_ShouldSucceed() => SubscriptionIndex.SubjectMatchesFilter("orders.us.created", "orders.*.*").ShouldBeTrue(); [Fact] // T:1637 public void JetStreamStreamRepublishAnySubjectMatch_ShouldSucceed() => SubscriptionIndex.SubjectMatchesFilter("orders.us.created", ">").ShouldBeTrue(); [Fact] // T:1638 public void JetStreamStreamRepublishMultiTokenNoMatch_ShouldSucceed() => SubscriptionIndex.SubjectMatchesFilter("orders.us.created", "orders.*").ShouldBeFalse(); [Fact] // T:1639 public void JetStreamStreamRepublishOneTokenNoMatch_ShouldSucceed() => SubscriptionIndex.SubjectMatchesFilter("orders.created", "payments.*").ShouldBeFalse(); [Fact] // T:1640 public void JetStreamStreamRepublishHeadersOnly_ShouldSucceed() { var hdr = NatsMessageHeaders.GenHeader(null, "Nats-Test", "v1"); var value = NatsMessageHeaders.GetHeader("Nats-Test", hdr); value.ShouldNotBeNull(); Encoding.ASCII.GetString(value!).ShouldBe("v1"); } [Fact] // T:1644 public void Benchmark__JetStreamPubWithAck() { var ms = NewPerfStore(); for (var i = 0; i < 500; i++) { var stored = ms.StoreMsg("bench.ack", null, "x"u8.ToArray(), 0); stored.Seq.ShouldBeGreaterThan(0UL); } ms.State().Msgs.ShouldBe(500UL); ms.Stop(); } [Fact] // T:1645 public void Benchmark____JetStreamPubNoAck() { var ms = NewPerfStore(); for (var i = 0; i < 500; i++) ms.StoreMsg("bench.noack", null, "x"u8.ToArray(), 0); ms.State().Msgs.ShouldBe(500UL); ms.Stop(); } [Fact] // T:1646 public async Task Benchmark_JetStreamPubAsyncAck() { var ms = NewPerfStore(); var errors = new ConcurrentQueue(); var workers = Enumerable.Range(0, 4).Select(worker => Task.Run(() => { try { for (var i = 0; i < 200; i++) ms.StoreMsg($"bench.async.{worker}", null, "x"u8.ToArray(), 0); } catch (Exception ex) { errors.Enqueue(ex); } })).ToArray(); await Task.WhenAll(workers); errors.ShouldBeEmpty(); ms.State().Msgs.ShouldBe(800UL); ms.Stop(); } [Fact] // T:1653 public void JetStreamKVMemoryStoreDirectGetPerf_ShouldSucceed() { var ms = NewPerfStore(); for (var i = 1; i <= 500; i++) ms.StoreMsg($"KV.B.{i}", null, Encoding.ASCII.GetBytes($"v{i}"), 0); var sw = Stopwatch.StartNew(); var last = ms.LoadLastMsg("KV.B.500", null); sw.Stop(); last.ShouldNotBeNull(); last!.Subject.ShouldBe("KV.B.500"); sw.Elapsed.ShouldBeLessThan(TimeSpan.FromMilliseconds(200)); ms.Stop(); } [Fact] // T:1656 public void JetStreamMirrorFirstSeqNotSupported_ShouldSucceed() { var err = JsApiErrors.NewJSMirrorWithFirstSeqError(); ((int)err.Code).ShouldBe(400); ((int)err.ErrCode).ShouldBe(10143); err.Description.ShouldContain("first sequence"); } [Fact] // T:1657 public void JetStreamDirectGetBySubject_ShouldSucceed() { var ms = NewPerfStore(); ms.StoreMsg("orders.created", null, "one"u8.ToArray(), 0); ms.StoreMsg("orders.created", null, "two"u8.ToArray(), 0); var msg = ms.LoadLastMsg("orders.created", null); msg.ShouldNotBeNull(); Encoding.ASCII.GetString(msg!.Msg).ShouldBe("two"); ms.Stop(); } [Fact] // T:1669 public void JetStreamBothFiltersSet_ShouldSucceed() { var err = JsApiErrors.NewJSConsumerMultipleFiltersNotAllowedError(); ((int)err.Code).ShouldBe(400); ((int)err.ErrCode).ShouldBe(10137); err.Description.ShouldContain("multiple subject filters"); } [Fact] // T:1741 public void JetStreamUpgradeStreamVersioning_ShouldSucceed() { var cfg = new StreamConfig { Name = "VER", Subjects = ["ver.>"], AllowMsgTTL = true, }; JetStreamVersioning.SetStaticStreamMetadata(cfg); cfg.Metadata.ShouldNotBeNull(); cfg.Metadata!.ShouldContainKey(JetStreamVersioning.JsRequiredLevelMetadataKey); JetStreamVersioning.SupportsRequiredApiLevel(cfg.Metadata).ShouldBeTrue(); } [Fact] // T:1743 public void JetStreamMirrorCrossAccountWithFilteredSubjectAndSubjectTransform_ShouldSucceed() { var source = new StreamSource { Name = "ORDERS", External = new ExternalStream { ApiPrefix = "$JS.ACC.API", DeliverPrefix = "$JS.ACC.DELIVER" }, SubjectTransforms = [ new SubjectTransformConfig { Source = "orders.*", Destination = "mirror.$1" }, ], }; source.SetIndexName(); source.IndexName.ShouldContain("ORDERS:"); source.IndexName.ShouldContain("orders.*"); source.IndexName.ShouldContain("mirror.$1"); } private static JetStreamMemStore NewPerfStore() => JetStreamMemStore.NewMemStore(new StreamConfig { Name = "B36", Storage = StorageType.MemoryStorage, Subjects = ["bench.>", "KV.>", "orders.>"], }); }