From 1e0d08a5bb86a91e301d9e78a9ff2c6b55d7536b Mon Sep 17 00:00:00 2001 From: luvs01 Date: Thu, 13 Aug 2026 10:28:53 +0900 Subject: [PATCH] fix(integrations): refuse invalid OMP alias removal --- src/integrations/omp-yaml-source.ts | 7 ++++- tests/integrations-writer.test.ts | 17 ++++++++++++ tests/management-integration-routes.test.ts | 30 +++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/integrations/omp-yaml-source.ts b/src/integrations/omp-yaml-source.ts index 94c4972eb4..2a0dc3839d 100644 --- a/src/integrations/omp-yaml-source.ts +++ b/src/integrations/omp-yaml-source.ts @@ -200,7 +200,12 @@ export function patchOmpYamlSource( let patched = `${text.slice(0, startOffset)}${text.slice(endOffset)}`; if (mutation.removeEmptyProviders) { - const remaining = Bun.YAML.parse(patched) as { providers?: unknown } | null; + let remaining: { providers?: unknown } | null; + try { + remaining = Bun.YAML.parse(patched) as { providers?: unknown } | null; + } catch { + return null; + } if (remaining && Object.hasOwn(remaining, "providers")) { const provider = remaining.providers; const empty = provider === null || ( diff --git a/tests/integrations-writer.test.ts b/tests/integrations-writer.test.ts index 642c9eecec..34f3cd079e 100644 --- a/tests/integrations-writer.test.ts +++ b/tests/integrations-writer.test.ts @@ -459,6 +459,23 @@ describe("OMP source preservation", () => { if (!result.ok) expect(result.reason).toBe("unsafe"); expect(readFileSync(configPath, "utf8")).toBe(edited); }); + + test("refuses disable when removing the managed block would break a YAML alias", () => { + const configPath = installOmp(); + expect(applyIntegration(input({ clientId: "omp" })).ok).toBe(true); + const edited = readFileSync(configPath, "utf8") + .replace(" baseUrl:", " baseUrl: &opencodex_url") + .concat("settings:\n inheritedBase: *opencodex_url\n"); + expect(edited).toContain(" baseUrl: &opencodex_url"); + writeFileSync(configPath, edited); + + const journalBefore = store.listOperations("omp"); + const result = disableIntegration(input({ clientId: "omp" })); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.reason).toBe("unsafe"); + expect(readFileSync(configPath, "utf8")).toBe(edited); + expect(store.listOperations("omp")).toEqual(journalBefore); + }); }); describe("restore", () => { diff --git a/tests/management-integration-routes.test.ts b/tests/management-integration-routes.test.ts index cbfca690df..0d573c1ff9 100644 --- a/tests/management-integration-routes.test.ts +++ b/tests/management-integration-routes.test.ts @@ -113,6 +113,13 @@ function installHermes(): string { return spec.configPath(routeEnv, home); } +function installOmp(): string { + const spec = INTEGRATION_CLIENTS.omp; + const dir = spec.detectDir(routeEnv, home); + mkdirSync(dir, { recursive: true }); + return spec.configPath(routeEnv, home); +} + function hermesConfigPath(): string { return INTEGRATION_CLIENTS.hermes.configPath(routeEnv, home); } @@ -412,6 +419,29 @@ function bookkeeping(): Pick { + test("invalid OMP alias removal returns unsafe without changing bytes or journal", async () => { + const configPath = installOmp(); + expect((await put("omp", true)).status).toBe(200); + const edited = readFileSync(configPath, "utf8") + .replace(" baseUrl:", " baseUrl: &opencodex_url") + .concat("settings:\n inheritedBase: *opencodex_url\n"); + expect(edited).toContain(" baseUrl: &opencodex_url"); + writeFileSync(configPath, edited); + const journalBefore = store.listOperations("omp"); + + const response = await put("omp", false); + expect(response.status).toBe(409); + expect(await response.json()).toMatchObject({ + error: "integration config is unsafe", + code: "integration_unsafe", + clientId: "omp", + state: "unsafe", + reason: "unsafe", + }); + expect(readFileSync(configPath, "utf8")).toBe(edited); + expect(store.listOperations("omp")).toEqual(journalBefore); + }); + test("conflict rejects disable without changing a managed-field edit", async () => { const configPath = installHermes(); expect((await put("hermes", true)).status).toBe(200);