meld
Version:
Meld: A template language for LLM prompts
317 lines (257 loc) • 11.6 kB
text/typescript
/**
* Tests for TestOutputFilterService
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { TestOutputFilterService } from './TestOutputFilterService';
import { TestOutputVerbosity } from '../StateVisualizationService/TestVisualizationManager';
import { LogLevel, TestOutputOptions } from './ITestOutputFilterService';
describe('TestOutputFilterService', () => {
let originalEnv: NodeJS.ProcessEnv;
beforeEach(() => {
// Save original environment
originalEnv = { ...process.env };
// Reset environment variables that might affect tests
delete process.env.TEST_OUTPUT_VERBOSITY;
delete process.env.TEST_VERBOSITY;
delete process.env.TEST_LOG_LEVEL;
delete process.env.TEST_OUTPUT_TO_FILES;
delete process.env.TEST_OUTPUT_DIR;
// Mock logger
vi.mock('@core/utils/logger', () => ({
serviceLogger: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn()
}
}));
});
afterEach(() => {
// Restore original environment
process.env = originalEnv;
// Clear mocks
vi.clearAllMocks();
});
describe('initialization', () => {
it('should initialize with default verbosity', () => {
const service = new TestOutputFilterService();
expect(service.getVerbosity()).toBe(TestOutputVerbosity.Standard);
});
it('should initialize with environment verbosity', () => {
process.env.TEST_OUTPUT_VERBOSITY = 'minimal';
const service = new TestOutputFilterService();
expect(service.getVerbosity()).toBe(TestOutputVerbosity.Minimal);
});
it('should initialize with provided configuration', () => {
const config: TestOutputOptions = {
verbosity: TestOutputVerbosity.Verbose,
maxDepth: 5,
outputToFiles: true
};
const service = new TestOutputFilterService(config);
expect(service.getVerbosity()).toBe(TestOutputVerbosity.Verbose);
// Test depth by checking if deeper objects are filtered
const deepObject = { a: { b: { c: { d: { e: 'value' } } } } };
const filtered = service.filterStateOutput(deepObject);
// With maxDepth 5, we should still see the full structure
expect(filtered).toHaveProperty('a.b.c.d.e');
});
});
describe('verbosity control', () => {
it('should set default verbosity', () => {
const service = new TestOutputFilterService();
service.setDefaultVerbosity(TestOutputVerbosity.Verbose);
expect(service.getVerbosity()).toBe(TestOutputVerbosity.Verbose);
// Reset should revert to default
service.configureTestOutput({ verbosity: TestOutputVerbosity.Minimal });
expect(service.getVerbosity()).toBe(TestOutputVerbosity.Minimal);
service.reset();
expect(service.getVerbosity()).toBe(TestOutputVerbosity.Verbose);
});
it('should configure test-specific verbosity', () => {
const service = new TestOutputFilterService();
// Set test-specific config
service.configureTestOutput({ verbosity: TestOutputVerbosity.Debug });
expect(service.getVerbosity()).toBe(TestOutputVerbosity.Debug);
// Reset back to default
service.reset();
expect(service.getVerbosity()).toBe(TestOutputVerbosity.Standard);
});
});
describe('operation filtering', () => {
it('should always log errors regardless of verbosity', () => {
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Minimal
});
expect(service.shouldLogOperation('anyOperation', LogLevel.Error)).toBe(true);
});
it('should filter operations based on verbosity', () => {
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Standard
});
// Standard verbosity includes Info but not Debug
expect(service.shouldLogOperation('operation1', LogLevel.Info)).toBe(true);
expect(service.shouldLogOperation('operation2', LogLevel.Debug)).toBe(false);
// Change to verbose - should now include Debug
service.configureTestOutput({ verbosity: TestOutputVerbosity.Verbose });
expect(service.shouldLogOperation('operation2', LogLevel.Debug)).toBe(true);
});
it('should respect operation inclusion list', () => {
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Minimal,
includeOperations: ['specialOperation']
});
// Normally this would be filtered in minimal mode
expect(service.shouldLogOperation('specialOperation', LogLevel.Info)).toBe(true);
expect(service.shouldLogOperation('otherOperation', LogLevel.Info)).toBe(false);
});
it('should respect operation exclusion list', () => {
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Verbose,
excludeOperations: ['noisyOperation']
});
// Should be filtered out despite verbose mode
expect(service.shouldLogOperation('noisyOperation', LogLevel.Debug)).toBe(false);
expect(service.shouldLogOperation('otherOperation', LogLevel.Debug)).toBe(true);
});
it('should apply default exclusions based on verbosity', () => {
// Standard mode should exclude detailed operations
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Standard
});
expect(service.shouldLogOperation('resolveVariable', LogLevel.Info)).toBe(false);
expect(service.shouldLogOperation('validateDirective', LogLevel.Info)).toBe(false);
// Debug mode should include all operations
service.configureTestOutput({ verbosity: TestOutputVerbosity.Debug });
expect(service.shouldLogOperation('resolveVariable', LogLevel.Info)).toBe(true);
expect(service.shouldLogOperation('validateDirective', LogLevel.Info)).toBe(true);
});
});
describe('state data filtering', () => {
it('should filter state data based on verbosity', () => {
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Minimal
});
const stateData = { id: '123', nodes: [], variables: {} };
// In minimal mode, info-level data should be filtered out
expect(service.filterStateOutput(stateData, LogLevel.Info)).toBeNull();
// Error level data should still be returned
expect(service.filterStateOutput(stateData, LogLevel.Error)).toEqual(stateData);
});
it('should limit object nesting depth', () => {
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Standard,
maxDepth: 2
});
const deepObject = {
level1: {
level2: {
level3: {
level4: 'deep value'
}
}
}
};
const filtered = service.filterStateOutput(deepObject);
// level3 should be replaced with [Object]
expect(filtered).toHaveProperty('level1.level2');
// With our implementation, objects at the max depth are replaced with '[Object]'
expect(filtered.level1.level2).toEqual({ level3: '[Object]' });
});
it('should filter state fields based on include/exclude lists', () => {
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Standard,
includeStateFields: ['id', 'name']
});
const stateData = {
id: '123',
name: 'Test State',
nodes: [],
variables: {},
metadata: {}
};
const filtered = service.filterStateOutput(stateData);
// Only included fields should be present
expect(filtered).toHaveProperty('id');
expect(filtered).toHaveProperty('name');
expect(filtered).not.toHaveProperty('nodes');
expect(filtered).not.toHaveProperty('variables');
expect(filtered).not.toHaveProperty('metadata');
// Now test with exclude list
service.configureTestOutput({
verbosity: TestOutputVerbosity.Standard,
includeStateFields: [],
excludeStateFields: ['metadata', 'nodes']
});
const filteredWithExclude = service.filterStateOutput(stateData);
// Excluded fields should be removed
expect(filteredWithExclude).toHaveProperty('id');
expect(filteredWithExclude).toHaveProperty('name');
expect(filteredWithExclude).toHaveProperty('variables');
expect(filteredWithExclude).not.toHaveProperty('nodes');
expect(filteredWithExclude).not.toHaveProperty('metadata');
});
it('should handle arrays correctly', () => {
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Standard,
maxDepth: 1
});
const arrayData = [
{ id: '1', deep: { value: 'too deep' } },
{ id: '2', deep: { value: 'too deep' } }
];
const filtered = service.filterStateOutput(arrayData);
// Should keep array structure but limit depth
expect(Array.isArray(filtered)).toBe(true);
expect(filtered).toHaveLength(2);
expect(filtered[0]).toHaveProperty('id');
// After filtering: deep objects are marked with [Object]
expect(filtered[0].deep).toBe('[Object]');
});
});
describe('state visualization control', () => {
it('should control state visualization based on verbosity', () => {
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Minimal
});
// Minimal mode should not visualize states
expect(service.shouldVisualizeState('state1')).toBe(false);
// Standard mode should visualize states
service.configureTestOutput({ verbosity: TestOutputVerbosity.Standard });
expect(service.shouldVisualizeState('state1')).toBe(true);
});
it('should always visualize specific states', () => {
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Minimal,
alwaysVisualizeStates: ['importantState']
});
// Should visualize even in minimal mode
expect(service.shouldVisualizeState('importantState')).toBe(true);
expect(service.shouldVisualizeState('otherState')).toBe(false);
});
});
describe('reset functionality', () => {
it('should reset configuration to defaults', () => {
const service = new TestOutputFilterService({
verbosity: TestOutputVerbosity.Standard
});
// Apply custom configuration
service.configureTestOutput({
verbosity: TestOutputVerbosity.Debug,
includeOperations: ['special'],
excludeStateFields: ['private'],
alwaysVisualizeStates: ['important']
});
// Verify configuration is applied
expect(service.getVerbosity()).toBe(TestOutputVerbosity.Debug);
expect(service.shouldLogOperation('special')).toBe(true);
expect(service.shouldVisualizeState('important')).toBe(true);
// Reset configuration
service.reset();
// Should be back to defaults
expect(service.getVerbosity()).toBe(TestOutputVerbosity.Standard);
expect(service.shouldLogOperation('special', LogLevel.Debug)).toBe(false);
expect(service.shouldVisualizeState('important')).toBe(true); // Standard mode visualizes all states
});
});
});