Files
openclaw/test/scripts/check-coercion-helper-declarations.test.ts
Peter Steinberger b080dd1e76 refactor: consolidate coercion contracts (#122458)
* refactor: consolidate coercion contracts

Centralize exact string, record, numeric, date, Boolean, argument, and structured-error coercions while preserving call-site semantics.

Migrate canonical-name collisions and deprecated internal SDK bypasses, deleting 55 net production/tooling lines. Expand declaration ownership enforcement to 101 allowed helpers and add a narrow export-completeness audit.

* fix: preserve standalone script coercions

Keep copied Control UI tooling self-contained and retain the trusted release harness module-relative source seam when the harness runs against an old target cwd.
2026-08-11 23:26:37 -07:00

384 lines
14 KiB
TypeScript

import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
auditCanonicalCoercionExports,
auditCoercionHelperDeclarations,
findBannedCoercionHelperDeclarations,
findExportedCallableNames,
isGovernedCoercionHelperPath,
runCoercionHelperDeclarationGuard,
type CoercionHelperCarveOut,
type CoercionHelperDeclaration,
} from "../../scripts/check-coercion-helper-declarations.mts";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
describe("coercion helper declaration AST guard", () => {
it("finds functions, callable variables, methods, fields, and object properties", () => {
const source = [
"export async function readString() {}",
"if (true) {",
" function asRecord() {}",
"}",
"const isRecord = (value: unknown) => Boolean(value);",
"let toError = function (value: unknown) { return value; };",
"var optionalString = async (value: unknown) => value;",
"const readString = (((value: unknown) => String(value)) satisfies ((value: unknown) => string));",
"function readNumber(record: Record<string, unknown>, key: string) { return record[key]; }",
"const timestampMs = (value: unknown) => Number(value);",
"function readBoolean() {}",
"function readOptionalString() {}",
"function normalizeString() {}",
"const asString = (value: unknown) => String(value);",
"function asObject() {}",
"const readOptionalString = normalizeOptionalString;",
"const optionalString = helpers.readStringValue;",
"const asObject = helpers.asOptionalRecord;",
"class Example {",
" readString() {}",
" toError = () => new Error();",
" asRecord = function () { return {}; };",
"}",
"const object = {",
" optionalString() {},",
" readBoolean: () => true,",
" readNumber: function () { return 1; },",
"};",
"function normalizeOptionalString() {}",
"const parseDateFirstTimestampMs = () => 0;",
"function safeParseJsonRecord() {}",
"function resolveIntegerOption() {}",
].join("\n");
expect(findBannedCoercionHelperDeclarations(source, "src/example.ts")).toEqual([
{ file: "src/example.ts", kind: "function", line: 1, name: "readString" },
{ file: "src/example.ts", kind: "function", line: 3, name: "asRecord" },
{ file: "src/example.ts", kind: "variable", line: 5, name: "isRecord" },
{ file: "src/example.ts", kind: "variable", line: 6, name: "toError" },
{ file: "src/example.ts", kind: "variable", line: 7, name: "optionalString" },
{ file: "src/example.ts", kind: "variable", line: 8, name: "readString" },
{ file: "src/example.ts", kind: "function", line: 9, name: "readNumber" },
{ file: "src/example.ts", kind: "variable", line: 10, name: "timestampMs" },
{ file: "src/example.ts", kind: "function", line: 11, name: "readBoolean" },
{ file: "src/example.ts", kind: "function", line: 12, name: "readOptionalString" },
{ file: "src/example.ts", kind: "function", line: 13, name: "normalizeString" },
{ file: "src/example.ts", kind: "variable", line: 14, name: "asString" },
{ file: "src/example.ts", kind: "function", line: 15, name: "asObject" },
{
file: "src/example.ts",
kind: "variable",
line: 16,
name: "readOptionalString",
},
{ file: "src/example.ts", kind: "variable", line: 17, name: "optionalString" },
{ file: "src/example.ts", kind: "variable", line: 18, name: "asObject" },
{ file: "src/example.ts", kind: "method", line: 20, name: "readString" },
{ file: "src/example.ts", kind: "field", line: 21, name: "toError" },
{ file: "src/example.ts", kind: "field", line: 22, name: "asRecord" },
{ file: "src/example.ts", kind: "method", line: 25, name: "optionalString" },
{ file: "src/example.ts", kind: "property", line: 26, name: "readBoolean" },
{ file: "src/example.ts", kind: "property", line: 27, name: "readNumber" },
{
file: "src/example.ts",
kind: "function",
line: 29,
name: "normalizeOptionalString",
},
{
file: "src/example.ts",
kind: "variable",
line: 30,
name: "parseDateFirstTimestampMs",
},
{
file: "src/example.ts",
kind: "function",
line: 31,
name: "safeParseJsonRecord",
},
{
file: "src/example.ts",
kind: "function",
line: 32,
name: "resolveIntegerOption",
},
]);
});
it("ignores imports, non-callable properties, shorthand aliases, callback names, and inert text", () => {
const source = [
'import { isRecord, readString as importedReadString } from "./helpers.js";',
"const alias = isRecord;",
"const { asRecord } = helpers;",
"const object = { isRecord, readString: 42, toError: importedToError };",
"const shorthand = { optionalString };",
"values.map(function readString(value) { return value; });",
"const aliasWithInternalName = function isRecord(value) { return value; };",
"const asRecord = raw as Record<string, unknown>;",
"const optionalString = value as string;",
"// function asRecord() {}",
'const fixture = "function toError() {}";',
].join("\n");
expect(findBannedCoercionHelperDeclarations(source, "src/example.ts")).toEqual([]);
});
it("allows an exact function-kind declaration and reports ordinary excess/stale counts", () => {
const declarations: CoercionHelperDeclaration[] = [
{ file: "src/allowed.ts", kind: "function", line: 2, name: "isRecord" },
{ file: "src/new.ts", kind: "function", line: 4, name: "readString" },
];
const carveOuts: CoercionHelperCarveOut[] = [
{
file: "src/allowed.ts",
name: "isRecord",
kind: "function",
count: 1,
reason: "Dependency-free protocol boundary.",
},
{
file: "src/removed.ts",
name: "toError",
kind: "function",
count: 1,
reason: "Hostile object trap semantics.",
},
];
expect(auditCoercionHelperDeclarations(declarations, carveOuts)).toEqual({
excessDeclarations: [{ file: "src/new.ts", kind: "function", line: 4, name: "readString" }],
invalidCarveOuts: [],
staleCarveOuts: [
{
file: "src/removed.ts",
name: "toError",
kind: "function",
count: 1,
reason: "Hostile object trap semantics.",
actualCount: 0,
},
],
});
});
it.each(["method", "field", "property"] as const)(
"treats %s drift as both excess and stale function ownership",
(kind) => {
const declaration: CoercionHelperDeclaration = {
file: "src/owner.ts",
kind,
line: 3,
name: "isRecord",
};
const carveOut: CoercionHelperCarveOut = {
file: "src/owner.ts",
name: "isRecord",
kind: "function",
count: 1,
reason: "Exact function owner.",
};
expect(auditCoercionHelperDeclarations([declaration], [carveOut])).toEqual({
excessDeclarations: [declaration],
invalidCarveOuts: [],
staleCarveOuts: [{ ...carveOut, actualCount: 0 }],
});
},
);
it("rejects duplicate, non-banned, and malformed carve-outs", () => {
const valid: CoercionHelperCarveOut = {
file: "src/owner.ts",
name: "isRecord",
kind: "function",
count: 1,
reason: "Exact function owner.",
};
const invalid = [
valid,
valid,
{
...valid,
file: "src/not-banned.ts",
name: "domainParser",
} as unknown as CoercionHelperCarveOut,
{ ...valid, file: "src/blank.ts", count: 0, reason: "" },
{
...valid,
file: "src/kind.ts",
kind: "getter",
} as unknown as CoercionHelperCarveOut,
];
expect(auditCoercionHelperDeclarations([], invalid).invalidCarveOuts).toEqual([
"src/owner.ts [isRecord] is listed more than once",
"src/not-banned.ts [domainParser] is not a banned helper name",
"src/blank.ts [isRecord] must have a positive count",
"src/blank.ts [isRecord] needs a non-empty reason",
"src/kind.ts [isRecord] has invalid kind getter",
]);
});
it("excludes structural fixtures and generated sources without hiding authored fixture-named files", () => {
expect(isGovernedCoercionHelperPath("src/runtime.ts")).toBe(true);
expect(isGovernedCoercionHelperPath("extensions/demo/runtime.jsx")).toBe(true);
expect(isGovernedCoercionHelperPath("src/runtime.d.ts")).toBe(false);
expect(isGovernedCoercionHelperPath("scripts/runtime.d.mts")).toBe(false);
expect(isGovernedCoercionHelperPath("test/fixtures/example.ts")).toBe(false);
expect(isGovernedCoercionHelperPath("extensions/demo/dist/index.js")).toBe(false);
expect(isGovernedCoercionHelperPath("src/example.test-fixtures.ts")).toBe(true);
expect(isGovernedCoercionHelperPath("extensions/demo/runtime-tool-fixture.ts")).toBe(true);
expect(isGovernedCoercionHelperPath("src/schema.generated.ts")).toBe(false);
expect(isGovernedCoercionHelperPath("ui/src/vendor.bundle.js")).toBe(false);
expect(
isGovernedCoercionHelperPath(
"extensions/browser/chrome-extension/modules/copilot-runtime.js",
),
).toBe(true);
expect(isGovernedCoercionHelperPath("root.config.ts")).toBe(true);
expect(isGovernedCoercionHelperPath(".github/actions/example/index.ts")).toBe(true);
});
it("finds directly exported callable declarations and export aliases", () => {
const source = [
"export function canonical() {}",
"function local() {}",
"export { local as alias };",
"export const VALUE = 1;",
].join("\n");
expect(findExportedCallableNames(source, "src/owner.ts")).toEqual(["alias", "canonical"]);
});
it.each([
["classified", ["kept"], [{ file: "src/owner.ts", name: "kept", status: "enforced" }]],
[
"deferred",
["format"],
[
{
file: "src/owner.ts",
name: "format",
status: "deferred",
reason: "Meaningful public collision.",
},
],
],
] as const)("accepts a %s canonical export", (_label, names, classifications) => {
expect(
auditCanonicalCoercionExports(new Map([["src/owner.ts", names]]), classifications),
).toEqual({
invalidClassifications: [],
staleClassifications: [],
unclassifiedExports: [],
});
});
it("reports unclassified exports and stale, duplicate, or blank deferred entries", () => {
const kept = { file: "src/owner.ts", name: "kept", status: "enforced" } as const;
const removed = {
file: "src/owner.ts",
name: "removed",
status: "deferred",
reason: "Removed owner.",
} as const;
const blank = {
file: "src/other.ts",
name: "unknown",
status: "deferred",
reason: "",
} as const;
const audit = auditCanonicalCoercionExports(
new Map([["src/owner.ts", ["kept", "newHelper"]]]),
[kept, kept, removed, blank],
);
expect(audit.invalidClassifications).toEqual([
"src/owner.ts [kept] is classified more than once",
"src/other.ts [unknown] needs a non-empty deferred reason",
]);
expect(audit.unclassifiedExports).toEqual([{ file: "src/owner.ts", name: "newHelper" }]);
expect(audit.staleClassifications).toEqual([removed, blank]);
});
it("scans a temporary repository and reports sorted, owner-specific diagnostics", () => {
const repoRoot = tempDirs.make("coercion-helper-guard-");
fs.mkdirSync(path.join(repoRoot, "src"), { recursive: true });
fs.mkdirSync(path.join(repoRoot, "extensions", "demo"), { recursive: true });
fs.mkdirSync(path.join(repoRoot, "config"), { recursive: true });
fs.writeFileSync(path.join(repoRoot, "src", "z.ts"), "class Owner { readString() {} }\n");
fs.writeFileSync(
path.join(repoRoot, "extensions", "demo", "a.ts"),
"const asRecord = () => ({});\n",
);
fs.writeFileSync(
path.join(repoRoot, "config", "root.ts"),
"class Config { normalizeOptionalString() {} }\n",
);
const stdout: string[] = [];
const stderr: string[] = [];
expect(
runCoercionHelperDeclarationGuard({
carveOuts: [
{
file: "src/z.ts",
name: "readString",
kind: "function",
count: 1,
reason: "Exact function owner.",
},
],
repoRoot,
io: {
stdout: { write: (value) => stdout.push(value) },
stderr: { write: (value) => stderr.push(value) },
},
}),
).toBe(1);
expect(stdout).toEqual([]);
const output = stderr.join("");
expect(output.indexOf("config/root.ts:1")).toBeLessThan(
output.indexOf("extensions/demo/a.ts:1"),
);
expect(output.indexOf("extensions/demo/a.ts:1")).toBeLessThan(output.indexOf("src/z.ts:1"));
expect(output).toContain("Banned local coercion-helper declarations:");
expect(output).toContain("readString (method declaration)");
expect(output).toContain("Stale coercion-helper carve-outs:");
expect(output).toContain("expected 1 function declaration(s), found 0");
expect(output).toContain("Core/package/UI/workspace-script code");
expect(output).toContain("Plugin production code");
expect(output).toContain("number-runtime");
expect(output).toContain("Dependency-free, copied, generated, or serialized code");
});
it("scans only tracked files when the repository has a Git index", () => {
const repoRoot = tempDirs.make("coercion-helper-tracked-guard-");
fs.mkdirSync(path.join(repoRoot, "src"), { recursive: true });
fs.writeFileSync(path.join(repoRoot, "src", "tracked.ts"), "function readString() {}\n");
fs.writeFileSync(path.join(repoRoot, "src", "untracked.ts"), "function readNumber() {}\n");
execFileSync("git", ["init", "-q"], { cwd: repoRoot });
execFileSync("git", ["add", "src/tracked.ts"], { cwd: repoRoot });
const stderr: string[] = [];
expect(
runCoercionHelperDeclarationGuard({
carveOuts: [],
repoRoot,
io: {
stdout: { write: () => undefined },
stderr: { write: (value) => stderr.push(value) },
},
}),
).toBe(1);
const output = stderr.join("");
expect(output).toContain("src/tracked.ts:1 readString");
expect(output).not.toContain("src/untracked.ts");
expect(output).not.toContain("readNumber");
});
});