superaugment
Version:
Enterprise-grade MCP server with world-class C++ analysis, robust error handling, and production-ready architecture for VS Code Augment
452 lines (442 loc) • 13.7 kB
JavaScript
/**
* SuperAugment Test Helpers
*
* Provides comprehensive testing utilities for unit tests, integration tests,
* and end-to-end testing scenarios. Designed to be reusable and maintainable.
*/
import { jest } from '@jest/globals';
import { ConfigManager } from '../config/ConfigManager.js';
import { FileSystemManager } from '../utils/FileSystemManager.js';
import { SchemaConverter } from '../utils/SchemaConverter.js';
import { ErrorHandler } from '../errors/ErrorHandler.js';
import { ToolManager } from '../tools/ToolManager.js';
import { z } from 'zod';
/**
* Test assertion helpers
*/
export class TestAssertions {
/**
* Assert that an error is of a specific type
*/
static assertErrorType(error, errorClass) {
expect(error).toBeInstanceOf(errorClass);
}
/**
* Assert that a promise rejects with a specific error type
*/
static async assertRejects(promise, errorClass) {
try {
await promise;
throw new Error('Expected promise to reject');
}
catch (error) {
TestAssertions.assertErrorType(error, errorClass);
return error;
}
}
/**
* Assert that an object has specific properties
*/
static assertHasProperties(obj, properties) {
for (const prop of properties) {
expect(obj).toHaveProperty(prop);
}
}
/**
* Assert that a schema conversion is valid
*/
static assertValidJsonSchema(schema) {
expect(schema).toHaveProperty('type');
expect(schema.type).toBe('object');
expect(schema).toHaveProperty('properties');
expect(typeof schema.properties).toBe('object');
}
}
/**
* Mock factory for creating test doubles
*/
export class MockFactory {
/**
* Create a mock ConfigManager
*/
static createMockConfigManager(overrides = {}) {
const mockConfig = {
tools: {
enabled: true,
timeout: 30000,
maxConcurrency: 5,
},
logging: {
level: 'info',
enableConsole: true,
enableFile: false,
},
...overrides,
};
const mock = {
getConfig: jest.fn().mockReturnValue(mockConfig),
get: jest.fn().mockImplementation((...args) => {
const key = args[0];
const keys = key.split('.');
let value = mockConfig;
for (const k of keys) {
value = value?.[k];
}
return value;
}),
set: jest.fn(),
reload: jest.fn(),
validate: jest.fn().mockReturnValue(true),
getHealthStatus: jest.fn().mockReturnValue({
isHealthy: true,
lastValidation: new Date(),
validationErrors: [],
}),
};
return mock;
}
/**
* Create a mock FileSystemManager
*/
static createMockFileSystemManager(mockFiles = {}) {
const mock = {
readFileContent: jest.fn().mockImplementation((...args) => {
const path = args[0];
const content = this.getMockFileContent(mockFiles, path);
if (content === undefined) {
throw new Error(`File not found: ${path}`);
}
return Promise.resolve(content);
}),
writeFileContent: jest.fn(),
fileExists: jest.fn().mockImplementation((...args) => {
const path = args[0];
return Promise.resolve(this.getMockFileContent(mockFiles, path) !== undefined);
}),
findFiles: jest.fn(),
readMultipleFiles: jest.fn(),
getCacheStats: jest.fn().mockReturnValue({
totalEntries: 0,
totalSize: 0,
hitCount: 0,
missCount: 0,
evictionCount: 0,
hitRate: 0,
memoryUsage: 0,
maxMemoryUsage: 256 * 1024 * 1024,
}),
clearCache: jest.fn(),
invalidateFile: jest.fn().mockReturnValue(true),
getFileStats: jest.fn(),
cleanup: jest.fn(),
};
return mock;
}
/**
* Get mock file content from structure
*/
static getMockFileContent(structure, path) {
const parts = path.split('/').filter(p => p);
let current = structure;
for (const part of parts) {
if (typeof current === 'string') {
return undefined;
}
const next = current[part];
if (next === undefined) {
return undefined;
}
current = next;
}
return typeof current === 'string' ? current : undefined;
}
/**
* Create a mock SchemaConverter
*/
static createMockSchemaConverter() {
const mock = {
convertZodToJsonSchema: jest.fn().mockReturnValue({
type: 'object',
properties: {},
additionalProperties: false,
}),
convertZodTypeToProperty: jest.fn().mockReturnValue({
type: 'string',
description: 'Mock property',
}),
getStats: jest.fn().mockReturnValue({
totalProperties: 0,
successfulConversions: 0,
failedConversions: 0,
unsupportedTypes: [],
conversionTime: 0,
}),
clearCache: jest.fn(),
};
return mock;
}
/**
* Create a mock ErrorHandler
*/
static createMockErrorHandler() {
const mock = {
handleError: jest.fn(),
getStats: jest.fn().mockReturnValue({
totalErrors: 0,
errorsByType: {},
errorsBySeverity: {},
retryAttempts: 0,
circuitBreakerTrips: 0,
}),
resetStats: jest.fn(),
isCircuitBreakerOpen: jest.fn().mockReturnValue(false),
};
return mock;
}
}
/**
* Test data generators
*/
export class TestDataGenerator {
/**
* Generate a sample Zod schema for testing
*/
static createSampleZodSchema() {
return z.object({
name: z.string().describe('The name of the item'),
age: z.number().int().min(0).max(150).describe('Age in years'),
email: z.string().email().optional().describe('Email address'),
tags: z.array(z.string()).default([]).describe('List of tags'),
isActive: z.boolean().default(true).describe('Whether the item is active'),
category: z.enum(['A', 'B', 'C']).describe('Category selection'),
});
}
/**
* Generate sample C++ code for testing
*/
static createSampleCppCode() {
return `
#include <iostream>
#include <vector>
#include <string>
class Calculator {
private:
std::vector<double> history;
public:
double add(double a, double b) {
double result = a + b;
history.push_back(result);
return result;
}
double multiply(double a, double b) {
double result = a * b;
history.push_back(result);
return result;
}
void printHistory() const {
for (const auto& value : history) {
std::cout << value << std::endl;
}
}
};
int main() {
Calculator calc;
double sum = calc.add(5.0, 3.0);
double product = calc.multiply(sum, 2.0);
calc.printHistory();
return 0;
}
`.trim();
}
/**
* Generate sample TypeScript code for testing
*/
static createSampleTypeScriptCode() {
return `
interface User {
id: number;
name: string;
email?: string;
}
class UserManager {
private users: User[] = [];
addUser(user: User): void {
this.users.push(user);
}
findUser(id: number): User | undefined {
return this.users.find(u => u.id === id);
}
getAllUsers(): User[] {
return [...this.users];
}
}
export { User, UserManager };
`.trim();
}
/**
* Generate sample configuration for testing
*/
static createSampleConfig() {
return {
tools: {
enabled: true,
timeout: 30000,
maxConcurrency: 5,
analysis: {
enableCpp: true,
enableTypeScript: true,
enablePython: true,
},
},
logging: {
level: 'info',
enableConsole: true,
enableFile: false,
maxFileSize: '10MB',
},
cache: {
enabled: true,
maxMemoryUsage: '256MB',
maxEntries: 10000,
ttl: '30m',
},
};
}
}
/**
* Test environment setup and teardown
*/
export class TestEnvironment {
static originalEnv = {};
/**
* Setup test environment
*/
static setup(config = {}) {
const defaultConfig = {
enableLogging: false,
mockFileSystem: true,
mockNetwork: true,
tempDirectory: '/tmp/superaugment-test',
};
const testConfig = { ...defaultConfig, ...config };
// Store original environment
this.originalEnv = { ...process.env };
// Set test environment variables
process.env['NODE_ENV'] = 'test';
process.env['LOG_LEVEL'] = testConfig.enableLogging ? 'debug' : 'silent';
process.env['TEMP_DIR'] = testConfig.tempDirectory;
// Mock console if logging is disabled
if (!testConfig.enableLogging) {
jest.spyOn(console, 'log').mockImplementation(() => { });
jest.spyOn(console, 'warn').mockImplementation(() => { });
jest.spyOn(console, 'error').mockImplementation(() => { });
jest.spyOn(console, 'info').mockImplementation(() => { });
jest.spyOn(console, 'debug').mockImplementation(() => { });
}
}
/**
* Teardown test environment
*/
static teardown() {
// Restore original environment
process.env = { ...this.originalEnv };
// Restore console
jest.restoreAllMocks();
}
/**
* Create a temporary test directory
*/
static async createTempDir() {
const tempDir = `/tmp/superaugment-test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
// In a real implementation, you would create the directory
// For testing, we'll just return the path
return tempDir;
}
/**
* Clean up temporary test directory
*/
static async cleanupTempDir(_path) {
// In a real implementation, you would remove the directory
// For testing, this is a no-op
}
}
/**
* Performance testing utilities
*/
export class PerformanceTestUtils {
/**
* Measure execution time of a function
*/
static async measureExecutionTime(fn) {
const start = Date.now();
const result = await fn();
const duration = Date.now() - start;
return { result, duration };
}
/**
* Assert that a function executes within a time limit
*/
static async assertExecutionTime(fn, maxDuration) {
const { result, duration } = await this.measureExecutionTime(fn);
expect(duration).toBeLessThanOrEqual(maxDuration);
return result;
}
/**
* Run a function multiple times and get statistics
*/
static async benchmarkFunction(fn, iterations = 10) {
const results = [];
const durations = [];
for (let i = 0; i < iterations; i++) {
const { result, duration } = await this.measureExecutionTime(fn);
results.push(result);
durations.push(duration);
}
const averageDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
const minDuration = Math.min(...durations);
const maxDuration = Math.max(...durations);
return {
results,
durations,
averageDuration,
minDuration,
maxDuration,
};
}
}
/**
* Integration test helpers
*/
export class IntegrationTestHelpers {
/**
* Create a full ToolManager instance for integration testing
*/
static async createTestToolManager(configOverrides = {}) {
const mockConfig = MockFactory.createMockConfigManager(configOverrides);
const toolManager = new ToolManager(mockConfig);
await toolManager.initialize();
return toolManager;
}
/**
* Test MCP protocol compatibility
*/
static async testMcpCompatibility(toolManager) {
const tools = await toolManager.listTools();
// Verify all tools have required MCP properties
for (const tool of tools) {
expect(tool).toHaveProperty('name');
expect(tool).toHaveProperty('description');
expect(tool).toHaveProperty('inputSchema');
TestAssertions.assertValidJsonSchema(tool.inputSchema);
}
}
/**
* Test tool execution with sample data
*/
static async testToolExecution(toolManager, toolName, args) {
const result = await toolManager.callTool(toolName, args);
expect(result).toBeDefined();
return result;
}
}
//# sourceMappingURL=TestHelpers.js.map