Files
natsdotnet/tests/NATS.Server.Tests/Events/EventGoParityTests.cs
Joseph Doherty 579063dabd test(parity): port 373 Go tests across protocol and services subsystems (C11+E15)
Protocol (C11):
- ClientProtocolGoParityTests: 45 tests (header stripping, tracing, limits, NRG)
- ConsumerGoParityTests: 60 tests (filters, actions, pinned, priority groups)
- JetStreamGoParityTests: 38 tests (stream CRUD, purge, mirror, retention)

Services (E15):
- MqttGoParityTests: 65 tests (packet parsing, QoS, retained, sessions)
- WsGoParityTests: 58 tests (compression, JWT auth, frame encoding)
- EventGoParityTests: 56 tests (event DTOs, serialization, health checks)
- AccountGoParityTests: 28 tests (route mapping, system account, limits)
- MonitorGoParityTests: 23 tests (connz filtering, pagination, sort)

DB: 1,148/2,937 mapped (39.1%), up from 1,012 (34.5%)
2026-02-24 16:52:15 -05:00

944 lines
32 KiB
C#

// Port of Go server/events_test.go — system event DTO and subject parity tests.
// Reference: golang/nats-server/server/events_test.go
//
// Tests cover: ConnectEventMsg, DisconnectEventMsg, ServerStatsMsg,
// AccountNumConns, AuthErrorEventMsg, ShutdownEventMsg serialization,
// event subject pattern formatting, event filtering by tag/server ID,
// and HealthZ status code mapping.
using System.Text.Json;
using NATS.Server.Events;
namespace NATS.Server.Tests.Events;
/// <summary>
/// Parity tests ported from Go server/events_test.go exercising
/// system event DTOs, JSON serialization shapes, event subjects,
/// and event filtering logic.
/// </summary>
public class EventGoParityTests
{
// ========================================================================
// ConnectEventMsg serialization
// Go reference: events_test.go TestSystemAccountNewConnection
// ========================================================================
[Fact]
public void ConnectEventMsg_JsonShape_MatchesGo()
{
// Go: TestSystemAccountNewConnection — verifies connect event JSON shape.
var evt = new ConnectEventMsg
{
Id = "evt-001",
Time = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),
Server = new EventServerInfo
{
Name = "test-server",
Id = "NSVR001",
Cluster = "test-cluster",
Version = "2.10.0",
},
Client = new EventClientInfo
{
Id = 42,
Account = "$G",
User = "alice",
Name = "test-client",
Lang = "csharp",
Version = "1.0",
},
};
var json = JsonSerializer.Serialize(evt);
json.ShouldContain("\"type\":");
json.ShouldContain(ConnectEventMsg.EventType);
json.ShouldContain("\"server\":");
json.ShouldContain("\"client\":");
json.ShouldContain("\"id\":\"evt-001\"");
}
[Fact]
public void ConnectEventMsg_EventType_Constant()
{
// Go: connect event type string.
ConnectEventMsg.EventType.ShouldBe("io.nats.server.advisory.v1.client_connect");
}
[Fact]
public void ConnectEventMsg_DefaultType_MatchesConstant()
{
var evt = new ConnectEventMsg();
evt.Type.ShouldBe(ConnectEventMsg.EventType);
}
// ========================================================================
// DisconnectEventMsg serialization
// Go reference: events_test.go TestSystemAccountNewConnection (disconnect part)
// ========================================================================
[Fact]
public void DisconnectEventMsg_JsonShape_MatchesGo()
{
// Go: TestSystemAccountNewConnection — verifies disconnect event includes
// sent/received stats and reason.
var evt = new DisconnectEventMsg
{
Id = "evt-002",
Time = DateTime.UtcNow,
Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
Client = new EventClientInfo { Id = 42, Account = "$G" },
Sent = new DataStats { Msgs = 100, Bytes = 10240 },
Received = new DataStats { Msgs = 50, Bytes = 5120 },
Reason = "Client Closed",
};
var json = JsonSerializer.Serialize(evt);
json.ShouldContain("\"type\":");
json.ShouldContain(DisconnectEventMsg.EventType);
json.ShouldContain("\"sent\":");
json.ShouldContain("\"received\":");
json.ShouldContain("\"reason\":");
}
[Fact]
public void DisconnectEventMsg_EventType_Constant()
{
DisconnectEventMsg.EventType.ShouldBe("io.nats.server.advisory.v1.client_disconnect");
}
[Fact]
public void DisconnectEventMsg_Reason_ClientClosed()
{
// Go: TestSystemAccountDisconnectBadLogin — reason is captured on disconnect.
var evt = new DisconnectEventMsg { Reason = "Client Closed" };
evt.Reason.ShouldBe("Client Closed");
}
[Fact]
public void DisconnectEventMsg_Reason_AuthViolation()
{
// Go: TestSystemAccountDisconnectBadLogin — bad login reason.
var evt = new DisconnectEventMsg { Reason = "Authentication Violation" };
evt.Reason.ShouldBe("Authentication Violation");
}
// ========================================================================
// DataStats
// Go reference: events_test.go TestSystemAccountingWithLeafNodes
// ========================================================================
[Fact]
public void DataStats_JsonSerialization()
{
// Go: TestSystemAccountingWithLeafNodes — verifies sent/received stats structure.
var stats = new DataStats
{
Msgs = 1000,
Bytes = 65536,
Routes = new MsgBytesStats { Msgs = 200, Bytes = 10240 },
Gateways = new MsgBytesStats { Msgs = 50, Bytes = 2048 },
Leafs = new MsgBytesStats { Msgs = 100, Bytes = 5120 },
};
var json = JsonSerializer.Serialize(stats);
json.ShouldContain("\"msgs\":");
json.ShouldContain("\"bytes\":");
json.ShouldContain("\"routes\":");
json.ShouldContain("\"gateways\":");
json.ShouldContain("\"leafs\":");
}
[Fact]
public void DataStats_NullSubStats_OmittedFromJson()
{
// Go: When no routes/gateways/leafs, those fields are omitted (omitempty).
var stats = new DataStats { Msgs = 100, Bytes = 1024 };
var json = JsonSerializer.Serialize(stats);
json.ShouldNotContain("\"routes\":");
json.ShouldNotContain("\"gateways\":");
json.ShouldNotContain("\"leafs\":");
}
// ========================================================================
// AccountNumConns
// Go reference: events_test.go TestAccountReqMonitoring
// ========================================================================
[Fact]
public void AccountNumConns_JsonShape_MatchesGo()
{
// Go: TestAccountReqMonitoring — verifies account connection count event shape.
var evt = new AccountNumConns
{
Id = "evt-003",
Time = DateTime.UtcNow,
Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
AccountName = "MYACCOUNT",
Connections = 5,
LeafNodes = 2,
TotalConnections = 10,
NumSubscriptions = 42,
Sent = new DataStats { Msgs = 500, Bytes = 25600 },
Received = new DataStats { Msgs = 250, Bytes = 12800 },
};
var json = JsonSerializer.Serialize(evt);
json.ShouldContain("\"type\":");
json.ShouldContain(AccountNumConns.EventType);
json.ShouldContain("\"acc\":");
json.ShouldContain("\"conns\":");
json.ShouldContain("\"leafnodes\":");
json.ShouldContain("\"total_conns\":");
json.ShouldContain("\"num_subscriptions\":");
}
[Fact]
public void AccountNumConns_EventType_Constant()
{
AccountNumConns.EventType.ShouldBe("io.nats.server.advisory.v1.account_connections");
}
[Fact]
public void AccountNumConns_SlowConsumers_IncludedWhenNonZero()
{
var evt = new AccountNumConns { SlowConsumers = 3 };
var json = JsonSerializer.Serialize(evt);
json.ShouldContain("\"slow_consumers\":3");
}
[Fact]
public void AccountNumConns_SlowConsumers_OmittedWhenZero()
{
// Go: omitempty behavior — zero slow_consumers omitted.
var evt = new AccountNumConns { SlowConsumers = 0 };
var json = JsonSerializer.Serialize(evt);
json.ShouldNotContain("\"slow_consumers\":");
}
// ========================================================================
// ServerStatsMsg
// Go reference: events_test.go TestServerEventsPingStatsZDedicatedRecvQ
// ========================================================================
[Fact]
public void ServerStatsMsg_JsonShape_MatchesGo()
{
// Go: TestServerEventsPingStatsZDedicatedRecvQ — verifies server stats shape.
var msg = new ServerStatsMsg
{
Server = new EventServerInfo
{
Name = "test-server",
Id = "NSVR001",
Version = "2.10.0",
JetStream = true,
},
Stats = new ServerStatsData
{
Start = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),
Mem = 134217728,
Cores = 8,
Cpu = 12.5,
Connections = 10,
TotalConnections = 100,
ActiveAccounts = 5,
Subscriptions = 42,
Sent = new DataStats { Msgs = 1000, Bytes = 65536 },
Received = new DataStats { Msgs = 500, Bytes = 32768 },
InMsgs = 500,
OutMsgs = 1000,
InBytes = 32768,
OutBytes = 65536,
},
};
var json = JsonSerializer.Serialize(msg);
json.ShouldContain("\"server\":");
json.ShouldContain("\"statsz\":");
json.ShouldContain("\"mem\":");
json.ShouldContain("\"cores\":");
json.ShouldContain("\"connections\":");
json.ShouldContain("\"total_connections\":");
json.ShouldContain("\"subscriptions\":");
json.ShouldContain("\"in_msgs\":");
json.ShouldContain("\"out_msgs\":");
}
[Fact]
public void ServerStatsData_SlowConsumerStats_JsonShape()
{
// Go: TestServerEventsPingStatsSlowConsumersStats — breakdown by type.
var data = new ServerStatsData
{
SlowConsumers = 10,
SlowConsumerStats = new SlowConsumersStats
{
Clients = 5,
Routes = 2,
Gateways = 1,
Leafs = 2,
},
};
var json = JsonSerializer.Serialize(data);
json.ShouldContain("\"slow_consumers\":10");
json.ShouldContain("\"slow_consumer_stats\":");
json.ShouldContain("\"clients\":5");
json.ShouldContain("\"routes\":2");
}
[Fact]
public void ServerStatsData_StaleConnectionStats_JsonShape()
{
// Go: TestServerEventsPingStatsStaleConnectionStats — stale conn breakdown.
var data = new ServerStatsData
{
StaleConnections = 7,
StaleConnectionStats = new StaleConnectionStats
{
Clients = 3,
Routes = 1,
Gateways = 2,
Leafs = 1,
},
};
var json = JsonSerializer.Serialize(data);
json.ShouldContain("\"stale_connections\":7");
json.ShouldContain("\"stale_connection_stats\":");
}
[Fact]
public void ServerStatsData_RouteStats_JsonShape()
{
// Go: TestServerEventsPingStatsZDedicatedRecvQ — route stats in statsz.
var data = new ServerStatsData
{
Routes =
[
new RouteStat
{
Id = 100,
Name = "route-1",
Sent = new DataStats { Msgs = 200, Bytes = 10240 },
Received = new DataStats { Msgs = 150, Bytes = 7680 },
Pending = 5,
},
],
};
var json = JsonSerializer.Serialize(data);
json.ShouldContain("\"routes\":");
json.ShouldContain("\"rid\":100");
json.ShouldContain("\"pending\":5");
}
[Fact]
public void ServerStatsData_GatewayStats_JsonShape()
{
// Go: TestGatewayNameClientInfo — gateway stats in statsz.
var data = new ServerStatsData
{
Gateways =
[
new GatewayStat
{
Id = 200,
Name = "gw-east",
Sent = new DataStats { Msgs = 500, Bytes = 25600 },
Received = new DataStats { Msgs = 300, Bytes = 15360 },
InboundConnections = 3,
},
],
};
var json = JsonSerializer.Serialize(data);
json.ShouldContain("\"gateways\":");
json.ShouldContain("\"gwid\":200");
json.ShouldContain("\"inbound_connections\":3");
}
// ========================================================================
// ShutdownEventMsg
// Go reference: events_test.go TestServerEventsLDMKick
// ========================================================================
[Fact]
public void ShutdownEventMsg_JsonShape_MatchesGo()
{
// Go: ShutdownEventMsg includes server info and reason.
var evt = new ShutdownEventMsg
{
Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
Reason = "process exit",
};
var json = JsonSerializer.Serialize(evt);
json.ShouldContain("\"server\":");
json.ShouldContain("\"reason\":");
json.ShouldContain("\"process exit\"");
}
// ========================================================================
// LameDuckEventMsg
// Go reference: events_test.go TestServerEventsLDMKick
// ========================================================================
[Fact]
public void LameDuckEventMsg_JsonShape_MatchesGo()
{
// Go: TestServerEventsLDMKick — lame duck event emitted before shutdown.
var evt = new LameDuckEventMsg
{
Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
};
var json = JsonSerializer.Serialize(evt);
json.ShouldContain("\"server\":");
json.ShouldContain("\"name\":\"test-server\"");
}
// ========================================================================
// AuthErrorEventMsg
// Go reference: events_test.go TestSystemAccountDisconnectBadLogin
// ========================================================================
[Fact]
public void AuthErrorEventMsg_JsonShape_MatchesGo()
{
// Go: TestSystemAccountDisconnectBadLogin — auth error advisory.
var evt = new AuthErrorEventMsg
{
Id = "evt-004",
Time = DateTime.UtcNow,
Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
Client = new EventClientInfo { Id = 99, Host = "192.168.1.100" },
Reason = "Authorization Violation",
};
var json = JsonSerializer.Serialize(evt);
json.ShouldContain("\"type\":");
json.ShouldContain(AuthErrorEventMsg.EventType);
json.ShouldContain("\"reason\":");
json.ShouldContain("\"Authorization Violation\"");
}
[Fact]
public void AuthErrorEventMsg_EventType_Constant()
{
AuthErrorEventMsg.EventType.ShouldBe("io.nats.server.advisory.v1.client_auth");
}
// ========================================================================
// OcspPeerRejectEventMsg
// Go reference: events.go OCSPPeerRejectEventMsg struct
// ========================================================================
[Fact]
public void OcspPeerRejectEventMsg_JsonShape_MatchesGo()
{
var evt = new OcspPeerRejectEventMsg
{
Id = "evt-005",
Time = DateTime.UtcNow,
Kind = "client",
Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
Reason = "OCSP certificate revoked",
};
var json = JsonSerializer.Serialize(evt);
json.ShouldContain("\"type\":");
json.ShouldContain(OcspPeerRejectEventMsg.EventType);
json.ShouldContain("\"kind\":\"client\"");
json.ShouldContain("\"reason\":");
}
[Fact]
public void OcspPeerRejectEventMsg_EventType_Constant()
{
OcspPeerRejectEventMsg.EventType.ShouldBe("io.nats.server.advisory.v1.ocsp_peer_reject");
}
// ========================================================================
// AccNumConnsReq
// Go reference: events.go accNumConnsReq
// ========================================================================
[Fact]
public void AccNumConnsReq_JsonShape_MatchesGo()
{
var req = new AccNumConnsReq
{
Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
Account = "$G",
};
var json = JsonSerializer.Serialize(req);
json.ShouldContain("\"server\":");
json.ShouldContain("\"acc\":\"$G\"");
}
// ========================================================================
// EventServerInfo
// Go reference: events_test.go TestServerEventsFilteredByTag
// ========================================================================
[Fact]
public void EventServerInfo_Tags_Serialized()
{
// Go: TestServerEventsFilteredByTag — server info includes tags for filtering.
var info = new EventServerInfo
{
Name = "test-server",
Id = "NSVR001",
Tags = ["region:us-east-1", "env:production"],
};
var json = JsonSerializer.Serialize(info);
json.ShouldContain("\"tags\":");
json.ShouldContain("\"region:us-east-1\"");
json.ShouldContain("\"env:production\"");
}
[Fact]
public void EventServerInfo_NullTags_OmittedFromJson()
{
// Go: omitempty — nil tags are not serialized.
var info = new EventServerInfo { Name = "test-server", Id = "NSVR001" };
var json = JsonSerializer.Serialize(info);
json.ShouldNotContain("\"tags\":");
}
[Fact]
public void EventServerInfo_Metadata_Serialized()
{
var info = new EventServerInfo
{
Name = "test-server",
Id = "NSVR001",
Metadata = new Dictionary<string, string>
{
["cloud"] = "aws",
["zone"] = "us-east-1a",
},
};
var json = JsonSerializer.Serialize(info);
json.ShouldContain("\"metadata\":");
json.ShouldContain("\"cloud\":");
json.ShouldContain("\"aws\"");
}
[Fact]
public void EventServerInfo_NullMetadata_OmittedFromJson()
{
var info = new EventServerInfo { Name = "test-server", Id = "NSVR001" };
var json = JsonSerializer.Serialize(info);
json.ShouldNotContain("\"metadata\":");
}
[Fact]
public void EventServerInfo_JetStream_IncludedWhenTrue()
{
var info = new EventServerInfo { Name = "s1", Id = "N1", JetStream = true };
var json = JsonSerializer.Serialize(info);
json.ShouldContain("\"jetstream\":true");
}
[Fact]
public void EventServerInfo_JetStream_OmittedWhenFalse()
{
// Go: omitempty — JetStream false is not serialized.
var info = new EventServerInfo { Name = "s1", Id = "N1", JetStream = false };
var json = JsonSerializer.Serialize(info);
json.ShouldNotContain("\"jetstream\":");
}
// ========================================================================
// EventClientInfo
// Go reference: events_test.go TestGatewayNameClientInfo
// ========================================================================
[Fact]
public void EventClientInfo_AllFields_Serialized()
{
// Go: TestGatewayNameClientInfo — client info includes all connection metadata.
var info = new EventClientInfo
{
Id = 42,
Account = "MYACCOUNT",
User = "alice",
Name = "test-client",
Lang = "go",
Version = "1.30.0",
RttNanos = 1_500_000, // 1.5ms
Host = "192.168.1.100",
Kind = "Client",
ClientType = "nats",
Tags = ["role:publisher"],
};
var json = JsonSerializer.Serialize(info);
json.ShouldContain("\"id\":42");
json.ShouldContain("\"acc\":\"MYACCOUNT\"");
json.ShouldContain("\"user\":\"alice\"");
json.ShouldContain("\"name\":\"test-client\"");
json.ShouldContain("\"lang\":\"go\"");
json.ShouldContain("\"rtt\":");
json.ShouldContain("\"kind\":\"Client\"");
}
[Fact]
public void EventClientInfo_MqttClient_Serialized()
{
// Go: MQTT client ID is included in client info when present.
var info = new EventClientInfo
{
Id = 10,
MqttClient = "mqtt-device-42",
};
var json = JsonSerializer.Serialize(info);
json.ShouldContain("\"client_id\":\"mqtt-device-42\"");
}
[Fact]
public void EventClientInfo_NullOptionalFields_OmittedFromJson()
{
// Go: omitempty — null optional fields are not serialized.
var info = new EventClientInfo { Id = 1 };
var json = JsonSerializer.Serialize(info);
json.ShouldNotContain("\"acc\":");
json.ShouldNotContain("\"user\":");
json.ShouldNotContain("\"name\":");
json.ShouldNotContain("\"lang\":");
json.ShouldNotContain("\"kind\":");
json.ShouldNotContain("\"tags\":");
}
// ========================================================================
// Event Subject Patterns
// Go reference: events.go subject constants
// ========================================================================
[Fact]
public void EventSubjects_ConnectEvent_Format()
{
// Go: $SYS.ACCOUNT.%s.CONNECT
var subject = string.Format(EventSubjects.ConnectEvent, "$G");
subject.ShouldBe("$SYS.ACCOUNT.$G.CONNECT");
}
[Fact]
public void EventSubjects_DisconnectEvent_Format()
{
// Go: $SYS.ACCOUNT.%s.DISCONNECT
var subject = string.Format(EventSubjects.DisconnectEvent, "$G");
subject.ShouldBe("$SYS.ACCOUNT.$G.DISCONNECT");
}
[Fact]
public void EventSubjects_AccountConns_Format()
{
// Go: $SYS.ACCOUNT.%s.SERVER.CONNS (new format)
var subject = string.Format(EventSubjects.AccountConnsNew, "MYACCOUNT");
subject.ShouldBe("$SYS.ACCOUNT.MYACCOUNT.SERVER.CONNS");
}
[Fact]
public void EventSubjects_AccountConnsOld_Format()
{
// Go: $SYS.SERVER.ACCOUNT.%s.CONNS (old format for backward compat)
var subject = string.Format(EventSubjects.AccountConnsOld, "MYACCOUNT");
subject.ShouldBe("$SYS.SERVER.ACCOUNT.MYACCOUNT.CONNS");
}
[Fact]
public void EventSubjects_ServerStats_Format()
{
// Go: $SYS.SERVER.%s.STATSZ
var subject = string.Format(EventSubjects.ServerStats, "NSVR001");
subject.ShouldBe("$SYS.SERVER.NSVR001.STATSZ");
}
[Fact]
public void EventSubjects_ServerShutdown_Format()
{
// Go: $SYS.SERVER.%s.SHUTDOWN
var subject = string.Format(EventSubjects.ServerShutdown, "NSVR001");
subject.ShouldBe("$SYS.SERVER.NSVR001.SHUTDOWN");
}
[Fact]
public void EventSubjects_ServerLameDuck_Format()
{
// Go: $SYS.SERVER.%s.LAMEDUCK
var subject = string.Format(EventSubjects.ServerLameDuck, "NSVR001");
subject.ShouldBe("$SYS.SERVER.NSVR001.LAMEDUCK");
}
[Fact]
public void EventSubjects_AuthError_Format()
{
// Go: $SYS.SERVER.%s.CLIENT.AUTH.ERR
var subject = string.Format(EventSubjects.AuthError, "NSVR001");
subject.ShouldBe("$SYS.SERVER.NSVR001.CLIENT.AUTH.ERR");
}
[Fact]
public void EventSubjects_AuthErrorAccount_IsConstant()
{
// Go: $SYS.ACCOUNT.CLIENT.AUTH.ERR (no server ID interpolation)
EventSubjects.AuthErrorAccount.ShouldBe("$SYS.ACCOUNT.CLIENT.AUTH.ERR");
}
[Fact]
public void EventSubjects_ServerPing_Format()
{
// Go: $SYS.REQ.SERVER.PING.%s (e.g., STATSZ, VARZ)
var subject = string.Format(EventSubjects.ServerPing, "STATSZ");
subject.ShouldBe("$SYS.REQ.SERVER.PING.STATSZ");
}
[Fact]
public void EventSubjects_ServerReq_Format()
{
// Go: $SYS.REQ.SERVER.%s.%s (server ID + request type)
var subject = string.Format(EventSubjects.ServerReq, "NSVR001", "VARZ");
subject.ShouldBe("$SYS.REQ.SERVER.NSVR001.VARZ");
}
[Fact]
public void EventSubjects_AccountReq_Format()
{
// Go: $SYS.REQ.ACCOUNT.%s.%s (account + request type)
var subject = string.Format(EventSubjects.AccountReq, "MYACCOUNT", "CONNZ");
subject.ShouldBe("$SYS.REQ.ACCOUNT.MYACCOUNT.CONNZ");
}
// ========================================================================
// Event filtering by tag
// Go reference: events_test.go TestServerEventsFilteredByTag
// ========================================================================
[Fact]
public void EventServerInfo_TagFiltering_MatchesTag()
{
// Go: TestServerEventsFilteredByTag — servers can be filtered by tag value.
var server = new EventServerInfo
{
Name = "s1",
Id = "NSVR001",
Tags = ["region:us-east-1", "env:prod"],
};
// Simulate filtering: check if server has a specific tag.
server.Tags.ShouldContain("region:us-east-1");
server.Tags.ShouldContain("env:prod");
server.Tags.ShouldNotContain("region:eu-west-1");
}
[Fact]
public void EventServerInfo_TagFiltering_EmptyTags_NoMatch()
{
// Go: TestServerEventsFilteredByTag — server with no tags does not match any filter.
var server = new EventServerInfo { Name = "s1", Id = "NSVR001" };
server.Tags.ShouldBeNull();
}
[Fact]
public void EventServerInfo_FilterByServerId()
{
// Go: TestServerEventsPingStatsZFilter — filter stats events by server ID.
var servers = new[]
{
new EventServerInfo { Name = "s1", Id = "NSVR001" },
new EventServerInfo { Name = "s2", Id = "NSVR002" },
new EventServerInfo { Name = "s3", Id = "NSVR003" },
};
var filtered = servers.Where(s => s.Id == "NSVR002").ToArray();
filtered.Length.ShouldBe(1);
filtered[0].Name.ShouldBe("s2");
}
[Fact]
public void EventServerInfo_FilterByServerId_NoMatch()
{
// Go: TestServerEventsPingStatsZFailFilter — non-existent server ID returns nothing.
var servers = new[]
{
new EventServerInfo { Name = "s1", Id = "NSVR001" },
};
var filtered = servers.Where(s => s.Id == "NONEXISTENT").ToArray();
filtered.Length.ShouldBe(0);
}
// ========================================================================
// Event JSON roundtrip via source-generated context
// Go reference: events_test.go TestServerEventsReceivedByQSubs
// ========================================================================
[Fact]
public void ConnectEventMsg_RoundTrip_ViaContext()
{
// Go: TestServerEventsReceivedByQSubs — events received and parsed correctly.
var original = new ConnectEventMsg
{
Id = "roundtrip-001",
Time = new DateTime(2024, 6, 15, 12, 0, 0, DateTimeKind.Utc),
Server = new EventServerInfo { Name = "s1", Id = "NSVR001" },
Client = new EventClientInfo { Id = 42, Account = "$G", User = "alice" },
};
var json = JsonSerializer.Serialize(original, EventJsonContext.Default.ConnectEventMsg);
var deserialized = JsonSerializer.Deserialize(json, EventJsonContext.Default.ConnectEventMsg);
deserialized.ShouldNotBeNull();
deserialized!.Id.ShouldBe("roundtrip-001");
deserialized.Type.ShouldBe(ConnectEventMsg.EventType);
deserialized.Server.Name.ShouldBe("s1");
deserialized.Client.Id.ShouldBe(42UL);
deserialized.Client.Account.ShouldBe("$G");
}
[Fact]
public void DisconnectEventMsg_RoundTrip_ViaContext()
{
var original = new DisconnectEventMsg
{
Id = "roundtrip-002",
Time = DateTime.UtcNow,
Server = new EventServerInfo { Name = "s1", Id = "NSVR001" },
Client = new EventClientInfo { Id = 99 },
Sent = new DataStats { Msgs = 100, Bytes = 1024 },
Received = new DataStats { Msgs = 50, Bytes = 512 },
Reason = "Client Closed",
};
var json = JsonSerializer.Serialize(original, EventJsonContext.Default.DisconnectEventMsg);
var deserialized = JsonSerializer.Deserialize(json, EventJsonContext.Default.DisconnectEventMsg);
deserialized.ShouldNotBeNull();
deserialized!.Reason.ShouldBe("Client Closed");
deserialized.Sent.Msgs.ShouldBe(100);
deserialized.Received.Bytes.ShouldBe(512);
}
[Fact]
public void ServerStatsMsg_RoundTrip_ViaContext()
{
var original = new ServerStatsMsg
{
Server = new EventServerInfo { Name = "s1", Id = "NSVR001", JetStream = true },
Stats = new ServerStatsData
{
Mem = 134217728,
Cores = 8,
Connections = 10,
Subscriptions = 42,
Sent = new DataStats { Msgs = 1000, Bytes = 65536 },
Received = new DataStats { Msgs = 500, Bytes = 32768 },
},
};
var json = JsonSerializer.Serialize(original, EventJsonContext.Default.ServerStatsMsg);
var deserialized = JsonSerializer.Deserialize(json, EventJsonContext.Default.ServerStatsMsg);
deserialized.ShouldNotBeNull();
deserialized!.Server.JetStream.ShouldBeTrue();
deserialized.Stats.Mem.ShouldBe(134217728);
deserialized.Stats.Connections.ShouldBe(10);
}
[Fact]
public void AccountNumConns_RoundTrip_ViaContext()
{
var original = new AccountNumConns
{
Id = "roundtrip-004",
Time = DateTime.UtcNow,
Server = new EventServerInfo { Name = "s1", Id = "NSVR001" },
AccountName = "$G",
Connections = 5,
TotalConnections = 20,
NumSubscriptions = 15,
};
var json = JsonSerializer.Serialize(original, EventJsonContext.Default.AccountNumConns);
var deserialized = JsonSerializer.Deserialize(json, EventJsonContext.Default.AccountNumConns);
deserialized.ShouldNotBeNull();
deserialized!.AccountName.ShouldBe("$G");
deserialized.Connections.ShouldBe(5);
deserialized.TotalConnections.ShouldBe(20);
}
[Fact]
public void AuthErrorEventMsg_RoundTrip_ViaContext()
{
var original = new AuthErrorEventMsg
{
Id = "roundtrip-005",
Time = DateTime.UtcNow,
Server = new EventServerInfo { Name = "s1", Id = "NSVR001" },
Client = new EventClientInfo { Id = 99, Host = "10.0.0.1" },
Reason = "Authorization Violation",
};
var json = JsonSerializer.Serialize(original, EventJsonContext.Default.AuthErrorEventMsg);
var deserialized = JsonSerializer.Deserialize(json, EventJsonContext.Default.AuthErrorEventMsg);
deserialized.ShouldNotBeNull();
deserialized!.Reason.ShouldBe("Authorization Violation");
deserialized.Type.ShouldBe(AuthErrorEventMsg.EventType);
}
// ========================================================================
// Event subject $SYS prefix validation
// Go reference: events.go — all system subjects start with $SYS
// ========================================================================
[Fact]
public void AllEventSubjects_StartWithSysDollarPrefix()
{
// Go: All system event subjects must start with $SYS.
EventSubjects.ConnectEvent.ShouldStartWith("$SYS.");
EventSubjects.DisconnectEvent.ShouldStartWith("$SYS.");
EventSubjects.AccountConnsNew.ShouldStartWith("$SYS.");
EventSubjects.AccountConnsOld.ShouldStartWith("$SYS.");
EventSubjects.ServerStats.ShouldStartWith("$SYS.");
EventSubjects.ServerShutdown.ShouldStartWith("$SYS.");
EventSubjects.ServerLameDuck.ShouldStartWith("$SYS.");
EventSubjects.AuthError.ShouldStartWith("$SYS.");
EventSubjects.AuthErrorAccount.ShouldStartWith("$SYS.");
EventSubjects.ServerPing.ShouldStartWith("$SYS.");
EventSubjects.ServerReq.ShouldStartWith("$SYS.");
EventSubjects.AccountReq.ShouldStartWith("$SYS.");
EventSubjects.InboxResponse.ShouldStartWith("$SYS.");
}
}