Files
remoteconn-gitea/apps/gateway/src/voice/volcAsrProtocol.test.ts
2026-03-21 18:57:10 +08:00

104 lines
3.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { gzipSync } from "node:zlib";
import {
VolcCompression,
VolcMessageFlags,
VolcMessageType,
VolcSerialization,
buildAudioOnlyRequestFrame,
buildVolcHeader,
isFinalServerResponse,
parseVolcServerFrame
} from "./volcAsrProtocol";
describe("volcAsrProtocol", () => {
it("audio-only 最后一包应携带 LAST_PACKAGE flag", () => {
const frame = buildAudioOnlyRequestFrame(Buffer.from([1, 2, 3, 4]), true);
const flag = (frame[1] ?? 0) & 0x0f;
expect(flag).toBe(VolcMessageFlags.LAST_PACKAGE);
});
it("可解析 gzip + json 的 full server response", () => {
const payloadObj = {
result: {
text: "测试文本"
}
};
const payload = gzipSync(Buffer.from(JSON.stringify(payloadObj), "utf8"));
const header = buildVolcHeader({
messageType: VolcMessageType.FULL_SERVER_RESPONSE,
flags: VolcMessageFlags.POSITIVE_SEQUENCE,
serialization: VolcSerialization.JSON,
compression: VolcCompression.GZIP
});
const sequence = Buffer.alloc(4);
sequence.writeInt32BE(2, 0);
const payloadSize = Buffer.alloc(4);
payloadSize.writeUInt32BE(payload.length, 0);
const parsed = parseVolcServerFrame(Buffer.concat([header, sequence, payloadSize, payload]));
expect(parsed.kind).toBe("server_response");
if (parsed.kind !== "server_response") {
return;
}
expect(parsed.sequence).toBe(2);
expect((parsed.payload as { result: { text: string } }).result.text).toBe("测试文本");
});
it("flags=LAST_PACKAGE 且包含 sequence 时可兼容解析", () => {
const payloadObj = {
result: {
text: "结束包"
}
};
const payload = gzipSync(Buffer.from(JSON.stringify(payloadObj), "utf8"));
const header = buildVolcHeader({
messageType: VolcMessageType.FULL_SERVER_RESPONSE,
flags: VolcMessageFlags.LAST_PACKAGE,
serialization: VolcSerialization.JSON,
compression: VolcCompression.GZIP
});
const sequence = Buffer.alloc(4);
sequence.writeInt32BE(7, 0);
const payloadSize = Buffer.alloc(4);
payloadSize.writeUInt32BE(payload.length, 0);
const parsed = parseVolcServerFrame(Buffer.concat([header, sequence, payloadSize, payload]));
expect(parsed.kind).toBe("server_response");
if (parsed.kind !== "server_response") {
return;
}
expect(parsed.sequence).toBe(7);
expect((parsed.payload as { result: { text: string } }).result.text).toBe("结束包");
});
it("可解析 error response", () => {
const errorPayload = Buffer.from("invalid request", "utf8");
const header = buildVolcHeader({
messageType: VolcMessageType.ERROR_RESPONSE,
flags: VolcMessageFlags.NONE,
serialization: VolcSerialization.NONE,
compression: VolcCompression.NONE
});
const errorCode = Buffer.alloc(4);
errorCode.writeUInt32BE(45000001, 0);
const payloadSize = Buffer.alloc(4);
payloadSize.writeUInt32BE(errorPayload.length, 0);
const parsed = parseVolcServerFrame(Buffer.concat([header, errorCode, payloadSize, errorPayload]));
expect(parsed.kind).toBe("error");
if (parsed.kind !== "error") {
return;
}
expect(parsed.errorCode).toBe(45000001);
expect(Buffer.isBuffer(parsed.payload)).toBe(true);
expect((parsed.payload as Buffer).toString("utf8")).toBe("invalid request");
});
it("final flag 判定覆盖 LAST_PACKAGE 与 NEGATIVE_SEQUENCE", () => {
expect(isFinalServerResponse(VolcMessageFlags.LAST_PACKAGE)).toBe(true);
expect(isFinalServerResponse(VolcMessageFlags.NEGATIVE_SEQUENCE)).toBe(true);
expect(isFinalServerResponse(VolcMessageFlags.POSITIVE_SEQUENCE)).toBe(false);
});
});