test(39-01): add failing tests for sentence streaming and multi-lang synthesis

This commit is contained in:
Nexus Dev 2026-04-04 03:29:23 +00:00
parent cef294ad4d
commit e61f471a62

View file

@ -0,0 +1,130 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
// Mock ffmpeg-static BEFORE any imports
vi.mock("ffmpeg-static", () => ({ default: "/mock/ffmpeg" }));
// Mock child_process
vi.mock("node:child_process", () => ({
execFile: vi.fn(),
spawn: vi.fn(),
}));
// Mock fs/promises for temp file operations
vi.mock("node:fs/promises", () => ({
writeFile: vi.fn().mockResolvedValue(undefined),
unlink: vi.fn().mockResolvedValue(undefined),
}));
import { splitSentences, voicePipelineService } from "../services/voice-pipeline.js";
import { execFile as execFileCb } from "node:child_process";
const execFileMock = vi.mocked(execFileCb);
describe("sentence streaming", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("splitSentences", () => {
it("splits basic sentences on period, question mark, exclamation", () => {
const result = splitSentences("Hello world. How are you? I am fine.");
expect(result).toEqual(["Hello world.", "How are you?", "I am fine."]);
});
it("keeps abbreviation-style dots intact (Dr., D.C.)", () => {
const result = splitSentences("Dr. Smith went to D.C. He liked it.");
// Should split only at sentence boundaries, not abbreviations
// The sentence "Dr. Smith went to D.C." should be one unit
expect(result).toHaveLength(2);
expect(result[0]).toContain("Dr. Smith");
expect(result[1]).toBe("He liked it.");
});
it("returns single-sentence array when text has no sentence boundaries", () => {
const result = splitSentences("Hello world");
expect(result).toEqual(["Hello world"]);
});
it("filters empty strings", () => {
const result = splitSentences("Hello. World.");
expect(result.every((s) => s.length > 0)).toBe(true);
});
});
describe("synthesizeSentenceStream", () => {
it("yields one chunk per sentence", async () => {
execFileMock.mockImplementation((_cmd: any, _args: any, _opts: any, callback: any) => {
callback(null, Buffer.from("wav-chunk"), "");
return {} as any;
});
const svc = voicePipelineService();
const text = "Hello world. How are you? I am fine.";
const chunks: Array<{ index: number; total: number; audio: Buffer }> = [];
for await (const chunk of svc.synthesizeSentenceStream(text)) {
chunks.push(chunk);
}
expect(chunks).toHaveLength(3);
expect(chunks[0]).toMatchObject({ index: 0, total: 3 });
expect(chunks[1]).toMatchObject({ index: 1, total: 3 });
expect(chunks[2]).toMatchObject({ index: 2, total: 3 });
chunks.forEach((c) => expect(Buffer.isBuffer(c.audio)).toBe(true));
});
it("yields single chunk for single-sentence text", async () => {
execFileMock.mockImplementation((_cmd: any, _args: any, _opts: any, callback: any) => {
callback(null, Buffer.from("wav-data"), "");
return {} as any;
});
const svc = voicePipelineService();
const chunks: Array<{ index: number; total: number; audio: Buffer }> = [];
for await (const chunk of svc.synthesizeSentenceStream("Hello world.")) {
chunks.push(chunk);
}
expect(chunks).toHaveLength(1);
expect(chunks[0]).toMatchObject({ index: 0, total: 1 });
});
});
describe("synthesizeMultiLang", () => {
it("returns a Map with one entry per voiceId", async () => {
execFileMock.mockImplementation((_cmd: any, _args: any, _opts: any, callback: any) => {
callback(null, Buffer.from("audio-data"), "");
return {} as any;
});
const svc = voicePipelineService();
const text = "Hello.";
const voiceIds = ["en_US-lessac-medium", "da_DK-talesyntese-medium"];
const result = await svc.synthesizeMultiLang(text, voiceIds);
expect(result).toBeInstanceOf(Map);
expect(result.size).toBe(2);
expect(result.has("en_US-lessac-medium")).toBe(true);
expect(result.has("da_DK-talesyntese-medium")).toBe(true);
voiceIds.forEach((v) => expect(Buffer.isBuffer(result.get(v))).toBe(true));
});
it("calls piper in parallel (Promise.all semantics)", async () => {
const callOrder: string[] = [];
execFileMock.mockImplementation((_cmd: any, args: any, _opts: any, callback: any) => {
const voiceId = args[1]; // second arg after '--model'
callOrder.push(voiceId);
callback(null, Buffer.from("audio"), "");
return {} as any;
});
const svc = voicePipelineService();
await svc.synthesizeMultiLang("Test.", ["voice-a", "voice-b"]);
// Both voices should have been requested
expect(callOrder).toContain("voice-a");
expect(callOrder).toContain("voice-b");
});
});
});