test(batch57): port 53 SuperCluster and LeafNode integration tests

Ports 36 JetStream super-cluster tests from jetstream_super_cluster_test.go,
3 JetStream leaf-node tests from jetstream_leafnode_test.go, and 14 leaf-node
tests from leafnode_test.go into the integration test project. Creates the
required harness infrastructure (TestSuperCluster, TestCluster, IntegrationTestBase,
CheckHelper, ConfigHelper, NatsTestClient, TestServerHelper). All 53 tests are
marked [Fact(Skip = "...")] pending full multi-server cluster runtime.
This commit is contained in:
Joseph Doherty
2026-03-01 12:15:44 -05:00
parent 41ea272c8a
commit bebff9168a
9 changed files with 1037 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
// Copyright 2020-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Polling / assertion helpers ported from the Go test framework's checkFor and
/// related utilities.
/// </summary>
public static class CheckHelper
{
/// <summary>
/// Polls <paramref name="check"/> every <paramref name="interval"/> until it
/// returns null (success) or <paramref name="timeout"/> expires.
/// </summary>
public static void CheckFor(
TimeSpan timeout,
TimeSpan interval,
Func<string?> check)
{
var deadline = DateTime.UtcNow + timeout;
string? last = null;
while (DateTime.UtcNow < deadline)
{
last = check();
if (last is null)
return;
Thread.Sleep(interval);
}
throw new Xunit.Sdk.XunitException($"CheckFor timed out after {timeout}: {last}");
}
/// <summary>
/// Waits until the supplied server reports exactly <paramref name="expected"/>
/// leaf-node connections.
/// </summary>
public static void CheckLeafNodeConnectedCount(object server, int expected) =>
throw new NotImplementedException("Requires a running server instance.");
}

View File

@@ -0,0 +1,38 @@
// Copyright 2020-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Helpers for generating and writing NATS server configuration files used by
/// integration tests.
/// </summary>
public static class ConfigHelper
{
/// <summary>
/// Template for a basic JetStream super-cluster node.
/// Placeholders: {ServerName}, {ClusterName}, {StoreDir}, {ClusterPort}, {Routes}
/// </summary>
public const string JsSuperClusterTemplate = """
listen: 127.0.0.1:-1
server_name: {ServerName}
jetstream: {max_mem_store: 256MB, max_file_store: 2GB, store_dir: '{StoreDir}'}
cluster {
name: {ClusterName}
listen: 127.0.0.1:{ClusterPort}
routes = [{Routes}]
}
accounts { $SYS { users = [ { user: "admin", pass: "s3cr3t!" } ] } }
""";
/// <summary>
/// Writes <paramref name="content"/> to a temporary file and returns its path.
/// The file is deleted when the process exits (via <see cref="Path.GetTempFileName"/>).
/// </summary>
public static string CreateConfigFile(string content)
{
var path = Path.Combine(Path.GetTempPath(), $"natsnet_{Guid.NewGuid():N}.conf");
File.WriteAllText(path, content);
return path;
}
}

View File

@@ -0,0 +1,18 @@
// Copyright 2020-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Base class for integration tests that require a running .NET NatsServer.
/// Tests that depend on multi-server cluster infrastructure skip automatically
/// until the full server runtime is available.
/// </summary>
public abstract class IntegrationTestBase
{
/// <summary>
/// Returns true when the full server runtime required by cluster / super-cluster
/// tests is not yet available.
/// </summary>
protected static bool ServerRuntimeUnavailable => true;
}

View File

@@ -0,0 +1,24 @@
// Copyright 2020-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using NATS.Client.Core;
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Helpers for connecting NATS test clients to running servers.
/// </summary>
public static class NatsTestClient
{
/// <summary>Connects to the given NATS URL.</summary>
public static async Task<NatsConnection> Connect(string url)
{
var conn = new NatsConnection(new NatsOpts { Url = url });
await conn.ConnectAsync();
return conn;
}
/// <summary>Connects to the client URL of the supplied server handle.</summary>
public static Task<NatsConnection> ConnectToServer(object server) =>
throw new NotImplementedException("Requires a running server instance.");
}

View File

@@ -0,0 +1,29 @@
// Copyright 2020-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Placeholder harness for a single JetStream cluster (multiple servers forming a
/// Raft group). The real implementation requires the full NatsServer runtime.
/// </summary>
public sealed class TestCluster : IDisposable
{
public string Name { get; }
public TestCluster(string name)
{
Name = name;
}
/// <summary>
/// Creates a JetStream cluster. Not yet implemented.
/// </summary>
public static TestCluster CreateJetStreamCluster(int numServers, string name) =>
new(name);
public void Dispose()
{
// TODO: shut down all in-process servers when runtime is available.
}
}

View File

@@ -0,0 +1,18 @@
// Copyright 2020-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Helpers for starting individual NATS server instances in integration tests.
/// </summary>
public static class TestServerHelper
{
/// <summary>Runs a server with programmatically supplied options.</summary>
public static IDisposable RunServer(object opts) =>
throw new NotImplementedException("Requires full NatsServer runtime.");
/// <summary>Runs a server from a config file on disk.</summary>
public static IDisposable RunServerWithConfig(string configFilePath) =>
throw new NotImplementedException("Requires full NatsServer runtime.");
}

View File

@@ -0,0 +1,49 @@
// Copyright 2020-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
namespace ZB.MOM.NatsNet.Server.IntegrationTests.Helpers;
/// <summary>
/// Placeholder harness for a JetStream super-cluster (multiple clusters connected
/// by gateways). The real implementation requires the full NatsServer runtime to
/// be capable of spawning in-process cluster nodes.
/// </summary>
public sealed class TestSuperCluster : IDisposable
{
private readonly List<TestCluster> _clusters;
private TestSuperCluster(List<TestCluster> clusters)
{
_clusters = clusters;
}
/// <summary>
/// Creates a super-cluster with <paramref name="numClusters"/> clusters, each
/// containing <paramref name="numPerCluster"/> servers.
/// Not yet implemented — tests that call this will be skipped.
/// </summary>
public static TestSuperCluster CreateJetStreamSuperCluster(int numPerCluster, int numClusters) =>
new([]);
/// <summary>Returns the current JetStream meta-leader across all clusters.</summary>
public object? Leader() => null;
/// <summary>Returns a random server from any cluster in the super-cluster.</summary>
public object? RandomServer() => null;
/// <summary>Waits until a meta-leader is elected.</summary>
public void WaitOnLeader() { /* placeholder */ }
/// <summary>Waits until a stream leader is elected in the given account.</summary>
public void WaitOnStreamLeader(string account, string stream) { /* placeholder */ }
/// <summary>Returns the named cluster.</summary>
public TestCluster ClusterForName(string name) =>
_clusters.Single(c => c.Name == name);
public void Dispose()
{
foreach (var c in _clusters)
c.Dispose();
}
}