Files
OmniRoute/@omniroute/opencode-plugin/tests/log-level.test.ts
小妍儿 ✨ fa923d974e fix(opencode-plugin): respect log level for lifecycle output (#8982) (#9316)
* test(opencode-plugin): cover configured log levels

* fix(opencode-plugin): respect lifecycle log level

* fix(opencode-plugin): isolate lifecycle loggers

---------

Co-authored-by: 千乘妍 (Xiaoyaner) <xiaoyaner0201@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
2026-08-13 07:54:47 -03:00

219 lines
6.7 KiB
TypeScript

import assert from "node:assert/strict";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import type { Config } from "@opencode-ai/plugin";
import { createOmniRouteConfigHook, OmniRoutePlugin } from "../src/index.js";
import { getLogLevel, logger, setLogLevel, type LogLevel } from "../src/logger.js";
type ConsoleMethod = "error" | "info" | "log" | "warn";
type ConsoleEntries = Record<ConsoleMethod, unknown[][]>;
const fakeInput = {} as Parameters<typeof OmniRoutePlugin>[0];
const consoleMethods: ConsoleMethod[] = ["error", "info", "log", "warn"];
async function captureConsole(run: () => Promise<void>): Promise<ConsoleEntries> {
const entries: ConsoleEntries = { error: [], info: [], log: [], warn: [] };
const originals = Object.fromEntries(
consoleMethods.map((method) => [method, console[method]])
) as Record<ConsoleMethod, typeof console.warn>;
for (const method of consoleMethods) {
console[method] = (...args: unknown[]) => {
entries[method].push(args);
};
}
try {
await run();
} finally {
for (const method of consoleMethods) console[method] = originals[method];
}
return entries;
}
function rendered(entries: ConsoleEntries): string[] {
return consoleMethods.flatMap((method) =>
entries[method].map((args) => args.map((arg) => String(arg)).join(" "))
);
}
async function capturePluginLifecycle(args: {
level: LogLevel;
autoSyncIntervalMs: number;
invokeConfig?: boolean;
}): Promise<string[]> {
const previousDataDir = process.env.OPENCODE_DATA_DIR;
const previousLevel = getLogLevel();
const dataDir = await mkdtemp(join(tmpdir(), "omniroute-log-level-"));
process.env.OPENCODE_DATA_DIR = dataDir;
try {
const entries = await captureConsole(async () => {
const hooks = await OmniRoutePlugin(fakeInput, {
autoSyncIntervalMs: args.autoSyncIntervalMs,
features: { logLevel: args.level },
});
if (args.invokeConfig) {
assert.equal(typeof hooks.config, "function");
await hooks.config!({} as Config);
}
});
return rendered(entries);
} finally {
setLogLevel(previousLevel);
if (previousDataDir === undefined) delete process.env.OPENCODE_DATA_DIR;
else process.env.OPENCODE_DATA_DIR = previousDataDir;
await rm(dataDir, { recursive: true, force: true });
}
}
test("logLevel error suppresses the initialization banner", async () => {
const lines = await capturePluginLifecycle({ level: "error", autoSyncIntervalMs: 0 });
assert.equal(lines.filter((line) => line.includes("initialized")).length, 0);
});
test("logLevel error suppresses the auto-sync enabled lifecycle message", async () => {
const lines = await capturePluginLifecycle({ level: "error", autoSyncIntervalMs: 60_000 });
assert.equal(lines.filter((line) => line.includes("auto-sync enabled")).length, 0);
});
test("logLevel error suppresses factory config-shim diagnostics", async () => {
const lines = await capturePluginLifecycle({
level: "error",
autoSyncIntervalMs: 0,
invokeConfig: true,
});
assert.equal(lines.filter((line) => line.includes("config shim skipped")).length, 0);
});
test("logLevel debug preserves startup and config-shim diagnostics", async () => {
const lines = await capturePluginLifecycle({
level: "debug",
autoSyncIntervalMs: 60_000,
invokeConfig: true,
});
assert.ok(
lines.some((line) => line.includes("initialized")),
"initialization banner emitted"
);
assert.ok(
lines.some((line) => line.includes("auto-sync enabled")),
"auto-sync message emitted"
);
assert.ok(
lines.some((line) => line.includes("config shim skipped")),
"config breadcrumb emitted"
);
});
test("debug instance retains config diagnostics after an error instance is created", async () => {
const lines = rendered(
await captureConsole(async () => {
const debugHooks = await OmniRoutePlugin(fakeInput, {
autoSyncIntervalMs: 0,
features: { logLevel: "debug" },
});
await OmniRoutePlugin(fakeInput, {
autoSyncIntervalMs: 0,
features: { logLevel: "error" },
});
await debugHooks.config!({} as Config);
})
);
assert.equal(lines.filter((line) => line.includes("config shim skipped")).length, 1);
});
test("error instance keeps config diagnostics suppressed after a debug instance is created", async () => {
const lines = rendered(
await captureConsole(async () => {
const errorHooks = await OmniRoutePlugin(fakeInput, {
autoSyncIntervalMs: 0,
features: { logLevel: "error" },
});
await OmniRoutePlugin(fakeInput, {
autoSyncIntervalMs: 0,
features: { logLevel: "debug" },
});
await errorHooks.config!({} as Config);
})
);
assert.equal(lines.filter((line) => line.includes("config shim skipped")).length, 0);
});
test("error-level config fetch failures remain visible as concise injected-logger messages", async () => {
const entries: unknown[][] = [];
const hook = createOmniRouteConfigHook(
{
baseURL: "https://omniroute.example/v1",
features: {
autoCombos: false,
diskCache: false,
enrichment: false,
logLevel: "error",
},
},
{
readAuthJson: async () => ({
"opencode-omniroute": { type: "api", key: "test-key" },
}),
fetcher: async () => {
throw new Error("models unavailable");
},
combosFetcher: async () => {
throw new Error("combos unavailable");
},
logger: {
warn: (...args: unknown[]) => {
entries.push(args);
},
},
}
);
await hook({} as Config);
assert.equal(entries.length, 2, "both genuine fetch failures remain visible");
assert.deepEqual(
entries.map((args) => args.length),
[1, 1],
"each failure is emitted as one concise argument"
);
const lines = entries.map(([message]) => String(message));
assert.ok(
lines.some((line) => line.includes("/v1/models") && line.includes("models unavailable"))
);
assert.ok(
lines.some((line) => line.includes("/api/combos") && line.includes("combos unavailable"))
);
assert.equal(
entries.flat().some((arg) => arg instanceof Error),
false,
"no raw Error object emitted"
);
});
test("logger error output remains visible at error level", async () => {
const previousLevel = getLogLevel();
try {
setLogLevel("error");
const lines = rendered(
await captureConsole(async () => {
logger.error("genuine startup failure");
})
);
assert.ok(lines.some((line) => line.includes("genuine startup failure")));
} finally {
setLogLevel(previousLevel);
}
});