惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
N
Netflix TechBlog - Medium
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
腾讯CDC
G
Google Developers Blog
Martin Fowler
Martin Fowler
Microsoft Security Blog
Microsoft Security Blog
Recent Announcements
Recent Announcements
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
有赞技术团队
有赞技术团队
Jina AI
Jina AI
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
美团技术团队
WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
test: clear video generation tool broad matchers · opencl...
steipete · 2026-05-10 · via Recent Commits to openclaw:main

@@ -147,6 +147,18 @@ function mockSavedVideoResult(fileName = "out.mp4") {

147147

return 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+150162

function resetVideoGenerateMocks() {

151163

vi.restoreAllMocks();

152164

for (const key of VIDEO_GENERATION_PROVIDER_AUTH_ENV_VARS) {

@@ -336,16 +348,15 @@ describe("createVideoGenerateTool", () => {

336348

);

337349

expect(text).toContain("Generated 1 video with qwen/wan2.6-t2v.");

338350

expect(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" });

349360

expect(taskExecutorMocks.createRunningTaskRun).not.toHaveBeenCalled();

350361

expect(taskExecutorMocks.completeTaskRunByRunId).not.toHaveBeenCalled();

351362

});

@@ -431,16 +442,15 @@ describe("createVideoGenerateTool", () => {

431442

expect(saveSpy).not.toHaveBeenCalled();

432443

expect(text).toContain("Generated 1 video with vydra/veo3.");

433444

expect(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

});

445455446456

it("falls back to the provider URL when generated video persistence exceeds the media cap", async () => {

@@ -482,15 +492,14 @@ describe("createVideoGenerateTool", () => {

482492483493

expect(text).toContain("Generated 1 video with fal/fal-ai/minimax/video-01-live.");

484494

expect(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

});

495504496505

it("starts background generation and wakes the session with url-only MEDIA lines", async () => {

@@ -552,39 +561,34 @@ describe("createVideoGenerateTool", () => {

552561553562

expect(text).toContain("Background task started for video generation (task-123).");

554563

expect(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");

562568

if (!scheduledWork) {

563569

throw new Error("expected scheduled video generation work");

564570

}

565571

await scheduledWork();

566572

expect(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

});

589593590594

it("surfaces provider generation failures inline when there is no detached session", async () => {

@@ -663,17 +667,20 @@ describe("createVideoGenerateTool", () => {

663667

const text = (result.content?.[0] as { text: string } | undefined)?.text ?? "";

664668665669

expect(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", () => {

726733

size: "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

});

744751745752

it("lists supported provider durations when advertised", async () => {

@@ -783,14 +790,13 @@ describe("createVideoGenerateTool", () => {

783790

const text = (result.content?.[0] as { text: string } | undefined)?.text ?? "";

784791

expect(text).toContain("modes=generate/imageToVideo");

785792

expect(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

});

795801796802

it("rejects image-to-video when the provider disables that mode", async () => {

@@ -899,21 +905,19 @@ describe("createVideoGenerateTool", () => {

899905

expect(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

});

918922919923

it("rejects providerOptions that is not a plain JSON object", async () => {

@@ -955,12 +959,11 @@ describe("createVideoGenerateTool", () => {

955959

providerOptions: { 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

});

965968966969

it("rejects *Roles arrays that are longer than the asset list", async () => {

@@ -1048,12 +1051,10 @@ describe("createVideoGenerateTool", () => {

10481051

image: "/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

});

1058105910591060

it("rejects audio data: URLs via the templated rejection branch", async () => {

@@ -1082,7 +1083,7 @@ describe("createVideoGenerateTool", () => {

10821083

aspectRatio: "adaptive",

10831084

});

108410851085-

expect(generateSpy).toHaveBeenCalledWith(expect.objectContaining({ aspectRatio: "adaptive" }));

1086+

expect(firstMockCallArg<{ aspectRatio?: string }>(generateSpy).aspectRatio).toBe("adaptive");

10861087

});

1087108810881089

it("accepts provider-specific aspectRatio and resolution values and forwards them to the runtime", async () => {

@@ -1096,11 +1097,8 @@ describe("createVideoGenerateTool", () => {

10961097

resolution: "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

});