test: tighten model catalog assertions

This commit is contained in:
Peter Steinberger
2026-05-09 19:49:50 +01:00
parent 04a9a61e18
commit b76df1fa8d

View File

@@ -105,6 +105,45 @@ function emptyPluginMetadataSnapshot() {
};
}
type ModelCatalogEntry = Awaited<
ReturnType<typeof import("./model-catalog.js").loadModelCatalog>
>[number];
function findCatalogEntry(
entries: readonly ModelCatalogEntry[],
provider: string,
id: string,
): ModelCatalogEntry | undefined {
return entries.find((entry) => entry.provider === provider && entry.id === id);
}
function requireCatalogEntry(
entries: readonly ModelCatalogEntry[],
provider: string,
id: string,
): ModelCatalogEntry {
const entry = findCatalogEntry(entries, provider, id);
if (!entry) {
throw new Error(`expected catalog entry ${provider}/${id}`);
}
return entry;
}
function expectNoCatalogEntry(entries: readonly ModelCatalogEntry[], provider: string, id: string) {
expect(findCatalogEntry(entries, provider, id)).toBeUndefined();
}
function requireMockCallParam(
mock: ReturnType<typeof vi.fn>,
label: string,
): Record<string, unknown> {
const first = mock.mock.calls[0]?.[0];
if (!first || typeof first !== "object" || Array.isArray(first)) {
throw new Error(`expected ${label}`);
}
return first as Record<string, unknown>;
}
describe("loadModelCatalog", () => {
beforeAll(async () => {
readFileMock = vi.fn();
@@ -278,9 +317,8 @@ describe("loadModelCatalog", () => {
readOnly: true,
});
expect(result).toContainEqual(
expect.objectContaining({ id: "gpt-test", name: "GPT Test", provider: "openai" }),
);
const entry = requireCatalogEntry(result, "openai", "gpt-test");
expect(entry.name).toBe("GPT Test");
expect(ensureOpenClawModelsJsonMock).not.toHaveBeenCalled();
expect(importPiSdk).not.toHaveBeenCalled();
expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled();
@@ -484,19 +522,9 @@ describe("loadModelCatalog", () => {
]);
const result = await loadModelCatalog({ config: {} as OpenClawConfig });
expect(result).not.toContainEqual(
expect.objectContaining({
provider: "openai-codex",
id: "gpt-5.3-codex-spark",
}),
);
expect(result).toContainEqual(
expect.objectContaining({
provider: "openai-codex",
id: "gpt-5.4",
name: "GPT-5.3 Codex",
}),
);
expectNoCatalogEntry(result, "openai-codex", "gpt-5.3-codex-spark");
const entry = requireCatalogEntry(result, "openai-codex", "gpt-5.4");
expect(entry.name).toBe("GPT-5.3 Codex");
});
it("filters stale gpt-5.3-codex-spark built-ins from the catalog", async () => {
@@ -528,24 +556,9 @@ describe("loadModelCatalog", () => {
]);
const result = await loadModelCatalog({ config: {} as OpenClawConfig });
expect(result).not.toContainEqual(
expect.objectContaining({
provider: "openai",
id: "gpt-5.3-codex-spark",
}),
);
expect(result).not.toContainEqual(
expect.objectContaining({
provider: "azure-openai-responses",
id: "gpt-5.3-codex-spark",
}),
);
expect(result).not.toContainEqual(
expect.objectContaining({
provider: "openai-codex",
id: "gpt-5.3-codex-spark",
}),
);
expectNoCatalogEntry(result, "openai", "gpt-5.3-codex-spark");
expectNoCatalogEntry(result, "azure-openai-responses", "gpt-5.3-codex-spark");
expectNoCatalogEntry(result, "openai-codex", "gpt-5.3-codex-spark");
});
it("filters stale openai-codex 5.1/5.2/5.3 built-ins from the catalog", async () => {
@@ -585,18 +598,10 @@ describe("loadModelCatalog", () => {
]);
const result = await loadModelCatalog({ config: {} as OpenClawConfig });
expect(result).not.toContainEqual(
expect.objectContaining({ provider: "openai-codex", id: "gpt-5.1-codex-mini" }),
);
expect(result).not.toContainEqual(
expect.objectContaining({ provider: "openai-codex", id: "gpt-5.2-codex" }),
);
expect(result).not.toContainEqual(
expect.objectContaining({ provider: "openai-codex", id: "gpt-5.3-codex" }),
);
expect(result).toContainEqual(
expect.objectContaining({ provider: "openai-codex", id: "gpt-5.5" }),
);
expectNoCatalogEntry(result, "openai-codex", "gpt-5.1-codex-mini");
expectNoCatalogEntry(result, "openai-codex", "gpt-5.2-codex");
expectNoCatalogEntry(result, "openai-codex", "gpt-5.3-codex");
expect(requireCatalogEntry(result, "openai-codex", "gpt-5.5").name).toBe("GPT-5.5");
});
it("does not synthesize gpt-5.4 OpenAI forward-compat entries from template models", async () => {
@@ -648,13 +653,8 @@ describe("loadModelCatalog", () => {
expect(
result.some((entry) => entry.provider === "openai" && entry.id.startsWith("gpt-5.4")),
).toBe(false);
expect(result).toContainEqual(
expect.objectContaining({
provider: "openai-codex",
id: "gpt-5.4",
name: "GPT-5.3 Codex",
}),
);
const entry = requireCatalogEntry(result, "openai-codex", "gpt-5.4");
expect(entry.name).toBe("GPT-5.3 Codex");
expect(
result.some((entry) => entry.provider === "openai-codex" && entry.id === "gpt-5.4-mini"),
).toBe(false);
@@ -675,13 +675,8 @@ describe("loadModelCatalog", () => {
const result = await loadModelCatalog({ config: {} as OpenClawConfig });
expect(result).toContainEqual(
expect.objectContaining({
provider: "kilocode",
id: "google/gemini-3.1-pro-preview",
name: "Gemini 3 Pro Preview",
}),
);
const entry = requireCatalogEntry(result, "kilocode", "google/gemini-3.1-pro-preview");
expect(entry.name).toBe("Gemini 3 Pro Preview");
});
it("loads manifest catalog rows from the current metadata snapshot without provider runtime", () => {
@@ -743,11 +738,8 @@ describe("loadModelCatalog", () => {
fallbackToMetadataScan: false,
});
expect(currentPluginMetadataSnapshotMock).toHaveBeenCalledWith(
expect.objectContaining({
allowWorkspaceScopedSnapshot: true,
}),
);
const call = requireMockCallParam(currentPluginMetadataSnapshotMock, "current snapshot params");
expect(call.allowWorkspaceScopedSnapshot).toBe(true);
expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled();
});
@@ -760,12 +752,9 @@ describe("loadModelCatalog", () => {
fallbackToMetadataScan: false,
});
expect(currentPluginMetadataSnapshotMock).toHaveBeenCalledWith(
expect.objectContaining({
env,
allowWorkspaceScopedSnapshot: true,
}),
);
const call = requireMockCallParam(currentPluginMetadataSnapshotMock, "current snapshot params");
expect(call.env).toBe(env);
expect(call.allowWorkspaceScopedSnapshot).toBe(true);
expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled();
});
@@ -789,9 +778,7 @@ describe("loadModelCatalog", () => {
const result = await loadModelCatalog({ config: {} as OpenClawConfig });
expect(result).toContainEqual(
expect.objectContaining({ provider: "ollama", id: "llama3.2", name: "Llama 3.2" }),
);
expect(requireCatalogEntry(result, "ollama", "llama3.2").name).toBe("Llama 3.2");
expect(
result.filter((entry) => entry.provider === "openai" && entry.id === "gpt-4.1"),
).toHaveLength(1);
@@ -823,16 +810,11 @@ describe("loadModelCatalog", () => {
} as OpenClawConfig,
});
expect(result).toContainEqual(
expect.objectContaining({
provider: "modelscope",
id: "Qwen/Qwen3.5-35B-A3B",
name: "Qwen3.5 35B",
input: ["text", "image"],
reasoning: true,
contextWindow: 128_000,
}),
);
const entry = requireCatalogEntry(result, "modelscope", "Qwen/Qwen3.5-35B-A3B");
expect(entry.name).toBe("Qwen3.5 35B");
expect(entry.input).toEqual(["text", "image"]);
expect(entry.reasoning).toBe(true);
expect(entry.contextWindow).toBe(128_000);
});
it("dedupes configured models against discovered provider aliases", async () => {
@@ -863,7 +845,10 @@ describe("loadModelCatalog", () => {
const matches = result.filter((entry) => findModelInCatalog([entry], "z-ai", "glm-5"));
expect(matches).toHaveLength(1);
expect(matches[0]).toMatchObject({ provider: "z.ai", id: "glm-5", name: "GLM-5" });
const match = matches[0];
expect(match?.provider).toBe("z.ai");
expect(match?.id).toBe("glm-5");
expect(match?.name).toBe("GLM-5");
});
it("does not add unrelated models when provider plugins return nothing", async () => {