Files
openclaw/test/scripts/plugin-npm-runtime-native-import.test.ts
Peter Steinberger 7fbe426cda test(core,plugins,scripts): remove low-value tests (batch d023) (#158911)
* test(media): deslop s155 tests

* test(googlechat): deslop s156 tests

* test(scripts): deslop s140 tests

* test(reef): deslop s158 tests

* test(infra): deslop s153 tests

* test(microsoft-foundry): deslop s163 tests

* test(ai): deslop s164 tests

* test(sessions): deslop s160 tests

* test(browser): deslop s165 tests

* test(config): deslop s162 tests

* test: preserve redaction and current release identity coverage

* test: retain helpers used by current main regressions

* test(googlechat): use current config type owner

* test(googlechat): remove unused config type import
2026-09-26 15:29:21 +00:00

339 lines
13 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
const cli = path.resolve(import.meta.dirname, "../../scripts/lib/plugin-npm-runtime-build.mjs");
function writeFile(root: string, relative: string, content: string) {
const target = path.join(root, relative);
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, content);
}
function fixture(format = "esm", declaration = "peerDependencies") {
const root = tempDirs.make("openclaw-native-import-");
fs.mkdirSync(path.join(root, "src"));
writeFile(root, "pnpm-workspace.yaml", "packages:\n - extensions/*\n");
// Setup runs source tooling with workspace aliases; native children below do not load tsx.
writeFile(
root,
"tsconfig.json",
JSON.stringify({ extends: path.resolve(import.meta.dirname, "../../tsconfig.json") }),
);
writeFile(
root,
"package.json",
JSON.stringify({
name: "openclaw",
version: "2026.9.5",
type: "module",
exports: {
"./plugin-sdk/media-runtime": "./dist/plugin-sdk/media-runtime.js",
"./plugin-sdk/text-utility-runtime": "./dist/plugin-sdk/text-utility-runtime.js",
"./plugin-sdk/realtime-voice": "./dist/plugin-sdk/realtime-voice.js",
"./plugin-sdk/realtime-voice-provider": "./dist/plugin-sdk/realtime-voice-provider.js",
},
}),
);
writeFile(root, "dist/plugin-sdk/text-utility-runtime.js", "export {};\n");
for (const module of ["grapheme", "utf16-slice"]) {
const relative = `packages/normalization-core/src/${module}.ts`;
writeFile(
root,
relative,
fs.readFileSync(path.resolve(import.meta.dirname, "../..", relative), "utf8"),
);
}
// The supported host predates the private FFmpeg and playback facades.
writeFile(
root,
"dist/plugin-sdk/media-runtime.js",
'export function resolveFfmpegBin() { return "host"; }\n',
);
writeFile(
root,
"dist/plugin-sdk/realtime-voice-provider.js",
'export const canonicalizeBase64 = () => "provider";\n',
);
writeFile(
root,
"dist/plugin-sdk/realtime-voice.js",
'export const createRealtimeVoiceOutputActivityTracker = () => "voice"; export const isRealtimeVoiceAudioAudible = () => "audible";\n',
);
writeFile(
root,
"node_modules/fixture-dep/package.json",
JSON.stringify({ name: "fixture-dep", main: "index.cjs" }),
);
writeFile(root, "node_modules/fixture-dep/index.cjs", 'exports.thirdParty = "third-party";\n');
const packageDir = path.join(root, "extensions/demo");
writeFile(
packageDir,
"package.json",
JSON.stringify({
name: "@openclaw/demo",
version: "1.0.0",
type: "module",
optionalDependencies: { "fixture-dep": "1.0.0" },
[declaration]: {
...(declaration === "optionalDependencies" ? { "fixture-dep": "1.0.0" } : {}),
openclaw: "*",
},
openclaw: {
extensions: ["./index.ts"],
build: { runtimeFormat: format },
release: { publishToNpm: true },
},
}),
);
writeFile(
packageDir,
"index.ts",
[
'import { resolveFfmpegBin } from "openclaw/plugin-sdk/media-ffmpeg";',
'import { createRealtimeVoiceOutputActivityTracker, isRealtimeVoiceAudioAudible } from "openclaw/plugin-sdk/realtime-voice-playback";',
'import { canonicalizeBase64 } from "openclaw/plugin-sdk/realtime-voice-provider";',
'import { findGraphemeChunkEnd } from "openclaw/plugin-sdk/text-grapheme";',
'import { thirdParty } from "fixture-dep";',
'import { writeFileSync } from "node:fs";',
'writeFileSync("executed", "yes");',
"export const answer = `${resolveFfmpegBin()} ${thirdParty} ${createRealtimeVoiceOutputActivityTracker()} ${isRealtimeVoiceAudioAudible()} ${canonicalizeBase64()} ${findGraphemeChunkEnd('xa\\u0301z', 0, 2)}`;",
].join("\n"),
);
const entry = `./extensions/demo/dist/index.${format === "cjs" ? "cjs" : "js"}`;
writeFile(root, entry, 'throw new Error("Preparation must not execute plugin code");\n');
return { root, packageDir, entry };
}
function runCli(root: string, args: string[]) {
return spawnSync(process.execPath, [cli, ...args], { cwd: root, encoding: "utf8" });
}
function nativeImport(root: string, entry: string, format: string) {
const env = { ...process.env };
delete env.NODE_OPTIONS;
delete env.NODE_PATH;
return spawnSync(
process.execPath,
[
"--input-type=module",
"-e",
format === "cjs"
? `import { createRequire } from "node:module"; console.log(createRequire(import.meta.url)(${JSON.stringify(entry)}).answer);`
: `console.log((await import(${JSON.stringify(entry)})).answer);`,
],
{ cwd: root, encoding: "utf8", env },
);
}
function snapshot(root: string, directories: string[]) {
return Object.fromEntries(
directories
.flatMap((directory) => {
const target = path.join(root, directory);
return [
directory,
...fs
.readdirSync(target, { recursive: true })
.map((name) => path.join(directory, String(name))),
];
})
.map((relative) => {
const target = path.join(root, relative);
const stat = fs.statSync(target, { bigint: true });
return [
relative,
{
mtime: stat.mtimeNs,
hash: stat.isFile()
? createHash("sha256").update(fs.readFileSync(target)).digest("hex")
: null,
},
];
}),
);
}
describe("explicit source native-import preparation", () => {
it.each([
{ argv: ["--prepare-native-import", "extensions/demo", ""] },
{ argv: ["extensions/demo", "", "--prepare-native-import"] },
{ argv: ["extensions/demo", "", "--prepare-native-import", "--unexpected"] },
])("rejects excess preparation argv $argv before changing the host link", ({ argv }) => {
const { root, packageDir } = fixture();
const before = snapshot(root, ["."]);
const result = runCli(root, argv);
expect(result.error, result.stderr).toBeUndefined();
expect(result.status, result.stderr).toBe(1);
expect(result.stderr).toContain("unexpected plugin npm runtime build argument");
expect(fs.existsSync(path.join(packageDir, "node_modules"))).toBe(false);
expect(fs.existsSync(path.join(root, "executed"))).toBe(false);
expect(snapshot(root, ["."])).toEqual(before);
});
it("rejects excess build argv through the public shim without compiling", () => {
const { root } = fixture();
const packageDir = path.join(root, "javascript-only");
writeFile(
packageDir,
"package.json",
JSON.stringify({
name: "@openclaw/arity-js-fixture",
version: "1.0.0",
type: "module",
openclaw: { extensions: ["./index.js"] },
}),
);
writeFile(packageDir, "index.js", 'throw new Error("JS-only argv proof must not execute");\n');
writeFile(packageDir, "dist/sentinel.js", "keep\n");
const before = snapshot(root, ["."]);
const valid = runCli(root, [packageDir]);
expect(valid.error, valid.stderr).toBeUndefined();
expect(valid.status, valid.stderr).toBe(0);
expect(snapshot(root, ["."])).toEqual(before);
const result = runCli(root, [packageDir, "", "--unexpected"]);
expect(result.error, result.stderr).toBeUndefined();
expect(result.status, result.stderr).toBe(1);
expect(result.stderr).toContain("unexpected plugin npm runtime build argument");
expect(snapshot(root, ["."])).toEqual(before);
});
it.each([
["esm", "peerDependencies"],
["cjs", "dependencies"],
["esm", "optionalDependencies"],
["cjs", "optionalDependencies"],
])("prepares %s output with a missing %s host link without rebuilding", (format, declaration) => {
const { root, packageDir, entry } = fixture(format, declaration);
const compiled = runCli(root, ["extensions/demo"]);
expect(compiled.status, compiled.stderr).toBe(0);
expect(fs.existsSync(path.join(packageDir, "node_modules"))).toBe(false);
const missing = nativeImport(root, entry, format);
expect(missing.status).not.toBe(0);
expect(missing.stderr).toMatch(/Cannot find (?:package|module) 'openclaw/u);
writeFile(packageDir, "node_modules/keep/marker", "unrelated local contents");
const directories = [
"dist",
"extensions/demo/dist",
"node_modules",
"extensions/demo/node_modules/keep",
];
const before = snapshot(root, directories);
const prepared = runCli(root, ["--prepare-native-import", "extensions/demo"]);
expect(prepared.status, prepared.stderr).toBe(0);
expect(fs.existsSync(path.join(root, "executed"))).toBe(false);
const link = path.join(packageDir, "node_modules/openclaw");
expect(fs.realpathSync(link)).toBe(root);
const linkBefore = fs.lstatSync(link, { bigint: true });
const repeated = runCli(root, ["extensions/demo", "--prepare-native-import"]);
expect(repeated.status, repeated.stderr).toBe(0);
expect(fs.lstatSync(link, { bigint: true }).mtimeNs).toBe(linkBefore.mtimeNs);
expect(snapshot(root, directories)).toEqual(before);
const loaded = nativeImport(root, entry, format);
expect(loaded.status, loaded.stderr).toBe(0);
expect(loaded.stdout.trim()).toBe("host third-party voice audible provider 1");
expect(fs.readFileSync(path.join(root, "executed"), "utf8")).toBe("yes");
expect(snapshot(root, directories)).toEqual(before);
});
it("does not infer a host declaration from devDependencies or publication metadata", () => {
const { root, packageDir } = fixture("esm", "devDependencies");
const result = runCli(root, ["--prepare-native-import", "extensions/demo"]);
expect(result.status).toBe(1);
expect(result.stderr).toMatch(/does not declare openclaw/u);
expect(fs.existsSync(path.join(packageDir, "node_modules"))).toBe(false);
});
it.each([
"outside package",
"nested package",
"symlinked package",
"symlinked extensions",
"symlinked manifest",
"invalid manifest",
"wrong host",
"not source",
"missing host output",
"missing plugin output",
"symlinked node_modules",
"unrelated host directory",
])("rejects %s without changing artifacts or unrelated contents", (scenario) => {
const { root, packageDir } = fixture();
const outside = tempDirs.make("openclaw-native-import-outside-");
writeFile(outside, "marker", "keep");
let selected = "extensions/demo";
let expected = /real immediate extensions/u;
switch (scenario) {
case "outside package":
selected = outside;
break;
case "nested package":
selected = "extensions/demo/nested";
break;
case "symlinked package":
fs.renameSync(packageDir, path.join(outside, "demo"));
fs.symlinkSync(path.join(outside, "demo"), packageDir, "junction");
break;
case "symlinked extensions":
fs.renameSync(path.join(root, "extensions"), path.join(outside, "extensions"));
fs.symlinkSync(path.join(outside, "extensions"), path.join(root, "extensions"), "junction");
break;
case "symlinked manifest":
fs.renameSync(path.join(packageDir, "package.json"), path.join(outside, "package.json"));
fs.symlinkSync(path.join(outside, "package.json"), path.join(packageDir, "package.json"));
expected = /safely read.*package.json/u;
break;
case "invalid manifest":
writeFile(packageDir, "package.json", "[]");
expected = /safely read.*package.json/u;
break;
case "wrong host":
writeFile(root, "package.json", JSON.stringify({ name: "unrelated" }));
expected = /source checkout root/u;
break;
case "not source":
fs.rmdirSync(path.join(root, "src"));
expected = /source checkout root/u;
break;
case "missing host output":
fs.rmSync(path.join(root, "dist"), { recursive: true });
expected = /Host SDK output is missing/u;
break;
case "missing plugin output":
fs.rmSync(path.join(packageDir, "dist"), { recursive: true });
expected = /run node scripts\/lib\/plugin-npm-runtime-build.mjs/u;
break;
case "symlinked node_modules":
fs.symlinkSync(outside, path.join(packageDir, "node_modules"), "junction");
expected = /not a real directory/u;
break;
case "unrelated host directory":
writeFile(
packageDir,
"node_modules/openclaw/package.json",
JSON.stringify({ name: "unrelated" }),
);
writeFile(packageDir, "node_modules/openclaw/marker", "keep");
expected = /already exists and is not a symlink/u;
break;
}
const before = snapshot(root, ["."]);
const outsideBefore = snapshot(outside, ["."]);
const result = runCli(root, ["--prepare-native-import", selected]);
expect(result.status, result.stderr).toBe(1);
expect(result.stderr).toMatch(expected);
expect(snapshot(root, ["."])).toEqual(before);
expect(snapshot(outside, ["."])).toEqual(outsideBefore);
});
});