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:
@@ -0,0 +1,152 @@
|
||||
using System.Text.Json;
|
||||
using NATS.Server.Events;
|
||||
|
||||
namespace NATS.Server.Tests.Events;
|
||||
|
||||
public class EventApiAndSubjectsParityBatch2Tests
|
||||
{
|
||||
[Fact]
|
||||
public void EventSubjects_DefineMissingServerRequestSubjects()
|
||||
{
|
||||
EventSubjects.RemoteLatency.ShouldBe("$SYS.SERVER.{0}.ACC.{1}.LATENCY.M2");
|
||||
EventSubjects.UserDirectInfo.ShouldBe("$SYS.REQ.USER.INFO");
|
||||
EventSubjects.UserDirectReq.ShouldBe("$SYS.REQ.USER.{0}.INFO");
|
||||
EventSubjects.AccountNumSubsReq.ShouldBe("$SYS.REQ.ACCOUNT.NSUBS");
|
||||
EventSubjects.AccountSubs.ShouldBe("$SYS._INBOX_.{0}.NSUBS");
|
||||
EventSubjects.ClientKickReq.ShouldBe("$SYS.REQ.SERVER.{0}.KICK");
|
||||
EventSubjects.ClientLdmReq.ShouldBe("$SYS.REQ.SERVER.{0}.LDM");
|
||||
EventSubjects.ServerStatsPingReq.ShouldBe("$SYS.REQ.SERVER.PING.STATSZ");
|
||||
EventSubjects.ServerReloadReq.ShouldBe("$SYS.REQ.SERVER.{0}.RELOAD");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OcspSubjects_MatchGoPatterns()
|
||||
{
|
||||
EventSubjects.OcspPeerReject.ShouldBe("$SYS.SERVER.{0}.OCSP.PEER.CONN.REJECT");
|
||||
EventSubjects.OcspPeerChainlinkInvalid.ShouldBe("$SYS.SERVER.{0}.OCSP.PEER.LINK.INVALID");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OcspPeerRejectEvent_IncludesPeerCertInfo()
|
||||
{
|
||||
var evt = new OcspPeerRejectEventMsg
|
||||
{
|
||||
Id = "id",
|
||||
Kind = "client",
|
||||
Reason = "revoked",
|
||||
Peer = new EventCertInfo
|
||||
{
|
||||
Subject = "CN=client",
|
||||
Issuer = "CN=issuer",
|
||||
Fingerprint = "fingerprint",
|
||||
Raw = "raw",
|
||||
},
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(evt);
|
||||
json.ShouldContain("\"peer\":");
|
||||
json.ShouldContain("\"subject\":\"CN=client\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OcspPeerChainlinkInvalidEvent_SerializesExpectedShape()
|
||||
{
|
||||
var evt = new OcspPeerChainlinkInvalidEventMsg
|
||||
{
|
||||
Id = "id",
|
||||
Link = new EventCertInfo { Subject = "CN=link" },
|
||||
Peer = new EventCertInfo { Subject = "CN=peer" },
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(evt);
|
||||
json.ShouldContain("\"type\":\"io.nats.server.advisory.v1.ocsp_peer_link_invalid\"");
|
||||
json.ShouldContain("\"link\":");
|
||||
json.ShouldContain("\"peer\":");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EventFilterOptions_HasCoreGoFields()
|
||||
{
|
||||
var opts = new EventFilterOptions
|
||||
{
|
||||
Name = "srv-a",
|
||||
Cluster = "cluster-a",
|
||||
Host = "127.0.0.1",
|
||||
Tags = ["a", "b"],
|
||||
Domain = "domain-a",
|
||||
};
|
||||
|
||||
opts.Name.ShouldBe("srv-a");
|
||||
opts.Cluster.ShouldBe("cluster-a");
|
||||
opts.Host.ShouldBe("127.0.0.1");
|
||||
opts.Tags.ShouldBe(["a", "b"]);
|
||||
opts.Domain.ShouldBe("domain-a");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OptionRequestTypes_IncludeBaseFilterFields()
|
||||
{
|
||||
new StatszEventOptions { Name = "n" }.Name.ShouldBe("n");
|
||||
new ConnzEventOptions { Cluster = "c" }.Cluster.ShouldBe("c");
|
||||
new RoutezEventOptions { Host = "h" }.Host.ShouldBe("h");
|
||||
new HealthzEventOptions { Domain = "d" }.Domain.ShouldBe("d");
|
||||
new JszEventOptions { Tags = ["t"] }.Tags.ShouldBe(["t"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServerApiResponses_ExposeDataAndError()
|
||||
{
|
||||
var response = new ServerAPIResponse
|
||||
{
|
||||
Server = new EventServerInfo { Id = "S1" },
|
||||
Data = new { ok = true },
|
||||
Error = new ServerAPIError { Code = 500, Description = "err" },
|
||||
};
|
||||
|
||||
response.Server.Id.ShouldBe("S1");
|
||||
response.Error?.Code.ShouldBe(500);
|
||||
response.Error?.Description.ShouldBe("err");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TypedServerApiWrappers_CarryResponsePayload()
|
||||
{
|
||||
new ServerAPIConnzResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
new ServerAPIRoutezResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
new ServerAPIGatewayzResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
new ServerAPIJszResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
new ServerAPIHealthzResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
new ServerAPIVarzResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
new ServerAPISubszResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
new ServerAPILeafzResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
new ServerAPIAccountzResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
new ServerAPIExpvarzResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
new ServerAPIpqueueszResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
new ServerAPIRaftzResponse { Data = new object() }.Data.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequestPayloadTypes_KickAndLdm()
|
||||
{
|
||||
var kick = new KickClientReq { ClientId = 22 };
|
||||
var ldm = new LDMClientReq { ClientId = 33 };
|
||||
|
||||
kick.ClientId.ShouldBe(22UL);
|
||||
ldm.ClientId.ShouldBe(33UL);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UserInfo_IncludesExpectedIdentityFields()
|
||||
{
|
||||
var info = new UserInfo
|
||||
{
|
||||
User = "alice",
|
||||
Account = "A",
|
||||
Permissions = "pubsub",
|
||||
};
|
||||
|
||||
info.User.ShouldBe("alice");
|
||||
info.Account.ShouldBe("A");
|
||||
info.Permissions.ShouldBe("pubsub");
|
||||
}
|
||||
}
|
||||
@@ -168,4 +168,31 @@ public class EventCompressionTests : IDisposable
|
||||
EventCompressor.TotalUncompressed.ShouldBe(0L);
|
||||
EventCompressor.BytesSaved.ShouldBe(0L);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAcceptEncoding_ParsesSnappyAndGzip()
|
||||
{
|
||||
EventCompressor.GetAcceptEncoding("gzip, snappy").ShouldBe(EventCompressionType.Snappy);
|
||||
EventCompressor.GetAcceptEncoding("gzip").ShouldBe(EventCompressionType.Gzip);
|
||||
EventCompressor.GetAcceptEncoding("br").ShouldBe(EventCompressionType.Unsupported);
|
||||
EventCompressor.GetAcceptEncoding(null).ShouldBe(EventCompressionType.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompressionHeaderConstants_MatchGo()
|
||||
{
|
||||
EventCompressor.AcceptEncodingHeader.ShouldBe("Accept-Encoding");
|
||||
EventCompressor.ContentEncodingHeader.ShouldBe("Content-Encoding");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CompressAndDecompress_Gzip_RoundTrip_MatchesOriginal()
|
||||
{
|
||||
var payload = Encoding.UTF8.GetBytes("""{"server":"s1","data":"gzip-payload"}""");
|
||||
|
||||
var compressed = EventCompressor.Compress(payload, EventCompressionType.Gzip);
|
||||
var restored = EventCompressor.Decompress(compressed, EventCompressionType.Gzip);
|
||||
|
||||
restored.ShouldBe(payload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Text.Json;
|
||||
using NATS.Server.Events;
|
||||
|
||||
namespace NATS.Server.Tests.Events;
|
||||
|
||||
public class EventServerInfoCapabilityParityBatch1Tests
|
||||
{
|
||||
[Fact]
|
||||
public void ServerCapability_flags_match_expected_values()
|
||||
{
|
||||
((ulong)ServerCapability.JetStreamEnabled).ShouldBe(1UL << 0);
|
||||
((ulong)ServerCapability.BinaryStreamSnapshot).ShouldBe(1UL << 1);
|
||||
((ulong)ServerCapability.AccountNRG).ShouldBe(1UL << 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EventServerInfo_capability_methods_set_and_read_flags()
|
||||
{
|
||||
var info = new EventServerInfo();
|
||||
|
||||
info.SetJetStreamEnabled();
|
||||
info.SetBinaryStreamSnapshot();
|
||||
info.SetAccountNRG();
|
||||
|
||||
info.JetStream.ShouldBeTrue();
|
||||
info.JetStreamEnabled().ShouldBeTrue();
|
||||
info.BinaryStreamSnapshot().ShouldBeTrue();
|
||||
info.AccountNRG().ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServerID_serializes_with_name_host_id_fields()
|
||||
{
|
||||
var payload = new ServerID
|
||||
{
|
||||
Name = "srv-a",
|
||||
Host = "127.0.0.1",
|
||||
Id = "N1",
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
json.ShouldContain("\"name\":\"srv-a\"");
|
||||
json.ShouldContain("\"host\":\"127.0.0.1\"");
|
||||
json.ShouldContain("\"id\":\"N1\"");
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ public class RemoteServerEventTests
|
||||
string.Format(EventSubjects.RemoteServerShutdown, serverId)
|
||||
.ShouldBe($"$SYS.SERVER.{serverId}.REMOTE.SHUTDOWN");
|
||||
string.Format(EventSubjects.LeafNodeConnected, serverId)
|
||||
.ShouldBe($"$SYS.SERVER.{serverId}.LEAFNODE.CONNECT");
|
||||
.ShouldBe($"$SYS.ACCOUNT.{serverId}.LEAFNODE.CONNECT");
|
||||
}
|
||||
|
||||
// --- JSON serialization ---
|
||||
|
||||
Reference in New Issue
Block a user