feat!: replace msgpack with protobuf for all NATS messages
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
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
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
This commit is contained in:
@@ -1,143 +1,58 @@
|
||||
// Package messages benchmarks compare three serialization strategies:
|
||||
//
|
||||
// 1. msgpack map[string]any — the old approach (dynamic, no types)
|
||||
// 2. msgpack typed struct — the new approach (compile-time safe, short keys)
|
||||
// 3. protobuf — optional future migration
|
||||
// Package messages benchmarks protobuf encoding/decoding of all message types.
|
||||
//
|
||||
// Run with:
|
||||
//
|
||||
// go test -bench=. -benchmem -count=5 ./messages/... | tee bench.txt
|
||||
// # optional: go install golang.org/x/perf/cmd/benchstat@latest && benchstat bench.txt
|
||||
//go test -bench=. -benchmem -count=5 ./messages/... | tee bench.txt
|
||||
//# optional: go install golang.org/x/perf/cmd/benchstat@latest && benchstat bench.txt
|
||||
package messages
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
pb "git.daviestechlabs.io/daviestechlabs/handler-base/messages/proto"
|
||||
pb "git.daviestechlabs.io/daviestechlabs/handler-base/gen/messagespb"
|
||||
)
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Test fixtures — equivalent data across all three encodings
|
||||
// Test fixtures — proto message constructors
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// chatRequestMap is the legacy map[string]any representation.
|
||||
func chatRequestMap() map[string]any {
|
||||
return map[string]any{
|
||||
"request_id": "req-abc-123",
|
||||
"user_id": "user-42",
|
||||
"message": "What is the capital of France?",
|
||||
"query": "",
|
||||
"premium": true,
|
||||
"enable_rag": true,
|
||||
"enable_reranker": true,
|
||||
"enable_streaming": false,
|
||||
"top_k": 10,
|
||||
"collection": "documents",
|
||||
"enable_tts": false,
|
||||
"system_prompt": "You are a helpful assistant.",
|
||||
"response_subject": "ai.chat.response.req-abc-123",
|
||||
}
|
||||
}
|
||||
|
||||
// chatRequestStruct is the typed struct representation.
|
||||
func chatRequestStruct() ChatRequest {
|
||||
return ChatRequest{
|
||||
RequestID: "req-abc-123",
|
||||
UserID: "user-42",
|
||||
Message: "What is the capital of France?",
|
||||
Premium: true,
|
||||
EnableRAG: true,
|
||||
EnableReranker: true,
|
||||
TopK: 10,
|
||||
Collection: "documents",
|
||||
SystemPrompt: "You are a helpful assistant.",
|
||||
ResponseSubject: "ai.chat.response.req-abc-123",
|
||||
}
|
||||
}
|
||||
|
||||
// chatRequestProto is the protobuf representation.
|
||||
func chatRequestProto() *pb.ChatRequest {
|
||||
return &pb.ChatRequest{
|
||||
RequestId: "req-abc-123",
|
||||
UserId: "user-42",
|
||||
Message: "What is the capital of France?",
|
||||
Premium: true,
|
||||
EnableRag: true,
|
||||
EnableReranker: true,
|
||||
TopK: 10,
|
||||
Collection: "documents",
|
||||
SystemPrompt: "You are a helpful assistant.",
|
||||
ResponseSubject: "ai.chat.response.req-abc-123",
|
||||
}
|
||||
return &pb.ChatRequest{
|
||||
RequestId: "req-abc-123",
|
||||
UserId: "user-42",
|
||||
Message: "What is the capital of France?",
|
||||
Premium: true,
|
||||
EnableRag: true,
|
||||
EnableReranker: true,
|
||||
TopK: 10,
|
||||
Collection: "documents",
|
||||
SystemPrompt: "You are a helpful assistant.",
|
||||
ResponseSubject: "ai.chat.response.req-abc-123",
|
||||
}
|
||||
|
||||
// voiceResponseMap is a voice response with a 16 KB audio payload.
|
||||
func voiceResponseMap() map[string]any {
|
||||
return map[string]any{
|
||||
"request_id": "vr-001",
|
||||
"response": "The capital of France is Paris.",
|
||||
"audio": make([]byte, 16384),
|
||||
"transcription": "What is the capital of France?",
|
||||
}
|
||||
}
|
||||
|
||||
func voiceResponseStruct() VoiceResponse {
|
||||
return VoiceResponse{
|
||||
RequestID: "vr-001",
|
||||
Response: "The capital of France is Paris.",
|
||||
Audio: make([]byte, 16384),
|
||||
Transcription: "What is the capital of France?",
|
||||
}
|
||||
}
|
||||
|
||||
func voiceResponseProto() *pb.VoiceResponse {
|
||||
return &pb.VoiceResponse{
|
||||
RequestId: "vr-001",
|
||||
Response: "The capital of France is Paris.",
|
||||
Audio: make([]byte, 16384),
|
||||
Transcription: "What is the capital of France?",
|
||||
}
|
||||
return &pb.VoiceResponse{
|
||||
RequestId: "vr-001",
|
||||
Response: "The capital of France is Paris.",
|
||||
Audio: make([]byte, 16384),
|
||||
Transcription: "What is the capital of France?",
|
||||
}
|
||||
|
||||
// ttsChunkMap simulates a streaming audio chunk (~32 KB).
|
||||
func ttsChunkMap() map[string]any {
|
||||
return map[string]any{
|
||||
"session_id": "tts-sess-99",
|
||||
"chunk_index": 3,
|
||||
"total_chunks": 12,
|
||||
"audio_b64": string(make([]byte, 32768)), // old: base64 string
|
||||
"is_last": false,
|
||||
"timestamp": time.Now().Unix(),
|
||||
"sample_rate": 24000,
|
||||
}
|
||||
}
|
||||
|
||||
func ttsChunkStruct() TTSAudioChunk {
|
||||
return TTSAudioChunk{
|
||||
SessionID: "tts-sess-99",
|
||||
ChunkIndex: 3,
|
||||
TotalChunks: 12,
|
||||
Audio: make([]byte, 32768), // new: raw bytes
|
||||
IsLast: false,
|
||||
Timestamp: time.Now().Unix(),
|
||||
SampleRate: 24000,
|
||||
}
|
||||
}
|
||||
|
||||
func ttsChunkProto() *pb.TTSAudioChunk {
|
||||
return &pb.TTSAudioChunk{
|
||||
SessionId: "tts-sess-99",
|
||||
ChunkIndex: 3,
|
||||
TotalChunks: 12,
|
||||
Audio: make([]byte, 32768),
|
||||
IsLast: false,
|
||||
Timestamp: time.Now().Unix(),
|
||||
SampleRate: 24000,
|
||||
}
|
||||
return &pb.TTSAudioChunk{
|
||||
SessionId: "tts-sess-99",
|
||||
ChunkIndex: 3,
|
||||
TotalChunks: 12,
|
||||
Audio: make([]byte, 32768),
|
||||
IsLast: false,
|
||||
Timestamp: time.Now().Unix(),
|
||||
SampleRate: 24000,
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
@@ -145,371 +60,253 @@ func ttsChunkProto() *pb.TTSAudioChunk {
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestWireSize(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mapData any
|
||||
structVal any
|
||||
protoMsg proto.Message
|
||||
}{
|
||||
{"ChatRequest", chatRequestMap(), chatRequestStruct(), chatRequestProto()},
|
||||
{"VoiceResponse", voiceResponseMap(), voiceResponseStruct(), voiceResponseProto()},
|
||||
{"TTSAudioChunk", ttsChunkMap(), ttsChunkStruct(), ttsChunkProto()},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
protoMsg proto.Message
|
||||
}{
|
||||
{"ChatRequest", chatRequestProto()},
|
||||
{"VoiceResponse", voiceResponseProto()},
|
||||
{"TTSAudioChunk", ttsChunkProto()},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
mapBytes, _ := msgpack.Marshal(tt.mapData)
|
||||
structBytes, _ := msgpack.Marshal(tt.structVal)
|
||||
protoBytes, _ := proto.Marshal(tt.protoMsg)
|
||||
|
||||
t.Logf("%-16s map=%5d B struct=%5d B proto=%5d B (struct saves %.0f%%, proto saves %.0f%%)",
|
||||
tt.name,
|
||||
len(mapBytes), len(structBytes), len(protoBytes),
|
||||
100*(1-float64(len(structBytes))/float64(len(mapBytes))),
|
||||
100*(1-float64(len(protoBytes))/float64(len(mapBytes))),
|
||||
)
|
||||
}
|
||||
for _, tt := range tests {
|
||||
protoBytes, _ := proto.Marshal(tt.protoMsg)
|
||||
t.Logf("%-16s proto=%5d B", tt.name, len(protoBytes))
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Encode benchmarks
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func BenchmarkEncode_ChatRequest_MsgpackMap(b *testing.B) {
|
||||
data := chatRequestMap()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = msgpack.Marshal(data)
|
||||
}
|
||||
func BenchmarkEncode_ChatRequest(b *testing.B) {
|
||||
data := chatRequestProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = proto.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_ChatRequest_MsgpackStruct(b *testing.B) {
|
||||
data := chatRequestStruct()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = msgpack.Marshal(data)
|
||||
}
|
||||
func BenchmarkEncode_VoiceResponse(b *testing.B) {
|
||||
data := voiceResponseProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = proto.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_ChatRequest_Protobuf(b *testing.B) {
|
||||
data := chatRequestProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = proto.Marshal(data)
|
||||
}
|
||||
func BenchmarkEncode_TTSChunk(b *testing.B) {
|
||||
data := ttsChunkProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = proto.Marshal(data)
|
||||
}
|
||||
|
||||
func BenchmarkEncode_VoiceResponse_MsgpackMap(b *testing.B) {
|
||||
data := voiceResponseMap()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = msgpack.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_VoiceResponse_MsgpackStruct(b *testing.B) {
|
||||
data := voiceResponseStruct()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = msgpack.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_VoiceResponse_Protobuf(b *testing.B) {
|
||||
data := voiceResponseProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = proto.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_TTSChunk_MsgpackMap(b *testing.B) {
|
||||
data := ttsChunkMap()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = msgpack.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_TTSChunk_MsgpackStruct(b *testing.B) {
|
||||
data := ttsChunkStruct()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = msgpack.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode_TTSChunk_Protobuf(b *testing.B) {
|
||||
data := ttsChunkProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_, _ = proto.Marshal(data)
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Decode benchmarks
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func BenchmarkDecode_ChatRequest_MsgpackMap(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(chatRequestMap())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m map[string]any
|
||||
_ = msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
func BenchmarkDecode_ChatRequest(b *testing.B) {
|
||||
encoded, _ := proto.Marshal(chatRequestProto())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m pb.ChatRequest
|
||||
_ = proto.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_ChatRequest_MsgpackStruct(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(chatRequestStruct())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m ChatRequest
|
||||
_ = msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
func BenchmarkDecode_VoiceResponse(b *testing.B) {
|
||||
encoded, _ := proto.Marshal(voiceResponseProto())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m pb.VoiceResponse
|
||||
_ = proto.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_ChatRequest_Protobuf(b *testing.B) {
|
||||
encoded, _ := proto.Marshal(chatRequestProto())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m pb.ChatRequest
|
||||
_ = proto.Unmarshal(encoded, &m)
|
||||
}
|
||||
func BenchmarkDecode_TTSChunk(b *testing.B) {
|
||||
encoded, _ := proto.Marshal(ttsChunkProto())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m pb.TTSAudioChunk
|
||||
_ = proto.Unmarshal(encoded, &m)
|
||||
}
|
||||
|
||||
func BenchmarkDecode_VoiceResponse_MsgpackMap(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(voiceResponseMap())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m map[string]any
|
||||
_ = msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_VoiceResponse_MsgpackStruct(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(voiceResponseStruct())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m VoiceResponse
|
||||
_ = msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_VoiceResponse_Protobuf(b *testing.B) {
|
||||
encoded, _ := proto.Marshal(voiceResponseProto())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m pb.VoiceResponse
|
||||
_ = proto.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_TTSChunk_MsgpackMap(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(ttsChunkMap())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m map[string]any
|
||||
_ = msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_TTSChunk_MsgpackStruct(b *testing.B) {
|
||||
encoded, _ := msgpack.Marshal(ttsChunkStruct())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m TTSAudioChunk
|
||||
_ = msgpack.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode_TTSChunk_Protobuf(b *testing.B) {
|
||||
encoded, _ := proto.Marshal(ttsChunkProto())
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
var m pb.TTSAudioChunk
|
||||
_ = proto.Unmarshal(encoded, &m)
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Roundtrip benchmarks (encode + decode)
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func BenchmarkRoundtrip_ChatRequest_MsgpackMap(b *testing.B) {
|
||||
data := chatRequestMap()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
enc, _ := msgpack.Marshal(data)
|
||||
var dec map[string]any
|
||||
_ = msgpack.Unmarshal(enc, &dec)
|
||||
}
|
||||
func BenchmarkRoundtrip_ChatRequest(b *testing.B) {
|
||||
data := chatRequestProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
enc, _ := proto.Marshal(data)
|
||||
var dec pb.ChatRequest
|
||||
_ = proto.Unmarshal(enc, &dec)
|
||||
}
|
||||
|
||||
func BenchmarkRoundtrip_ChatRequest_MsgpackStruct(b *testing.B) {
|
||||
data := chatRequestStruct()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
enc, _ := msgpack.Marshal(data)
|
||||
var dec ChatRequest
|
||||
_ = msgpack.Unmarshal(enc, &dec)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRoundtrip_ChatRequest_Protobuf(b *testing.B) {
|
||||
data := chatRequestProto()
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
enc, _ := proto.Marshal(data)
|
||||
var dec pb.ChatRequest
|
||||
_ = proto.Unmarshal(enc, &dec)
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Typed struct unit tests — verify roundtrip correctness
|
||||
// Correctness tests — verify proto roundtrip
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestRoundtrip_ChatRequest(t *testing.T) {
|
||||
orig := chatRequestStruct()
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec ChatRequest
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.RequestID != orig.RequestID {
|
||||
t.Errorf("RequestID = %q, want %q", dec.RequestID, orig.RequestID)
|
||||
}
|
||||
if dec.Message != orig.Message {
|
||||
t.Errorf("Message = %q, want %q", dec.Message, orig.Message)
|
||||
}
|
||||
if dec.TopK != orig.TopK {
|
||||
t.Errorf("TopK = %d, want %d", dec.TopK, orig.TopK)
|
||||
}
|
||||
if dec.Premium != orig.Premium {
|
||||
t.Errorf("Premium = %v, want %v", dec.Premium, orig.Premium)
|
||||
}
|
||||
if dec.EffectiveQuery() != orig.Message {
|
||||
t.Errorf("EffectiveQuery() = %q, want %q", dec.EffectiveQuery(), orig.Message)
|
||||
}
|
||||
orig := chatRequestProto()
|
||||
data, err := proto.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec pb.ChatRequest
|
||||
if err := proto.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.GetRequestId() != orig.GetRequestId() {
|
||||
t.Errorf("RequestId = %q, want %q", dec.GetRequestId(), orig.GetRequestId())
|
||||
}
|
||||
if dec.GetMessage() != orig.GetMessage() {
|
||||
t.Errorf("Message = %q, want %q", dec.GetMessage(), orig.GetMessage())
|
||||
}
|
||||
if dec.GetTopK() != orig.GetTopK() {
|
||||
t.Errorf("TopK = %d, want %d", dec.GetTopK(), orig.GetTopK())
|
||||
}
|
||||
if dec.GetPremium() != orig.GetPremium() {
|
||||
t.Errorf("Premium = %v, want %v", dec.GetPremium(), orig.GetPremium())
|
||||
}
|
||||
if EffectiveQuery(&dec) != orig.GetMessage() {
|
||||
t.Errorf("EffectiveQuery() = %q, want %q", EffectiveQuery(&dec), orig.GetMessage())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip_VoiceResponse(t *testing.T) {
|
||||
orig := voiceResponseStruct()
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec VoiceResponse
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.RequestID != orig.RequestID {
|
||||
t.Errorf("RequestID mismatch")
|
||||
}
|
||||
if len(dec.Audio) != len(orig.Audio) {
|
||||
t.Errorf("Audio len = %d, want %d", len(dec.Audio), len(orig.Audio))
|
||||
}
|
||||
if dec.Transcription != orig.Transcription {
|
||||
t.Errorf("Transcription mismatch")
|
||||
}
|
||||
orig := voiceResponseProto()
|
||||
data, err := proto.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec pb.VoiceResponse
|
||||
if err := proto.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.GetRequestId() != orig.GetRequestId() {
|
||||
t.Errorf("RequestId mismatch")
|
||||
}
|
||||
if len(dec.GetAudio()) != len(orig.GetAudio()) {
|
||||
t.Errorf("Audio len = %d, want %d", len(dec.GetAudio()), len(orig.GetAudio()))
|
||||
}
|
||||
if dec.GetTranscription() != orig.GetTranscription() {
|
||||
t.Errorf("Transcription mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip_TTSAudioChunk(t *testing.T) {
|
||||
orig := ttsChunkStruct()
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec TTSAudioChunk
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.SessionID != orig.SessionID {
|
||||
t.Errorf("SessionID mismatch")
|
||||
}
|
||||
if dec.ChunkIndex != orig.ChunkIndex {
|
||||
t.Errorf("ChunkIndex = %d, want %d", dec.ChunkIndex, orig.ChunkIndex)
|
||||
}
|
||||
if len(dec.Audio) != len(orig.Audio) {
|
||||
t.Errorf("Audio len = %d, want %d", len(dec.Audio), len(orig.Audio))
|
||||
}
|
||||
if dec.SampleRate != orig.SampleRate {
|
||||
t.Errorf("SampleRate = %d, want %d", dec.SampleRate, orig.SampleRate)
|
||||
}
|
||||
orig := ttsChunkProto()
|
||||
data, err := proto.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec pb.TTSAudioChunk
|
||||
if err := proto.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.GetSessionId() != orig.GetSessionId() {
|
||||
t.Errorf("SessionId mismatch")
|
||||
}
|
||||
if dec.GetChunkIndex() != orig.GetChunkIndex() {
|
||||
t.Errorf("ChunkIndex = %d, want %d", dec.GetChunkIndex(), orig.GetChunkIndex())
|
||||
}
|
||||
if len(dec.GetAudio()) != len(orig.GetAudio()) {
|
||||
t.Errorf("Audio len = %d, want %d", len(dec.GetAudio()), len(orig.GetAudio()))
|
||||
}
|
||||
if dec.GetSampleRate() != orig.GetSampleRate() {
|
||||
t.Errorf("SampleRate = %d, want %d", dec.GetSampleRate(), orig.GetSampleRate())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip_PipelineTrigger(t *testing.T) {
|
||||
orig := PipelineTrigger{
|
||||
RequestID: "pip-001",
|
||||
Pipeline: "document-ingestion",
|
||||
Parameters: map[string]any{"source": "s3://bucket/data"},
|
||||
}
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec PipelineTrigger
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.Pipeline != orig.Pipeline {
|
||||
t.Errorf("Pipeline = %q, want %q", dec.Pipeline, orig.Pipeline)
|
||||
}
|
||||
if dec.Parameters["source"] != orig.Parameters["source"] {
|
||||
t.Errorf("Parameters[source] mismatch")
|
||||
}
|
||||
orig := &pb.PipelineTrigger{
|
||||
RequestId: "pip-001",
|
||||
Pipeline: "document-ingestion",
|
||||
Parameters: map[string]string{"source": "s3://bucket/data"},
|
||||
}
|
||||
data, err := proto.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec pb.PipelineTrigger
|
||||
if err := proto.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.GetPipeline() != orig.GetPipeline() {
|
||||
t.Errorf("Pipeline = %q, want %q", dec.GetPipeline(), orig.GetPipeline())
|
||||
}
|
||||
if dec.GetParameters()["source"] != orig.GetParameters()["source"] {
|
||||
t.Errorf("Parameters[source] mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip_STTTranscription(t *testing.T) {
|
||||
orig := STTTranscription{
|
||||
SessionID: "stt-001",
|
||||
Transcript: "hello world",
|
||||
Sequence: 5,
|
||||
IsPartial: false,
|
||||
IsFinal: true,
|
||||
Timestamp: time.Now().Unix(),
|
||||
SpeakerID: "speaker-1",
|
||||
HasVoiceActivity: true,
|
||||
State: "listening",
|
||||
}
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec STTTranscription
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.Transcript != orig.Transcript {
|
||||
t.Errorf("Transcript = %q, want %q", dec.Transcript, orig.Transcript)
|
||||
}
|
||||
if dec.IsFinal != orig.IsFinal {
|
||||
t.Error("IsFinal mismatch")
|
||||
}
|
||||
orig := &pb.STTTranscription{
|
||||
SessionId: "stt-001",
|
||||
Transcript: "hello world",
|
||||
Sequence: 5,
|
||||
IsPartial: false,
|
||||
IsFinal: true,
|
||||
Timestamp: time.Now().Unix(),
|
||||
SpeakerId: "speaker-1",
|
||||
HasVoiceActivity: true,
|
||||
State: "listening",
|
||||
}
|
||||
data, err := proto.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec pb.STTTranscription
|
||||
if err := proto.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dec.GetTranscript() != orig.GetTranscript() {
|
||||
t.Errorf("Transcript = %q, want %q", dec.GetTranscript(), orig.GetTranscript())
|
||||
}
|
||||
if dec.GetIsFinal() != orig.GetIsFinal() {
|
||||
t.Error("IsFinal mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundtrip_ErrorResponse(t *testing.T) {
|
||||
orig := ErrorResponse{Error: true, Message: "something broke", Type: "InternalError"}
|
||||
data, err := msgpack.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec ErrorResponse
|
||||
if err := msgpack.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !dec.Error || dec.Message != "something broke" || dec.Type != "InternalError" {
|
||||
t.Errorf("ErrorResponse roundtrip mismatch: %+v", dec)
|
||||
}
|
||||
orig := &pb.ErrorResponse{Error: true, Message: "something broke", Type: "InternalError"}
|
||||
data, err := proto.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var dec pb.ErrorResponse
|
||||
if err := proto.Unmarshal(data, &dec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !dec.GetError() || dec.GetMessage() != "something broke" || dec.GetType() != "InternalError" {
|
||||
t.Errorf("ErrorResponse roundtrip mismatch: %+v", &dec)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectiveQuery_MessageSet(t *testing.T) {
|
||||
req := &pb.ChatRequest{Message: "hello", Query: "world"}
|
||||
if got := EffectiveQuery(req); got != "hello" {
|
||||
t.Errorf("EffectiveQuery() = %q, want %q", got, "hello")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectiveQuery_FallbackToQuery(t *testing.T) {
|
||||
req := &pb.ChatRequest{Query: "world"}
|
||||
if got := EffectiveQuery(req); got != "world" {
|
||||
t.Errorf("EffectiveQuery() = %q, want %q", got, "world")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestamp(t *testing.T) {
|
||||
ts := Timestamp()
|
||||
now := time.Now().Unix()
|
||||
if ts < now-1 || ts > now+1 {
|
||||
t.Errorf("Timestamp() = %d, expected ~%d", ts, now)
|
||||
}
|
||||
ts := Timestamp()
|
||||
now := time.Now().Unix()
|
||||
if ts < now-1 || ts > now+1 {
|
||||
t.Errorf("Timestamp() = %d, expected ~%d", ts, now)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user