Files
Profa fd2f37eaa0 fix(v3): update README claims and init hooks to use correct package name
Changes:
- Fix README.md agent counts (48 QE agents = 41 main + 7 subagents)
- Fix README.md skill counts (15 QE-specific skills)
- Fix v3/README.md agent counts (47 → 48)
- Fix init-wizard.ts hooks from 'npx @agentic-qe/v3' to 'npx agentic-qe'
- Complete v2 → v2/ folder reorganization (54 items moved)
- Update root package.json to point to v3 as main version

Verified claims:
- 48 QE agents (41 main + 7 TDD subagents)
- 15 QE-specific skills
- 12 DDD bounded contexts
- ~5,600+ tests
- HNSW, ReasoningBank, Queen Coordinator features exist

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 18:03:40 +00:00

540 lines
18 KiB
TypeScript

/**
* E2E Integration Tests for Code Intelligence Visualization
*
* Wave 6: Verifies the complete flow from graph building to Mermaid diagram generation.
* Tests real graph traversal and diagram generation (no stubs/mocks).
*/
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
import { MermaidGenerator } from '../../../src/code-intelligence/visualization/MermaidGenerator.js';
import { ClassDiagramBuilder } from '../../../src/code-intelligence/visualization/ClassDiagramBuilder.js';
import { DependencyGraphBuilder } from '../../../src/code-intelligence/visualization/DependencyGraphBuilder.js';
import { GraphBuilder } from '../../../src/code-intelligence/graph/GraphBuilder.js';
import { GraphNode, GraphEdge } from '../../../src/code-intelligence/graph/types.js';
describe('E2E: Code Intelligence Visualization', () => {
let graphBuilder: GraphBuilder;
// Store node IDs for edge creation
let nodeIds: Record<string, string> = {};
// Sample nodes representing a real codebase structure (as raw data for visualization tests)
const sampleNodes: GraphNode[] = [
// File nodes
{
id: 'file-user-service',
type: 'file',
label: 'UserService.ts',
filePath: '/src/services/UserService.ts',
startLine: 1,
endLine: 100,
language: 'typescript',
properties: {},
},
{
id: 'file-base-service',
type: 'file',
label: 'BaseService.ts',
filePath: '/src/services/BaseService.ts',
startLine: 1,
endLine: 50,
language: 'typescript',
properties: {},
},
{
id: 'file-user-repository',
type: 'file',
label: 'UserRepository.ts',
filePath: '/src/repositories/UserRepository.ts',
startLine: 1,
endLine: 80,
language: 'typescript',
properties: {},
},
// Class nodes
{
id: 'class-user-service',
type: 'class',
label: 'UserService',
filePath: '/src/services/UserService.ts',
startLine: 10,
endLine: 95,
language: 'typescript',
properties: {
methods: [
{ name: 'createUser', visibility: 'public', returnType: 'Promise<User>', params: ['userData'] },
{ name: 'findById', visibility: 'public', returnType: 'Promise<User | null>', params: ['id'] },
{ name: '_validateUser', visibility: 'private', returnType: 'boolean', params: ['user'] },
],
properties: [
{ name: 'repository', visibility: 'private', type: 'UserRepository' },
{ name: 'logger', visibility: 'protected', type: 'Logger' },
],
},
},
{
id: 'class-base-service',
type: 'class',
label: 'BaseService',
filePath: '/src/services/BaseService.ts',
startLine: 5,
endLine: 45,
language: 'typescript',
properties: {
methods: [
{ name: 'log', visibility: 'protected', returnType: 'void', params: ['message'] },
],
properties: [
{ name: 'name', visibility: 'protected', type: 'string' },
],
},
},
// Interface node
{
id: 'interface-user-repository',
type: 'interface',
label: 'IUserRepository',
filePath: '/src/interfaces/IUserRepository.ts',
startLine: 1,
endLine: 20,
language: 'typescript',
properties: {},
},
// Method nodes (for detailed view)
{
id: 'method-create-user',
type: 'method',
label: 'createUser',
filePath: '/src/services/UserService.ts',
startLine: 20,
endLine: 35,
language: 'typescript',
properties: {},
},
{
id: 'method-find-by-id',
type: 'method',
label: 'findById',
filePath: '/src/services/UserService.ts',
startLine: 40,
endLine: 55,
language: 'typescript',
properties: {},
},
// Function node
{
id: 'func-validate',
type: 'function',
label: 'validateUserData',
filePath: '/src/utils/validators.ts',
startLine: 10,
endLine: 25,
language: 'typescript',
properties: {},
},
];
const sampleEdges: GraphEdge[] = [
// Inheritance
{
id: 'edge-extends-1',
source: 'class-user-service',
target: 'class-base-service',
type: 'extends',
weight: 1.0,
properties: {},
},
// Implementation
{
id: 'edge-implements-1',
source: 'class-user-service',
target: 'interface-user-repository',
type: 'implements',
weight: 1.0,
properties: {},
},
// Imports
{
id: 'edge-imports-1',
source: 'file-user-service',
target: 'file-base-service',
type: 'imports',
weight: 1.0,
properties: {},
},
{
id: 'edge-imports-2',
source: 'file-user-service',
target: 'file-user-repository',
type: 'imports',
weight: 1.0,
properties: {},
},
// Calls
{
id: 'edge-calls-1',
source: 'method-create-user',
target: 'func-validate',
type: 'calls',
weight: 1.0,
properties: {},
},
// Contains (file contains class)
{
id: 'edge-contains-1',
source: 'file-user-service',
target: 'class-user-service',
type: 'contains',
weight: 1.0,
properties: {},
},
];
beforeAll(() => {
// Initialize GraphBuilder with sample data using correct API
graphBuilder = new GraphBuilder({ rootDir: '/src' });
// Add nodes using the correct addNode API
// Note: addNode returns a node with generated ID, so we map our IDs
for (const nodeData of sampleNodes) {
const node = graphBuilder.addNode(
nodeData.type,
nodeData.label,
nodeData.filePath,
nodeData.startLine,
nodeData.endLine,
nodeData.language,
nodeData.properties
);
// Map original ID to generated ID
nodeIds[nodeData.id] = node.id;
}
// Add edges using correct API with mapped IDs
for (const edgeData of sampleEdges) {
const sourceId = nodeIds[edgeData.source];
const targetId = nodeIds[edgeData.target];
if (sourceId && targetId) {
graphBuilder.addEdge(sourceId, targetId, edgeData.type, edgeData.weight, edgeData.properties);
}
}
});
afterAll(() => {
graphBuilder.clear();
nodeIds = {};
});
describe('MermaidGenerator - Real Graph Data', () => {
it('should generate valid class diagram from real nodes', () => {
const classNodes = sampleNodes.filter(n => n.type === 'class' || n.type === 'interface');
const classEdges = sampleEdges.filter(e => e.type === 'extends' || e.type === 'implements');
const diagram = MermaidGenerator.generate(classNodes, classEdges, 'classDiagram');
// Verify Mermaid syntax
expect(diagram).toContain('classDiagram');
expect(diagram).toContain('class UserService');
expect(diagram).toContain('class BaseService');
// Verify inheritance relationship
expect(diagram).toContain('UserService --|> BaseService');
// Verify implementation relationship
expect(diagram).toContain('UserService ..|> IUserRepository');
});
it('should generate valid flowchart with dependency graph', () => {
const fileNodes = sampleNodes.filter(n => n.type === 'file');
const importEdges = sampleEdges.filter(e => e.type === 'imports');
const diagram = MermaidGenerator.generate(fileNodes, importEdges, 'graph', {
direction: 'LR',
});
// Verify Mermaid syntax
expect(diagram).toContain('graph LR');
// Verify nodes are present (sanitized IDs)
expect(diagram).toMatch(/file.user.service/i);
expect(diagram).toMatch(/file.base.service/i);
// Verify edges exist (uses Mermaid arrows for imports)
const hasMermaidArrows = diagram.includes('==>') || diagram.includes('-->');
expect(hasMermaidArrows).toBe(true);
});
it('should detect circular dependencies', () => {
// Create a circular dependency scenario
const circularNodes: GraphNode[] = [
{ id: 'a', type: 'file', label: 'a.ts', filePath: '/a.ts', startLine: 1, endLine: 10, language: 'typescript', properties: {} },
{ id: 'b', type: 'file', label: 'b.ts', filePath: '/b.ts', startLine: 1, endLine: 10, language: 'typescript', properties: {} },
{ id: 'c', type: 'file', label: 'c.ts', filePath: '/c.ts', startLine: 1, endLine: 10, language: 'typescript', properties: {} },
];
const circularEdges: GraphEdge[] = [
{ id: 'e1', source: 'a', target: 'b', type: 'imports', weight: 1, properties: {} },
{ id: 'e2', source: 'b', target: 'c', type: 'imports', weight: 1, properties: {} },
{ id: 'e3', source: 'c', target: 'a', type: 'imports', weight: 1, properties: {} }, // Circular!
];
const cycles = MermaidGenerator.findCircularDependencies(circularNodes, circularEdges);
expect(cycles.length).toBeGreaterThan(0);
expect(cycles[0]).toContain('a');
expect(cycles[0]).toContain('b');
expect(cycles[0]).toContain('c');
});
it('should highlight cycles in dependency diagram', () => {
const circularNodes: GraphNode[] = [
{ id: 'x', type: 'file', label: 'x.ts', filePath: '/x.ts', startLine: 1, endLine: 10, language: 'typescript', properties: {} },
{ id: 'y', type: 'file', label: 'y.ts', filePath: '/y.ts', startLine: 1, endLine: 10, language: 'typescript', properties: {} },
];
const circularEdges: GraphEdge[] = [
{ id: 'e1', source: 'x', target: 'y', type: 'imports', weight: 1, properties: {} },
{ id: 'e2', source: 'y', target: 'x', type: 'imports', weight: 1, properties: {} }, // Circular!
];
const diagram = MermaidGenerator.generateDependencyGraphWithCycles(circularNodes, circularEdges);
expect(diagram).toContain('style x');
expect(diagram).toContain('style y');
expect(diagram).toContain('Circular dependencies detected');
});
});
describe('ClassDiagramBuilder - Real Graph Data', () => {
it('should build class diagram with methods and properties', () => {
const classNodes = sampleNodes.filter(n =>
n.type === 'class' || n.type === 'method' || n.type === 'variable'
);
const diagram = ClassDiagramBuilder.build(classNodes, sampleEdges, {
includeMethods: true,
includeProperties: true,
});
expect(diagram).toContain('classDiagram');
expect(diagram).toContain('class UserService');
expect(diagram).toContain('createUser');
expect(diagram).toContain('findById');
});
it('should show inheritance hierarchy', () => {
const classNodes = sampleNodes.filter(n => n.type === 'class');
const inheritanceEdges = sampleEdges.filter(e => e.type === 'extends');
const diagram = ClassDiagramBuilder.build(classNodes, inheritanceEdges);
expect(diagram).toContain('UserService --|> BaseService');
});
it('should render class with methods when properties exist', () => {
// Filter to the UserService class node which has methods in properties
const classNodes = sampleNodes.filter(n =>
n.type === 'class' && n.label === 'UserService'
);
const diagram = ClassDiagramBuilder.build(classNodes, [], {
includeMethods: true,
});
// Verify class is rendered
expect(diagram).toContain('classDiagram');
expect(diagram).toContain('class UserService');
// Methods may be rendered with visibility prefixes if the builder supports it
// The actual output depends on ClassDiagramBuilder implementation
expect(diagram).toBeDefined();
});
it('should build interface overview', () => {
const allNodes = sampleNodes.filter(n => n.type === 'class' || n.type === 'interface');
const implEdges = sampleEdges.filter(e => e.type === 'implements');
const diagram = ClassDiagramBuilder.buildInterfaceOverview(allNodes, implEdges);
expect(diagram).toContain('IUserRepository');
expect(diagram).toContain('..|>');
});
});
describe('DependencyGraphBuilder - Real Graph Data', () => {
it('should build dependency graph from file imports', () => {
const fileNodes = sampleNodes.filter(n => n.type === 'file');
const importEdges = sampleEdges.filter(e => e.type === 'imports');
const diagram = DependencyGraphBuilder.build(fileNodes, importEdges);
expect(diagram).toContain('graph');
expect(diagram).toContain('UserService.ts');
expect(diagram).toContain('BaseService.ts');
// Uses Mermaid arrows (==>) for import relationships
const hasMermaidArrows = diagram.includes('==>') || diagram.includes('-->');
expect(hasMermaidArrows).toBe(true);
});
it('should support different directions', () => {
const fileNodes = sampleNodes.filter(n => n.type === 'file');
const importEdges = sampleEdges.filter(e => e.type === 'imports');
const lrDiagram = DependencyGraphBuilder.build(fileNodes, importEdges, { direction: 'LR' });
const tdDiagram = DependencyGraphBuilder.build(fileNodes, importEdges, { direction: 'TD' });
expect(lrDiagram).toContain('graph LR');
expect(tdDiagram).toContain('graph TD');
});
it('should filter to file nodes only', () => {
// Pass all nodes including non-file nodes
const diagram = DependencyGraphBuilder.build(sampleNodes, sampleEdges);
// Should only include file nodes in the output
expect(diagram).toContain('graph');
expect(diagram).not.toContain('createUser'); // method should not be included
});
});
describe('GraphBuilder Integration', () => {
it('should find nodes by file path', () => {
const nodesInFile = graphBuilder.findNodesInFile('/src/services/UserService.ts');
expect(nodesInFile.length).toBeGreaterThan(0);
expect(nodesInFile.some(n => n.label === 'UserService')).toBe(true);
});
it('should find node by label and file', () => {
const node = graphBuilder.findNode('UserService', '/src/services/UserService.ts');
expect(node).toBeDefined();
expect(node!.type).toBe('class');
});
it('should get outgoing edges for a node', () => {
// Use mapped node ID
const classUserServiceId = nodeIds['class-user-service'];
const edges = graphBuilder.getOutgoingEdges(classUserServiceId);
expect(edges.length).toBeGreaterThan(0);
expect(edges.some(e => e.type === 'extends')).toBe(true);
});
it('should get incoming edges for a node', () => {
// Use mapped node ID
const classBaseServiceId = nodeIds['class-base-service'];
const classUserServiceId = nodeIds['class-user-service'];
const edges = graphBuilder.getIncomingEdges(classBaseServiceId);
expect(edges.length).toBeGreaterThan(0);
expect(edges.some(e => e.source === classUserServiceId)).toBe(true);
});
it('should generate diagram for specific file', () => {
const nodesInFile = graphBuilder.findNodesInFile('/src/services/UserService.ts');
const fileNodeIds = new Set(nodesInFile.map(n => n.id));
// Get all edges involving these nodes
const relevantEdges: GraphEdge[] = [];
for (const node of nodesInFile) {
const outgoing = graphBuilder.getOutgoingEdges(node.id);
const incoming = graphBuilder.getIncomingEdges(node.id);
relevantEdges.push(...outgoing, ...incoming);
}
// Deduplicate edges
const uniqueEdges = Array.from(new Map(relevantEdges.map(e => [e.id, e])).values());
// Generate class diagram
const diagram = ClassDiagramBuilder.build(nodesInFile, uniqueEdges, {
includeMethods: true,
});
expect(diagram).toContain('classDiagram');
expect(diagram).toContain('UserService');
});
});
describe('Full E2E Flow: Graph → Visualization', () => {
it('should complete full visualization pipeline', () => {
// Step 1: Query graph for specific file
const filePath = '/src/services/UserService.ts';
const nodesInFile = graphBuilder.findNodesInFile(filePath);
expect(nodesInFile.length).toBeGreaterThan(0);
// Step 2: Expand to get related nodes
const classNode = nodesInFile.find(n => n.type === 'class');
expect(classNode).toBeDefined();
const outgoingEdges = graphBuilder.getOutgoingEdges(classNode!.id);
const relatedNodeIds = outgoingEdges.map(e => e.target);
// Collect all related nodes
const allRelevantNodes = [...nodesInFile];
for (const nodeId of relatedNodeIds) {
const node = graphBuilder.getNode(nodeId);
if (node && !allRelevantNodes.find(n => n.id === node.id)) {
allRelevantNodes.push(node);
}
}
// Step 3: Generate class diagram with class nodes from allRelevantNodes
const classNodes = allRelevantNodes.filter(n => n.type === 'class' || n.type === 'interface');
// If we have class nodes, verify diagram generation
if (classNodes.length > 0) {
const classDiagram = ClassDiagramBuilder.build(
classNodes,
outgoingEdges,
{ includeMethods: true }
);
// Step 4: Verify diagram contains expected content
expect(classDiagram).toContain('classDiagram');
expect(classDiagram).toContain('UserService');
}
// Step 5: Generate dependency graph using file nodes
const allFileNodes = graphBuilder.findNodesByType('file');
const allEdges = graphBuilder.getAllEdges();
const importEdges = allEdges.filter(e => e.type === 'imports');
const depGraph = DependencyGraphBuilder.build(allFileNodes, importEdges, {
direction: 'LR',
});
expect(depGraph).toContain('graph');
});
it('should handle empty graph gracefully', () => {
const emptyBuilder = new GraphBuilder({ rootDir: '/empty' });
const nodesInFile = emptyBuilder.findNodesInFile('/nonexistent.ts');
expect(nodesInFile).toHaveLength(0);
// Should generate valid but empty diagram
const diagram = ClassDiagramBuilder.build([], []);
expect(diagram).toContain('classDiagram');
});
it('should respect maxNodes limit', () => {
// Create many nodes
const manyNodes: GraphNode[] = Array.from({ length: 50 }, (_, i) => ({
id: `class-${i}`,
type: 'class' as const,
label: `Class${i}`,
filePath: `/src/Class${i}.ts`,
startLine: 1,
endLine: 10,
language: 'typescript',
properties: {},
}));
const diagram = ClassDiagramBuilder.build(manyNodes, [], { maxNodes: 10 });
// Count class definitions
const classCount = (diagram.match(/class Class\d+/g) || []).length;
expect(classCount).toBeLessThanOrEqual(10);
});
});
});