Files
Endoleap/tests/headless/ledger.test.mjs

60 lines
4.5 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createDeque, createMinHeap } from '../../src/lib/canvas-runtime/structures.ts';
import { createLedger, compressChanges } from '../../src/lib/canvas-runtime/ledger.ts';
const node = { id: 'a', incarnation: 1, ownerCanvasId: 'root', componentId: 'text', dataVersion: 1, data: { title: 'a', text: '' }, structure: 'node', geometry: { x: 0, y: 0, width: 20, height: 20, rotation: 0 } };
const intent = (id, changes = [], dependencies = []) => ({ id, operationType: 'document.change', operationSchemaVersion: 1, sequence: 1, origin: 'user', changes, dependencies });
const frozen = ledger => ledger.freeze({ id: 'request', operationIds: ['create'], canvasIds: ['root'], expectedVersions: { root: 1 }, payload: { changes: [] } });
test('FIFO retains order across compaction; retry heap orders deadline then sequence', () => {
const queue = createDeque(); for (let i = 0; i < 4000; i++) queue.push(i);
for (let i = 0; i < 4000; i++) assert.equal(queue.pop(), i);
assert.equal(queue.size, 0); assert.equal(queue.pop(), undefined);
const heap = createMinHeap((a, b) => a.due - b.due || a.sequence - b.sequence);
for (const value of [{ due: 5, sequence: 2 }, { due: 2, sequence: 9 }, { due: 5, sequence: 1 }, { due: 1, sequence: 8 }]) heap.push(value);
assert.deepEqual([heap.pop(), heap.pop(), heap.pop(), heap.pop()].map(item => [item.due, item.sequence]), [[1,8],[2,9],[5,1],[5,2]]);
assert.equal(heap.pop(), undefined);
});
test('wholly unsent create-data-delete disappears but a position update preserves the title', () => {
const create = { kind: 'element', id: 'a', before: null, after: node };
const rename = { kind: 'field', target: { id: 'a', incarnation: 1 }, field: 'data.title', before: 'a', after: 'renamed', beforePresent: true, afterPresent: true };
const move = { kind: 'field', target: { id: 'a', incarnation: 1 }, field: 'geometry', before: node.geometry, after: { ...node.geometry, x: 100 } };
const compressed = compressChanges([create, rename, move]);
assert.equal(compressed.length, 1); assert.equal(compressed[0].after.data.title, 'renamed');
assert.deepEqual(compressChanges([...compressed, { kind: 'element', id: 'a', before: compressed[0].after, after: null }]), []);
});
test('frozen identity remains exact after unknown/accepted outcomes and deletion is a separate pending operation', () => {
const ledger = createLedger(); ledger.add(intent('create'), { a: 'absent' });
const request = frozen(ledger), body = request.body;
ledger.markDurable(ledger.getSnapshot().generation); ledger.markDispatch('request');
ledger.result('request', 'unresolved', 'transport_unknown');
ledger.add(intent('delete', [], ['create']), { a: 'exists' });
assert.equal(ledger.eligible('delete'), false); assert.equal(ledger.possiblyApplied('request'), true);
ledger.result('request', 'accepted');
assert.equal(ledger.eligible('delete'), false); assert.equal(ledger.getSnapshot().requests.get('request').body, body);
ledger.markDispatch('request'); assert.equal(ledger.getSnapshot().requests.get('request').body, body);
ledger.result('request', 'applied'); assert.equal(ledger.eligible('delete'), true);
});
test('dependencies are explicit, duplicate identities conflict and late durable completion does not mark newer work durable', () => {
const ledger = createLedger();
assert.throws(() => ledger.add(intent('child', [], ['missing']), {}), /dependency_missing/);
ledger.add(intent('first'), {}); const oldGeneration = ledger.getSnapshot().generation;
ledger.add(intent('second', [], ['first']), {}); ledger.markDurable(oldGeneration);
assert.ok(ledger.getSnapshot().durableGeneration < ledger.getSnapshot().generation);
assert.throws(() => ledger.add(intent('first', [], ['second']), {}), /operation_identity_conflict/);
assert.equal(ledger.eligible('second'), false);
});
test('recovery retains possible application; explicit terminal zero-write does not pretend dependent writes succeeded', () => {
const original = createLedger(); original.add(intent('create'), {}); frozen(original); original.markDispatch('request');
const restored = createLedger(original.getSnapshot());
assert.equal(restored.possiblyApplied('request'), true);
restored.result('request', 'not-applied', 'permission');
assert.equal(restored.possiblyApplied('request'), false);
assert.equal(restored.getSnapshot().operations.get('create').phase, 'blocked');
assert.throws(() => restored.result('request', 'applied'), /receipt_conflict/);
});