diff --git a/tests/NATS.Server.Tests/Routes/RouteHashStorageTests.cs b/tests/NATS.Server.Tests/Routes/RouteHashStorageTests.cs
new file mode 100644
index 0000000..e61df9f
--- /dev/null
+++ b/tests/NATS.Server.Tests/Routes/RouteHashStorageTests.cs
@@ -0,0 +1,199 @@
+// Reference: golang/nats-server/server/route.go — route hash map for O(1) server-ID lookup.
+// Tests for Gap 13.4: hash-based route storage added to RouteManager.
+
+using System.Net;
+using System.Net.Sockets;
+using Microsoft.Extensions.Logging.Abstractions;
+using NATS.Server;
+using NATS.Server.Configuration;
+using NATS.Server.Routes;
+
+namespace NATS.Server.Tests.Routes;
+
+///
+/// Tests for the FNV-1a hash-based route storage on .
+/// Covers ComputeRouteHash, RegisterRouteByHash,
+/// UnregisterRouteByHash, GetRouteByHash,
+/// GetRouteByServerId, and HashedRouteCount.
+/// Go reference: server/route.go — server-ID-keyed route hash map.
+///
+public class RouteHashStorageTests
+{
+ // Helper: build a RouteManager instance that is NOT started (no listener).
+ // Only hash-map methods are exercised; StartAsync is never called.
+ private static RouteManager CreateManager() =>
+ new(
+ new ClusterOptions { Host = "127.0.0.1", Port = 0 },
+ new ServerStats(),
+ serverId: Guid.NewGuid().ToString("N"),
+ remoteSubSink: static _ => { },
+ routedMessageSink: static _ => { },
+ logger: NullLogger.Instance);
+
+ // Helper: create a RouteConnection backed by a loopback-connected socket.
+ // RouteConnection's constructor wraps the socket in NetworkStream, which
+ // requires a connected socket, so we use a listener/accept pair.
+ private static RouteConnection MakeRouteConnection()
+ {
+ using var listener = new TcpListener(IPAddress.Loopback, 0);
+ listener.Start();
+ var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ client.Connect(IPAddress.Loopback, ((IPEndPoint)listener.LocalEndpoint).Port);
+ // Accept server side so the TCP handshake completes; we hand the client
+ // socket to RouteConnection and let the server-side socket be GC'd.
+ _ = listener.AcceptSocket();
+ return new RouteConnection(client);
+ }
+
+ // 1 -----------------------------------------------------------------------
+ [Fact]
+ public void ComputeRouteHash_Deterministic()
+ {
+ // Go reference: server/route.go — hash derivation must be stable across calls.
+ const string serverId = "server-abc-123";
+
+ var h1 = RouteManager.ComputeRouteHash(serverId);
+ var h2 = RouteManager.ComputeRouteHash(serverId);
+ var h3 = RouteManager.ComputeRouteHash(serverId);
+
+ h1.ShouldBe(h2);
+ h2.ShouldBe(h3);
+ }
+
+ // 2 -----------------------------------------------------------------------
+ [Fact]
+ public void ComputeRouteHash_DifferentInputs_DifferentHashes()
+ {
+ // Go reference: server/route.go — distinct server IDs must not collide.
+ var h1 = RouteManager.ComputeRouteHash("server-1");
+ var h2 = RouteManager.ComputeRouteHash("server-2");
+
+ h1.ShouldNotBe(h2);
+ }
+
+ // 3 -----------------------------------------------------------------------
+ [Fact]
+ public void ComputeRouteHash_EmptyString_DoesNotThrow()
+ {
+ // Go reference: server/route.go — empty server ID is a degenerate but
+ // valid input; the FNV offset basis is returned.
+ var ex = Record.Exception(() => RouteManager.ComputeRouteHash(string.Empty));
+ ex.ShouldBeNull();
+
+ // The hash of an empty string equals the FNV-1a 64-bit offset basis.
+ const ulong fnvOffsetBasis = 14695981039346656037UL;
+ RouteManager.ComputeRouteHash(string.Empty).ShouldBe(fnvOffsetBasis);
+ }
+
+ // 4 -----------------------------------------------------------------------
+ [Fact]
+ public async Task RegisterRouteByHash_CanRetrieve()
+ {
+ // Go reference: server/route.go — after registration the connection must
+ // be retrievable by its hash key.
+ var mgr = CreateManager();
+ await using var conn = MakeRouteConnection();
+
+ mgr.RegisterRouteByHash("srv-A", conn);
+
+ var hash = RouteManager.ComputeRouteHash("srv-A");
+ mgr.GetRouteByHash(hash).ShouldBeSameAs(conn);
+ }
+
+ // 5 -----------------------------------------------------------------------
+ [Fact]
+ public async Task UnregisterRouteByHash_RemovesEntry()
+ {
+ // Go reference: server/route.go — deregistration removes the hash entry.
+ var mgr = CreateManager();
+ await using var conn = MakeRouteConnection();
+
+ mgr.RegisterRouteByHash("srv-B", conn);
+ mgr.UnregisterRouteByHash("srv-B");
+
+ var hash = RouteManager.ComputeRouteHash("srv-B");
+ mgr.GetRouteByHash(hash).ShouldBeNull();
+ }
+
+ // 6 -----------------------------------------------------------------------
+ [Fact]
+ public async Task GetRouteByServerId_FindsRegistered()
+ {
+ // Go reference: server/route.go — string-based lookup computes hash internally.
+ var mgr = CreateManager();
+ await using var conn = MakeRouteConnection();
+
+ mgr.RegisterRouteByHash("srv-C", conn);
+
+ mgr.GetRouteByServerId("srv-C").ShouldBeSameAs(conn);
+ }
+
+ // 7 -----------------------------------------------------------------------
+ [Fact]
+ public void GetRouteByServerId_NotRegistered_ReturnsNull()
+ {
+ // Go reference: server/route.go — unknown server ID yields null.
+ var mgr = CreateManager();
+
+ mgr.GetRouteByServerId("unknown-server").ShouldBeNull();
+ }
+
+ // 8 -----------------------------------------------------------------------
+ [Fact]
+ public async Task HashedRouteCount_MatchesRegistrations()
+ {
+ // Go reference: server/route.go — count reflects registered entries.
+ var mgr = CreateManager();
+ await using var c1 = MakeRouteConnection();
+ await using var c2 = MakeRouteConnection();
+ await using var c3 = MakeRouteConnection();
+
+ mgr.HashedRouteCount.ShouldBe(0);
+
+ mgr.RegisterRouteByHash("srv-1", c1);
+ mgr.HashedRouteCount.ShouldBe(1);
+
+ mgr.RegisterRouteByHash("srv-2", c2);
+ mgr.HashedRouteCount.ShouldBe(2);
+
+ mgr.RegisterRouteByHash("srv-3", c3);
+ mgr.HashedRouteCount.ShouldBe(3);
+
+ mgr.UnregisterRouteByHash("srv-2");
+ mgr.HashedRouteCount.ShouldBe(2);
+ }
+
+ // 9 -----------------------------------------------------------------------
+ [Fact]
+ public async Task RegisterRouteByHash_OverwritesPrevious()
+ {
+ // Go reference: server/route.go — re-registering the same server ID
+ // replaces the stale connection with the new one.
+ var mgr = CreateManager();
+ await using var old = MakeRouteConnection();
+ await using var replacement = MakeRouteConnection();
+
+ mgr.RegisterRouteByHash("srv-D", old);
+ mgr.RegisterRouteByHash("srv-D", replacement);
+
+ mgr.GetRouteByServerId("srv-D").ShouldBeSameAs(replacement);
+ mgr.HashedRouteCount.ShouldBe(1);
+ }
+
+ // 10 ----------------------------------------------------------------------
+ [Fact]
+ public async Task UnregisterRouteByHash_NonExistent_NoOp()
+ {
+ // Go reference: server/route.go — removing a hash key that was never
+ // registered must not throw and must not alter the count.
+ var mgr = CreateManager();
+ await using var conn = MakeRouteConnection();
+
+ mgr.RegisterRouteByHash("srv-E", conn);
+
+ var ex = Record.Exception(() => mgr.UnregisterRouteByHash("does-not-exist"));
+ ex.ShouldBeNull();
+
+ mgr.HashedRouteCount.ShouldBe(1);
+ }
+}