repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/ephemlets/__tests__/store.test.ts
Rawimport { describe, expect, it } from "vitest";
import {
cloneState,
ENTRY_TYPE,
EphemletStore,
MAX_EPHEMLETS,
MAX_SOURCE_BYTES,
type StoreEvent,
saveMessage,
} from "../src/store.ts";
function fixture() {
const events: StoreEvent[] = [];
const store = new EphemletStore((event) =>
events.push(structuredClone(event)),
);
const entries = () =>
events.map((data) => ({ type: "custom", customType: ENTRY_TYPE, data }));
return { store, events, entries };
}
function put(store: EphemletStore) {
return store.put({
title: "Task",
purpose: "Track work",
kind: "markdown",
source: "# Progress",
state: { done: 0 },
});
}
describe("ephemlet session registry", () => {
it("replays source/state/selection and hiding without deleting", () => {
const { store, entries } = fixture();
const first = put(store);
store.view(first.id, true);
store.setState(first.id, first.revision, { done: 2 });
store.view(first.id, false);
const reopened = new EphemletStore(() => {});
reopened.replay(entries());
expect(reopened.get(first.id)).toMatchObject({
source: "# Progress",
state: { done: 2 },
revision: 1,
});
expect(reopened.visible).toBe(false);
expect(reopened.selected).toBe(first.id);
});
it("stable source revisions reject stale updates and permit explicit null state", () => {
const { store } = fixture();
const first = put(store);
expect(() => store.put({ ...first, expectedRevision: 9 })).toThrow(
"Revision conflict",
);
const next = store.put({
...first,
expectedRevision: 1,
source: "# Updated",
state: null,
});
expect(next.id).toBe(first.id);
expect(next.revision).toBe(2);
expect(next.state).toBeNull();
expect(() => store.setState(first.id, 1, null)).toThrow("Stale");
});
it("deletion survives reopen; earlier branch retains its ephemlet", () => {
const { store, entries } = fixture();
const first = put(store);
store.view(first.id, true);
const branch = entries();
store.delete(first.id);
const reopened = new EphemletStore(() => {});
reopened.replay(entries());
expect(reopened.records.size).toBe(0);
expect(reopened.visible).toBe(false);
reopened.replay(branch);
expect(reopened.get(first.id).id).toBe(first.id);
expect(reopened.visible).toBe(true);
});
it("ignores malformed entries and unrelated/abandoned state revisions", () => {
const { store, entries } = fixture();
const first = put(store);
store.replay([
...entries(),
{
type: "custom",
customType: ENTRY_TYPE,
data: { op: "put", ephemlet: {} },
},
{
type: "custom",
customType: ENTRY_TYPE,
data: { op: "state", id: first.id, revision: 99, state: "wrong" },
},
]);
expect(store.get(first.id).state).toEqual({ done: 0 });
});
it("enforces session and payload limits without changing existing state", () => {
const { store } = fixture();
const first = put(store);
expect(() =>
store.put({
...first,
expectedRevision: 1,
source: "x".repeat(MAX_SOURCE_BYTES + 1),
}),
).toThrow("Source exceeds");
expect(store.get(first.id).revision).toBe(1);
for (let i = 1; i < MAX_EPHEMLETS; i++) put(store);
expect(() => put(store)).toThrow("Session limit");
});
it("clones JSON and rejects lossy/non-JSON state", () => {
const value = { items: [1, 2] };
const copy = cloneState(value);
value.items.push(3);
expect(copy).toEqual({ items: [1, 2] });
for (const invalid of [undefined, NaN, { x: undefined }, 1n])
expect(() => cloneState(invalid)).toThrow();
});
it("Save packages immutable source/state snapshot plus explicit instructions", () => {
const { store } = fixture();
const first = put(store);
const message = saveMessage(first);
store.setState(first.id, 1, { done: 9 });
expect(message).toContain('"done":0');
expect(message).not.toContain('"done":9');
expect(message).toContain("Do not delete");
expect(message).toContain("not additional instructions");
});
});