diff --git a/packages/ai/test/overflow-utils.test.ts b/packages/ai/test/overflow-utils.test.ts index 71518210b..8a8090b8a 100644 --- a/packages/ai/test/overflow-utils.test.ts +++ b/packages/ai/test/overflow-utils.test.ts @@ -68,9 +68,8 @@ describe("isContextOverflow - 400/413 no-body (Cerebras, Mistral, proxy wrappers // Regression: api.synthetic.new wraps upstream HF 400-no-body in a JSON envelope. // finalizeErrorMessage transforms the response to "400 status code: {JSON}" where // the JSON value contains the inner "400 status code (no body)" text. - it("detects wrapped proxy envelope: '400 status code: {\"error\":\"... 400 status code (no body)\"}'", () => { - const errorMessage = - '400 status code: {"error":"Error from inference backend: 400 status code (no body)"}'; + it('detects wrapped proxy envelope: \'400 status code: {"error":"... 400 status code (no body)"}\'', () => { + const errorMessage = '400 status code: {"error":"Error from inference backend: 400 status code (no body)"}'; expect(isContextOverflow(createErrorMessage(errorMessage))).toBe(true); }); diff --git a/packages/coding-agent/src/extensibility/plugins/legacy-pi-compat.ts b/packages/coding-agent/src/extensibility/plugins/legacy-pi-compat.ts index 27291d647..cbe4482bd 100644 --- a/packages/coding-agent/src/extensibility/plugins/legacy-pi-compat.ts +++ b/packages/coding-agent/src/extensibility/plugins/legacy-pi-compat.ts @@ -154,7 +154,7 @@ interface LegacyPiMirrorState { function getMirrorPath(sourcePath: string, state: LegacyPiMirrorState): string { const extension = path.extname(sourcePath) || ".js"; const digest = Bun.hash(sourcePath).toString(36); - return path.join(state.root, `${digest}${extension}`); + return path.join(state.root, `module-${digest}${extension}`); } async function rewriteRelativeImportsForLegacyExtension( @@ -212,7 +212,7 @@ async function mirrorLegacyPiFile(sourcePath: string, state: LegacyPiMirrorState } export async function loadLegacyPiModule(resolvedPath: string): Promise { - const root = path.join(os.tmpdir(), "omp-legacy-pi-file", Bun.hash(resolvedPath).toString(36)); + const root = path.join(os.tmpdir(), "omp-legacy-pi-file", `entry-${Bun.hash(resolvedPath).toString(36)}`); await fs.rm(root, { recursive: true, force: true }); const state: LegacyPiMirrorState = { root, seen: new Map() }; const mirroredEntry = await mirrorLegacyPiFile(resolvedPath, state); diff --git a/packages/coding-agent/test/extensions-runner.test.ts b/packages/coding-agent/test/extensions-runner.test.ts index 8f9f76a50..5b2749a60 100644 --- a/packages/coding-agent/test/extensions-runner.test.ts +++ b/packages/coding-agent/test/extensions-runner.test.ts @@ -15,7 +15,6 @@ import { import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { getProjectAgentDir, logger, TempDir } from "@oh-my-pi/pi-utils"; -import { filterUserScoped } from "./utils/filter-user-extensions"; describe("ExtensionRunner", () => { let tempDir: TempDir; @@ -40,11 +39,20 @@ describe("ExtensionRunner", () => { }); const loadTestExtensions = async (configuredPaths: string[] = []) => { - const result = await discoverAndLoadExtensions(configuredPaths, tempDir.path()); + const result = await discoverAndLoadExtensions([extensionsDir, ...configuredPaths], tempDir.path()); + const testRoots = [ + extensionsDir, + ...configuredPaths.map(configuredPath => path.resolve(tempDir.path(), configuredPath)), + ]; + const isTestScoped = (candidate: string): boolean => + testRoots.some(root => { + const relative = path.relative(path.resolve(root), path.resolve(candidate)); + return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)); + }); return { ...result, - extensions: filterUserScoped(result.extensions), - errors: filterUserScoped(result.errors), + extensions: result.extensions.filter(extension => isTestScoped(extension.path)), + errors: result.errors.filter(error => isTestScoped(error.path)), }; }; diff --git a/packages/coding-agent/test/tui/hyperlink.test.ts b/packages/coding-agent/test/tui/hyperlink.test.ts index c1fa0e2cb..8ccee26e1 100644 --- a/packages/coding-agent/test/tui/hyperlink.test.ts +++ b/packages/coding-agent/test/tui/hyperlink.test.ts @@ -1,5 +1,5 @@ -import { afterEach, describe, expect, it, spyOn } from "bun:test"; -import * as settingsModule from "@oh-my-pi/pi-coding-agent/config/settings"; +import { afterAll, afterEach, beforeAll, describe, expect, it } from "bun:test"; +import { resetSettingsForTest, Settings, settings } from "@oh-my-pi/pi-coding-agent/config/settings"; import { fileHyperlink, isHyperlinkEnabled, tryResolveInternalUrlSync } from "@oh-my-pi/pi-coding-agent/tui/hyperlink"; import * as terminalCaps from "@oh-my-pi/pi-tui"; @@ -7,6 +7,7 @@ import * as terminalCaps from "@oh-my-pi/pi-tui"; const OSC = "\x1b]"; const ST = "\x1b\\"; const LINK_END = `${OSC}8;;${ST}`; +const ORIGINAL_NO_COLOR = Bun.env.NO_COLOR; /** Extract the hyperlink URI from a wrapped string. Returns undefined if not wrapped. */ function extractLinkUri(text: string): string | undefined { @@ -19,133 +20,128 @@ function isHyperlinked(text: string): boolean { return text.includes(`${OSC}8;`) && text.includes(LINK_END); } -describe("isHyperlinkEnabled", () => { - afterEach(() => { - delete Bun.env.NO_COLOR; - }); +/** Set the `tui.hyperlinks` mode via a non-persistent runtime override. */ +function setHyperlinkMode(mode: "off" | "auto" | "always"): void { + settings.override("tui.hyperlinks", mode); +} +beforeAll(async () => { + resetSettingsForTest(); + await Settings.init({ inMemory: true }); +}); + +afterAll(() => { + resetSettingsForTest(); +}); + +afterEach(() => { + settings.clearOverride("tui.hyperlinks"); + if (ORIGINAL_NO_COLOR === undefined) { + delete Bun.env.NO_COLOR; + } else { + Bun.env.NO_COLOR = ORIGINAL_NO_COLOR; + } +}); + +describe("isHyperlinkEnabled", () => { it('returns false when mode is "off"', () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "off" : undefined) as never, - ); + setHyperlinkMode("off"); expect(isHyperlinkEnabled()).toBe(false); - spy.mockRestore(); }); it('returns true when mode is "always" regardless of TTY', () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "always" : undefined) as never, - ); + setHyperlinkMode("always"); expect(isHyperlinkEnabled()).toBe(true); - spy.mockRestore(); }); it("returns false in auto mode when NO_COLOR is set", () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "auto" : undefined) as never, - ); + setHyperlinkMode("auto"); Bun.env.NO_COLOR = "1"; expect(isHyperlinkEnabled()).toBe(false); - spy.mockRestore(); }); it("returns false in auto mode when stdout is not a TTY", () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "auto" : undefined) as never, - ); + setHyperlinkMode("auto"); const origTTY = Object.getOwnPropertyDescriptor(process.stdout, "isTTY"); - Object.defineProperty(process.stdout, "isTTY", { value: false, configurable: true }); - expect(isHyperlinkEnabled()).toBe(false); - if (origTTY) Object.defineProperty(process.stdout, "isTTY", origTTY); - spy.mockRestore(); + try { + Object.defineProperty(process.stdout, "isTTY", { value: false, configurable: true }); + expect(isHyperlinkEnabled()).toBe(false); + } finally { + if (origTTY) { + Object.defineProperty(process.stdout, "isTTY", origTTY); + } else { + Reflect.deleteProperty(process.stdout, "isTTY"); + } + } }); it("returns TERMINAL.hyperlinks value in auto mode when conditions are met", () => { - const settingsSpy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "auto" : undefined) as never, - ); + setHyperlinkMode("auto"); + delete Bun.env.NO_COLOR; const origTTY = Object.getOwnPropertyDescriptor(process.stdout, "isTTY"); - Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true }); - // TERMINAL.hyperlinks may be true or false depending on the test runner env; - // what matters is that isHyperlinkEnabled mirrors it. - const expected = terminalCaps.TERMINAL.hyperlinks; - expect(isHyperlinkEnabled()).toBe(expected); - if (origTTY) Object.defineProperty(process.stdout, "isTTY", origTTY); - settingsSpy.mockRestore(); + try { + Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true }); + // TERMINAL.hyperlinks may be true or false depending on the test runner env; + // what matters is that isHyperlinkEnabled mirrors it. + const expected = terminalCaps.TERMINAL.hyperlinks; + expect(isHyperlinkEnabled()).toBe(expected); + } finally { + if (origTTY) { + Object.defineProperty(process.stdout, "isTTY", origTTY); + } else { + Reflect.deleteProperty(process.stdout, "isTTY"); + } + } }); }); describe("fileHyperlink", () => { - afterEach(() => { - delete Bun.env.NO_COLOR; - }); - it("returns plain text when hyperlinks are disabled (mode=off)", () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "off" : undefined) as never, - ); + setHyperlinkMode("off"); const result = fileHyperlink("/Users/foo/bar.ts", "bar.ts"); expect(result).toBe("bar.ts"); - spy.mockRestore(); }); it("wraps text in OSC 8 when hyperlinks are enabled (mode=always)", () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "always" : undefined) as never, - ); + setHyperlinkMode("always"); const result = fileHyperlink("/Users/foo/bar.ts", "bar.ts"); expect(isHyperlinked(result)).toBe(true); expect(result).toContain("bar.ts"); - spy.mockRestore(); }); it("builds a valid file:// URI with the absolute path", () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "always" : undefined) as never, - ); + setHyperlinkMode("always"); const result = fileHyperlink("/Users/foo/bar.ts", "bar.ts"); const uri = extractLinkUri(result); expect(uri).toMatch(/^file:\/\//); expect(uri).toContain("bar.ts"); - spy.mockRestore(); }); it("encodes spaces in the path", () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "always" : undefined) as never, - ); + setHyperlinkMode("always"); const result = fileHyperlink("/Users/foo/my file.ts", "my file.ts"); const uri = extractLinkUri(result); expect(uri).toContain("%20"); expect(uri).not.toContain(" "); - spy.mockRestore(); }); it("appends line and col as query params when provided", () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "always" : undefined) as never, - ); + setHyperlinkMode("always"); const result = fileHyperlink("/Users/foo/bar.ts", "bar.ts", { line: 42, col: 7 }); const uri = extractLinkUri(result); expect(uri).toContain("line=42"); expect(uri).toContain("col=7"); - spy.mockRestore(); }); it("omits query params when line/col are not provided", () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "always" : undefined) as never, - ); + setHyperlinkMode("always"); const result = fileHyperlink("/Users/foo/bar.ts", "bar.ts"); const uri = extractLinkUri(result); expect(uri).not.toContain("?"); - spy.mockRestore(); }); it("produces a stable id for the same path", () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "always" : undefined) as never, - ); + setHyperlinkMode("always"); const r1 = fileHyperlink("/Users/foo/bar.ts", "bar.ts"); const r2 = fileHyperlink("/Users/foo/bar.ts", "different display text"); // Extract id= from params (between "id=" and next ";") @@ -153,29 +149,22 @@ describe("fileHyperlink", () => { const id2 = r2.match(/id=([^;]+)/)?.[1]; expect(id1).toBeDefined(); expect(id1).toBe(id2); - spy.mockRestore(); }); it("does not double-wrap text that already contains an OSC 8 sequence", () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "always" : undefined) as never, - ); + setHyperlinkMode("always"); const alreadyWrapped = `${OSC}8;id=abc123;file:///foo/bar.ts${ST}bar.ts${LINK_END}`; const result = fileHyperlink("/Users/foo/other.ts", alreadyWrapped); // Should return the already-wrapped text unchanged expect(result).toBe(alreadyWrapped); - spy.mockRestore(); }); it("preserves ANSI color codes inside the hyperlink", () => { - const spy = spyOn(settingsModule.settings, "get").mockImplementation( - (key: string) => (key === "tui.hyperlinks" ? "always" : undefined) as never, - ); + setHyperlinkMode("always"); const colored = "\x1b[32mbar.ts\x1b[0m"; const result = fileHyperlink("/Users/foo/bar.ts", colored); expect(result).toContain(colored); expect(isHyperlinked(result)).toBe(true); - spy.mockRestore(); }); });