Compare commits

...

1 Commits

Author SHA1 Message Date
diegosouzapw
0e3c918465 fix(api): give the #13376 model-test skip a real httpStatus
2a6d0586 added an early return in runSingleModelTest that skips image/music/
video-only models so a chat test never triggers a billable generation. The
returned object omitted httpStatus, which SingleModelTestResult requires
(TS2741 — this is the API Route Typecheck red on release/v3.8.51).

It was not only a type error. src/app/api/models/test/route.ts passes
result.httpStatus straight to NextResponse.json(body, { status }), so the
status was undefined, Next fell back to 200, and a skipped test reached the
client as an HTTP success with status: "error" in the body.

Answer 422 rather than the 409 the managed-lease return uses: the request is
well-formed, the model's modality just cannot be exercised by a chat test.

TDD: the new case seeds an images-only custom model, replaces fetch with a
throwing stub, and asserts the skip fires with a numeric 422 and no dispatch.
It failed with httpStatus undefined before the one-line fix and passes after.

model-test-runner + model-test-modality-guard-13376: 35/35
check-api-typecheck: OK (the TS2741 is gone)
2026-09-15 08:32:35 -03:00
3 changed files with 49 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
- **fix(api):** the model-test skip for image/music/video-only models (#13376) returned a
result with no `httpStatus`, and the `/api/models/test` route hands that field straight to
`NextResponse` — so a skipped test reached the client as HTTP 200 carrying `status: "error"`
in the body. It now answers 422: the request is valid, but that model's modality cannot be
exercised by a chat test. Also clears the `TS2741` that was failing `API Route Typecheck`
on the release branch.

View File

@@ -488,6 +488,10 @@ export async function runSingleModelTest(
modelId: fullModelStr,
status: "error",
latencyMs: 0,
// 422, not the 409 the managed-lease return above uses: the request is valid, but this
// model's modality cannot be exercised by a chat test. The route passes httpStatus
// straight to NextResponse — omitting it made Next answer 200 for a skipped test.
httpStatus: 422,
error:
"Skipped: non-chat generation model (images/music/video) — use the corresponding generation endpoint instead",
};

View File

@@ -439,3 +439,42 @@ test("runSingleModelTest preserves trusted local limiter HTTP statuses", async (
await rateLimitManager.__resetRateLimitManagerForTests();
}
});
// ---------------------------------------------------------------------------
// #13376 skip path — a non-chat generation model (image/music/video) must be
// rejected with a real HTTP status and never dispatched as a chat completion.
//
// The route hands `result.httpStatus` straight to NextResponse
// (src/app/api/models/test/route.ts). When the early return omitted it, the
// status was `undefined`, Next fell back to 200, and a skipped test reached the
// client as an HTTP success carrying `status: "error"` in the body.
// ---------------------------------------------------------------------------
test("#13376 a generation-only model is skipped with a 4xx and is never dispatched", async () => {
const { addCustomModel } = await import("@/lib/db/models");
await addCustomModel("openai", "image-only-13376", "Image only", "manual", "images-generations", [
"images",
]);
const originalFetch = globalThis.fetch;
let dispatched = false;
globalThis.fetch = async () => {
dispatched = true;
throw new Error("a generation-only model must not be dispatched as a chat completion");
};
try {
const result = await runSingleModelTest({
providerId: "openai",
modelId: "image-only-13376",
timeoutMs: 1_000,
});
assert.equal(dispatched, false, "no billable generation may be triggered");
assert.equal(result.status, "error");
assert.equal(typeof result.httpStatus, "number", "the route needs a real status code");
assert.equal(result.httpStatus, 422);
} finally {
globalThis.fetch = originalFetch;
}
});