Fix E2E test gaps and add comprehensive E2E + parity test suites

- Fix pull consumer fetch: send original stream subject in HMSG (not inbox)
  so NATS client distinguishes data messages from control messages
- Fix MaxAge expiry: add background timer in StreamManager for periodic pruning
- Fix JetStream wire format: Go-compatible anonymous objects with string enums,
  proper offset-based pagination for stream/consumer list APIs
- Add 42 E2E black-box tests (core messaging, auth, TLS, accounts, JetStream)
- Add ~1000 parity tests across all subsystems (gaps closure)
- Update gap inventory docs to reflect implementation status
This commit is contained in:
Joseph Doherty
2026-03-12 14:09:23 -04:00
parent 79c1ee8776
commit c30e67a69d
226 changed files with 17801 additions and 709 deletions

View File

@@ -92,12 +92,12 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
| `RouteType` (type alias + consts `Implicit`/`Explicit`) | route.go:3644 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs` — implicit/explicit distinction tracked in `RouteConnection` handshake and `RouteManager.ConnectToRouteWithRetryAsync` | No explicit enum; Implicit/Explicit distinction is encoded in how routes are established (solicited vs inbound) |
| `route` struct (unexported) | route.go:5694 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:8` | Fields `remoteID`, `poolIdx`, `accName`, `noPool`, `compression`, `gossipMode` are present. Fields for `lnoc`, `lnocu`, `jetstream`, `connectURLs`, `wsConnURLs`, `gatewayURL`, `leafnodeURL`, `hash`, `idHash`, `startNewRoute`, `retry` are MISSING — not modelled in .NET |
| `routeInfo` struct (unexported) | route.go:97101 | MISSING | — | Used internally for deferred pool-connection creation after first PONG; no .NET equivalent |
| `gossipDefault`/`gossipDisabled`/`gossipOverride` consts | route.go:104108 | MISSING | — | Gossip mode bytes used in INFO propagation; not implemented in .NET |
| `connectInfo` struct | route.go:110124 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:328` (`BuildConnectInfoJson`) | .NET builds a simplified JSON payload; `connectInfo` fields Echo, Verbose, Pedantic, TLS, Headers, Cluster, Dynamic, LNOC, LNOCU are all MISSING from the .NET payload |
| `ConProto`/`InfoProto` protocol format strings | route.go:127130 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:73,83` | Handshake uses a simplified `ROUTE <serverId>` format rather than `CONNECT <json>` / `INFO <json>` |
| `gossipDefault`/`gossipDisabled`/`gossipOverride` consts | route.go:104108 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:1719` | Gossip mode constants are defined as byte constants (`GossipDefault`, `GossipDisabled`, `GossipOverride`) |
| `connectInfo` struct | route.go:110124 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:378` (`BuildConnectInfoJson`) | Connect payload now includes parity fields: `echo`, `verbose`, `pedantic`, `tls_required`, `headers`, `cluster`, `dynamic`, `lnoc`, `lnocu` |
| `ConProto`/`InfoProto` protocol format strings | route.go:127130 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:1112,8395` | CONNECT/INFO format constants added, but active wire handshake remains simplified `ROUTE <serverId>` rather than full CONNECT/INFO exchange |
| `clusterTLSInsecureWarning` const | route.go:134 | NOT_APPLICABLE | — | TLS not yet implemented in .NET port; warning string has no counterpart |
| `defaultRouteMaxPingInterval` const | route.go:140 | MISSING | — | Ping interval management for compression RTT auto-mode not implemented |
| `routeConnectDelay`/`routeConnectMaxDelay`/`routeMaxPingInterval` vars | route.go:145148 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:486` (250ms hardcoded delay) | .NET hardcodes 250ms retry delay; Go uses configurable `DEFAULT_ROUTE_CONNECT` with exponential backoff |
| `defaultRouteMaxPingInterval` const | route.go:140 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:20` | `DefaultRouteMaxPingInterval` constant added |
| `routeConnectDelay`/`routeConnectMaxDelay`/`routeMaxPingInterval` vars | route.go:145148 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:1415,20,751757` | Route reconnect delay now uses bounded exponential backoff (`ComputeRetryDelay`) with dedicated delay/max constants; still not runtime-configurable from route config |
| `(c *client) removeReplySub` | route.go:151 | MISSING | — | Reply-sub cleanup for remote reply subs not implemented |
| `(c *client) processAccountSub` | route.go:167 | NOT_APPLICABLE | — | Gateway-only path; gateway sub interest not in routes module |
| `(c *client) processAccountUnsub` | route.go:174 | NOT_APPLICABLE | — | Gateway-only path |
@@ -110,48 +110,48 @@ Add rows to the Gap Inventory table below. Group by Go source file. Include the
| `(c *client) processRouteInfo` | route.go:549 | MISSING | — | Full INFO processing (cluster name negotiation, compression negotiation, duplicate detection, account route setup) not implemented; .NET handshake is a simple ID exchange |
| `(s *Server) negotiateRouteCompression` | route.go:897 | PARTIAL | `src/NATS.Server/Routes/RouteCompressionCodec.cs:82` (`NegotiateCompression`) | .NET has the negotiation logic; but integration into handshake (INFO exchange, switching compression writer/reader mid-stream) is MISSING |
| `(s *Server) updateRemoteRoutePerms` | route.go:953 | MISSING | — | Route permission update on INFO reload not implemented |
| `(s *Server) sendAsyncInfoToClients` | route.go:1015 | MISSING | — | Async INFO broadcast to connected clients not implemented |
| `(s *Server) processImplicitRoute` | route.go:1043 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:107` (`ProcessImplicitRoute`) | .NET collects discovered URLs; missing: duplicate-ID check, pinned-account re-solicitation, `hasThisRouteConfigured` guard |
| `(s *Server) hasThisRouteConfigured` | route.go:1104 | MISSING | — | Check whether incoming gossip URL is already a configured explicit route; not implemented |
| `(s *Server) sendAsyncInfoToClients` | route.go:1015 | PORTED | `src/NATS.Server/NatsServer.cs:181` | Added `UpdateServerINFOAndSendINFOToClients()` INFO refresh/broadcast path to connected clients; validated with targeted socket-level parity test. |
| `(s *Server) processImplicitRoute` | route.go:1043 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:115` (`ProcessImplicitRoute`) | Now guards discovered URLs with `HasThisRouteConfigured` and normalized duplicate checks; pinned-account re-solicitation and duplicate-ID handling remain missing |
| `(s *Server) hasThisRouteConfigured` | route.go:1104 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:164` | Implemented with normalized URL matching against explicit configured routes and known route URLs |
| `(s *Server) forwardNewRouteInfoToKnownServers` | route.go:1139 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:127` (`ForwardNewRouteInfoToKnownServers`) | .NET raises an event with the new peer URL; missing: gossip mode logic (`gossipDefault`/`gossipDisabled`/`gossipOverride`), pinned-account route filtering, serialized INFO JSON sending |
| `(c *client) canImport` | route.go:1226 | MISSING | — | Route import permission check not implemented |
| `(c *client) canExport` | route.go:1235 | MISSING | — | Route export permission check not implemented |
| `(c *client) setRoutePermissions` | route.go:1244 | MISSING | — | Route permission mapping (Import→Publish, Export→Subscribe) not implemented |
| `asubs` struct | route.go:1263 | NOT_APPLICABLE | — | Internal Go helper to group subscriptions by account during cleanup; .NET uses LINQ equivalents |
| `getAccNameFromRoutedSubKey` | route.go:1273 | MISSING | — | Sub key parsing for account name extraction not implemented |
| `(c *client) getRoutedSubKeyInfo` | route.go:1290 | MISSING | — | Helper to determine account/key info for a route's subscriptions; not implemented |
| `(c *client) removeRemoteSubs` | route.go:1299 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:577` (`RemoveRoute`) | .NET removes the route connection but does NOT remove individual remote subscriptions from the SubList on close |
| `(c *client) removeRemoteSubsForAcc` | route.go:1352 | MISSING | — | Per-account remote sub removal for dedicated route transition not implemented |
| `(c *client) parseUnsubProto` | route.go:1366 | MISSING | — | RS-/LS- protocol arg parser not implemented; .NET `ReadFramesAsync` only extracts account/subject/queue loosely |
| `getAccNameFromRoutedSubKey` | route.go:1273 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:176` (`GetAccNameFromRoutedSubKey`) | Routed-sub key parsing helper added and validated with parity tests |
| `(c *client) getRoutedSubKeyInfo` | route.go:1290 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:179` (`GetRoutedSubKeyInfo`) | Routed-sub key decomposition helper added (route/account/subject/queue) and covered by tests |
| `(c *client) removeRemoteSubs` | route.go:1299 | PORTED | `src/NATS.Server/Subscriptions/SubList.cs:195` (`RemoveRemoteSubs`), `src/NATS.Server/Routes/RouteManager.cs:593` (`WatchRouteAsync`), `src/NATS.Server/NatsServer.cs:960` (`RemoveRemoteSubscriptionsForRoute`) | Route close/removal now triggers remote-sub cleanup from account SubLists when the last connection for a remote server is gone |
| `(c *client) removeRemoteSubsForAcc` | route.go:1352 | PARTIAL | `src/NATS.Server/Subscriptions/SubList.cs:229` (`RemoveRemoteSubsForAccount`), `src/NATS.Server/Routes/RouteManager.cs:286` (`UnregisterAccountRoute`), `src/NATS.Server/NatsServer.cs:966` (`RemoveRemoteSubscriptionsForRouteAccount`) | Per-account cleanup path is now wired on dedicated-route unregistration; full dedicated-route transition parity remains incomplete |
| `(c *client) parseUnsubProto` | route.go:1366 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:357` (`TryParseRemoteUnsub`) | Dedicated RS-/LS- parser now handles account/subject/optional-queue extraction and is used by frame processing |
| `(c *client) processRemoteUnsub` | route.go:1404 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:177185` | .NET fires `RemoteSubscriptionReceived` with `IsRemoval=true`; missing: sub key lookup and removal from SubList, gateway/leafnode interest updates |
| `(c *client) processRemoteSub` | route.go:1489 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:167175` | .NET fires `RemoteSubscriptionReceived`; missing: key construction with type byte prefix, account lookup/creation, permission check (`canExport`), SubList insertion, gateway/leafnode updates, queue-weight delta tracking |
| `(c *client) addRouteSubOrUnsubProtoToBuf` | route.go:1729 | PARTIAL | `src/NATS.Server/Routes/RouteConnection.cs:95109` (`SendRsPlusAsync`/`SendRsMinusAsync`) | .NET sends RS+/RS- with account and optional queue; missing: LS+/LS- variant for leaf origin clusters, queue weight field in RS+ |
| `(c *client) addRouteSubOrUnsubProtoToBuf` | route.go:1729 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:179` (`SendRouteSubOrUnSubProtosAsync`) | Added low-level buffered route-proto sender that batches RS+/RS-/LS+/LS- control lines into a single write/flush. |
| `(s *Server) sendSubsToRoute` | route.go:1781 | MISSING | — | Bulk send of local subscription interest to newly connected route not implemented; .NET only propagates incremental sub/unsub |
| `(c *client) sendRouteSubProtos` | route.go:1881 | MISSING | — | Batch RS+ send not implemented |
| `(c *client) sendRouteUnSubProtos` | route.go:1890 | MISSING | — | Batch RS- send not implemented |
| `(c *client) sendRouteSubOrUnSubProtos` | route.go:1898 | MISSING | — | Low-level batch RS+/RS-/LS+/LS- sender not implemented |
| `(c *client) sendRouteSubProtos` | route.go:1881 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:146` (`SendRouteSubProtosAsync`) | Added batched RS+ emission from remote-subscription models with queue/weight support. |
| `(c *client) sendRouteUnSubProtos` | route.go:1890 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:165` (`SendRouteUnSubProtosAsync`) | Added batched RS- emission from remote-subscription models. |
| `(c *client) sendRouteSubOrUnSubProtos` | route.go:1898 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:179` (`SendRouteSubOrUnSubProtosAsync`) | Added low-level batch sender for arbitrary route sub/unsub protocol lines. |
| `(s *Server) createRoute` | route.go:1935 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:447,462` (`HandleInboundRouteAsync`/`ConnectToRouteWithRetryAsync`) | .NET creates a RouteConnection and performs handshake; missing: TLS setup, auth timeout timer, CONNECT protocol sending, INFO JSON sending, compression negotiation, ping timer |
| `routeShouldDelayInfo` | route.go:2082 | MISSING | — | Logic to delay initial INFO until pool connection auth is confirmed not implemented |
| `(s *Server) generateRouteInitialInfoJSON` | route.go:2090 | MISSING | — | Route INFO JSON generation (with nonce, pool index, gossip mode, compression) not implemented |
| `(s *Server) addRoute` | route.go:2113 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:496` (`Register`) | .NET registers route in dictionary; missing: pool index management, duplicate detection with `handleDuplicateRoute`, per-account route registration in `accRoutes`, `sendSubsToRoute` call, gateway/leafnode URL propagation, `forwardNewRouteInfoToKnownServers` |
| `hasSolicitedRoute` | route.go:2438 | MISSING | — | Helper to find a solicited route in a pool slice; not implemented |
| `upgradeRouteToSolicited` | route.go:2458 | MISSING | — | Upgrade an inbound route to solicited status; not implemented |
| `hasSolicitedRoute` | route.go:2438 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:721` | Implemented helper to query whether a given remote server currently has a solicited route |
| `upgradeRouteToSolicited` | route.go:2458 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:730` | Implemented route upgrade helper to flip an existing route into solicited mode |
| `handleDuplicateRoute` | route.go:2473 | MISSING | — | Duplicate route resolution (close extra connection, preserve retry flag) not implemented |
| `(c *client) importFilter` | route.go:2510 | MISSING | — | Permission-based subscription filter for sending to routes not implemented |
| `(s *Server) updateRouteSubscriptionMap` | route.go:2519 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:381,392` (`PropagateLocalSubscription`/`PropagateLocalUnsubscription`) | .NET broadcasts RS+/RS- to all routes; missing: account routePoolIdx-based routing, queue-weight dedup (`sqmu`/`lqws`), no-pool route handling, gateway/leafnode interest updates |
| `(s *Server) startRouteAcceptLoop` | route.go:2696 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:333` (`StartAsync`) | .NET binds and starts accept loop, solicits configured routes; missing: cluster name logging, TLS config on accept, routeInfo construction, advertise/NoAdvertise, LeafNode/Gateway URL propagation |
| `(s *Server) setRouteInfoHostPortAndIP` | route.go:2829 | MISSING | — | Route INFO host/port/IP with Cluster.Advertise support not implemented |
| `(s *Server) StartRouting` | route.go:2849 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:333` (`StartAsync`) | Functionally equivalent: starts accept loop and solicits routes |
| `(s *Server) reConnectToRoute` | route.go:2861 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:462` (`ConnectToRouteWithRetryAsync`) | .NET retries indefinitely with 250ms delay; missing: random jitter delay, explicit vs implicit distinction affecting delay, quit-channel integration |
| `(s *Server) routeStillValid` | route.go:2881 | MISSING | — | Check that a route URL is still in configured routes list (for reconnect guard) not implemented |
| `(s *Server) connectToRoute` | route.go:2890 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:462` (`ConnectToRouteWithRetryAsync`) | .NET connects and retries; missing: explicit/implicit distinction, ConnectRetries limit, exponential backoff (`ConnectBackoff`), `routesToSelf` exclusion, address randomization from DNS |
| `(c *client) isSolicitedRoute` | route.go:2976 | MISSING | — | Helper predicate; not implemented |
| `(s *Server) reConnectToRoute` | route.go:2861 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:512` (`ConnectToRouteWithRetryAsync`) | Retry loop now uses bounded exponential backoff and route validity guard; jitter, explicit/implicit-specific delay behavior, and quit-channel parity remain missing |
| `(s *Server) routeStillValid` | route.go:2881 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:180` | Implemented reconnect guard that validates configured and discovered route URLs using normalized comparisons |
| `(s *Server) connectToRoute` | route.go:2890 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:512` (`ConnectToRouteWithRetryAsync`) | Exponential backoff and route-validity checks are implemented; ConnectRetries/ConnectBackoff config parity, routes-to-self exclusion, and DNS randomization are still missing |
| `(c *client) isSolicitedRoute` | route.go:2976 | PORTED | `src/NATS.Server/Routes/RouteConnection.cs:35,370` | `IsSolicited` state is tracked on route connections and exposed via `IsSolicitedRoute()` helper |
| `(s *Server) saveRouteTLSName` | route.go:2985 | NOT_APPLICABLE | — | TLS not yet implemented in .NET port |
| `(s *Server) solicitRoutes` | route.go:2996 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:347354` | .NET solicits configured routes with pool connections; missing: per-account (pinned) route solicitation, `saveRouteTLSName` |
| `(c *client) processRouteConnect` | route.go:3011 | MISSING | — | Parsing and validation of inbound CONNECT from route (cluster name check, wrong-port detection, LNOC/LNOCU flags) not implemented; .NET uses a simpler handshake |
| `(s *Server) removeAllRoutesExcept` | route.go:3085 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:602` (`RemoveAllRoutesExcept`) | Equivalent behavior: remove all routes not in the keep-set |
| `(s *Server) removeRoute` | route.go:3113 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:577` (`RemoveRoute`) | .NET removes from `_routes` dict; missing: per-account route cleanup (`accRoutes`), hash removal, gateway/leafnode URL withdrawal, noPool counter, reconnect-after-noPool logic |
| `(s *Server) isDuplicateServerName` | route.go:3233 | MISSING | — | Duplicate server name detection across routes not implemented |
| `(s *Server) removeRoute` | route.go:3113 | PARTIAL | `src/NATS.Server/Routes/RouteManager.cs:632` (`RemoveRoute`) | Remove path now also cleans hash index and per-account route mappings tied to removed connections; gateway/leafnode URL withdrawal, noPool counters, and reconnect-after-noPool logic remain missing |
| `(s *Server) isDuplicateServerName` | route.go:3233 | PORTED | `src/NATS.Server/Routes/RouteManager.cs:748` | Duplicate server-name detection helper implemented against current connected-server ID set |
| `(s *Server) forEachNonPerAccountRoute` | route.go:3263 | NOT_APPLICABLE | — | Internal Go iterator over route slice; .NET uses `_routes.Values` LINQ directly |
| `(s *Server) forEachRoute` | route.go:3277 | NOT_APPLICABLE | — | Internal Go iterator; .NET enumerates `_routes` and `_accountRoutes` directly |
| `(s *Server) forEachRouteIdx` | route.go:3292 | NOT_APPLICABLE | — | Internal Go pool-index iterator; .NET `ComputeRoutePoolIdx` achieves equivalent selection |
@@ -182,5 +182,9 @@ After porting work is completed:
| Date | Change | By |
|------|--------|----|
| 2026-02-26 | Ported async INFO broadcast parity slice: wired/validated `UpdateServerINFOAndSendINFOToClients()` as the `sendAsyncInfoToClients` equivalent and added targeted socket broadcast test (`RouteInfoBroadcastParityBatch4Tests`). | codex |
| 2026-02-25 | File created with LLM analysis instructions | auto |
| 2026-02-25 | Full gap inventory populated: 57 Go symbols classified across route.go (3,314 lines). Counts: PORTED 4, PARTIAL 21, MISSING 23, NOT_APPLICABLE 9, DEFERRED 0 | auto |
| 2026-02-25 | Ported route parity helper batch: gossip/default constants, connect-info parity fields, configured-route/reconnect guard helpers, solicited-route helpers, duplicate-server-name detection, RS-/LS- parser, and LS+/LS- + queue-weight wire helpers; updated row statuses and notes | codex |
| 2026-02-25 | Ported routed-sub key helpers and remote-sub cleanup parity batch: added `getAccNameFromRoutedSubKey`/`getRoutedSubKeyInfo` equivalents plus route-close and per-account cleanup plumbing with targeted tests | codex |
| 2026-02-25 | Ported route batch-proto parity batch: added buffered batch sender APIs (`SendRouteSubProtosAsync`, `SendRouteUnSubProtosAsync`, `SendRouteSubOrUnSubProtosAsync`) for RS+/RS-/LS+/LS- protocol frames with targeted tests (`RouteBatchProtoParityBatch3Tests`) | codex |