Add EventBuilder static class to EventTypes.cs with helpers for constructing fully-populated ConnectEventMsg, DisconnectEventMsg, AccountNumConns, and ServerStatsMsg. Also add RemoteServerShutdownEvent, RemoteServerUpdateEvent, LeafNodeConnectEvent, and LeafNodeDisconnectEvent advisory types. Add FullEventPayloadTests.cs (10 tests) covering all builders, GenerateEventId uniqueness, GetTimestamp ISO 8601 format, DataStats zero defaults, and ConnectEventMsg JSON roundtrip.
309 lines
11 KiB
C#
309 lines
11 KiB
C#
using System.Text.Json;
|
|
using NATS.Server.Events;
|
|
|
|
namespace NATS.Server.Tests.Events;
|
|
|
|
/// <summary>
|
|
/// Tests that EventBuilder produces fully-populated system event messages
|
|
/// and that all field contracts are met.
|
|
/// Go reference: events.go sendConnectEventMsg, sendDisconnectEventMsg,
|
|
/// sendAccountNumConns, sendStatsz — Gap 10.6.
|
|
/// </summary>
|
|
public class FullEventPayloadTests
|
|
{
|
|
// ========================================================================
|
|
// BuildConnectEvent
|
|
// ========================================================================
|
|
|
|
[Fact]
|
|
public void BuildConnectEvent_AllFieldsPopulated()
|
|
{
|
|
// Go reference: events.go sendConnectEventMsg — all client fields must be set.
|
|
var evt = EventBuilder.BuildConnectEvent(
|
|
serverId: "SRV-001",
|
|
serverName: "test-server",
|
|
cluster: "east-cluster",
|
|
clientId: 42,
|
|
host: "10.0.0.1",
|
|
account: "$G",
|
|
user: "alice",
|
|
name: "my-client",
|
|
lang: "csharp",
|
|
version: "1.0.0");
|
|
|
|
evt.ShouldNotBeNull();
|
|
evt.Id.ShouldNotBeNullOrEmpty();
|
|
evt.Time.ShouldBeGreaterThan(DateTime.MinValue);
|
|
evt.Server.Id.ShouldBe("SRV-001");
|
|
evt.Server.Name.ShouldBe("test-server");
|
|
evt.Server.Cluster.ShouldBe("east-cluster");
|
|
evt.Client.Id.ShouldBe(42UL);
|
|
evt.Client.Host.ShouldBe("10.0.0.1");
|
|
evt.Client.Account.ShouldBe("$G");
|
|
evt.Client.User.ShouldBe("alice");
|
|
evt.Client.Name.ShouldBe("my-client");
|
|
evt.Client.Lang.ShouldBe("csharp");
|
|
evt.Client.Version.ShouldBe("1.0.0");
|
|
evt.Client.Start.ShouldBeGreaterThan(DateTime.MinValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildConnectEvent_HasCorrectEventType()
|
|
{
|
|
// Go reference: events.go — connect advisory type constant.
|
|
var evt = EventBuilder.BuildConnectEvent(
|
|
serverId: "SRV-001",
|
|
serverName: "test-server",
|
|
cluster: null,
|
|
clientId: 1,
|
|
host: "127.0.0.1",
|
|
account: null,
|
|
user: null,
|
|
name: null,
|
|
lang: null,
|
|
version: null);
|
|
|
|
evt.Type.ShouldBe(ConnectEventMsg.EventType);
|
|
evt.Type.ShouldBe("io.nats.server.advisory.v1.client_connect");
|
|
}
|
|
|
|
// ========================================================================
|
|
// BuildDisconnectEvent
|
|
// ========================================================================
|
|
|
|
[Fact]
|
|
public void BuildDisconnectEvent_AllFieldsPopulated()
|
|
{
|
|
// Go reference: events.go sendDisconnectEventMsg — sent, received, and reason required.
|
|
var sent = new DataStats { Msgs = 100, Bytes = 2_048 };
|
|
var received = new DataStats { Msgs = 50, Bytes = 1_024 };
|
|
|
|
var evt = EventBuilder.BuildDisconnectEvent(
|
|
serverId: "SRV-002",
|
|
serverName: "test-server",
|
|
cluster: "west-cluster",
|
|
clientId: 99,
|
|
host: "192.168.1.5",
|
|
account: "MYACC",
|
|
user: "bob",
|
|
reason: "Client Closed",
|
|
sent: sent,
|
|
received: received);
|
|
|
|
evt.ShouldNotBeNull();
|
|
evt.Id.ShouldNotBeNullOrEmpty();
|
|
evt.Time.ShouldBeGreaterThan(DateTime.MinValue);
|
|
evt.Server.Id.ShouldBe("SRV-002");
|
|
evt.Server.Name.ShouldBe("test-server");
|
|
evt.Server.Cluster.ShouldBe("west-cluster");
|
|
evt.Client.Id.ShouldBe(99UL);
|
|
evt.Client.Host.ShouldBe("192.168.1.5");
|
|
evt.Client.Account.ShouldBe("MYACC");
|
|
evt.Client.User.ShouldBe("bob");
|
|
evt.Client.Stop.ShouldBeGreaterThan(DateTime.MinValue);
|
|
evt.Reason.ShouldBe("Client Closed");
|
|
evt.Sent.Msgs.ShouldBe(100);
|
|
evt.Sent.Bytes.ShouldBe(2_048);
|
|
evt.Received.Msgs.ShouldBe(50);
|
|
evt.Received.Bytes.ShouldBe(1_024);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildDisconnectEvent_HasCorrectEventType()
|
|
{
|
|
// Go reference: events.go — disconnect advisory type constant.
|
|
var evt = EventBuilder.BuildDisconnectEvent(
|
|
serverId: "SRV-002",
|
|
serverName: "test-server",
|
|
cluster: null,
|
|
clientId: 1,
|
|
host: "127.0.0.1",
|
|
account: null,
|
|
user: null,
|
|
reason: "Server Closed",
|
|
sent: new DataStats(),
|
|
received: new DataStats());
|
|
|
|
evt.Type.ShouldBe(DisconnectEventMsg.EventType);
|
|
evt.Type.ShouldBe("io.nats.server.advisory.v1.client_disconnect");
|
|
}
|
|
|
|
// ========================================================================
|
|
// BuildAccountConnsEvent
|
|
// ========================================================================
|
|
|
|
[Fact]
|
|
public void BuildAccountConnsEvent_AllFieldsPopulated()
|
|
{
|
|
// Go reference: events.go sendAccountNumConns — all AccountStat fields must transfer.
|
|
var sent = new DataStats { Msgs = 500, Bytes = 10_000 };
|
|
var received = new DataStats { Msgs = 400, Bytes = 8_000 };
|
|
|
|
var evt = EventBuilder.BuildAccountConnsEvent(
|
|
serverId: "SRV-003",
|
|
serverName: "test-server",
|
|
accountName: "ACCT-A",
|
|
connections: 7,
|
|
leafNodes: 2,
|
|
totalConnections: 150,
|
|
numSubscriptions: 33,
|
|
sent: sent,
|
|
received: received,
|
|
slowConsumers: 3);
|
|
|
|
evt.ShouldNotBeNull();
|
|
evt.Id.ShouldNotBeNullOrEmpty();
|
|
evt.Time.ShouldBeGreaterThan(DateTime.MinValue);
|
|
evt.Server.Id.ShouldBe("SRV-003");
|
|
evt.Server.Name.ShouldBe("test-server");
|
|
evt.AccountName.ShouldBe("ACCT-A");
|
|
evt.Connections.ShouldBe(7);
|
|
evt.LeafNodes.ShouldBe(2);
|
|
evt.TotalConnections.ShouldBe(150);
|
|
evt.NumSubscriptions.ShouldBe(33u);
|
|
evt.Sent.Msgs.ShouldBe(500);
|
|
evt.Received.Bytes.ShouldBe(8_000);
|
|
evt.SlowConsumers.ShouldBe(3);
|
|
evt.Type.ShouldBe(AccountNumConns.EventType);
|
|
}
|
|
|
|
// ========================================================================
|
|
// BuildServerStats
|
|
// ========================================================================
|
|
|
|
[Fact]
|
|
public void BuildServerStats_AllFieldsPopulated()
|
|
{
|
|
// Go reference: events.go sendStatsz — all stat fields must be set.
|
|
var sent = new DataStats { Msgs = 1_000, Bytes = 50_000 };
|
|
var received = new DataStats { Msgs = 800, Bytes = 40_000 };
|
|
|
|
var msg = EventBuilder.BuildServerStats(
|
|
serverId: "SRV-004",
|
|
serverName: "stats-server",
|
|
mem: 134_217_728,
|
|
cores: 8,
|
|
cpu: 15.5,
|
|
connections: 12,
|
|
totalConnections: 600,
|
|
activeAccounts: 4,
|
|
subscriptions: 55,
|
|
sent: sent,
|
|
received: received);
|
|
|
|
msg.ShouldNotBeNull();
|
|
msg.Server.Id.ShouldBe("SRV-004");
|
|
msg.Server.Name.ShouldBe("stats-server");
|
|
msg.Stats.Mem.ShouldBe(134_217_728);
|
|
msg.Stats.Cores.ShouldBe(8);
|
|
msg.Stats.Cpu.ShouldBe(15.5);
|
|
msg.Stats.Connections.ShouldBe(12);
|
|
msg.Stats.TotalConnections.ShouldBe(600);
|
|
msg.Stats.ActiveAccounts.ShouldBe(4);
|
|
msg.Stats.Subscriptions.ShouldBe(55);
|
|
msg.Stats.Sent.Msgs.ShouldBe(1_000);
|
|
msg.Stats.Received.Bytes.ShouldBe(40_000);
|
|
// Flat counters mirror the structured stats
|
|
msg.Stats.OutMsgs.ShouldBe(1_000);
|
|
msg.Stats.InMsgs.ShouldBe(800);
|
|
msg.Stats.OutBytes.ShouldBe(50_000);
|
|
msg.Stats.InBytes.ShouldBe(40_000);
|
|
msg.Stats.Start.ShouldBeGreaterThan(DateTime.MinValue);
|
|
}
|
|
|
|
// ========================================================================
|
|
// GenerateEventId
|
|
// ========================================================================
|
|
|
|
[Fact]
|
|
public void GenerateEventId_UniquePerCall()
|
|
{
|
|
// Go reference: events.go uses nuid.Next() — every call produces a distinct ID.
|
|
var ids = Enumerable.Range(0, 20).Select(_ => EventBuilder.GenerateEventId()).ToList();
|
|
|
|
ids.Distinct().Count().ShouldBe(20);
|
|
foreach (var id in ids)
|
|
{
|
|
id.ShouldNotBeNullOrEmpty();
|
|
id.Length.ShouldBe(32); // Guid.ToString("N") is always 32 hex chars
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
// GetTimestamp
|
|
// ========================================================================
|
|
|
|
[Fact]
|
|
public void GetTimestamp_ReturnsIso8601()
|
|
{
|
|
// Go reference: events use RFC3339Nano timestamps; .NET "O" format is ISO 8601.
|
|
var ts = EventBuilder.GetTimestamp();
|
|
|
|
ts.ShouldNotBeNullOrEmpty();
|
|
// "O" format: 2025-02-25T12:34:56.7890000Z — parseable as UTC DateTimeOffset
|
|
var parsed = DateTimeOffset.Parse(ts);
|
|
parsed.Year.ShouldBeGreaterThanOrEqualTo(2024);
|
|
}
|
|
|
|
// ========================================================================
|
|
// DataStats default values
|
|
// ========================================================================
|
|
|
|
[Fact]
|
|
public void DataStats_DefaultZeroValues()
|
|
{
|
|
// Go reference: zero-value DataStats is valid and all fields default to 0.
|
|
var ds = new DataStats();
|
|
|
|
ds.Msgs.ShouldBe(0L);
|
|
ds.Bytes.ShouldBe(0L);
|
|
ds.Gateways.ShouldBeNull();
|
|
ds.Routes.ShouldBeNull();
|
|
ds.Leafs.ShouldBeNull();
|
|
}
|
|
|
|
// ========================================================================
|
|
// JSON roundtrip
|
|
// ========================================================================
|
|
|
|
[Fact]
|
|
public void ConnectEventMsg_SerializesToJson()
|
|
{
|
|
// Go reference: events.go — connect event serializes to JSON and round-trips.
|
|
var original = EventBuilder.BuildConnectEvent(
|
|
serverId: "SRV-RT",
|
|
serverName: "roundtrip-server",
|
|
cluster: "rt-cluster",
|
|
clientId: 77,
|
|
host: "10.1.2.3",
|
|
account: "RTACC",
|
|
user: "rtuser",
|
|
name: "rt-client",
|
|
lang: "dotnet",
|
|
version: "2.0.0");
|
|
|
|
var json = JsonSerializer.Serialize(original);
|
|
|
|
json.ShouldNotBeNullOrEmpty();
|
|
json.ShouldContain("\"type\":");
|
|
json.ShouldContain(ConnectEventMsg.EventType);
|
|
json.ShouldContain("\"server\":");
|
|
json.ShouldContain("\"client\":");
|
|
json.ShouldContain("\"SRV-RT\"");
|
|
|
|
var deserialized = JsonSerializer.Deserialize<ConnectEventMsg>(json);
|
|
|
|
deserialized.ShouldNotBeNull();
|
|
deserialized!.Type.ShouldBe(ConnectEventMsg.EventType);
|
|
deserialized.Id.ShouldBe(original.Id);
|
|
deserialized.Server.Id.ShouldBe("SRV-RT");
|
|
deserialized.Server.Name.ShouldBe("roundtrip-server");
|
|
deserialized.Server.Cluster.ShouldBe("rt-cluster");
|
|
deserialized.Client.Id.ShouldBe(77UL);
|
|
deserialized.Client.Account.ShouldBe("RTACC");
|
|
deserialized.Client.User.ShouldBe("rtuser");
|
|
deserialized.Client.Lang.ShouldBe("dotnet");
|
|
deserialized.Client.Version.ShouldBe("2.0.0");
|
|
}
|
|
}
|