@@ -9,11 +9,16 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
99type SpeechSynthesisTarget = SpeechSynthesisRequest [ "target" ] ;
1010
1111const runFfmpegMock = vi . hoisted ( ( ) => vi . fn < ( args : string [ ] ) => Promise < string | void > > ( ) ) ;
12+ const debugLogMock = vi . hoisted ( ( ) => vi . fn ( ) ) ;
1213
1314vi . mock ( "openclaw/plugin-sdk/media-runtime" , ( ) => ( {
1415 runFfmpeg : runFfmpegMock ,
1516} ) ) ;
1617
18+ vi . mock ( "openclaw/plugin-sdk/runtime-env" , ( ) => ( {
19+ createSubsystemLogger : ( ) => ( { debug : debugLogMock } ) ,
20+ } ) ) ;
21+
1722import { buildCliSpeechProvider } from "./speech-provider.js" ;
1823
1924const TEST_CFG = { } as OpenClawConfig ;
@@ -256,6 +261,27 @@ describe("buildCliSpeechProvider", () => {
256261 }
257262 } ) ;
258263
264+ it . each ( [ "synthesize" , "synthesizeTelephony" ] as const ) (
265+ "keeps %s debug previews free of lone surrogates" ,
266+ async ( method ) => {
267+ const text = `${ "a" . repeat ( 49 ) } 😀tail` ;
268+ const providerConfig = { command : "missing-openclaw-tts-test-command" } ;
269+ const run =
270+ method === "synthesize"
271+ ? synthesize ( { providerConfig, text } )
272+ : buildCliSpeechProvider ( ) . synthesizeTelephony ?.( {
273+ text,
274+ cfg : TEST_CFG ,
275+ providerConfig,
276+ timeoutMs : 1000 ,
277+ } ) ;
278+ await expect ( run ) . rejects . toThrow ( ) ;
279+
280+ const preview = String ( debugLogMock . mock . calls [ 0 ] ?. [ 0 ] ) ;
281+ expect ( Buffer . from ( preview ) . toString ( ) ) . toBe ( preview ) ;
282+ } ,
283+ ) ;
284+
259285 it ( "can synthesize through a real local CLI fixture and ffmpeg" , async ( ) => {
260286 if ( process . env . OPENCLAW_LIVE_TEST !== "1" ) {
261287 return ;
0 commit comments