Files
handler-base/natsutil/natsutil_test.go
Billy D. 13ef1df109
Some checks failed
CI / Lint (push) Failing after 3m2s
CI / Test (push) Successful in 3m44s
CI / Release (push) Has been skipped
CI / Notify Downstream (chat-handler) (push) Has been skipped
CI / Notify Downstream (pipeline-bridge) (push) Has been skipped
CI / Notify Downstream (stt-module) (push) Has been skipped
CI / Notify Downstream (tts-module) (push) Has been skipped
CI / Notify Downstream (voice-assistant) (push) Has been skipped
CI / Notify (push) Successful in 1s
feat!: replace msgpack with protobuf for all NATS messages
BREAKING CHANGE: All NATS message serialization now uses Protocol Buffers.
- Added proto/messages/v1/messages.proto with 22 message types
- Generated Go code at gen/messagespb/
- messages/ package now exports type aliases to proto types
- natsutil.Publish/Request/Decode use proto.Marshal/Unmarshal
- Removed legacy MessageHandler, OnMessage, wrapMapHandler
- TypedMessageHandler now returns (proto.Message, error)
- EffectiveQuery is now a free function: messages.EffectiveQuery(req)
- Removed msgpack dependency entirely
2026-02-21 14:58:05 -05:00

215 lines
6.4 KiB
Go

package natsutil
import (
"testing"
"google.golang.org/protobuf/proto"
pb "git.daviestechlabs.io/daviestechlabs/handler-base/gen/messagespb"
)
// ────────────────────────────────────────────────────────────────────────────
// Decode tests
// ────────────────────────────────────────────────────────────────────────────
func TestDecode_ChatRequest_Roundtrip(t *testing.T) {
orig := &pb.ChatRequest{
RequestId: "req-001",
UserId: "user-42",
Premium: true,
TopK: 10,
}
data, err := proto.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded pb.ChatRequest
if err := Decode(data, &decoded); err != nil {
t.Fatal(err)
}
if decoded.GetRequestId() != "req-001" {
t.Errorf("RequestId = %v", decoded.GetRequestId())
}
if decoded.GetUserId() != "user-42" {
t.Errorf("UserId = %v", decoded.GetUserId())
}
if decoded.GetPremium() != true {
t.Errorf("Premium = %v", decoded.GetPremium())
}
if decoded.GetTopK() != 10 {
t.Errorf("TopK = %v", decoded.GetTopK())
}
}
func TestDecode_EmptyMessage(t *testing.T) {
data, err := proto.Marshal(&pb.ChatRequest{})
if err != nil {
t.Fatal(err)
}
var decoded pb.ChatRequest
if err := Decode(data, &decoded); err != nil {
t.Fatal(err)
}
if decoded.GetRequestId() != "" {
t.Errorf("expected empty RequestId, got %q", decoded.GetRequestId())
}
}
func TestDecode_InvalidData(t *testing.T) {
err := Decode([]byte{0xFF, 0xFE}, &pb.ChatRequest{})
if err == nil {
t.Error("expected error for invalid protobuf data")
}
}
// ────────────────────────────────────────────────────────────────────────────
// Typed struct roundtrip tests
// ────────────────────────────────────────────────────────────────────────────
func TestDecode_VoiceResponse_Roundtrip(t *testing.T) {
orig := &pb.VoiceResponse{
RequestId: "vr-001",
Response: "The capital of France is Paris.",
Transcription: "What is the capital of France?",
}
data, err := proto.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded pb.VoiceResponse
if err := Decode(data, &decoded); err != nil {
t.Fatal(err)
}
if decoded.GetRequestId() != orig.GetRequestId() {
t.Errorf("RequestId = %q, want %q", decoded.GetRequestId(), orig.GetRequestId())
}
if decoded.GetResponse() != orig.GetResponse() {
t.Errorf("Response = %q, want %q", decoded.GetResponse(), orig.GetResponse())
}
if decoded.GetTranscription() != orig.GetTranscription() {
t.Errorf("Transcription = %q, want %q", decoded.GetTranscription(), orig.GetTranscription())
}
}
func TestDecode_ErrorResponse_Roundtrip(t *testing.T) {
orig := &pb.ErrorResponse{
Error: true,
Message: "something broke",
Type: "InternalError",
}
data, err := proto.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded pb.ErrorResponse
if err := Decode(data, &decoded); err != nil {
t.Fatal(err)
}
if !decoded.GetError() {
t.Error("expected Error=true")
}
if decoded.GetMessage() != "something broke" {
t.Errorf("Message = %q", decoded.GetMessage())
}
if decoded.GetType() != "InternalError" {
t.Errorf("Type = %q", decoded.GetType())
}
}
// ────────────────────────────────────────────────────────────────────────────
// Binary data tests (audio []byte in protobuf)
// ────────────────────────────────────────────────────────────────────────────
func TestBinaryDataRoundtrip(t *testing.T) {
audio := make([]byte, 32768)
for i := range audio {
audio[i] = byte(i % 256)
}
orig := &pb.TTSAudioChunk{
SessionId: "sess-audio-001",
Audio: audio,
SampleRate: 24000,
}
data, err := proto.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded pb.TTSAudioChunk
if err := Decode(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.GetAudio()) != len(orig.GetAudio()) {
t.Fatalf("audio len = %d, want %d", len(decoded.GetAudio()), len(orig.GetAudio()))
}
for i := range decoded.GetAudio() {
if decoded.GetAudio()[i] != orig.GetAudio()[i] {
t.Fatalf("audio[%d] = %d, want %d", i, decoded.GetAudio()[i], orig.GetAudio()[i])
}
}
}
// TestProtoWireSize shows protobuf wire size for binary payloads.
func TestProtoWireSize(t *testing.T) {
audio := make([]byte, 16384)
msg := &pb.TTSAudioChunk{
SessionId: "sess-1",
Audio: audio,
}
data, _ := proto.Marshal(msg)
t.Logf("TTSAudioChunk with 16KB audio: %d bytes on wire", len(data))
}
// ────────────────────────────────────────────────────────────────────────────
// Benchmarks
// ────────────────────────────────────────────────────────────────────────────
func BenchmarkEncode_ChatRequest(b *testing.B) {
data := &pb.ChatRequest{
RequestId: "req-bench",
UserId: "user-bench",
Message: "What is the weather today?",
Premium: true,
TopK: 10,
}
for b.Loop() {
_, _ = proto.Marshal(data)
}
}
func BenchmarkDecode_ChatRequest(b *testing.B) {
raw, _ := proto.Marshal(&pb.ChatRequest{
RequestId: "req-bench",
UserId: "user-bench",
Message: "What is the weather today?",
Premium: true,
TopK: 10,
})
for b.Loop() {
var m pb.ChatRequest
_ = Decode(raw, &m)
}
}
func BenchmarkDecode_Audio32KB(b *testing.B) {
raw, _ := proto.Marshal(&pb.TTSAudioChunk{
SessionId: "s1",
Audio: make([]byte, 32768),
SampleRate: 24000,
})
for b.Loop() {
var m pb.TTSAudioChunk
_ = Decode(raw, &m)
}
}