import { tap, expect } from '@git.zone/tstest/tapbundle'; import { makeTestDb } from './helpers/services.js'; import type * as smartdata from '@push.rocks/smartdata'; import { MongoLedger } from '../ts/ledgers/classes.mongoledger.js'; // Smartdata's CollectionFactory caches collections by class name globally // (see classes.collection.ts:22), so we MUST share a single db across all // tests in a file. We isolate tests via unique ledger names — the EasyStore // stores data keyed by `nameId`, so distinct ledger names live in the same // underlying mongo collection without interference. let db: smartdata.SmartdataDb; let cleanup: () => Promise; tap.test('setup: connect shared db', async () => { const r = await makeTestDb('mongoledger'); db = r.db; cleanup = r.cleanup; }); tap.test('MongoLedger: init creates an empty ledger', async () => { const ledger = new MongoLedger(db, 'init-test'); await ledger.init(); const data = await ledger.read(); expect(data.currentVersion).toBeNull(); expect(data.steps).toEqual({}); expect(data.lock.holder).toBeNull(); expect(data.checkpoints).toEqual({}); }); tap.test('MongoLedger: write+read roundtrips', async () => { const ledger = new MongoLedger(db, 'rw-test'); await ledger.init(); await ledger.write({ currentVersion: '1.5.0', steps: { foo: { id: 'foo', fromVersion: '1.0.0', toVersion: '1.5.0', status: 'applied', startedAt: 'a', finishedAt: 'b', durationMs: 12 } }, lock: { holder: null, acquiredAt: null, expiresAt: null }, checkpoints: { foo: { progress: 42 } }, }); const round = await ledger.read(); expect(round.currentVersion).toEqual('1.5.0'); expect(round.steps['foo'].durationMs).toEqual(12); expect((round.checkpoints['foo'] as any).progress).toEqual(42); }); tap.test('MongoLedger: lock acquire/release', async () => { const ledger = new MongoLedger(db, 'lock-test'); await ledger.init(); const acquired = await ledger.acquireLock('holder-A', 60_000); expect(acquired).toBeTrue(); // A second acquire from a different holder should fail. const acquired2 = await ledger.acquireLock('holder-B', 60_000); expect(acquired2).toBeFalse(); // Release lets holder-B in. await ledger.releaseLock('holder-A'); const acquired3 = await ledger.acquireLock('holder-B', 60_000); expect(acquired3).toBeTrue(); }); tap.test('MongoLedger: expired lock can be stolen', async () => { const ledger = new MongoLedger(db, 'expire-test'); await ledger.init(); // Acquire with a 1ms TTL so it's instantly expired. const got = await ledger.acquireLock('stale-holder', 1); expect(got).toBeTrue(); await new Promise((r) => setTimeout(r, 10)); // Now another holder should be able to take over (lock is expired). const got2 = await ledger.acquireLock('fresh-holder', 60_000); expect(got2).toBeTrue(); }); tap.test('cleanup: close shared db', async () => { await cleanup(); }); export default tap.start();