Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/ephemlets/__tests__/store.test.ts

Raw
import { 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");
	});
});