// Tests to cover stale PID and lock file cleanup in teleportBotAgent import { ChildProcess, spawn } from 'child_process'; import fs from 'fs'; import path from 'path'; import { teleportBotAgent } from '../src/services/teleportBotAgent'; // Mock spawn to simulate tbot process jest.mock('child_process', () => ({ spawn: jest.fn(), })); describe('teleportBotAgent cleanup behaviors', () => { const workDir = '/tmp/test-work'; const pidFile = path.join(workDir, 'tbot.pid'); const dataDir = path.join(workDir, 'data'); const lockFile = path.join(dataDir, 'lock'); const destDir = path.join(workDir, 'dest'); let spawnMock: jest.MockedFunction; let killSpy: jest.SpyInstance; let existsSpy: jest.SpyInstance; let readSpy: jest.SpyInstance; let unlinkSpy: jest.SpyInstance; let writeSpy: jest.SpyInstance; let logSpy: jest.SpyInstance; beforeAll(() => { logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); }); afterAll(() => { logSpy.mockRestore(); }); beforeEach(() => { // Reset and stub spawn spawnMock = (spawn as unknown) as jest.MockedFunction; spawnMock.mockReset(); const fakeProc = { pid: 4321, unref: jest.fn() } as unknown as ChildProcess; spawnMock.mockReturnValue(fakeProc); // Stub fs methods existsSpy = jest.spyOn(fs, 'existsSync'); readSpy = jest.spyOn(fs, 'readFileSync'); unlinkSpy = jest.spyOn(fs, 'unlinkSync').mockImplementation(() => {}); writeSpy = jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); // Default: no files exist existsSpy.mockImplementation(() => false); // Stub mkdirSync and openSync/closeSync to allow file creation jest.spyOn(fs, 'mkdirSync').mockImplementation((): any => undefined); jest.spyOn(fs, 'openSync').mockImplementation(() => 1); jest.spyOn(fs, 'closeSync').mockImplementation(() => {}); }); afterEach(() => { jest.restoreAllMocks(); }); it('removes stale PID and kills old process', async () => { // PID file exists existsSpy.mockImplementation((p) => p === pidFile); readSpy.mockImplementation(() => '1234'); // Spy on process.kill killSpy = jest.spyOn(process, 'kill').mockImplementation((): any => true); const result = await teleportBotAgent({ appName: 'app', joinToken: 'tok', workDir }); // process.kill called with old PID expect(killSpy).toHaveBeenCalledWith(1234); // Unlink stale PID file expect(unlinkSpy).toHaveBeenCalledWith(pidFile); // New PID written expect(writeSpy).toHaveBeenCalledWith(pidFile, '4321'); expect(result.pid).toBe(4321); }); it('removes stale lock file', async () => { // PID file absent, lock file exists existsSpy.mockImplementation((p) => p === lockFile); // Spy console.warn for lock removal const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); const result = await teleportBotAgent({ appName: 'app', joinToken: 'tok', workDir }); // Lock file removed expect(unlinkSpy).toHaveBeenCalledWith(lockFile); // A warning issued expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('removing stale lock file')); warnSpy.mockRestore(); }); });