Files
openclaw/test/scripts/extension-import-boundary-checker.test.ts
Peter Steinberger c70aee247e refactor(scripts): migrate JavaScript tools to TypeScript (#121005)
* refactor(scripts): migrate JavaScript tools to TypeScript

* fix(ci): keep changed-scope preflight zero-install

* fix(ci): preserve zero-install script owners

* fix(ci): complete script migration follow-through

* fix(release): keep stable closeout zero-install

* fix(scripts): preserve standalone execution boundaries

* fix(scripts): repair standalone loader boundaries

* fix(scripts): normalize gateway observation ids

* fix(scripts): keep Docker packager standalone

* test(scripts): preserve rebase cleanup helpers

* test(sessions): use tracked temp directory
2026-08-09 07:21:35 -07:00

157 lines
6.1 KiB
TypeScript

// Extension import boundary checker tests cover bounded source reads.
import { mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { createExtensionImportBoundaryChecker } from "../../scripts/lib/extension-import-boundary-checker.mts";
import { listGeneratedExtensionAssetSources } from "../../scripts/lib/static-extension-assets.mts";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
describe("extension import boundary checker", () => {
it("rejects oversized TypeScript source files before scanning imports", async () => {
const root = tempDirs.make("openclaw-extension-boundary-");
const sourcePath = path.join(root, "large.ts");
writeFileSync(sourcePath, "x".repeat(33), "utf8");
const checker = createExtensionImportBoundaryChecker({
boundaryLabel: "test",
cleanMessage: "clean",
inventoryTitle: "inventory",
maxSourceBytes: 32,
roots: [path.relative(process.cwd(), root)],
});
await expect(checker.collectInventory()).rejects.toThrow(
"extension import boundary source file exceeds 32 byte limit",
);
});
it("skips declared generated bundles without admitting oversized handwritten JavaScript", async () => {
const root = tempDirs.make("openclaw-extension-boundary-");
const pluginRoot = path.join(root, "extensions", "generated-plugin");
const assetRoot = path.join(pluginRoot, "assets");
const generatedPath = path.join(assetRoot, "generated.js");
const handwrittenPath = path.join(assetRoot, "handwritten.js");
const oversizedBytes = 2 * 1024 * 1024 + 1;
mkdirSync(assetRoot, { recursive: true });
writeFileSync(
path.join(pluginRoot, "package.json"),
JSON.stringify({
openclaw: {
assetScripts: { build: "node generate.mjs" },
build: {
staticAssets: [{ source: "./assets/generated.js", output: "assets/generated.js" }],
},
},
}),
"utf8",
);
// Match CI ordering: the build materializes its declared bundle before source guards run.
writeFileSync(generatedPath, "/".repeat(oversizedBytes), "utf8");
const generatedSources = new Set(
listGeneratedExtensionAssetSources({ rootDir: root }).map((source) =>
path.relative(process.cwd(), path.join(root, source)).replaceAll(path.sep, "/"),
),
);
const createChecker = () =>
createExtensionImportBoundaryChecker({
boundaryLabel: "test",
cleanMessage: "clean",
inventoryTitle: "inventory",
roots: [path.relative(process.cwd(), path.join(root, "extensions"))],
shouldSkipFile: (relativeFile: string) => generatedSources.has(relativeFile),
sourceOptions: { fileExtensions: [".js"] },
});
await expect(createChecker().collectInventory()).resolves.toEqual([]);
const targetPath = path.join(process.cwd(), "extensions", "security-proof", "private.js");
const specifier = path.relative(assetRoot, targetPath).replaceAll(path.sep, "/");
writeFileSync(
handwrittenPath,
"import " + JSON.stringify(specifier) + ";\n" + "/".repeat(oversizedBytes),
"utf8",
);
await expect(createChecker().collectInventory()).rejects.toThrow(
"extension import boundary source file exceeds 2097152 byte limit",
);
});
it.each([
{
name: "comment-obscured side-effect import",
createSource: (specifier: string) => "import /* gap */ " + JSON.stringify(specifier),
kind: "import",
},
{
name: "dynamic import with attributes",
createSource: (specifier: string) =>
"import /* gap */ (" + JSON.stringify(specifier) + ', { with: { type: "json" } })',
kind: "dynamic-import",
},
{
name: "CommonJS require",
createSource: (specifier: string) => "require(" + JSON.stringify(specifier) + ")",
kind: "commonjs-require",
},
{
name: "import.meta URL",
createSource: (specifier: string) =>
"new URL(" + JSON.stringify(specifier) + ", import.meta.url)",
kind: "import-meta-url",
},
{
name: "comment-obscured namespace export",
createSource: (specifier: string) =>
"export /* gap */ * /* gap */ as privateModule from " + JSON.stringify(specifier),
kind: "export",
},
{
name: "escaped plugin-path separator",
createSource: (specifier: string) =>
"import " + JSON.stringify(specifier).replace("extensions/", "extensions\\/"),
kind: "import",
},
{
name: "Unicode-escaped plugin-path character",
createSource: (specifier: string) =>
"import " + JSON.stringify(specifier).replace("extensions/", "exten\\u0073ions/"),
kind: "import",
},
{
name: "hex-escaped plugin-path character",
createSource: (specifier: string) =>
"import " + JSON.stringify(specifier).replace("extensions/", "\\x65xtensions/"),
kind: "import",
},
{
name: "plugin-path line continuation",
createSource: (specifier: string) =>
"import " + JSON.stringify(specifier).replace("extensions/", "exten\\\nsions/"),
kind: "import",
},
])("rejects $name crossing the real plugin boundary", async ({ createSource, kind }) => {
const root = tempDirs.make("openclaw-extension-boundary-");
const sourcePath = path.join(root, "guarded.ts");
const targetPath = path.join(process.cwd(), "extensions", "security-proof", "private.js");
const specifier = path.relative(root, targetPath).split(path.sep).join("/");
writeFileSync(sourcePath, createSource(specifier), "utf8");
const checker = createExtensionImportBoundaryChecker({
boundaryLabel: "test",
cleanMessage: "clean",
inventoryTitle: "inventory",
roots: [path.relative(process.cwd(), root)],
skipSourcesWithoutBundledPluginPrefix: true,
});
await expect(checker.collectInventory()).resolves.toEqual([
expect.objectContaining({
kind,
resolvedPath: "extensions/security-proof/private.js",
specifier,
}),
]);
});
});