/ src / lib / server / runtime / daemon-guards.test.ts
daemon-guards.test.ts
 1  import assert from 'node:assert/strict'
 2  import fs from 'node:fs'
 3  import os from 'node:os'
 4  import path from 'node:path'
 5  import { after, before, describe, it } from 'node:test'
 6  
 7  const originalEnv = {
 8    DATA_DIR: process.env.DATA_DIR,
 9    WORKSPACE_DIR: process.env.WORKSPACE_DIR,
10    SWARMCLAW_BUILD_MODE: process.env.SWARMCLAW_BUILD_MODE,
11    SWARMCLAW_DAEMON_AUTOSTART: process.env.SWARMCLAW_DAEMON_AUTOSTART,
12    SWARMCLAW_DAEMON_BACKGROUND_SERVICES: process.env.SWARMCLAW_DAEMON_BACKGROUND_SERVICES,
13  }
14  
15  let tempDir = ''
16  let mod: typeof import('@/lib/server/runtime/daemon-state')
17  
18  before(async () => {
19    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-daemon-guards-'))
20    process.env.DATA_DIR = path.join(tempDir, 'data')
21    process.env.WORKSPACE_DIR = path.join(tempDir, 'workspace')
22    process.env.SWARMCLAW_BUILD_MODE = '1'
23    process.env.SWARMCLAW_DAEMON_AUTOSTART = '0'
24    mod = await import('@/lib/server/runtime/daemon-state')
25  })
26  
27  after(async () => {
28    try { await mod.stopDaemon({ source: 'test-cleanup' }) } catch { /* ignore */ }
29    for (const [key, val] of Object.entries(originalEnv)) {
30      if (val === undefined) delete process.env[key]
31      else process.env[key] = val
32    }
33    fs.rmSync(tempDir, { recursive: true, force: true })
34  })
35  
36  // ── getDaemonStatus includes guards ──────────────────────────────────────
37  
38  describe('getDaemonStatus guards field', () => {
39    it('includes guards key with expected shape', () => {
40      const status = mod.getDaemonStatus()
41      assert.ok('guards' in status, 'status should have guards field')
42      const { guards } = status
43      assert.equal(typeof guards.healthCheckRunning, 'boolean')
44      assert.equal(typeof guards.connectorHealthCheckRunning, 'boolean')
45      assert.equal(typeof guards.shuttingDown, 'boolean')
46      assert.equal(typeof guards.providerCircuitBreakers, 'number')
47    })
48  
49    it('guards defaults are all false/zero when idle', () => {
50      const { guards } = mod.getDaemonStatus()
51      assert.equal(guards.healthCheckRunning, false)
52      assert.equal(guards.connectorHealthCheckRunning, false)
53      assert.equal(guards.shuttingDown, false)
54      assert.equal(guards.providerCircuitBreakers, 0)
55    })
56  })
57  
58  // ── shuttingDown flag lifecycle ──────────────────────────────────────────
59  
60  describe('shuttingDown flag', () => {
61    it('resets to false after stopDaemon completes', async () => {
62      mod.startDaemon({ source: 'test', manualStart: true })
63      await mod.stopDaemon({ source: 'test' })
64      const { guards } = mod.getDaemonStatus()
65      assert.equal(guards.shuttingDown, false)
66    })
67  
68    it('double stopDaemon calls do not throw', async () => {
69      mod.startDaemon({ source: 'test', manualStart: true })
70      await mod.stopDaemon({ source: 'first' })
71      await assert.doesNotReject(() => mod.stopDaemon({ source: 'second' }))
72      assert.equal(mod.getDaemonStatus().running, false)
73    })
74  })