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

@@ -12,6 +12,7 @@ using System.Net.Sockets;
using System.Text;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server;
using NATS.Server.Auth;
using NATS.Server.Monitoring;
using NATS.Server.Protocol;
@@ -717,6 +718,121 @@ public class MsgTraceGoParityTests : IAsyncLifetime
await cts.CancelAsync();
}
/// <summary>
/// Username/password authorization violations are tracked in closed connections.
/// Go: TestClosedUPAuthorizationViolation (closed_conns_test.go:187)
/// </summary>
[Fact]
public async Task ClosedConns_up_auth_violation_close_reason_tracked()
{
// Go: TestClosedUPAuthorizationViolation (closed_conns_test.go:187)
var port = GetFreePort();
using var cts = new CancellationTokenSource();
using var server = new NatsServer(
new NatsOptions
{
Port = port,
Users =
[
new User { Username = "my_user", Password = "my_secret" },
],
},
NullLoggerFactory.Instance);
_ = server.StartAsync(cts.Token);
await server.WaitForReadyAsync();
// No credentials
using (var conn1 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
await conn1.ConnectAsync(IPAddress.Loopback, port);
await ReadUntilAsync(conn1, "\r\n"); // INFO
await conn1.SendAsync("CONNECT {\"verbose\":false}\r\nPING\r\n"u8.ToArray());
await ReadUntilAsync(conn1, "-ERR", 2000);
}
// Wrong password
using (var conn2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
await conn2.ConnectAsync(IPAddress.Loopback, port);
await ReadUntilAsync(conn2, "\r\n"); // INFO
await conn2.SendAsync(
"CONNECT {\"verbose\":false,\"user\":\"my_user\",\"pass\":\"wrong_pass\"}\r\nPING\r\n"u8.ToArray());
await ReadUntilAsync(conn2, "-ERR", 2000);
}
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5);
while (DateTime.UtcNow < deadline)
{
if (server.GetClosedClients().Count >= 2)
break;
await Task.Delay(10);
}
var conns = server.GetClosedClients().ToList();
conns.Count.ShouldBeGreaterThanOrEqualTo(2);
conns.Take(2).All(c => c.Reason.Contains("Authorization Violation", StringComparison.OrdinalIgnoreCase))
.ShouldBeTrue();
await cts.CancelAsync();
}
/// <summary>
/// TLS handshake failures are tracked in closed connections with the TLS reason.
/// Go: TestClosedTLSHandshake (closed_conns_test.go:247)
/// </summary>
[Fact]
public async Task ClosedConns_tls_handshake_close_reason_tracked()
{
// Go: TestClosedTLSHandshake (closed_conns_test.go:247)
var (certPath, keyPath) = TlsHelperTests.GenerateTestCertFiles();
try
{
var port = GetFreePort();
using var cts = new CancellationTokenSource();
using var server = new NatsServer(
new NatsOptions
{
Port = port,
TlsCert = certPath,
TlsKey = keyPath,
TlsVerify = true,
AllowNonTls = false,
},
NullLoggerFactory.Instance);
_ = server.StartAsync(cts.Token);
await server.WaitForReadyAsync();
// Plain TCP client against TLS-required port should fail handshake.
using (var conn = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
await conn.ConnectAsync(IPAddress.Loopback, port);
await ReadUntilAsync(conn, "\r\n"); // INFO
await conn.SendAsync("CONNECT {\"verbose\":false}\r\nPING\r\n"u8.ToArray());
_ = await ReadUntilAsync(conn, "-ERR", 1000);
}
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5);
while (DateTime.UtcNow < deadline)
{
if (server.GetClosedClients().Any())
break;
await Task.Delay(10);
}
var conns = server.GetClosedClients().ToList();
conns.Count.ShouldBeGreaterThan(0);
conns.Any(c => c.Reason.Contains("TLS Handshake Error", StringComparison.OrdinalIgnoreCase))
.ShouldBeTrue();
await cts.CancelAsync();
}
finally
{
File.Delete(certPath);
File.Delete(keyPath);
}
}
// ─── ClosedState enum (closed_conns_test.go — checkReason) ───────────────
/// <summary>