import { describe, expect, it } from "@suaveplan/testing/runner"; import { CrdtSyncManager } from "../src/core/sync-manager/sync-manager.js"; import { GCounter } from "../src/crdts/g-counter/g-counter.js"; import { LWWRegister } from "../src/crdts/lww-register/lww-register.js"; import { MVRegister } from "../src/crdts/mv-register/mv-register.js"; import { ORSet } from "../src/crdts/or-set/or-set.js"; import { PNCounter } from "../src/crdts/pn-counter/pn-counter.js"; import { RGA } from "../src/crdts/rga/rga.js"; import { TwoPSet } from "../src/crdts/two-p-set/two-p-set.js"; describe("CRDT Integration", () => { it("GCounter: all replicas converge after three-way sync", { timeout: 5000, }, () => { const a = new GCounter("shared"); const b = new GCounter("shared"); const c = new GCounter("shared"); a.increment("a", 1); b.increment("b", 2); c.increment("c", 3); a.merge(b.state()); a.merge(c.state()); b.merge(a.state()); c.merge(a.state()); expect(a.total()).toBe(6); expect(b.total()).toBe(6); expect(c.total()).toBe(6); }); it("PNCounter: bidirectional increments and decrements", { timeout: 5000, }, () => { const a = new PNCounter("shared"); const b = new PNCounter("shared"); a.increment("a", 10); b.decrement("b", 4); a.merge(b.state()); b.merge(a.state()); expect(a.total()).toBe(6); expect(b.total()).toBe(6); }); it("ORSet: concurrent add/remove converges", { timeout: 5000 }, () => { const a = new ORSet("s1"); const b = new ORSet("s1"); a.add("x", "a"); b.merge(a.state()); b.add("x", "b"); // new unique tag for same element a.remove("x"); // tombstones a's tag a.merge(b.state()); // b's tag is still alive — element should be present expect(a.has("x")).toBe(true); }); it("LWWRegister: last write wins", { timeout: 5000 }, () => { const a = new LWWRegister("nodeA", "r1"); const b = new LWWRegister("nodeB", "r1"); a.set("old", 100); b.set("new", 200); a.merge(b.state()); expect(a.value()).toBe("new"); }); it("MVRegister: concurrent writes preserved", { timeout: 5000 }, () => { const a = new MVRegister("nodeA", "r1"); const b = new MVRegister("nodeB", "r1"); a.set("val-a"); b.set("val-b"); a.merge(b.state()); const vals = a.get(); expect(vals).toContain("val-a"); expect(vals).toContain("val-b"); }); it("TwoPSet: removal prevents re-add", { timeout: 5000 }, () => { const s = new TwoPSet(); s.add("item"); s.remove("item"); expect(() => s.add("item")).toThrow(); }); it("RGA: three-replica document convergence", { timeout: 5000 }, () => { const a = new RGA("a", "doc"); const b = new RGA("b", "doc"); const c = new RGA("c", "doc"); const r1 = a.insert("A"); b.merge(a.state()); c.merge(a.state()); a.insert("X", r1); b.insert("Y", r1); c.insert("Z", r1); a.merge(b.state()); a.merge(c.state()); b.merge(a.state()); c.merge(a.state()); expect(a.value()).toEqual(b.value()); expect(b.value()).toEqual(c.value()); expect(a.value()).toHaveLength(4); }); it("SyncManager: multi-CRDT sync across replicas", { timeout: 5000, }, () => { const mgrA = new CrdtSyncManager({ nodeId: "a" }); const mgrB = new CrdtSyncManager({ nodeId: "b" }); const counterA = new GCounter("counter"); const counterB = new GCounter("counter"); const setA = new ORSet("set"); const setB = new ORSet("set"); mgrA.register(counterA); mgrA.register(setA); mgrB.register(counterB); mgrB.register(setB); counterA.increment("a", 5); setA.add("hello", "a"); mgrA.tick(); counterB.increment("b", 3); mgrB.tick(); mgrA.syncWith(mgrB); expect(counterA.total()).toBe(8); expect(counterB.total()).toBe(8); expect(setA.has("hello")).toBe(true); expect(setB.has("hello")).toBe(true); }); it("JSON round-trip for all CRDT types", { timeout: 5000 }, () => { const counter = new GCounter("n", "c1"); counter.increment("n", 42); const c2 = new GCounter("n", "c1"); c2.fromJSON(counter.toJSON()); expect(c2.total()).toBe(42); const set = new ORSet("s1"); set.add("foo", "n"); const s2 = new ORSet("s1"); s2.fromJSON(set.toJSON()); expect(s2.has("foo")).toBe(true); const rga = new RGA("n", "r1"); rga.insert(99); const r2 = new RGA("n", "r1"); r2.fromJSON(rga.toJSON()); expect(r2.value()).toContain(99); }); });