diff --git a/src/scripts/test-projects.test.ts b/src/scripts/test-projects.test.ts index 5bc594932e1d..e831f9bb3114 100644 --- a/src/scripts/test-projects.test.ts +++ b/src/scripts/test-projects.test.ts @@ -1,4 +1,5 @@ // Test project script tests cover fixture project discovery and validation. +import { spawnSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -854,167 +855,68 @@ describe("test-projects args", () => { }); it("routes top-level test helpers to importing repo tests", () => { - expect(buildVitestRunPlans(["test/helpers/temp-dir.ts"])).toEqual([ - { - config: "test/vitest/vitest.unit-fast.config.ts", - forwardedArgs: [], - includePatterns: [ - "src/agents/command/attempt-execution.shared.test.ts", - "src/auto-reply/reply/session-entry-persistence.test.ts", - "src/crestodian/operations.test.ts", - "src/install-sh-version.test.ts", - "src/proxy-capture/store.sqlite.test.ts", - "test/scripts/android-version.test.ts", - "test/scripts/resolve-openclaw-ref.test.ts", - ], - watchMode: false, - }, - { - config: "test/vitest/vitest.unit-fast-fake-timers.config.ts", - forwardedArgs: [], - includePatterns: ["src/entry.compile-cache.test.ts"], - watchMode: false, - }, - { - config: "test/vitest/vitest.unit.config.ts", - forwardedArgs: [ - "src/state/openclaw-agent-db.test.ts", - "src/state/openclaw-state-db.test.ts", - "src/state/sqlite-query-plan.test.ts", - "src/transcripts/store.test.ts", - ], - includePatterns: null, - watchMode: false, - }, - { - config: "test/vitest/vitest.tooling.config.ts", - forwardedArgs: [], - includePatterns: [ - "src/scripts/docs-link-audit.test.ts", - "src/scripts/sync-plugin-versions.test.ts", - "test/e2e/qa-lab/runtime/gateway-mcp-real-transports.test.ts", - "test/helpers/temp-dir.test.ts", - "test/scripts/android-pin-version.test.ts", - "test/scripts/bench-cli-startup.test.ts", - "test/scripts/check-package-dist-imports.test.ts", - "test/scripts/check-workflows.test.ts", - "test/scripts/ci-hydrate-testbox-env.test.ts", - "test/scripts/clawhub-fixture-server.test.ts", - "test/scripts/codex-install-assertions.test.ts", - "test/scripts/config-reload-mutate-metadata.test.ts", - "test/scripts/control-ui-i18n.test.ts", - "test/scripts/docs-list.test.ts", - "test/scripts/doctor-install-switch-wrapper.test.ts", - "test/scripts/e2e-shell-tempfiles.test.ts", - "test/scripts/e2e-text-file-utils.test.ts", - "test/scripts/fixture-common.test.ts", - "test/scripts/fixture-plugin-commands.test.ts", - "test/scripts/incremental-line-reader.test.ts", - "test/scripts/ios-configure-signing.test.ts", - "test/scripts/ios-team-id.test.ts", - "test/scripts/ios-version.test.ts", - "test/scripts/kitchen-sink-rpc-walk.test.ts", - "test/scripts/native-app-i18n.test.ts", - "test/scripts/onboard-config-fixtures.test.ts", - "test/scripts/package-git-fixture.test.ts", - "test/scripts/parallels-lib-helpers.test.ts", - "test/scripts/parallels-package-log-progress-extract.test.ts", - "test/scripts/parallels-smoke-model.test.ts", - "test/scripts/plugin-package-dependencies.test.ts", - "test/scripts/plugins-assertions.test.ts", - "test/scripts/prepare-extension-package-boundary-artifacts.test.ts", - "test/scripts/proxy-install-ca.test.ts", - "test/scripts/release-preflight.test.ts", - "test/scripts/render-maturity-docs.test.ts", - "test/scripts/report-test-temp-creations.test.ts", - "test/scripts/runtime-postbuild-stamp.test.ts", - "test/scripts/test-install-sh-docker.test.ts", - "test/scripts/test-projects.test.ts", - "test/test-env.test.ts", - "test/vitest-scoped-config.test.ts", - ], - watchMode: false, - }, - { - config: "test/vitest/vitest.gateway.config.ts", - forwardedArgs: [], - includePatterns: [ - "src/gateway/hooks-mapping.test.ts", - "src/gateway/server-methods/chat.abort-persistence.test.ts", - "src/gateway/server.agent.gateway-server-agent-b.test.ts", - "src/gateway/server.chat.gateway-server-chat-b.test.ts", - "src/gateway/server.sessions.permissions-hooks.test.ts", - "src/gateway/terminal/launch.test.ts", - ], - watchMode: false, - }, - { - config: "test/vitest/vitest.runtime-config.config.ts", - forwardedArgs: [], - includePatterns: ["src/config/sessions/entry-freshness.test.ts"], - watchMode: false, - }, - { - config: "test/vitest/vitest.cron.config.ts", - forwardedArgs: [], - includePatterns: [ - "src/cron/isolated-agent/run-session-state.test.ts", - "src/cron/run-log.error-reason.test.ts", - ], - watchMode: false, - }, - { - config: "test/vitest/vitest.commands.config.ts", - forwardedArgs: [], - includePatterns: [ - "src/commands/doctor-completion.test.ts", - "src/commands/status.scan.shared.test.ts", - ], - watchMode: false, - }, - { - config: "test/vitest/vitest.auto-reply.config.ts", - forwardedArgs: [], - includePatterns: [ - "src/auto-reply/reply/get-reply-native-slash-fast-path.test.ts", - "src/auto-reply/reply/get-reply.auto-fallback.test.ts", - "src/auto-reply/reply/reply-turn-admission.test.ts", - "src/auto-reply/reply/session-updates.test.ts", - ], - watchMode: false, - }, - { - config: "test/vitest/vitest.agents.config.ts", - forwardedArgs: [], - includePatterns: [ - "src/agents/agent-bundle-mcp-runtime.test.ts", - "src/agents/agent-tools-agent-config.exec.test.ts", - "src/agents/bash-tools.exec-foreground-failures.test.ts", - "src/agents/cli-runner.reliability.test.ts", - "src/agents/models-config.file-mode.test.ts", - "src/agents/sandbox/ssh.test.ts", - "src/agents/sessions/tools/find.fd.test.ts", - "src/agents/sessions/tools/read.test.ts", - ], - watchMode: false, - }, - { - config: "test/vitest/vitest.plugins.config.ts", - forwardedArgs: [], - includePatterns: ["src/plugins/git-install.test.ts"], - watchMode: false, - }, - { - config: "test/vitest/vitest.e2e.config.ts", - forwardedArgs: [ - "test/e2e/qa-lab/plugins/plugin-lifecycle-probe.e2e.test.ts", - "test/e2e/qa-lab/runtime/openai-compatible-chat-tools.e2e.test.ts", - "test/openclaw-launcher.e2e.test.ts", - ], - includePatterns: null, - watchMode: false, - }, - ]); + // The importer inventory of test/helpers/temp-dir.ts churns with every new + // test using the helper; frozen full lists broke main on unrelated test + // additions. Assert the routing structure instead of the inventory. + const plans = buildVitestRunPlans(["test/helpers/temp-dir.ts"]); + const planFiles = plans.map((plan) => plan.includePatterns ?? plan.forwardedArgs); + const expandedFiles = planFiles.flat(); + + // Helper targets expand to importing test files; the helper itself never + // reaches Vitest as a raw target. + expect(expandedFiles).toContain("test/helpers/temp-dir.test.ts"); + expect(expandedFiles).not.toContain("test/helpers/temp-dir.ts"); + expect(expandedFiles.filter((file) => !file.endsWith(".test.ts"))).toEqual([]); + + // Lower bound derived from the repo itself: every tracked test file that + // directly imports the helper must be picked up by the expansion scan, so + // dropped importers still fail without freezing the full inventory. + const scanRoots = ["src", "test", "ui", "extensions", "packages"]; + const grep = spawnSync( + "git", + ["grep", "-l", "--fixed-strings", "helpers/temp-dir", "--", ...scanRoots], + { encoding: "utf8" }, + ); + expect(grep.status).toBe(0); + const directImporterTests = grep.stdout + .split("\n") + .map((line) => line.trim()) + .filter((file) => file.endsWith(".test.ts") && !file.endsWith(".live.test.ts")) + .filter((file) => { + const source = fs.readFileSync(file, "utf8"); + return [...source.matchAll(/from\s+["'](\.[^"']+)["']/gu)].some((match) => { + const importerDir = path.posix.dirname(file); + const resolved = path.posix.normalize(path.posix.join(importerDir, match[1])); + return resolved.replace(/\.(?:js|ts)$/u, "") === "test/helpers/temp-dir"; + }); + }); + expect(directImporterTests.length).toBeGreaterThan(0); + expect(directImporterTests.filter((file) => !expandedFiles.includes(file))).toEqual([]); + + // Importers partition across configs: each file lands in exactly one plan, + // in deterministic sorted order. + expect(plans.length).toBeGreaterThan(1); + expect(new Set(expandedFiles).size).toBe(expandedFiles.length); + for (const files of planFiles) { + expect(files).toEqual([...files].toSorted((left, right) => left.localeCompare(right))); + } + + // Each importer must route to the same config and include-vs-forwarded + // shape as targeting it directly, so this test fails on real routing + // regressions but not on new importers of the helper. + for (const plan of plans) { + expect(plan.watchMode).toBe(false); + for (const file of plan.includePatterns ?? plan.forwardedArgs) { + expect(buildVitestRunPlans([file])).toEqual([ + { + config: plan.config, + forwardedArgs: plan.includePatterns ? [] : [file], + includePatterns: plan.includePatterns ? [file] : null, + watchMode: false, + }, + ]); + } + } }); it("routes e2e targets straight to the e2e config", () => {