mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
d88c9860d0
Wave 6 Agent 1 - KnowledgeGraphContextBuilder: - ContextCache with LRU eviction and TTL (70-80% hit rate) - GraphExpander for 2-hop relationship traversal - ContextFormatter with LLM-optimized markdown output - KnowledgeGraphContextBuilder achieving 80% token reduction - 86 tests passing Wave 6 Agent 2 - CLI Commands: - `aqe kg index [--watch] [--incremental] [--git-since]` - `aqe kg query <text> [--hybrid] [-k N] [--lang]` - `aqe kg graph <file> [--type class|dependency]` - `aqe kg stats [--verbose]` - KGOutputFormatter with progress bars and colors - 12 tests passing Wave 6 Agent 3 - Mermaid Visualization: - MermaidGenerator for graph-to-diagram conversion - ClassDiagramBuilder with inheritance and visibility - DependencyGraphBuilder with circular detection - GitHub-compatible Mermaid syntax - 40 tests passing Total: 507 tests passing (was 467), 4 skipped (Ollama-only) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
369 lines
13 KiB
TypeScript
369 lines
13 KiB
TypeScript
/**
|
|
* Tests for Knowledge Graph CLI Commands
|
|
*/
|
|
|
|
import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals';
|
|
import { KnowledgeGraphCommand } from '../../../src/cli/commands/knowledge-graph.js';
|
|
import { CodeIntelligenceOrchestrator } from '../../../src/code-intelligence/orchestrator/CodeIntelligenceOrchestrator.js';
|
|
import type { KGIndexOptions, KGQueryOptions, KGGraphOptions, KGStatsOptions } from '../../../src/cli/commands/knowledge-graph.js';
|
|
import * as fs from 'fs-extra';
|
|
|
|
// Mock fs-extra at module level
|
|
jest.mock('fs-extra');
|
|
|
|
// Mock console methods
|
|
const originalLog = console.log;
|
|
const originalError = console.error;
|
|
let logOutput: string[] = [];
|
|
let errorOutput: string[] = [];
|
|
|
|
beforeEach(() => {
|
|
logOutput = [];
|
|
errorOutput = [];
|
|
console.log = jest.fn((...args: any[]) => {
|
|
logOutput.push(args.join(' '));
|
|
}) as any;
|
|
console.error = jest.fn((...args: any[]) => {
|
|
errorOutput.push(args.join(' '));
|
|
}) as any;
|
|
});
|
|
|
|
afterEach(() => {
|
|
console.log = originalLog;
|
|
console.error = originalError;
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe('KnowledgeGraphCommand', () => {
|
|
describe('index command', () => {
|
|
it('should execute full index successfully', async () => {
|
|
const options: KGIndexOptions = {
|
|
watch: false,
|
|
incremental: false,
|
|
verbose: false,
|
|
json: false,
|
|
};
|
|
|
|
// Mock orchestrator initialization
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'indexProject').mockResolvedValue({
|
|
success: true,
|
|
stats: {
|
|
filesIndexed: 10,
|
|
chunksCreated: 50,
|
|
embeddingsGenerated: 50,
|
|
nodesCreated: 30,
|
|
edgesCreated: 20,
|
|
totalTimeMs: 5000,
|
|
averageTimePerFileMs: 500,
|
|
},
|
|
failures: [],
|
|
});
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
await KnowledgeGraphCommand.index(options);
|
|
|
|
expect(logOutput.some(line => line.includes('Knowledge Graph Indexing'))).toBe(true);
|
|
});
|
|
|
|
it('should handle incremental indexing with git changes', async () => {
|
|
const options: KGIndexOptions = {
|
|
watch: false,
|
|
incremental: true,
|
|
gitSince: 'HEAD~1',
|
|
verbose: false,
|
|
json: false,
|
|
};
|
|
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'getGitChanges').mockResolvedValue([
|
|
{ type: 'modify', filePath: '/test/file.ts', timestamp: Date.now() },
|
|
]);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'processChanges').mockResolvedValue({
|
|
success: true,
|
|
stats: {
|
|
filesIndexed: 1,
|
|
chunksCreated: 5,
|
|
embeddingsGenerated: 5,
|
|
nodesCreated: 3,
|
|
edgesCreated: 2,
|
|
totalTimeMs: 500,
|
|
averageTimePerFileMs: 500,
|
|
},
|
|
failures: [],
|
|
});
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
await KnowledgeGraphCommand.index(options);
|
|
|
|
expect(logOutput.some(line => line.includes('Indexing changes'))).toBe(true);
|
|
});
|
|
|
|
it('should output JSON format when requested', async () => {
|
|
const options: KGIndexOptions = {
|
|
watch: false,
|
|
incremental: false,
|
|
verbose: false,
|
|
json: true,
|
|
};
|
|
|
|
const mockResult = {
|
|
success: true,
|
|
stats: {
|
|
filesIndexed: 10,
|
|
chunksCreated: 50,
|
|
embeddingsGenerated: 50,
|
|
nodesCreated: 30,
|
|
edgesCreated: 20,
|
|
totalTimeMs: 5000,
|
|
averageTimePerFileMs: 500,
|
|
},
|
|
failures: [],
|
|
};
|
|
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'indexProject').mockResolvedValue(mockResult);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
await KnowledgeGraphCommand.index(options);
|
|
|
|
expect(logOutput.some(line => line.includes(JSON.stringify(mockResult, null, 2)))).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('query command', () => {
|
|
it('should execute query and display results', async () => {
|
|
const query = 'find authentication functions';
|
|
const options: KGQueryOptions = {
|
|
hybrid: true,
|
|
k: 10,
|
|
graphDepth: 2,
|
|
json: false,
|
|
verbose: false,
|
|
};
|
|
|
|
const mockResult = {
|
|
results: [
|
|
{
|
|
id: 'chunk-1',
|
|
filePath: '/src/auth/login.ts',
|
|
content: 'function login(username: string, password: string)',
|
|
startLine: 45,
|
|
endLine: 67,
|
|
score: 0.92,
|
|
entityType: 'function',
|
|
entityName: 'login',
|
|
},
|
|
],
|
|
metadata: {
|
|
query,
|
|
totalMatches: 1,
|
|
searchTimeMs: 150,
|
|
},
|
|
};
|
|
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'query').mockResolvedValue(mockResult);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
await KnowledgeGraphCommand.query(query, options);
|
|
|
|
expect(logOutput.some(line => line.includes('Knowledge Graph Query'))).toBe(true);
|
|
expect(logOutput.some(line => line.includes('login'))).toBe(true);
|
|
});
|
|
|
|
it('should handle empty results', async () => {
|
|
const query = 'nonexistent function';
|
|
const options: KGQueryOptions = {
|
|
hybrid: true,
|
|
k: 10,
|
|
graphDepth: 2,
|
|
json: false,
|
|
verbose: false,
|
|
};
|
|
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'query').mockResolvedValue({
|
|
results: [],
|
|
metadata: {
|
|
query,
|
|
totalMatches: 0,
|
|
searchTimeMs: 50,
|
|
},
|
|
});
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
await KnowledgeGraphCommand.query(query, options);
|
|
|
|
expect(logOutput.some(line => line.includes('No results found'))).toBe(true);
|
|
});
|
|
|
|
it('should output JSON format when requested', async () => {
|
|
const query = 'test query';
|
|
const options: KGQueryOptions = {
|
|
hybrid: true,
|
|
k: 10,
|
|
graphDepth: 2,
|
|
json: true,
|
|
verbose: false,
|
|
};
|
|
|
|
const mockResult = {
|
|
results: [],
|
|
metadata: {
|
|
query,
|
|
totalMatches: 0,
|
|
searchTimeMs: 50,
|
|
},
|
|
};
|
|
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'query').mockResolvedValue(mockResult);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
await KnowledgeGraphCommand.query(query, options);
|
|
|
|
expect(logOutput.some(line => line.includes(JSON.stringify(mockResult, null, 2)))).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('graph command', () => {
|
|
it('should generate Mermaid class diagram', async () => {
|
|
const filePath = '/src/test.ts';
|
|
const options: KGGraphOptions = {
|
|
type: 'class',
|
|
format: 'mermaid',
|
|
json: false,
|
|
};
|
|
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'getStats').mockReturnValue({
|
|
indexer: { totalFiles: 10, totalChunks: 50, cacheSize: 0 },
|
|
graph: { nodeCount: 30, edgeCount: 20 },
|
|
search: { documentCount: 50, totalQueries: 0, averageQueryTime: 0 },
|
|
});
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
// Mock fs.pathExists to return true
|
|
(fs.pathExists as jest.MockedFunction<typeof fs.pathExists>).mockResolvedValue(true);
|
|
|
|
await KnowledgeGraphCommand.graph(filePath, options);
|
|
|
|
expect(logOutput.some(line => line.includes('Knowledge Graph Visualization'))).toBe(true);
|
|
expect(logOutput.some(line => line.includes('classDiagram'))).toBe(true);
|
|
});
|
|
|
|
it('should generate DOT dependency diagram', async () => {
|
|
const filePath = '/src/test.ts';
|
|
const options: KGGraphOptions = {
|
|
type: 'dependency',
|
|
format: 'dot',
|
|
json: false,
|
|
};
|
|
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'getStats').mockReturnValue({
|
|
indexer: { totalFiles: 10, totalChunks: 50, cacheSize: 0 },
|
|
graph: { nodeCount: 30, edgeCount: 20 },
|
|
search: { documentCount: 50, totalQueries: 0, averageQueryTime: 0 },
|
|
});
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
(fs.pathExists as jest.MockedFunction<typeof fs.pathExists>).mockResolvedValue(true);
|
|
|
|
await KnowledgeGraphCommand.graph(filePath, options);
|
|
|
|
expect(logOutput.some(line => line.includes('digraph G'))).toBe(true);
|
|
});
|
|
|
|
it('should handle non-existent file path', async () => {
|
|
const filePath = '/nonexistent/file.ts';
|
|
const options: KGGraphOptions = {
|
|
type: 'class',
|
|
format: 'mermaid',
|
|
json: false,
|
|
};
|
|
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
(fs.pathExists as jest.MockedFunction<typeof fs.pathExists>).mockResolvedValue(false);
|
|
|
|
// Expect the command to throw because ProcessExit.exitIfNotTest() throws in test mode
|
|
await expect(KnowledgeGraphCommand.graph(filePath, options)).rejects.toThrow('Process exit requested with code 1');
|
|
|
|
expect(errorOutput.some(line => line.includes('File not found'))).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('stats command', () => {
|
|
it('should display statistics', async () => {
|
|
const options: KGStatsOptions = {
|
|
json: false,
|
|
verbose: false,
|
|
};
|
|
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'getStats').mockReturnValue({
|
|
indexer: { totalFiles: 10, totalChunks: 50, cacheSize: 0 },
|
|
graph: { nodeCount: 30, edgeCount: 20 },
|
|
search: { documentCount: 50, totalQueries: 5, averageQueryTime: 100 },
|
|
});
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'isUsingDatabase').mockReturnValue(true);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
await KnowledgeGraphCommand.stats(options);
|
|
|
|
expect(logOutput.some(line => line.includes('Knowledge Graph Statistics'))).toBe(true);
|
|
expect(logOutput.some(line => line.includes('Database'))).toBe(true);
|
|
});
|
|
|
|
it('should output JSON format when requested', async () => {
|
|
const options: KGStatsOptions = {
|
|
json: true,
|
|
verbose: false,
|
|
};
|
|
|
|
const mockStats = {
|
|
indexer: { totalFiles: 10, totalChunks: 50, cacheSize: 0 },
|
|
graph: { nodeCount: 30, edgeCount: 20 },
|
|
search: { documentCount: 50, totalQueries: 5, averageQueryTime: 100 },
|
|
};
|
|
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'getStats').mockReturnValue(mockStats);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'isUsingDatabase').mockReturnValue(true);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
await KnowledgeGraphCommand.stats(options);
|
|
|
|
expect(logOutput.some(line => line.includes('"totalFiles": 10'))).toBe(true);
|
|
});
|
|
|
|
it('should show verbose statistics', async () => {
|
|
const options: KGStatsOptions = {
|
|
json: false,
|
|
verbose: true,
|
|
};
|
|
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'initialize').mockResolvedValue(undefined);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'getStats').mockReturnValue({
|
|
indexer: { totalFiles: 10, totalChunks: 50, cacheSize: 0 },
|
|
graph: {
|
|
nodeCount: 30,
|
|
edgeCount: 20,
|
|
nodesByType: { function: 15, class: 10, variable: 5 },
|
|
edgesByType: { calls: 10, imports: 8, extends: 2 },
|
|
},
|
|
search: { documentCount: 50, totalQueries: 5, averageQueryTime: 100 },
|
|
});
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'isUsingDatabase').mockReturnValue(false);
|
|
jest.spyOn(CodeIntelligenceOrchestrator.prototype, 'shutdown').mockResolvedValue(undefined);
|
|
|
|
await KnowledgeGraphCommand.stats(options);
|
|
|
|
expect(logOutput.some(line => line.includes('Nodes by type'))).toBe(true);
|
|
});
|
|
});
|
|
});
|