



























@@ -124,6 +124,8 @@ function expectMusicGenerateTool(
124124function resetMusicGenerateMocks() {
125125vi.restoreAllMocks();
126126vi.spyOn(musicGenerationRuntime, "listRuntimeMusicGenerationProviders").mockReturnValue([]);
127+musicGenerationRuntimeMocks.generateMusic.mockReset();
128+mediaStoreMocks.saveMediaBuffer.mockReset();
127129taskRuntimeInternalMocks.listTasksForOwnerKey.mockReset();
128130taskRuntimeInternalMocks.listTasksForOwnerKey.mockReturnValue([]);
129131taskExecutorMocks.createRunningTaskRun.mockReset();
@@ -132,6 +134,43 @@ function resetMusicGenerateMocks() {
132134taskExecutorMocks.recordTaskRunProgressByRunId.mockReset();
133135}
134136137+function detailsOf(result: { details?: unknown }): Record<string, unknown> {
138+expect(typeof result.details).toBe("object");
139+expect(result.details).not.toBeNull();
140+return result.details as Record<string, unknown>;
141+}
142+143+function generateMusicOptions(
144+callIndex = musicGenerationRuntimeMocks.generateMusic.mock.calls.length - 1,
145+): Record<string, unknown> {
146+const options = musicGenerationRuntimeMocks.generateMusic.mock.calls[callIndex]?.[0];
147+expect(typeof options).toBe("object");
148+expect(options).not.toBeNull();
149+return options as Record<string, unknown>;
150+}
151+152+function taskProgressCall(callIndex = 0): Record<string, unknown> {
153+const call = taskExecutorMocks.recordTaskRunProgressByRunId.mock.calls[callIndex]?.[0];
154+expect(typeof call).toBe("object");
155+expect(call).not.toBeNull();
156+return call as Record<string, unknown>;
157+}
158+159+function taskCompleteCall(callIndex = 0): Record<string, unknown> {
160+const call = taskExecutorMocks.completeTaskRunByRunId.mock.calls[callIndex]?.[0];
161+expect(typeof call).toBe("object");
162+expect(call).not.toBeNull();
163+return call as Record<string, unknown>;
164+}
165+166+function wakeCompletionCall(callIndex = 0): Record<string, unknown> {
167+const call =
168+musicGenerateBackgroundMocks.wakeMusicGenerationTaskCompletion.mock.calls[callIndex]?.[0];
169+expect(typeof call).toBe("object");
170+expect(call).not.toBeNull();
171+return call as Record<string, unknown>;
172+}
173+135174describe("createMusicGenerateTool", () => {
136175beforeEach(resetMusicGenerateMocks);
137176@@ -222,24 +261,16 @@ describe("createMusicGenerateTool", () => {
222261throw new Error("expected music_generate tool");
223262}
224263225-await expect(
226-tool.execute("call-1", {
227-prompt: "night-drive synthwave",
228-instrumental: true,
229-}),
230-).resolves.toMatchObject({
231-details: {
232-instrumental: true,
233-provider: "google",
234-paths: ["/tmp/generated-night-drive.mp3"],
235-},
264+const result = await tool.execute("call-1", {
265+prompt: "night-drive synthwave",
266+instrumental: true,
236267});
268+const details = detailsOf(result);
269+expect(details.instrumental).toBe(true);
270+expect(details.provider).toBe("google");
271+expect(details.paths).toEqual(["/tmp/generated-night-drive.mp3"]);
237272expect(listProviders).not.toHaveBeenCalled();
238-expect(musicGenerationRuntime.generateMusic).toHaveBeenCalledWith(
239-expect.objectContaining({
240-autoProviderFallback: false,
241-}),
242-);
273+expect(generateMusicOptions().autoProviderFallback).toBe(false);
243274});
244275245276it("generates tracks, saves them, and emits MEDIA paths without a session-backed detach", async () => {
@@ -308,18 +339,17 @@ describe("createMusicGenerateTool", () => {
308339expect(text).toContain("Generated 1 track with google/lyria-3-clip-preview.");
309340expect(text).toContain("Lyrics returned.");
310341expect(text).toContain("MEDIA:/tmp/generated-night-drive.mp3");
311-expect(result.details).toMatchObject({
312-provider: "google",
313-model: "lyria-3-clip-preview",
314-count: 1,
315-instrumental: true,
316-lyrics: ["wake the city up"],
317-media: {
318-mediaUrls: ["/tmp/generated-night-drive.mp3"],
319-},
320-paths: ["/tmp/generated-night-drive.mp3"],
321-metadata: { taskId: "music-task-1" },
322-});
342+const details = detailsOf(result);
343+expect(details.provider).toBe("google");
344+expect(details.model).toBe("lyria-3-clip-preview");
345+expect(details.count).toBe(1);
346+expect(details.instrumental).toBe(true);
347+expect(details.lyrics).toEqual(["wake the city up"]);
348+expect((details.media as { mediaUrls?: unknown }).mediaUrls).toEqual([
349+"/tmp/generated-night-drive.mp3",
350+]);
351+expect(details.paths).toEqual(["/tmp/generated-night-drive.mp3"]);
352+expect(details.metadata).toEqual({ taskId: "music-task-1" });
323353expect(taskExecutorMocks.createRunningTaskRun).not.toHaveBeenCalled();
324354expect(taskExecutorMocks.completeTaskRunByRunId).not.toHaveBeenCalled();
325355});
@@ -364,21 +394,17 @@ describe("createMusicGenerateTool", () => {
364394});
365395const text = (result.content?.[0] as { text: string } | undefined)?.text ?? "";
366396367-expect(generateSpy).toHaveBeenCalledWith(
368-expect.objectContaining({
369-autoProviderFallback: false,
370-timeoutMs: 10_000,
371-}),
372-);
397+expect(generateSpy).toHaveBeenCalledTimes(1);
398+expect(generateMusicOptions().autoProviderFallback).toBe(false);
399+expect(generateMusicOptions().timeoutMs).toBe(10_000);
373400expect(text).toContain("Timeout normalized: requested 1000ms; used 10000ms.");
374-expect(result.details).toMatchObject({
375-timeoutMs: 10_000,
376-requestedTimeoutMs: 1000,
377-timeoutNormalization: {
378-requested: 1000,
379-applied: 10_000,
380-minimum: 10_000,
381-},
401+const details = detailsOf(result);
402+expect(details.timeoutMs).toBe(10_000);
403+expect(details.requestedTimeoutMs).toBe(1000);
404+expect(details.timeoutNormalization).toEqual({
405+requested: 1000,
406+applied: 10_000,
407+minimum: 10_000,
382408});
383409});
384410@@ -451,51 +477,33 @@ describe("createMusicGenerateTool", () => {
451477expect(text).toContain("Background task started for music generation (task-123).");
452478expect(text).toContain("Do not call music_generate again for this request.");
453479expect(text).toContain("Timeout normalized: requested 1000ms; used 10000ms.");
454-expect(result.details).toMatchObject({
455-async: true,
456-status: "started",
457-task: {
458-taskId: "task-123",
459-},
460-instrumental: true,
461-timeoutMs: 10_000,
462-requestedTimeoutMs: 1000,
463-timeoutNormalization: {
464-requested: 1000,
465-applied: 10_000,
466-minimum: 10_000,
467-},
480+const details = detailsOf(result);
481+expect(details.async).toBe(true);
482+expect(details.status).toBe("started");
483+expect((details.task as { taskId?: unknown }).taskId).toBe("task-123");
484+expect(details.instrumental).toBe(true);
485+expect(details.timeoutMs).toBe(10_000);
486+expect(details.requestedTimeoutMs).toBe(1000);
487+expect(details.timeoutNormalization).toEqual({
488+requested: 1000,
489+applied: 10_000,
490+minimum: 10_000,
468491});
469492if (!scheduledWork) {
470493throw new Error("expected scheduled music generation work");
471494}
472495await scheduledWork();
473-expect(musicGenerationRuntime.generateMusic).toHaveBeenCalledWith(
474-expect.objectContaining({
475-autoProviderFallback: false,
476-timeoutMs: 10_000,
477-}),
478-);
479-expect(taskExecutorMocks.recordTaskRunProgressByRunId).toHaveBeenCalledWith(
480-expect.objectContaining({
481-runId: expect.stringMatching(/^tool:music_generate:/),
482-progressSummary: "Generating music",
483-}),
484-);
485-expect(taskExecutorMocks.completeTaskRunByRunId).toHaveBeenCalledWith(
486-expect.objectContaining({
487-runId: expect.stringMatching(/^tool:music_generate:/),
488-}),
489-);
490-expect(wakeSpy).toHaveBeenCalledWith(
491-expect.objectContaining({
492-handle: expect.objectContaining({
493-taskId: "task-123",
494-}),
495-status: "ok",
496-result: expect.stringContaining("MEDIA:/tmp/generated-night-drive.mp3"),
497-}),
498-);
496+expect(generateMusicOptions().autoProviderFallback).toBe(false);
497+expect(generateMusicOptions().timeoutMs).toBe(10_000);
498+const progress = taskProgressCall();
499+expect(String(progress.runId)).toMatch(/^tool:music_generate:/);
500+expect(progress.progressSummary).toBe("Generating music");
501+expect(String(taskCompleteCall().runId)).toMatch(/^tool:music_generate:/);
502+expect(wakeSpy).toHaveBeenCalledTimes(1);
503+const wake = wakeCompletionCall();
504+expect((wake.handle as { taskId?: unknown }).taskId).toBe("task-123");
505+expect(wake.status).toBe("ok");
506+expect(wake.result).toContain("MEDIA:/tmp/generated-night-drive.mp3");
499507});
500508501509it("lists provider capabilities", async () => {
@@ -608,19 +616,17 @@ describe("createMusicGenerateTool", () => {
608616expect(text).toContain(
609617"Warning: Ignored unsupported overrides for google/lyria-3-clip-preview: durationSeconds=30, format=wav.",
610618);
611-expect(result).toMatchObject({
612-details: {
613-instrumental: true,
614-warning:
615-"Ignored unsupported overrides for google/lyria-3-clip-preview: durationSeconds=30, format=wav.",
616-ignoredOverrides: [
617-{ key: "durationSeconds", value: 30 },
618-{ key: "format", value: "wav" },
619-],
620-},
621-});
622-expect(result.details).not.toHaveProperty("durationSeconds");
623-expect(result.details).not.toHaveProperty("format");
619+const details = detailsOf(result);
620+expect(details.instrumental).toBe(true);
621+expect(details.warning).toBe(
622+"Ignored unsupported overrides for google/lyria-3-clip-preview: durationSeconds=30, format=wav.",
623+);
624+expect(details.ignoredOverrides).toEqual([
625+{ key: "durationSeconds", value: 30 },
626+{ key: "format", value: "wav" },
627+]);
628+expect(details).not.toHaveProperty("durationSeconds");
629+expect(details).not.toHaveProperty("format");
624630});
625631626632it("surfaces normalized durations from runtime metadata", async () => {
@@ -674,14 +680,13 @@ describe("createMusicGenerateTool", () => {
674680const text = (result.content?.[0] as { text: string } | undefined)?.text ?? "";
675681676682expect(text).toContain("Duration normalized: requested 45s; used 30s.");
677-expect(result.details).toMatchObject({
678-durationSeconds: 30,
679-requestedDurationSeconds: 45,
680-normalization: {
681-durationSeconds: {
682-requested: 45,
683-applied: 30,
684-},
683+const details = detailsOf(result);
684+expect(details.durationSeconds).toBe(30);
685+expect(details.requestedDurationSeconds).toBe(45);
686+expect(details.normalization).toEqual({
687+durationSeconds: {
688+requested: 45,
689+applied: 30,
685690},
686691});
687692});
@@ -737,12 +742,14 @@ describe("createMusicGenerateTool", () => {
737742image: "http://198.18.0.153/reference.png",
738743});
739744740-expect(webMedia.loadWebMedia).toHaveBeenCalledWith(
741-"http://198.18.0.153/reference.png",
742-expect.objectContaining({
743-requestInit: expect.objectContaining({ signal: expect.any(AbortSignal) }),
744-ssrfPolicy: { allowRfc2544BenchmarkRange: true },
745-}),
746-);
745+expect(webMedia.loadWebMedia).toHaveBeenCalledTimes(1);
746+const loadCall = vi.mocked(webMedia.loadWebMedia).mock.calls[0];
747+expect(loadCall?.[0]).toBe("http://198.18.0.153/reference.png");
748+const loadOptions = loadCall?.[1] as {
749+requestInit?: { signal?: unknown };
750+ssrfPolicy?: unknown;
751+};
752+expect(loadOptions.requestInit?.signal).toBeInstanceOf(AbortSignal);
753+expect(loadOptions.ssrfPolicy).toEqual({ allowRfc2544BenchmarkRange: true });
747754});
748755});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。