HomePilot / frontend /src /ui /lib /visionError.test.ts
HomePilot Deploy Bot
chore(hf): sync HomePilot to HF Space
2c440a8
Raw History Blame Contribute Delete
4.74 kB
/**
* The message a person actually reads when vision fails.
*
* The strings below are the real ones from a real session, pasted rather than invented.
*/
import { describe, it, expect } from "vitest";
import { visionErrorMessage, parseVisionFailure } from "./visionError";
/** Exactly what the chat was handed when the local model's runner crashed. */
const RUNNER_STOPPED = new Error(
'HTTP 422 Unprocessable Entity: {"ok":false,"error":"Ollama could not keep model \'moondream:latest\' running. It may exceed this computer\'s available RAM or VRAM. Select a smaller multimodal model or check the Ollama server logs.","error_code":"model_runner_stopped","analysis_text":"","meta":{"model":"moondream:latest","mode":"both"}}',
);
/** And when the model loaded but said nothing about a tall screenshot. */
const EMPTY = new Error(
'HTTP 422 Unprocessable Entity: {"ok":false,"error":"moondream:latest returned no description of the image.","error_code":"empty_model_response","analysis_text":"","meta":{"model":"moondream:latest","mode":"both","image_size_bytes":278904}}',
);
describe("it stops dumping JSON at people", () => {
it("shows the backend sentence, not the envelope", () => {
const msg = visionErrorMessage(RUNNER_STOPPED);
expect(msg).toContain("could not keep model");
expect(msg).not.toContain("{");
expect(msg).not.toContain("error_code");
expect(msg).not.toContain("422");
});
it("and never shows the raw meta block", () => {
expect(visionErrorMessage(EMPTY)).not.toContain("image_size_bytes");
});
});
describe("it stops giving advice that is wrong", () => {
it("does not tell someone to install the model they are looking at", () => {
// This is the sentence that sent a user to check something already true: Moondream
// was installed and visible in the model list while the chat told them to pull it.
expect(visionErrorMessage(RUNNER_STOPPED)).not.toMatch(
/ollama pull moondream/i,
);
expect(visionErrorMessage(EMPTY)).not.toMatch(
/make sure a multimodal model is installed/i,
);
});
it("gives the advice that matches what actually failed", () => {
// "close some applications" appears only in the advice. An earlier version matched
// "smaller multimodal model", which the backend's own sentence already contains — so
// the test passed with the advice removed entirely, and a mutation proved it.
expect(visionErrorMessage(RUNNER_STOPPED)).toMatch(
/close some applications/i,
);
expect(visionErrorMessage(EMPTY)).toMatch(/larger multimodal model/i);
});
it("and says to install one only when none is installed", () => {
const none = new Error(
'{"ok":false,"error":"No multimodal model found.","error_code":"no_vision_model"}',
);
expect(visionErrorMessage(none)).toMatch(/ollama pull/i);
});
it("adds nothing when there is no code to go on", () => {
// A guess about the cause is what caused this. Silence beats invention.
const bare = new Error(
'{"ok":false,"error":"Failed to connect to Ollama: timed out"}',
);
const msg = visionErrorMessage(bare);
expect(msg).toBe("Failed to connect to Ollama: timed out.");
});
});
describe("the shapes that are not a tidy envelope", () => {
it("a plain transport error still reads as a sentence", () => {
expect(visionErrorMessage(new Error("Network request failed"))).toBe(
"Network request failed.",
);
});
it("an envelope whose error is itself JSON is not shown raw", () => {
// The nested case from the original report: the backend had wrapped a provider blob.
const nested = new Error(
'{"ok":false,"error":"{\\"error\\":\\"runner stopped\\"}","error_code":""}',
);
const msg = visionErrorMessage(nested);
expect(msg).not.toContain("{");
expect(msg).toBe("That image couldn't be analysed.");
});
it("null, undefined and nonsense do not throw", () => {
for (const bad of [null, undefined, 42, {}, []]) {
expect(() => visionErrorMessage(bad)).not.toThrow();
}
});
it("never ends in a double full stop", () => {
const dotted = new Error(
'{"ok":false,"error":"Something failed.","error_code":"model_runner_stopped"}',
);
expect(visionErrorMessage(dotted)).not.toMatch(/\.\./);
});
});
describe("parsing the envelope out of a transport message", () => {
it("finds it where it actually sits", () => {
expect(parseVisionFailure(RUNNER_STOPPED)?.error_code).toBe(
"model_runner_stopped",
);
});
it("returns null when there is none", () => {
expect(parseVisionFailure(new Error("Network request failed"))).toBeNull();
expect(parseVisionFailure(null)).toBeNull();
});
});