package natsutil import ( "testing" "github.com/vmihailenco/msgpack/v5" ) // ──────────────────────────────────────────────────────────────────────────── // DecodeMsgpackMap tests // ──────────────────────────────────────────────────────────────────────────── func TestDecodeMsgpackMap_Roundtrip(t *testing.T) { orig := map[string]any{ "request_id": "req-001", "user_id": "user-42", "premium": true, "top_k": int64(10), // msgpack decodes ints as int64 } data, err := msgpack.Marshal(orig) if err != nil { t.Fatal(err) } decoded, err := DecodeMsgpackMap(data) if err != nil { t.Fatal(err) } if decoded["request_id"] != "req-001" { t.Errorf("request_id = %v", decoded["request_id"]) } if decoded["premium"] != true { t.Errorf("premium = %v", decoded["premium"]) } } func TestDecodeMsgpackMap_Empty(t *testing.T) { data, _ := msgpack.Marshal(map[string]any{}) m, err := DecodeMsgpackMap(data) if err != nil { t.Fatal(err) } if len(m) != 0 { t.Errorf("expected empty map, got %v", m) } } func TestDecodeMsgpackMap_InvalidData(t *testing.T) { _, err := DecodeMsgpackMap([]byte{0xFF, 0xFE}) if err == nil { t.Error("expected error for invalid msgpack data") } } // ──────────────────────────────────────────────────────────────────────────── // DecodeMsgpack (typed struct) tests // ──────────────────────────────────────────────────────────────────────────── type testMessage struct { RequestID string `msgpack:"request_id"` UserID string `msgpack:"user_id"` Count int `msgpack:"count"` Active bool `msgpack:"active"` } func TestDecodeMsgpackTyped_Roundtrip(t *testing.T) { orig := testMessage{ RequestID: "req-typed-001", UserID: "user-7", Count: 42, Active: true, } data, err := msgpack.Marshal(orig) if err != nil { t.Fatal(err) } // Simulate nats.Msg data decoding. var decoded testMessage if err := msgpack.Unmarshal(data, &decoded); err != nil { t.Fatal(err) } if decoded.RequestID != orig.RequestID { t.Errorf("RequestID = %q, want %q", decoded.RequestID, orig.RequestID) } if decoded.Count != orig.Count { t.Errorf("Count = %d, want %d", decoded.Count, orig.Count) } if decoded.Active != orig.Active { t.Errorf("Active = %v, want %v", decoded.Active, orig.Active) } } // TestTypedStructDecodesMapEncoding verifies that a typed struct can be // decoded from data that was encoded as map[string]any (backwards compat). func TestTypedStructDecodesMapEncoding(t *testing.T) { // Encode as map (the old way). mapData := map[string]any{ "request_id": "req-compat", "user_id": "user-compat", "count": int64(99), "active": false, } data, err := msgpack.Marshal(mapData) if err != nil { t.Fatal(err) } // Decode into typed struct (the new way). var msg testMessage if err := msgpack.Unmarshal(data, &msg); err != nil { t.Fatal(err) } if msg.RequestID != "req-compat" { t.Errorf("RequestID = %q", msg.RequestID) } if msg.Count != 99 { t.Errorf("Count = %d, want 99", msg.Count) } } // ──────────────────────────────────────────────────────────────────────────── // Binary data tests (audio []byte in msgpack) // ──────────────────────────────────────────────────────────────────────────── type audioMessage struct { SessionID string `msgpack:"session_id"` Audio []byte `msgpack:"audio"` SampleRate int `msgpack:"sample_rate"` } func TestBinaryDataRoundtrip(t *testing.T) { audio := make([]byte, 32768) for i := range audio { audio[i] = byte(i % 256) } orig := audioMessage{ SessionID: "sess-audio-001", Audio: audio, SampleRate: 24000, } data, err := msgpack.Marshal(orig) if err != nil { t.Fatal(err) } var decoded audioMessage if err := msgpack.Unmarshal(data, &decoded); err != nil { t.Fatal(err) } if len(decoded.Audio) != len(orig.Audio) { t.Fatalf("audio len = %d, want %d", len(decoded.Audio), len(orig.Audio)) } for i := range decoded.Audio { if decoded.Audio[i] != orig.Audio[i] { t.Fatalf("audio[%d] = %d, want %d", i, decoded.Audio[i], orig.Audio[i]) } } } // TestBinaryVsBase64Size shows the wire-size win of raw bytes vs base64 string. func TestBinaryVsBase64Size(t *testing.T) { audio := make([]byte, 16384) // Old approach: base64 string in map. import_b64 := make([]byte, (len(audio)*4+2)/3) // approximate base64 size mapMsg := map[string]any{ "session_id": "sess-1", "audio_b64": string(import_b64), } mapData, _ := msgpack.Marshal(mapMsg) // New approach: raw bytes in struct. structMsg := audioMessage{ SessionID: "sess-1", Audio: audio, } structData, _ := msgpack.Marshal(structMsg) t.Logf("base64-in-map: %d bytes, raw-bytes-in-struct: %d bytes (%.0f%% smaller)", len(mapData), len(structData), 100*(1-float64(len(structData))/float64(len(mapData)))) } // ──────────────────────────────────────────────────────────────────────────── // Benchmarks // ──────────────────────────────────────────────────────────────────────────── func BenchmarkEncodeMap(b *testing.B) { data := map[string]any{ "request_id": "req-bench", "user_id": "user-bench", "message": "What is the weather today?", "premium": true, "top_k": 10, } for b.Loop() { _, _ = msgpack.Marshal(data) } } func BenchmarkEncodeStruct(b *testing.B) { data := testMessage{ RequestID: "req-bench", UserID: "user-bench", Count: 10, Active: true, } for b.Loop() { _, _ = msgpack.Marshal(data) } } func BenchmarkDecodeMap(b *testing.B) { raw, _ := msgpack.Marshal(map[string]any{ "request_id": "req-bench", "user_id": "user-bench", "message": "What is the weather today?", "premium": true, "top_k": 10, }) for b.Loop() { var m map[string]any _ = msgpack.Unmarshal(raw, &m) } } func BenchmarkDecodeStruct(b *testing.B) { raw, _ := msgpack.Marshal(testMessage{ RequestID: "req-bench", UserID: "user-bench", Count: 10, Active: true, }) for b.Loop() { var m testMessage _ = msgpack.Unmarshal(raw, &m) } } func BenchmarkDecodeAudio32KB(b *testing.B) { raw, _ := msgpack.Marshal(audioMessage{ SessionID: "s1", Audio: make([]byte, 32768), SampleRate: 24000, }) for b.Loop() { var m audioMessage _ = msgpack.Unmarshal(raw, &m) } }