feat: unify installation model catalog
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { test, expect, vi } from "vitest";
|
||||
import { test, expect } from "vitest";
|
||||
import Fastify from "fastify";
|
||||
import { buildApp } from "../src/app.js";
|
||||
import { loadConfig } from "../src/config.js";
|
||||
@@ -156,12 +156,23 @@ test("registry-backed SQL preview resolves and uses the session's pinned runtime
|
||||
// Workspace registry route coverage lives in routes-workspaces.test.ts. `/workspaces` no longer
|
||||
// reads legacy harness files: the Git registry is the single shared source of truth.
|
||||
|
||||
test("GET /models returns {models:[...]} from injected listModels stub", async () => {
|
||||
test("GET /models returns session choices from the installation model catalog", async () => {
|
||||
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
|
||||
thtRunner: {} as any,
|
||||
listModels: async () => [
|
||||
{ provider: "zai", id: "glm-5.2", name: "GLM 5.2", reasoning: true },
|
||||
],
|
||||
runtimeModelCatalog: {
|
||||
defaultSession: "zai/glm-5.2",
|
||||
defaultMetadataGeneration: null,
|
||||
embedding: { id: "ollama/qwen3-embedding:0.6b", dimensions: 1024 },
|
||||
sessionModels: () => [{
|
||||
id: "zai/glm-5.2", provider: "zai", model: "glm-5.2", label: "GLM 5.2",
|
||||
upstreamModel: "glm-5.2", authentication: { mode: "pi_auth" },
|
||||
sessionAdapter: { mode: "pi_builtin" }, session: { reasoning: true },
|
||||
}],
|
||||
metadataModels: () => [],
|
||||
hasSession: (id: string) => id === "zai/glm-5.2",
|
||||
},
|
||||
// Runtime introspection is a health gate for starting a session, not a second catalog.
|
||||
listModels: async () => { throw new Error("Pi is unavailable"); },
|
||||
});
|
||||
|
||||
const res = await app.inject({ method: "GET", url: "/models" });
|
||||
@@ -172,36 +183,17 @@ test("GET /models returns {models:[...]} from injected listModels stub", async (
|
||||
});
|
||||
});
|
||||
|
||||
test("GET /models returns {models:[]} when listModels throws (graceful fallback)", async () => {
|
||||
test("GET /models returns an empty list when the catalog has no session models", async () => {
|
||||
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
|
||||
thtRunner: {} as any,
|
||||
listModels: async () => { throw new Error("Pi not running"); },
|
||||
});
|
||||
|
||||
const res = await app.inject({ method: "GET", url: "/models" });
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.json()).toEqual({ models: [] });
|
||||
});
|
||||
|
||||
test("GET /models logs a sanitized warning when listing fails", async () => {
|
||||
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
|
||||
thtRunner: {} as any,
|
||||
listModels: async () => { throw new Error("credential-value-must-not-appear"); },
|
||||
});
|
||||
const warn = vi.spyOn(app.log, "warn");
|
||||
|
||||
const res = await app.inject({ method: "GET", url: "/models" });
|
||||
|
||||
expect(res.json()).toEqual({ models: [] });
|
||||
expect(JSON.stringify(warn.mock.calls)).not.toContain("credential-value-must-not-appear");
|
||||
expect(warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("GET /models with empty listModels stub returns empty array", async () => {
|
||||
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
|
||||
thtRunner: {} as any,
|
||||
listModels: async () => [],
|
||||
runtimeModelCatalog: {
|
||||
defaultSession: null,
|
||||
defaultMetadataGeneration: null,
|
||||
embedding: null,
|
||||
sessionModels: () => [],
|
||||
metadataModels: () => [],
|
||||
hasSession: () => false,
|
||||
},
|
||||
});
|
||||
|
||||
const res = await app.inject({ method: "GET", url: "/models" });
|
||||
|
||||
Reference in New Issue
Block a user