From 438da9596e5a32c91136108dc4accb2228e53110 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 28 Apr 2026 08:19:12 +0100 Subject: [PATCH] test: expand fast lane coverage --- src/crestodian/assistant.configured.test.ts | 27 +++----- src/crestodian/assistant.ts | 26 ++++++-- test/vitest-unit-fast-config.test.ts | 22 +++++++ test/vitest/vitest.unit-fast-paths.mjs | 72 ++++++++++++++++----- 4 files changed, 109 insertions(+), 38 deletions(-) diff --git a/src/crestodian/assistant.configured.test.ts b/src/crestodian/assistant.configured.test.ts index 08dcea4a560..f300c953984 100644 --- a/src/crestodian/assistant.configured.test.ts +++ b/src/crestodian/assistant.configured.test.ts @@ -1,22 +1,9 @@ import { describe, expect, it, vi } from "vitest"; - -const readConfigFileSnapshotMock = vi.hoisted(() => vi.fn()); -const prepareSimpleCompletionModelForAgentMock = vi.hoisted(() => vi.fn()); - -vi.mock("../config/config.js", () => ({ - readConfigFileSnapshot: readConfigFileSnapshotMock, -})); - -vi.mock("../agents/simple-completion-runtime.js", () => ({ - prepareSimpleCompletionModelForAgent: prepareSimpleCompletionModelForAgentMock, - completeWithPreparedSimpleCompletionModel: vi.fn(), -})); - -const { planCrestodianCommandWithConfiguredModel } = await import("./assistant.js"); +import { planCrestodianCommandWithConfiguredModel } from "./assistant.js"; describe("Crestodian configured-model planner", () => { it("skips the configured model path when no config file exists", async () => { - readConfigFileSnapshotMock.mockResolvedValue({ + const readConfigFileSnapshot = vi.fn(async () => ({ path: "/tmp/openclaw.json", exists: false, raw: null, @@ -27,8 +14,10 @@ describe("Crestodian configured-model planner", () => { runtimeConfig: {}, config: {}, issues: [], + legacyIssues: [], warnings: [], - }); + })); + const prepareSimpleCompletionModelForAgent = vi.fn(); await expect( planCrestodianCommandWithConfiguredModel({ @@ -58,9 +47,13 @@ describe("Crestodian configured-model planner", () => { sourceUrl: "https://github.com/openclaw/openclaw", }, }, + deps: { + readConfigFileSnapshot, + prepareSimpleCompletionModelForAgent, + }, }), ).resolves.toBeNull(); - expect(prepareSimpleCompletionModelForAgentMock).not.toHaveBeenCalled(); + expect(prepareSimpleCompletionModelForAgent).not.toHaveBeenCalled(); }); }); diff --git a/src/crestodian/assistant.ts b/src/crestodian/assistant.ts index 74d96b631f5..ec01c3777ad 100644 --- a/src/crestodian/assistant.ts +++ b/src/crestodian/assistant.ts @@ -33,6 +33,15 @@ export type CrestodianAssistantPlanner = (params: { type RunCliAgentFn = typeof import("../agents/cli-runner.js").runCliAgent; type RunEmbeddedPiAgentFn = typeof import("../agents/pi-embedded.js").runEmbeddedPiAgent; +type ReadConfigFileSnapshotFn = typeof readConfigFileSnapshot; +type PrepareSimpleCompletionModelForAgentFn = typeof prepareSimpleCompletionModelForAgent; +type CompleteWithPreparedSimpleCompletionModelFn = typeof completeWithPreparedSimpleCompletionModel; + +export type CrestodianConfiguredModelPlannerDeps = { + readConfigFileSnapshot?: ReadConfigFileSnapshotFn; + prepareSimpleCompletionModelForAgent?: PrepareSimpleCompletionModelForAgentFn; + completeWithPreparedSimpleCompletionModel?: CompleteWithPreparedSimpleCompletionModelFn; +}; export type CrestodianLocalRuntimePlannerDeps = { runCliAgent?: RunCliAgentFn; @@ -41,10 +50,13 @@ export type CrestodianLocalRuntimePlannerDeps = { removeTempDir?: (dir: string) => Promise; }; +export type CrestodianPlannerDeps = CrestodianConfiguredModelPlannerDeps & + CrestodianLocalRuntimePlannerDeps; + export async function planCrestodianCommand(params: { input: string; overview: CrestodianOverview; - deps?: CrestodianLocalRuntimePlannerDeps; + deps?: CrestodianPlannerDeps; }): Promise { const configured = await planCrestodianCommandWithConfiguredModel(params); if (configured) { @@ -56,18 +68,21 @@ export async function planCrestodianCommand(params: { export async function planCrestodianCommandWithConfiguredModel(params: { input: string; overview: CrestodianOverview; + deps?: CrestodianConfiguredModelPlannerDeps; }): Promise { const input = params.input.trim(); if (!input) { return null; } - const snapshot = await readConfigFileSnapshot(); + const snapshot = await (params.deps?.readConfigFileSnapshot ?? readConfigFileSnapshot)(); if (!snapshot.exists || !snapshot.valid) { return null; } const cfg = snapshot.runtimeConfig ?? snapshot.config; const agentId = resolveDefaultAgentId(cfg); - const prepared = await prepareSimpleCompletionModelForAgent({ + const prepared = await ( + params.deps?.prepareSimpleCompletionModelForAgent ?? prepareSimpleCompletionModelForAgent + )({ cfg, agentId, allowMissingApiKeyModes: ["aws-sdk"], @@ -79,7 +94,10 @@ export async function planCrestodianCommandWithConfiguredModel(params: { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), CRESTODIAN_ASSISTANT_TIMEOUT_MS); try { - const response = await completeWithPreparedSimpleCompletionModel({ + const response = await ( + params.deps?.completeWithPreparedSimpleCompletionModel ?? + completeWithPreparedSimpleCompletionModel + )({ model: prepared.model, auth: prepared.auth, context: { diff --git a/test/vitest-unit-fast-config.test.ts b/test/vitest-unit-fast-config.test.ts index bf70e2a01be..76ff0f40196 100644 --- a/test/vitest-unit-fast-config.test.ts +++ b/test/vitest-unit-fast-config.test.ts @@ -26,8 +26,30 @@ describe("unit-fast vitest lane", () => { expect(config.test?.include).toContain("src/acp/control-plane/runtime-cache.test.ts"); expect(config.test?.include).toContain("src/acp/runtime/registry.test.ts"); expect(config.test?.include).toContain("src/commands/status-overview-values.test.ts"); + expect(config.test?.include).toContain("src/entry.respawn.test.ts"); + expect(config.test?.include).toContain("src/entry.version-fast-path.test.ts"); expect(config.test?.include).toContain("src/flows/doctor-startup-channel-maintenance.test.ts"); + expect(config.test?.include).toContain("src/crestodian/rescue-policy.test.ts"); + expect(config.test?.include).toContain("src/crestodian/assistant.configured.test.ts"); + expect(config.test?.include).toContain("src/flows/search-setup.test.ts"); + expect(config.test?.include).toContain("src/memory-host-sdk/host/mirror.test.ts"); expect(config.test?.include).toContain("src/plugins/config-policy.test.ts"); + expect(config.test?.include).toContain("src/proxy-capture/proxy-server.test.ts"); + expect(config.test?.include).toContain("src/realtime-voice/agent-consult-tool.test.ts"); + expect(config.test?.include).toContain("src/sessions/transcript-events.test.ts"); + expect(config.test?.include).toContain( + "src/security/audit-channel-source-config-slack.test.ts", + ); + expect(config.test?.include).toContain("src/security/audit-config-symlink.test.ts"); + expect(config.test?.include).toContain("src/security/audit-exec-sandbox-host.test.ts"); + expect(config.test?.include).toContain("src/security/audit-gateway-auth-selection.test.ts"); + expect(config.test?.include).toContain("src/security/audit-gateway-http-auth.test.ts"); + expect(config.test?.include).toContain("src/security/audit-gateway-tools-http.test.ts"); + expect(config.test?.include).toContain("src/security/audit-plugin-readonly-scope.test.ts"); + expect(config.test?.include).toContain("src/security/audit-loopback-logging.test.ts"); + expect(config.test?.include).toContain("src/security/audit-sandbox-browser.test.ts"); + expect(config.test?.include).toContain("src/ui-app-settings.agents-files-refresh.test.ts"); + expect(config.test?.include).toContain("src/video-generation/provider-registry.test.ts"); expect(config.test?.include).toContain("src/plugin-sdk/provider-entry.test.ts"); expect(config.test?.include).toContain("src/security/dangerous-config-flags.test.ts"); expect(config.test?.include).toContain("src/security/safe-regex.test.ts"); diff --git a/test/vitest/vitest.unit-fast-paths.mjs b/test/vitest/vitest.unit-fast-paths.mjs index 772cc9205cb..284d1bcea7c 100644 --- a/test/vitest/vitest.unit-fast-paths.mjs +++ b/test/vitest/vitest.unit-fast-paths.mjs @@ -75,9 +75,11 @@ export const forcedUnitFastTestFiles = [ "src/browser-lifecycle-cleanup.test.ts", "src/canvas-host/server.test.ts", "src/crestodian/audit.test.ts", + "src/crestodian/assistant.configured.test.ts", "src/crestodian/crestodian.test.ts", "src/crestodian/operations.test.ts", "src/crestodian/overview.test.ts", + "src/crestodian/rescue-policy.test.ts", "src/crestodian/rescue-message.test.ts", "src/crestodian/tui-backend.test.ts", "src/flows/channel-setup.test.ts", @@ -91,8 +93,11 @@ export const forcedUnitFastTestFiles = [ "src/docker-image-digests.test.ts", "src/dockerfile.test.ts", "src/entry.compile-cache.test.ts", + "src/entry.respawn.test.ts", + "src/entry.version-fast-path.test.ts", "src/entry.test.ts", "src/flows/doctor-startup-channel-maintenance.test.ts", + "src/flows/search-setup.test.ts", "src/i18n/registry.test.ts", "src/image-generation/openai-compatible-image-provider.test.ts", "src/install-sh-version.test.ts", @@ -103,6 +108,7 @@ export const forcedUnitFastTestFiles = [ "src/memory-host-sdk/host/batch-http.test.ts", "src/memory-host-sdk/host/backend-config.test.ts", "src/memory-host-sdk/host/embeddings-remote-fetch.test.ts", + "src/memory-host-sdk/host/mirror.test.ts", "src/memory-host-sdk/host/post-json.test.ts", "src/memory-host-sdk/host/session-files.test.ts", "src/music-generation/runtime.test.ts", @@ -118,18 +124,26 @@ export const forcedUnitFastTestFiles = [ "src/plugin-activation-boundary.test.ts", "src/plugin-sdk/memory-host-events.test.ts", "src/proxy-capture/runtime.test.ts", + "src/proxy-capture/proxy-server.test.ts", "src/proxy-capture/store.sqlite.test.ts", "src/realtime-voice/agent-consult-runtime.test.ts", "src/realtime-voice/session-runtime.test.ts", "src/security/audit-channel-dm-policy.test.ts", + "src/security/audit-channel-source-config-slack.test.ts", "src/security/audit-channel-readonly-resolution.test.ts", + "src/security/audit-config-symlink.test.ts", "src/security/audit-exec-surface.test.ts", + "src/security/audit-exec-sandbox-host.test.ts", "src/security/audit-exec-safe-bins.test.ts", "src/security/dangerous-config-flags.test.ts", "src/security/audit-extra.sync.test.ts", "src/security/audit-filesystem-windows.test.ts", "src/security/audit-gateway-exposure.test.ts", + "src/security/audit-gateway-auth-selection.test.ts", + "src/security/audit-gateway-http-auth.test.ts", + "src/security/audit-gateway-tools-http.test.ts", "src/security/audit-sandbox-docker-config.test.ts", + "src/security/audit-sandbox-browser.test.ts", "src/security/safe-regex.test.ts", "src/security/audit-small-model-risk.test.ts", "src/security/audit-node-command-findings.test.ts", @@ -137,6 +151,8 @@ export const forcedUnitFastTestFiles = [ "src/security/audit-trust-model.test.ts", "src/security/dm-policy-shared.test.ts", "src/security/audit-plugins-trust.test.ts", + "src/security/audit-plugin-readonly-scope.test.ts", + "src/security/audit-loopback-logging.test.ts", "src/security/audit-workspace-skill-escape.test.ts", "src/security/external-content.test.ts", "src/security/fix.test.ts", @@ -144,7 +160,9 @@ export const forcedUnitFastTestFiles = [ "src/security/skill-scanner.test.ts", "src/security/audit-config-include-perms.test.ts", "src/realtime-transcription/websocket-session.test.ts", + "src/realtime-voice/agent-consult-tool.test.ts", "src/routing/resolve-route.test.ts", + "src/sessions/transcript-events.test.ts", "src/security/windows-acl.test.ts", "src/trajectory/cleanup.test.ts", "src/trajectory/export.test.ts", @@ -154,6 +172,7 @@ export const forcedUnitFastTestFiles = [ "src/tts/provider-registry.test.ts", "src/tts/status-config.test.ts", "src/tts/tts-config.test.ts", + "src/ui-app-settings.agents-files-refresh.test.ts", "src/terminal/restore.test.ts", "src/terminal/table.test.ts", "src/test-helpers/state-dir-env.test.ts", @@ -161,6 +180,7 @@ export const forcedUnitFastTestFiles = [ "src/test-utils/temp-home.test.ts", "src/utils.test.ts", "src/version.test.ts", + "src/video-generation/provider-registry.test.ts", ]; const forcedUnitFastTestFileSet = new Set(forcedUnitFastTestFiles); const unitFastCandidateExactFiles = [...pluginSdkLightTestFiles, ...commandsLightTestFiles]; @@ -269,6 +289,21 @@ function walkFiles(directory, files = []) { return files; } +const walkedTestFilesByCwd = new Map(); + +function collectRepoTestFiles(cwd) { + const normalizedCwd = normalizeRepoPath(cwd); + const cached = walkedTestFilesByCwd.get(normalizedCwd); + if (cached) { + return cached; + } + const files = ["src", "packages", "test"] + .flatMap((directory) => walkFiles(path.join(cwd, directory))) + .map((file) => normalizeRepoPath(path.relative(cwd, file))); + walkedTestFilesByCwd.set(normalizedCwd, files); + return files; +} + export function classifyUnitFastTestFileContent(source) { const reasons = []; for (const { code, pattern } of disqualifyingPatterns) { @@ -280,39 +315,40 @@ export function classifyUnitFastTestFileContent(source) { } export function collectUnitFastTestCandidates(cwd = process.cwd()) { - const discovered = ["src", "packages", "test"] - .flatMap((directory) => walkFiles(path.join(cwd, directory))) - .map((file) => normalizeRepoPath(path.relative(cwd, file))) - .filter( - (file) => - matchesAnyGlob(file, unitFastCandidateGlobs) && - !matchesAnyGlob(file, broadUnitFastCandidateSkipGlobs), - ); + const discovered = collectRepoTestFiles(cwd).filter( + (file) => + matchesAnyGlob(file, unitFastCandidateGlobs) && + !matchesAnyGlob(file, broadUnitFastCandidateSkipGlobs), + ); return [ ...new Set([...discovered, ...unitFastCandidateExactFiles, ...forcedUnitFastTestFiles]), ].toSorted((a, b) => a.localeCompare(b)); } export function collectBroadUnitFastTestCandidates(cwd = process.cwd()) { - const discovered = ["src", "packages", "test"] - .flatMap((directory) => walkFiles(path.join(cwd, directory))) - .map((file) => normalizeRepoPath(path.relative(cwd, file))) - .filter( - (file) => - matchesAnyGlob(file, broadUnitFastCandidateGlobs) && - !matchesAnyGlob(file, broadUnitFastCandidateSkipGlobs), - ); + const discovered = collectRepoTestFiles(cwd).filter( + (file) => + matchesAnyGlob(file, broadUnitFastCandidateGlobs) && + !matchesAnyGlob(file, broadUnitFastCandidateSkipGlobs), + ); return [ ...new Set([...discovered, ...unitFastCandidateExactFiles, ...forcedUnitFastTestFiles]), ].toSorted((a, b) => a.localeCompare(b)); } +const unitFastAnalysisByKey = new Map(); + export function collectUnitFastTestFileAnalysis(cwd = process.cwd(), options = {}) { + const cacheKey = `${normalizeRepoPath(cwd)}\0${options.scope ?? "default"}`; + const cached = unitFastAnalysisByKey.get(cacheKey); + if (cached) { + return cached; + } const candidates = options.scope === "broad" ? collectBroadUnitFastTestCandidates(cwd) : collectUnitFastTestCandidates(cwd); - return candidates.map((file) => { + const analysis = candidates.map((file) => { const absolutePath = path.join(cwd, file); let source = ""; try { @@ -333,6 +369,8 @@ export function collectUnitFastTestFileAnalysis(cwd = process.cwd(), options = { reasons, }; }); + unitFastAnalysisByKey.set(cacheKey, analysis); + return analysis; } let cachedUnitFastTestFiles = null;