mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
test(plugin-state): seed limit fixtures in one transaction
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
} from "./plugin-state-store.js";
|
||||
import { resolvePluginStateDir, resolvePluginStateSqlitePath } from "./plugin-state-store.paths.js";
|
||||
import { MAX_PLUGIN_STATE_ENTRIES_PER_PLUGIN } from "./plugin-state-store.sqlite.js";
|
||||
import { seedPluginStateEntriesForTests } from "./plugin-state-store.test-helpers.js";
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
@@ -198,21 +199,25 @@ describe("limits", () => {
|
||||
// namespace eviction never fires (each namespace has generous room).
|
||||
const nsCount = 10;
|
||||
const perNs = MAX_PLUGIN_STATE_ENTRIES_PER_PLUGIN / nsCount; // 100
|
||||
const stores = Array.from({ length: nsCount }, (_, i) =>
|
||||
createPluginStateKeyedStore("fixture-plugin", {
|
||||
namespace: `ns-${i}`,
|
||||
maxEntries: perNs + 1,
|
||||
seedPluginStateEntriesForTests(
|
||||
Array.from({ length: MAX_PLUGIN_STATE_ENTRIES_PER_PLUGIN }, (_, index) => {
|
||||
const ns = Math.floor(index / perNs);
|
||||
const k = index % perNs;
|
||||
return {
|
||||
pluginId: "fixture-plugin",
|
||||
namespace: `ns-${ns}`,
|
||||
key: `k-${k}`,
|
||||
value: { ns, k },
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
for (let ns = 0; ns < nsCount; ns += 1) {
|
||||
for (let k = 0; k < perNs; k += 1) {
|
||||
await stores[ns].register(`k-${k}`, { ns, k });
|
||||
}
|
||||
}
|
||||
const store = createPluginStateKeyedStore("fixture-plugin", {
|
||||
namespace: "ns-0",
|
||||
maxEntries: perNs + 1,
|
||||
});
|
||||
|
||||
// One more row tips over the plugin-wide limit.
|
||||
await expect(stores[0].register("overflow", { boom: true })).rejects.toMatchObject({
|
||||
await expect(store.register("overflow", { boom: true })).rejects.toMatchObject({
|
||||
code: "PLUGIN_STATE_LIMIT_EXCEEDED",
|
||||
});
|
||||
});
|
||||
|
||||
67
src/plugin-state/plugin-state-store.test-helpers.ts
Normal file
67
src/plugin-state/plugin-state-store.test-helpers.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { requireNodeSqlite } from "../infra/node-sqlite.js";
|
||||
import { resolvePluginStateSqlitePath } from "./plugin-state-store.paths.js";
|
||||
import { closePluginStateSqliteStore, probePluginStateStore } from "./plugin-state-store.sqlite.js";
|
||||
|
||||
export type PluginStateSeedEntry = {
|
||||
pluginId: string;
|
||||
namespace: string;
|
||||
key: string;
|
||||
value: unknown;
|
||||
createdAt?: number;
|
||||
expiresAt?: number | null;
|
||||
};
|
||||
|
||||
export function seedPluginStateEntriesForTests(entries: PluginStateSeedEntry[]): void {
|
||||
if (entries.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
probePluginStateStore();
|
||||
closePluginStateSqliteStore();
|
||||
|
||||
const { DatabaseSync } = requireNodeSqlite();
|
||||
const db = new DatabaseSync(resolvePluginStateSqlitePath());
|
||||
const insertEntry = db.prepare(`
|
||||
INSERT INTO plugin_state_entries (
|
||||
plugin_id,
|
||||
namespace,
|
||||
entry_key,
|
||||
value_json,
|
||||
created_at,
|
||||
expires_at
|
||||
) VALUES (
|
||||
@plugin_id,
|
||||
@namespace,
|
||||
@entry_key,
|
||||
@value_json,
|
||||
@created_at,
|
||||
@expires_at
|
||||
)
|
||||
`);
|
||||
const now = Date.now();
|
||||
|
||||
db.exec("BEGIN IMMEDIATE");
|
||||
try {
|
||||
for (let index = 0; index < entries.length; index += 1) {
|
||||
const entry = entries[index];
|
||||
const valueJson = JSON.stringify(entry.value);
|
||||
if (valueJson == null) {
|
||||
throw new Error("plugin state seed value must be JSON serializable");
|
||||
}
|
||||
insertEntry.run({
|
||||
plugin_id: entry.pluginId,
|
||||
namespace: entry.namespace,
|
||||
entry_key: entry.key,
|
||||
value_json: valueJson,
|
||||
created_at: entry.createdAt ?? now + index,
|
||||
expires_at: entry.expiresAt ?? null,
|
||||
});
|
||||
}
|
||||
db.exec("COMMIT");
|
||||
} catch (error) {
|
||||
db.exec("ROLLBACK");
|
||||
throw error;
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
sweepExpiredPluginStateEntries,
|
||||
} from "./plugin-state-store.js";
|
||||
import { resolvePluginStateDir, resolvePluginStateSqlitePath } from "./plugin-state-store.paths.js";
|
||||
import { seedPluginStateEntriesForTests } from "./plugin-state-store.test-helpers.js";
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
@@ -126,22 +127,38 @@ describe("plugin state keyed store", () => {
|
||||
|
||||
it("rejects when the per-plugin live row ceiling would be exceeded without evicting siblings", async () => {
|
||||
await withOpenClawTestState({ label: "plugin-state-plugin-limit" }, async () => {
|
||||
const stores = Array.from({ length: 10 }, (_, index) =>
|
||||
createPluginStateKeyedStore("discord", {
|
||||
namespace: `ns-${index}`,
|
||||
maxEntries: 101,
|
||||
}),
|
||||
);
|
||||
for (let namespaceIndex = 0; namespaceIndex < stores.length; namespaceIndex += 1) {
|
||||
for (let entryIndex = 0; entryIndex < 100; entryIndex += 1) {
|
||||
await stores[namespaceIndex].register(`k-${entryIndex}`, { namespaceIndex, entryIndex });
|
||||
}
|
||||
}
|
||||
seedPluginStateEntriesForTests([
|
||||
...Array.from({ length: 999 }, (_, entryIndex) => ({
|
||||
pluginId: "discord",
|
||||
namespace: "limit",
|
||||
key: `k-${entryIndex}`,
|
||||
value: { namespaceIndex: 0, entryIndex },
|
||||
})),
|
||||
{
|
||||
pluginId: "discord",
|
||||
namespace: "sibling",
|
||||
key: "k-0",
|
||||
value: { namespaceIndex: 1, entryIndex: 0 },
|
||||
},
|
||||
]);
|
||||
|
||||
await expect(stores[0].register("overflow", { overflow: true })).rejects.toMatchObject({
|
||||
const limitStore = createPluginStateKeyedStore("discord", {
|
||||
namespace: "limit",
|
||||
maxEntries: 1_001,
|
||||
});
|
||||
const siblingStore = createPluginStateKeyedStore("discord", {
|
||||
namespace: "sibling",
|
||||
maxEntries: 10,
|
||||
});
|
||||
|
||||
await expect(limitStore.register("overflow", { overflow: true })).rejects.toMatchObject({
|
||||
code: "PLUGIN_STATE_LIMIT_EXCEEDED",
|
||||
});
|
||||
await expect(stores[1].lookup("k-0")).resolves.toEqual({ namespaceIndex: 1, entryIndex: 0 });
|
||||
await expect(siblingStore.lookup("k-0")).resolves.toEqual({
|
||||
namespaceIndex: 1,
|
||||
entryIndex: 0,
|
||||
});
|
||||
await expect(limitStore.lookup("overflow")).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user