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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user