mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 14:21:20 +00:00
151 lines
4.6 KiB
TypeScript
151 lines
4.6 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
|
|
import {
|
|
executeCursorProfile,
|
|
generateCursorEnv,
|
|
resolveCursorImageAnalysisEnv,
|
|
} from '../../../src/cursor/cursor-profile-executor';
|
|
import { saveCredentials } from '../../../src/cursor/cursor-auth';
|
|
import type { CursorConfig } from '../../../src/config/unified-config-types';
|
|
|
|
const BASE_CONFIG: CursorConfig = {
|
|
enabled: true,
|
|
port: 20129,
|
|
auto_start: false,
|
|
ghost_mode: true,
|
|
model: 'gpt-5.3-codex',
|
|
};
|
|
|
|
describe('cursor-profile-executor', () => {
|
|
let originalCcsHome: string | undefined;
|
|
let tempDir: string;
|
|
|
|
beforeEach(() => {
|
|
originalCcsHome = process.env.CCS_HOME;
|
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-cursor-profile-executor-'));
|
|
process.env.CCS_HOME = tempDir;
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (originalCcsHome !== undefined) {
|
|
process.env.CCS_HOME = originalCcsHome;
|
|
} else {
|
|
delete process.env.CCS_HOME;
|
|
}
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it('builds Cursor env for Claude runtime', () => {
|
|
const env = generateCursorEnv(
|
|
{
|
|
...BASE_CONFIG,
|
|
opus_model: 'cursor-opus',
|
|
sonnet_model: 'cursor-sonnet',
|
|
haiku_model: 'cursor-haiku',
|
|
},
|
|
'test-token',
|
|
'/tmp/claude-config'
|
|
);
|
|
|
|
expect(env.ANTHROPIC_BASE_URL).toBe('http://127.0.0.1:20129');
|
|
expect(env.ANTHROPIC_AUTH_TOKEN).toBe('test-token');
|
|
expect(env.ANTHROPIC_MODEL).toBe('gpt-5.3-codex');
|
|
expect(env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('cursor-opus');
|
|
expect(env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('cursor-sonnet');
|
|
expect(env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('cursor-haiku');
|
|
expect(env.CLAUDE_CONFIG_DIR).toBe('/tmp/claude-config');
|
|
});
|
|
|
|
it('skips image-analysis provider routing for cursor unless explicitly mapped', async () => {
|
|
const { env, warning } = await resolveCursorImageAnalysisEnv();
|
|
|
|
expect(env.CCS_CURRENT_PROVIDER).toBe('');
|
|
expect(env.CCS_IMAGE_ANALYSIS_SKIP).toBe('1');
|
|
expect(warning).toBeNull();
|
|
});
|
|
|
|
it('starts local CLIProxy on the configured lifecycle port for cursor image analysis', async () => {
|
|
let ensuredPort: number | undefined;
|
|
|
|
const { env, warning } = await resolveCursorImageAnalysisEnv(false, {
|
|
getImageAnalysisHookEnv: () => ({
|
|
CCS_CURRENT_PROVIDER: 'ghcp',
|
|
CCS_IMAGE_ANALYSIS_SKIP: '0',
|
|
}),
|
|
resolveImageAnalysisRuntimeStatus: async () => ({
|
|
enabled: true,
|
|
supported: true,
|
|
status: 'active',
|
|
backendId: 'ghcp',
|
|
backendDisplayName: 'GitHub Copilot (OAuth)',
|
|
model: 'claude-haiku-4.5',
|
|
resolutionSource: 'cursor-alias',
|
|
reason: null,
|
|
shouldPersistHook: true,
|
|
persistencePath: 'cursor.settings.json',
|
|
runtimePath: '/api/provider/ghcp',
|
|
usesCurrentTarget: true,
|
|
usesCurrentAuthToken: true,
|
|
hookInstalled: true,
|
|
sharedHookInstalled: true,
|
|
authReadiness: 'ready',
|
|
authProvider: 'ghcp',
|
|
authDisplayName: 'GitHub Copilot (OAuth)',
|
|
authReason: null,
|
|
proxyReadiness: 'stopped',
|
|
proxyReason:
|
|
'Local CLIProxy service is idle. CCS will start it automatically when image analysis is needed.',
|
|
effectiveRuntimeMode: 'cliproxy-image-analysis',
|
|
effectiveRuntimeReason: null,
|
|
}),
|
|
ensureCliproxyService: async (port: number) => {
|
|
ensuredPort = port;
|
|
return {
|
|
started: true,
|
|
alreadyRunning: false,
|
|
port,
|
|
};
|
|
},
|
|
resolveLifecyclePort: () => 9321,
|
|
});
|
|
|
|
expect(ensuredPort).toBe(9321);
|
|
expect(env.CCS_CURRENT_PROVIDER).toBe('ghcp');
|
|
expect(env.CCS_IMAGE_ANALYSIS_SKIP).toBe('0');
|
|
expect(warning).toBeNull();
|
|
});
|
|
|
|
it('fails fast when Cursor integration is disabled', async () => {
|
|
const exitCode = await executeCursorProfile({ ...BASE_CONFIG, enabled: false }, []);
|
|
expect(exitCode).toBe(1);
|
|
});
|
|
|
|
it('fails when credentials are missing', async () => {
|
|
const exitCode = await executeCursorProfile(BASE_CONFIG, []);
|
|
expect(exitCode).toBe(1);
|
|
});
|
|
|
|
it('fails with actionable guidance when daemon is down and auto_start is false', async () => {
|
|
saveCredentials({
|
|
accessToken: 'a'.repeat(60),
|
|
machineId: '1234567890abcdef1234567890abcdef',
|
|
authMethod: 'manual',
|
|
importedAt: new Date().toISOString(),
|
|
});
|
|
|
|
const exitCode = await executeCursorProfile(
|
|
{
|
|
...BASE_CONFIG,
|
|
port: 29991,
|
|
auto_start: false,
|
|
},
|
|
[]
|
|
);
|
|
|
|
expect(exitCode).toBe(1);
|
|
});
|
|
});
|