/ src / commands / daemon.test.ts
daemon.test.ts
  1  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
  2  
  3  const {
  4    fetchDaemonStatusMock,
  5    requestDaemonShutdownMock,
  6  } = vi.hoisted(() => ({
  7    fetchDaemonStatusMock: vi.fn(),
  8    requestDaemonShutdownMock: vi.fn(),
  9  }));
 10  
 11  vi.mock('../browser/daemon-client.js', () => ({
 12    fetchDaemonStatus: fetchDaemonStatusMock,
 13    requestDaemonShutdown: requestDaemonShutdownMock,
 14  }));
 15  
 16  import { daemonStatus, daemonStop } from './daemon.js';
 17  
 18  describe('daemonStatus', () => {
 19    let stdoutSpy: ReturnType<typeof vi.spyOn>;
 20  
 21    beforeEach(() => {
 22      stdoutSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
 23      fetchDaemonStatusMock.mockReset();
 24      requestDaemonShutdownMock.mockReset();
 25    });
 26  
 27    afterEach(() => {
 28      vi.restoreAllMocks();
 29    });
 30  
 31    it('reports "not running" when daemon is unreachable', async () => {
 32      fetchDaemonStatusMock.mockResolvedValue(null);
 33  
 34      await daemonStatus();
 35  
 36      expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining('not running'));
 37    });
 38  
 39    it('shows daemon info when running', async () => {
 40      fetchDaemonStatusMock.mockResolvedValue({
 41        ok: true,
 42        pid: 12345,
 43        uptime: 3661,
 44        extensionConnected: true,
 45        extensionVersion: '1.6.8',
 46        pending: 0,
 47        memoryMB: 64,
 48        port: 19825,
 49      });
 50  
 51      await daemonStatus();
 52  
 53      expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining('running'));
 54      expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining('PID 12345'));
 55      expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining('1h 1m'));
 56      expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining('connected'));
 57      expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining('v1.6.8'));
 58      expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining('64 MB'));
 59      expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining('19825'));
 60    });
 61  
 62    it('shows disconnected when extension is not connected', async () => {
 63      fetchDaemonStatusMock.mockResolvedValue({
 64        ok: true,
 65        pid: 99,
 66        uptime: 120,
 67        extensionConnected: false,
 68        pending: 0,
 69        memoryMB: 32,
 70        port: 19825,
 71      });
 72  
 73      await daemonStatus();
 74  
 75      expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining('disconnected'));
 76    });
 77  
 78    it('shows version unknown when the connected extension does not report one', async () => {
 79      fetchDaemonStatusMock.mockResolvedValue({
 80        ok: true,
 81        pid: 99,
 82        uptime: 120,
 83        extensionConnected: true,
 84        extensionVersion: undefined,
 85        pending: 0,
 86        memoryMB: 32,
 87        port: 19825,
 88      });
 89  
 90      await daemonStatus();
 91  
 92      expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining('version unknown'));
 93    });
 94  });
 95  
 96  describe('daemonStop', () => {
 97    let stderrSpy: ReturnType<typeof vi.spyOn>;
 98  
 99    beforeEach(() => {
100      stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
101      fetchDaemonStatusMock.mockReset();
102      requestDaemonShutdownMock.mockReset();
103    });
104  
105    afterEach(() => {
106      vi.restoreAllMocks();
107    });
108  
109    it('reports "not running" when daemon is unreachable', async () => {
110      fetchDaemonStatusMock.mockResolvedValue(null);
111  
112      await daemonStop();
113  
114      expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('not running'));
115    });
116  
117    it('sends shutdown and reports success', async () => {
118      fetchDaemonStatusMock.mockResolvedValue({
119        ok: true,
120        pid: 12345,
121        uptime: 100,
122        extensionConnected: true,
123        pending: 0,
124        memoryMB: 50,
125        port: 19825,
126      });
127      requestDaemonShutdownMock.mockResolvedValue(true);
128  
129      await daemonStop();
130  
131      expect(requestDaemonShutdownMock).toHaveBeenCalledTimes(1);
132      expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('Daemon stopped'));
133    });
134  
135    it('reports failure when shutdown request fails', async () => {
136      fetchDaemonStatusMock.mockResolvedValue({
137        ok: true,
138        pid: 12345,
139        uptime: 100,
140        extensionConnected: true,
141        pending: 0,
142        memoryMB: 50,
143        port: 19825,
144      });
145      requestDaemonShutdownMock.mockResolvedValue(false);
146  
147      await daemonStop();
148  
149      expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to stop daemon'));
150    });
151  });