Files
openclaw/src/gateway/operator-approval-runtime-token.test.ts
T
Peter Steinberger 9eae43bd37 refactor(infra): move exec approvals into the shared SQLite state DB (#114063)
* refactor(infra): move exec approvals into the shared SQLite state DB

Delete the file-runtime exec-approvals store (exec-approvals.json + .lock
sidecar machinery) on both runtimes and make the reserved
exec_approvals_config singleton row canonical. Doctor owns the one-time
import with claim/verify/receipt discipline; runtime fails closed with a
doctor instruction while un-migrated legacy state exists. The wire CAS
contract, socket semantics, and gateway auth-token derivations are
unchanged. Kills the #113929 lock-contention bug class structurally and
nets around -2.9k lines.

* fix(infra): green CI gates and retire file-era exec approvals tests

Break the migration-type import cycle with a leaf contract, regenerate the
plugin-SDK API and native i18n baselines for the intentional surface change,
drop unused exports, and replace the macOS file-era approvals test suite with
SQLite-backed behavior coverage per the obsolete-internals test policy.

* chore: green max-lines ratchet, native i18n baseline, and unused-export scan
2026-07-26 06:39:23 -04:00

101 lines
3.8 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { ExecApprovalsFile } from "../infra/exec-approvals-core.js";
import { saveExecApprovals } from "../infra/exec-approvals-store.js";
import { testing as execApprovalsStoreTesting } from "../infra/exec-approvals-store.test-support.js";
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
import { captureEnv, setTestEnvValue } from "../test-utils/env.js";
const envSnapshot = captureEnv(["HOME", "OPENCLAW_HOME", "OPENCLAW_STATE_DIR"]);
const tempHomes: string[] = [];
function useTempHome(): string {
const home = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-approval-runtime-"));
tempHomes.push(home);
setTestEnvValue("HOME", home);
setTestEnvValue("OPENCLAW_HOME", home);
setTestEnvValue("OPENCLAW_STATE_DIR", path.join(home, ".openclaw"));
closeOpenClawStateDatabaseForTest();
execApprovalsStoreTesting.reset();
return home;
}
function execApprovalsPath(home: string): string {
return path.join(home, ".openclaw", "exec-approvals.json");
}
function writeExecApprovalsToken(_home: string, token: string): void {
saveExecApprovals({
version: 1,
socket: {
path: "~/.openclaw/exec-approvals.sock",
token,
},
agents: {},
} satisfies ExecApprovalsFile);
}
async function importRuntimeTokenModule(): Promise<
typeof import("./operator-approval-runtime-token.js")
> {
vi.resetModules();
return await import("./operator-approval-runtime-token.js");
}
afterEach(() => {
closeOpenClawStateDatabaseForTest();
execApprovalsStoreTesting.reset();
vi.resetModules();
envSnapshot.restore();
for (const home of tempHomes.splice(0)) {
fs.rmSync(home, { recursive: true, force: true });
}
});
describe("operator approval runtime token", () => {
it("derives the shared approval runtime token from the exec approvals socket token", async () => {
const home = useTempHome();
writeExecApprovalsToken(home, "shared-runtime-token");
const runtimeToken = await importRuntimeTokenModule();
const sharedToken = runtimeToken.getOperatorApprovalRuntimeToken();
expect(sharedToken).toEqual(expect.any(String));
expect(sharedToken).not.toBe("shared-runtime-token");
expect(runtimeToken.isOperatorApprovalRuntimeToken(` ${sharedToken} `)).toBe(true);
expect(runtimeToken.isOperatorApprovalRuntimeToken(sharedToken.slice(0, -1))).toBe(false);
expect(runtimeToken.isOperatorApprovalRuntimeToken("shared-runtime-token")).toBe(false);
expect(runtimeToken.isOperatorApprovalRuntimeToken("different-token")).toBe(false);
});
it("does not pin the process fallback once a shared exec approvals token appears", async () => {
const home = useTempHome();
const runtimeToken = await importRuntimeTokenModule();
const fallback = runtimeToken.getOperatorApprovalRuntimeToken();
writeExecApprovalsToken(home, "late-shared-runtime-token");
const sharedToken = runtimeToken.getOperatorApprovalRuntimeToken();
expect(sharedToken).not.toBe(fallback);
expect(sharedToken).not.toBe("late-shared-runtime-token");
expect(runtimeToken.isOperatorApprovalRuntimeToken(fallback)).toBe(true);
expect(runtimeToken.isOperatorApprovalRuntimeToken(sharedToken)).toBe(true);
expect(runtimeToken.isOperatorApprovalRuntimeToken("late-shared-runtime-token")).toBe(false);
});
it("keeps a stable process fallback without creating exec-approvals.json", async () => {
const home = useTempHome();
const runtimeToken = await importRuntimeTokenModule();
const first = runtimeToken.getOperatorApprovalRuntimeToken();
const second = runtimeToken.getOperatorApprovalRuntimeToken();
expect(first).toEqual(expect.any(String));
expect(second).toBe(first);
expect(fs.existsSync(execApprovalsPath(home))).toBe(false);
});
});