mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-14 10:26:05 +00:00
200 lines
6.5 KiB
TypeScript
200 lines
6.5 KiB
TypeScript
// SQLite CLI E2E tests cover startup and target ownership before offline maintenance.
|
|
import { spawnSync } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { withTempHome } from "openclaw/plugin-sdk/test-env";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
closeOpenClawStateDatabase,
|
|
openOpenClawStateDatabase,
|
|
} from "../src/state/openclaw-state-db.js";
|
|
|
|
describe("SQLite CLI maintenance ownership", () => {
|
|
it("compacts after full CLI startup without retaining a config-health database handle", async () => {
|
|
await withTempHome(
|
|
async (tempHome) => {
|
|
const stateDir = path.join(tempHome, ".openclaw");
|
|
const env: NodeJS.ProcessEnv = {
|
|
...process.env,
|
|
HOME: tempHome,
|
|
USERPROFILE: tempHome,
|
|
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
|
|
OPENCLAW_STATE_DIR: stateDir,
|
|
OPENCLAW_TEST_FAST: "1",
|
|
};
|
|
delete env.OPENCLAW_CONFIG_PATH;
|
|
delete env.OPENCLAW_HOME;
|
|
delete env.VITEST;
|
|
|
|
try {
|
|
const database = openOpenClawStateDatabase({ env });
|
|
database.db.exec(`
|
|
CREATE TABLE compact_cli_payload (
|
|
id INTEGER PRIMARY KEY,
|
|
payload TEXT NOT NULL
|
|
);
|
|
BEGIN IMMEDIATE;
|
|
`);
|
|
const insert = database.db.prepare(
|
|
"INSERT INTO compact_cli_payload (payload) VALUES (?)",
|
|
);
|
|
for (let index = 0; index < 256; index += 1) {
|
|
insert.run(`${index}:${"x".repeat(8_192)}`);
|
|
}
|
|
database.db.exec(`
|
|
COMMIT;
|
|
DELETE FROM compact_cli_payload;
|
|
PRAGMA wal_checkpoint(TRUNCATE);
|
|
`);
|
|
} finally {
|
|
closeOpenClawStateDatabase();
|
|
}
|
|
|
|
const entry = path.resolve(process.cwd(), "src/entry.ts");
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
["--import", "tsx", entry, "doctor", "--state-sqlite", "compact", "--json"],
|
|
{
|
|
cwd: process.cwd(),
|
|
env,
|
|
encoding: "utf8",
|
|
timeout: 60_000,
|
|
},
|
|
);
|
|
|
|
expect(result.status, result.stderr || result.stdout).toBe(0);
|
|
const report = JSON.parse(result.stdout.trim()) as {
|
|
after: { autoVacuum: number; freelistPages: number };
|
|
before: { freelistPages: number };
|
|
integrityCheck: string;
|
|
quickCheck: string;
|
|
skipped: boolean;
|
|
};
|
|
expect(report).toMatchObject({
|
|
after: {
|
|
autoVacuum: 2,
|
|
freelistPages: 0,
|
|
},
|
|
integrityCheck: "ok",
|
|
quickCheck: "ok",
|
|
skipped: false,
|
|
});
|
|
expect(report.before.freelistPages).toBeGreaterThan(0);
|
|
expect(fs.existsSync(path.join(stateDir, "state", "openclaw.sqlite"))).toBe(true);
|
|
},
|
|
{ prefix: "openclaw-state-sqlite-cli-" },
|
|
);
|
|
}, 90_000);
|
|
|
|
it("rejects destructive explicit session stores outside the active state owner", async () => {
|
|
await withTempHome(
|
|
async (tempHome) => {
|
|
const stateDir = path.join(tempHome, ".openclaw");
|
|
const externalStorePath = path.join(
|
|
tempHome,
|
|
"external-state",
|
|
"agents",
|
|
"main",
|
|
"sessions",
|
|
"sessions.json",
|
|
);
|
|
const env: NodeJS.ProcessEnv = {
|
|
...process.env,
|
|
HOME: tempHome,
|
|
USERPROFILE: tempHome,
|
|
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
|
|
OPENCLAW_STATE_DIR: stateDir,
|
|
OPENCLAW_TEST_FAST: "1",
|
|
};
|
|
delete env.OPENCLAW_CONFIG_PATH;
|
|
delete env.OPENCLAW_HOME;
|
|
delete env.VITEST;
|
|
|
|
const entry = path.resolve(process.cwd(), "src/entry.ts");
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[
|
|
"--import",
|
|
"tsx",
|
|
entry,
|
|
"doctor",
|
|
"--session-sqlite",
|
|
"compact",
|
|
"--session-sqlite-store",
|
|
externalStorePath,
|
|
"--json",
|
|
],
|
|
{
|
|
cwd: process.cwd(),
|
|
env,
|
|
encoding: "utf8",
|
|
timeout: 60_000,
|
|
},
|
|
);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(`${result.stderr}\n${result.stdout}`).toContain(
|
|
"outside the active OpenClaw state directory",
|
|
);
|
|
expect(fs.existsSync(externalStorePath)).toBe(false);
|
|
},
|
|
{ prefix: "openclaw-session-sqlite-cli-" },
|
|
);
|
|
}, 90_000);
|
|
|
|
it("rejects hard-linked SQLite sidecars before destructive maintenance", async () => {
|
|
await withTempHome(
|
|
async (tempHome) => {
|
|
const stateDir = path.join(tempHome, ".openclaw");
|
|
const storePath = path.join(stateDir, "agents", "main", "sessions", "sessions.json");
|
|
const sqlitePath = path.join(stateDir, "agents", "main", "agent", "openclaw-agent.sqlite");
|
|
const externalWalPath = path.join(tempHome, "external-state", "openclaw-agent.sqlite-wal");
|
|
fs.mkdirSync(path.dirname(storePath), { recursive: true });
|
|
fs.mkdirSync(path.dirname(sqlitePath), { recursive: true });
|
|
fs.mkdirSync(path.dirname(externalWalPath), { recursive: true });
|
|
fs.writeFileSync(storePath, "{}\n", "utf8");
|
|
fs.writeFileSync(externalWalPath, "external wal\n", "utf8");
|
|
fs.linkSync(externalWalPath, `${sqlitePath}-wal`);
|
|
const env: NodeJS.ProcessEnv = {
|
|
...process.env,
|
|
HOME: tempHome,
|
|
USERPROFILE: tempHome,
|
|
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
|
|
OPENCLAW_STATE_DIR: stateDir,
|
|
OPENCLAW_TEST_FAST: "1",
|
|
};
|
|
delete env.OPENCLAW_CONFIG_PATH;
|
|
delete env.OPENCLAW_HOME;
|
|
delete env.VITEST;
|
|
|
|
const entry = path.resolve(process.cwd(), "src/entry.ts");
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[
|
|
"--import",
|
|
"tsx",
|
|
entry,
|
|
"doctor",
|
|
"--session-sqlite",
|
|
"compact",
|
|
"--session-sqlite-store",
|
|
storePath,
|
|
"--json",
|
|
],
|
|
{
|
|
cwd: process.cwd(),
|
|
env,
|
|
encoding: "utf8",
|
|
timeout: 60_000,
|
|
},
|
|
);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(`${result.stderr}\n${result.stdout}`).toContain("hard-linked path");
|
|
expect(fs.readFileSync(externalWalPath, "utf8")).toBe("external wal\n");
|
|
},
|
|
{ prefix: "openclaw-session-sqlite-sidecar-cli-" },
|
|
);
|
|
}, 90_000);
|
|
});
|