// Ported from golang/nats-server/server/jetstream_test.go // Stream CRUD operations: create, update, delete, purge, info, validation using NATS.Server.JetStream; using NATS.Server.JetStream.Api; using NATS.Server.JetStream.Models; using NATS.Server.JetStream.Validation; namespace NATS.Server.Tests.JetStream; public class JetStreamStreamCrudTests { // Go: TestJetStreamAddStream server/jetstream_test.go:178 [Fact] public async Task Create_stream_returns_config_and_empty_state() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("ORDERS", "orders.*"); var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.ORDERS", "{}"); info.Error.ShouldBeNull(); info.StreamInfo.ShouldNotBeNull(); info.StreamInfo!.Config.Name.ShouldBe("ORDERS"); info.StreamInfo.Config.Subjects.ShouldContain("orders.*"); info.StreamInfo.State.Messages.ShouldBe(0UL); } // Go: TestJetStreamAddStreamDiscardNew server/jetstream_test.go:122 // Verifies discard new policy with max_bytes rejects new messages when stream is full. [Fact] public async Task Create_stream_with_discard_new_policy() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "DN", Subjects = ["dn.>"], MaxBytes = 30, Discard = DiscardPolicy.New, }); var ack1 = await fx.PublishAndGetAckAsync("dn.one", "1"); ack1.ErrorCode.ShouldBeNull(); var ack2 = await fx.PublishAndGetAckAsync("dn.two", "2"); ack2.ErrorCode.ShouldBeNull(); // Oversized publish should be rejected due to discard new + max_bytes var ack3 = await fx.PublishAndGetAckAsync("dn.three", "this-is-a-large-payload-that-exceeds-bytes"); ack3.ErrorCode.ShouldNotBeNull(); } // Go: TestJetStreamAddStreamMaxMsgSize server/jetstream_test.go:484 [Fact] public async Task Create_stream_with_max_msg_size_rejects_oversized() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "SIZED", Subjects = ["sized.>"], MaxMsgSize = 10, }); var small = await fx.PublishAndGetAckAsync("sized.ok", "tiny"); small.ErrorCode.ShouldBeNull(); var big = await fx.PublishAndGetAckAsync("sized.big", "this-is-definitely-larger-than-ten-bytes"); big.ErrorCode.ShouldNotBeNull(); } // Go: TestJetStreamAddStreamCanonicalNames server/jetstream_test.go:537 [Fact] public async Task Create_stream_name_is_preserved_in_info() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("MyStream", "my.>"); var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.MyStream", "{}"); info.Error.ShouldBeNull(); info.StreamInfo!.Config.Name.ShouldBe("MyStream"); } // Go: TestJetStreamAddStreamSameConfigOK server/jetstream_test.go:701 [Fact] public async Task Create_stream_with_same_config_is_idempotent() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("ORDERS", "orders.*"); var second = await fx.RequestLocalAsync( "$JS.API.STREAM.CREATE.ORDERS", """{"name":"ORDERS","subjects":["orders.*"]}"""); second.Error.ShouldBeNull(); second.StreamInfo.ShouldNotBeNull(); second.StreamInfo!.Config.Name.ShouldBe("ORDERS"); } // Go: TestJetStreamUpdateStream server/jetstream_test.go:6409 [Fact] public async Task Update_stream_changes_subjects_and_limits() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("ORDERS", "orders.*"); _ = await fx.PublishAndGetAckAsync("orders.x", "1"); var update = await fx.RequestLocalAsync( "$JS.API.STREAM.UPDATE.ORDERS", """{"name":"ORDERS","subjects":["orders.v2.*"],"max_msgs":50}"""); update.Error.ShouldBeNull(); update.StreamInfo!.Config.Subjects.ShouldContain("orders.v2.*"); update.StreamInfo.Config.MaxMsgs.ShouldBe(50); } // Go: TestJetStreamStreamPurge server/jetstream_test.go:4182 [Fact] public async Task Purge_stream_removes_all_messages() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("P", "p.*"); for (var i = 0; i < 5; i++) _ = await fx.PublishAndGetAckAsync("p.msg", $"payload-{i}"); var before = await fx.GetStreamStateAsync("P"); before.Messages.ShouldBe(5UL); var purge = await fx.RequestLocalAsync("$JS.API.STREAM.PURGE.P", "{}"); purge.Success.ShouldBeTrue(); var after = await fx.GetStreamStateAsync("P"); after.Messages.ShouldBe(0UL); } // Go: TestJetStreamDeleteMsg server/jetstream_test.go:6464 [Fact] public async Task Delete_individual_message_by_sequence() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("DEL", "del.>"); var ack1 = await fx.PublishAndGetAckAsync("del.a", "1"); _ = await fx.PublishAndGetAckAsync("del.b", "2"); var del = await fx.RequestLocalAsync( "$JS.API.STREAM.MSG.DELETE.DEL", $$"""{ "seq": {{ack1.Seq}} }"""); del.Success.ShouldBeTrue(); var state = await fx.GetStreamStateAsync("DEL"); state.Messages.ShouldBe(1UL); } // Go: TestJetStreamAddStream — delete removes stream [Fact] public async Task Delete_stream_makes_it_inaccessible() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("GONE", "gone.>"); _ = await fx.PublishAndGetAckAsync("gone.x", "data"); var del = await fx.RequestLocalAsync("$JS.API.STREAM.DELETE.GONE", "{}"); del.Success.ShouldBeTrue(); var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.GONE", "{}"); info.Error.ShouldNotBeNull(); } // Go: TestJetStreamStreamPurge — publish after purge works [Fact] public async Task Publish_after_purge_adds_new_message() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("PP", "pp.>"); _ = await fx.PublishAndGetAckAsync("pp.x", "before"); _ = await fx.RequestLocalAsync("$JS.API.STREAM.PURGE.PP", "{}"); var ack = await fx.PublishAndGetAckAsync("pp.x", "after"); ack.ErrorCode.ShouldBeNull(); var state = await fx.GetStreamStateAsync("PP"); state.Messages.ShouldBe(1UL); } // Go: TestJetStreamBasicNilConfig server/jetstream_test.go:56 [Fact] public void Stream_config_requires_name() { var sm = new StreamManager(); var resp = sm.CreateOrUpdate(new StreamConfig { Name = "" }); resp.Error.ShouldNotBeNull(); resp.Error!.Code.ShouldBe(400); } // Go: TestJetStreamAddStreamBadSubjects server/jetstream_test.go:587 [Fact] public void Validation_rejects_empty_name_and_subjects() { var config = new StreamConfig { Name = "", Subjects = [] }; var result = JetStreamConfigValidator.Validate(config); result.IsValid.ShouldBeFalse(); } // Go: TestJetStreamAddStreamBadSubjects — valid name required [Fact] public void Validation_accepts_valid_stream_config() { var config = new StreamConfig { Name = "OK", Subjects = ["ok.>"] }; var result = JetStreamConfigValidator.Validate(config); result.IsValid.ShouldBeTrue(); } // Go: TestJetStreamMaxConsumers server/jetstream_test.go:619 [Fact] public void Validation_workqueue_requires_max_consumers() { var config = new StreamConfig { Name = "WQ", Subjects = ["wq.>"], Retention = RetentionPolicy.WorkQueue, MaxConsumers = 0, }; var result = JetStreamConfigValidator.Validate(config); result.IsValid.ShouldBeFalse(); } // Go: TestJetStreamInvalidConfigValues server/jetstream_test.go [Fact] public void Validation_rejects_negative_max_msg_size() { var config = new StreamConfig { Name = "NEG", Subjects = ["neg.>"], MaxMsgSize = -1, }; var result = JetStreamConfigValidator.Validate(config); result.IsValid.ShouldBeFalse(); } // Go: TestJetStreamInvalidConfigValues [Fact] public void Validation_rejects_negative_max_msgs_per() { var config = new StreamConfig { Name = "NEG2", Subjects = ["neg2.>"], MaxMsgsPer = -1, }; var result = JetStreamConfigValidator.Validate(config); result.IsValid.ShouldBeFalse(); } // Go: TestJetStreamInvalidConfigValues [Fact] public void Validation_rejects_negative_max_age_ms() { var config = new StreamConfig { Name = "NEG3", Subjects = ["neg3.>"], MaxAgeMs = -1, }; var result = JetStreamConfigValidator.Validate(config); result.IsValid.ShouldBeFalse(); } // Go: TestJetStreamStreamPurge — sealed stream cannot be purged [Fact] public async Task Sealed_stream_rejects_purge() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "SEAL", Subjects = ["seal.>"], Sealed = true, }); var purge = await fx.RequestLocalAsync("$JS.API.STREAM.PURGE.SEAL", "{}"); purge.Success.ShouldBeFalse(); } // Go: TestJetStreamDeleteMsg — deny_delete prevents removal [Fact] public async Task Deny_delete_prevents_message_removal() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "NODELETE", Subjects = ["nodelete.>"], DenyDelete = true, }); var ack = await fx.PublishAndGetAckAsync("nodelete.x", "data"); ack.ErrorCode.ShouldBeNull(); var del = await fx.RequestLocalAsync( "$JS.API.STREAM.MSG.DELETE.NODELETE", $$"""{ "seq": {{ack.Seq}} }"""); del.Success.ShouldBeFalse(); } // Go: TestJetStreamDeleteMsg — deny_purge prevents purge [Fact] public async Task Deny_purge_prevents_stream_purge() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "NOPURGE", Subjects = ["nopurge.>"], DenyPurge = true, }); _ = await fx.PublishAndGetAckAsync("nopurge.x", "data"); var purge = await fx.RequestLocalAsync("$JS.API.STREAM.PURGE.NOPURGE", "{}"); purge.Success.ShouldBeFalse(); } // Go: TestJetStreamStreamStorageTrackingAndLimits server/jetstream_test.go:4931 [Fact] public async Task Stream_with_max_msgs_limit_enforces_count() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("LIMITED", "limited.>", maxMsgs: 3); for (var i = 0; i < 5; i++) _ = await fx.PublishAndGetAckAsync("limited.x", $"msg-{i}"); var state = await fx.GetStreamStateAsync("LIMITED"); state.Messages.ShouldBeLessThanOrEqualTo(3UL); } // Go: TestJetStreamMaxBytesIgnored server/jetstream_test.go [Fact] public async Task Stream_with_max_bytes_discard_old_evicts_oldest() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "BYTES", Subjects = ["bytes.>"], MaxBytes = 100, Discard = DiscardPolicy.Old, }); for (var i = 0; i < 20; i++) _ = await fx.PublishAndGetAckAsync("bytes.x", $"payload-{i:D10}"); var state = await fx.GetStreamStateAsync("BYTES"); ((long)state.Bytes).ShouldBeLessThanOrEqualTo(100L); } // Go: TestJetStreamMaxMsgsPerSubject server/jetstream_test.go [Fact] public async Task Max_msgs_per_subject_enforces_limit() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "MPS", Subjects = ["mps.>"], MaxMsgsPer = 2, }); _ = await fx.PublishAndGetAckAsync("mps.a", "1"); _ = await fx.PublishAndGetAckAsync("mps.a", "2"); _ = await fx.PublishAndGetAckAsync("mps.a", "3"); _ = await fx.PublishAndGetAckAsync("mps.b", "4"); var state = await fx.GetStreamStateAsync("MPS"); // mps.a should have 2 kept, mps.b has 1 = 3 total state.Messages.ShouldBeLessThanOrEqualTo(3UL); } // Go: TestJetStreamStreamFileTrackingAndLimits server/jetstream_test.go:4982 [Fact] public async Task Stream_with_file_storage_type() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "FSTORE", Subjects = ["fstore.>"], Storage = StorageType.File, }); var ack = await fx.PublishAndGetAckAsync("fstore.x", "data"); ack.ErrorCode.ShouldBeNull(); var backendType = await fx.GetStreamBackendTypeAsync("FSTORE"); backendType.ShouldBe("file"); } // Go: TestJetStreamStreamFileTrackingAndLimits — memory store [Fact] public async Task Stream_with_memory_storage_type() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "MSTORE", Subjects = ["mstore.>"], Storage = StorageType.Memory, }); var ack = await fx.PublishAndGetAckAsync("mstore.x", "data"); ack.ErrorCode.ShouldBeNull(); var backendType = await fx.GetStreamBackendTypeAsync("MSTORE"); backendType.ShouldBe("memory"); } // Go: TestJetStreamStreamLimitUpdate server/jetstream_test.go:4905 [Fact] public async Task Update_stream_max_msgs_trims_existing_messages() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("UPD", "upd.>"); for (var i = 0; i < 10; i++) _ = await fx.PublishAndGetAckAsync("upd.x", $"msg-{i}"); var before = await fx.GetStreamStateAsync("UPD"); before.Messages.ShouldBe(10UL); // Update to max_msgs=3 var update = await fx.RequestLocalAsync( "$JS.API.STREAM.UPDATE.UPD", """{"name":"UPD","subjects":["upd.>"],"max_msgs":3}"""); update.Error.ShouldBeNull(); // Publish one more to trigger enforcement _ = await fx.PublishAndGetAckAsync("upd.x", "trigger"); var after = await fx.GetStreamStateAsync("UPD"); after.Messages.ShouldBeLessThanOrEqualTo(3UL); } // Go: TestJetStreamAllowDirectAfterUpdate server/jetstream_test.go [Fact] public async Task Allow_direct_can_be_set_via_update() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "DIR", Subjects = ["dir.>"], AllowDirect = false, }); var update = await fx.RequestLocalAsync( "$JS.API.STREAM.UPDATE.DIR", """{"name":"DIR","subjects":["dir.>"],"allow_direct":true}"""); update.Error.ShouldBeNull(); update.StreamInfo!.Config.AllowDirect.ShouldBeTrue(); } // Go: TestJetStreamStreamConfigClone server/jetstream_test.go [Fact] public async Task Stream_config_is_independent_after_creation() { var config = new StreamConfig { Name = "CLONE", Subjects = ["clone.>"], MaxMsgs = 100, }; await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(config); // Mutate the original config config.MaxMsgs = 999; var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.CLONE", "{}"); info.StreamInfo!.Config.MaxMsgs.ShouldBe(100); } // Go: TestJetStreamStreamPurgeWithConsumer server/jetstream_test.go:4215 [Fact] public async Task Purge_with_active_consumer_resets_delivery() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("PC", "pc.>"); _ = await fx.CreateConsumerAsync("PC", "C1", "pc.>"); for (var i = 0; i < 5; i++) _ = await fx.PublishAndGetAckAsync("pc.x", $"msg-{i}"); var purge = await fx.RequestLocalAsync("$JS.API.STREAM.PURGE.PC", "{}"); purge.Success.ShouldBeTrue(); var state = await fx.GetStreamStateAsync("PC"); state.Messages.ShouldBe(0UL); } // Go: TestJetStreamGetLastMsgBySubject server/jetstream_test.go [Fact] public async Task Get_message_by_sequence_returns_correct_data() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("GM", "gm.>"); var ack = await fx.PublishAndGetAckAsync("gm.first", "hello"); _ = await fx.PublishAndGetAckAsync("gm.second", "world"); var msg = await fx.RequestLocalAsync( "$JS.API.STREAM.MSG.GET.GM", $$"""{ "seq": {{ack.Seq}} }"""); msg.StreamMessage.ShouldNotBeNull(); msg.StreamMessage!.Payload.ShouldBe("hello"); msg.StreamMessage.Subject.ShouldBe("gm.first"); } // Go: TestJetStreamStateTimestamps server/jetstream_test.go:758 [Fact] public async Task Stream_state_tracks_first_and_last_sequence() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("TS", "ts.>"); _ = await fx.PublishAndGetAckAsync("ts.a", "1"); _ = await fx.PublishAndGetAckAsync("ts.b", "2"); _ = await fx.PublishAndGetAckAsync("ts.c", "3"); var state = await fx.GetStreamStateAsync("TS"); state.Messages.ShouldBe(3UL); state.FirstSeq.ShouldBe(1UL); state.LastSeq.ShouldBe(3UL); } // Go: TestJetStreamAddStreamDiscardNew — discard new + max bytes [Fact] public async Task Discard_new_with_max_bytes_rejects_when_full() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "DNB", Subjects = ["dnb.>"], MaxBytes = 50, Discard = DiscardPolicy.New, }); // Fill up for (var i = 0; i < 10; i++) { _ = await fx.PublishAndGetAckAsync("dnb.x", $"msg-{i:D20}"); } // Eventually one should be rejected var state = await fx.GetStreamStateAsync("DNB"); ((long)state.Bytes).ShouldBeLessThanOrEqualTo(50L + 50); } // Go: TestJetStreamStreamRetentionUpdatesConsumers server/jetstream_test.go [Fact] public async Task Stream_info_after_multiple_publishes() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("INF", "inf.>"); for (var i = 0; i < 10; i++) _ = await fx.PublishAndGetAckAsync("inf.x", $"data-{i}"); var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.INF", "{}"); info.Error.ShouldBeNull(); info.StreamInfo!.State.Messages.ShouldBe(10UL); info.StreamInfo.State.FirstSeq.ShouldBe(1UL); info.StreamInfo.State.LastSeq.ShouldBe(10UL); } // Go: TestJetStreamDeleteMsg — sequence 0 returns error [Fact] public async Task Delete_message_with_zero_sequence_returns_error() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("DZ", "dz.>"); _ = await fx.PublishAndGetAckAsync("dz.x", "data"); var del = await fx.RequestLocalAsync("$JS.API.STREAM.MSG.DELETE.DZ", """{"seq":0}"""); del.Error.ShouldNotBeNull(); } // Go: TestJetStreamDeleteMsg — non-existent stream [Fact] public async Task Delete_message_from_non_existent_stream() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("EXISTS", "exists.>"); var del = await fx.RequestLocalAsync("$JS.API.STREAM.MSG.DELETE.NOTEXIST", """{"seq":1}"""); del.Success.ShouldBeFalse(); } // Go: TestJetStreamRestoreBadStream server/jetstream_test.go [Fact] public async Task Info_for_non_existent_stream_returns_error() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("X", "x.>"); var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.DOESNOTEXIST", "{}"); info.Error.ShouldNotBeNull(); } // Go: TestJetStreamStreamPurge — multiple purges are idempotent [Fact] public async Task Multiple_purges_are_idempotent() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("MP", "mp.>"); for (var i = 0; i < 3; i++) _ = await fx.PublishAndGetAckAsync("mp.x", $"msg-{i}"); _ = await fx.RequestLocalAsync("$JS.API.STREAM.PURGE.MP", "{}"); var second = await fx.RequestLocalAsync("$JS.API.STREAM.PURGE.MP", "{}"); second.Success.ShouldBeTrue(); var state = await fx.GetStreamStateAsync("MP"); state.Messages.ShouldBe(0UL); } // Go: TestJetStreamAddStream — retention policy Limits [Fact] public async Task Create_stream_with_limits_retention() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "LIM", Subjects = ["lim.>"], Retention = RetentionPolicy.Limits, }); var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.LIM", "{}"); info.StreamInfo!.Config.Retention.ShouldBe(RetentionPolicy.Limits); } // Go: TestJetStreamInterestRetentionStream server/jetstream_test.go:4336 [Fact] public async Task Create_stream_with_interest_retention() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "INT", Subjects = ["int.>"], Retention = RetentionPolicy.Interest, }); var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.INT", "{}"); info.StreamInfo!.Config.Retention.ShouldBe(RetentionPolicy.Interest); } // Go: TestJetStreamBasicWorkQueue server/jetstream_test.go:937 [Fact] public async Task Create_stream_with_workqueue_retention() { await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig { Name = "WQ", Subjects = ["wq.>"], Retention = RetentionPolicy.WorkQueue, MaxConsumers = 1, }); var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.WQ", "{}"); info.StreamInfo!.Config.Retention.ShouldBe(RetentionPolicy.WorkQueue); } // Go: TestJetStreamSnapshotsAPI server/jetstream_test.go:3328 [Fact] public async Task Snapshot_and_restore_roundtrip() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("SNAP", "snap.>"); _ = await fx.PublishAndGetAckAsync("snap.a", "data1"); _ = await fx.PublishAndGetAckAsync("snap.b", "data2"); var snap = await fx.RequestLocalAsync("$JS.API.STREAM.SNAPSHOT.SNAP", "{}"); snap.Error.ShouldBeNull(); snap.Snapshot.ShouldNotBeNull(); snap.Snapshot!.Payload.ShouldNotBeNullOrWhiteSpace(); // Restore into the same stream var restore = await fx.RequestLocalAsync( "$JS.API.STREAM.RESTORE.SNAP", snap.Snapshot.Payload); restore.Success.ShouldBeTrue(); } // Go: TestJetStreamAddStreamOverlapWithJSAPISubjects server/jetstream_test.go:666 [Fact] public async Task Create_multiple_streams_with_non_overlapping_subjects() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("S1", "s1.>"); var s2 = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.S2", """{"subjects":["s2.>"]}"""); s2.Error.ShouldBeNull(); var ack1 = await fx.PublishAndGetAckAsync("s1.x", "data1"); ack1.Stream.ShouldBe("S1"); var ack2 = await fx.PublishAndGetAckAsync("s2.x", "data2"); ack2.Stream.ShouldBe("S2"); } // Go: TestJetStreamStreamPurge — verify bytes reset after purge [Fact] public async Task Purge_resets_byte_count() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("PB", "pb.>"); for (var i = 0; i < 5; i++) _ = await fx.PublishAndGetAckAsync("pb.x", "some-data"); var before = await fx.GetStreamStateAsync("PB"); before.Bytes.ShouldBeGreaterThan(0UL); _ = await fx.RequestLocalAsync("$JS.API.STREAM.PURGE.PB", "{}"); var after = await fx.GetStreamStateAsync("PB"); after.Bytes.ShouldBe(0UL); } // Go: TestJetStreamDefaultMaxMsgsPer server/jetstream_test.go [Fact] public async Task Stream_defaults_replicas_to_one() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("DEF", "def.>"); var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.DEF", "{}"); info.StreamInfo!.Config.Replicas.ShouldBe(1); } // Go: TestJetStreamSuppressAllowDirect server/jetstream_test.go [Fact] public async Task Allow_direct_defaults_to_false() { await using var fx = await JetStreamApiFixture.StartWithStreamAsync("AD", "ad.>"); var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.AD", "{}"); info.StreamInfo!.Config.AllowDirect.ShouldBeFalse(); } }