using System.Text; using System.Text.Json; using NSubstitute; using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server.Tests.JetStream; public sealed class NatsStreamDirectGetPipelineTests { [Fact] public void GetMessageScheduleTTL_InvalidValue_ReturnsNotOk() { var hdr = NatsMessageHeaders.GenHeader(null, NatsHeaderConstants.JsScheduleTtl, "not-a-ttl"); var (ttl, ok) = NatsStream.GetMessageScheduleTTL(hdr); ok.ShouldBeFalse(); ttl.ShouldBe(string.Empty); } [Fact] public void GetBatchSequence_ValidHeader_ReturnsSequence() { var hdr = NatsMessageHeaders.GenHeader(null, NatsHeaderConstants.JsBatchSeq, "9"); var (seq, ok) = NatsStream.GetBatchSequence(hdr); ok.ShouldBeTrue(); seq.ShouldBe(9UL); } [Fact] public void IsClustered_WithAssignmentNode_ReturnsTrue() { var stream = CreateStream(); var node = Substitute.For(); var assignment = new StreamAssignment { Group = new RaftGroup { Node = node, Peers = ["N1"], }, }; stream.SetStreamAssignment(assignment); stream.IsClustered().ShouldBeTrue(); stream.IsClusteredInternal().ShouldBeTrue(); } [Fact] public void InMsgReturnToPool_WithValues_ClearsState() { var msg = new InMsg { Subject = "foo", Reply = "bar", Hdr = [1, 2], Msg = [3, 4], Client = new object(), }; msg.ReturnToPool(); msg.Subject.ShouldBe(string.Empty); msg.Reply.ShouldBeNull(); msg.Hdr.ShouldBeNull(); msg.Msg.ShouldBeNull(); } [Fact] public void QueueInbound_WithQueue_PushesMessage() { var stream = CreateStream(); var queue = new IpQueue("inbound"); stream.QueueInbound(queue, "orders.created", null, null, [1], null, null); var popped = queue.Pop(); popped.ShouldNotBeNull(); popped!.Length.ShouldBe(1); popped[0].Subject.ShouldBe("orders.created"); } [Fact] public void ProcessDirectGetRequest_SeqRequest_ReturnsStoredMessage() { var stream = CreateStream(); stream.SetupStore(null).ShouldBeNull(); stream.Store!.StoreMsg("orders", null, [7, 8], ttl: 0); var request = JsonSerializer.SerializeToUtf8Bytes(new JsApiMsgGetRequest { Seq = 1 }); var response = stream.ProcessDirectGetRequest("reply.inbox", null, request); response.Error.ShouldBeNull(); response.Message.ShouldNotBeNull(); response.Message!.Sequence.ShouldBe(1UL); response.Message.Subject.ShouldBe("orders"); } [Fact] public void ProcessDirectGetLastBySubjectRequest_InvalidSubject_ReturnsBadRequest() { var stream = CreateStream(); var response = stream.ProcessDirectGetLastBySubjectRequest("$JS.API.DIRECT.GET.STREAM", "reply", null, null); response.Error.ShouldNotBeNull(); response.Error!.ErrCode.ShouldBe(JsApiErrors.BadRequest.ErrCode); } [Fact] public void ProcessJetStreamMsg_WithMemoryStore_StoresMessage() { var stream = CreateStream(); stream.SetupStore(null).ShouldBeNull(); var error = stream.ProcessJetStreamMsg("events", string.Empty, null, Encoding.ASCII.GetBytes("m1"), 0, 0, null, false, true); var stored = stream.Store!.LoadMsg(1, new StoreMsg()); error.ShouldBeNull(); stored.ShouldNotBeNull(); stored!.Subject.ShouldBe("events"); } [Fact] public void ProcessJetStreamBatchMsg_MissingSequence_ReturnsApiError() { var stream = CreateStream(); stream.SetupStore(null).ShouldBeNull(); var error = stream.ProcessJetStreamBatchMsg("batch-1", "events", string.Empty, null, [1], null); error.ShouldNotBeNull(); error.ShouldBeOfType(); } private static NatsStream CreateStream() { var account = new Account { Name = "A" }; var config = new StreamConfig { Name = "S", Storage = StorageType.MemoryStorage, Subjects = ["events.>"], }; return new NatsStream(account, config, DateTime.UtcNow); } }