using System.Text.Json; using NATS.Server.Raft; namespace NATS.Server.Tests.Raft; /// /// 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. /// 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(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(json); decoded.ShouldNotBeNull(); decoded.Granted.ShouldBeTrue(); var denied = new VoteResponse { Granted = false }; var json2 = JsonSerializer.Serialize(denied); var decoded2 = JsonSerializer.Deserialize(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(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>(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(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(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(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(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(() => JsonSerializer.Deserialize(badJson)); } }