mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 06:42:03 +00:00
chore: Add Cursor CLI integration plan and phases
- Introduced a comprehensive integration plan for the Cursor CLI, including detailed phases for implementation. - Created initial markdown files for each phase, outlining objectives, tasks, and verification steps. - Established a global prompt template for starting new sessions with the Cursor CLI. - Added necessary types and configuration for Cursor models and their integration into the AutoMaker architecture. - Implemented routing logic to ensure proper model handling between Cursor and Claude providers. - Developed UI components for setup and settings management related to Cursor integration. - Included extensive testing and validation plans to ensure robust functionality across all scenarios.
This commit is contained in:
649
plan/cursor-cli-integration/phases/phase-10-testing.md
Normal file
649
plan/cursor-cli-integration/phases/phase-10-testing.md
Normal file
@@ -0,0 +1,649 @@
|
||||
# Phase 10: Testing & Validation
|
||||
|
||||
**Status:** `pending`
|
||||
**Dependencies:** All previous phases
|
||||
**Estimated Effort:** Medium (comprehensive testing)
|
||||
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Create comprehensive tests and perform validation to ensure the Cursor CLI integration works correctly across all scenarios.
|
||||
|
||||
---
|
||||
|
||||
## Tasks
|
||||
|
||||
### Task 10.1: Unit Tests - Cursor Provider
|
||||
|
||||
**Status:** `pending`
|
||||
|
||||
**File:** `apps/server/tests/unit/providers/cursor-provider.test.ts`
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { CursorProvider, CursorErrorCode } from '../../../src/providers/cursor-provider';
|
||||
import { execSync, spawn } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
|
||||
// Mock child_process
|
||||
vi.mock('child_process', () => ({
|
||||
execSync: vi.fn(),
|
||||
spawn: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock fs
|
||||
vi.mock('fs', () => ({
|
||||
existsSync: vi.fn(),
|
||||
readFileSync: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('CursorProvider', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getName', () => {
|
||||
it('should return "cursor"', () => {
|
||||
const provider = new CursorProvider();
|
||||
expect(provider.getName()).toBe('cursor');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isInstalled', () => {
|
||||
it('should return true when CLI is found in PATH', async () => {
|
||||
vi.mocked(execSync).mockReturnValue('/usr/local/bin/cursor-agent\n');
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
|
||||
const provider = new CursorProvider();
|
||||
const result = await provider.isInstalled();
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when CLI is not found', async () => {
|
||||
vi.mocked(execSync).mockImplementation(() => {
|
||||
throw new Error('not found');
|
||||
});
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
const provider = new CursorProvider();
|
||||
const result = await provider.isInstalled();
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkAuth', () => {
|
||||
it('should detect API key authentication', async () => {
|
||||
process.env.CURSOR_API_KEY = 'test-key';
|
||||
|
||||
const provider = new CursorProvider();
|
||||
const result = await provider.checkAuth();
|
||||
|
||||
expect(result.authenticated).toBe(true);
|
||||
expect(result.method).toBe('api_key');
|
||||
|
||||
delete process.env.CURSOR_API_KEY;
|
||||
});
|
||||
|
||||
it('should detect login authentication from credentials file', async () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ accessToken: 'token' }));
|
||||
|
||||
const provider = new CursorProvider();
|
||||
const result = await provider.checkAuth();
|
||||
|
||||
expect(result.authenticated).toBe(true);
|
||||
expect(result.method).toBe('login');
|
||||
});
|
||||
|
||||
it('should return not authenticated when no credentials', async () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
const provider = new CursorProvider();
|
||||
const result = await provider.checkAuth();
|
||||
|
||||
expect(result.authenticated).toBe(false);
|
||||
expect(result.method).toBe('none');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseStreamLine', () => {
|
||||
it('should parse valid JSON event', () => {
|
||||
const provider = new CursorProvider();
|
||||
const line = '{"type":"system","subtype":"init","session_id":"abc"}';
|
||||
|
||||
const result = (provider as any).parseStreamLine(line);
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'system',
|
||||
subtype: 'init',
|
||||
session_id: 'abc',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null for invalid JSON', () => {
|
||||
const provider = new CursorProvider();
|
||||
const result = (provider as any).parseStreamLine('not json');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for empty lines', () => {
|
||||
const provider = new CursorProvider();
|
||||
expect((provider as any).parseStreamLine('')).toBeNull();
|
||||
expect((provider as any).parseStreamLine(' ')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapError', () => {
|
||||
it('should map authentication errors', () => {
|
||||
const provider = new CursorProvider();
|
||||
|
||||
const error = (provider as any).mapError('Error: not authenticated', 1);
|
||||
|
||||
expect(error.code).toBe(CursorErrorCode.NOT_AUTHENTICATED);
|
||||
expect(error.recoverable).toBe(true);
|
||||
expect(error.suggestion).toBeDefined();
|
||||
});
|
||||
|
||||
it('should map rate limit errors', () => {
|
||||
const provider = new CursorProvider();
|
||||
|
||||
const error = (provider as any).mapError('Rate limit exceeded', 1);
|
||||
|
||||
expect(error.code).toBe(CursorErrorCode.RATE_LIMITED);
|
||||
expect(error.recoverable).toBe(true);
|
||||
});
|
||||
|
||||
it('should map network errors', () => {
|
||||
const provider = new CursorProvider();
|
||||
|
||||
const error = (provider as any).mapError('ECONNREFUSED', 1);
|
||||
|
||||
expect(error.code).toBe(CursorErrorCode.NETWORK_ERROR);
|
||||
expect(error.recoverable).toBe(true);
|
||||
});
|
||||
|
||||
it('should return unknown error for unrecognized messages', () => {
|
||||
const provider = new CursorProvider();
|
||||
|
||||
const error = (provider as any).mapError('Something weird happened', 1);
|
||||
|
||||
expect(error.code).toBe(CursorErrorCode.UNKNOWN);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAvailableModels', () => {
|
||||
it('should return all Cursor models', () => {
|
||||
const provider = new CursorProvider();
|
||||
const models = provider.getAvailableModels();
|
||||
|
||||
expect(models.length).toBeGreaterThan(0);
|
||||
expect(models.every((m) => m.provider === 'cursor')).toBe(true);
|
||||
expect(models.some((m) => m.id.includes('auto'))).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Task 10.2: Unit Tests - Provider Factory
|
||||
|
||||
**Status:** `pending`
|
||||
|
||||
**File:** `apps/server/tests/unit/providers/provider-factory.test.ts`
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ProviderFactory } from '../../../src/providers/provider-factory';
|
||||
import { ClaudeProvider } from '../../../src/providers/claude-provider';
|
||||
import { CursorProvider } from '../../../src/providers/cursor-provider';
|
||||
|
||||
describe('ProviderFactory', () => {
|
||||
describe('getProviderNameForModel', () => {
|
||||
it('should route cursor-prefixed models to cursor', () => {
|
||||
expect(ProviderFactory.getProviderNameForModel('cursor-auto')).toBe('cursor');
|
||||
expect(ProviderFactory.getProviderNameForModel('cursor-gpt-4o')).toBe('cursor');
|
||||
expect(ProviderFactory.getProviderNameForModel('cursor-claude-sonnet-4')).toBe('cursor');
|
||||
});
|
||||
|
||||
it('should route claude models to claude', () => {
|
||||
expect(ProviderFactory.getProviderNameForModel('claude-sonnet-4')).toBe('claude');
|
||||
expect(ProviderFactory.getProviderNameForModel('opus')).toBe('claude');
|
||||
expect(ProviderFactory.getProviderNameForModel('sonnet')).toBe('claude');
|
||||
expect(ProviderFactory.getProviderNameForModel('haiku')).toBe('claude');
|
||||
});
|
||||
|
||||
it('should default unknown models to claude', () => {
|
||||
expect(ProviderFactory.getProviderNameForModel('unknown-model')).toBe('claude');
|
||||
expect(ProviderFactory.getProviderNameForModel('random')).toBe('claude');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProviderForModel', () => {
|
||||
it('should return CursorProvider for cursor models', () => {
|
||||
const provider = ProviderFactory.getProviderForModel('cursor-auto');
|
||||
expect(provider).toBeInstanceOf(CursorProvider);
|
||||
expect(provider.getName()).toBe('cursor');
|
||||
});
|
||||
|
||||
it('should return ClaudeProvider for claude models', () => {
|
||||
const provider = ProviderFactory.getProviderForModel('sonnet');
|
||||
expect(provider).toBeInstanceOf(ClaudeProvider);
|
||||
expect(provider.getName()).toBe('claude');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllProviders', () => {
|
||||
it('should return both providers', () => {
|
||||
const providers = ProviderFactory.getAllProviders();
|
||||
const names = providers.map((p) => p.getName());
|
||||
|
||||
expect(names).toContain('claude');
|
||||
expect(names).toContain('cursor');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProviderByName', () => {
|
||||
it('should return correct provider by name', () => {
|
||||
expect(ProviderFactory.getProviderByName('cursor')?.getName()).toBe('cursor');
|
||||
expect(ProviderFactory.getProviderByName('claude')?.getName()).toBe('claude');
|
||||
expect(ProviderFactory.getProviderByName('unknown')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllAvailableModels', () => {
|
||||
it('should include models from all providers', () => {
|
||||
const models = ProviderFactory.getAllAvailableModels();
|
||||
|
||||
const cursorModels = models.filter((m) => m.provider === 'cursor');
|
||||
const claudeModels = models.filter((m) => m.provider === 'claude');
|
||||
|
||||
expect(cursorModels.length).toBeGreaterThan(0);
|
||||
expect(claudeModels.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Task 10.3: Unit Tests - Types
|
||||
|
||||
**Status:** `pending`
|
||||
|
||||
**File:** `libs/types/tests/cursor-types.test.ts`
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
CURSOR_MODEL_MAP,
|
||||
cursorModelHasThinking,
|
||||
getCursorModelLabel,
|
||||
getAllCursorModelIds,
|
||||
CursorModelId,
|
||||
} from '../src/cursor-models';
|
||||
import { profileHasThinking, getProfileModelString, AIProfile } from '../src/settings';
|
||||
|
||||
describe('Cursor Model Types', () => {
|
||||
describe('CURSOR_MODEL_MAP', () => {
|
||||
it('should have all required models', () => {
|
||||
const requiredModels: CursorModelId[] = [
|
||||
'auto',
|
||||
'claude-sonnet-4',
|
||||
'claude-sonnet-4-thinking',
|
||||
'gpt-4o',
|
||||
'gpt-4o-mini',
|
||||
];
|
||||
|
||||
for (const model of requiredModels) {
|
||||
expect(CURSOR_MODEL_MAP[model]).toBeDefined();
|
||||
expect(CURSOR_MODEL_MAP[model].id).toBe(model);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have valid tier values', () => {
|
||||
for (const config of Object.values(CURSOR_MODEL_MAP)) {
|
||||
expect(['free', 'pro']).toContain(config.tier);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('cursorModelHasThinking', () => {
|
||||
it('should return true for thinking models', () => {
|
||||
expect(cursorModelHasThinking('claude-sonnet-4-thinking')).toBe(true);
|
||||
expect(cursorModelHasThinking('o3-mini')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-thinking models', () => {
|
||||
expect(cursorModelHasThinking('auto')).toBe(false);
|
||||
expect(cursorModelHasThinking('gpt-4o')).toBe(false);
|
||||
expect(cursorModelHasThinking('claude-sonnet-4')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCursorModelLabel', () => {
|
||||
it('should return correct labels', () => {
|
||||
expect(getCursorModelLabel('auto')).toBe('Auto (Recommended)');
|
||||
expect(getCursorModelLabel('gpt-4o')).toBe('GPT-4o');
|
||||
});
|
||||
|
||||
it('should return model ID for unknown models', () => {
|
||||
expect(getCursorModelLabel('unknown' as CursorModelId)).toBe('unknown');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Profile Helpers', () => {
|
||||
describe('profileHasThinking', () => {
|
||||
it('should detect Claude thinking levels', () => {
|
||||
const profile: AIProfile = {
|
||||
id: '1',
|
||||
name: 'Test',
|
||||
description: '',
|
||||
provider: 'claude',
|
||||
model: 'sonnet',
|
||||
thinkingLevel: 'high',
|
||||
isBuiltIn: false,
|
||||
};
|
||||
expect(profileHasThinking(profile)).toBe(true);
|
||||
|
||||
profile.thinkingLevel = 'none';
|
||||
expect(profileHasThinking(profile)).toBe(false);
|
||||
});
|
||||
|
||||
it('should detect Cursor thinking models', () => {
|
||||
const profile: AIProfile = {
|
||||
id: '1',
|
||||
name: 'Test',
|
||||
description: '',
|
||||
provider: 'cursor',
|
||||
cursorModel: 'claude-sonnet-4-thinking',
|
||||
isBuiltIn: false,
|
||||
};
|
||||
expect(profileHasThinking(profile)).toBe(true);
|
||||
|
||||
profile.cursorModel = 'gpt-4o';
|
||||
expect(profileHasThinking(profile)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProfileModelString', () => {
|
||||
it('should format Cursor models correctly', () => {
|
||||
const profile: AIProfile = {
|
||||
id: '1',
|
||||
name: 'Test',
|
||||
description: '',
|
||||
provider: 'cursor',
|
||||
cursorModel: 'gpt-4o',
|
||||
isBuiltIn: false,
|
||||
};
|
||||
expect(getProfileModelString(profile)).toBe('cursor-gpt-4o');
|
||||
});
|
||||
|
||||
it('should format Claude models correctly', () => {
|
||||
const profile: AIProfile = {
|
||||
id: '1',
|
||||
name: 'Test',
|
||||
description: '',
|
||||
provider: 'claude',
|
||||
model: 'sonnet',
|
||||
isBuiltIn: false,
|
||||
};
|
||||
expect(getProfileModelString(profile)).toBe('sonnet');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Task 10.4: Integration Tests
|
||||
|
||||
**Status:** `pending`
|
||||
|
||||
**File:** `apps/server/tests/integration/cursor-integration.test.ts`
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { CursorProvider } from '../../src/providers/cursor-provider';
|
||||
import { ProviderFactory } from '../../src/providers/provider-factory';
|
||||
|
||||
describe('Cursor Integration (requires cursor-agent)', () => {
|
||||
let provider: CursorProvider;
|
||||
let isInstalled: boolean;
|
||||
|
||||
beforeAll(async () => {
|
||||
provider = new CursorProvider();
|
||||
isInstalled = await provider.isInstalled();
|
||||
});
|
||||
|
||||
describe('when cursor-agent is installed', () => {
|
||||
it.skipIf(!isInstalled)('should get version', async () => {
|
||||
const version = await provider.getVersion();
|
||||
expect(version).toBeTruthy();
|
||||
expect(typeof version).toBe('string');
|
||||
});
|
||||
|
||||
it.skipIf(!isInstalled)('should check auth status', async () => {
|
||||
const auth = await provider.checkAuth();
|
||||
expect(auth).toHaveProperty('authenticated');
|
||||
expect(auth).toHaveProperty('method');
|
||||
});
|
||||
|
||||
it.skipIf(!isInstalled)('should detect installation', async () => {
|
||||
const status = await provider.detectInstallation();
|
||||
expect(status.installed).toBe(true);
|
||||
expect(status.path).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when cursor-agent is not installed', () => {
|
||||
it.skipIf(isInstalled)('should report not installed', async () => {
|
||||
const status = await provider.detectInstallation();
|
||||
expect(status.installed).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Task 10.5: E2E Tests
|
||||
|
||||
**Status:** `pending`
|
||||
|
||||
**File:** `apps/ui/tests/e2e/cursor-setup.spec.ts`
|
||||
|
||||
```typescript
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Cursor Setup Wizard', () => {
|
||||
test('should show Cursor setup step', async ({ page }) => {
|
||||
// Navigate to setup (fresh install)
|
||||
await page.goto('/setup');
|
||||
|
||||
// Wait for Cursor step to appear
|
||||
await expect(page.getByText('Cursor CLI Setup')).toBeVisible();
|
||||
await expect(page.getByText('Optional')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should allow skipping Cursor setup', async ({ page }) => {
|
||||
await page.goto('/setup');
|
||||
|
||||
// Find and click skip button
|
||||
await page.getByRole('button', { name: 'Skip for now' }).click();
|
||||
|
||||
// Should proceed to next step
|
||||
await expect(page.getByText('Cursor CLI Setup')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should show installation instructions when not installed', async ({ page }) => {
|
||||
await page.goto('/setup');
|
||||
|
||||
// Check for install command
|
||||
await expect(page.getByText('curl https://cursor.com/install')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Cursor Settings', () => {
|
||||
test('should show Cursor tab in settings', async ({ page }) => {
|
||||
await page.goto('/settings/providers');
|
||||
|
||||
// Should have tabs for both providers
|
||||
await expect(page.getByRole('tab', { name: 'Claude' })).toBeVisible();
|
||||
await expect(page.getByRole('tab', { name: 'Cursor' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should switch between provider tabs', async ({ page }) => {
|
||||
await page.goto('/settings/providers');
|
||||
|
||||
// Click Cursor tab
|
||||
await page.getByRole('tab', { name: 'Cursor' }).click();
|
||||
|
||||
// Should show Cursor settings
|
||||
await expect(page.getByText('Cursor CLI Status')).toBeVisible();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Task 10.6: Manual Testing Checklist
|
||||
|
||||
**Status:** `pending`
|
||||
|
||||
Create a manual testing checklist:
|
||||
|
||||
```markdown
|
||||
## Manual Testing Checklist
|
||||
|
||||
### Setup Flow
|
||||
|
||||
- [ ] Fresh install shows Cursor step
|
||||
- [ ] Can skip Cursor setup
|
||||
- [ ] Installation status is accurate
|
||||
- [ ] Login flow works (copy command, poll for auth)
|
||||
- [ ] Refresh button updates status
|
||||
|
||||
### Settings
|
||||
|
||||
- [ ] Provider tabs work
|
||||
- [ ] Cursor status shows correctly
|
||||
- [ ] Model selection works
|
||||
- [ ] Default model saves
|
||||
- [ ] Enabled models save
|
||||
|
||||
### Profiles
|
||||
|
||||
- [ ] Can create Cursor profile
|
||||
- [ ] Provider switch resets options
|
||||
- [ ] Cursor models show thinking badge
|
||||
- [ ] Built-in Cursor profiles appear
|
||||
- [ ] Profile cards show provider info
|
||||
|
||||
### Execution
|
||||
|
||||
- [ ] Tasks with Cursor models execute
|
||||
- [ ] Streaming works correctly
|
||||
- [ ] Tool calls are displayed
|
||||
- [ ] Errors show suggestions
|
||||
- [ ] Can abort Cursor tasks
|
||||
|
||||
### Log Viewer
|
||||
|
||||
- [ ] Cursor events parsed correctly
|
||||
- [ ] Tool calls categorized
|
||||
- [ ] File paths highlighted
|
||||
- [ ] Provider badge shown
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- [ ] Switch provider mid-session
|
||||
- [ ] Cursor not installed handling
|
||||
- [ ] Network errors handled
|
||||
- [ ] Rate limiting handled
|
||||
- [ ] Auth expired handling
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
### Test 1: Run All Unit Tests
|
||||
|
||||
```bash
|
||||
pnpm test:unit
|
||||
```
|
||||
|
||||
All tests should pass.
|
||||
|
||||
### Test 2: Run Integration Tests
|
||||
|
||||
```bash
|
||||
pnpm test:integration
|
||||
```
|
||||
|
||||
Tests requiring cursor-agent will be skipped if not installed.
|
||||
|
||||
### Test 3: Run E2E Tests
|
||||
|
||||
```bash
|
||||
pnpm test:e2e
|
||||
```
|
||||
|
||||
Browser tests should pass.
|
||||
|
||||
### Test 4: Type Check
|
||||
|
||||
```bash
|
||||
pnpm typecheck
|
||||
```
|
||||
|
||||
No TypeScript errors.
|
||||
|
||||
### Test 5: Lint Check
|
||||
|
||||
```bash
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
No linting errors.
|
||||
|
||||
### Test 6: Build
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
Build should succeed without errors.
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
Before marking this phase complete:
|
||||
|
||||
- [ ] Unit tests pass (cursor-provider)
|
||||
- [ ] Unit tests pass (provider-factory)
|
||||
- [ ] Unit tests pass (types)
|
||||
- [ ] Integration tests pass (or skip if not installed)
|
||||
- [ ] E2E tests pass
|
||||
- [ ] Manual testing checklist completed
|
||||
- [ ] No TypeScript errors
|
||||
- [ ] No linting errors
|
||||
- [ ] Build succeeds
|
||||
- [ ] Documentation updated
|
||||
|
||||
---
|
||||
|
||||
## Files Changed
|
||||
|
||||
| File | Action | Description |
|
||||
| ----------------------------------------------------------- | ------ | ----------------- |
|
||||
| `apps/server/tests/unit/providers/cursor-provider.test.ts` | Create | Provider tests |
|
||||
| `apps/server/tests/unit/providers/provider-factory.test.ts` | Create | Factory tests |
|
||||
| `libs/types/tests/cursor-types.test.ts` | Create | Type tests |
|
||||
| `apps/server/tests/integration/cursor-integration.test.ts` | Create | Integration tests |
|
||||
| `apps/ui/tests/e2e/cursor-setup.spec.ts` | Create | E2E tests |
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Integration tests may be skipped if cursor-agent is not installed
|
||||
- E2E tests should work regardless of cursor-agent installation
|
||||
- Manual testing should cover both installed and not-installed scenarios
|
||||
Reference in New Issue
Block a user