

























@@ -147,6 +147,18 @@ function mockSavedVideoResult(fileName = "out.mp4") {
147147return generateSpy;
148148}
149149150+function resultDetails(result: { details?: unknown }): Record<string, unknown> {
151+expect(result.details).toBeDefined();
152+expect(typeof result.details).toBe("object");
153+return result.details as Record<string, unknown>;
154+}
155+156+function firstMockCallArg<T>(mock: { mock: { calls: unknown[][] } }): T {
157+const firstCall = mock.mock.calls[0];
158+expect(firstCall).toBeDefined();
159+return firstCall[0] as T;
160+}
161+150162function resetVideoGenerateMocks() {
151163vi.restoreAllMocks();
152164for (const key of VIDEO_GENERATION_PROVIDER_AUTH_ENV_VARS) {
@@ -336,16 +348,15 @@ describe("createVideoGenerateTool", () => {
336348);
337349expect(text).toContain("Generated 1 video with qwen/wan2.6-t2v.");
338350expect(text).toContain("MEDIA:/tmp/generated-lobster.mp4");
339-expect(result.details).toMatchObject({
340-provider: "qwen",
341-model: "wan2.6-t2v",
342-count: 1,
343-media: {
344-mediaUrls: ["/tmp/generated-lobster.mp4"],
345-},
346-paths: ["/tmp/generated-lobster.mp4"],
347-metadata: { taskId: "task-1" },
348-});
351+const details = resultDetails(result);
352+expect(details.provider).toBe("qwen");
353+expect(details.model).toBe("wan2.6-t2v");
354+expect(details.count).toBe(1);
355+expect((details.media as { mediaUrls?: string[] }).mediaUrls).toEqual([
356+"/tmp/generated-lobster.mp4",
357+]);
358+expect(details.paths).toEqual(["/tmp/generated-lobster.mp4"]);
359+expect(details.metadata).toEqual({ taskId: "task-1" });
349360expect(taskExecutorMocks.createRunningTaskRun).not.toHaveBeenCalled();
350361expect(taskExecutorMocks.completeTaskRunByRunId).not.toHaveBeenCalled();
351362});
@@ -431,16 +442,15 @@ describe("createVideoGenerateTool", () => {
431442expect(saveSpy).not.toHaveBeenCalled();
432443expect(text).toContain("Generated 1 video with vydra/veo3.");
433444expect(text).toContain("MEDIA:https://example.com/generated-lobster.mp4");
434-expect(result.details).toMatchObject({
435-provider: "vydra",
436-model: "veo3",
437-count: 1,
438-media: {
439-mediaUrls: ["https://example.com/generated-lobster.mp4"],
440-},
441-paths: ["https://example.com/generated-lobster.mp4"],
442-metadata: { taskId: "task-1" },
443-});
445+const details = resultDetails(result);
446+expect(details.provider).toBe("vydra");
447+expect(details.model).toBe("veo3");
448+expect(details.count).toBe(1);
449+expect((details.media as { mediaUrls?: string[] }).mediaUrls).toEqual([
450+"https://example.com/generated-lobster.mp4",
451+]);
452+expect(details.paths).toEqual(["https://example.com/generated-lobster.mp4"]);
453+expect(details.metadata).toEqual({ taskId: "task-1" });
444454});
445455446456it("falls back to the provider URL when generated video persistence exceeds the media cap", async () => {
@@ -482,15 +492,14 @@ describe("createVideoGenerateTool", () => {
482492483493expect(text).toContain("Generated 1 video with fal/fal-ai/minimax/video-01-live.");
484494expect(text).toContain("MEDIA:https://fal.run/files/generated-lobster.mp4");
485-expect(result.details).toMatchObject({
486-provider: "fal",
487-model: "fal-ai/minimax/video-01-live",
488-count: 1,
489-media: {
490-mediaUrls: ["https://fal.run/files/generated-lobster.mp4"],
491-},
492-paths: ["https://fal.run/files/generated-lobster.mp4"],
493-});
495+const details = resultDetails(result);
496+expect(details.provider).toBe("fal");
497+expect(details.model).toBe("fal-ai/minimax/video-01-live");
498+expect(details.count).toBe(1);
499+expect((details.media as { mediaUrls?: string[] }).mediaUrls).toEqual([
500+"https://fal.run/files/generated-lobster.mp4",
501+]);
502+expect(details.paths).toEqual(["https://fal.run/files/generated-lobster.mp4"]);
494503});
495504496505it("starts background generation and wakes the session with url-only MEDIA lines", async () => {
@@ -552,39 +561,34 @@ describe("createVideoGenerateTool", () => {
552561553562expect(text).toContain("Background task started for video generation (task-123).");
554563expect(text).toContain("Do not call video_generate again for this request.");
555-expect(result.details).toMatchObject({
556-async: true,
557-status: "started",
558-task: {
559-taskId: "task-123",
560-},
561-});
564+const details = resultDetails(result);
565+expect(details.async).toBe(true);
566+expect(details.status).toBe("started");
567+expect((details.task as { taskId?: string }).taskId).toBe("task-123");
562568if (!scheduledWork) {
563569throw new Error("expected scheduled video generation work");
564570}
565571await scheduledWork();
566572expect(saveSpy).not.toHaveBeenCalled();
567-expect(taskExecutorMocks.recordTaskRunProgressByRunId).toHaveBeenCalledWith(
568-expect.objectContaining({
569-runId: expect.stringMatching(/^tool:video_generate:/),
570-progressSummary: "Generating video",
571-}),
573+const progress = firstMockCallArg<{ runId: string; progressSummary: string }>(
574+taskExecutorMocks.recordTaskRunProgressByRunId,
572575);
573-expect(taskExecutorMocks.completeTaskRunByRunId).toHaveBeenCalledWith(
574-expect.objectContaining({
575-runId: expect.stringMatching(/^tool:video_generate:/),
576-}),
577-);
578-expect(wakeSpy).toHaveBeenCalledWith(
579-expect.objectContaining({
580-handle: expect.objectContaining({
581-taskId: "task-123",
582-}),
583-status: "ok",
584-mediaUrls: ["https://example.com/generated-lobster.mp4"],
585-result: expect.stringContaining("MEDIA:https://example.com/generated-lobster.mp4"),
586-}),
576+expect(progress.runId).toMatch(/^tool:video_generate:/);
577+expect(progress.progressSummary).toBe("Generating video");
578+const completion = firstMockCallArg<{ runId: string }>(
579+taskExecutorMocks.completeTaskRunByRunId,
587580);
581+expect(completion.runId).toMatch(/^tool:video_generate:/);
582+const wake = firstMockCallArg<{
583+handle: { taskId?: string };
584+status: string;
585+mediaUrls: string[];
586+result: string;
587+}>(wakeSpy);
588+expect(wake.handle.taskId).toBe("task-123");
589+expect(wake.status).toBe("ok");
590+expect(wake.mediaUrls).toEqual(["https://example.com/generated-lobster.mp4"]);
591+expect(wake.result).toContain("MEDIA:https://example.com/generated-lobster.mp4");
588592});
589593590594it("surfaces provider generation failures inline when there is no detached session", async () => {
@@ -663,17 +667,20 @@ describe("createVideoGenerateTool", () => {
663667const text = (result.content?.[0] as { text: string } | undefined)?.text ?? "";
664668665669expect(text).toContain("Duration normalized: requested 5s; used 6s.");
666-expect(result.details).toMatchObject({
667-durationSeconds: 6,
668-requestedDurationSeconds: 5,
669-supportedDurationSeconds: [4, 6, 8],
670-normalization: {
671-durationSeconds: {
672-requested: 5,
673-applied: 6,
674-supportedValues: [4, 6, 8],
675-},
676-},
670+const details = resultDetails(result);
671+expect(details.durationSeconds).toBe(6);
672+expect(details.requestedDurationSeconds).toBe(5);
673+expect(details.supportedDurationSeconds).toEqual([4, 6, 8]);
674+expect(
675+(
676+details.normalization as {
677+durationSeconds?: { requested?: number; applied?: number; supportedValues?: number[] };
678+}
679+).durationSeconds,
680+).toEqual({
681+requested: 5,
682+applied: 6,
683+supportedValues: [4, 6, 8],
677684});
678685});
679686@@ -726,20 +733,20 @@ describe("createVideoGenerateTool", () => {
726733size: "1280x720",
727734});
728735729-expect(result.details).toMatchObject({
730-aspectRatio: "16:9",
731-normalization: {
732-aspectRatio: {
733-applied: "16:9",
734-derivedFrom: "size",
735-},
736-},
737-metadata: {
738-requestedSize: "1280x720",
739-normalizedAspectRatio: "16:9",
740-},
736+const details = resultDetails(result);
737+expect(details.aspectRatio).toBe("16:9");
738+expect(
739+(details.normalization as { aspectRatio?: { applied?: string; derivedFrom?: string } })
740+.aspectRatio,
741+).toEqual({
742+applied: "16:9",
743+derivedFrom: "size",
741744});
742-expect(result.details).not.toHaveProperty("size");
745+expect(details.metadata).toEqual({
746+requestedSize: "1280x720",
747+normalizedAspectRatio: "16:9",
748+});
749+expect(details).not.toHaveProperty("size");
743750});
744751745752it("lists supported provider durations when advertised", async () => {
@@ -783,14 +790,13 @@ describe("createVideoGenerateTool", () => {
783790const text = (result.content?.[0] as { text: string } | undefined)?.text ?? "";
784791expect(text).toContain("modes=generate/imageToVideo");
785792expect(text).toContain("supportedDurationSeconds=4/6/8");
786-expect(result.details).toMatchObject({
787-providers: [
788-expect.objectContaining({
789-id: "google",
790-modes: ["generate", "imageToVideo"],
791-}),
792-],
793-});
793+const providers = resultDetails(result).providers as Array<{
794+id?: string;
795+modes?: string[];
796+}>;
797+expect(providers).toHaveLength(1);
798+expect(providers[0]?.id).toBe("google");
799+expect(providers[0]?.modes).toEqual(["generate", "imageToVideo"]);
794800});
795801796802it("rejects image-to-video when the provider disables that mode", async () => {
@@ -899,21 +905,19 @@ describe("createVideoGenerateTool", () => {
899905expect(text).toContain(
900906"Warning: Ignored unsupported overrides for openai/sora-2: resolution=720P, audio=false, watermark=false.",
901907);
902-expect(result).toMatchObject({
903-details: {
904-size: "1280x720",
905-warning:
906-"Ignored unsupported overrides for openai/sora-2: resolution=720P, audio=false, watermark=false.",
907-ignoredOverrides: [
908-{ key: "resolution", value: "720P" },
909-{ key: "audio", value: false },
910-{ key: "watermark", value: false },
911-],
912-},
913-});
914-expect(result.details).not.toHaveProperty("resolution");
915-expect(result.details).not.toHaveProperty("audio");
916-expect(result.details).not.toHaveProperty("watermark");
908+const details = resultDetails(result);
909+expect(details.size).toBe("1280x720");
910+expect(details.warning).toBe(
911+"Ignored unsupported overrides for openai/sora-2: resolution=720P, audio=false, watermark=false.",
912+);
913+expect(details.ignoredOverrides).toEqual([
914+{ key: "resolution", value: "720P" },
915+{ key: "audio", value: false },
916+{ key: "watermark", value: false },
917+]);
918+expect(details).not.toHaveProperty("resolution");
919+expect(details).not.toHaveProperty("audio");
920+expect(details).not.toHaveProperty("watermark");
917921});
918922919923it("rejects providerOptions that is not a plain JSON object", async () => {
@@ -955,12 +959,11 @@ describe("createVideoGenerateTool", () => {
955959providerOptions: { seed: 42, draft: true },
956960});
957961958-expect(generateSpy).toHaveBeenCalledWith(
959-expect.objectContaining({
960-autoProviderFallback: false,
961-providerOptions: { seed: 42, draft: true },
962-}),
962+const input = firstMockCallArg<{ autoProviderFallback?: boolean; providerOptions?: unknown }>(
963+generateSpy,
963964);
965+expect(input.autoProviderFallback).toBe(false);
966+expect(input.providerOptions).toEqual({ seed: 42, draft: true });
964967});
965968966969it("rejects *Roles arrays that are longer than the asset list", async () => {
@@ -1048,12 +1051,10 @@ describe("createVideoGenerateTool", () => {
10481051image: "/tmp/reference.png",
10491052});
105010531051-expect(webMedia.loadWebMedia).toHaveBeenCalledWith(
1052-"/tmp/reference.png",
1053-expect.objectContaining({
1054-ssrfPolicy: { allowRfc2544BenchmarkRange: true },
1055-}),
1056-);
1054+const loadCall = vi.mocked(webMedia.loadWebMedia).mock.calls[0];
1055+expect(loadCall?.[0]).toBe("/tmp/reference.png");
1056+const loadOptions = loadCall?.[1] as { ssrfPolicy?: unknown } | undefined;
1057+expect(loadOptions?.ssrfPolicy).toEqual({ allowRfc2544BenchmarkRange: true });
10571058});
1058105910591060it("rejects audio data: URLs via the templated rejection branch", async () => {
@@ -1082,7 +1083,7 @@ describe("createVideoGenerateTool", () => {
10821083aspectRatio: "adaptive",
10831084});
108410851085-expect(generateSpy).toHaveBeenCalledWith(expect.objectContaining({ aspectRatio: "adaptive" }));
1086+expect(firstMockCallArg<{ aspectRatio?: string }>(generateSpy).aspectRatio).toBe("adaptive");
10861087});
1087108810881089it("accepts provider-specific aspectRatio and resolution values and forwards them to the runtime", async () => {
@@ -1096,11 +1097,8 @@ describe("createVideoGenerateTool", () => {
10961097resolution: "draft-large",
10971098});
109810991099-expect(generateSpy).toHaveBeenCalledWith(
1100-expect.objectContaining({
1101-aspectRatio: "17:9",
1102-resolution: "draft-large",
1103-}),
1104-);
1100+const input = firstMockCallArg<{ aspectRatio?: string; resolution?: string }>(generateSpy);
1101+expect(input.aspectRatio).toBe("17:9");
1102+expect(input.resolution).toBe("draft-large");
11051103});
11061104});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。