mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Add snapshot tests for system prompts to ensure modifications are intentional
This commit is contained in:
2054
src/core/prompts/__tests__/__snapshots__/system.test.ts.snap
Normal file
2054
src/core/prompts/__tests__/__snapshots__/system.test.ts.snap
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,112 +1,203 @@
|
|||||||
|
import { SYSTEM_PROMPT, addCustomInstructions } from '../system'
|
||||||
|
import { McpHub } from '../../../services/mcp/McpHub'
|
||||||
|
import { McpServer } from '../../../shared/mcp'
|
||||||
|
import { ClineProvider } from '../../../core/webview/ClineProvider'
|
||||||
|
import { SearchReplaceDiffStrategy } from '../../../core/diff/strategies/search-replace'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import path from 'path'
|
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
import { addCustomInstructions } from '../system'
|
// Import path utils to get access to toPosix string extension
|
||||||
|
import '../../../utils/path'
|
||||||
|
|
||||||
// Mock external dependencies
|
// Mock environment-specific values for consistent tests
|
||||||
jest.mock('os-name', () => () => 'macOS')
|
|
||||||
jest.mock('default-shell', () => '/bin/zsh')
|
|
||||||
jest.mock('os', () => ({
|
jest.mock('os', () => ({
|
||||||
homedir: () => '/Users/test',
|
...jest.requireActual('os'),
|
||||||
...jest.requireActual('os')
|
homedir: () => '/home/user'
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('system.ts', () => {
|
jest.mock('default-shell', () => '/bin/bash')
|
||||||
let tempDir: string
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
jest.mock('os-name', () => () => 'Linux')
|
||||||
// Create a temporary directory for test files
|
|
||||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cline-test-'))
|
// Mock fs.readFile to return empty mcpServers config and mock .clinerules
|
||||||
|
jest.mock('fs/promises', () => ({
|
||||||
|
...jest.requireActual('fs/promises'),
|
||||||
|
readFile: jest.fn().mockImplementation(async (path: string) => {
|
||||||
|
if (path.endsWith('mcpSettings.json')) {
|
||||||
|
return '{"mcpServers": {}}'
|
||||||
|
}
|
||||||
|
if (path.endsWith('.clinerules')) {
|
||||||
|
return '# Test Rules\n1. First rule\n2. Second rule'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}),
|
||||||
|
writeFile: jest.fn().mockResolvedValue(undefined)
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Create a minimal mock of ClineProvider
|
||||||
|
const mockProvider = {
|
||||||
|
ensureMcpServersDirectoryExists: async () => '/mock/mcp/path',
|
||||||
|
ensureSettingsDirectoryExists: async () => '/mock/settings/path',
|
||||||
|
postMessageToWebview: async () => {},
|
||||||
|
context: {
|
||||||
|
extension: {
|
||||||
|
packageJSON: {
|
||||||
|
version: '1.0.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as unknown as ClineProvider
|
||||||
|
|
||||||
|
// Instead of extending McpHub, create a mock that implements just what we need
|
||||||
|
const createMockMcpHub = (): McpHub => ({
|
||||||
|
getServers: () => [],
|
||||||
|
getMcpServersPath: async () => '/mock/mcp/path',
|
||||||
|
getMcpSettingsFilePath: async () => '/mock/settings/path',
|
||||||
|
dispose: async () => {},
|
||||||
|
// Add other required public methods with no-op implementations
|
||||||
|
restartConnection: async () => {},
|
||||||
|
readResource: async () => ({ contents: [] }),
|
||||||
|
callTool: async () => ({ content: [] }),
|
||||||
|
toggleServerDisabled: async () => {},
|
||||||
|
toggleToolAlwaysAllow: async () => {},
|
||||||
|
isConnecting: false,
|
||||||
|
connections: []
|
||||||
|
} as unknown as McpHub)
|
||||||
|
|
||||||
|
describe('SYSTEM_PROMPT', () => {
|
||||||
|
let mockMcpHub: McpHub
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
// Clean up temporary directory after each test
|
// Clean up any McpHub instances
|
||||||
await fs.rm(tempDir, { recursive: true, force: true })
|
if (mockMcpHub) {
|
||||||
|
await mockMcpHub.dispose()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should maintain consistent system prompt', async () => {
|
||||||
|
const prompt = await SYSTEM_PROMPT(
|
||||||
|
'/test/path',
|
||||||
|
false, // supportsComputerUse
|
||||||
|
undefined, // mcpHub
|
||||||
|
undefined, // diffStrategy
|
||||||
|
undefined // browserViewportSize
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prompt).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should include browser actions when supportsComputerUse is true', async () => {
|
||||||
|
const prompt = await SYSTEM_PROMPT(
|
||||||
|
'/test/path',
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
'1280x800'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prompt).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should include MCP server info when mcpHub is provided', async () => {
|
||||||
|
mockMcpHub = createMockMcpHub()
|
||||||
|
|
||||||
|
const prompt = await SYSTEM_PROMPT(
|
||||||
|
'/test/path',
|
||||||
|
false,
|
||||||
|
mockMcpHub
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prompt).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should explicitly handle undefined mcpHub', async () => {
|
||||||
|
const prompt = await SYSTEM_PROMPT(
|
||||||
|
'/test/path',
|
||||||
|
false,
|
||||||
|
undefined, // explicitly undefined mcpHub
|
||||||
|
undefined,
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prompt).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle different browser viewport sizes', async () => {
|
||||||
|
const prompt = await SYSTEM_PROMPT(
|
||||||
|
'/test/path',
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
'900x600' // different viewport size
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prompt).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should include diff strategy tool description', async () => {
|
||||||
|
const prompt = await SYSTEM_PROMPT(
|
||||||
|
'/test/path',
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
new SearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prompt).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.restoreAllMocks()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('addCustomInstructions', () => {
|
describe('addCustomInstructions', () => {
|
||||||
it('should include content from .clinerules and .cursorrules if present', async () => {
|
beforeEach(() => {
|
||||||
// Create test rule files
|
jest.clearAllMocks()
|
||||||
await fs.writeFile(path.join(tempDir, '.clinerules'), 'Always write tests\nUse TypeScript')
|
|
||||||
await fs.writeFile(path.join(tempDir, '.cursorrules'), 'Format code before committing')
|
|
||||||
|
|
||||||
const customInstructions = 'Base instructions'
|
|
||||||
const result = await addCustomInstructions(customInstructions, tempDir)
|
|
||||||
|
|
||||||
// Verify all instructions are included
|
|
||||||
expect(result).toContain('Base instructions')
|
|
||||||
expect(result).toContain('Always write tests')
|
|
||||||
expect(result).toContain('Use TypeScript')
|
|
||||||
expect(result).toContain('Format code before committing')
|
|
||||||
expect(result).toContain('Rules from .clinerules:')
|
|
||||||
expect(result).toContain('Rules from .cursorrules:')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle missing rule files gracefully', async () => {
|
it('should include preferred language when provided', async () => {
|
||||||
const customInstructions = 'Base instructions'
|
const result = await addCustomInstructions(
|
||||||
const result = await addCustomInstructions(customInstructions, tempDir)
|
'',
|
||||||
|
'/test/path',
|
||||||
|
'Spanish'
|
||||||
|
)
|
||||||
|
|
||||||
// Should only contain base instructions
|
expect(result).toMatchSnapshot()
|
||||||
expect(result).toContain('Base instructions')
|
|
||||||
expect(result).not.toContain('Rules from')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle empty rule files', async () => {
|
it('should include custom instructions when provided', async () => {
|
||||||
// Create empty rule files
|
const result = await addCustomInstructions(
|
||||||
await fs.writeFile(path.join(tempDir, '.clinerules'), '')
|
'Custom test instructions',
|
||||||
await fs.writeFile(path.join(tempDir, '.cursorrules'), '')
|
'/test/path'
|
||||||
|
)
|
||||||
|
|
||||||
const customInstructions = 'Base instructions'
|
expect(result).toMatchSnapshot()
|
||||||
const result = await addCustomInstructions(customInstructions, tempDir)
|
|
||||||
|
|
||||||
// Should only contain base instructions
|
|
||||||
expect(result).toContain('Base instructions')
|
|
||||||
expect(result).not.toContain('Rules from')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle whitespace-only rule files', async () => {
|
it('should include rules from .clinerules', async () => {
|
||||||
// Create rule files with only whitespace
|
const result = await addCustomInstructions(
|
||||||
await fs.writeFile(path.join(tempDir, '.clinerules'), ' \n \t ')
|
'',
|
||||||
await fs.writeFile(path.join(tempDir, '.cursorrules'), ' \n ')
|
'/test/path'
|
||||||
|
)
|
||||||
|
|
||||||
const customInstructions = 'Base instructions'
|
expect(result).toMatchSnapshot()
|
||||||
const result = await addCustomInstructions(customInstructions, tempDir)
|
|
||||||
|
|
||||||
// Should only contain base instructions
|
|
||||||
expect(result).toContain('Base instructions')
|
|
||||||
expect(result).not.toContain('Rules from')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle one rule file present and one missing', async () => {
|
it('should combine all custom instructions', async () => {
|
||||||
// Create only .clinerules
|
const result = await addCustomInstructions(
|
||||||
await fs.writeFile(path.join(tempDir, '.clinerules'), 'Always write tests')
|
'Custom test instructions',
|
||||||
|
'/test/path',
|
||||||
|
'French'
|
||||||
|
)
|
||||||
|
|
||||||
const customInstructions = 'Base instructions'
|
expect(result).toMatchSnapshot()
|
||||||
const result = await addCustomInstructions(customInstructions, tempDir)
|
|
||||||
|
|
||||||
// Should contain base instructions and .clinerules content
|
|
||||||
expect(result).toContain('Base instructions')
|
|
||||||
expect(result).toContain('Always write tests')
|
|
||||||
expect(result).toContain('Rules from .clinerules:')
|
|
||||||
expect(result).not.toContain('Rules from .cursorrules:')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle empty custom instructions with rule files', async () => {
|
afterAll(() => {
|
||||||
await fs.writeFile(path.join(tempDir, '.clinerules'), 'Always write tests')
|
jest.restoreAllMocks()
|
||||||
await fs.writeFile(path.join(tempDir, '.cursorrules'), 'Format code before committing')
|
|
||||||
|
|
||||||
const result = await addCustomInstructions('', tempDir)
|
|
||||||
|
|
||||||
// Should contain rule file content even with empty custom instructions
|
|
||||||
expect(result).toContain('Always write tests')
|
|
||||||
expect(result).toContain('Format code before committing')
|
|
||||||
expect(result).toContain('Rules from .clinerules:')
|
|
||||||
expect(result).toContain('Rules from .cursorrules:')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return empty string when no instructions or rules exist', async () => {
|
|
||||||
const result = await addCustomInstructions('', tempDir)
|
|
||||||
expect(result).toBe('')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user