openclaw / src /plugin-state /plugin-blob-store.readonly.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
76289e7 verified
Raw
History Blame Contribute Delete
8.56 kB
import { existsSync, mkdirSync, readFileSync } from "node:fs";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import { afterEach, describe, expect, it } from "vitest";
import {
clearOpenClawDatabaseQuarantine,
recordOpenClawDatabaseQuarantine,
} from "../state/openclaw-quarantine-store.js";
import { closeOpenClawStateDatabaseByPath } from "../state/openclaw-state-db-cache.js";
import { OPENCLAW_STATE_SCHEMA_VERSION } from "../state/openclaw-state-db-contract.js";
import {
clearOpenClawStateDatabaseOpenFailure,
isOpenClawStateDatabaseOpen,
openOpenClawStateDatabase,
recordOpenClawStateDatabaseOpenFailure,
} from "../state/openclaw-state-db.js";
import { resolveOpenClawStateSqlitePath } from "../state/openclaw-state-db.paths.js";
import { withOpenClawTestState } from "../test-utils/openclaw-test-state.js";
import {
createPluginBlobStoreForTests,
resetPluginBlobStoreForTests,
} from "./plugin-blob-store.js";
afterEach(() => resetPluginBlobStoreForTests());
function createStore(env: NodeJS.ProcessEnv) {
return createPluginBlobStoreForTests<{ version: number }>(
"diffs",
{ namespace: "readonly", maxEntries: 3, maxBytesPerEntry: 16, maxBytesPerNamespace: 32 },
env,
);
}
describe("plugin blob read-only access", () => {
it("returns empty reads without creating an absent database", async () => {
await withOpenClawTestState({ label: "blob-read-absent", applyEnv: false }, async (state) => {
const store = createStore(state.env);
const databasePath = resolveOpenClawStateSqlitePath(state.env);
await expect(store.lookup("missing")).resolves.toBeUndefined();
await expect(store.entries()).resolves.toEqual([]);
expect(existsSync(path.dirname(databasePath))).toBe(false);
expect(isOpenClawStateDatabaseOpen(databasePath)).toBe(false);
});
});
it("reads committed blobs after close without reopening a writable owner", async () => {
await withOpenClawTestState({ label: "blob-read-reopen", applyEnv: false }, async (state) => {
const store = createStore(state.env);
await store.register("saved", new Uint8Array([1, 2]), { version: 1 });
const databasePath = resolveOpenClawStateSqlitePath(state.env);
expect(closeOpenClawStateDatabaseByPath(databasePath)).toBe(true);
const entry = await store.lookup("saved");
expect(entry).toMatchObject({ metadata: { version: 1 }, bytes: new Uint8Array([1, 2]) });
entry!.bytes[0] = 9;
await expect(store.lookup("saved")).resolves.toMatchObject({ bytes: new Uint8Array([1, 2]) });
await expect(store.entries()).resolves.toMatchObject([{ key: "saved", sizeBytes: 2 }]);
expect(isOpenClawStateDatabaseOpen(databasePath)).toBe(false);
});
});
it("keeps an active writer's uncommitted changes out of blob reads", async () => {
await withOpenClawTestState(
{ label: "blob-read-transaction", applyEnv: false },
async (state) => {
const store = createStore(state.env);
await store.register("saved", new Uint8Array([1]), { version: 1 });
const { db } = openOpenClawStateDatabase({ env: state.env });
db.exec("BEGIN IMMEDIATE; DELETE FROM plugin_blob_entries;");
try {
await expect(store.lookup("saved")).resolves.toMatchObject({ metadata: { version: 1 } });
await expect(store.entries()).resolves.toMatchObject([{ key: "saved" }]);
} finally {
db.exec("ROLLBACK");
}
await expect(store.lookup("saved")).resolves.toMatchObject({ metadata: { version: 1 } });
},
);
});
it("leaves a checkpoint-only database unchanged when its blob table is absent", async () => {
await withOpenClawTestState(
{ label: "blob-read-bootstrap", applyEnv: false },
async (state) => {
const databasePath = resolveOpenClawStateSqlitePath(state.env);
mkdirSync(path.dirname(databasePath), { recursive: true });
const db = new DatabaseSync(databasePath);
db.exec(`
CREATE TABLE schema_meta (
meta_key TEXT NOT NULL PRIMARY KEY, role TEXT NOT NULL, schema_version INTEGER NOT NULL,
agent_id TEXT, app_version TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
);
CREATE TABLE state_leases (
scope TEXT NOT NULL, lease_key TEXT NOT NULL, owner TEXT NOT NULL, expires_at INTEGER,
heartbeat_at INTEGER, payload_json TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL,
PRIMARY KEY (scope, lease_key)
);
`);
db.close();
const before = readFileSync(databasePath);
const store = createStore(state.env);
await expect(store.lookup("missing")).resolves.toBeUndefined();
await expect(store.entries()).resolves.toEqual([]);
expect(readFileSync(databasePath)).toEqual(before);
expect(isOpenClawStateDatabaseOpen(databasePath)).toBe(false);
},
);
});
it("reports a missing initialized blob table as a read error without repairing it", async () => {
await withOpenClawTestState({ label: "blob-read-damaged", applyEnv: false }, async (state) => {
const store = createStore(state.env);
await store.register("saved", new Uint8Array([1]), { version: 1 });
const databasePath = resolveOpenClawStateSqlitePath(state.env);
openOpenClawStateDatabase({ env: state.env }).db.exec("DROP TABLE plugin_blob_entries");
closeOpenClawStateDatabaseByPath(databasePath);
const before = readFileSync(databasePath);
for (const operation of ["lookup", "entries"] as const) {
await expect(
operation === "lookup" ? store.lookup("saved") : store.entries(),
).rejects.toMatchObject({
code: "PLUGIN_BLOB_READ_FAILED",
operation,
path: databasePath,
});
}
expect(readFileSync(databasePath)).toEqual(before);
expect(isOpenClawStateDatabaseOpen(databasePath)).toBe(false);
});
});
it.each(["warm", "cold"])(
"rejects a newer schema through %s acquisition",
async (temperature) => {
await withOpenClawTestState({ label: "blob-read-newer", applyEnv: false }, async (state) => {
const store = createStore(state.env);
await store.register("saved", new Uint8Array([1]), { version: 1 });
const { db, path: databasePath } = openOpenClawStateDatabase({ env: state.env });
db.exec(`PRAGMA user_version = ${OPENCLAW_STATE_SCHEMA_VERSION + 1};`);
if (temperature === "cold") {
closeOpenClawStateDatabaseByPath(databasePath);
}
for (const operation of ["lookup", "entries"] as const) {
await expect(
operation === "lookup" ? store.lookup("saved") : store.entries(),
).rejects.toMatchObject({
code: "PLUGIN_BLOB_OPEN_FAILED",
operation,
path: databasePath,
});
}
});
},
);
it("preserves process-local and persisted quarantine failures on cold reads", async () => {
await withOpenClawTestState(
{ label: "blob-read-quarantine", applyEnv: false },
async (state) => {
const store = createStore(state.env);
await store.register("saved", new Uint8Array([1]), { version: 1 });
const databasePath = resolveOpenClawStateSqlitePath(state.env);
closeOpenClawStateDatabaseByPath(databasePath);
recordOpenClawStateDatabaseOpenFailure(databasePath, new Error("latched failure"));
try {
await expect(store.lookup("saved")).rejects.toMatchObject({
code: "PLUGIN_BLOB_OPEN_FAILED",
});
await expect(store.entries()).rejects.toMatchObject({ code: "PLUGIN_BLOB_OPEN_FAILED" });
} finally {
clearOpenClawStateDatabaseOpenFailure(databasePath);
}
expect(
recordOpenClawDatabaseQuarantine({
env: state.env,
kind: "state",
path: databasePath,
reason: "persisted failure",
}),
).toBe(true);
try {
await expect(store.lookup("saved")).rejects.toMatchObject({
code: "PLUGIN_BLOB_OPEN_FAILED",
});
await expect(store.entries()).rejects.toMatchObject({ code: "PLUGIN_BLOB_OPEN_FAILED" });
} finally {
clearOpenClawStateDatabaseOpenFailure(databasePath);
expect(clearOpenClawDatabaseQuarantine(databasePath, { env: state.env })).toBe(true);
}
},
);
});
});