Files
ccs/tests/unit/cursor/cursor-daemon.test.ts
T
Tam Nhu Tran 0fe195cdc3 fix(test): add kimi to provider tests, remove merge conflict marker
- Add kimi to CLIPROXY_PROVIDER_IDS expected array
- Add kimi to device_code providers expected array
- Remove leftover >>>>>>> origin/dev merge conflict marker in cursor-daemon test
- Fix prettier formatting in image-analysis-check.ts
2026-02-17 17:31:27 +07:00

404 lines
12 KiB
TypeScript

/**
* Unit tests for Cursor daemon module
*/
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as http from 'http';
import { spawn } from 'child_process';
import {
getPidFromFile,
writePidToFile,
removePidFile,
isDaemonRunning,
getDaemonStatus,
stopDaemon,
startDaemon,
} from '../../../src/cursor/cursor-daemon';
import { getCcsDir } from '../../../src/utils/config-manager';
import { handleCursorCommand } from '../../../src/commands/cursor-command';
import { loadCredentials, saveCredentials } from '../../../src/cursor/cursor-auth';
// Test isolation
let originalCcsHome: string | undefined;
let tempDir: string;
beforeEach(() => {
originalCcsHome = process.env.CCS_HOME;
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-daemon-test-'));
process.env.CCS_HOME = tempDir;
});
afterEach(() => {
if (originalCcsHome !== undefined) {
process.env.CCS_HOME = originalCcsHome;
} else {
delete process.env.CCS_HOME;
}
// Cleanup temp directory
try {
fs.rmSync(tempDir, { recursive: true, force: true });
} catch {
// Ignore cleanup errors
}
});
// Use getCcsDir() for consistent path resolution with production code
const getTestCursorDir = () => path.join(getCcsDir(), 'cursor');
describe('getPidFromFile', () => {
it('returns null when no PID file exists', () => {
expect(getPidFromFile()).toBeNull();
});
it('returns PID when valid PID file exists', () => {
const dir = getTestCursorDir();
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, 'daemon.pid'), '12345');
expect(getPidFromFile()).toBe(12345);
});
it('returns null when PID file contains invalid content', () => {
const dir = getTestCursorDir();
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, 'daemon.pid'), 'not-a-number');
expect(getPidFromFile()).toBeNull();
});
it('trims whitespace from PID file content', () => {
const dir = getTestCursorDir();
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, 'daemon.pid'), ' 42 \n');
expect(getPidFromFile()).toBe(42);
});
});
describe('writePidToFile', () => {
it('creates PID file with correct content', () => {
writePidToFile(12345);
const pidFile = path.join(getTestCursorDir(), 'daemon.pid');
expect(fs.existsSync(pidFile)).toBe(true);
expect(fs.readFileSync(pidFile, 'utf8')).toBe('12345');
});
it('creates cursor directory if it does not exist', () => {
const dir = getTestCursorDir();
expect(fs.existsSync(dir)).toBe(false);
writePidToFile(999);
expect(fs.existsSync(dir)).toBe(true);
});
it('overwrites existing PID file', () => {
writePidToFile(111);
writePidToFile(222);
const pidFile = path.join(getTestCursorDir(), 'daemon.pid');
expect(fs.readFileSync(pidFile, 'utf8')).toBe('222');
});
});
describe('removePidFile', () => {
it('removes existing PID file', () => {
writePidToFile(12345);
const pidFile = path.join(getTestCursorDir(), 'daemon.pid');
expect(fs.existsSync(pidFile)).toBe(true);
removePidFile();
expect(fs.existsSync(pidFile)).toBe(false);
});
it('does not throw when PID file does not exist', () => {
expect(() => removePidFile()).not.toThrow();
});
});
describe('startDaemon', () => {
it('rejects invalid port (0)', async () => {
const result = await startDaemon({ port: 0 });
expect(result.success).toBe(false);
expect(result.error).toContain('Invalid port');
});
it('rejects invalid port (65536)', async () => {
const result = await startDaemon({ port: 65536 });
expect(result.success).toBe(false);
expect(result.error).toContain('Invalid port');
});
it('rejects non-integer port', async () => {
const result = await startDaemon({ port: 3.14 });
expect(result.success).toBe(false);
expect(result.error).toContain('Invalid port');
});
it('starts and stops daemon successfully', async () => {
const port = 10000 + Math.floor(Math.random() * 50000);
const result = await startDaemon({ port, ghost_mode: true });
expect(result.success).toBe(true);
expect(result.pid).toBeDefined();
// Verify health
const running = await isDaemonRunning(port);
expect(running).toBe(true);
// Verify models endpoint exists and is OpenAI-compatible list shape
const modelsResponse = await fetch(`http://127.0.0.1:${port}/v1/models`);
expect(modelsResponse.status).toBe(200);
const modelsJson = (await modelsResponse.json()) as { object?: string; data?: unknown[] };
expect(modelsJson.object).toBe('list');
expect(Array.isArray(modelsJson.data)).toBe(true);
// Verify chat endpoint exists (requires auth, should not be 404)
const chatResponse = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-4.1',
messages: [{ role: 'user', content: 'hello' }],
}),
});
expect(chatResponse.status).toBe(401);
// Stop
const stopResult = await stopDaemon();
expect(stopResult.success).toBe(true);
// Verify stopped
const stillRunning = await isDaemonRunning(port);
expect(stillRunning).toBe(false);
},
35000
);
it('returns 404 for unknown routes', async () => {
const port = 10000 + Math.floor(Math.random() * 50000);
const result = await startDaemon({ port, ghost_mode: true });
expect(result.success).toBe(true);
try {
const response = await fetch(`http://127.0.0.1:${port}/unknown`);
expect(response.status).toBe(404);
} finally {
await stopDaemon();
}
});
it('returns 401 when credentials are expired', async () => {
const port = 10000 + Math.floor(Math.random() * 50000);
const expiredAt = new Date(Date.now() - 26 * 60 * 60 * 1000).toISOString();
saveCredentials({
accessToken: 'a'.repeat(60),
machineId: '1234567890abcdef1234567890abcdef',
authMethod: 'manual',
importedAt: expiredAt,
});
const result = await startDaemon({ port, ghost_mode: true });
expect(result.success).toBe(true);
try {
const response = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-4.1',
messages: [{ role: 'user', content: 'hello' }],
}),
});
expect(response.status).toBe(401);
const body = (await response.json()) as { error?: { message?: string } };
expect(body.error?.message).toContain('expired');
} finally {
await stopDaemon();
}
});
it('validates invalid JSON, invalid message schema, and oversized body', async () => {
const port = 10000 + Math.floor(Math.random() * 50000);
const result = await startDaemon({ port, ghost_mode: true });
expect(result.success).toBe(true);
try {
const invalidJson = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{invalid-json',
});
expect(invalidJson.status).toBe(400);
const invalidSchema = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-4.1',
messages: { role: 'user', content: 'hello' },
}),
});
expect(invalidSchema.status).toBe(400);
const oversized = await fetch(`http://127.0.0.1:${port}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-4.1',
messages: [
{
role: 'user',
content: 'x'.repeat(10 * 1024 * 1024 + 1024),
},
],
}),
});
expect(oversized.status).toBe(413);
} finally {
await stopDaemon();
}
});
});
describe('isDaemonRunning', () => {
it('returns false when no daemon is running on port', async () => {
// Use a port that should not have anything running
const result = await isDaemonRunning(19999);
expect(result).toBe(false);
});
it('returns false when /health is 200 but service is not cursor-daemon', async () => {
const server = http.createServer((req, res) => {
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true, service: 'not-cursor-daemon' }));
return;
}
res.writeHead(404);
res.end();
});
await new Promise<void>((resolve) => {
server.listen(0, '127.0.0.1', () => resolve());
});
try {
const address = server.address();
if (!address || typeof address === 'string') {
throw new Error('Unable to resolve test server port');
}
const result = await isDaemonRunning(address.port);
expect(result).toBe(false);
} finally {
await new Promise<void>((resolve) => {
server.close(() => resolve());
});
}
});
});
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(getTestCursorDir(), 'daemon.pid');
expect(fs.existsSync(pidFile)).toBe(false);
});
it('does not terminate unrelated process from stale PID file', async () => {
const unrelatedProcess = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000);'], {
detached: true,
stdio: 'ignore',
});
unrelatedProcess.unref();
const unrelatedPid = unrelatedProcess.pid;
expect(unrelatedPid).toBeDefined();
if (!unrelatedPid) {
throw new Error('Failed to spawn unrelated process');
}
writePidToFile(unrelatedPid);
try {
const result = await stopDaemon();
expect(result.success).toBe(true);
// Unrelated process should still be alive.
expect(() => process.kill(unrelatedPid, 0)).not.toThrow();
} finally {
try {
process.kill(unrelatedPid, 'SIGTERM');
} catch {
// Process already exited.
}
}
});
});
describe('handleCursorCommand', () => {
it('returns exit code 1 for unknown subcommand', async () => {
const exitCode = await handleCursorCommand(['nonexistent']);
expect(exitCode).toBe(1);
});
it('supports manual auth import from CLI flags', async () => {
const token = 'a'.repeat(60);
const machineId = '1234567890abcdef1234567890abcdef';
const exitCode = await handleCursorCommand([
'auth',
'--manual',
'--token',
token,
'--machine-id',
machineId,
]);
expect(exitCode).toBe(0);
const credentials = loadCredentials();
expect(credentials).not.toBeNull();
expect(credentials?.authMethod).toBe('manual');
expect(credentials?.machineId).toBe(machineId);
});
});