mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-11 17:21:03 +08:00
Add comprehensive tests for CLI functionality and CodexLens compatibility
- Introduced tests for stale running fallback in CLI watch functionality to ensure proper handling of saved conversations. - Added compatibility tests for CodexLens CLI to verify index initialization despite compatibility conflicts. - Implemented tests for Smart Search MCP usage to validate default settings and path handling. - Created tests for UV Manager to ensure Python preference handling works as expected. - Added a detailed guide for CCW/Codex commands and skills, covering core commands, execution modes, and templates.
This commit is contained in:
135
ccw/tests/smart-search-mcp-usage.test.js
Normal file
135
ccw/tests/smart-search-mcp-usage.test.js
Normal file
@@ -0,0 +1,135 @@
|
||||
import { afterEach, before, describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const smartSearchPath = new URL('../dist/tools/smart-search.js', import.meta.url).href;
|
||||
|
||||
describe('Smart Search MCP usage defaults and path handling', async () => {
|
||||
let smartSearchModule;
|
||||
const tempDirs = [];
|
||||
|
||||
before(async () => {
|
||||
try {
|
||||
smartSearchModule = await import(smartSearchPath);
|
||||
} catch (err) {
|
||||
console.log('Note: smart-search module import skipped:', err?.message ?? String(err));
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
while (tempDirs.length > 0) {
|
||||
rmSync(tempDirs.pop(), { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
function createWorkspace() {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'ccw-smart-search-'));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
it('keeps schema defaults aligned with runtime docs', () => {
|
||||
if (!smartSearchModule) return;
|
||||
|
||||
const { schema } = smartSearchModule;
|
||||
const props = schema.inputSchema.properties;
|
||||
|
||||
assert.equal(props.maxResults.default, 5);
|
||||
assert.equal(props.limit.default, 5);
|
||||
assert.match(schema.description, /static FTS index/i);
|
||||
assert.match(props.path.description, /single file path/i);
|
||||
});
|
||||
|
||||
it('honors explicit small limit values', async () => {
|
||||
if (!smartSearchModule) return;
|
||||
|
||||
const dir = createWorkspace();
|
||||
const file = join(dir, 'many.ts');
|
||||
writeFileSync(file, ['const hit = 1;', 'const hit = 2;', 'const hit = 3;'].join('\n'));
|
||||
|
||||
const toolResult = await smartSearchModule.handler({
|
||||
action: 'search',
|
||||
query: 'hit',
|
||||
path: dir,
|
||||
limit: 1,
|
||||
regex: false,
|
||||
tokenize: false,
|
||||
});
|
||||
|
||||
assert.equal(toolResult.success, true, toolResult.error);
|
||||
assert.equal(toolResult.result.success, true);
|
||||
assert.equal(toolResult.result.results.length, 1);
|
||||
assert.equal(toolResult.result.metadata.pagination.limit, 1);
|
||||
});
|
||||
|
||||
it('scopes search results to a single file path', async () => {
|
||||
if (!smartSearchModule) return;
|
||||
|
||||
const dir = createWorkspace();
|
||||
const target = join(dir, 'target.ts');
|
||||
const other = join(dir, 'other.ts');
|
||||
writeFileSync(target, 'const TARGET_TOKEN = 1;\n');
|
||||
writeFileSync(other, 'const TARGET_TOKEN = 2;\n');
|
||||
|
||||
const toolResult = await smartSearchModule.handler({
|
||||
action: 'search',
|
||||
query: 'TARGET_TOKEN',
|
||||
path: target,
|
||||
regex: false,
|
||||
tokenize: false,
|
||||
});
|
||||
|
||||
assert.equal(toolResult.success, true, toolResult.error);
|
||||
assert.equal(toolResult.result.success, true);
|
||||
assert.ok(Array.isArray(toolResult.result.results));
|
||||
assert.ok(toolResult.result.results.length >= 1);
|
||||
|
||||
const normalizedFiles = toolResult.result.results.map((item) => String(item.file).replace(/\\/g, '/'));
|
||||
assert.ok(normalizedFiles.every((file) => file.endsWith('/target.ts') || file === 'target.ts'));
|
||||
assert.ok(normalizedFiles.every((file) => !file.endsWith('/other.ts')));
|
||||
});
|
||||
|
||||
it('normalizes wrapped multiline query and file path inputs', async () => {
|
||||
if (!smartSearchModule) return;
|
||||
|
||||
const dir = createWorkspace();
|
||||
const nestedDir = join(dir, 'hydro_generator_module', 'builders');
|
||||
mkdirSync(nestedDir, { recursive: true });
|
||||
const target = join(nestedDir, 'full_machine_builders.py');
|
||||
writeFileSync(target, 'def _resolve_rotor_inner():\n return rotor_main_seg\n');
|
||||
|
||||
const wrappedPath = target.replace(/([\\/])builders([\\/])/, '$1\n builders$2');
|
||||
const wrappedQuery = '_resolve_rotor_inner OR\n rotor_main_seg';
|
||||
|
||||
const toolResult = await smartSearchModule.handler({
|
||||
action: 'search',
|
||||
query: wrappedQuery,
|
||||
path: wrappedPath,
|
||||
regex: false,
|
||||
caseSensitive: false,
|
||||
});
|
||||
|
||||
assert.equal(toolResult.success, true, toolResult.error);
|
||||
assert.equal(toolResult.result.success, true);
|
||||
assert.ok(toolResult.result.results.length >= 1);
|
||||
});
|
||||
|
||||
it('surfaces backend failure details when fuzzy search fully fails', async () => {
|
||||
if (!smartSearchModule) return;
|
||||
|
||||
const missingPath = join(createWorkspace(), 'missing-folder', 'missing.ts');
|
||||
const toolResult = await smartSearchModule.handler({
|
||||
action: 'search',
|
||||
query: 'TARGET_TOKEN',
|
||||
path: missingPath,
|
||||
regex: false,
|
||||
tokenize: false,
|
||||
});
|
||||
|
||||
assert.equal(toolResult.success, false);
|
||||
assert.match(toolResult.error, /Both search backends failed:/);
|
||||
assert.match(toolResult.error, /(FTS|Ripgrep)/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user