package voice import ( "bytes" "encoding/binary" "testing" ) // serverFrame 按参考实现 parse_response 的布局手搓一个服务端帧,供解析测试。 // 布局:header(4) [+event4] + sessionIdLen(4) + sessionId + payloadLen(4) + payload。 func serverFrame(msgType, flags, serial, comp byte, event int32, sid string, payload []byte) []byte { var b bytes.Buffer b.WriteByte((0b0001 << 4) | 0b0001) b.WriteByte((msgType << 4) | flags) b.WriteByte((serial << 4) | comp) b.WriteByte(0x00) if flags&ttsFlagWithEvent != 0 { _ = binary.Write(&b, binary.BigEndian, event) } _ = binary.Write(&b, binary.BigEndian, uint32(len(sid))) b.WriteString(sid) _ = binary.Write(&b, binary.BigEndian, uint32(len(payload))) b.Write(payload) return b.Bytes() } // 音频响应(SERVER_ACK + 事件 352 + raw payload)应被识别为音频、取到原始 PCM。 func TestParseTTSFrame_Audio(t *testing.T) { pcm := []byte{0x01, 0x02, 0x03, 0x04, 0xff, 0xfe} frame := serverFrame(ttsServerAck, ttsFlagWithEvent, ttsSerialNone, ttsCompNone, evTTSResponse, "sess-1", pcm) r, err := parseTTSFrame(frame) if err != nil { t.Fatalf("解析音频帧失败: %v", err) } if !r.IsAudio { t.Errorf("应识别为音频帧") } if r.Event != evTTSResponse { t.Errorf("event=%d,期望 %d", r.Event, evTTSResponse) } if !bytes.Equal(r.Payload, pcm) { t.Errorf("PCM 不一致:得 %v,期望 %v", r.Payload, pcm) } } // gzip 压缩的 JSON 事件帧(如 SessionStarted)应被正确解压。 func TestParseTTSFrame_GzipJSON(t *testing.T) { js := []byte(`{"event":"SessionStarted"}`) frame := serverFrame(ttsServerFull, ttsFlagWithEvent, ttsSerialJSON, ttsCompGzip, evSessionStarted, "sess-1", gzipBytes(js)) r, err := parseTTSFrame(frame) if err != nil { t.Fatalf("解析 JSON 帧失败: %v", err) } if r.Event != evSessionStarted { t.Errorf("event=%d,期望 %d", r.Event, evSessionStarted) } if !bytes.Equal(r.Payload, js) { t.Errorf("gzip 解压后 JSON 不一致:得 %s", string(r.Payload)) } } // 错误帧(SERVER_ERROR):code(4) + payloadLen(4) + payload,无 event/session。 func TestParseTTSFrame_Error(t *testing.T) { var b bytes.Buffer b.WriteByte((0b0001 << 4) | 0b0001) b.WriteByte((ttsServerErr << 4) | ttsFlagNone) b.WriteByte((ttsSerialJSON << 4) | ttsCompNone) b.WriteByte(0x00) _ = binary.Write(&b, binary.BigEndian, uint32(45000001)) // code msg := []byte(`{"error":"quota"}`) _ = binary.Write(&b, binary.BigEndian, uint32(len(msg))) b.Write(msg) r, err := parseTTSFrame(b.Bytes()) if err != nil { t.Fatalf("解析错误帧失败: %v", err) } if r.MsgType != ttsServerErr { t.Errorf("msgType=%d,期望错误类型", r.MsgType) } if r.Code != 45000001 { t.Errorf("code=%d,期望 45000001", r.Code) } if !bytes.Equal(r.Payload, msg) { t.Errorf("错误体不一致:得 %s", string(r.Payload)) } } // 客户端会话级事件帧应带上事件号与会话 ID,且 payload 为 gzip。 func TestSessionEventFrame_RoundTrip(t *testing.T) { frame := sessionEventFrame(evTaskRequest, "sess-x", []byte(`{"text":"你好"}`)) // 手工核对头两个字节 + 事件号 + 会话 ID 长度。 if frame[0] != ((0b0001<<4)|0b0001) || (frame[1]>>4) != ttsClientFull { t.Fatalf("头字节不对: %08b %08b", frame[0], frame[1]) } if frame[1]&0x0f&ttsFlagWithEvent == 0 { t.Errorf("应带 withEvent flag") } ev := int32(binary.BigEndian.Uint32(frame[4:8])) if ev != evTaskRequest { t.Errorf("event=%d,期望 %d", ev, evTaskRequest) } sidLen := binary.BigEndian.Uint32(frame[8:12]) if int(sidLen) != len("sess-x") { t.Errorf("sessionId 长度=%d,期望 %d", sidLen, len("sess-x")) } if got := string(frame[12 : 12+sidLen]); got != "sess-x" { t.Errorf("sessionId=%q", got) } }