- 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
104 lines
3.0 KiB
C#
104 lines
3.0 KiB
C#
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using NATS.Server.Routes;
|
|
using NATS.Server.Subscriptions;
|
|
|
|
namespace NATS.Server.Tests.Routes;
|
|
|
|
public class RouteBatchProtoParityBatch3Tests
|
|
{
|
|
[Fact]
|
|
public async Task SendRouteSubProtosAsync_writes_batched_rs_plus_frames()
|
|
{
|
|
var (connection, peer) = CreateRoutePair();
|
|
try
|
|
{
|
|
await connection.SendRouteSubProtosAsync(
|
|
[
|
|
new RemoteSubscription("orders.*", null, "r1", Account: "A"),
|
|
new RemoteSubscription("orders.q", "workers", "r1", Account: "A", QueueWeight: 2),
|
|
],
|
|
CancellationToken.None);
|
|
|
|
var data = ReadFromPeer(peer);
|
|
data.ShouldContain("RS+ A orders.*");
|
|
data.ShouldContain("RS+ A orders.q workers 2");
|
|
}
|
|
finally
|
|
{
|
|
await connection.DisposeAsync();
|
|
peer.Dispose();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendRouteUnSubProtosAsync_writes_batched_rs_minus_frames()
|
|
{
|
|
var (connection, peer) = CreateRoutePair();
|
|
try
|
|
{
|
|
await connection.SendRouteUnSubProtosAsync(
|
|
[
|
|
new RemoteSubscription("orders.*", null, "r1", Account: "A"),
|
|
new RemoteSubscription("orders.q", "workers", "r1", Account: "A"),
|
|
],
|
|
CancellationToken.None);
|
|
|
|
var data = ReadFromPeer(peer);
|
|
data.ShouldContain("RS- A orders.*");
|
|
data.ShouldContain("RS- A orders.q workers");
|
|
}
|
|
finally
|
|
{
|
|
await connection.DisposeAsync();
|
|
peer.Dispose();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendRouteSubOrUnSubProtosAsync_skips_empty_lines_and_flushes_once()
|
|
{
|
|
var (connection, peer) = CreateRoutePair();
|
|
try
|
|
{
|
|
await connection.SendRouteSubOrUnSubProtosAsync(
|
|
["RS+ A foo.bar", "", " ", "RS- A foo.bar"],
|
|
CancellationToken.None);
|
|
|
|
var data = ReadFromPeer(peer);
|
|
data.ShouldContain("RS+ A foo.bar");
|
|
data.ShouldContain("RS- A foo.bar");
|
|
data.ShouldNotContain("\r\n\r\n");
|
|
}
|
|
finally
|
|
{
|
|
await connection.DisposeAsync();
|
|
peer.Dispose();
|
|
}
|
|
}
|
|
|
|
private static (RouteConnection Route, Socket Peer) CreateRoutePair()
|
|
{
|
|
var listener = new TcpListener(IPAddress.Loopback, 0);
|
|
listener.Start();
|
|
var endpoint = (IPEndPoint)listener.LocalEndpoint;
|
|
|
|
var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
|
client.Connect(endpoint);
|
|
|
|
var server = listener.AcceptSocket();
|
|
listener.Stop();
|
|
|
|
return (new RouteConnection(client), server);
|
|
}
|
|
|
|
private static string ReadFromPeer(Socket peer)
|
|
{
|
|
peer.ReceiveTimeout = 2_000;
|
|
var buffer = new byte[4096];
|
|
var read = peer.Receive(buffer);
|
|
return Encoding.ASCII.GetString(buffer, 0, read);
|
|
}
|
|
}
|