mirror of
https://github.com/unraid/api.git
synced 2026-01-02 14:40:01 -06:00
test: Add unit tests for nodemon path configuration and enhance error handling
- Introduced a new test file to validate nodemon path configurations, ensuring they anchor to the package root by default. - Enhanced the NodemonService to throw an error when nodemon exits immediately after starting, improving robustness. - Added tests to cover scenarios where nodemon fails to start, ensuring proper logging and resource cleanup.
This commit is contained in:
29
api/src/__test__/environment.nodemon-paths.test.ts
Normal file
29
api/src/__test__/environment.nodemon-paths.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
describe('nodemon path configuration', () => {
|
||||
const originalUnraidApiCwd = process.env.UNRAID_API_CWD;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
delete process.env.UNRAID_API_CWD;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalUnraidApiCwd === undefined) {
|
||||
delete process.env.UNRAID_API_CWD;
|
||||
} else {
|
||||
process.env.UNRAID_API_CWD = originalUnraidApiCwd;
|
||||
}
|
||||
});
|
||||
|
||||
it('anchors nodemon paths to the package root by default', async () => {
|
||||
const environment = await import('@app/environment.js');
|
||||
const { UNRAID_API_ROOT, NODEMON_CONFIG_PATH, NODEMON_PATH, UNRAID_API_CWD } = environment;
|
||||
|
||||
expect(UNRAID_API_CWD).toBe(UNRAID_API_ROOT);
|
||||
expect(NODEMON_CONFIG_PATH).toBe(join(UNRAID_API_ROOT, 'nodemon.json'));
|
||||
expect(NODEMON_PATH).toBe(join(UNRAID_API_ROOT, 'node_modules', 'nodemon', 'bin', 'nodemon.js'));
|
||||
});
|
||||
});
|
||||
@@ -39,6 +39,7 @@ describe('NodemonService', () => {
|
||||
const mockMkdir = vi.mocked(fs.mkdir);
|
||||
const mockWriteFile = vi.mocked(fs.writeFile);
|
||||
const mockRm = vi.mocked(fs.rm);
|
||||
const killSpy = vi.spyOn(process, 'kill');
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -46,6 +47,7 @@ describe('NodemonService', () => {
|
||||
mockWriteFile.mockResolvedValue(undefined as unknown as void);
|
||||
mockRm.mockResolvedValue(undefined as unknown as void);
|
||||
vi.mocked(fileExists).mockResolvedValue(false);
|
||||
killSpy.mockReturnValue(true as unknown as boolean);
|
||||
});
|
||||
|
||||
it('ensures directories needed by nodemon exist', async () => {
|
||||
@@ -81,6 +83,7 @@ describe('NodemonService', () => {
|
||||
stderr,
|
||||
unref,
|
||||
} as unknown as ReturnType<typeof execa>);
|
||||
killSpy.mockReturnValue(true as unknown as boolean);
|
||||
|
||||
await service.start({ env: { LOG_LEVEL: 'DEBUG' } });
|
||||
|
||||
@@ -156,6 +159,32 @@ describe('NodemonService', () => {
|
||||
expect(logger.info).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('throws when nodemon exits immediately after start', async () => {
|
||||
const service = new NodemonService(logger);
|
||||
const logStream = { pipe: vi.fn(), close: vi.fn() };
|
||||
vi.mocked(createWriteStream).mockReturnValue(
|
||||
logStream as unknown as ReturnType<typeof createWriteStream>
|
||||
);
|
||||
const stdout = { pipe: vi.fn() };
|
||||
const stderr = { pipe: vi.fn() };
|
||||
const unref = vi.fn();
|
||||
vi.mocked(execa).mockReturnValue({
|
||||
pid: 456,
|
||||
stdout,
|
||||
stderr,
|
||||
unref,
|
||||
} as unknown as ReturnType<typeof execa>);
|
||||
killSpy.mockImplementation(() => {
|
||||
throw new Error('not running');
|
||||
});
|
||||
const logsSpy = vi.spyOn(service, 'logs').mockResolvedValue('recent log lines');
|
||||
|
||||
await expect(service.start()).rejects.toThrow(/Nodemon exited immediately/);
|
||||
expect(logStream.close).toHaveBeenCalled();
|
||||
expect(mockRm).toHaveBeenCalledWith('/var/run/unraid-api/nodemon.pid', { force: true });
|
||||
expect(logsSpy).toHaveBeenCalledWith(50);
|
||||
});
|
||||
|
||||
it('returns not running when pid file is missing', async () => {
|
||||
const service = new NodemonService(logger);
|
||||
vi.mocked(fileExists).mockResolvedValue(false);
|
||||
|
||||
@@ -89,6 +89,18 @@ export class NodemonService {
|
||||
}
|
||||
|
||||
await writeFile(NODEMON_PID_PATH, `${nodemonProcess.pid}`);
|
||||
|
||||
// Give nodemon a brief moment to boot, then verify it is still alive.
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
const stillRunning = await this.isPidRunning(nodemonProcess.pid);
|
||||
if (!stillRunning) {
|
||||
const recentLogs = await this.logs(50);
|
||||
await rm(NODEMON_PID_PATH, { force: true });
|
||||
logStream.close();
|
||||
const logMessage = recentLogs ? ` Recent logs:\n${recentLogs}` : '';
|
||||
throw new Error(`Nodemon exited immediately after start.${logMessage}`);
|
||||
}
|
||||
|
||||
this.logger.info(`Started nodemon (pid ${nodemonProcess.pid})`);
|
||||
} catch (error) {
|
||||
logStream.close();
|
||||
|
||||
Reference in New Issue
Block a user