Files
oh-my-pi/packages/coding-agent/test/model-registry-create.test.ts
T
can1357 346ae48b0c fix(session): prevented runtime model switches from persisting default role
- Restricted `setModel` to persist settings only when `persist: true` is passed; all runtime switches (Ctrl+P, `--model`, `/model`, model picker temp selections) no longer overwrite `modelRoles.default`.
- Changed `cycleRoleModels` to accept a direction ("forward"/"backward") instead of a `temporary` flag; both directions now use `applyRoleModel` without persisting.
- Added `persist: true` exclusively to the model picker's "Set as default" action in `SelectorController`.
- Added test suite covering persistence behavior for `setModel`, `cycleRoleModels`, and `cycleModel`.
2026-05-31 06:21:29 +02:00

73 lines
2.7 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import * as fs from "node:fs";
import * as path from "node:path";
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
import { TempDir } from "@oh-my-pi/pi-utils";
import { ConfigFile } from "../src/config/config-file";
import { ModelRegistry } from "../src/config/model-registry";
import { ModelsConfigSchema } from "../src/config/models-config-schema";
describe("ModelRegistry.create() factory (F6)", () => {
let tempDir: TempDir;
beforeEach(() => {
tempDir = TempDir.createSync("@model-registry-create-");
});
afterEach(async () => {
// On Windows the cache SQLite handle inside the registry may briefly hold
// the dir; treat cleanup errors as best-effort like TempDir's Symbol.dispose.
await tempDir.remove().catch(() => {});
});
test("produces an instance whose authStorage matches and that exposes bundled models", async () => {
const authStorage = await AuthStorage.create(path.join(tempDir.path(), "auth.db"));
try {
const registry = new ModelRegistry(authStorage, path.join(tempDir.path(), "models.yml"));
expect(registry.authStorage).toBe(authStorage);
// The constructor's bundled-model load runs after warmup, so the
// factory's returned instance must be queryable immediately.
const claude = registry.find("anthropic", "claude-sonnet-4-5");
expect(claude).toBeDefined();
expect(claude?.id).toBe("claude-sonnet-4-5");
} finally {
authStorage.close();
}
});
test("migrates legacy models.json → models.yml ahead of the sync constructor", async () => {
const yml = path.join(tempDir.path(), "models.yml");
const json = path.join(tempDir.path(), "models.json");
// Seed a legacy JSON config; factory should migrate it asynchronously
// before the sync constructor reads from the yml path.
await Bun.write(json, JSON.stringify({ models: [] }));
expect(fs.existsSync(yml)).toBe(false);
const authStorage = await AuthStorage.create(path.join(tempDir.path(), "auth.db"));
try {
new ModelRegistry(authStorage, yml);
expect(fs.existsSync(yml)).toBe(true);
} finally {
authStorage.close();
}
});
test("ConfigFile migration is idempotent — second load is a no-op", async () => {
const yml = path.join(tempDir.path(), "models.yml");
const json = path.join(tempDir.path(), "models.json");
await Bun.write(json, JSON.stringify({ models: [] }));
const cf = new ConfigFile("models", ModelsConfigSchema, yml);
cf.tryLoad();
expect(fs.existsSync(yml)).toBe(true);
const mtime1 = fs.statSync(yml).mtimeMs;
// Second load should not rewrite the file (idempotent migration path).
cf.invalidate();
cf.tryLoad();
const mtime2 = fs.statSync(yml).mtimeMs;
expect(mtime2).toBe(mtime1);
});
});