feat: Waves 3-5 — FileStore, RAFT, JetStream clustering, and concurrency tests
Add comprehensive Go-parity test coverage across 3 subsystems: - FileStore: basic CRUD, limits, purge, recovery, subjects, encryption, compression, MemStore (161 tests, 24 skipped for not-yet-implemented) - RAFT: core types, wire format, election, log replication, snapshots (95 tests) - JetStream Clustering: meta controller, stream/consumer replica groups, concurrency stress tests (90 tests) Total: ~346 new test annotations across 17 files (+7,557 lines) Full suite: 2,606 passing, 0 failures, 27 skipped
This commit is contained in:
166
tests/NATS.Server.Tests/Raft/RaftWireFormatTests.cs
Normal file
166
tests/NATS.Server.Tests/Raft/RaftWireFormatTests.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System.Text.Json;
|
||||
using NATS.Server.Raft;
|
||||
|
||||
namespace NATS.Server.Tests.Raft;
|
||||
|
||||
/// <summary>
|
||||
/// Wire format encoding/decoding tests for RAFT RPC contracts.
|
||||
/// Go: TestNRGAppendEntryEncode, TestNRGAppendEntryDecode in server/raft_test.go:82-152.
|
||||
/// The .NET implementation uses JSON serialization rather than binary encoding,
|
||||
/// so these tests validate JSON round-trip fidelity for all RPC types.
|
||||
/// </summary>
|
||||
public class RaftWireFormatTests
|
||||
{
|
||||
// Go: TestNRGAppendEntryEncode server/raft_test.go:82
|
||||
[Fact]
|
||||
public void VoteRequest_json_round_trip()
|
||||
{
|
||||
var original = new VoteRequest { Term = 5, CandidateId = "node-alpha" };
|
||||
var json = JsonSerializer.Serialize(original);
|
||||
json.ShouldNotBeNullOrWhiteSpace();
|
||||
|
||||
var decoded = JsonSerializer.Deserialize<VoteRequest>(json);
|
||||
decoded.ShouldNotBeNull();
|
||||
decoded.Term.ShouldBe(5);
|
||||
decoded.CandidateId.ShouldBe("node-alpha");
|
||||
}
|
||||
|
||||
// Go: TestNRGAppendEntryEncode server/raft_test.go:82
|
||||
[Fact]
|
||||
public void VoteResponse_json_round_trip()
|
||||
{
|
||||
var granted = new VoteResponse { Granted = true };
|
||||
var json = JsonSerializer.Serialize(granted);
|
||||
var decoded = JsonSerializer.Deserialize<VoteResponse>(json);
|
||||
decoded.ShouldNotBeNull();
|
||||
decoded.Granted.ShouldBeTrue();
|
||||
|
||||
var denied = new VoteResponse { Granted = false };
|
||||
var json2 = JsonSerializer.Serialize(denied);
|
||||
var decoded2 = JsonSerializer.Deserialize<VoteResponse>(json2);
|
||||
decoded2.ShouldNotBeNull();
|
||||
decoded2.Granted.ShouldBeFalse();
|
||||
}
|
||||
|
||||
// Go: TestNRGAppendEntryEncode server/raft_test.go:82
|
||||
[Fact]
|
||||
public void AppendResult_json_round_trip()
|
||||
{
|
||||
var original = new AppendResult { FollowerId = "f1", Success = true };
|
||||
var json = JsonSerializer.Serialize(original);
|
||||
var decoded = JsonSerializer.Deserialize<AppendResult>(json);
|
||||
decoded.ShouldNotBeNull();
|
||||
decoded.FollowerId.ShouldBe("f1");
|
||||
decoded.Success.ShouldBeTrue();
|
||||
}
|
||||
|
||||
// Go: TestNRGAppendEntryEncode server/raft_test.go:82 — multiple entries
|
||||
[Fact]
|
||||
public void RaftLogEntry_batch_json_round_trip_preserves_order()
|
||||
{
|
||||
var entries = Enumerable.Range(1, 50)
|
||||
.Select(i => new RaftLogEntry(Index: i, Term: (i % 3) + 1, Command: $"op-{i}"))
|
||||
.ToList();
|
||||
|
||||
var json = JsonSerializer.Serialize(entries);
|
||||
var decoded = JsonSerializer.Deserialize<List<RaftLogEntry>>(json);
|
||||
|
||||
decoded.ShouldNotBeNull();
|
||||
decoded.Count.ShouldBe(50);
|
||||
|
||||
for (var i = 0; i < 50; i++)
|
||||
{
|
||||
decoded[i].Index.ShouldBe(i + 1);
|
||||
decoded[i].Term.ShouldBe((i + 1) % 3 + 1);
|
||||
decoded[i].Command.ShouldBe($"op-{i + 1}");
|
||||
}
|
||||
}
|
||||
|
||||
// Go: TestNRGAppendEntryEncode server/raft_test.go:82 — large payload
|
||||
[Fact]
|
||||
public void RaftLogEntry_large_command_round_trips()
|
||||
{
|
||||
var largeCommand = new string('x', 65536);
|
||||
var entry = new RaftLogEntry(Index: 1, Term: 1, Command: largeCommand);
|
||||
|
||||
var json = JsonSerializer.Serialize(entry);
|
||||
var decoded = JsonSerializer.Deserialize<RaftLogEntry>(json);
|
||||
|
||||
decoded.ShouldNotBeNull();
|
||||
decoded.Command.Length.ShouldBe(65536);
|
||||
decoded.Command.ShouldBe(largeCommand);
|
||||
}
|
||||
|
||||
// Go: TestNRGAppendEntryEncode server/raft_test.go:82 — snapshot marker
|
||||
[Fact]
|
||||
public void RaftSnapshot_json_round_trip()
|
||||
{
|
||||
var data = new byte[256];
|
||||
Random.Shared.NextBytes(data);
|
||||
|
||||
var snapshot = new RaftSnapshot
|
||||
{
|
||||
LastIncludedIndex = 999,
|
||||
LastIncludedTerm = 42,
|
||||
Data = data,
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(snapshot);
|
||||
var decoded = JsonSerializer.Deserialize<RaftSnapshot>(json);
|
||||
|
||||
decoded.ShouldNotBeNull();
|
||||
decoded.LastIncludedIndex.ShouldBe(999);
|
||||
decoded.LastIncludedTerm.ShouldBe(42);
|
||||
decoded.Data.ShouldBe(data);
|
||||
}
|
||||
|
||||
// Go: TestNRGAppendEntryEncode server/raft_test.go:82 — empty snapshot data
|
||||
[Fact]
|
||||
public void RaftSnapshot_empty_data_round_trips()
|
||||
{
|
||||
var snapshot = new RaftSnapshot
|
||||
{
|
||||
LastIncludedIndex = 10,
|
||||
LastIncludedTerm = 2,
|
||||
Data = [],
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(snapshot);
|
||||
var decoded = JsonSerializer.Deserialize<RaftSnapshot>(json);
|
||||
|
||||
decoded.ShouldNotBeNull();
|
||||
decoded.Data.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
// Go: TestNRGAppendEntryEncode server/raft_test.go:82 — special characters
|
||||
[Fact]
|
||||
public void RaftLogEntry_special_characters_in_command_round_trips()
|
||||
{
|
||||
var commands = new[]
|
||||
{
|
||||
"hello\nworld",
|
||||
"tab\there",
|
||||
"quote\"inside",
|
||||
"backslash\\path",
|
||||
"unicode-\u00e9\u00e0\u00fc",
|
||||
"{\"nested\":\"json\"}",
|
||||
};
|
||||
|
||||
foreach (var cmd in commands)
|
||||
{
|
||||
var entry = new RaftLogEntry(Index: 1, Term: 1, Command: cmd);
|
||||
var json = JsonSerializer.Serialize(entry);
|
||||
var decoded = JsonSerializer.Deserialize<RaftLogEntry>(json);
|
||||
decoded.ShouldNotBeNull();
|
||||
decoded.Command.ShouldBe(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
// Go: TestNRGAppendEntryDecode server/raft_test.go:125 — deserialization of malformed input
|
||||
[Fact]
|
||||
public void Malformed_json_returns_null_or_throws()
|
||||
{
|
||||
var badJson = "not-json-at-all";
|
||||
Should.Throw<JsonException>(() => JsonSerializer.Deserialize<RaftLogEntry>(badJson));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user