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:
Tam Nhu Tran
2026-02-12 04:27:35 +07:00
parent afb5e746b3
commit 7d4e6d6b65
2 changed files with 63 additions and 13 deletions
+24 -13
View File
@@ -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 };
}
+39
View File
@@ -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);
});
});