test: make root permission assertions deterministic

This commit is contained in:
Vincent Koc
2026-05-11 10:33:42 +08:00
parent f3361dc928
commit a504cd0190
2 changed files with 67 additions and 30 deletions

View File

@@ -477,36 +477,53 @@ describe("runGlobalPackageUpdateSteps", () => {
await fs.mkdir(path.dirname(targetShim), { recursive: true });
await fs.writeFile(targetShim, "old shim\n", "utf8");
const result = await runGlobalPackageUpdateSteps({
installTarget: createNpmTarget(globalRoot),
installSpec: "openclaw@2.0.0",
packageName: "openclaw",
packageRoot,
runCommand: createRootRunner(globalRoot),
runStep: async ({ name, argv, cwd }) => {
const prefixIndex = argv.indexOf("--prefix");
const stagePrefix = argv[prefixIndex + 1];
if (!stagePrefix) {
throw new Error("missing staged prefix");
let stagedShimForFailure: string | undefined;
const realCopyFile = fs.copyFile.bind(fs);
const copyFileSpy = vi
.spyOn(fs, "copyFile")
.mockImplementation(async (...args: Parameters<typeof fs.copyFile>) => {
const [source] = args;
if (stagedShimForFailure && String(source) === stagedShimForFailure) {
throw createFsError("EACCES", "staged shim copy failed");
}
await writePackageRoot(
path.join(stagePrefix, "lib", "node_modules", "openclaw"),
"2.0.0",
);
const stagedShim = path.join(stagePrefix, "bin", "openclaw");
await fs.mkdir(path.dirname(stagedShim), { recursive: true });
await fs.writeFile(stagedShim, "new shim\n", "utf8");
await fs.chmod(stagedShim, 0);
return {
name,
command: argv.join(" "),
cwd: cwd ?? process.cwd(),
durationMs: 1,
exitCode: 0,
};
},
timeoutMs: 1000,
});
return await realCopyFile(...args);
});
let result: Awaited<ReturnType<typeof runGlobalPackageUpdateSteps>>;
try {
result = await runGlobalPackageUpdateSteps({
installTarget: createNpmTarget(globalRoot),
installSpec: "openclaw@2.0.0",
packageName: "openclaw",
packageRoot,
runCommand: createRootRunner(globalRoot),
runStep: async ({ name, argv, cwd }) => {
const prefixIndex = argv.indexOf("--prefix");
const stagePrefix = argv[prefixIndex + 1];
if (!stagePrefix) {
throw new Error("missing staged prefix");
}
await writePackageRoot(
path.join(stagePrefix, "lib", "node_modules", "openclaw"),
"2.0.0",
);
const stagedShim = path.join(stagePrefix, "bin", "openclaw");
stagedShimForFailure = stagedShim;
await fs.mkdir(path.dirname(stagedShim), { recursive: true });
await fs.writeFile(stagedShim, "new shim\n", "utf8");
return {
name,
command: argv.join(" "),
cwd: cwd ?? process.cwd(),
durationMs: 1,
exitCode: 0,
};
},
timeoutMs: 1000,
});
} finally {
copyFileSpy.mockRestore();
}
expect(result.failedStep?.name).toBe("global install swap");
expect(result.verifiedPackageRoot).toBe(packageRoot);

View File

@@ -65,6 +65,22 @@ function sha256FileSync(filePath: string): string {
return createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
}
function canWritePathSync(targetPath: string): boolean {
try {
fs.accessSync(targetPath, fs.constants.W_OK);
return true;
} catch {
return false;
}
}
function canMutateNativeBinaryFixturePath(binaryPath: string): boolean {
const realPath = fs.realpathSync(binaryPath);
return [binaryPath, path.dirname(binaryPath), realPath, path.dirname(realPath)].some((entry) =>
canWritePathSync(entry),
);
}
function createScriptOperandFixture(tmp: string, fixture?: RuntimeFixture): ScriptOperandFixture {
if (fixture) {
return {
@@ -646,7 +662,7 @@ describe("hardenApprovedExecutionPaths", () => {
);
});
it("allows shell payloads that invoke absolute-path native binaries", () => {
it("handles shell payloads that invoke absolute-path native binaries", () => {
if (process.platform === "win32") {
return;
}
@@ -656,6 +672,10 @@ describe("hardenApprovedExecutionPaths", () => {
rawCommand: binaryPath,
cwd: process.cwd(),
});
if (canMutateNativeBinaryFixturePath(binaryPath)) {
expect(prepared).toEqual(DENIED_RUNTIME_APPROVAL);
return;
}
expect(prepared.ok).toBe(true);
if (!prepared.ok) {
throw new Error("unreachable");