using Shouldly; using ZB.MOM.NatsNet.Server; namespace ZB.MOM.NatsNet.Server.Tests.JetStream; public sealed class StreamLifecycleGroupBTests { [Fact] public void MaxMsgSize_UsesConfiguredLimit() { var stream = CreateStream(new StreamConfig { Name = "ORDERS", Subjects = ["orders.*"], Storage = StorageType.MemoryStorage, MaxMsgSize = 1024, }); stream.MaxMsgSize().ShouldBeGreaterThan(1024UL); } [Fact] public void AutoTuneFileStorageBlockSize_WithMaxMsgsPer_UsesKvDefault() { var stream = CreateStream(new StreamConfig { Name = "KV", Subjects = ["kv.>"], Storage = StorageType.MemoryStorage, MaxMsgsPer = 10, }); var cfg = new FileStoreConfig(); stream.AutoTuneFileStorageBlockSize(cfg); cfg.BlockSize.ShouldBe(FileStoreDefaults.DefaultKvBlockSize); } [Fact] public void ClusterSequenceHelpers_RoundTripValues() { var stream = CreateStream(); stream.SetCLFS(4); stream.SetLastSeq(10); stream.GetCLFS().ShouldBe(4UL); stream.LastSeqValue().ShouldBe(10UL); stream.LastSeqAndCLFS().ShouldBe((10UL, 4UL)); } [Fact] public void CreatedTime_SetCreatedTime_UpdatesValue() { var stream = CreateStream(); var timestamp = DateTime.UtcNow.AddMinutes(-5); stream.SetCreatedTime(timestamp); stream.CreatedTime().ShouldBe(timestamp); } [Fact] public void Update_AndUpdatePedantic_ApplyConfig() { var stream = CreateStream(); var updated = new StreamConfig { Name = "ORDERS", Subjects = ["orders.v2"], Storage = StorageType.MemoryStorage }; stream.Update(updated).ShouldBeNull(); stream.GetConfig().Subjects.ShouldBe(["orders.v2"]); var updatedAgain = new StreamConfig { Name = "ORDERS", Subjects = ["orders.v3"], Storage = StorageType.MemoryStorage }; stream.UpdatePedantic(updatedAgain, pedantic: true).ShouldBeNull(); stream.GetConfig().Subjects.ShouldBe(["orders.v3"]); } [Fact] public void CheckStreamCfg_NormalizesSubjects_AndRejectsNegativeMaxMsgSize() { var (server, error) = NatsServer.NewServer(new ServerOptions()); error.ShouldBeNull(); server.ShouldNotBeNull(); var account = new Account { Name = "A" }; var (normalized, okError) = server.CheckStreamCfg(new StreamConfig { Name = "ORDERS", Storage = StorageType.MemoryStorage }, account, pedantic: false); okError.ShouldBeNull(); normalized.Subjects.ShouldBe(["ORDERS.>"]); var (_, badError) = server.CheckStreamCfg(new StreamConfig { Name = "ORDERS", Storage = StorageType.MemoryStorage, MaxMsgSize = -1, }, account, pedantic: false); badError.ShouldNotBeNull(); } [Fact] public void JsAccount_ConfigUpdateCheck_DetectsInvalidChanges() { var jsa = new JsAccount(); var current = new StreamConfig { Name = "ORDERS", Storage = StorageType.MemoryStorage }; jsa.ConfigUpdateCheck(current, new StreamConfig { Name = "DIFFERENT", Storage = StorageType.MemoryStorage }).ShouldNotBeNull(); jsa.ConfigUpdateCheck(current, new StreamConfig { Name = "ORDERS", Storage = StorageType.FileStorage }).ShouldNotBeNull(); jsa.ConfigUpdateCheck(current, new StreamConfig { Name = "ORDERS", Storage = StorageType.MemoryStorage }).ShouldBeNull(); } [Fact] public void JsAccount_SubjectsOverlap_IgnoresOwnAssignment() { var jsa = new JsAccount(); var assignment = new StreamAssignment(); var stream = CreateStream(new StreamConfig { Name = "ORDERS", Subjects = ["orders.*"], Storage = StorageType.MemoryStorage, }); stream.SetStreamAssignment(assignment); jsa.Streams["ORDERS"] = stream; jsa.SubjectsOverlap(["orders.created"], assignment).ShouldBeFalse(); jsa.SubjectsOverlap(["orders.created"], ownAssignment: null).ShouldBeTrue(); } [Fact] public void GroupCHelpers_GetCfgNameAndIsMirror_ReturnExpectedValues() { var mirrorCfg = new StreamConfig { Name = "MIRROR", Storage = StorageType.MemoryStorage, Mirror = new StreamSource { Name = "ORIGIN" }, }; var stream = CreateStream(mirrorCfg); stream.GetCfgName().ShouldBe("MIRROR"); stream.IsMirror().ShouldBeTrue(); stream.MirrorInfo().ShouldBeNull(); } [Fact] public void GroupCHelpers_SourceInfoAndBackoff_Behave() { var stream = CreateStream(); var info = new StreamSourceInfo { Name = "SRC", FilterSubject = "orders.*", Lag = 10, Error = "x", }; var cloned = stream.SourceInfo(info); cloned.ShouldNotBeNull(); cloned!.Name.ShouldBe("SRC"); cloned.FilterSubject.ShouldBe("orders.*"); NatsStream.CalculateRetryBackoff(1).ShouldBeGreaterThan(TimeSpan.Zero); NatsStream.CalculateRetryBackoff(1000).ShouldBe(TimeSpan.FromMinutes(2)); } private static NatsStream CreateStream(StreamConfig? cfg = null) { cfg ??= new StreamConfig { Name = "ORDERS", Subjects = ["orders.*"], Storage = StorageType.MemoryStorage }; return NatsStream.Create( new Account { Name = "A" }, cfg, null, new JetStreamMemStore(cfg.Clone()), null, null)!; } }