mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
test: add focused seams for faster isolated tests
This commit is contained in:
@@ -369,6 +369,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
|
||||
afterEach(async () => {
|
||||
__testing.resetCodexAppServerClientFactoryForTests();
|
||||
__testing.resetOpenClawCodingToolsFactoryForTests();
|
||||
resetCodexRateLimitCacheForTests();
|
||||
nativeHookRelayTesting.clearNativeHookRelaysForTests();
|
||||
resetAgentEventsForTest();
|
||||
@@ -475,6 +476,18 @@ describe("runCodexAppServerAttempt", () => {
|
||||
params.config = { tools: { profile: "coding" } };
|
||||
params.sourceReplyDeliveryMode = "message_tool_only";
|
||||
params.messageProvider = "whatsapp";
|
||||
let seenForceMessageTool: boolean | undefined;
|
||||
__testing.setOpenClawCodingToolsFactoryForTests((options) => {
|
||||
seenForceMessageTool = options?.forceMessageTool;
|
||||
return [
|
||||
{
|
||||
name: "message",
|
||||
description: "message test tool",
|
||||
parameters: { type: "object", properties: {} },
|
||||
execute: vi.fn(),
|
||||
},
|
||||
] as never;
|
||||
});
|
||||
|
||||
const dynamicTools = await __testing.buildDynamicTools({
|
||||
params,
|
||||
@@ -489,6 +502,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
});
|
||||
const dynamicToolNames = dynamicTools.map((tool) => tool.name);
|
||||
|
||||
expect(seenForceMessageTool).toBe(true);
|
||||
expect(dynamicToolNames).toContain("message");
|
||||
});
|
||||
|
||||
@@ -517,6 +531,18 @@ describe("runCodexAppServerAttempt", () => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
let seenRunSessionKey: string | undefined;
|
||||
__testing.setOpenClawCodingToolsFactoryForTests((options) => {
|
||||
seenRunSessionKey = options?.runSessionKey;
|
||||
return [
|
||||
{
|
||||
name: "session_status",
|
||||
description: "session status test tool",
|
||||
parameters: { type: "object", properties: {} },
|
||||
execute: vi.fn(async () => ({ details: { sessionKey: options?.runSessionKey } })),
|
||||
},
|
||||
] as never;
|
||||
});
|
||||
|
||||
const dynamicTools = await __testing.buildDynamicTools({
|
||||
params,
|
||||
@@ -533,6 +559,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
|
||||
expect(sessionStatus).toBeDefined();
|
||||
const result = await sessionStatus?.execute("call-current", { sessionKey: "current" });
|
||||
expect(seenRunSessionKey).toBe("agent:main:main");
|
||||
expect((result?.details as { sessionKey?: string } | undefined)?.sessionKey).toBe(
|
||||
"agent:main:main",
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import { createHash } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
@@ -46,8 +47,8 @@ import {
|
||||
resolveCodexAppServerAuthProfileIdForAgent,
|
||||
} from "./auth-bridge.js";
|
||||
import {
|
||||
createCodexAppServerClientFactoryTestHooks,
|
||||
defaultCodexAppServerClientFactory,
|
||||
type CodexAppServerClientFactory,
|
||||
} from "./client-factory.js";
|
||||
import {
|
||||
isCodexAppServerApprovalRequest,
|
||||
@@ -126,8 +127,16 @@ const CODEX_BOOTSTRAP_CONTEXT_ORDER = new Map<string, number>([
|
||||
type OpenClawCodingToolsOptions = NonNullable<
|
||||
Parameters<(typeof import("openclaw/plugin-sdk/agent-harness"))["createOpenClawCodingTools"]>[0]
|
||||
>;
|
||||
type OpenClawCodingToolsFactory =
|
||||
(typeof import("openclaw/plugin-sdk/agent-harness"))["createOpenClawCodingTools"];
|
||||
|
||||
let clientFactory = defaultCodexAppServerClientFactory;
|
||||
const testClientFactoryStorage = new AsyncLocalStorage<CodexAppServerClientFactory | undefined>();
|
||||
const clientFactory = defaultCodexAppServerClientFactory;
|
||||
let openClawCodingToolsFactoryForTests: OpenClawCodingToolsFactory | undefined;
|
||||
|
||||
function resolveCodexAppServerClientFactory(): CodexAppServerClientFactory {
|
||||
return testClientFactoryStorage.getStore() ?? clientFactory;
|
||||
}
|
||||
|
||||
function emitCodexAppServerEvent(
|
||||
params: EmbeddedRunAttemptParams,
|
||||
@@ -351,7 +360,7 @@ export async function runCodexAppServerAttempt(
|
||||
} = {},
|
||||
): Promise<EmbeddedRunAttemptResult> {
|
||||
const attemptStartedAt = Date.now();
|
||||
const attemptClientFactory = clientFactory;
|
||||
const attemptClientFactory = resolveCodexAppServerClientFactory();
|
||||
const pluginConfig = readCodexPluginConfig(options.pluginConfig);
|
||||
const appServer = resolveCodexAppServerRuntimeOptions({ pluginConfig });
|
||||
const resolvedWorkspace = resolveUserPath(params.workspaceDir);
|
||||
@@ -1478,7 +1487,9 @@ async function buildDynamicTools(input: DynamicToolBuildParams) {
|
||||
}
|
||||
const modelHasVision = params.model.input?.includes("image") ?? false;
|
||||
const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, input.sessionAgentId);
|
||||
const { createOpenClawCodingTools } = await import("openclaw/plugin-sdk/agent-harness");
|
||||
const createOpenClawCodingTools =
|
||||
openClawCodingToolsFactoryForTests ??
|
||||
(await import("openclaw/plugin-sdk/agent-harness")).createOpenClawCodingTools;
|
||||
const allTools = createOpenClawCodingTools({
|
||||
agentId: input.sessionAgentId,
|
||||
...buildEmbeddedAttemptToolRunContext(params),
|
||||
@@ -1963,7 +1974,16 @@ export const __testing = {
|
||||
buildDynamicTools,
|
||||
filterToolsForVisionInputs,
|
||||
handleDynamicToolCallWithTimeout,
|
||||
...createCodexAppServerClientFactoryTestHooks((factory) => {
|
||||
clientFactory = factory;
|
||||
}),
|
||||
setOpenClawCodingToolsFactoryForTests(factory: OpenClawCodingToolsFactory): void {
|
||||
openClawCodingToolsFactoryForTests = factory;
|
||||
},
|
||||
resetOpenClawCodingToolsFactoryForTests(): void {
|
||||
openClawCodingToolsFactoryForTests = undefined;
|
||||
},
|
||||
setCodexAppServerClientFactoryForTests(factory: CodexAppServerClientFactory): void {
|
||||
testClientFactoryStorage.enterWith(factory);
|
||||
},
|
||||
resetCodexAppServerClientFactoryForTests(): void {
|
||||
testClientFactoryStorage.enterWith(undefined);
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -12,11 +12,16 @@ const PROVIDER_RUNTIME_CANDIDATES = [
|
||||
] as const;
|
||||
|
||||
let providerRuntimeModule: ProviderRuntimeModule | undefined;
|
||||
let providerRuntimeLoadAttempted = false;
|
||||
|
||||
function loadProviderRuntime(): ProviderRuntimeModule | null {
|
||||
if (providerRuntimeModule) {
|
||||
return providerRuntimeModule;
|
||||
}
|
||||
if (providerRuntimeLoadAttempted) {
|
||||
return null;
|
||||
}
|
||||
providerRuntimeLoadAttempted = true;
|
||||
for (const candidate of PROVIDER_RUNTIME_CANDIDATES) {
|
||||
try {
|
||||
providerRuntimeModule = require(candidate) as ProviderRuntimeModule;
|
||||
|
||||
@@ -14,7 +14,7 @@ process.env.FORCE_COLOR = "0";
|
||||
|
||||
mockSessionsConfig();
|
||||
|
||||
import { sessionsCommand } from "./sessions.js";
|
||||
import { sessionsCommand, __testing } from "./sessions.js";
|
||||
|
||||
describe("sessionsCommand", () => {
|
||||
beforeEach(() => {
|
||||
@@ -226,31 +226,8 @@ describe("sessionsCommand", () => {
|
||||
expect(payload.sessions?.map((row) => row.key)).toEqual(["recent"]);
|
||||
});
|
||||
|
||||
it("limits JSON output to the newest 100 sessions by default", async () => {
|
||||
const entries: Record<string, { sessionId: string; updatedAt: number; model: string }> = {};
|
||||
for (let i = 0; i < 105; i += 1) {
|
||||
entries[`session-${String(i).padStart(3, "0")}`] = {
|
||||
sessionId: `session-${i}`,
|
||||
updatedAt: Date.now() - i * 60_000,
|
||||
model: "pi:opus",
|
||||
};
|
||||
}
|
||||
const store = writeStore(entries, "sessions-default-limit");
|
||||
|
||||
const payload = await runSessionsJson<{
|
||||
count?: number;
|
||||
totalCount?: number;
|
||||
limitApplied?: number | null;
|
||||
hasMore?: boolean;
|
||||
sessions?: Array<{ key: string }>;
|
||||
}>(sessionsCommand, store);
|
||||
|
||||
expect(payload.count).toBe(100);
|
||||
expect(payload.totalCount).toBe(105);
|
||||
expect(payload.limitApplied).toBe(100);
|
||||
expect(payload.hasMore).toBe(true);
|
||||
expect(payload.sessions?.at(0)?.key).toBe("session-000");
|
||||
expect(payload.sessions?.some((row) => row.key === "session-104")).toBe(false);
|
||||
it("uses a default JSON output limit of 100 sessions", () => {
|
||||
expect(__testing.parseSessionsLimit(undefined)).toBe(100);
|
||||
});
|
||||
|
||||
it("honors explicit JSON output limits", async () => {
|
||||
|
||||
@@ -415,3 +415,7 @@ export async function sessionsCommand(
|
||||
runtime.log(line.trimEnd());
|
||||
}
|
||||
}
|
||||
|
||||
export const __testing = {
|
||||
parseSessionsLimit,
|
||||
} as const;
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
getResolvedLoggerSettings,
|
||||
isFileLogLevelEnabled,
|
||||
resetLogger,
|
||||
setLoggerConfigLoaderForTests,
|
||||
setLoggerOverride,
|
||||
toPinoLikeLogger,
|
||||
} from "./logging/logger.js";
|
||||
@@ -50,6 +51,7 @@ export {
|
||||
getResolvedLoggerSettings,
|
||||
isFileLogLevelEnabled,
|
||||
resetLogger,
|
||||
setLoggerConfigLoaderForTests,
|
||||
setLoggerOverride,
|
||||
toPinoLikeLogger,
|
||||
createSubsystemLogger,
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { readLoggingConfigMock, shouldSkipMutatingLoggingConfigReadMock } = vi.hoisted(() => ({
|
||||
readLoggingConfigMock: vi.fn<() => unknown>(() => undefined),
|
||||
shouldSkipMutatingLoggingConfigReadMock: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
vi.mock("./config.js", () => ({
|
||||
readLoggingConfig: readLoggingConfigMock,
|
||||
shouldSkipMutatingLoggingConfigRead: shouldSkipMutatingLoggingConfigReadMock,
|
||||
}));
|
||||
|
||||
let originalTestFileLog: string | undefined;
|
||||
let originalOpenClawLogLevel: string | undefined;
|
||||
let logging: typeof import("../logging.js");
|
||||
@@ -23,10 +13,6 @@ beforeEach(() => {
|
||||
originalOpenClawLogLevel = process.env.OPENCLAW_LOG_LEVEL;
|
||||
delete process.env.OPENCLAW_TEST_FILE_LOG;
|
||||
delete process.env.OPENCLAW_LOG_LEVEL;
|
||||
readLoggingConfigMock.mockReset();
|
||||
readLoggingConfigMock.mockReturnValue(undefined);
|
||||
shouldSkipMutatingLoggingConfigReadMock.mockReset();
|
||||
shouldSkipMutatingLoggingConfigReadMock.mockReturnValue(false);
|
||||
logging.resetLogger();
|
||||
logging.setLoggerOverride(null);
|
||||
});
|
||||
@@ -44,23 +30,28 @@ afterEach(() => {
|
||||
}
|
||||
logging.resetLogger();
|
||||
logging.setLoggerOverride(null);
|
||||
logging.setLoggerConfigLoaderForTests();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("getResolvedLoggerSettings", () => {
|
||||
it("uses a silent fast path in default Vitest mode without config reads", () => {
|
||||
const readLoggingConfig = vi.fn(() => undefined);
|
||||
logging.setLoggerConfigLoaderForTests(readLoggingConfig);
|
||||
|
||||
const settings = logging.getResolvedLoggerSettings();
|
||||
|
||||
expect(settings.level).toBe("silent");
|
||||
expect(readLoggingConfigMock).not.toHaveBeenCalled();
|
||||
expect(readLoggingConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reads logging config when test file logging is explicitly enabled", () => {
|
||||
process.env.OPENCLAW_TEST_FILE_LOG = "1";
|
||||
readLoggingConfigMock.mockReturnValue({
|
||||
logging.setLoggerConfigLoaderForTests(() => ({
|
||||
level: "debug",
|
||||
file: "/tmp/openclaw-configured.log",
|
||||
maxFileBytes: 2048,
|
||||
});
|
||||
}));
|
||||
|
||||
const settings = logging.getResolvedLoggerSettings();
|
||||
|
||||
@@ -71,9 +62,9 @@ describe("getResolvedLoggerSettings", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses defaults when config schema skips logging config reads", () => {
|
||||
it("uses defaults when no logging config is available", () => {
|
||||
process.env.OPENCLAW_TEST_FILE_LOG = "1";
|
||||
shouldSkipMutatingLoggingConfigReadMock.mockReturnValue(true);
|
||||
logging.setLoggerConfigLoaderForTests(() => undefined);
|
||||
|
||||
const settings = logging.getResolvedLoggerSettings();
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ type ResolvedSettings = {
|
||||
};
|
||||
export type LoggerResolvedSettings = ResolvedSettings;
|
||||
type TsLogRecord = Record<string, unknown>;
|
||||
type LoggerConfigLoader = () => OpenClawConfig["logging"] | undefined;
|
||||
|
||||
type DiagnosticLogCode = {
|
||||
line?: number;
|
||||
@@ -78,6 +79,15 @@ type DiagnosticLogCode = {
|
||||
|
||||
const MAX_DIAGNOSTIC_LOG_BINDINGS_JSON_CHARS = 8 * 1024;
|
||||
const MAX_DIAGNOSTIC_LOG_MESSAGE_CHARS = 4 * 1024;
|
||||
|
||||
const loadLoggerConfigDefault: LoggerConfigLoader = () => readLoggingConfig();
|
||||
let loadLoggerConfig: LoggerConfigLoader = loadLoggerConfigDefault;
|
||||
|
||||
export function setLoggerConfigLoaderForTests(loader?: LoggerConfigLoader): void {
|
||||
loadLoggerConfig = loader ?? loadLoggerConfigDefault;
|
||||
loggingState.cachedLogger = null;
|
||||
loggingState.cachedSettings = null;
|
||||
}
|
||||
const MAX_DIAGNOSTIC_LOG_ATTRIBUTE_COUNT = 32;
|
||||
const MAX_DIAGNOSTIC_LOG_ATTRIBUTE_VALUE_CHARS = 2 * 1024;
|
||||
const MAX_DIAGNOSTIC_LOG_NAME_CHARS = 120;
|
||||
@@ -473,7 +483,7 @@ function resolveSettings(): ResolvedSettings {
|
||||
}
|
||||
|
||||
const cfg: OpenClawConfig["logging"] | undefined =
|
||||
(loggingState.overrideSettings as LoggerSettings | null) ?? readLoggingConfig();
|
||||
(loggingState.overrideSettings as LoggerSettings | null) ?? loadLoggerConfig();
|
||||
const defaultLevel =
|
||||
process.env.VITEST === "true" && process.env.OPENCLAW_TEST_FILE_LOG !== "1" ? "silent" : "info";
|
||||
const fromConfig = normalizeLogLevel(cfg?.level, defaultLevel);
|
||||
@@ -670,6 +680,7 @@ export function resetLogger() {
|
||||
loggingState.cachedSettings = null;
|
||||
loggingState.cachedConsoleSettings = null;
|
||||
loggingState.overrideSettings = null;
|
||||
loadLoggerConfig = loadLoggerConfigDefault;
|
||||
}
|
||||
|
||||
export const __test__ = {
|
||||
|
||||
@@ -87,7 +87,11 @@ export {
|
||||
} from "../agents/pi-embedded-subscribe.tools.js";
|
||||
export { normalizeUsage } from "../agents/usage.js";
|
||||
export { resolveOpenClawAgentDir } from "./agent-dir-compat.js";
|
||||
export { resolveAgentDir, resolveSessionAgentIds } from "../agents/agent-scope.js";
|
||||
export {
|
||||
resolveAgentDir,
|
||||
resolveDefaultAgentDir,
|
||||
resolveSessionAgentIds,
|
||||
} from "../agents/agent-scope.js";
|
||||
export { resolveModelAuthMode } from "../agents/model-auth.js";
|
||||
export { supportsModelTools } from "../agents/model-tool-support.js";
|
||||
export { resolveAttemptSpawnWorkspaceDir } from "../agents/pi-embedded-runner/run/attempt.thread-helpers.js";
|
||||
|
||||
Reference in New Issue
Block a user