Skip to content

Commit 678c41a

Browse files
committed
test: tighten media image runtime assertions
1 parent 9385eaa commit 678c41a

1 file changed

Lines changed: 69 additions & 80 deletions

File tree

src/media-understanding/image.test.ts

Lines changed: 69 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ type ResolveModelWithRegistryTestParams = {
4141
modelId: string;
4242
};
4343

44+
type AuthRequestCall = {
45+
profileId?: string;
46+
store?: unknown;
47+
};
48+
4449
vi.mock("@mariozechner/pi-ai", async () => {
4550
const actual = await vi.importActual<typeof import("@mariozechner/pi-ai")>("@mariozechner/pi-ai");
4651
return {
@@ -123,6 +128,14 @@ describe("describeImageWithModel", () => {
123128
);
124129
});
125130

131+
function getApiKeyForModelCall(index = 0): AuthRequestCall {
132+
const call = (getApiKeyForModelMock.mock.calls as unknown[][])[index];
133+
if (!call) {
134+
throw new Error(`Expected getApiKeyForModel call ${index}`);
135+
}
136+
return call[0] as AuthRequestCall;
137+
}
138+
126139
it("routes minimax-portal image models through the MiniMax VLM endpoint", async () => {
127140
const timeoutSpy = vi.spyOn(AbortSignal, "timeout");
128141
const authStore = { version: 1, profiles: {} };
@@ -144,27 +157,25 @@ describe("describeImageWithModel", () => {
144157
model: "MiniMax-VL-01",
145158
});
146159
expect(ensureOpenClawModelsJsonMock).toHaveBeenCalled();
147-
expect(getApiKeyForModelMock).toHaveBeenCalledWith(
148-
expect.objectContaining({ store: authStore }),
149-
);
160+
const authRequest = getApiKeyForModelCall();
161+
expect(authRequest?.store).toBe(authStore);
150162
expect(requireApiKeyMock).toHaveBeenCalled();
151163
expect(setRuntimeApiKeyMock).toHaveBeenCalledWith("minimax-portal", "oauth-test");
152-
expect(fetchMock).toHaveBeenCalledWith(
153-
"https://api.minimax.io/v1/coding_plan/vlm",
154-
expect.objectContaining({
155-
method: "POST",
156-
headers: {
157-
Authorization: "Bearer oauth-test",
158-
"Content-Type": "application/json",
159-
"MM-API-Source": "OpenClaw",
160-
},
161-
body: JSON.stringify({
162-
prompt: "Describe the image.",
163-
image_url: `data:image/png;base64,${Buffer.from("png-bytes").toString("base64")}`,
164-
}),
164+
const [fetchUrl, fetchOptions] = fetchMock.mock.calls[0] ?? [];
165+
expect(fetchUrl).toBe("https://api.minimax.io/v1/coding_plan/vlm");
166+
expect(fetchOptions).toEqual({
167+
method: "POST",
168+
headers: {
169+
Authorization: "Bearer oauth-test",
170+
"Content-Type": "application/json",
171+
"MM-API-Source": "OpenClaw",
172+
},
173+
body: JSON.stringify({
174+
prompt: "Describe the image.",
175+
image_url: `data:image/png;base64,${Buffer.from("png-bytes").toString("base64")}`,
165176
}),
166-
);
167-
const [, fetchOptions] = fetchMock.mock.calls[0] ?? [];
177+
signal: fetchOptions?.signal,
178+
});
168179
expect(fetchOptions?.signal).toBeInstanceOf(AbortSignal);
169180
expect(timeoutSpy).toHaveBeenCalledWith(1000);
170181
expect(completeMock).not.toHaveBeenCalled();
@@ -205,16 +216,17 @@ describe("describeImageWithModel", () => {
205216
text: "generic ok",
206217
model: "custom-vision",
207218
});
208-
expect(registerProviderStreamForModelMock).toHaveBeenCalledWith(
209-
expect.objectContaining({
210-
model: expect.objectContaining({
211-
provider: "minimax-portal",
212-
id: "custom-vision",
213-
}),
214-
cfg: {},
215-
agentDir: "/tmp/openclaw-agent",
216-
}),
217-
);
219+
const [streamRequest] = registerProviderStreamForModelMock.mock.calls[0] ?? [];
220+
expect(streamRequest).toEqual({
221+
model: {
222+
provider: "minimax-portal",
223+
id: "custom-vision",
224+
input: ["text", "image"],
225+
baseUrl: "https://api.minimax.io/anthropic",
226+
},
227+
cfg: {},
228+
agentDir: "/tmp/openclaw-agent",
229+
});
218230
expect(completeMock).toHaveBeenCalledOnce();
219231
expect(fetchMock).not.toHaveBeenCalled();
220232
});
@@ -278,21 +290,11 @@ describe("describeImageWithModel", () => {
278290
model: "google/gemma-4-e2b",
279291
});
280292
expect(registryFind).not.toHaveBeenCalled();
281-
expect(resolveModelWithRegistryMock).toHaveBeenCalledWith(
282-
expect.objectContaining({
283-
provider: "lmstudio",
284-
modelId: "google/gemma-4-e2b",
285-
cfg: expect.objectContaining({
286-
models: expect.objectContaining({
287-
providers: expect.objectContaining({
288-
lmstudio: expect.objectContaining({
289-
baseUrl: "http://127.0.0.1:1234",
290-
}),
291-
}),
292-
}),
293-
}),
294-
}),
295-
);
293+
const [resolveRequest] = resolveModelWithRegistryMock.mock.calls[0] ?? [];
294+
expect(resolveRequest?.provider).toBe("lmstudio");
295+
expect(resolveRequest?.modelId).toBe("google/gemma-4-e2b");
296+
expect(resolveRequest?.agentDir).toBe("/tmp/openclaw-agent");
297+
expect(resolveRequest?.cfg.models?.providers?.lmstudio?.baseUrl).toBe("http://127.0.0.1:1234");
296298
expect(prepareProviderDynamicModelMock).not.toHaveBeenCalled();
297299
expect(completeMock).toHaveBeenCalledOnce();
298300
});
@@ -362,44 +364,36 @@ describe("describeImageWithModel", () => {
362364
model: "gpt-5.4",
363365
});
364366
expect(completeMock).toHaveBeenCalledOnce();
365-
expect(completeMock).toHaveBeenCalledWith(
366-
expect.objectContaining({
367-
provider: "openai-codex",
368-
id: "gpt-5.4",
369-
}),
370-
expect.objectContaining({
371-
systemPrompt: "Describe the image.",
372-
messages: [
373-
expect.objectContaining({
374-
role: "user",
375-
content: [
376-
expect.objectContaining({
377-
type: "image",
378-
mimeType: "image/png",
379-
}),
380-
],
381-
}),
382-
],
383-
}),
384-
expect.objectContaining({
385-
apiKey: "oauth-test",
386-
maxTokens: 512,
387-
}),
388-
);
389367
const firstCall = completeMock.mock.calls[0];
390368
if (!firstCall) {
391369
throw new Error("Expected image completion call");
392370
}
393-
const [, context, options] = firstCall;
371+
const [completionModel, context, options] = firstCall;
372+
expect(completionModel).toEqual({
373+
provider: "openai-codex",
374+
id: "gpt-5.4",
375+
input: ["text", "image"],
376+
baseUrl: "https://chatgpt.com/backend-api",
377+
});
378+
expect(context.systemPrompt).toBe("Describe the image.");
379+
expect(context.messages).toHaveLength(1);
394380
expect(Object.keys(options).toSorted()).toEqual(["apiKey", "maxTokens", "signal", "timeoutMs"]);
381+
expect(options.apiKey).toBe("oauth-test");
382+
expect(options.maxTokens).toBe(512);
395383
expect(options.signal).toBeInstanceOf(AbortSignal);
396384
expect(options.timeoutMs).toBeGreaterThan(0);
397385
expect(options.timeoutMs).toBeLessThanOrEqual(1000);
398386
const userMessage = context.messages[0];
399387
if (!userMessage) {
400388
throw new Error("expected image completion user message");
401389
}
390+
expect(userMessage.role).toBe("user");
402391
expect(userMessage.content).toHaveLength(1);
392+
expect(userMessage.content[0]).toEqual({
393+
type: "image",
394+
data: Buffer.from("png-bytes").toString("base64"),
395+
mimeType: "image/png",
396+
});
403397
});
404398

