-
Notifications
You must be signed in to change notification settings - Fork 3k
refactor(e2e): route live-Vitest invocations through a validated helper #6996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
907c5bb
refactor(e2e): route live-Vitest invocations through a validated helper
laitingsheng da4e6dc
Merge remote-tracking branch 'origin/main' into refactor/e2e-live-inv…
laitingsheng 6d41b33
fix(e2e): preserve spawnSync error and signal exit codes
laitingsheng 9e16dd8
merge(e2e): refresh helper branch from main
cv 65426da
fix(e2e): fail closed on invalid helper commands
cv 108099b
fix(e2e): reject malformed helper options
cv 4e82f67
merge(e2e): refresh helper branch after gate updates
cv fb778f6
test(e2e): cover repeated live vitest options
cv 30059b5
refactor(e2e): validate security posture invocation
cv File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import { describe, expect, it } from "vitest"; | ||
|
|
||
| import { | ||
| buildLiveVitestArgs, | ||
| LIVE_VITEST_PROJECT, | ||
| RISK_SIGNAL_REPORTER, | ||
| validateLiveProject, | ||
| validateLiveSelector, | ||
| validateLiveTestPath, | ||
| } from "../../../tools/e2e/live-vitest-invocation.mts"; | ||
|
|
||
| describe("validateLiveProject (#6961)", () => { | ||
| it("accepts the live project and defaults to it", () => { | ||
| expect(validateLiveProject("e2e-live")).toBe(LIVE_VITEST_PROJECT); | ||
| expect(validateLiveProject(undefined)).toBe(LIVE_VITEST_PROJECT); | ||
| }); | ||
|
|
||
| it("rejects any other project", () => { | ||
| for (const project of ["cli", "e2e-support", "e2e-live-extra", "integration"]) { | ||
| expect(() => validateLiveProject(project)).toThrow(/unsupported vitest project/); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe("validateLiveTestPath (#6961)", () => { | ||
| it("accepts a real live test path", () => { | ||
| expect(validateLiveTestPath("test/e2e/live/registry-targets.test.ts")).toBe( | ||
| "test/e2e/live/registry-targets.test.ts", | ||
| ); | ||
| }); | ||
|
|
||
| it("rejects paths outside the live test root", () => { | ||
| expect(() => validateLiveTestPath("test/e2e/support/thing.test.ts")).toThrow( | ||
| /must be under test\/e2e\/live/, | ||
| ); | ||
| expect(() => validateLiveTestPath("src/lib/onboard.ts")).toThrow(/must be under/); | ||
| }); | ||
|
|
||
| it("rejects '..' traversal", () => { | ||
| expect(() => validateLiveTestPath("test/e2e/live/../support/x.test.ts")).toThrow(/traverse/); | ||
| }); | ||
|
|
||
| it("rejects absolute paths", () => { | ||
| expect(() => validateLiveTestPath("/etc/passwd")).toThrow(/unsupported character|absolute/); | ||
| }); | ||
|
|
||
| it("rejects shell metacharacters", () => { | ||
| for (const bad of [ | ||
| "test/e2e/live/x.test.ts; rm -rf /", | ||
| "test/e2e/live/$(whoami).test.ts", | ||
| "test/e2e/live/x.test.ts && curl evil", | ||
| "test/e2e/live/`id`.test.ts", | ||
| "test/e2e/live/x.test.ts|cat", | ||
| ]) { | ||
| expect(() => validateLiveTestPath(bad)).toThrow(/unsupported character/); | ||
| } | ||
| }); | ||
|
|
||
| it("requires a .test.ts file", () => { | ||
| expect(() => validateLiveTestPath("test/e2e/live/fixtures")).toThrow(/\.test\.ts/); | ||
| }); | ||
|
|
||
| it("requires a non-empty path", () => { | ||
| expect(() => validateLiveTestPath("")).toThrow(/required/); | ||
| expect(() => validateLiveTestPath(undefined)).toThrow(/required/); | ||
| }); | ||
| }); | ||
|
|
||
| describe("validateLiveSelector (#6961)", () => { | ||
| it("accepts anchored title patterns", () => { | ||
| expect(validateLiveSelector("^ubuntu-repo-cloud-openclaw$")).toBe( | ||
| "^ubuntu-repo-cloud-openclaw$", | ||
| ); | ||
| expect(validateLiveSelector("^skill-agent$")).toBe("^skill-agent$"); | ||
| }); | ||
|
|
||
| it("treats an absent or empty selector as no selector", () => { | ||
| expect(validateLiveSelector(undefined)).toBeUndefined(); | ||
| expect(validateLiveSelector("")).toBeUndefined(); | ||
| expect(validateLiveSelector(" ")).toBeUndefined(); | ||
| }); | ||
|
|
||
| it("rejects shell metacharacters in the expanded selector", () => { | ||
| for (const bad of [ | ||
| "^$(touch pwned)$", | ||
| "^x$; rm -rf /", | ||
| "^x$ && evil", | ||
| "^`id`$", | ||
| "^x|y$", | ||
| "^x>out$", | ||
| ]) { | ||
| expect(() => validateLiveSelector(bad)).toThrow(/unsupported character/); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe("buildLiveVitestArgs (#6961)", () => { | ||
| it("builds the standard invocation with a selector", () => { | ||
| expect( | ||
| buildLiveVitestArgs({ | ||
| testPath: "test/e2e/live/registry-targets.test.ts", | ||
| selector: "^ubuntu-repo-cloud-openclaw$", | ||
| }), | ||
| ).toEqual([ | ||
| "vitest", | ||
| "run", | ||
| "--project", | ||
| "e2e-live", | ||
| "test/e2e/live/registry-targets.test.ts", | ||
| "-t", | ||
| "^ubuntu-repo-cloud-openclaw$", | ||
| "--silent=false", | ||
| "--reporter=default", | ||
| `--reporter=${RISK_SIGNAL_REPORTER}`, | ||
| ]); | ||
| }); | ||
|
|
||
| it("omits the selector arguments for a single-file target", () => { | ||
| expect( | ||
| buildLiveVitestArgs({ | ||
| testPath: "test/e2e/live/diagnostics.test.ts", | ||
| }), | ||
| ).toEqual([ | ||
| "vitest", | ||
| "run", | ||
| "--project", | ||
| "e2e-live", | ||
| "test/e2e/live/diagnostics.test.ts", | ||
| "--silent=false", | ||
| "--reporter=default", | ||
| `--reporter=${RISK_SIGNAL_REPORTER}`, | ||
| ]); | ||
| }); | ||
|
|
||
| it("fails closed on an invalid input before producing any argv", () => { | ||
| expect(() => | ||
| buildLiveVitestArgs({ | ||
| testPath: "test/e2e/live/x.test.ts", | ||
| selector: "^x$; rm -rf /", | ||
| }), | ||
| ).toThrow(/unsupported character/); | ||
| expect(() => | ||
| buildLiveVitestArgs({ | ||
| testPath: "test/e2e/support/x.test.ts", | ||
| selector: "^x$", | ||
| project: "e2e-live", | ||
| }), | ||
| ).toThrow(/must be under/); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import { spawnSync } from "node:child_process"; | ||
| import { pathToFileURL } from "node:url"; | ||
|
|
||
| import { parseArgs } from "../advisors/io.mts"; | ||
|
|
||
| export const LIVE_VITEST_PROJECT = "e2e-live"; | ||
| export const LIVE_TEST_ROOT = "test/e2e/live/"; | ||
| export const RISK_SIGNAL_REPORTER = "test/e2e/risk-signal-reporter.ts"; | ||
|
|
||
| const SHELL_METACHARACTER = /[^A-Za-z0-9_./^$=:@+-]/u; | ||
| const TEST_PATH_PATTERN = /^[A-Za-z0-9_./-]+$/u; | ||
|
|
||
| export interface LiveVitestInvocation { | ||
| testPath: string | undefined; | ||
| selector?: string | undefined; | ||
| project?: string | undefined; | ||
| } | ||
|
|
||
| function assertNoShellMetacharacters(value: string, field: string): void { | ||
| const match = SHELL_METACHARACTER.exec(value); | ||
| if (match) { | ||
| throw new Error(`${field} contains an unsupported character ${JSON.stringify(match[0])}`); | ||
| } | ||
| } | ||
|
|
||
| export function validateLiveProject(project: string | undefined): string { | ||
| const resolved = (project ?? LIVE_VITEST_PROJECT).trim(); | ||
| if (resolved !== LIVE_VITEST_PROJECT) { | ||
| throw new Error( | ||
| `unsupported vitest project ${JSON.stringify(resolved)}; this helper only runs ${LIVE_VITEST_PROJECT}`, | ||
| ); | ||
| } | ||
| return resolved; | ||
| } | ||
|
|
||
| export function validateLiveTestPath(testPath: string | undefined): string { | ||
| const value = (testPath ?? "").trim(); | ||
| if (!value) { | ||
| throw new Error("test path is required"); | ||
| } | ||
| if (!TEST_PATH_PATTERN.test(value)) { | ||
| assertNoShellMetacharacters(value, "test path"); | ||
| throw new Error(`test path ${JSON.stringify(value)} has an unsupported character`); | ||
| } | ||
| if (value.startsWith("/")) { | ||
| throw new Error("test path must be repository-relative, not absolute"); | ||
| } | ||
| if (value.split("/").includes("..")) { | ||
| throw new Error("test path must not traverse with '..'"); | ||
| } | ||
| if (!value.startsWith(LIVE_TEST_ROOT)) { | ||
| throw new Error(`test path must be under ${LIVE_TEST_ROOT}, got ${JSON.stringify(value)}`); | ||
| } | ||
| if (!value.endsWith(".test.ts")) { | ||
| throw new Error("test path must name a .test.ts file"); | ||
| } | ||
| return value; | ||
| } | ||
|
|
||
| export function validateLiveSelector(selector: string | undefined): string | undefined { | ||
| const value = (selector ?? "").trim(); | ||
| if (!value) { | ||
| return undefined; | ||
| } | ||
| assertNoShellMetacharacters(value, "selector"); | ||
| return value; | ||
| } | ||
|
|
||
| export function buildLiveVitestArgs(invocation: LiveVitestInvocation): string[] { | ||
| const project = validateLiveProject(invocation.project); | ||
| const testPath = validateLiveTestPath(invocation.testPath); | ||
| const selector = validateLiveSelector(invocation.selector); | ||
| const selectorArgs = selector ? ["-t", selector] : []; | ||
| return [ | ||
| "vitest", | ||
| "run", | ||
| "--project", | ||
| project, | ||
| testPath, | ||
| ...selectorArgs, | ||
| "--silent=false", | ||
| "--reporter=default", | ||
| `--reporter=${RISK_SIGNAL_REPORTER}`, | ||
| ]; | ||
| } | ||
|
|
||
| function runCli(): void { | ||
| const args = parseArgs(process.argv.slice(3)); | ||
| const argv = buildLiveVitestArgs({ | ||
| testPath: args.testPath, | ||
| selector: args.selector, | ||
| project: args.project, | ||
| }); | ||
| const result = spawnSync("npx", argv, { stdio: "inherit" }); | ||
| process.exit(typeof result.status === "number" ? result.status : 1); | ||
| } | ||
|
|
||
| if ( | ||
| process.argv[1] && | ||
| import.meta.url === pathToFileURL(process.argv[1]).href && | ||
| process.argv[2] === "run" | ||
| ) { | ||
| runCli(); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.