OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-room / test / lease.test.js

47 lines · javascript ref:
// Lease: single active-signer election. Injected `now` makes the wall-clock TTL deterministic.
// Mock KV log (last-write-wins models the autobase's deterministic linearized winner).

import '../lib/bare-globals.js'
import test from 'brittle'
import { Lease } from '../lib/lease.js'

function mockSB () {
  const m = new Map()
  return { m, async set (k, v) { m.set(k, v) }, async get (k) { return m.has(k) ? m.get(k) : null }, async del (k) { m.delete(k) }, async list (p) { const o = {}; for (const [k, v] of m) if (k.startsWith(p)) o[k] = v; return o } }
}

test('claim → single holder; standby takes over ONLY after TTL lapse (no live-lease steal)', async (t) => {
  const sb = mockSB()
  const A = new Lease(sb, { self: 'A', ttlMs: 1000 })
  const B = new Lease(sb, { self: 'B', ttlMs: 1000 })
  const t0 = 1000
  await A.claim(t0)
  t.ok(await A.holds(t0), 'A holds after claiming')
  t.absent(await B.holds(t0), 'B does not hold')
  t.is(await B.claim(t0), null, 'B CANNOT steal A\'s LIVE lease (split-brain fix)')
  t.ok(await A.holds(t0), 'A still holds — no split-brain')
  // A goes silent; once its lease TTL-lapses, B may take over (failover).
  t.is(await B.claim(t0 + 1001), 2, 'epoch monotonic (B claims epoch 2 after A lapsed)')
  t.ok(await B.holds(t0 + 1001), 'B now holds')
  t.absent(await A.holds(t0 + 1001), 'A lost the lease')
  t.is((await A.current()).holder, 'B', 'current holder = B (linearized winner)')
})

test('expiry + renew (out-of-apply wall-clock TTL)', async (t) => {
  const sb = mockSB()
  const A = new Lease(sb, { self: 'A', ttlMs: 1000 })
  const B = new Lease(sb, { self: 'B', ttlMs: 1000 })
  await A.claim(1000)
  t.ok(await A.holds(1500), 'holds within ttl')
  t.absent(await A.holds(2001), 'expired past ttl')
  t.ok(await A.isFree(2001), 'free to reclaim after expiry')
  t.absent(await B.renew(1500), 'B cannot renew a lease it does not hold')
  t.ok(await A.renew(1900), 'A renews (bumps ts)')
  t.ok(await A.holds(2100), 'renewed → live again')
})

test('guards', (t) => {
  t.exception(() => new Lease(null, { self: 'A' }), /statebase/)
  t.exception(() => new Lease(mockSB(), {}), /self/)
})