// Go reference: server/events.go:2082-2090 — compressionType / snappyCompression, // and events.go:578-598 — internalSendLoop optional compression branch. using System.Text; using NATS.Server.Events; namespace NATS.Server.Monitoring.Tests.Events; /// /// Tests for — S2/Snappy compression for system event payloads. /// Go reference: server/events.go — compressed system events via snappyCompression. /// public class EventCompressionTests : IDisposable { public EventCompressionTests() { // Ensure a clean statistics baseline for every test. EventCompressor.ResetStats(); } public void Dispose() { EventCompressor.ResetStats(); } // ── 1 ────────────────────────────────────────────────────────────────────── [Fact] public void Compress_ValidPayload_ReturnsCompressed() { // Arrange var json = """{"server":{"name":"s1","id":"ABCDEF"},"data":{"conns":42,"bytes":1024}}"""; var payload = Encoding.UTF8.GetBytes(json); // Act var compressed = EventCompressor.Compress(payload); // Assert compressed.ShouldNotBeNull(); compressed.Length.ShouldBeGreaterThan(0); // Snappy output begins with a varint for the original length — not the same raw bytes. compressed.ShouldNotBe(payload); } // ── 2 ────────────────────────────────────────────────────────────────────── [Fact] public void Decompress_RoundTrip_MatchesOriginal() { // Arrange var original = Encoding.UTF8.GetBytes( """{"server":{"name":"test","id":"XYZ"},"stats":{"cpu":0.5,"mem":1048576}}"""); // Act var compressed = EventCompressor.Compress(original); var decompressed = EventCompressor.Decompress(compressed); // Assert decompressed.ShouldBe(original); } // ── 3 ────────────────────────────────────────────────────────────────────── [Fact] public void ShouldCompress_BelowThreshold_ReturnsFalse() { // 100 bytes is well below the default 256-byte threshold. EventCompressor.ShouldCompress(100).ShouldBeFalse(); } // ── 4 ────────────────────────────────────────────────────────────────────── [Fact] public void ShouldCompress_AboveThreshold_ReturnsTrue() { // 500 bytes exceeds the default 256-byte threshold. EventCompressor.ShouldCompress(500).ShouldBeTrue(); } // ── 5 ────────────────────────────────────────────────────────────────────── [Fact] public void CompressIfBeneficial_SmallPayload_NotCompressed() { // Arrange — 50 bytes is below the 256-byte threshold. var payload = Encoding.UTF8.GetBytes("small"); // Act var (data, compressed) = EventCompressor.CompressIfBeneficial(payload); // Assert compressed.ShouldBeFalse(); data.ShouldBe(payload); } // ── 6 ────────────────────────────────────────────────────────────────────── [Fact] public void CompressIfBeneficial_LargePayload_Compressed() { // Arrange — build a payload well above the 256-byte threshold. var largeJson = """{"server":{"name":"s1"},"data":""" + new string('x', 500) + "}"; var payload = Encoding.UTF8.GetBytes(largeJson); payload.Length.ShouldBeGreaterThan(256); // Act var (data, isCompressed) = EventCompressor.CompressIfBeneficial(payload); // Assert isCompressed.ShouldBeTrue(); // The returned bytes should decompress back to the original. var restored = EventCompressor.Decompress(data); restored.ShouldBe(payload); } // ── 7 ────────────────────────────────────────────────────────────────────── [Fact] public void GetCompressionRatio_Calculates() { // 100 / 200 = 0.5 var ratio = EventCompressor.GetCompressionRatio(originalSize: 200, compressedSize: 100); ratio.ShouldBe(0.5, tolerance: 0.001); } // ── 8 ────────────────────────────────────────────────────────────────────── [Fact] public void TotalCompressed_IncrementedOnCompress() { // Arrange — stats were reset in constructor. EventCompressor.TotalCompressed.ShouldBe(0L); var largePayload = Encoding.UTF8.GetBytes(new string('a', 512)); // Act — two calls that exceed threshold. EventCompressor.CompressIfBeneficial(largePayload); EventCompressor.CompressIfBeneficial(largePayload); // Assert EventCompressor.TotalCompressed.ShouldBe(2L); } // ── 9 ────────────────────────────────────────────────────────────────────── [Fact] public void BytesSaved_TracksCorrectly() { // Arrange // Use a highly-compressible payload so savings are guaranteed. var payload = Encoding.UTF8.GetBytes(new string('z', 1024)); // Act EventCompressor.CompressIfBeneficial(payload); // Assert — compressed version of 1 024 repeated bytes should be much smaller. EventCompressor.BytesSaved.ShouldBeGreaterThan(0L); // BytesSaved = original - compressed; should be less than original size. EventCompressor.BytesSaved.ShouldBeLessThan(payload.Length); } // ── 10 ───────────────────────────────────────────────────────────────────── [Fact] public void ResetStats_ClearsAll() { // Arrange — produce some stats first. var largePayload = Encoding.UTF8.GetBytes(new string('b', 512)); EventCompressor.CompressIfBeneficial(largePayload); EventCompressor.TotalCompressed.ShouldBeGreaterThan(0L); // Act EventCompressor.ResetStats(); // Assert EventCompressor.TotalCompressed.ShouldBe(0L); 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); } }