import assert from "node:assert/strict"; import { test } from "node:test"; import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent"; import { refreshCatalog } from "../src/index.ts"; interface Fake { configured?: boolean; hasUI?: boolean; result?: { aborted: boolean; errors: Map }; count?: number; } function fakeContext(fake: Fake) { const notices: { message: string; level: string }[] = []; const refreshes: unknown[] = []; const ctx = { hasUI: fake.hasUI ?? true, ui: { notify: (message: string, level: string) => notices.push({ message, level }) }, modelRegistry: { getProviderAuthStatus: () => ({ configured: fake.configured ?? true }), getProviderAuth: () => { throw new Error("must not resolve credentials"); }, refresh: async (options: unknown) => { refreshes.push(options); return fake.result ?? { aborted: false, errors: new Map() }; }, getProvider: () => ({ getModels: () => Array.from({ length: fake.count ?? 0 }) }), }, } as unknown as ExtensionCommandContext; return { ctx, notices, refreshes }; } test("refresh asks to log in without resolving credentials or calling the network", async () => { const { ctx, notices, refreshes } = fakeContext({ configured: false }); await refreshCatalog(ctx); assert.equal(refreshes.length, 0); assert.deepEqual(notices, [{ message: "UniGPT is not configured. Run /login unigpt first.", level: "warning" }]); }); test("refresh targets only UniGPT, bounded by a deadline, and reports the model count", async () => { const { ctx, notices, refreshes } = fakeContext({ count: 3 }); await refreshCatalog(ctx); const [options] = refreshes as { providers: string[]; signal: AbortSignal }[]; assert.deepEqual(options.providers, ["unigpt"]); assert.ok(options.signal instanceof AbortSignal); assert.deepEqual(notices, [{ message: "UniGPT: 3 models available. Select one with /model.", level: "info" }]); }); test("refresh reports errors, timeouts, and empty catalogs as warnings", async () => { const failed = fakeContext({ result: { aborted: false, errors: new Map([["unigpt", new Error("HTTP 503")]]) } }); await refreshCatalog(failed.ctx); assert.match(failed.notices[0].message, /refresh failed \(HTTP 503\); keeping the cached/); const timedOut = fakeContext({ result: { aborted: true, errors: new Map() } }); await refreshCatalog(timedOut.ctx); assert.match(timedOut.notices[0].message, /no response within 20s/); const empty = fakeContext({ count: 0 }); await refreshCatalog(empty.ctx); assert.deepEqual(empty.notices, [{ message: "UniGPT lists no models for this API key.", level: "warning" }]); }); test("without a UI, refresh reports on stderr instead of staying silent", async (t) => { const lines: string[] = []; t.mock.method(console, "error", (line: string) => lines.push(line)); const { ctx, notices } = fakeContext({ hasUI: false, count: 2 }); await refreshCatalog(ctx); assert.equal(notices.length, 0); assert.deepEqual(lines, ["[unigpt] UniGPT: 2 models available. Select one with /model."]); });