243 lines
8.4 KiB
TypeScript
243 lines
8.4 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import * as path from 'path';
|
|
import { LazyFileLoader } from '../ts/context/lazy-file-loader.js';
|
|
import type { IFileMetadata } from '../ts/context/types.js';
|
|
|
|
const testProjectRoot = process.cwd();
|
|
|
|
tap.test('LazyFileLoader should create instance with project root', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
expect(loader).toBeInstanceOf(LazyFileLoader);
|
|
});
|
|
|
|
tap.test('LazyFileLoader.getMetadata should return file metadata without loading contents', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
const packageJsonPath = path.join(testProjectRoot, 'package.json');
|
|
|
|
const metadata = await loader.getMetadata(packageJsonPath);
|
|
|
|
expect(metadata.path).toEqual(packageJsonPath);
|
|
expect(metadata.relativePath).toEqual('package.json');
|
|
expect(metadata.size).toBeGreaterThan(0);
|
|
expect(metadata.mtime).toBeGreaterThan(0);
|
|
expect(metadata.estimatedTokens).toBeGreaterThan(0);
|
|
// Rough estimate: size / 4
|
|
expect(metadata.estimatedTokens).toBeCloseTo(metadata.size / 4, 10);
|
|
});
|
|
|
|
tap.test('LazyFileLoader.getMetadata should cache metadata for same file', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
const packageJsonPath = path.join(testProjectRoot, 'package.json');
|
|
|
|
const metadata1 = await loader.getMetadata(packageJsonPath);
|
|
const metadata2 = await loader.getMetadata(packageJsonPath);
|
|
|
|
// Should return identical metadata from cache
|
|
expect(metadata1.mtime).toEqual(metadata2.mtime);
|
|
expect(metadata1.size).toEqual(metadata2.size);
|
|
expect(metadata1.estimatedTokens).toEqual(metadata2.estimatedTokens);
|
|
});
|
|
|
|
tap.test('LazyFileLoader.scanFiles should scan TypeScript files', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
|
|
const metadata = await loader.scanFiles(['ts/context/types.ts']);
|
|
|
|
expect(metadata.length).toBeGreaterThan(0);
|
|
const typesFile = metadata.find(m => m.relativePath.includes('types.ts'));
|
|
expect(typesFile).toBeDefined();
|
|
expect(typesFile!.size).toBeGreaterThan(0);
|
|
expect(typesFile!.estimatedTokens).toBeGreaterThan(0);
|
|
});
|
|
|
|
tap.test('LazyFileLoader.scanFiles should handle multiple globs', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
|
|
const metadata = await loader.scanFiles([
|
|
'package.json',
|
|
'readme.md'
|
|
]);
|
|
|
|
expect(metadata.length).toBeGreaterThanOrEqual(2);
|
|
const hasPackageJson = metadata.some(m => m.relativePath === 'package.json');
|
|
const hasReadme = metadata.some(m => m.relativePath.toLowerCase() === 'readme.md');
|
|
expect(hasPackageJson).toBe(true);
|
|
expect(hasReadme).toBe(true);
|
|
});
|
|
|
|
tap.test('LazyFileLoader.loadFile should load file with actual token count', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
const packageJsonPath = path.join(testProjectRoot, 'package.json');
|
|
|
|
const tokenizer = (content: string) => Math.ceil(content.length / 4);
|
|
const fileInfo = await loader.loadFile(packageJsonPath, tokenizer);
|
|
|
|
expect(fileInfo.path).toEqual(packageJsonPath);
|
|
expect(fileInfo.contents).toBeDefined();
|
|
expect(fileInfo.contents.length).toBeGreaterThan(0);
|
|
expect(fileInfo.tokenCount).toBeGreaterThan(0);
|
|
expect(fileInfo.relativePath).toEqual('package.json');
|
|
});
|
|
|
|
tap.test('LazyFileLoader.loadFiles should load multiple files in parallel', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
|
|
const metadata: IFileMetadata[] = [
|
|
{
|
|
path: path.join(testProjectRoot, 'package.json'),
|
|
relativePath: 'package.json',
|
|
size: 100,
|
|
mtime: Date.now(),
|
|
estimatedTokens: 25
|
|
},
|
|
{
|
|
path: path.join(testProjectRoot, 'readme.md'),
|
|
relativePath: 'readme.md',
|
|
size: 200,
|
|
mtime: Date.now(),
|
|
estimatedTokens: 50
|
|
}
|
|
];
|
|
|
|
const tokenizer = (content: string) => Math.ceil(content.length / 4);
|
|
const startTime = Date.now();
|
|
const files = await loader.loadFiles(metadata, tokenizer);
|
|
const endTime = Date.now();
|
|
|
|
expect(files.length).toEqual(2);
|
|
expect(files[0].contents).toBeDefined();
|
|
expect(files[1].contents).toBeDefined();
|
|
|
|
// Should be fast (parallel loading)
|
|
expect(endTime - startTime).toBeLessThan(5000); // 5 seconds max
|
|
});
|
|
|
|
tap.test('LazyFileLoader.updateImportanceScores should update cached metadata', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
const packageJsonPath = path.join(testProjectRoot, 'package.json');
|
|
|
|
// Get initial metadata
|
|
await loader.getMetadata(packageJsonPath);
|
|
|
|
// Update importance scores
|
|
const scores = new Map<string, number>();
|
|
scores.set(packageJsonPath, 0.95);
|
|
loader.updateImportanceScores(scores);
|
|
|
|
// Check cached metadata has updated score
|
|
const cached = loader.getCachedMetadata();
|
|
const packageJsonMeta = cached.find(m => m.path === packageJsonPath);
|
|
|
|
expect(packageJsonMeta).toBeDefined();
|
|
expect(packageJsonMeta!.importanceScore).toEqual(0.95);
|
|
});
|
|
|
|
tap.test('LazyFileLoader.getTotalEstimatedTokens should sum all cached metadata tokens', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
|
|
// Scan some files
|
|
await loader.scanFiles(['package.json', 'readme.md']);
|
|
|
|
const totalTokens = loader.getTotalEstimatedTokens();
|
|
|
|
expect(totalTokens).toBeGreaterThan(0);
|
|
});
|
|
|
|
tap.test('LazyFileLoader.clearCache should clear metadata cache', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
|
|
// Scan files to populate cache
|
|
await loader.scanFiles(['package.json']);
|
|
expect(loader.getCachedMetadata().length).toBeGreaterThan(0);
|
|
|
|
// Clear cache
|
|
loader.clearCache();
|
|
|
|
expect(loader.getCachedMetadata().length).toEqual(0);
|
|
});
|
|
|
|
tap.test('LazyFileLoader.getCachedMetadata should return all cached entries', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
|
|
// Scan files
|
|
await loader.scanFiles(['package.json', 'readme.md']);
|
|
|
|
const cached = loader.getCachedMetadata();
|
|
|
|
expect(cached.length).toBeGreaterThanOrEqual(2);
|
|
expect(cached.every(m => m.path && m.size && m.estimatedTokens)).toBe(true);
|
|
});
|
|
|
|
tap.test('LazyFileLoader should handle non-existent files gracefully', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
const nonExistentPath = path.join(testProjectRoot, 'this-file-does-not-exist.ts');
|
|
|
|
try {
|
|
await loader.getMetadata(nonExistentPath);
|
|
expect(false).toBe(true); // Should not reach here
|
|
} catch (error) {
|
|
expect(error).toBeDefined();
|
|
}
|
|
});
|
|
|
|
tap.test('LazyFileLoader.loadFiles should filter out failed file loads', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
|
|
const metadata: IFileMetadata[] = [
|
|
{
|
|
path: path.join(testProjectRoot, 'package.json'),
|
|
relativePath: 'package.json',
|
|
size: 100,
|
|
mtime: Date.now(),
|
|
estimatedTokens: 25
|
|
},
|
|
{
|
|
path: path.join(testProjectRoot, 'non-existent-file.txt'),
|
|
relativePath: 'non-existent-file.txt',
|
|
size: 100,
|
|
mtime: Date.now(),
|
|
estimatedTokens: 25
|
|
}
|
|
];
|
|
|
|
const tokenizer = (content: string) => Math.ceil(content.length / 4);
|
|
const files = await loader.loadFiles(metadata, tokenizer);
|
|
|
|
// Should only include the successfully loaded file
|
|
expect(files.length).toEqual(1);
|
|
expect(files[0].relativePath).toEqual('package.json');
|
|
});
|
|
|
|
tap.test('LazyFileLoader should handle glob patterns for TypeScript source files', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
|
|
const metadata = await loader.scanFiles(['ts/context/*.ts']);
|
|
|
|
expect(metadata.length).toBeGreaterThan(0);
|
|
|
|
// Should find multiple context files
|
|
const hasEnhancedContext = metadata.some(m => m.relativePath.includes('enhanced-context.ts'));
|
|
const hasTypes = metadata.some(m => m.relativePath.includes('types.ts'));
|
|
|
|
expect(hasEnhancedContext).toBe(true);
|
|
expect(hasTypes).toBe(true);
|
|
});
|
|
|
|
tap.test('LazyFileLoader should estimate tokens reasonably accurately', async () => {
|
|
const loader = new LazyFileLoader(testProjectRoot);
|
|
const packageJsonPath = path.join(testProjectRoot, 'package.json');
|
|
|
|
const metadata = await loader.getMetadata(packageJsonPath);
|
|
const tokenizer = (content: string) => Math.ceil(content.length / 4);
|
|
const fileInfo = await loader.loadFile(packageJsonPath, tokenizer);
|
|
|
|
// Estimated tokens should be close to actual (within reasonable range)
|
|
const difference = Math.abs(metadata.estimatedTokens - fileInfo.tokenCount);
|
|
const percentDiff = (difference / fileInfo.tokenCount) * 100;
|
|
|
|
// Should be within 20% accuracy (since it's just an estimate)
|
|
expect(percentDiff).toBeLessThan(20);
|
|
});
|
|
|
|
export default tap.start();
|