/**
* Direct Fixture Test Environment
*
* Provides test environment that uses fixtures directly (no copying)
* while creating cache directories in temporary locations.
*
* Benefits:
* - 20-100x faster test setup (no file copying)
* - Tests run against real fixture files
* - Isolated cache directories per test
* - Parallel test execution safe
*/
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import { existsSync } from 'fs';
// Core infrastructure imports
import { DependencyContainer } from '../../src/di/container.js';
import { setupDependencyInjection } from '../../src/di/setup.js';
import { resolveConfig } from '../../src/config/resolver.js';
import type {
IVectorSearchService,
IFileParsingService,
IEmbeddingService,
IFileSystemService,
ILoggingService,
ICacheService
} from '../../src/di/interfaces.js';
import { SERVICE_TOKENS } from '../../src/di/interfaces.js';
/**
* Direct fixture test environment setup
*/
export interface DirectFixtureEnvironment {
/** Path to the actual test fixtures (not copied) */
knowledgeBasePath: string;
/** Path to temporary cache directory */
cacheDir: string;
/** Temporary directory for this test (cache only) */
tempDir: string;
/** DI container with real services */
container: DependencyContainer;
/** Real services (no mocks) */
services: {
vectorSearch: IVectorSearchService;
fileParsing: IFileParsingService;
embedding: IEmbeddingService;
fileSystem: IFileSystemService;
logging: ILoggingService;
cache: ICacheService;
};
/** Cleanup function to call after test */
cleanup: () => Promise<void>;
}
/**
* Sets up a test environment using fixtures directly
* Only creates temporary directory for cache, not for data
*/
export async function setupDirectFixtureEnvironment(
testName: string,
options: {
fixture?: string; // Specific fixture subdirectory to use
logLevel?: string;
} = {}
): Promise<DirectFixtureEnvironment> {
console.log(`๐ง Setting up direct fixture environment for: ${testName}`);
// Use fixtures directly - no copying!
const fixtureBase = path.join(process.cwd(), 'tests', 'fixtures');
const knowledgeBasePath = options.fixture
? path.join(fixtureBase, options.fixture)
: path.join(fixtureBase, 'test-knowledge-base');
if (!existsSync(knowledgeBasePath)) {
throw new Error(`Fixture path does not exist: ${knowledgeBasePath}`);
}
console.log(`๐ Using fixture directly: ${knowledgeBasePath}`);
// Create temporary directory ONLY for cache
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), `cache-${testName}-`));
const cacheDir = path.join(tempDir, '.folder-mcp');
console.log(`๐พ Cache directory: ${cacheDir}`);
// Create configuration that uses fixtures for reading, temp for cache
// NOTE: This will need to be updated when we add cacheDir support to services
const config = await resolveConfig(knowledgeBasePath);
// Override cache paths in config
// TODO: When services support cacheDir parameter, pass it here
const customConfig = {
...config,
// Future: cacheDir option
// cacheDir: cacheDir,
};
// Set up DI container with real services
const container = setupDependencyInjection({
config: customConfig,
folderPath: knowledgeBasePath,
logLevel: (options.logLevel || 'error') as 'error' | 'debug' | 'info' | 'warn'
});
// TODO: When SQLiteVecStorage supports custom cache location,
// we'll need to configure it here to use tempDir instead of folderPath/.folder-mcp
// Get real services from container
const services = {
vectorSearch: container.resolve<IVectorSearchService>(SERVICE_TOKENS.VECTOR_SEARCH),
fileParsing: container.resolve<IFileParsingService>(SERVICE_TOKENS.FILE_PARSING),
embedding: container.resolve<IEmbeddingService>(SERVICE_TOKENS.EMBEDDING),
fileSystem: container.resolve<IFileSystemService>(SERVICE_TOKENS.FILE_SYSTEM),
logging: container.resolve<ILoggingService>(SERVICE_TOKENS.LOGGING),
cache: container.resolve<ICacheService>(SERVICE_TOKENS.CACHE)
};
console.log(`โ
Services initialized with direct fixture access`);
// Return test environment
return {
knowledgeBasePath,
cacheDir,
tempDir,
container,
services,
cleanup: async () => {
console.log(`๐งน Cleaning up cache for: ${testName}`);
// Only cleanup temporary cache directory
try {
if (existsSync(tempDir)) {
await fs.rm(tempDir, { recursive: true, force: true });
}
} catch (error) {
console.warn(`Failed to cleanup ${tempDir}:`, error);
}
console.log(`โ
Cleanup completed for: ${testName}`);
}
};
}
/**
* Shared environment for multiple tests in the same suite
* Useful for test files that run many related tests
*/
export class SharedDirectFixtureEnvironment {
private static instances = new Map<string, DirectFixtureEnvironment>();
/**
* Get or create a shared environment for a test suite
*/
static async setup(
suiteName: string,
options?: Parameters<typeof setupDirectFixtureEnvironment>[1]
): Promise<DirectFixtureEnvironment> {
const existing = this.instances.get(suiteName);
if (existing) {
return existing;
}
const env = await setupDirectFixtureEnvironment(suiteName, options);
this.instances.set(suiteName, env);
return env;
}
/**
* Cleanup a specific suite's environment
*/
static async cleanup(suiteName: string): Promise<void> {
const env = this.instances.get(suiteName);
if (env) {
await env.cleanup();
this.instances.delete(suiteName);
}
}
/**
* Cleanup all shared environments
*/
static async cleanupAll(): Promise<void> {
for (const [, env] of this.instances) {
await env.cleanup();
}
this.instances.clear();
}
}
/**
* Helper to temporarily override cache location for a specific service
* This is a workaround until services natively support cacheDir parameter
*/
export function overrideCacheLocation(_service: any, _cacheDir: string): void {
// This will be implemented based on specific service requirements
// For now, it's a placeholder for future implementation
// Example for SQLiteVecStorage:
// if (service.constructor.name === 'SQLiteVecStorage') {
// service._cacheDir = cacheDir;
// service._dbPath = path.join(cacheDir, 'embeddings.db');
// }
console.warn('Cache location override not yet implemented. Services will use default locations.');
}