Files
automaker/apps/server/tests/unit/services/dev-server-service.test.ts
DhanushSantosh 1a460c301a fix(test): Set HOSTNAME in dev server tests for consistent behavior
Dev server test was failing on non-localhost hostnames (e.g., 'fedora')
because it expected 'localhost' in the URL. Now sets HOSTNAME env var
in test setup and restores it in teardown for consistent test behavior
across all environments.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 19:55:23 +05:30

396 lines
14 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { EventEmitter } from 'events';
import path from 'path';
import os from 'os';
import fs from 'fs/promises';
// Mock child_process
vi.mock('child_process', () => ({
spawn: vi.fn(),
execSync: vi.fn(),
execFile: vi.fn(),
}));
// Mock secure-fs
vi.mock('@/lib/secure-fs.js', () => ({
access: vi.fn(),
}));
// Mock net
vi.mock('net', () => ({
default: {
createServer: vi.fn(),
},
createServer: vi.fn(),
}));
import { spawn, execSync } from 'child_process';
import * as secureFs from '@/lib/secure-fs.js';
import net from 'net';
describe('dev-server-service.ts', () => {
let testDir: string;
let originalHostname: string | undefined;
beforeEach(async () => {
vi.clearAllMocks();
vi.resetModules();
// Store and set HOSTNAME for consistent test behavior
originalHostname = process.env.HOSTNAME;
process.env.HOSTNAME = 'localhost';
testDir = path.join(os.tmpdir(), `dev-server-test-${Date.now()}`);
await fs.mkdir(testDir, { recursive: true });
// Default mock for secureFs.access - return resolved (file exists)
vi.mocked(secureFs.access).mockResolvedValue(undefined);
// Default mock for net.createServer - port available
const mockServer = new EventEmitter() as any;
mockServer.listen = vi.fn().mockImplementation((port: number, host: string) => {
process.nextTick(() => mockServer.emit('listening'));
});
mockServer.close = vi.fn();
vi.mocked(net.createServer).mockReturnValue(mockServer);
// Default mock for execSync - no process on port
vi.mocked(execSync).mockImplementation(() => {
throw new Error('No process found');
});
});
afterEach(async () => {
// Restore original HOSTNAME
if (originalHostname === undefined) {
delete process.env.HOSTNAME;
} else {
process.env.HOSTNAME = originalHostname;
}
try {
await fs.rm(testDir, { recursive: true, force: true });
} catch {
// Ignore cleanup errors
}
});
describe('getDevServerService', () => {
it('should return a singleton instance', async () => {
const { getDevServerService } = await import('@/services/dev-server-service.js');
const instance1 = getDevServerService();
const instance2 = getDevServerService();
expect(instance1).toBe(instance2);
});
});
describe('startDevServer', () => {
it('should return error if worktree path does not exist', async () => {
vi.mocked(secureFs.access).mockRejectedValueOnce(new Error('File not found'));
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
const result = await service.startDevServer('/project', '/nonexistent/worktree');
expect(result.success).toBe(false);
expect(result.error).toContain('does not exist');
});
it('should return error if no package.json found', async () => {
vi.mocked(secureFs.access).mockImplementation(async (p: any) => {
if (typeof p === 'string' && p.includes('package.json')) {
throw new Error('File not found');
}
return undefined;
});
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
const result = await service.startDevServer(testDir, testDir);
expect(result.success).toBe(false);
expect(result.error).toContain('No package.json found');
});
it('should detect npm as package manager with package-lock.json', async () => {
vi.mocked(secureFs.access).mockImplementation(async (p: any) => {
const pathStr = typeof p === 'string' ? p : '';
if (pathStr.includes('bun.lockb')) throw new Error('Not found');
if (pathStr.includes('pnpm-lock.yaml')) throw new Error('Not found');
if (pathStr.includes('yarn.lock')) throw new Error('Not found');
if (pathStr.includes('package-lock.json')) return undefined;
if (pathStr.includes('package.json')) return undefined;
return undefined;
});
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
expect(spawn).toHaveBeenCalledWith('npm', ['run', 'dev'], expect.any(Object));
});
it('should detect yarn as package manager with yarn.lock', async () => {
vi.mocked(secureFs.access).mockImplementation(async (p: any) => {
const pathStr = typeof p === 'string' ? p : '';
if (pathStr.includes('bun.lockb')) throw new Error('Not found');
if (pathStr.includes('pnpm-lock.yaml')) throw new Error('Not found');
if (pathStr.includes('yarn.lock')) return undefined;
if (pathStr.includes('package.json')) return undefined;
return undefined;
});
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
expect(spawn).toHaveBeenCalledWith('yarn', ['dev'], expect.any(Object));
});
it('should detect pnpm as package manager with pnpm-lock.yaml', async () => {
vi.mocked(secureFs.access).mockImplementation(async (p: any) => {
const pathStr = typeof p === 'string' ? p : '';
if (pathStr.includes('bun.lockb')) throw new Error('Not found');
if (pathStr.includes('pnpm-lock.yaml')) return undefined;
if (pathStr.includes('package.json')) return undefined;
return undefined;
});
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
expect(spawn).toHaveBeenCalledWith('pnpm', ['run', 'dev'], expect.any(Object));
});
it('should detect bun as package manager with bun.lockb', async () => {
vi.mocked(secureFs.access).mockImplementation(async (p: any) => {
const pathStr = typeof p === 'string' ? p : '';
if (pathStr.includes('bun.lockb')) return undefined;
if (pathStr.includes('package.json')) return undefined;
return undefined;
});
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
expect(spawn).toHaveBeenCalledWith('bun', ['run', 'dev'], expect.any(Object));
});
it('should return existing server info if already running', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
// Start first server
const result1 = await service.startDevServer(testDir, testDir);
expect(result1.success).toBe(true);
// Try to start again - should return existing
const result2 = await service.startDevServer(testDir, testDir);
expect(result2.success).toBe(true);
expect(result2.result?.message).toContain('already running');
});
it('should start dev server successfully', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
const result = await service.startDevServer(testDir, testDir);
expect(result.success).toBe(true);
expect(result.result).toBeDefined();
expect(result.result?.port).toBeGreaterThanOrEqual(3001);
expect(result.result?.url).toContain('http://localhost:');
});
});
describe('stopDevServer', () => {
it('should return success if server not found', async () => {
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
const result = await service.stopDevServer('/nonexistent/path');
expect(result.success).toBe(true);
expect(result.result?.message).toContain('already stopped');
});
it('should stop a running server', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
// Start server
await service.startDevServer(testDir, testDir);
// Stop server
const result = await service.stopDevServer(testDir);
expect(result.success).toBe(true);
expect(mockProcess.kill).toHaveBeenCalledWith('SIGTERM');
});
});
describe('listDevServers', () => {
it('should return empty list when no servers running', async () => {
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
const result = service.listDevServers();
expect(result.success).toBe(true);
expect(result.result.servers).toEqual([]);
});
it('should list running servers', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
const result = service.listDevServers();
expect(result.success).toBe(true);
expect(result.result.servers.length).toBeGreaterThanOrEqual(1);
expect(result.result.servers[0].worktreePath).toBe(testDir);
});
});
describe('isRunning', () => {
it('should return false for non-running server', async () => {
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
expect(service.isRunning('/some/path')).toBe(false);
});
it('should return true for running server', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
expect(service.isRunning(testDir)).toBe(true);
});
});
describe('getServerInfo', () => {
it('should return undefined for non-running server', async () => {
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
expect(service.getServerInfo('/some/path')).toBeUndefined();
});
it('should return info for running server', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
const info = service.getServerInfo(testDir);
expect(info).toBeDefined();
expect(info?.worktreePath).toBe(testDir);
expect(info?.port).toBeGreaterThanOrEqual(3001);
});
});
describe('getAllocatedPorts', () => {
it('should return allocated ports', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
const ports = service.getAllocatedPorts();
expect(ports.length).toBeGreaterThanOrEqual(1);
expect(ports[0]).toBeGreaterThanOrEqual(3001);
});
});
describe('stopAll', () => {
it('should stop all running servers', async () => {
vi.mocked(secureFs.access).mockResolvedValue(undefined);
const mockProcess = createMockProcess();
vi.mocked(spawn).mockReturnValue(mockProcess as any);
const { getDevServerService } = await import('@/services/dev-server-service.js');
const service = getDevServerService();
await service.startDevServer(testDir, testDir);
await service.stopAll();
expect(service.listDevServers().result.servers).toHaveLength(0);
});
});
});
// Helper to create a mock child process
function createMockProcess() {
const mockProcess = new EventEmitter() as any;
mockProcess.stdout = new EventEmitter();
mockProcess.stderr = new EventEmitter();
mockProcess.kill = vi.fn();
mockProcess.killed = false;
// Don't exit immediately - let the test control the lifecycle
return mockProcess;
}