// The property that matters for a `crdtText()` column is CONVERGENCE: two // clients that edit the same document body concurrently must end at the same // text, no matter which write the server processes first, with BOTH edits // surviving (no last-write-wins loser). // // This runs the real thing with no database and no server: `ctx.store` from // `makeTestContext` is the SAME `wrapStoreWithMixinBehaviour` store the handler // gets in production, so the authoritative server-side CRDT merge on the write // path executes exactly as at runtime — the mutation just writes the incoming // update bytes, and `ctx.store.update` folds them into the stored state. // Importing `../database/schema` registers `documents` / `actors` / `tenants` // so the store knows `body` is a CRDT-managed column. Run with `voltro test`. import { describe, it, expect } from 'vitest' import { makeTestContext, mockStore } from '@voltro/testing' import { crdtText, decodeCrdtText } from '@voltro/local-first' import '../database/schema' // registers documents / actors / tenants import createDocument from '../mutations/documents.create.mutation.server' import setBody from '../mutations/documents.setBody.mutation.server' const bodyText = (row: { body?: unknown }): string => decodeCrdtText(row.body as Uint8Array) const freshCtx = () => makeTestContext({ subject: { type: 'user', id: 'u1', tenantId: 'acme' }, store: mockStore({ documents: [] }), }) describe('documents.setBody — authoritative server-side CRDT merge', () => { it('folds two concurrent edits together so BOTH survive', async () => { const ctx = freshCtx() const doc = await createDocument({ tenantId: 'acme', title: 'Design doc' }, ctx) await setBody({ id: String(doc['id']), update: crdtText().insert(0, 'Hello ').encode() }, ctx) const merged = await setBody({ id: String(doc['id']), update: crdtText().insert(0, 'World').encode() }, ctx) const text = bodyText(merged) expect(text).toContain('Hello') expect(text).toContain('World') }) it('converges to the SAME text whichever order the server processes the writes', async () => { // Capture the two updates ONCE and replay the SAME bytes in both orders — // reusing the encoded state (not re-generating it) is what makes the CRDT // tie-break deterministic, so this asserts order-independence, not chance. const clientA = crdtText().insert(0, 'Hello ').encode() const clientB = crdtText().insert(0, 'World').encode() const forward = freshCtx() const d1 = await createDocument({ tenantId: 'acme', title: 'Doc' }, forward) await setBody({ id: String(d1['id']), update: clientA }, forward) const fwd = await setBody({ id: String(d1['id']), update: clientB }, forward) const reverse = freshCtx() const d2 = await createDocument({ tenantId: 'acme', title: 'Doc' }, reverse) await setBody({ id: String(d2['id']), update: clientB }, reverse) const rev = await setBody({ id: String(d2['id']), update: clientA }, reverse) expect(bodyText(fwd)).toBe(bodyText(rev)) // order-independent convergence }) it('is idempotent — re-applying an update already folded in changes nothing', async () => { const ctx = freshCtx() const doc = await createDocument({ tenantId: 'acme', title: 'Doc' }, ctx) const update = crdtText().insert(0, 'once').encode() const first = await setBody({ id: String(doc['id']), update }, ctx) const again = await setBody({ id: String(doc['id']), update }, ctx) expect(bodyText(again)).toBe(bodyText(first)) expect(bodyText(again)).toBe('once') }) it('a first write establishes the document (no stored state yet)', async () => { const ctx = freshCtx() const doc = await createDocument({ tenantId: 'acme', title: 'Doc' }, ctx) const merged = await setBody({ id: String(doc['id']), update: crdtText().insert(0, 'seed').encode() }, ctx) expect(bodyText(merged)).toBe('seed') }) // NOT "404s" — the assertion is that the two cases are INDISTINGUISHABLE. // `documents` is `.with(tenant())`, so a keyed write to a row that does not // exist and one to another tenant's row both throw the same `TenantRowNotFound` // carrying the same fields. Answering "not found" for one and "forbidden" for // the other would let a caller walk ids and learn which are real in someone // else's tenant, which is the isolation `tenant()` exists to provide. it('refuses a write to a document the caller cannot see, without saying which case it was', async () => { const ctx = freshCtx() await expect( setBody({ id: 'doc_does_not_exist', update: crdtText().insert(0, 'x').encode() }, ctx), ).rejects.toMatchObject({ _tag: 'TenantRowNotFound', table: 'documents', id: 'doc_does_not_exist' }) }) })