/ src / lib / server / daemon / lease-owner.test.ts
lease-owner.test.ts
 1  import { describe, it } from 'node:test'
 2  import assert from 'node:assert/strict'
 3  import { isOwnerProcessDead, parseOwnerPid } from '@/lib/server/daemon/lease-owner'
 4  
 5  function probeThrowing(code: string) {
 6    return {
 7      kill: () => {
 8        const err = new Error('mock probe failure') as NodeJS.ErrnoException
 9        err.code = code
10        throw err
11      },
12    }
13  }
14  
15  const probeAlive = { kill: () => true as const }
16  
17  describe('parseOwnerPid', () => {
18    it('returns the pid for a well-formed owner string', () => {
19      assert.equal(parseOwnerPid('pid:12345:abc'), 12345)
20      assert.equal(parseOwnerPid('pid:1:xyz'), 1)
21    })
22  
23    it('returns null for unrecognised owner strings', () => {
24      assert.equal(parseOwnerPid(null), null)
25      assert.equal(parseOwnerPid(undefined), null)
26      assert.equal(parseOwnerPid(''), null)
27      assert.equal(parseOwnerPid('another process'), null)
28      assert.equal(parseOwnerPid('pid::abc'), null)
29      assert.equal(parseOwnerPid('pid:abc:xyz'), null)
30      assert.equal(parseOwnerPid('host:hostname:pid:1:abc'), null)
31    })
32  
33    it('rejects zero and negative pids', () => {
34      assert.equal(parseOwnerPid('pid:0:abc'), null)
35      assert.equal(parseOwnerPid('pid:-1:abc'), null)
36    })
37  })
38  
39  describe('isOwnerProcessDead — bug #41 stale-lease recovery', () => {
40    it('returns true when the probe reports ESRCH (no such process)', () => {
41      assert.equal(isOwnerProcessDead('pid:99999:abc', probeThrowing('ESRCH')), true)
42    })
43  
44    it('returns false when the probe reports EPERM (process owned by someone else)', () => {
45      // EPERM means the process exists but signal delivery is blocked. Assume alive
46      // and do not steal the lease — bias towards waiting for TTL.
47      assert.equal(isOwnerProcessDead('pid:99999:abc', probeThrowing('EPERM')), false)
48    })
49  
50    it('returns false when the probe succeeds (process is alive)', () => {
51      assert.equal(isOwnerProcessDead('pid:99999:abc', probeAlive), false)
52    })
53  
54    it('returns false for any unknown probe error code (do not guess)', () => {
55      assert.equal(isOwnerProcessDead('pid:99999:abc', probeThrowing('EAGAIN')), false)
56      assert.equal(isOwnerProcessDead('pid:99999:abc', probeThrowing('UNKNOWN')), false)
57    })
58  
59    it('returns false for owner strings we cannot parse (different host, malformed, missing)', () => {
60      assert.equal(isOwnerProcessDead(null, probeThrowing('ESRCH')), false)
61      assert.equal(isOwnerProcessDead('another process', probeThrowing('ESRCH')), false)
62      assert.equal(isOwnerProcessDead('host:remote:pid:1:abc', probeThrowing('ESRCH')), false)
63    })
64  
65    it('refuses to declare its own pid dead even if probe lies', () => {
66      // Defence in depth: the current process is obviously alive; if a
67      // pathological probe returned ESRCH for its own pid, we must not
68      // act on that.
69      const owner = `pid:${process.pid}:self`
70      assert.equal(isOwnerProcessDead(owner, probeThrowing('ESRCH')), false)
71    })
72  })