pi-mono/packages/coding-agent/test/models-store.test.ts
Mario Zechner fed6009cc9 fix(coding-agent): make model refresh cancellation caller-owned
Add generation-safe catalog publication, cancellable auth and storage operations, locally consistent credential mutations, and cache-first interactive model flows.
2026-08-04 00:03:52 +02:00

74 lines
2.8 KiB
TypeScript

import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { Model } from "@earendil-works/pi-ai";
import lockfile from "proper-lockfile";
import { afterEach, describe, expect, it } from "vitest";
import { FileModelsStore } from "../src/core/models-store.ts";
const tempDirs: string[] = [];
afterEach(() => {
for (const path of tempDirs.splice(0)) {
if (existsSync(path)) rmSync(path, { recursive: true });
}
});
function model(provider: string, id: string): Model<"openai-completions"> {
return {
id,
name: id,
api: "openai-completions",
provider,
baseUrl: "https://example.test/v1",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 1000,
maxTokens: 100,
};
}
describe("FileModelsStore", () => {
it("persists provider catalogs without replacing unrelated providers", async () => {
const dir = join(tmpdir(), `pi-models-store-${Date.now()}-${Math.random().toString(36).slice(2)}`);
tempDirs.push(dir);
mkdirSync(dir, { recursive: true });
const path = join(dir, "models-store.json");
const store = new FileModelsStore(path);
await store.write("one", { models: [model("one", "m1")], checkedAt: 100 });
await store.write("two", { models: [model("two", "m2")], checkedAt: 200 });
const reloaded = new FileModelsStore(path);
expect((await reloaded.read("one"))?.models.map((entry) => entry.id)).toEqual(["m1"]);
expect((await reloaded.read("one"))?.checkedAt).toBe(100);
expect((await reloaded.read("two"))?.models.map((entry) => entry.id)).toEqual(["m2"]);
await reloaded.delete("one");
expect(await reloaded.read("one")).toBeUndefined();
expect((await reloaded.read("two"))?.models.map((entry) => entry.id)).toEqual(["m2"]);
});
it("cancels a catalog write waiting for a held file lock without writing later", async () => {
const dir = join(tmpdir(), `pi-models-store-${Date.now()}-${Math.random().toString(36).slice(2)}`);
tempDirs.push(dir);
mkdirSync(dir, { recursive: true });
const path = join(dir, "models-store.json");
writeFileSync(path, JSON.stringify({ one: { models: [model("one", "existing")] } }));
const store = new FileModelsStore(path);
const release = await lockfile.lock(path, { realpath: false });
const controller = new AbortController();
const pending = store.write("two", { models: [model("two", "cancelled")] }, { signal: controller.signal });
await new Promise((resolve) => setTimeout(resolve, 10));
controller.abort();
await expect(pending).rejects.toMatchObject({ name: "AbortError" });
await release();
await new Promise((resolve) => setTimeout(resolve, 150));
const stored = JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
expect(stored.one).toBeDefined();
expect(stored.two).toBeUndefined();
});
});