knowledge-graph-preview / cli /analyzer /domain-context.test.js
mr4's picture
Upload 136 files
fd8cdf5 verified
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import { generateDomainContext, detectEntryPoints, extractFileSignatures, extractMetadata, truncateToFit, } from './domain-context';
function createTempDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'domain-ctx-test-'));
}
function removeTempDir(dir) {
fs.rmSync(dir, { recursive: true, force: true });
}
function createFileEntry(tmpDir, relativePath, content) {
const fullPath = path.join(tmpDir, relativePath);
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, content);
return {
path: fullPath,
relativePath,
category: 'code',
language: 'typescript',
lineCount: content.split('\n').length,
};
}
describe('domain-context', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempDir();
});
afterEach(() => {
removeTempDir(tmpDir);
});
describe('detectEntryPoints', () => {
it('detects Express-style HTTP routes', () => {
const file = createFileEntry(tmpDir, 'src/routes.ts', `
import express from 'express';
const app = express();
app.get('/users', (req, res) => {
res.json([]);
});
app.post('/users', (req, res) => {
res.status(201).json({});
});
`);
const entryPoints = detectEntryPoints(tmpDir, [file]);
expect(entryPoints.length).toBe(2);
expect(entryPoints[0].type).toBe('http');
expect(entryPoints[0].description).toBe('Express/Koa route');
expect(entryPoints[0].match).toContain('/users');
expect(entryPoints[0].file).toBe('src/routes.ts');
expect(entryPoints[0].line).toBeGreaterThan(0);
expect(entryPoints[0].snippet.length).toBeGreaterThan(0);
});
it('detects Python decorator routes', () => {
const file = createFileEntry(tmpDir, 'app/views.py', `
from flask import Flask
app = Flask(__name__)
@app.route('/api/items')
def get_items():
return jsonify([])
@app.route('/api/items/<id>')
def get_item(id):
return jsonify({})
`);
// Override language for Python
file.language = 'python';
const entryPoints = detectEntryPoints(tmpDir, [file]);
expect(entryPoints.length).toBe(2);
expect(entryPoints[0].type).toBe('http');
expect(entryPoints[0].description).toBe('Decorator route (Flask/FastAPI/NestJS)');
expect(entryPoints[0].match).toContain('/api/items');
});
it('skips test files in entry point detection', () => {
const testFile = createFileEntry(tmpDir, 'src/routes.test.ts', `
import express from 'express';
const app = express();
app.get('/test-route', (req, res) => {
res.json([]);
});
`);
const specFile = createFileEntry(tmpDir, 'src/__tests__/api.ts', `
app.post('/another-route', handler);
`);
const entryPoints = detectEntryPoints(tmpDir, [testFile, specFile]);
expect(entryPoints.length).toBe(0);
});
it('detects CLI commands', () => {
const file = createFileEntry(tmpDir, 'src/cli.ts', `
program
.command('build')
.description('Build the project')
.action(buildAction);
program
.command('deploy')
.description('Deploy to production')
.action(deployAction);
`);
const entryPoints = detectEntryPoints(tmpDir, [file]);
expect(entryPoints.length).toBe(2);
expect(entryPoints[0].type).toBe('cli');
expect(entryPoints[0].match).toContain('build');
});
it('detects event listeners', () => {
const file = createFileEntry(tmpDir, 'src/events.ts', `
emitter.on('user:created', handleUserCreated);
emitter.on('order:completed', handleOrderCompleted);
`);
const entryPoints = detectEntryPoints(tmpDir, [file]);
expect(entryPoints.length).toBe(2);
expect(entryPoints[0].type).toBe('event');
});
});
describe('extractFileSignatures', () => {
it('extracts file signatures with exports and imports', () => {
const file = createFileEntry(tmpDir, 'src/service/user-service.ts', `
import { db } from '../database';
import { UserModel } from '../models/user';
import { hashPassword } from '../utils/crypto';
export class UserService {
async createUser(name: string, email: string) {
return db.insert({ name, email });
}
}
export function validateEmail(email: string): boolean {
return email.includes('@');
}
export const USER_ROLES = ['admin', 'user'] as const;
`);
const signatures = extractFileSignatures(tmpDir, [file]);
expect(signatures.length).toBe(1);
expect(signatures[0].file).toBe('src/service/user-service.ts');
expect(signatures[0].exports).toContain('UserService');
expect(signatures[0].exports).toContain('validateEmail');
expect(signatures[0].exports).toContain('USER_ROLES');
expect(signatures[0].imports).toContain('../database');
expect(signatures[0].imports).toContain('../models/user');
expect(signatures[0].imports).toContain('../utils/crypto');
expect(signatures[0].lines).toBeGreaterThan(0);
expect(signatures[0].preview.length).toBeGreaterThan(0);
expect(signatures[0].preview.length).toBeLessThanOrEqual(500);
});
it('prioritizes files with business-logic keywords', () => {
const genericFile = createFileEntry(tmpDir, 'src/utils/helpers.ts', `
export function formatDate(d: Date): string { return d.toISOString(); }
`);
const controllerFile = createFileEntry(tmpDir, 'src/controller/user-controller.ts', `
export class UserController {
getUser() {}
}
`);
const signatures = extractFileSignatures(tmpDir, [genericFile, controllerFile]);
// Controller should come first due to priority keywords
expect(signatures[0].file).toBe('src/controller/user-controller.ts');
});
});
describe('extractMetadata', () => {
it('reads package.json metadata', () => {
const pkgContent = JSON.stringify({
name: 'my-app',
description: 'A test application',
scripts: { build: 'tsc', test: 'vitest' },
dependencies: { express: '^4.18.0', lodash: '^4.17.0' },
devDependencies: { typescript: '^5.0.0' },
});
fs.writeFileSync(path.join(tmpDir, 'package.json'), pkgContent);
const metadata = extractMetadata(tmpDir);
expect(metadata['package.json']).toBeDefined();
const pkg = metadata['package.json'];
expect(pkg.name).toBe('my-app');
expect(pkg.description).toBe('A test application');
expect(pkg.scripts).toEqual(['build', 'test']);
expect(pkg.dependencies).toEqual(['express', 'lodash']);
expect(pkg.devDependencies).toEqual(['typescript']);
});
it('reads README.md metadata', () => {
fs.writeFileSync(path.join(tmpDir, 'README.md'), '# My Project\n\nThis is a test project.');
const metadata = extractMetadata(tmpDir);
expect(metadata['README.md']).toBe('# My Project\n\nThis is a test project.');
});
});
describe('truncateToFit', () => {
it('respects 512 KB output cap', () => {
// Generate a large domain context that exceeds 512 KB
const largeFileTree = [];
for (let i = 0; i < 5000; i++) {
largeFileTree.push(`src/modules/module-${i}/very-long-file-name-that-takes-up-space-${i}.ts`);
}
const largeEntryPoints = [];
for (let i = 0; i < 200; i++) {
largeEntryPoints.push({
file: `src/routes/route-${i}.ts`,
line: i,
type: 'http',
description: 'Express/Koa route',
match: `app.get('/api/v1/resources/${i}/details/nested/path')`.slice(0, 120),
snippet: 'x'.repeat(300),
});
}
const largeSignatures = [];
for (let i = 0; i < 40; i++) {
largeSignatures.push({
file: `src/services/service-${i}.ts`,
exports: Array.from({ length: 20 }, (_, j) => `export${j}`),
imports: Array.from({ length: 20 }, (_, j) => `./module${j}`),
lines: 500,
preview: 'y'.repeat(500),
});
}
const context = {
projectRoot: '/tmp/test-project',
fileCount: 5000,
fileTree: largeFileTree,
entryPoints: largeEntryPoints,
fileSignatures: largeSignatures,
metadata: {},
};
const truncated = truncateToFit(context);
const output = JSON.stringify(truncated, null, 2);
const byteSize = Buffer.byteLength(output, 'utf-8');
expect(byteSize).toBeLessThanOrEqual(512 * 1024);
});
it('does not truncate when under the limit', () => {
const context = {
projectRoot: '/tmp/small',
fileCount: 2,
fileTree: ['src/a.ts', 'src/b.ts'],
entryPoints: [],
fileSignatures: [],
metadata: {},
};
const result = truncateToFit(context);
expect(result.fileTree).toEqual(['src/a.ts', 'src/b.ts']);
});
});
describe('generateDomainContext', () => {
it('handles empty file list gracefully', () => {
const context = generateDomainContext(tmpDir, []);
expect(context.projectRoot).toBe(tmpDir);
expect(context.fileCount).toBe(0);
expect(context.fileTree).toEqual([]);
expect(context.entryPoints).toEqual([]);
expect(context.fileSignatures).toEqual([]);
});
it('generates complete domain context', () => {
const routeFile = createFileEntry(tmpDir, 'src/routes.ts', `
import express from 'express';
const router = express.Router();
router.get('/api/users', getUsers);
export function handleRequest() {}
`);
const serviceFile = createFileEntry(tmpDir, 'src/service.ts', `
import { db } from './db';
export class UserService {
findAll() { return db.query('SELECT * FROM users'); }
}
`);
const context = generateDomainContext(tmpDir, [routeFile, serviceFile]);
expect(context.fileCount).toBe(2);
expect(context.fileTree).toContain('src/routes.ts');
expect(context.fileTree).toContain('src/service.ts');
expect(context.entryPoints.length).toBeGreaterThan(0);
expect(context.fileSignatures.length).toBe(2);
});
});
});