mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
fix(cursor): add resolve guard, port validation, and daemon tests
- Add double-resolve guard in startDaemon with safeResolve wrapper - Add port validation (1-65535) before Node.js script interpolation - Fix misleading comment in stopDaemon (no PID file handling) - Add getDaemonStatus test for no daemon running case - Add stopDaemon tests for graceful non-existent PID handling
This commit is contained in:
+24
-13
@@ -140,10 +140,25 @@ export async function startDaemon(
|
||||
return { success: true, pid: getPidFromFile() ?? undefined };
|
||||
}
|
||||
|
||||
// Validate port before interpolation (prevents injection)
|
||||
if (!Number.isInteger(config.port) || config.port < 1 || config.port > 65535) {
|
||||
return { success: false, error: `Invalid port: ${config.port}` };
|
||||
}
|
||||
|
||||
// For now, create a simple structure that will be filled in later
|
||||
// The actual server implementation will be added in a separate task
|
||||
return new Promise((resolve) => {
|
||||
let proc: ChildProcess;
|
||||
let resolved = false;
|
||||
|
||||
const safeResolve = (result: { success: boolean; pid?: number; error?: string }) => {
|
||||
if (resolved) return;
|
||||
resolved = true;
|
||||
if (checkInterval) clearInterval(checkInterval);
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
let checkInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
try {
|
||||
// Spawn a placeholder Node.js process
|
||||
@@ -184,15 +199,13 @@ export async function startDaemon(
|
||||
// Wait for daemon to be ready (poll for up to 30 seconds)
|
||||
let attempts = 0;
|
||||
const maxAttempts = 30;
|
||||
const checkInterval = setInterval(async () => {
|
||||
checkInterval = setInterval(async () => {
|
||||
attempts++;
|
||||
|
||||
if (await isDaemonRunning(config.port)) {
|
||||
clearInterval(checkInterval);
|
||||
resolve({ success: true, pid: proc.pid });
|
||||
safeResolve({ success: true, pid: proc.pid });
|
||||
} else if (attempts >= maxAttempts) {
|
||||
clearInterval(checkInterval);
|
||||
resolve({
|
||||
safeResolve({
|
||||
success: false,
|
||||
error: 'Daemon did not start within 30 seconds',
|
||||
});
|
||||
@@ -200,34 +213,32 @@ export async function startDaemon(
|
||||
}, 1000);
|
||||
|
||||
proc.on('error', (err) => {
|
||||
clearInterval(checkInterval);
|
||||
resolve({
|
||||
safeResolve({
|
||||
success: false,
|
||||
error: `Failed to start daemon: ${err.message}`,
|
||||
});
|
||||
});
|
||||
|
||||
proc.on('exit', (code, signal) => {
|
||||
clearInterval(checkInterval);
|
||||
if (code === null) {
|
||||
resolve({
|
||||
safeResolve({
|
||||
success: false,
|
||||
error: `Daemon process was killed by signal ${signal}`,
|
||||
});
|
||||
} else if (code === 0) {
|
||||
resolve({
|
||||
safeResolve({
|
||||
success: false,
|
||||
error: 'Daemon process exited unexpectedly with code 0',
|
||||
});
|
||||
} else if (code !== null) {
|
||||
resolve({
|
||||
safeResolve({
|
||||
success: false,
|
||||
error: `Daemon process exited with code ${code}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
resolve({
|
||||
safeResolve({
|
||||
success: false,
|
||||
error: `Failed to spawn daemon: ${(err as Error).message}`,
|
||||
});
|
||||
@@ -242,7 +253,7 @@ export async function stopDaemon(): Promise<{ success: boolean; error?: string }
|
||||
const pid = getPidFromFile();
|
||||
|
||||
if (!pid) {
|
||||
// No PID file, try to find by port
|
||||
// No PID file — daemon is not running or was already stopped
|
||||
removePidFile();
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
writePidToFile,
|
||||
removePidFile,
|
||||
isDaemonRunning,
|
||||
getDaemonStatus,
|
||||
stopDaemon,
|
||||
} from '../../../src/cursor/cursor-daemon';
|
||||
|
||||
// Test isolation
|
||||
@@ -121,3 +123,40 @@ describe('isDaemonRunning', () => {
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDaemonStatus', () => {
|
||||
it('returns status with running=false when no daemon running', async () => {
|
||||
const status = await getDaemonStatus(19999);
|
||||
expect(status.running).toBe(false);
|
||||
expect(status.port).toBe(19999);
|
||||
expect(status.pid).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns status with pid when PID file exists but daemon not running', async () => {
|
||||
writePidToFile(99999);
|
||||
const status = await getDaemonStatus(19999);
|
||||
expect(status.running).toBe(false);
|
||||
expect(status.port).toBe(19999);
|
||||
expect(status.pid).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('stopDaemon', () => {
|
||||
it('returns success when no PID file exists', async () => {
|
||||
const result = await stopDaemon();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns success when PID refers to non-existent process', async () => {
|
||||
// Write a PID that doesn't exist
|
||||
writePidToFile(999999);
|
||||
const result = await stopDaemon();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.error).toBeUndefined();
|
||||
|
||||
// PID file should be removed
|
||||
const pidFile = path.join(ccsDir(), 'cursor', 'daemon.pid');
|
||||
expect(fs.existsSync(pidFile)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user