meld
Version:
Meld: A template language for LLM prompts
194 lines (178 loc) • 4.82 kB
text/typescript
/**
* Basic Error Test Utilities (Phase 1)
*
* Simple utilities for testing error handling in the Meld language interpreter.
* This is a minimal implementation for the initial release.
*/
import { MeldError } from '@core/errors/MeldError.js';
import { ErrorSeverity } from '@core/errors/ErrorSeverity.js';
/**
* Options for creating a test environment
*/
export interface ErrorTestOptions {
strict?: boolean;
logger?: any;
}
/**
* Create options for strict mode testing
*/
export function createStrictModeOptions(options: Partial<ErrorTestOptions> = {}): ErrorTestOptions {
return {
strict: true,
...options
};
}
/**
* Create options for permissive mode testing
*/
export function createPermissiveModeOptions(options: Partial<ErrorTestOptions> = {}): ErrorTestOptions {
return {
strict: false,
...options
};
}
/**
* Verify that an error has the expected severity
*/
export function expectErrorSeverity(error: MeldError, expectedSeverity: ErrorSeverity): void {
if (error.severity !== expectedSeverity) {
throw new Error(
`Error severity mismatch. Expected: ${ErrorSeverity[expectedSeverity]}, ` +
`Actual: ${ErrorSeverity[error.severity]}`
);
}
}
/**
* Verify that a function throws an error with the expected type and severity
*/
export async function expectThrowsWithSeverity<T extends Error>(
fn: () => Promise<any> | any,
errorType: new (...args: any[]) => T,
expectedSeverity: ErrorSeverity
): Promise<void> {
try {
await fn();
throw new Error(`Expected function to throw ${errorType.name} but it did not throw`);
} catch (error) {
if (!(error instanceof errorType)) {
throw new Error(
`Expected error to be instance of ${errorType.name} but got ${error.constructor.name}`
);
}
if (error instanceof MeldError) {
expectErrorSeverity(error, expectedSeverity);
} else {
throw new Error(
`Expected error to be instance of MeldError but got ${error.constructor.name}`
);
}
}
}
/**
* Create a mock logger for testing
*/
export function createMockLogger() {
return {
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
reset() {
this.error.mockReset();
this.warn.mockReset();
this.info.mockReset();
this.debug.mockReset();
}
};
}
/**
* Mock console for CLI testing
*/
export function mockConsole() {
const originalConsole = {
log: console.log,
error: console.error,
warn: console.warn,
info: console.info
};
const mockFns = {
log: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
info: jest.fn()
};
// Replace console methods with mocks
console.log = mockFns.log;
console.error = mockFns.error;
console.warn = mockFns.warn;
console.info = mockFns.info;
// Return mock functions and a restore function
return {
...mockFns,
restore() {
console.log = originalConsole.log;
console.error = originalConsole.error;
console.warn = originalConsole.warn;
console.info = originalConsole.info;
}
};
}
/**
* Mock process.exit for CLI testing
*/
export function mockProcessExit() {
const originalExit = process.exit;
const mockExit = jest.fn();
// @ts-ignore - We're intentionally mocking this
process.exit = mockExit;
// Return the mock function and a restore function
return Object.assign(mockExit, {
restore() {
process.exit = originalExit;
}
});
}
/**
* Example usage:
*
* ```typescript
* // Test a fatal error
* it('should throw fatal error for invalid syntax', async () => {
* await expectThrowsWithSeverity(
* () => service.process('@invalid', createStrictModeOptions()),
* MeldSyntaxError,
* ErrorSeverity.Fatal
* );
* });
*
* // Test a recoverable error
* it('should handle recoverable errors differently in strict and permissive modes', async () => {
* // Strict mode
* await expect(
* service.process('@text x = #{undefined}', createStrictModeOptions())
* ).rejects.toThrow(MeldResolutionError);
*
* // Permissive mode
* const result = await service.process('@text x = #{undefined}', createPermissiveModeOptions());
* expect(result).toBeDefined();
* });
*
* // Test CLI error handling
* it('should display error message and exit with code 1', async () => {
* const consoleMock = mockConsole();
* const exitMock = mockProcessExit();
*
* try {
* await cli.run(['--strict', 'non-existent.meld']);
*
* expect(consoleMock.error).toHaveBeenCalledWith(
* expect.stringContaining('File not found')
* );
* expect(exitMock).toHaveBeenCalledWith(1);
* } finally {
* consoleMock.restore();
* exitMock.restore();
* }
* });
* ```
*/