refactor: extract NATS.Server.JetStream.Tests project
Move 225 JetStream-related test files from NATS.Server.Tests into a dedicated NATS.Server.JetStream.Tests project. This includes root-level JetStream*.cs files, storage test files (FileStore, MemStore, StreamStoreContract), and the full JetStream/ subfolder tree (Api, Cluster, Consumers, MirrorSource, Snapshots, Storage, Streams). Updated all namespaces, added InternalsVisibleTo, registered in the solution file, and added the JETSTREAM_INTEGRATION_MATRIX define.
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
// Go reference: jetstream_api.go — advisory event publication for stream/consumer lifecycle.
|
||||
// Advisory subjects use the pattern $JS.EVENT.ADVISORY.{type}.{stream}[.{consumer}].
|
||||
|
||||
using NATS.Server.Events;
|
||||
using NATS.Server.JetStream.Api;
|
||||
|
||||
namespace NATS.Server.JetStream.Tests.JetStream.Api;
|
||||
|
||||
public class AdvisoryEventTests
|
||||
{
|
||||
private static (AdvisoryPublisher Publisher, List<(string Subject, object Body)> Published) CreatePublisher()
|
||||
{
|
||||
var published = new List<(string Subject, object Body)>();
|
||||
var publisher = new AdvisoryPublisher((s, b) => published.Add((s, b)));
|
||||
return (publisher, published);
|
||||
}
|
||||
|
||||
// Go reference: jetstream_api.go — stream created advisory on $JS.EVENT.ADVISORY.STREAM.CREATED.{stream}.
|
||||
[Fact]
|
||||
public void StreamCreated_publishes_advisory_to_correct_subject()
|
||||
{
|
||||
var (publisher, published) = CreatePublisher();
|
||||
|
||||
publisher.StreamCreated("ORDERS");
|
||||
|
||||
published.Count.ShouldBe(1);
|
||||
published[0].Subject.ShouldBe("$JS.EVENT.ADVISORY.STREAM.CREATED.ORDERS");
|
||||
}
|
||||
|
||||
// Go reference: jetstream_api.go — stream deleted advisory includes stream name in subject.
|
||||
[Fact]
|
||||
public void StreamDeleted_publishes_advisory_with_stream_name()
|
||||
{
|
||||
var (publisher, published) = CreatePublisher();
|
||||
|
||||
publisher.StreamDeleted("PAYMENTS");
|
||||
|
||||
published.Count.ShouldBe(1);
|
||||
published[0].Subject.ShouldBe("$JS.EVENT.ADVISORY.STREAM.DELETED.PAYMENTS");
|
||||
var evt = published[0].Body.ShouldBeOfType<AdvisoryEvent>();
|
||||
evt.Stream.ShouldBe("PAYMENTS");
|
||||
}
|
||||
|
||||
// Go reference: jetstream_api.go — stream updated advisory carries optional detail payload.
|
||||
[Fact]
|
||||
public void StreamUpdated_publishes_advisory_with_detail()
|
||||
{
|
||||
var (publisher, published) = CreatePublisher();
|
||||
var detail = new { Reason = "config_change" };
|
||||
|
||||
publisher.StreamUpdated("EVENTS", detail);
|
||||
|
||||
published.Count.ShouldBe(1);
|
||||
published[0].Subject.ShouldBe("$JS.EVENT.ADVISORY.STREAM.UPDATED.EVENTS");
|
||||
var evt = published[0].Body.ShouldBeOfType<AdvisoryEvent>();
|
||||
evt.Detail.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
// Go reference: jetstream_api.go — consumer created advisory on $JS.EVENT.ADVISORY.CONSUMER.CREATED.{stream}.{consumer}.
|
||||
[Fact]
|
||||
public void ConsumerCreated_publishes_advisory_with_stream_and_consumer()
|
||||
{
|
||||
var (publisher, published) = CreatePublisher();
|
||||
|
||||
publisher.ConsumerCreated("ORDERS", "push-consumer");
|
||||
|
||||
published.Count.ShouldBe(1);
|
||||
published[0].Subject.ShouldBe("$JS.EVENT.ADVISORY.CONSUMER.CREATED.ORDERS.push-consumer");
|
||||
var evt = published[0].Body.ShouldBeOfType<AdvisoryEvent>();
|
||||
evt.Stream.ShouldBe("ORDERS");
|
||||
evt.Consumer.ShouldBe("push-consumer");
|
||||
}
|
||||
|
||||
// Go reference: jetstream_api.go — consumer deleted advisory type field identifies event kind.
|
||||
[Fact]
|
||||
public void ConsumerDeleted_publishes_advisory_with_correct_type()
|
||||
{
|
||||
var (publisher, published) = CreatePublisher();
|
||||
|
||||
publisher.ConsumerDeleted("ORDERS", "my-consumer");
|
||||
|
||||
published.Count.ShouldBe(1);
|
||||
var evt = published[0].Body.ShouldBeOfType<AdvisoryEvent>();
|
||||
evt.Type.ShouldBe("io.nats.jetstream.advisory.consumer_deleted");
|
||||
}
|
||||
|
||||
// Go reference: jetstream_api.go — publish count tracks all emitted advisories atomically.
|
||||
[Fact]
|
||||
public void PublishCount_increments_for_each_advisory()
|
||||
{
|
||||
var (publisher, _) = CreatePublisher();
|
||||
|
||||
publisher.PublishCount.ShouldBe(0);
|
||||
|
||||
publisher.StreamCreated("S1");
|
||||
publisher.PublishCount.ShouldBe(1);
|
||||
|
||||
publisher.StreamDeleted("S1");
|
||||
publisher.PublishCount.ShouldBe(2);
|
||||
|
||||
publisher.ConsumerCreated("S1", "C1");
|
||||
publisher.PublishCount.ShouldBe(3);
|
||||
}
|
||||
|
||||
// Go reference: jetstream_api.go — each advisory type has its own descriptive type string.
|
||||
[Fact]
|
||||
public void Advisory_event_has_correct_type_field()
|
||||
{
|
||||
var (publisher, published) = CreatePublisher();
|
||||
|
||||
publisher.StreamCreated("S");
|
||||
published[0].Body.ShouldBeOfType<AdvisoryEvent>().Type
|
||||
.ShouldBe("io.nats.jetstream.advisory.stream_created");
|
||||
|
||||
publisher.StreamDeleted("S");
|
||||
published[1].Body.ShouldBeOfType<AdvisoryEvent>().Type
|
||||
.ShouldBe("io.nats.jetstream.advisory.stream_deleted");
|
||||
|
||||
publisher.StreamUpdated("S");
|
||||
published[2].Body.ShouldBeOfType<AdvisoryEvent>().Type
|
||||
.ShouldBe("io.nats.jetstream.advisory.stream_updated");
|
||||
|
||||
publisher.ConsumerCreated("S", "C");
|
||||
published[3].Body.ShouldBeOfType<AdvisoryEvent>().Type
|
||||
.ShouldBe("io.nats.jetstream.advisory.consumer_created");
|
||||
|
||||
publisher.ConsumerDeleted("S", "C");
|
||||
published[4].Body.ShouldBeOfType<AdvisoryEvent>().Type
|
||||
.ShouldBe("io.nats.jetstream.advisory.consumer_deleted");
|
||||
}
|
||||
|
||||
// Go reference: jetstream_api.go — advisory timestamps use UTC to ensure cross-cluster consistency.
|
||||
[Fact]
|
||||
public void Advisory_event_has_utc_timestamp()
|
||||
{
|
||||
var (publisher, published) = CreatePublisher();
|
||||
var before = DateTime.UtcNow;
|
||||
|
||||
publisher.StreamCreated("TEST");
|
||||
|
||||
var after = DateTime.UtcNow;
|
||||
var evt = published[0].Body.ShouldBeOfType<AdvisoryEvent>();
|
||||
evt.TimeStamp.Kind.ShouldBe(DateTimeKind.Utc);
|
||||
evt.TimeStamp.ShouldBeGreaterThanOrEqualTo(before);
|
||||
evt.TimeStamp.ShouldBeLessThanOrEqualTo(after);
|
||||
}
|
||||
|
||||
// Go reference: jetstream_api.go — advisory subjects are derived from EventSubjects constants.
|
||||
[Fact]
|
||||
public void Advisory_subjects_format_correctly()
|
||||
{
|
||||
string.Format(EventSubjects.JsAdvisoryStreamCreated, "MY_STREAM")
|
||||
.ShouldBe("$JS.EVENT.ADVISORY.STREAM.CREATED.MY_STREAM");
|
||||
|
||||
string.Format(EventSubjects.JsAdvisoryStreamDeleted, "MY_STREAM")
|
||||
.ShouldBe("$JS.EVENT.ADVISORY.STREAM.DELETED.MY_STREAM");
|
||||
|
||||
string.Format(EventSubjects.JsAdvisoryStreamUpdated, "MY_STREAM")
|
||||
.ShouldBe("$JS.EVENT.ADVISORY.STREAM.UPDATED.MY_STREAM");
|
||||
|
||||
string.Format(EventSubjects.JsAdvisoryConsumerCreated, "MY_STREAM", "MY_CONSUMER")
|
||||
.ShouldBe("$JS.EVENT.ADVISORY.CONSUMER.CREATED.MY_STREAM.MY_CONSUMER");
|
||||
|
||||
string.Format(EventSubjects.JsAdvisoryConsumerDeleted, "MY_STREAM", "MY_CONSUMER")
|
||||
.ShouldBe("$JS.EVENT.ADVISORY.CONSUMER.DELETED.MY_STREAM.MY_CONSUMER");
|
||||
|
||||
string.Format(EventSubjects.JsAdvisoryStreamSnapshotCreated, "MY_STREAM")
|
||||
.ShouldBe("$JS.EVENT.ADVISORY.STREAM.SNAPSHOT_CREATE.MY_STREAM");
|
||||
|
||||
string.Format(EventSubjects.JsAdvisoryStreamSnapshotCompleted, "MY_STREAM")
|
||||
.ShouldBe("$JS.EVENT.ADVISORY.STREAM.SNAPSHOT_COMPLETE.MY_STREAM");
|
||||
|
||||
string.Format(EventSubjects.JsAdvisoryStreamRestoreCreated, "MY_STREAM")
|
||||
.ShouldBe("$JS.EVENT.ADVISORY.STREAM.RESTORE_CREATE.MY_STREAM");
|
||||
|
||||
string.Format(EventSubjects.JsAdvisoryStreamRestoreCompleted, "MY_STREAM")
|
||||
.ShouldBe("$JS.EVENT.ADVISORY.STREAM.RESTORE_COMPLETE.MY_STREAM");
|
||||
|
||||
string.Format(EventSubjects.JsAdvisoryStreamLeaderElected, "MY_STREAM")
|
||||
.ShouldBe("$JS.EVENT.ADVISORY.STREAM.LEADER_ELECTED.MY_STREAM");
|
||||
|
||||
string.Format(EventSubjects.JsAdvisoryStreamQuorumLost, "MY_STREAM")
|
||||
.ShouldBe("$JS.EVENT.ADVISORY.STREAM.QUORUM_LOST.MY_STREAM");
|
||||
}
|
||||
|
||||
// Go reference: jetstream_api.go — full lifecycle sequence (create, update, delete) emits all advisories.
|
||||
[Fact]
|
||||
public void Multiple_advisories_all_published()
|
||||
{
|
||||
var (publisher, published) = CreatePublisher();
|
||||
|
||||
publisher.StreamCreated("LIFECYCLE");
|
||||
publisher.StreamUpdated("LIFECYCLE", new { Reason = "retention_change" });
|
||||
publisher.ConsumerCreated("LIFECYCLE", "worker");
|
||||
publisher.ConsumerDeleted("LIFECYCLE", "worker");
|
||||
publisher.StreamDeleted("LIFECYCLE");
|
||||
|
||||
published.Count.ShouldBe(5);
|
||||
published[0].Subject.ShouldBe("$JS.EVENT.ADVISORY.STREAM.CREATED.LIFECYCLE");
|
||||
published[1].Subject.ShouldBe("$JS.EVENT.ADVISORY.STREAM.UPDATED.LIFECYCLE");
|
||||
published[2].Subject.ShouldBe("$JS.EVENT.ADVISORY.CONSUMER.CREATED.LIFECYCLE.worker");
|
||||
published[3].Subject.ShouldBe("$JS.EVENT.ADVISORY.CONSUMER.DELETED.LIFECYCLE.worker");
|
||||
published[4].Subject.ShouldBe("$JS.EVENT.ADVISORY.STREAM.DELETED.LIFECYCLE");
|
||||
publisher.PublishCount.ShouldBe(5);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user