mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
Add comprehensive tests for semantic chunking and search functionality
- Implemented tests for the ChunkConfig and Chunker classes, covering default and custom configurations. - Added tests for symbol-based chunking, including single and multiple symbols, handling of empty symbols, and preservation of line numbers. - Developed tests for sliding window chunking, ensuring correct chunking behavior with various content sizes and configurations. - Created integration tests for semantic search, validating embedding generation, vector storage, and search accuracy across a complex codebase. - Included performance tests for embedding generation and search operations. - Established tests for chunking strategies, comparing symbol-based and sliding window approaches. - Enhanced test coverage for edge cases, including handling of unicode characters and out-of-bounds symbol ranges.
This commit is contained in:
471
ccw/tests/codex-lens.test.js
Normal file
471
ccw/tests/codex-lens.test.js
Normal file
@@ -0,0 +1,471 @@
|
||||
/**
|
||||
* Tests for CodexLens API endpoints and tool integration
|
||||
*
|
||||
* Tests the following endpoints:
|
||||
* - GET /api/codexlens/status
|
||||
* - POST /api/codexlens/bootstrap
|
||||
* - POST /api/codexlens/init
|
||||
* - GET /api/codexlens/semantic/status
|
||||
* - POST /api/codexlens/semantic/install
|
||||
*
|
||||
* Also tests the codex-lens.js tool functions directly
|
||||
*/
|
||||
|
||||
import { describe, it, before, after, mock } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import { createServer } from 'http';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Import the codex-lens module - use file:// URL format for Windows compatibility
|
||||
const codexLensPath = new URL('../src/tools/codex-lens.js', import.meta.url).href;
|
||||
|
||||
describe('CodexLens Tool Functions', async () => {
|
||||
let codexLensModule;
|
||||
|
||||
before(async () => {
|
||||
try {
|
||||
codexLensModule = await import(codexLensPath);
|
||||
} catch (err) {
|
||||
console.log('Note: codex-lens module import skipped (module may not be available):', err.message);
|
||||
}
|
||||
});
|
||||
|
||||
describe('checkVenvStatus', () => {
|
||||
it('should return an object with ready property', async () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const status = await codexLensModule.checkVenvStatus();
|
||||
assert.ok(typeof status === 'object', 'Status should be an object');
|
||||
assert.ok('ready' in status, 'Status should have ready property');
|
||||
assert.ok(typeof status.ready === 'boolean', 'ready should be boolean');
|
||||
|
||||
if (status.ready) {
|
||||
assert.ok('version' in status, 'Ready status should include version');
|
||||
} else {
|
||||
assert.ok('error' in status, 'Not ready status should include error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkSemanticStatus', () => {
|
||||
it('should return semantic availability status', async () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const status = await codexLensModule.checkSemanticStatus();
|
||||
assert.ok(typeof status === 'object', 'Status should be an object');
|
||||
assert.ok('available' in status, 'Status should have available property');
|
||||
assert.ok(typeof status.available === 'boolean', 'available should be boolean');
|
||||
|
||||
if (status.available) {
|
||||
assert.ok('backend' in status, 'Available status should include backend');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('executeCodexLens', () => {
|
||||
it('should execute codexlens command and return result', async () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
// First check if CodexLens is ready
|
||||
const status = await codexLensModule.checkVenvStatus();
|
||||
if (!status.ready) {
|
||||
console.log('Skipping: CodexLens not installed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute a simple status command
|
||||
const result = await codexLensModule.executeCodexLens(['--help']);
|
||||
assert.ok(typeof result === 'object', 'Result should be an object');
|
||||
assert.ok('success' in result, 'Result should have success property');
|
||||
|
||||
// --help should succeed
|
||||
if (result.success) {
|
||||
assert.ok('output' in result, 'Success result should have output');
|
||||
assert.ok(result.output.includes('CodexLens') || result.output.includes('codexlens'),
|
||||
'Help output should mention CodexLens');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle timeout gracefully', async () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const status = await codexLensModule.checkVenvStatus();
|
||||
if (!status.ready) {
|
||||
console.log('Skipping: CodexLens not installed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a very short timeout to trigger timeout behavior
|
||||
// Note: This test may not always trigger timeout depending on system speed
|
||||
const result = await codexLensModule.executeCodexLens(['status', '--json'], { timeout: 1 });
|
||||
assert.ok(typeof result === 'object', 'Result should be an object');
|
||||
assert.ok('success' in result, 'Result should have success property');
|
||||
});
|
||||
});
|
||||
|
||||
describe('codexLensTool.execute', () => {
|
||||
it('should handle check action', async () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await codexLensModule.codexLensTool.execute({ action: 'check' });
|
||||
assert.ok(typeof result === 'object', 'Result should be an object');
|
||||
assert.ok('ready' in result, 'Check result should have ready property');
|
||||
});
|
||||
|
||||
it('should throw error for unknown action', async () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
await assert.rejects(
|
||||
async () => codexLensModule.codexLensTool.execute({ action: 'unknown_action' }),
|
||||
/Unknown action/,
|
||||
'Should throw error for unknown action'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle status action', async () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const checkResult = await codexLensModule.checkVenvStatus();
|
||||
if (!checkResult.ready) {
|
||||
console.log('Skipping: CodexLens not installed');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await codexLensModule.codexLensTool.execute({
|
||||
action: 'status',
|
||||
path: __dirname
|
||||
});
|
||||
assert.ok(typeof result === 'object', 'Result should be an object');
|
||||
assert.ok('success' in result, 'Result should have success property');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('CodexLens API Endpoints (Integration)', async () => {
|
||||
// These tests require a running server
|
||||
// They test the actual HTTP endpoints
|
||||
|
||||
const TEST_PORT = 19999;
|
||||
let serverModule;
|
||||
let server;
|
||||
let baseUrl;
|
||||
|
||||
before(async () => {
|
||||
// Note: We cannot easily start the ccw server in tests
|
||||
// So we test the endpoint handlers directly or mock the server
|
||||
baseUrl = `http://localhost:${TEST_PORT}`;
|
||||
|
||||
// Try to import server module for handler testing
|
||||
try {
|
||||
// serverModule = await import(join(__dirname, '..', 'src', 'core', 'server.js'));
|
||||
console.log('Note: Server integration tests require manual server start');
|
||||
} catch (err) {
|
||||
console.log('Server module not available for direct testing');
|
||||
}
|
||||
});
|
||||
|
||||
describe('GET /api/codexlens/status', () => {
|
||||
it('should return JSON response with ready status', async () => {
|
||||
// This test requires a running server
|
||||
// Skip if server is not running
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/api/codexlens/status`);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
assert.ok(typeof data === 'object', 'Response should be JSON object');
|
||||
assert.ok('ready' in data, 'Response should have ready property');
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.cause?.code === 'ECONNREFUSED') {
|
||||
console.log('Skipping: Server not running on port', TEST_PORT);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/codexlens/init', () => {
|
||||
it('should initialize index for given path', async () => {
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/api/codexlens/init`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path: __dirname })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
assert.ok(typeof data === 'object', 'Response should be JSON object');
|
||||
assert.ok('success' in data, 'Response should have success property');
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.cause?.code === 'ECONNREFUSED') {
|
||||
console.log('Skipping: Server not running on port', TEST_PORT);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/codexlens/semantic/status', () => {
|
||||
it('should return semantic search status', async () => {
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/api/codexlens/semantic/status`);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
assert.ok(typeof data === 'object', 'Response should be JSON object');
|
||||
assert.ok('available' in data, 'Response should have available property');
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.cause?.code === 'ECONNREFUSED') {
|
||||
console.log('Skipping: Server not running on port', TEST_PORT);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('CodexLens Tool Definition', async () => {
|
||||
let codexLensModule;
|
||||
|
||||
before(async () => {
|
||||
try {
|
||||
codexLensModule = await import(codexLensPath);
|
||||
} catch (err) {
|
||||
console.log('Note: codex-lens module not available');
|
||||
}
|
||||
});
|
||||
|
||||
it('should have correct tool name', () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
assert.strictEqual(codexLensModule.codexLensTool.name, 'codex_lens');
|
||||
});
|
||||
|
||||
it('should have description', () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
assert.ok(codexLensModule.codexLensTool.description, 'Should have description');
|
||||
assert.ok(codexLensModule.codexLensTool.description.includes('CodexLens'),
|
||||
'Description should mention CodexLens');
|
||||
});
|
||||
|
||||
it('should have parameters schema', () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const { parameters } = codexLensModule.codexLensTool;
|
||||
assert.ok(parameters, 'Should have parameters');
|
||||
assert.strictEqual(parameters.type, 'object');
|
||||
assert.ok(parameters.properties, 'Should have properties');
|
||||
assert.ok(parameters.properties.action, 'Should have action property');
|
||||
assert.deepStrictEqual(parameters.required, ['action'], 'action should be required');
|
||||
});
|
||||
|
||||
it('should support all documented actions', () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const { parameters } = codexLensModule.codexLensTool;
|
||||
const supportedActions = parameters.properties.action.enum;
|
||||
|
||||
const expectedActions = ['init', 'search', 'symbol', 'status', 'update', 'bootstrap', 'check'];
|
||||
|
||||
for (const action of expectedActions) {
|
||||
assert.ok(supportedActions.includes(action), `Should support ${action} action`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have execute function', () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
assert.ok(typeof codexLensModule.codexLensTool.execute === 'function',
|
||||
'Should have execute function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('CodexLens Path Configuration', () => {
|
||||
it('should use correct venv path based on platform', async () => {
|
||||
const codexLensDataDir = join(homedir(), '.codexlens');
|
||||
const codexLensVenv = join(codexLensDataDir, 'venv');
|
||||
|
||||
const expectedPython = process.platform === 'win32'
|
||||
? join(codexLensVenv, 'Scripts', 'python.exe')
|
||||
: join(codexLensVenv, 'bin', 'python');
|
||||
|
||||
// Just verify the path construction logic is correct
|
||||
assert.ok(expectedPython.includes('codexlens'), 'Python path should include codexlens');
|
||||
assert.ok(expectedPython.includes('venv'), 'Python path should include venv');
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
assert.ok(expectedPython.includes('Scripts'), 'Windows should use Scripts directory');
|
||||
assert.ok(expectedPython.endsWith('.exe'), 'Windows should have .exe extension');
|
||||
} else {
|
||||
assert.ok(expectedPython.includes('bin'), 'Unix should use bin directory');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('CodexLens Error Handling', async () => {
|
||||
let codexLensModule;
|
||||
|
||||
before(async () => {
|
||||
try {
|
||||
codexLensModule = await import(codexLensPath);
|
||||
} catch (err) {
|
||||
console.log('Note: codex-lens module not available');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle missing file parameter for symbol action', async () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const checkResult = await codexLensModule.checkVenvStatus();
|
||||
if (!checkResult.ready) {
|
||||
console.log('Skipping: CodexLens not installed');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await codexLensModule.codexLensTool.execute({
|
||||
action: 'symbol'
|
||||
// file is missing
|
||||
});
|
||||
|
||||
// Should either error or return success: false
|
||||
assert.ok(typeof result === 'object', 'Result should be an object');
|
||||
});
|
||||
|
||||
it('should handle missing files parameter for update action', async () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await codexLensModule.codexLensTool.execute({
|
||||
action: 'update'
|
||||
// files is missing
|
||||
});
|
||||
|
||||
assert.ok(typeof result === 'object', 'Result should be an object');
|
||||
assert.strictEqual(result.success, false, 'Should return success: false');
|
||||
assert.ok(result.error, 'Should have error message');
|
||||
assert.ok(result.error.includes('files'), 'Error should mention files parameter');
|
||||
});
|
||||
|
||||
it('should handle empty files array for update action', async () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await codexLensModule.codexLensTool.execute({
|
||||
action: 'update',
|
||||
files: []
|
||||
});
|
||||
|
||||
assert.ok(typeof result === 'object', 'Result should be an object');
|
||||
assert.strictEqual(result.success, false, 'Should return success: false');
|
||||
});
|
||||
});
|
||||
|
||||
describe('CodexLens Search Parameters', async () => {
|
||||
let codexLensModule;
|
||||
|
||||
before(async () => {
|
||||
try {
|
||||
codexLensModule = await import(codexLensPath);
|
||||
} catch (err) {
|
||||
console.log('Note: codex-lens module not available');
|
||||
}
|
||||
});
|
||||
|
||||
it('should support text and semantic search modes', () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const { parameters } = codexLensModule.codexLensTool;
|
||||
const modeEnum = parameters.properties.mode?.enum;
|
||||
|
||||
assert.ok(modeEnum, 'Should have mode enum');
|
||||
assert.ok(modeEnum.includes('text'), 'Should support text mode');
|
||||
assert.ok(modeEnum.includes('semantic'), 'Should support semantic mode');
|
||||
});
|
||||
|
||||
it('should have limit parameter with default', () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const { parameters } = codexLensModule.codexLensTool;
|
||||
const limitProp = parameters.properties.limit;
|
||||
|
||||
assert.ok(limitProp, 'Should have limit property');
|
||||
assert.strictEqual(limitProp.type, 'number', 'limit should be number');
|
||||
assert.strictEqual(limitProp.default, 20, 'Default limit should be 20');
|
||||
});
|
||||
|
||||
it('should support output format options', () => {
|
||||
if (!codexLensModule) {
|
||||
console.log('Skipping: codex-lens module not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const { parameters } = codexLensModule.codexLensTool;
|
||||
const formatEnum = parameters.properties.format?.enum;
|
||||
|
||||
assert.ok(formatEnum, 'Should have format enum');
|
||||
assert.ok(formatEnum.includes('json'), 'Should support json format');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user