import { describe, expect, it, vi } from "vitest"; import type { Api, Model } from "@earendil-works/pi-ai"; import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; import { DEFAULT_AUTO_REVIEW_CONFIG, type AutoReviewConfig, } from "#src/auto-review/config"; import { installBuiltinAutoReview } from "#src/auto-review/runtime"; import type { ConfigReader } from "#src/config-store"; import type { PermissionsService } from "#src/service"; function makeModel(id: string, provider = "cfg"): Model { return { id, provider } as unknown as Model; } /** * A registry exposing `streamSimple`, as a real Pi 1.3.0 runtime does — the * reviewer completes through `streamSimple(...).result()`. * * The pinned development types predate this surface, so the registry is built * as a plain object — the same structural approach the reviewer takes. */ function chatRegistry(provider: string, modelId: string) { return { find: (p: string, m: string) => p === provider && m === modelId ? makeModel(m, p) : undefined, streamSimple: vi.fn(), }; } interface HarnessOptions { readonly autoReview: AutoReviewConfig; readonly hasUI?: boolean; readonly registry?: object; } function harness(options: HarnessOptions) { const { autoReview, hasUI = true } = options; const notify = vi.fn(); const registerAuthorizer = vi.fn(() => () => {}); const controller = installBuiltinAutoReview({ service: { registerAuthorizer } as unknown as PermissionsService, config: { current: () => ({ debugLog: false, permissionReviewLog: true, yoloMode: false, doublePressToConfirm: true, authorizerChain: ["auto-review"], autoReview, }), } as ConfigReader, }); const ctx = { hasUI, ui: { notify }, modelRegistry: options.registry ?? {}, sessionManager: { getBranch: () => [], getSessionId: () => "s" }, model: makeModel("session-model"), signal: undefined, } as unknown as ExtensionContext; controller.handleSessionStart(ctx); return { controller, notify, registerAuthorizer }; } describe("built-in AutoReview lifecycle", () => { it("registers the auto-review authorizer", () => { const { registerAuthorizer } = harness({ autoReview: { ...DEFAULT_AUTO_REVIEW_CONFIG }, }); expect(registerAuthorizer).toHaveBeenCalledWith( "auto-review", expect.any(Function), ); }); it("warns when exactly one of provider/model is set", () => { const { notify } = harness({ autoReview: { ...DEFAULT_AUTO_REVIEW_CONFIG, provider: "cfg" }, }); expect(notify).toHaveBeenCalledTimes(1); expect(notify.mock.calls[0]?.[1]).toBe("warning"); expect(notify.mock.calls[0]?.[0]).toMatch(/only one of/iu); }); it("warns when the named model cannot be resolved", () => { const { notify } = harness({ autoReview: { ...DEFAULT_AUTO_REVIEW_CONFIG, provider: "cfg", model: "missing" }, registry: chatRegistry("cfg", "present"), }); expect(notify).toHaveBeenCalledTimes(1); expect(notify.mock.calls[0]?.[0]).toMatch(/cfg\/missing.*not available/iu); }); it("warns when the runtime exposes no completion capability", () => { const { notify } = harness({ autoReview: { ...DEFAULT_AUTO_REVIEW_CONFIG, provider: "cfg", model: "m" }, registry: {}, }); expect(notify).toHaveBeenCalledTimes(1); expect(notify.mock.calls[0]?.[0]).toMatch(/does not support an independent model call/iu); }); it("stays silent when the named model resolves", () => { const { notify } = harness({ autoReview: { ...DEFAULT_AUTO_REVIEW_CONFIG, provider: "cfg", model: "m" }, registry: chatRegistry("cfg", "m"), }); expect(notify).not.toHaveBeenCalled(); }); it("stays silent in follow-the-session-model mode", () => { const { notify } = harness({ autoReview: { ...DEFAULT_AUTO_REVIEW_CONFIG }, registry: {}, }); expect(notify).not.toHaveBeenCalled(); }); it("stays silent when AutoReview is disabled", () => { const { notify } = harness({ autoReview: { ...DEFAULT_AUTO_REVIEW_CONFIG, enabled: false, provider: "cfg" }, }); expect(notify).not.toHaveBeenCalled(); }); it("does not notify without a UI", () => { const { notify } = harness({ autoReview: { ...DEFAULT_AUTO_REVIEW_CONFIG, provider: "cfg" }, hasUI: false, }); expect(notify).not.toHaveBeenCalled(); }); it("stays silent when auto-review is not in the authorizer chain", () => { const notify = vi.fn(); const controller = installBuiltinAutoReview({ service: { registerAuthorizer: vi.fn(() => () => {}) } as unknown as PermissionsService, config: { current: () => ({ debugLog: false, permissionReviewLog: true, yoloMode: false, doublePressToConfirm: true, authorizerChain: [], autoReview: { ...DEFAULT_AUTO_REVIEW_CONFIG, provider: "cfg" }, }), } as ConfigReader, }); controller.handleSessionStart({ hasUI: true, ui: { notify }, } as unknown as ExtensionContext); expect(notify).not.toHaveBeenCalled(); }); });