import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; // Mock the analyzer module vi.mock('./analyzer/index.js', () => ({ analyze: vi.fn(), })); // Mock the logger vi.mock('./logger.js', () => ({ log: vi.fn(), })); describe('runAnalyze output files', () => { let tmpDir; beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'analyze-test-')); }); afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); it('should write knowledge-graph.json with full DashboardJSON schema', async () => { const { analyze } = await import('./analyzer/index.js'); const mockedAnalyze = vi.mocked(analyze); const mockDashboard = { version: '1.0.0', project: { name: 'test-project', description: 'A test project', languages: ['typescript'], frameworks: ['express'], analyzedAt: '2024-01-01T00:00:00.000Z', gitCommitHash: 'abc123def456', }, nodes: [ { id: 'file::src/index.ts', type: 'file', name: 'index.ts', summary: 'Entry point', tags: ['entry'] }, ], edges: [ { source: 'file::src/index.ts', target: 'file::src/utils.ts', type: 'imports' }, ], layers: [ { id: 'layer::core', name: 'Core', description: 'Core layer', nodeIds: ['file::src/index.ts'] }, ], tour: [ { order: 1, title: 'Start here', description: 'Entry point', nodeIds: ['file::src/index.ts'] }, ], }; mockedAnalyze.mockResolvedValue({ dashboard: mockDashboard, stats: { filesAnalyzed: 5, nodesByType: { file: 3, function: 2 }, edgesCreated: 4, layersIdentified: 1, }, files: [], }); // Simulate what runAnalyze does (without process.exit) const targetPath = tmpDir; const { dashboard, stats } = await analyze(targetPath, { full: false }); const outputDir = path.join(targetPath, '.understand-anything'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } const knowledgeGraphPath = path.join(outputDir, 'knowledge-graph.json'); fs.writeFileSync(knowledgeGraphPath, JSON.stringify(dashboard, null, 2)); const meta = { lastAnalyzedAt: new Date().toISOString(), gitCommitHash: dashboard.project.gitCommitHash || '', version: '1.0.0', analyzedFiles: stats.filesAnalyzed, }; const metaPath = path.join(outputDir, 'meta.json'); fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2)); // Verify knowledge-graph.json const kgContent = JSON.parse(fs.readFileSync(knowledgeGraphPath, 'utf-8')); expect(kgContent.version).toBe('1.0.0'); expect(kgContent.project.name).toBe('test-project'); expect(kgContent.project.gitCommitHash).toBe('abc123def456'); expect(kgContent.nodes).toHaveLength(1); expect(kgContent.edges).toHaveLength(1); expect(kgContent.layers).toHaveLength(1); expect(kgContent.tour).toHaveLength(1); // Verify meta.json const metaContent = JSON.parse(fs.readFileSync(metaPath, 'utf-8')); expect(metaContent.lastAnalyzedAt).toBeDefined(); expect(metaContent.gitCommitHash).toBe('abc123def456'); expect(metaContent.version).toBe('1.0.0'); expect(metaContent.analyzedFiles).toBe(5); }); it('should create .understand-anything directory if it does not exist', async () => { const { analyze } = await import('./analyzer/index.js'); const mockedAnalyze = vi.mocked(analyze); mockedAnalyze.mockResolvedValue({ dashboard: { version: '1.0.0', project: { name: 'test', description: '', languages: [], frameworks: [], analyzedAt: '2024-01-01T00:00:00.000Z', gitCommitHash: '', }, nodes: [], edges: [], layers: [], tour: [], }, stats: { filesAnalyzed: 0, nodesByType: {}, edgesCreated: 0, layersIdentified: 0, }, files: [], }); const targetPath = tmpDir; const outputDir = path.join(targetPath, '.understand-anything'); expect(fs.existsSync(outputDir)).toBe(false); const { dashboard, stats } = await analyze(targetPath, { full: false }); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } fs.writeFileSync(path.join(outputDir, 'knowledge-graph.json'), JSON.stringify(dashboard, null, 2)); fs.writeFileSync(path.join(outputDir, 'meta.json'), JSON.stringify({ lastAnalyzedAt: new Date().toISOString(), gitCommitHash: dashboard.project.gitCommitHash || '', version: '1.0.0', analyzedFiles: stats.filesAnalyzed, }, null, 2)); expect(fs.existsSync(outputDir)).toBe(true); expect(fs.existsSync(path.join(outputDir, 'knowledge-graph.json'))).toBe(true); expect(fs.existsSync(path.join(outputDir, 'meta.json'))).toBe(true); }); it('should produce output compatible with --preview command', async () => { const { analyze } = await import('./analyzer/index.js'); const mockedAnalyze = vi.mocked(analyze); mockedAnalyze.mockResolvedValue({ dashboard: { version: '1.0.0', project: { name: 'my-project', description: 'Test', languages: ['typescript'], frameworks: [], analyzedAt: '2024-06-01T12:00:00.000Z', gitCommitHash: 'deadbeef', }, nodes: [ { id: 'file::app.ts', type: 'file', name: 'app.ts', summary: 'App', tags: [] }, ], edges: [], layers: [], tour: [], }, stats: { filesAnalyzed: 1, nodesByType: { file: 1 }, edgesCreated: 0, layersIdentified: 0, }, files: [], }); const targetPath = tmpDir; const { dashboard, stats } = await analyze(targetPath, { full: true }); const outputDir = path.join(targetPath, '.understand-anything'); fs.mkdirSync(outputDir, { recursive: true }); fs.writeFileSync(path.join(outputDir, 'knowledge-graph.json'), JSON.stringify(dashboard, null, 2)); fs.writeFileSync(path.join(outputDir, 'meta.json'), JSON.stringify({ lastAnalyzedAt: new Date().toISOString(), gitCommitHash: dashboard.project.gitCommitHash || '', version: '1.0.0', analyzedFiles: stats.filesAnalyzed, }, null, 2)); // The output should be compatible with --preview: // knowledge-graph.json must have nodes and edges (DashboardJSON schema) const kg = JSON.parse(fs.readFileSync(path.join(outputDir, 'knowledge-graph.json'), 'utf-8')); expect(kg).toHaveProperty('version'); expect(kg).toHaveProperty('project'); expect(kg).toHaveProperty('nodes'); expect(kg).toHaveProperty('edges'); expect(Array.isArray(kg.nodes)).toBe(true); expect(Array.isArray(kg.edges)).toBe(true); // meta.json must have the expected fields const meta = JSON.parse(fs.readFileSync(path.join(outputDir, 'meta.json'), 'utf-8')); expect(meta).toHaveProperty('lastAnalyzedAt'); expect(meta).toHaveProperty('gitCommitHash'); expect(meta).toHaveProperty('version'); expect(meta).toHaveProperty('analyzedFiles'); expect(typeof meta.analyzedFiles).toBe('number'); }); });