openclaw/test/scripts/inventory-report-cli.test.ts
RoboClaw fea8a1499a
refactor(scripts): share inventory report CLI parsing (#150212)
Consolidate the two inventory argument loops over existing flag primitives while preserving strict values, errors, ordering, defaults, and report output.

Related: #137293 (retired validator-only proposal); this active QA deliverable retires the complete duplicate parsers.

Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
2026-09-16 18:09:53 -07:00

140 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import fs from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import * as fileUtils from "../../scripts/check-file-utils.js";
import { main as runEnvReport } from "../../scripts/test-env-mutation-report.js";
import { main as runSkipReport } from "../../scripts/test-skip-inventory.js";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
afterEach(() => vi.restoreAllMocks());
describe.each([
{ name: "env mutation", main: runEnvReport },
{ name: "skip inventory", main: runSkipReport },
])("$name CLI argument contract", ({ main }) => {
function makeFixture() {
const repoRoot = path.join(tempDirs.make("openclaw-inventory-argv-"), " literal root ");
fs.mkdirSync(path.join(repoRoot, "src"), { recursive: true });
fs.writeFileSync(
path.join(repoRoot, "src/example.test.ts"),
'process.env.HOME = "fixture"; it.skip("fixture", () => {});\n'.repeat(122),
);
return repoRoot;
}
it("uses cwd and the default limit, then honors the last split root and limit", () => {
const repoRoot = makeFixture();
vi.spyOn(process, "cwd").mockReturnValue(repoRoot);
const stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true);
expect(main([])).toBe(0);
expect(stdout).toHaveBeenLastCalledWith(expect.stringContaining("... 2 more finding(s)"));
expect(
main([
"--repo-root",
"unused",
"--",
"--repo-root",
repoRoot,
"--limit",
"1",
"--",
"--limit",
"0002",
"--",
]),
).toBe(0);
expect(stdout).toHaveBeenLastCalledWith(expect.stringContaining("... 120 more finding(s)"));
for (const limit of ["0", "000", String(Number.MAX_SAFE_INTEGER)]) {
stdout.mockClear();
expect(main(["--repo-root", repoRoot, "--limit", limit])).toBe(0);
expect(stdout).toHaveBeenCalledOnce();
expect(stdout).toHaveBeenCalledWith(expect.stringContaining("L122 "));
expect(stdout).not.toHaveBeenCalledWith(expect.stringContaining("more finding(s)"));
}
});
it("accepts repeated JSON flags and standalone separators", () => {
const stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true);
expect(main(["--", "--json", "--", "--json", "--repo-root", makeFixture(), "--"])).toBe(0);
expect(stdout).toHaveBeenCalledOnce();
expect(stdout).toHaveBeenCalledWith(expect.stringContaining('"findingCount": 122'));
expect(stdout).not.toHaveBeenCalledWith(expect.stringContaining("more finding(s)"));
});
it("accepts repeated help aliases without scanning", () => {
const scan = vi.spyOn(fileUtils, "listRepoFilesSync");
const stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true);
expect(main(["--help", "-h", "--", "--help"])).toBe(0);
expect(stdout).toHaveBeenCalledWith(expect.stringContaining("Usage:"));
expect(scan).not.toHaveBeenCalled();
});
it.each([
{ argv: ["--limit"], error: "--limit expects a non-negative integer" },
...[
"",
" ",
" 1",
"1 ",
"+1",
"-1",
"1.0",
"1e3",
"0x10",
"١",
"1",
"9007199254740992",
"9".repeat(400),
"--",
"-h",
].map((value) => ({
argv: ["--limit", value],
error: "--limit expects a non-negative integer",
})),
{ argv: ["--repo-root"], error: "--repo-root expects a path" },
...["", "-", "-h", "--"].map((value) => ({
argv: ["--repo-root", value],
error: "--repo-root expects a path",
})),
...["--limit=1", "--repo-root=repo", "--json=true", "--unknown", "positional", ""].map(
(arg) => ({
argv: [arg],
error: `Unknown argument: ${arg}`,
}),
),
{ argv: ["--limit", "1", "--limit", "bad"], error: "--limit expects a non-negative integer" },
{ argv: ["--limit", "bad", "--limit", "1"], error: "--limit expects a non-negative integer" },
{ argv: ["--repo-root", "valid", "--repo-root", ""], error: "--repo-root expects a path" },
{ argv: ["--repo-root", "", "--repo-root", "valid"], error: "--repo-root expects a path" },
{ argv: ["--unknown", "--limit", "bad"], error: "Unknown argument: --unknown" },
{ argv: ["--limit", "bad", "--unknown"], error: "--limit expects a non-negative integer" },
])("rejects argv %# before output or scanning, even with help", ({ argv, error }) => {
const scan = vi.spyOn(fileUtils, "listRepoFilesSync");
const stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true);
for (const args of [argv, ["--help", ...argv], [...argv, "--help"]]) {
expect(() => main(args)).toThrow(new Error(error));
}
expect(stdout).not.toHaveBeenCalled();
expect(scan).not.toHaveBeenCalled();
});
});
it("registers the repeatable include-allowed flag only for the env report", () => {
const repoRoot = tempDirs.make("openclaw-inventory-allowed-");
fs.mkdirSync(path.join(repoRoot, "src/test-utils"), { recursive: true });
fs.writeFileSync(
path.join(repoRoot, "src/test-utils/openclaw-test-state.ts"),
'process.env.HOME = "fixture";',
);
const stdout = vi.spyOn(process.stdout, "write").mockReturnValue(true);
expect(runEnvReport(["--repo-root", repoRoot])).toBe(0);
expect(stdout).not.toHaveBeenCalledWith(expect.stringContaining("Allowed harness findings:"));
expect(runEnvReport(["--repo-root", repoRoot, "--include-allowed", "--include-allowed"])).toBe(0);
expect(stdout).toHaveBeenLastCalledWith(expect.stringContaining("Allowed harness findings:"));
expect(() => runSkipReport(["--help", "--include-allowed"])).toThrow(
new Error("Unknown argument: --include-allowed"),
);
});