mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
214 lines
8.5 KiB
TypeScript
214 lines
8.5 KiB
TypeScript
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import fs from 'fs/promises';
|
|
import path from 'path';
|
|
import os from 'os';
|
|
import {
|
|
walkRepositoryPaths,
|
|
readFileContents,
|
|
} from '../../src/core/ingestion/filesystem-walker.js';
|
|
import { parseFilesWithWorkers } from '../helpers/worker-parse.js';
|
|
import { isLanguageAvailable } from '../../src/core/tree-sitter/parser-loader.js';
|
|
import { SupportedLanguages } from '../../src/config/supported-languages.js';
|
|
|
|
// ============================================================================
|
|
// E2E: .gitignore + .gitnexusignore + unsupported language skip
|
|
// ============================================================================
|
|
|
|
describe('ignore + language-skip E2E', () => {
|
|
let tmpDir: string;
|
|
|
|
beforeAll(async () => {
|
|
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-e2e-ignore-skip-'));
|
|
|
|
// Create directory structure
|
|
await fs.mkdir(path.join(tmpDir, 'src'), { recursive: true });
|
|
await fs.mkdir(path.join(tmpDir, 'data'), { recursive: true });
|
|
await fs.mkdir(path.join(tmpDir, 'vendor'), { recursive: true });
|
|
|
|
// .gitignore — excludes data/ and *.log
|
|
await fs.writeFile(path.join(tmpDir, '.gitignore'), 'data/\n*.log\n');
|
|
|
|
// .gitnexusignore — excludes vendor/
|
|
await fs.writeFile(path.join(tmpDir, '.gitnexusignore'), 'vendor/\n');
|
|
|
|
// Source files (should be indexed)
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'src', 'index.ts'),
|
|
"import { greet } from './greet';\n\nexport function main(): string {\n return greet();\n}\n",
|
|
);
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'src', 'greet.ts'),
|
|
"export function greet(): string {\n return 'hello';\n}\n",
|
|
);
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'src', 'service.ts'),
|
|
'export class UserService { load(): string { return "loaded"; } }\n',
|
|
);
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'src', 'service.d.ts'),
|
|
'export declare class UserService { load(): string; }\n',
|
|
);
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'src', 'vite-env.d.ts'),
|
|
'declare const APP_ENV: string;\n',
|
|
);
|
|
await fs.writeFile(path.join(tmpDir, 'src', 'esm-service.mts'), 'export class EsmService {}\n');
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'src', 'esm-service.d.mts'),
|
|
'export declare class EsmService {}\n',
|
|
);
|
|
await fs.writeFile(path.join(tmpDir, 'src', 'cjs-service.cts'), 'export class CjsService {}\n');
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'src', 'cjs-service.d.cts'),
|
|
'export declare class CjsService {}\n',
|
|
);
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'src', 'ambient.d.mts'),
|
|
'export declare class AmbientEsmService {}\n',
|
|
);
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'src', 'ambient.d.cts'),
|
|
'export declare class AmbientCjsService {}\n',
|
|
);
|
|
|
|
// Swift file — triggers language skip when grammar unavailable
|
|
await fs.writeFile(
|
|
path.join(tmpDir, 'src', 'App.swift'),
|
|
'class App {\n func run() {\n print("running")\n }\n}\n',
|
|
);
|
|
|
|
// Files that should be excluded
|
|
await fs.writeFile(path.join(tmpDir, 'data', 'seed.json'), '{}');
|
|
await fs.writeFile(path.join(tmpDir, 'vendor', 'lib.js'), 'var x = 1;\n');
|
|
await fs.writeFile(path.join(tmpDir, 'debug.log'), 'debug log entry\n');
|
|
});
|
|
|
|
afterAll(async () => {
|
|
try {
|
|
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
} catch {
|
|
/* best-effort */
|
|
}
|
|
});
|
|
|
|
// ── File Discovery ──────────────────────────────────────────────────
|
|
|
|
describe('file discovery (walkRepositoryPaths)', () => {
|
|
it('includes source files from src/', async () => {
|
|
const files = await walkRepositoryPaths(tmpDir);
|
|
const paths = files.map((f) => f.path.replace(/\\/g, '/'));
|
|
|
|
expect(paths).toContain('src/index.ts');
|
|
expect(paths).toContain('src/greet.ts');
|
|
expect(paths).toContain('src/service.ts');
|
|
expect(paths).not.toContain('src/service.d.ts');
|
|
expect(paths).toContain('src/vite-env.d.ts');
|
|
expect(paths).toContain('src/esm-service.mts');
|
|
expect(paths).not.toContain('src/esm-service.d.mts');
|
|
expect(paths).toContain('src/cjs-service.cts');
|
|
expect(paths).not.toContain('src/cjs-service.d.cts');
|
|
expect(paths).toContain('src/ambient.d.mts');
|
|
expect(paths).toContain('src/ambient.d.cts');
|
|
});
|
|
|
|
it('includes .swift files (discovery does not filter by language)', async () => {
|
|
const files = await walkRepositoryPaths(tmpDir);
|
|
const paths = files.map((f) => f.path.replace(/\\/g, '/'));
|
|
|
|
// Swift file should be discovered — language skip happens at parse time
|
|
expect(paths).toContain('src/App.swift');
|
|
});
|
|
|
|
it('excludes gitignored directories (data/)', async () => {
|
|
const files = await walkRepositoryPaths(tmpDir);
|
|
const paths = files.map((f) => f.path.replace(/\\/g, '/'));
|
|
|
|
expect(paths.every((p) => !p.includes('data/'))).toBe(true);
|
|
});
|
|
|
|
it('excludes gitignored file patterns (*.log)', async () => {
|
|
const files = await walkRepositoryPaths(tmpDir);
|
|
const paths = files.map((f) => f.path.replace(/\\/g, '/'));
|
|
|
|
expect(paths.every((p) => !p.endsWith('.log'))).toBe(true);
|
|
});
|
|
|
|
it('excludes gitnexusignored directories (vendor/)', async () => {
|
|
const files = await walkRepositoryPaths(tmpDir);
|
|
const paths = files.map((f) => f.path.replace(/\\/g, '/'));
|
|
|
|
expect(paths.every((p) => !p.includes('vendor/'))).toBe(true);
|
|
});
|
|
});
|
|
|
|
// ── Parsing ─────────────────────────────────────────────────────────
|
|
|
|
describe('parsing (processParsing)', () => {
|
|
it('parses TypeScript files into graph nodes and skips Swift gracefully', async () => {
|
|
// Phase 1: discover files
|
|
const scannedFiles = await walkRepositoryPaths(tmpDir);
|
|
const relativePaths = scannedFiles.map((f) => f.path);
|
|
|
|
// Phase 2: read contents
|
|
const contentMap = await readFileContents(tmpDir, relativePaths);
|
|
const files = Array.from(contentMap.entries()).map(([p, content]) => ({
|
|
path: p,
|
|
content,
|
|
}));
|
|
|
|
// Phase 3: parse through the worker pool (the sole parse path).
|
|
// Should NOT throw even if the Swift grammar is unavailable — the
|
|
// worker skips files whose native parser can't load.
|
|
const { graph } = await parseFilesWithWorkers(files);
|
|
|
|
// TypeScript files should produce Function nodes
|
|
const nodes = graph.nodes;
|
|
const functionNodes = nodes.filter((n) => n.label === 'Function');
|
|
const functionNames = functionNodes.map((n) => n.properties.name);
|
|
|
|
expect(functionNames).toContain('main');
|
|
expect(functionNames).toContain('greet');
|
|
|
|
const userServiceNodes = nodes.filter(
|
|
(node) => node.label === 'Class' && node.properties.name === 'UserService',
|
|
);
|
|
expect(userServiceNodes).toHaveLength(1);
|
|
expect(userServiceNodes[0].properties.filePath).toBe('src/service.ts');
|
|
expect(nodes.some((node) => node.properties.filePath === 'src/service.d.ts')).toBe(false);
|
|
|
|
expect(
|
|
nodes.filter((node) => node.label === 'Class' && node.properties.name === 'EsmService'),
|
|
).toHaveLength(1);
|
|
expect(
|
|
nodes.filter((node) => node.label === 'Class' && node.properties.name === 'CjsService'),
|
|
).toHaveLength(1);
|
|
expect(
|
|
nodes.filter(
|
|
(node) => node.label === 'Class' && node.properties.name === 'AmbientEsmService',
|
|
),
|
|
).toHaveLength(1);
|
|
expect(
|
|
nodes.filter(
|
|
(node) => node.label === 'Class' && node.properties.name === 'AmbientCjsService',
|
|
),
|
|
).toHaveLength(1);
|
|
|
|
// Function nodes should reference the correct source files
|
|
const fnFilePaths = functionNodes.map((n) =>
|
|
(n.properties.filePath as string).replace(/\\/g, '/'),
|
|
);
|
|
expect(fnFilePaths.some((p) => p.includes('index.ts'))).toBe(true);
|
|
expect(fnFilePaths.some((p) => p.includes('greet.ts'))).toBe(true);
|
|
|
|
// Swift behavior depends on grammar availability
|
|
if (!isLanguageAvailable(SupportedLanguages.Swift)) {
|
|
// No Swift-sourced nodes should appear in the graph
|
|
const swiftNodes = nodes.filter((n) =>
|
|
(n.properties.filePath as string | undefined)?.endsWith('.swift'),
|
|
);
|
|
expect(swiftNodes).toHaveLength(0);
|
|
}
|
|
// If Swift IS available, Swift nodes may appear — that's fine
|
|
});
|
|
});
|
|
});
|