using System.Text.Json; using NATS.Server.Events; namespace NATS.Server.Tests.Events; /// /// Tests that all event DTOs have complete JSON fields matching Go's output. /// Go reference: events.go:100-300 — TypedEvent, ServerInfo, ClientInfo, /// DataStats, ServerStats, ConnectEventMsg, DisconnectEventMsg, AccountNumConns. /// public class EventPayloadTests { // --- EventServerInfo --- [Fact] public void EventServerInfo_serializes_all_fields_matching_Go() { var info = new EventServerInfo { Name = "test-server", Host = "127.0.0.1", Id = "ABCDEF123456", Cluster = "test-cluster", Domain = "test-domain", Version = "2.10.0", Tags = ["tag1", "tag2"], Metadata = new Dictionary { ["env"] = "test" }, JetStream = true, Flags = 1, Seq = 42, Time = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc), }; var json = JsonSerializer.Serialize(info); var doc = JsonDocument.Parse(json); var root = doc.RootElement; root.GetProperty("name").GetString().ShouldBe("test-server"); root.GetProperty("host").GetString().ShouldBe("127.0.0.1"); root.GetProperty("id").GetString().ShouldBe("ABCDEF123456"); root.GetProperty("cluster").GetString().ShouldBe("test-cluster"); root.GetProperty("domain").GetString().ShouldBe("test-domain"); root.GetProperty("ver").GetString().ShouldBe("2.10.0"); root.GetProperty("tags").GetArrayLength().ShouldBe(2); root.GetProperty("metadata").GetProperty("env").GetString().ShouldBe("test"); root.GetProperty("jetstream").GetBoolean().ShouldBeTrue(); root.GetProperty("flags").GetUInt64().ShouldBe(1UL); root.GetProperty("seq").GetUInt64().ShouldBe(42UL); root.GetProperty("time").GetDateTime().Year.ShouldBe(2025); } [Fact] public void EventServerInfo_omits_null_optional_fields() { var info = new EventServerInfo { Name = "s", Id = "ID", }; var json = JsonSerializer.Serialize(info); var doc = JsonDocument.Parse(json); var root = doc.RootElement; root.TryGetProperty("cluster", out _).ShouldBeFalse(); root.TryGetProperty("domain", out _).ShouldBeFalse(); root.TryGetProperty("tags", out _).ShouldBeFalse(); root.TryGetProperty("metadata", out _).ShouldBeFalse(); } // --- EventClientInfo --- [Fact] public void EventClientInfo_serializes_all_fields_matching_Go() { var ci = new EventClientInfo { Start = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc), Stop = new DateTime(2025, 1, 1, 1, 0, 0, DateTimeKind.Utc), Host = "10.0.0.1", Id = 99, Account = "$G", Service = "orders", User = "admin", Name = "my-client", Lang = "go", Version = "1.30.0", RttNanos = 5_000_000, // 5ms Server = "srv-1", Cluster = "cluster-east", Alternates = ["alt1", "alt2"], Jwt = "eyJ...", IssuerKey = "OABC...", NameTag = "test-tag", Tags = ["dev"], Kind = "Client", ClientType = "nats", MqttClient = "mqtt-abc", Nonce = "nonce123", }; var json = JsonSerializer.Serialize(ci); var doc = JsonDocument.Parse(json); var root = doc.RootElement; root.GetProperty("host").GetString().ShouldBe("10.0.0.1"); root.GetProperty("id").GetUInt64().ShouldBe(99UL); root.GetProperty("acc").GetString().ShouldBe("$G"); root.GetProperty("svc").GetString().ShouldBe("orders"); root.GetProperty("user").GetString().ShouldBe("admin"); root.GetProperty("name").GetString().ShouldBe("my-client"); root.GetProperty("lang").GetString().ShouldBe("go"); root.GetProperty("ver").GetString().ShouldBe("1.30.0"); root.GetProperty("rtt").GetInt64().ShouldBe(5_000_000); root.GetProperty("server").GetString().ShouldBe("srv-1"); root.GetProperty("cluster").GetString().ShouldBe("cluster-east"); root.GetProperty("alts").GetArrayLength().ShouldBe(2); root.GetProperty("jwt").GetString().ShouldBe("eyJ..."); root.GetProperty("issuer_key").GetString().ShouldBe("OABC..."); root.GetProperty("name_tag").GetString().ShouldBe("test-tag"); root.GetProperty("tags").GetArrayLength().ShouldBe(1); root.GetProperty("kind").GetString().ShouldBe("Client"); root.GetProperty("client_type").GetString().ShouldBe("nats"); root.GetProperty("client_id").GetString().ShouldBe("mqtt-abc"); root.GetProperty("nonce").GetString().ShouldBe("nonce123"); } [Fact] public void EventClientInfo_omits_null_optional_fields() { var ci = new EventClientInfo { Id = 1 }; var json = JsonSerializer.Serialize(ci); var doc = JsonDocument.Parse(json); var root = doc.RootElement; root.TryGetProperty("svc", out _).ShouldBeFalse(); root.TryGetProperty("user", out _).ShouldBeFalse(); root.TryGetProperty("server", out _).ShouldBeFalse(); root.TryGetProperty("cluster", out _).ShouldBeFalse(); root.TryGetProperty("alts", out _).ShouldBeFalse(); root.TryGetProperty("jwt", out _).ShouldBeFalse(); root.TryGetProperty("issuer_key", out _).ShouldBeFalse(); root.TryGetProperty("nonce", out _).ShouldBeFalse(); } // --- DataStats --- [Fact] public void DataStats_serializes_with_optional_sub_stats() { var ds = new DataStats { Msgs = 100, Bytes = 2048, Gateways = new MsgBytesStats { Msgs = 10, Bytes = 256 }, Routes = new MsgBytesStats { Msgs = 50, Bytes = 1024 }, Leafs = new MsgBytesStats { Msgs = 40, Bytes = 768 }, }; var json = JsonSerializer.Serialize(ds); var doc = JsonDocument.Parse(json); var root = doc.RootElement; root.GetProperty("msgs").GetInt64().ShouldBe(100); root.GetProperty("bytes").GetInt64().ShouldBe(2048); root.GetProperty("gateways").GetProperty("msgs").GetInt64().ShouldBe(10); root.GetProperty("routes").GetProperty("bytes").GetInt64().ShouldBe(1024); root.GetProperty("leafs").GetProperty("msgs").GetInt64().ShouldBe(40); } [Fact] public void DataStats_omits_null_sub_stats() { var ds = new DataStats { Msgs = 5, Bytes = 50 }; var json = JsonSerializer.Serialize(ds); var doc = JsonDocument.Parse(json); var root = doc.RootElement; root.TryGetProperty("gateways", out _).ShouldBeFalse(); root.TryGetProperty("routes", out _).ShouldBeFalse(); root.TryGetProperty("leafs", out _).ShouldBeFalse(); } // --- ConnectEventMsg --- [Fact] public void ConnectEventMsg_has_correct_type_and_required_fields() { var evt = new ConnectEventMsg { Id = "evt-1", Time = DateTime.UtcNow, Server = new EventServerInfo { Name = "s1", Id = "SRV1" }, Client = new EventClientInfo { Id = 42, Name = "test-client" }, }; var json = JsonSerializer.Serialize(evt); var doc = JsonDocument.Parse(json); var root = doc.RootElement; root.GetProperty("type").GetString().ShouldBe("io.nats.server.advisory.v1.client_connect"); root.GetProperty("id").GetString().ShouldBe("evt-1"); root.GetProperty("server").GetProperty("name").GetString().ShouldBe("s1"); root.GetProperty("client").GetProperty("id").GetUInt64().ShouldBe(42UL); } // --- DisconnectEventMsg --- [Fact] public void DisconnectEventMsg_has_correct_type_and_data_stats() { var evt = new DisconnectEventMsg { Id = "evt-2", Time = DateTime.UtcNow, Server = new EventServerInfo { Name = "s1", Id = "SRV1" }, Client = new EventClientInfo { Id = 42 }, Sent = new DataStats { Msgs = 100, Bytes = 2000 }, Received = new DataStats { Msgs = 50, Bytes = 1000 }, Reason = "Client Closed", }; var json = JsonSerializer.Serialize(evt); var doc = JsonDocument.Parse(json); var root = doc.RootElement; root.GetProperty("type").GetString().ShouldBe("io.nats.server.advisory.v1.client_disconnect"); root.GetProperty("sent").GetProperty("msgs").GetInt64().ShouldBe(100); root.GetProperty("received").GetProperty("bytes").GetInt64().ShouldBe(1000); root.GetProperty("reason").GetString().ShouldBe("Client Closed"); } // --- AccountNumConns --- [Fact] public void AccountNumConns_serializes_all_Go_AccountStat_fields() { var evt = new AccountNumConns { Id = "evt-3", Time = DateTime.UtcNow, Server = new EventServerInfo { Name = "s1", Id = "SRV1" }, AccountName = "$G", Name = "Global", Connections = 5, LeafNodes = 2, TotalConnections = 100, NumSubscriptions = 42, Sent = new DataStats { Msgs = 500, Bytes = 10_000 }, Received = new DataStats { Msgs = 400, Bytes = 8_000 }, SlowConsumers = 1, }; var json = JsonSerializer.Serialize(evt); var doc = JsonDocument.Parse(json); var root = doc.RootElement; root.GetProperty("type").GetString().ShouldBe("io.nats.server.advisory.v1.account_connections"); root.GetProperty("acc").GetString().ShouldBe("$G"); root.GetProperty("name").GetString().ShouldBe("Global"); root.GetProperty("conns").GetInt32().ShouldBe(5); root.GetProperty("leafnodes").GetInt32().ShouldBe(2); root.GetProperty("total_conns").GetInt32().ShouldBe(100); root.GetProperty("num_subscriptions").GetUInt32().ShouldBe(42u); root.GetProperty("sent").GetProperty("msgs").GetInt64().ShouldBe(500); root.GetProperty("received").GetProperty("bytes").GetInt64().ShouldBe(8_000); root.GetProperty("slow_consumers").GetInt64().ShouldBe(1); } // --- ServerStatsMsg --- [Fact] public void ServerStatsMsg_has_sent_received_and_breakdown_fields() { var msg = new ServerStatsMsg { Server = new EventServerInfo { Name = "s1", Id = "SRV1", Seq = 1 }, Stats = new ServerStatsData { Start = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc), Mem = 100_000_000, Cores = 8, Cpu = 12.5, Connections = 10, TotalConnections = 500, ActiveAccounts = 3, Subscriptions = 50, Sent = new DataStats { Msgs = 1000, Bytes = 50_000 }, Received = new DataStats { Msgs = 800, Bytes = 40_000 }, InMsgs = 800, OutMsgs = 1000, InBytes = 40_000, OutBytes = 50_000, SlowConsumers = 2, SlowConsumerStats = new SlowConsumersStats { Clients = 1, Routes = 1 }, StaleConnections = 3, StaleConnectionStats = new StaleConnectionStats { Clients = 2, Leafs = 1 }, ActiveServers = 3, Routes = [new RouteStat { Id = 1, Name = "r1", Sent = new DataStats { Msgs = 10 }, Received = new DataStats { Msgs = 5 }, Pending = 0 }], Gateways = [new GatewayStat { Id = 1, Name = "gw1", Sent = new DataStats { Msgs = 20 }, Received = new DataStats { Msgs = 15 }, InboundConnections = 2 }], }, }; var json = JsonSerializer.Serialize(msg); var doc = JsonDocument.Parse(json); var root = doc.RootElement; var stats = root.GetProperty("statsz"); stats.GetProperty("mem").GetInt64().ShouldBe(100_000_000); stats.GetProperty("cores").GetInt32().ShouldBe(8); stats.GetProperty("cpu").GetDouble().ShouldBe(12.5); stats.GetProperty("connections").GetInt32().ShouldBe(10); stats.GetProperty("total_connections").GetInt64().ShouldBe(500); stats.GetProperty("active_accounts").GetInt32().ShouldBe(3); stats.GetProperty("subscriptions").GetInt64().ShouldBe(50); stats.GetProperty("sent").GetProperty("msgs").GetInt64().ShouldBe(1000); stats.GetProperty("received").GetProperty("bytes").GetInt64().ShouldBe(40_000); stats.GetProperty("in_msgs").GetInt64().ShouldBe(800); stats.GetProperty("out_msgs").GetInt64().ShouldBe(1000); stats.GetProperty("slow_consumers").GetInt64().ShouldBe(2); stats.GetProperty("slow_consumer_stats").GetProperty("clients").GetInt64().ShouldBe(1); stats.GetProperty("stale_connections").GetInt64().ShouldBe(3); stats.GetProperty("stale_connection_stats").GetProperty("leafs").GetInt64().ShouldBe(1); stats.GetProperty("active_servers").GetInt32().ShouldBe(3); stats.GetProperty("routes").GetArrayLength().ShouldBe(1); stats.GetProperty("routes")[0].GetProperty("rid").GetUInt64().ShouldBe(1UL); stats.GetProperty("gateways").GetArrayLength().ShouldBe(1); stats.GetProperty("gateways")[0].GetProperty("name").GetString().ShouldBe("gw1"); } // --- AuthErrorEventMsg --- [Fact] public void AuthErrorEventMsg_has_correct_type() { var evt = new AuthErrorEventMsg { Id = "evt-4", Time = DateTime.UtcNow, Server = new EventServerInfo { Name = "s1", Id = "SRV1" }, Client = new EventClientInfo { Id = 99, Host = "10.0.0.1" }, Reason = "Authorization Violation", }; var json = JsonSerializer.Serialize(evt); var doc = JsonDocument.Parse(json); var root = doc.RootElement; root.GetProperty("type").GetString().ShouldBe("io.nats.server.advisory.v1.client_auth"); root.GetProperty("reason").GetString().ShouldBe("Authorization Violation"); root.GetProperty("client").GetProperty("host").GetString().ShouldBe("10.0.0.1"); } // --- OcspPeerRejectEventMsg --- [Fact] public void OcspPeerRejectEventMsg_has_correct_type() { var evt = new OcspPeerRejectEventMsg { Id = "evt-5", Time = DateTime.UtcNow, Kind = "client", Server = new EventServerInfo { Name = "s1", Id = "SRV1" }, Reason = "OCSP revoked", }; var json = JsonSerializer.Serialize(evt); var doc = JsonDocument.Parse(json); var root = doc.RootElement; root.GetProperty("type").GetString().ShouldBe("io.nats.server.advisory.v1.ocsp_peer_reject"); root.GetProperty("kind").GetString().ShouldBe("client"); root.GetProperty("reason").GetString().ShouldBe("OCSP revoked"); } // --- ShutdownEventMsg --- [Fact] public void ShutdownEventMsg_serializes_reason() { var evt = new ShutdownEventMsg { Server = new EventServerInfo { Name = "s1", Id = "SRV1" }, Reason = "Server Shutdown", }; var json = JsonSerializer.Serialize(evt); var doc = JsonDocument.Parse(json); doc.RootElement.GetProperty("reason").GetString().ShouldBe("Server Shutdown"); } // --- AccNumConnsReq --- [Fact] public void AccNumConnsReq_serializes_account() { var req = new AccNumConnsReq { Server = new EventServerInfo { Name = "s1", Id = "SRV1" }, Account = "myAccount", }; var json = JsonSerializer.Serialize(req); var doc = JsonDocument.Parse(json); doc.RootElement.GetProperty("acc").GetString().ShouldBe("myAccount"); } // --- Round-trip deserialization --- [Fact] public void ConnectEventMsg_roundtrips_through_json() { var original = new ConnectEventMsg { Id = "rt-1", Time = new DateTime(2025, 6, 15, 12, 0, 0, DateTimeKind.Utc), Server = new EventServerInfo { Name = "srv", Id = "SRV1", Version = "2.10.0", Seq = 5 }, Client = new EventClientInfo { Id = 42, Host = "10.0.0.1", Account = "$G", Name = "test", Lang = "dotnet", Version = "1.0.0", RttNanos = 1_000_000, Kind = "Client", }, }; var json = JsonSerializer.Serialize(original); var deserialized = JsonSerializer.Deserialize(json); deserialized.ShouldNotBeNull(); deserialized.Type.ShouldBe(ConnectEventMsg.EventType); deserialized.Id.ShouldBe("rt-1"); deserialized.Server.Name.ShouldBe("srv"); deserialized.Server.Seq.ShouldBe(5UL); deserialized.Client.Id.ShouldBe(42UL); deserialized.Client.Kind.ShouldBe("Client"); deserialized.Client.RttNanos.ShouldBe(1_000_000); } [Fact] public void ServerStatsMsg_roundtrips_through_json() { var original = new ServerStatsMsg { Server = new EventServerInfo { Name = "srv", Id = "SRV1" }, Stats = new ServerStatsData { Connections = 10, Sent = new DataStats { Msgs = 100, Bytes = 5000 }, Received = new DataStats { Msgs = 80, Bytes = 4000 }, InMsgs = 80, OutMsgs = 100, }, }; var json = JsonSerializer.Serialize(original); var deserialized = JsonSerializer.Deserialize(json); deserialized.ShouldNotBeNull(); deserialized.Stats.Connections.ShouldBe(10); deserialized.Stats.Sent.Msgs.ShouldBe(100); deserialized.Stats.Received.Bytes.ShouldBe(4000); } }