72 lines
2.9 KiB
TypeScript
72 lines
2.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
CanonicalJsonEventCodec,
|
|
EventDB,
|
|
EventHashMismatchError,
|
|
MemoryBlobStorage,
|
|
composeStorageTransforms,
|
|
createAesGcmTransform,
|
|
createEventIdentity,
|
|
createMsgPackTransform,
|
|
type BaseEventData,
|
|
type BlobStorageAddedEvent,
|
|
} from "../src/index.js";
|
|
|
|
interface TestEvent extends BaseEventData {
|
|
readonly type: "test";
|
|
readonly payload: unknown;
|
|
}
|
|
|
|
const codec = new CanonicalJsonEventCodec<TestEvent>();
|
|
const identify = createEventIdentity<TestEvent>((event) => codec.encodeCanonical(event));
|
|
|
|
describe("EventDB", () => {
|
|
it("loads historical events, deduplicates observations, and sorts deterministically", async () => {
|
|
const storage = new ObservableMemoryStorage<TestEvent>();
|
|
const first: TestEvent = { version: 1, timestamp: 2, nonce: "a", type: "test", payload: 1 };
|
|
const firstId = await identify(first);
|
|
await storage.set(firstId, first);
|
|
const database = await EventDB.open(storage, { identify });
|
|
const observed: string[] = [];
|
|
database.on("event", (event) => observed.push(event.id));
|
|
|
|
expect((await database.append(first)).id).toBe(firstId);
|
|
storage.notify({ id: firstId, data: first, source: "remote" });
|
|
const earlier = await database.append({ ...first, timestamp: 1, nonce: "b" });
|
|
const tied = await database.append({ ...first, nonce: "c" });
|
|
|
|
const events = await database.events();
|
|
expect(events[0]?.id).toBe(earlier.id);
|
|
expect(events.slice(1).map((event) => event.id)).toEqual([firstId, tied.id].sort());
|
|
expect(observed).toEqual([earlier.id, tied.id]);
|
|
});
|
|
|
|
it("rejects a logical value stored under the wrong ID", async () => {
|
|
const storage = new MemoryBlobStorage<TestEvent>();
|
|
await storage.set("sha256:wrong", {
|
|
version: 1, timestamp: 1, nonce: "a", type: "test", payload: null,
|
|
});
|
|
await expect(EventDB.open(storage, { identify })).rejects.toBeInstanceOf(EventHashMismatchError);
|
|
});
|
|
|
|
it("keeps event identity stable when storage encryption changes physical bytes", async () => {
|
|
const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]);
|
|
const transform = composeStorageTransforms(
|
|
createMsgPackTransform<TestEvent>(),
|
|
createAesGcmTransform(key),
|
|
);
|
|
const plainStorage = new MemoryBlobStorage<TestEvent>();
|
|
const encryptedStorage = new MemoryBlobStorage<TestEvent>({ transform });
|
|
const plain = await EventDB.open(plainStorage, { identify });
|
|
const encrypted = await EventDB.open(encryptedStorage, { identify });
|
|
const event: TestEvent = { version: 1, timestamp: 1, nonce: "same", type: "test", payload: "value" };
|
|
expect((await plain.append(event)).id).toBe((await encrypted.append(event)).id);
|
|
});
|
|
});
|
|
|
|
class ObservableMemoryStorage<T> extends MemoryBlobStorage<T> {
|
|
notify(event: BlobStorageAddedEvent<T>): void {
|
|
this.emitAdded(event);
|
|
}
|
|
}
|