// Copyright 2019-2025 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Mirrors server/leafnode_test.go (first 14 tests) from the NATS server Go source. // All tests require a running NATS server with leaf-node support and are // deferred until the full server runtime is available. using ZB.MOM.NatsNet.Server.IntegrationTests.Helpers; namespace ZB.MOM.NatsNet.Server.IntegrationTests.LeafNode; /// /// Integration tests for leaf-node connectivity, authentication, TLS, and /// loop detection scenarios. /// Mirrors server/leafnode_test.go. /// All tests are deferred pending leaf-node server infrastructure. /// [Collection("LeafNodeIntegration")] [Trait("Category", "Integration")] public sealed class LeafNodeTests : IntegrationTestBase { /// /// Verifies that when a leaf-node remote URL resolves to multiple IP addresses /// the server randomly cycles through them, ensuring all IPs are eventually used. /// Mirrors TestLeafNodeRandomIP. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node support")] public void RandomIP_ShouldSucceed() { // Go source: DefaultOptions with LeafNode.Remotes pointing to a hostname that // resolves to 3 IPs (127.0.0.1/2/3) via a custom DNS resolver. // ReconnectInterval = 50ms, dialTimeout = 15ms. // Verifies all three IPs appear in debug logs within 3 seconds. } /// /// Verifies that leaf-node remote URL lists are randomised on startup when /// NoRandomize is false, and preserved in order when NoRandomize is true. /// Mirrors TestLeafNodeRandomRemotes. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node support")] public void RandomRemotes_ShouldSucceed() { // Go source: DefaultOptions with 2 RemoteLeafOpts — rem0 (NoRandomize=true) // and rem1 (NoRandomize=false), each with 16 URLs. // Asserts rem0 URLs are in original order; rem1 URLs are shuffled. } /// /// Verifies that a leaf node can connect to a server that requires mutual TLS /// (client and server certificates). /// Mirrors TestLeafNodeTLSWithCerts. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node TLS support")] public void TlsWithCerts_ShouldSucceed() { // Go source: s1 configured with leaf TLS (ca, cert, key) listening. // s2 leaf remote specifies client cert and key (tlsauth/* certs). // Verifies leaf node establishes TLS-authenticated connection. } /// /// Verifies that a leaf-node remote that does not provide a certificate to a /// server requiring mutual TLS fails to connect. /// Mirrors TestLeafNodeTLSRemoteWithNoCerts. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node TLS support")] public void TlsRemoteWithNoCerts_ShouldSucceed() { // Go source: s1 requires client cert (verify=true). s2 provides no cert. // Verifies that s2 fails to connect and reports TLS error in logs. } /// /// Verifies that attempting to connect a leaf node with a local account that /// does not exist on the server produces a clear error, and that retries are /// observed after the account is removed mid-operation. /// Mirrors TestLeafNodeAccountNotFound. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node support")] public void AccountNotFound_ShouldSucceed() { // Go source: // 1. NewServer with LeafNode.Remotes specifying LocalAccount="foo" (missing) → error. // 2. Add account "foo", RunServer → leaf connects to sb. // 3. Delete account "foo" from sa; restart sb → expect "Unable to lookup account" error log. // 4. Verify gcid keeps incrementing (retries happen). } /// /// Verifies that a leaf node reconnects to an alternate server in the cluster /// after the primary server it was connected to shuts down, and that the leaf /// node password never appears in debug or trace logs. /// Mirrors TestLeafNodeBasicAuthFailover. /// [Fact(Skip = "deferred: requires running NATS server cluster with leaf-node support")] public void BasicAuthFailover_ShouldSucceed() { // Go source: 2-server cluster (sb1+sb2) with leafnode auth user=foo/password=pwdfatal. // sa configured as leaf remote pointing to sb1 only. // sb1 shuts down; sa must reconnect to sb2. // All log messages checked to ensure "pwdfatal" never appears. } /// /// Verifies that the RTT (round-trip time) reported by a leaf-node connection /// is non-zero and updated after PING/PONG exchanges. /// Mirrors TestLeafNodeRTT. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node support")] public void Rtt_ShouldSucceed() { // Go source: PingInterval=15ms on both servers. Leaf connects to sb. // After a short wait, checks that sa leaf RTT > 0. // Also verifies RTT is reported in CONNZ output. } /// /// Verifies that configuring both a single-user credential and a Users array /// on the leaf-node listener returns a clear error, and that duplicate user /// names in the array also produce an error. /// Mirrors TestLeafNodeValidateAuthOptions. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node support")] public void ValidateAuthOptions_ShouldSucceed() { // Go source: DefaultOptions with both LeafNode.Username and LeafNode.Users set → // "can not have a single user/pass and a users array". // Then clears Username, adds duplicate "user" → "duplicate user". // These are options-validation errors; NewServer must return error before starting. } /// /// Verifies that single-user leaf-node authorization correctly routes connections /// to the configured account, and that connections with the wrong credentials /// are rejected. /// Mirrors TestLeafNodeBasicAuthSingleton. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node support")] public void BasicAuthSingleton_ShouldSucceed() { // Go source: 4 sub-test combinations: // 1. No user spec, no creds → fail (no LN connection established). // 2. No user spec, creds=user2:user2 → succeeds, bound to ACC2. // 3. No user spec, unknown user → fail. // 4. user=ln/pass=pwd, creds=ln:pwd → succeeds, bound to ACC1. // Verifies pub/sub message routing to correct account. } /// /// Verifies that a leaf-node server with multiple authorised users can /// map different leaf connections to different accounts, and that each /// account's messages are isolated from the others. /// Mirrors TestLeafNodeBasicAuthMultiple. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node support")] public void BasicAuthMultiple_ShouldSucceed() { // Go source: s1 with users ln1→S1ACC1, ln2→S1ACC2, ln3 (no account). // s2 with 2 leaf remotes: ln1 bound to S2ACC1, ln2 to S2ACC2. // Verifies publish from S2ACC1 is received by S1ACC1 subscribers only, // and publish from S2ACC2 reaches only S1ACC2 subscribers. } /// /// Verifies that a self-loop leaf-node configuration (A→B and B→A) is detected /// and reported as an error by both standalone and clustered server combinations. /// Mirrors TestLeafNodeLoop. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node support")] public void Loop_ShouldSucceed() { // Go source: t.Run("standalone", ...) and t.Run("cluster", ...). // Server A on port 1234 pointing to B on 5678; B pointing back to A. // Within 5s, one of the loop-detected loggers must fire. // After B restarts without the return remote, A must connect successfully. } /// /// Verifies that a loop formed through a directed acyclic graph (C→A and C→B, /// where B→A) is detected: C receives the loop error and establishes zero connections. /// Mirrors TestLeafNodeLoopFromDAG. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node support")] public void LoopFromDAG_ShouldSucceed() { // Go source: A standalone, B→A, C→A and C→B. // Loop detected on C; C has 0 leaf connections. // After restarting C with only C→B, A has 1, B has 2, C has 1. // Uses CheckHelper.CheckLeafNodeConnectedCount. } /// /// Verifies that a pending write that is blocking does not prevent the server /// from closing a TLS leaf-node connection within a reasonable timeout. /// Mirrors TestLeafNodeCloseTLSConnection. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node TLS support")] public void CloseTlsConnection_ShouldSucceed() { // Go source: Server with TLSTimeout=100ms. TLS client performs raw TCP // dial, TLS handshake, sends CONNECT+PING, verifies leaf is established. // Fills the kernel write buffer to create a blocked write, then closes // the connection — must complete within 3 seconds without hanging. } /// /// Verifies that the server name used in TLS SNI is saved and returned /// in varz/connz output for a leaf-node connection. /// Mirrors TestLeafNodeTLSSaveName. /// [Fact(Skip = "deferred: requires running NATS server with leaf-node TLS support")] public void TlsSaveName_ShouldSucceed() { // Go source: Leaf remote with TLSConfig containing ServerName. // After connection, checks that the leaf connection's TLS server name // is saved and visible in connz (RemoteAddr or TLS info). } }