405399
it("places OpenRouter image prompts in user content before images", async () => {
@@ -450,10 +444,11 @@ describe("describeImageWithModel", () => {
450444
}
451445
expect(userMessage.content).toEqual([
452446
{ type: "text", text: "Describe the image." },
453-
expect.objectContaining({
447+
{
454448
type: "image",
449+
data: Buffer.from("png-bytes").toString("base64"),
455450
mimeType: "image/png",
456-
}),
451+
},
457452
]);
458453
});
459454

@@ -682,11 +677,8 @@ describe("describeImageWithModel", () => {
682677
model: "gemini-3-flash-preview",
683678
});
684679
expect(findMock).toHaveBeenCalledOnce();
685-
expect(getApiKeyForModelMock).toHaveBeenCalledWith(
686-
expect.objectContaining({
687-
profileId: "google:default",
688-
}),
689-
);
680+
const authRequest = getApiKeyForModelCall();
681+
expect(authRequest?.profileId).toBe("google:default");
690682
expect(setRuntimeApiKeyMock).toHaveBeenCalledWith("google", "oauth-test");
691683
});
692684

@@ -730,11 +722,8 @@ describe("describeImageWithModel", () => {
730722
model: "gemini-3.1-flash-lite-preview",
731723
});
732724
expect(findMock).toHaveBeenCalledOnce();
733-
expect(getApiKeyForModelMock).toHaveBeenCalledWith(
734-
expect.objectContaining({
735-
profileId: "google:default",
736-
}),
737-
);
725+
const authRequest = getApiKeyForModelCall();
726+
expect(authRequest?.profileId).toBe("google:default");
738727
expect(setRuntimeApiKeyMock).toHaveBeenCalledWith("google", "oauth-test");
739728
});
740729
});

0 commit comments

Comments
 (0)