using System.Text.Json;
using NATS.Server.Events;
namespace NATS.Server.Monitoring.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 NATS.Server.Events.SlowConsumersStats { Clients = 1, Routes = 1 },
StaleConnections = 3,
StaleConnectionStats = new NATS.Server.Events.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);
}
}