diff --git a/src/NATS.Server/Events/EventSubjects.cs b/src/NATS.Server/Events/EventSubjects.cs
index 4eed2bd..81401c6 100644
--- a/src/NATS.Server/Events/EventSubjects.cs
+++ b/src/NATS.Server/Events/EventSubjects.cs
@@ -22,6 +22,12 @@ public static class EventSubjects
public const string AuthError = "$SYS.SERVER.{0}.CLIENT.AUTH.ERR";
public const string AuthErrorAccount = "$SYS.ACCOUNT.CLIENT.AUTH.ERR";
+ // Remote server and leaf node events
+ public const string RemoteServerShutdown = "$SYS.SERVER.{0}.REMOTE.SHUTDOWN";
+ public const string RemoteServerUpdate = "$SYS.SERVER.{0}.REMOTE.UPDATE";
+ public const string LeafNodeConnected = "$SYS.SERVER.{0}.LEAFNODE.CONNECT";
+ public const string LeafNodeDisconnected = "$SYS.SERVER.{0}.LEAFNODE.DISCONNECT";
+
// Request-reply subjects (server-specific)
public const string ServerReq = "$SYS.REQ.SERVER.{0}.{1}";
diff --git a/tests/NATS.Server.Tests/Events/RemoteServerEventTests.cs b/tests/NATS.Server.Tests/Events/RemoteServerEventTests.cs
new file mode 100644
index 0000000..661b355
--- /dev/null
+++ b/tests/NATS.Server.Tests/Events/RemoteServerEventTests.cs
@@ -0,0 +1,200 @@
+using System.Text.Json;
+using NATS.Server.Events;
+
+namespace NATS.Server.Tests.Events;
+
+///
+/// Tests for remote server and leaf node event DTOs and subject constants.
+/// Go reference: events.go — remote server lifecycle, leaf node advisory subjects.
+/// Gap 10.8: RemoteServerShutdown, RemoteServerUpdate, LeafNodeConnected events.
+///
+public class RemoteServerEventTests
+{
+ // --- RemoteServerShutdownEvent ---
+
+ [Fact]
+ public void RemoteServerShutdownEvent_HasCorrectEventType()
+ {
+ RemoteServerShutdownEvent.EventType.ShouldBe("io.nats.server.advisory.v1.remote_shutdown");
+ var ev = new RemoteServerShutdownEvent();
+ ev.Type.ShouldBe(RemoteServerShutdownEvent.EventType);
+ }
+
+ [Fact]
+ public void RemoteServerShutdownEvent_GeneratesUniqueId()
+ {
+ var ev1 = new RemoteServerShutdownEvent();
+ var ev2 = new RemoteServerShutdownEvent();
+ ev1.Id.ShouldNotBeNullOrEmpty();
+ ev2.Id.ShouldNotBeNullOrEmpty();
+ ev1.Id.ShouldNotBe(ev2.Id);
+ }
+
+ // --- RemoteServerUpdateEvent ---
+
+ [Fact]
+ public void RemoteServerUpdateEvent_HasCorrectEventType()
+ {
+ RemoteServerUpdateEvent.EventType.ShouldBe("io.nats.server.advisory.v1.remote_update");
+ var ev = new RemoteServerUpdateEvent();
+ ev.Type.ShouldBe(RemoteServerUpdateEvent.EventType);
+ }
+
+ [Fact]
+ public void RemoteServerUpdateEvent_AllFieldsSettable()
+ {
+ var server = new EventServerInfo { Id = "srv1", Name = "my-server" };
+ var ev = new RemoteServerUpdateEvent
+ {
+ Server = server,
+ RemoteServerId = "remote-id-123",
+ RemoteServerName = "remote-server",
+ UpdateType = "routes_changed",
+ };
+
+ ev.Server.ShouldBeSameAs(server);
+ ev.RemoteServerId.ShouldBe("remote-id-123");
+ ev.RemoteServerName.ShouldBe("remote-server");
+ ev.UpdateType.ShouldBe("routes_changed");
+ }
+
+ // --- LeafNodeConnectEvent ---
+
+ [Fact]
+ public void LeafNodeConnectEvent_HasCorrectEventType()
+ {
+ LeafNodeConnectEvent.EventType.ShouldBe("io.nats.server.advisory.v1.leafnode_connect");
+ var ev = new LeafNodeConnectEvent();
+ ev.Type.ShouldBe(LeafNodeConnectEvent.EventType);
+ }
+
+ [Fact]
+ public void LeafNodeConnectEvent_AllFieldsSettable()
+ {
+ var server = new EventServerInfo { Id = "srv1", Name = "hub" };
+ var ev = new LeafNodeConnectEvent
+ {
+ Server = server,
+ LeafNodeId = "leaf-id-abc",
+ LeafNodeName = "leaf-node-1",
+ RemoteUrl = "nats://10.0.0.1:7422",
+ Account = "ACC",
+ };
+
+ ev.Server.ShouldBeSameAs(server);
+ ev.LeafNodeId.ShouldBe("leaf-id-abc");
+ ev.LeafNodeName.ShouldBe("leaf-node-1");
+ ev.RemoteUrl.ShouldBe("nats://10.0.0.1:7422");
+ ev.Account.ShouldBe("ACC");
+ }
+
+ // --- LeafNodeDisconnectEvent ---
+
+ [Fact]
+ public void LeafNodeDisconnectEvent_HasCorrectEventType()
+ {
+ LeafNodeDisconnectEvent.EventType.ShouldBe("io.nats.server.advisory.v1.leafnode_disconnect");
+ var ev = new LeafNodeDisconnectEvent();
+ ev.Type.ShouldBe(LeafNodeDisconnectEvent.EventType);
+ }
+
+ [Fact]
+ public void LeafNodeDisconnectEvent_AllFieldsSettable()
+ {
+ var server = new EventServerInfo { Id = "srv1", Name = "hub" };
+ var ev = new LeafNodeDisconnectEvent
+ {
+ Server = server,
+ LeafNodeId = "leaf-id-xyz",
+ Reason = "connection closed",
+ };
+
+ ev.Server.ShouldBeSameAs(server);
+ ev.LeafNodeId.ShouldBe("leaf-id-xyz");
+ ev.Reason.ShouldBe("connection closed");
+ }
+
+ // --- EventSubjects ---
+
+ [Fact]
+ public void EventSubjects_RemoteShutdown_HasPlaceholder()
+ {
+ EventSubjects.RemoteServerShutdown.ShouldContain("{0}");
+ EventSubjects.RemoteServerUpdate.ShouldContain("{0}");
+ EventSubjects.LeafNodeConnected.ShouldContain("{0}");
+ EventSubjects.LeafNodeDisconnected.ShouldContain("{0}");
+
+ // Verify format strings produce expected subjects when formatted
+ var serverId = "ABCDEF123456";
+ string.Format(EventSubjects.RemoteServerShutdown, serverId)
+ .ShouldBe($"$SYS.SERVER.{serverId}.REMOTE.SHUTDOWN");
+ string.Format(EventSubjects.LeafNodeConnected, serverId)
+ .ShouldBe($"$SYS.SERVER.{serverId}.LEAFNODE.CONNECT");
+ }
+
+ // --- JSON serialization ---
+
+ [Fact]
+ public void AllRemoteEvents_SerializeToJson()
+ {
+ var server = new EventServerInfo { Id = "srv-id", Name = "test-server" };
+
+ var shutdown = new RemoteServerShutdownEvent
+ {
+ Server = server,
+ RemoteServerId = "r1",
+ RemoteServerName = "remote",
+ Reason = "graceful",
+ };
+ var shutdownJson = JsonSerializer.Serialize(shutdown);
+ var shutdownDoc = JsonDocument.Parse(shutdownJson).RootElement;
+ shutdownDoc.GetProperty("type").GetString().ShouldBe(RemoteServerShutdownEvent.EventType);
+ shutdownDoc.GetProperty("id").GetString().ShouldNotBeNullOrEmpty();
+ shutdownDoc.GetProperty("remote_server_id").GetString().ShouldBe("r1");
+ shutdownDoc.GetProperty("reason").GetString().ShouldBe("graceful");
+
+ var update = new RemoteServerUpdateEvent
+ {
+ Server = server,
+ RemoteServerId = "r2",
+ RemoteServerName = "remote2",
+ UpdateType = "config_updated",
+ };
+ var updateJson = JsonSerializer.Serialize(update);
+ var updateDoc = JsonDocument.Parse(updateJson).RootElement;
+ updateDoc.GetProperty("type").GetString().ShouldBe(RemoteServerUpdateEvent.EventType);
+ updateDoc.GetProperty("update_type").GetString().ShouldBe("config_updated");
+
+ var connect = new LeafNodeConnectEvent
+ {
+ Server = server,
+ LeafNodeId = "leaf1",
+ LeafNodeName = "leaf-node",
+ RemoteUrl = "nats://10.0.0.1:7422",
+ Account = "ACC",
+ };
+ var connectJson = JsonSerializer.Serialize(connect);
+ var connectDoc = JsonDocument.Parse(connectJson).RootElement;
+ connectDoc.GetProperty("type").GetString().ShouldBe(LeafNodeConnectEvent.EventType);
+ connectDoc.GetProperty("leaf_node_id").GetString().ShouldBe("leaf1");
+ connectDoc.GetProperty("account").GetString().ShouldBe("ACC");
+
+ var disconnect = new LeafNodeDisconnectEvent
+ {
+ Server = server,
+ LeafNodeId = "leaf1",
+ Reason = "timeout",
+ };
+ var disconnectJson = JsonSerializer.Serialize(disconnect);
+ var disconnectDoc = JsonDocument.Parse(disconnectJson).RootElement;
+ disconnectDoc.GetProperty("type").GetString().ShouldBe(LeafNodeDisconnectEvent.EventType);
+ disconnectDoc.GetProperty("leaf_node_id").GetString().ShouldBe("leaf1");
+ disconnectDoc.GetProperty("reason").GetString().ShouldBe("timeout");
+
+ // Roundtrip: deserialize back and verify type field survives
+ var shutdownRt = JsonSerializer.Deserialize(shutdownJson);
+ shutdownRt.ShouldNotBeNull();
+ shutdownRt!.Type.ShouldBe(RemoteServerShutdownEvent.EventType);
+ shutdownRt.RemoteServerId.ShouldBe("r1");
+ }
+}