Files
proffesor-for-testing__agen…/tests/unit/logging/console-logger.test.ts
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
Remove the nested v3/ subdirectory by moving all active source code,
tests, configs, and assets directly to the project root. Archive v2/
(dead code, 847MB) to v2-archive.tar.gz.

Key changes:
- Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/
- Move v3/tsconfig.json, v3/vitest.config.ts to root
- Merge v3/package.json into root package.json (ESM, scripts, deps, exports)
- Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/
- Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes
- Fix all init installer path resolution (3-level-up → 2-level-up)
- Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe"
- Update settings.json ADR/DDD directories to docs/implementation/
- Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks)
- Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces
- Update 15 guidance shards: v3/src/ → src/ path references
- Fix sync interfaces: remove v3/ and v2/ path references
- Fix test path calculations (process.cwd() + '..' no longer needed)
- Fix flaky perf test threshold (500ms → 1000ms for CI environments)
- Fix complexity-analyzer test to match ADR-051 keyword-based eligibility

Build passes, 17,901 tests pass, CLI and MCP bundles verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:35:41 +00:00

416 lines
14 KiB
TypeScript

/**
* ConsoleLogger Tests
* Milestone 2.7: Structured Logging Facade
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
ConsoleLogger,
NullLogger,
DEFAULT_CONSOLE_LOGGER_CONFIG,
} from '../../../src/logging/console-logger.js';
import { LogLevel } from '../../../src/logging/logger.js';
describe('ConsoleLogger', () => {
let consoleDebugSpy: ReturnType<typeof vi.spyOn>;
let consoleInfoSpy: ReturnType<typeof vi.spyOn>;
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
consoleDebugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});
consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('level filtering', () => {
it('should log all levels when set to DEBUG', () => {
const logger = new ConsoleLogger('test', LogLevel.DEBUG);
logger.debug('debug message');
logger.info('info message');
logger.warn('warn message');
logger.error('error message');
expect(consoleDebugSpy).toHaveBeenCalledTimes(1);
expect(consoleInfoSpy).toHaveBeenCalledTimes(1);
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
});
it('should filter DEBUG when level is INFO', () => {
const logger = new ConsoleLogger('test', LogLevel.INFO);
logger.debug('debug message');
logger.info('info message');
logger.warn('warn message');
logger.error('error message');
expect(consoleDebugSpy).not.toHaveBeenCalled();
expect(consoleInfoSpy).toHaveBeenCalledTimes(1);
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
});
it('should filter DEBUG and INFO when level is WARN', () => {
const logger = new ConsoleLogger('test', LogLevel.WARN);
logger.debug('debug message');
logger.info('info message');
logger.warn('warn message');
logger.error('error message');
expect(consoleDebugSpy).not.toHaveBeenCalled();
expect(consoleInfoSpy).not.toHaveBeenCalled();
expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
});
it('should only log ERROR when level is ERROR', () => {
const logger = new ConsoleLogger('test', LogLevel.ERROR);
logger.debug('debug message');
logger.info('info message');
logger.warn('warn message');
logger.error('error message');
expect(consoleDebugSpy).not.toHaveBeenCalled();
expect(consoleInfoSpy).not.toHaveBeenCalled();
expect(consoleWarnSpy).not.toHaveBeenCalled();
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
});
it('should log nothing when level is SILENT', () => {
const logger = new ConsoleLogger('test', LogLevel.SILENT);
logger.debug('debug message');
logger.info('info message');
logger.warn('warn message');
logger.error('error message');
expect(consoleDebugSpy).not.toHaveBeenCalled();
expect(consoleInfoSpy).not.toHaveBeenCalled();
expect(consoleWarnSpy).not.toHaveBeenCalled();
expect(consoleErrorSpy).not.toHaveBeenCalled();
});
});
describe('isEnabled', () => {
it('should return true for enabled levels', () => {
const logger = new ConsoleLogger('test', LogLevel.INFO);
expect(logger.isEnabled(LogLevel.INFO)).toBe(true);
expect(logger.isEnabled(LogLevel.WARN)).toBe(true);
expect(logger.isEnabled(LogLevel.ERROR)).toBe(true);
});
it('should return false for disabled levels', () => {
const logger = new ConsoleLogger('test', LogLevel.INFO);
expect(logger.isEnabled(LogLevel.DEBUG)).toBe(false);
});
it('should return false for all levels when SILENT', () => {
const logger = new ConsoleLogger('test', LogLevel.SILENT);
expect(logger.isEnabled(LogLevel.DEBUG)).toBe(false);
expect(logger.isEnabled(LogLevel.INFO)).toBe(false);
expect(logger.isEnabled(LogLevel.WARN)).toBe(false);
expect(logger.isEnabled(LogLevel.ERROR)).toBe(false);
});
});
describe('domain prefix', () => {
it('should include domain in log output', () => {
const logger = new ConsoleLogger('queen-coordinator', LogLevel.INFO, {
includeTimestamp: false,
includeLevel: false,
});
logger.info('test message');
expect(consoleInfoSpy).toHaveBeenCalledWith(
expect.stringContaining('[queen-coordinator]')
);
});
it('should return domain from getDomain', () => {
const logger = new ConsoleLogger('my-domain', LogLevel.INFO);
expect(logger.getDomain()).toBe('my-domain');
});
});
describe('context formatting', () => {
it('should include context in log output', () => {
const logger = new ConsoleLogger('test', LogLevel.INFO, {
includeTimestamp: false,
includeLevel: false,
});
logger.info('task submitted', { taskId: 'task-123', priority: 'high' });
expect(consoleInfoSpy).toHaveBeenCalledWith(
expect.stringContaining('"taskId":"task-123"')
);
expect(consoleInfoSpy).toHaveBeenCalledWith(
expect.stringContaining('"priority":"high"')
);
});
it('should not include context when not provided', () => {
const logger = new ConsoleLogger('test', LogLevel.INFO, {
includeTimestamp: false,
includeLevel: false,
});
logger.info('simple message');
const output = consoleInfoSpy.mock.calls[0][0];
expect(output).toBe('[test] simple message');
});
it('should handle complex context objects', () => {
const logger = new ConsoleLogger('test', LogLevel.DEBUG, {
includeTimestamp: false,
includeLevel: false,
});
logger.debug('complex', {
nested: { deep: { value: 42 } },
array: [1, 2, 3],
null: null,
});
expect(consoleDebugSpy).toHaveBeenCalledWith(
expect.stringContaining('"nested"')
);
});
});
describe('error handling', () => {
it('should include error details in output', () => {
const logger = new ConsoleLogger('test', LogLevel.ERROR, {
includeTimestamp: false,
includeLevel: false,
});
const error = new Error('Something went wrong');
logger.error('task failed', error, { taskId: 'task-123' });
const output = consoleErrorSpy.mock.calls[0][0];
expect(output).toContain('[test]');
expect(output).toContain('task failed');
expect(output).toContain('"errorName":"Error"');
expect(output).toContain('"errorMessage":"Something went wrong"');
expect(output).toContain('"taskId":"task-123"');
});
it('should include stack trace in error output', () => {
const logger = new ConsoleLogger('test', LogLevel.ERROR, {
includeTimestamp: false,
includeLevel: false,
});
const error = new Error('Stack trace test');
logger.error('error with stack', error);
const output = consoleErrorSpy.mock.calls[0][0];
expect(output).toContain('"stack"');
});
it('should handle error without Error object', () => {
const logger = new ConsoleLogger('test', LogLevel.ERROR, {
includeTimestamp: false,
includeLevel: false,
});
logger.error('error without object');
expect(consoleErrorSpy).toHaveBeenCalled();
const output = consoleErrorSpy.mock.calls[0][0];
expect(output).toContain('error without object');
});
});
describe('timestamp formatting', () => {
it('should include timestamp when configured', () => {
const logger = new ConsoleLogger('test', LogLevel.INFO, {
includeTimestamp: true,
includeLevel: false,
timestampFormat: 'short',
});
logger.info('message');
const output = consoleInfoSpy.mock.calls[0][0];
// Short format: [HH:MM:SS.mmm]
expect(output).toMatch(/\[\d{2}:\d{2}:\d{2}\.\d{3}\]/);
});
it('should use ISO format when configured', () => {
const logger = new ConsoleLogger('test', LogLevel.INFO, {
includeTimestamp: true,
includeLevel: false,
timestampFormat: 'iso',
});
logger.info('message');
const output = consoleInfoSpy.mock.calls[0][0];
// ISO format: [2024-01-01T00:00:00.000Z]
expect(output).toMatch(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\]/);
});
it('should exclude timestamp when disabled', () => {
const logger = new ConsoleLogger('test', LogLevel.INFO, {
includeTimestamp: false,
includeLevel: false,
});
logger.info('message');
const output = consoleInfoSpy.mock.calls[0][0];
expect(output).toBe('[test] message');
});
});
describe('level formatting', () => {
it('should include level name when configured', () => {
const logger = new ConsoleLogger('test', LogLevel.DEBUG, {
includeTimestamp: false,
includeLevel: true,
});
logger.debug('debug');
logger.info('info');
logger.warn('warn');
logger.error('error');
expect(consoleDebugSpy.mock.calls[0][0]).toContain('[DEBUG]');
expect(consoleInfoSpy.mock.calls[0][0]).toContain('[INFO ]');
expect(consoleWarnSpy.mock.calls[0][0]).toContain('[WARN ]');
expect(consoleErrorSpy.mock.calls[0][0]).toContain('[ERROR]');
});
});
describe('child logger', () => {
it('should create child with inherited context', () => {
const logger = new ConsoleLogger('test', LogLevel.INFO, {
includeTimestamp: false,
includeLevel: false,
});
const child = logger.child({ requestId: 'req-123' });
child.info('child message');
const output = consoleInfoSpy.mock.calls[0][0];
expect(output).toContain('"requestId":"req-123"');
});
it('should merge child context with message context', () => {
const logger = new ConsoleLogger('test', LogLevel.INFO, {
includeTimestamp: false,
includeLevel: false,
});
const child = logger.child({ requestId: 'req-123' });
child.info('message', { taskId: 'task-456' });
const output = consoleInfoSpy.mock.calls[0][0];
expect(output).toContain('"requestId":"req-123"');
expect(output).toContain('"taskId":"task-456"');
});
it('should override inherited context with message context', () => {
const logger = new ConsoleLogger('test', LogLevel.INFO, {
includeTimestamp: false,
includeLevel: false,
});
const child = logger.child({ value: 'inherited' });
child.info('message', { value: 'overridden' });
const output = consoleInfoSpy.mock.calls[0][0];
expect(output).toContain('"value":"overridden"');
expect(output).not.toContain('"value":"inherited"');
});
it('should inherit domain from parent', () => {
const logger = new ConsoleLogger('parent-domain', LogLevel.INFO);
const child = logger.child({});
expect(child.getDomain()).toBe('parent-domain');
});
});
describe('default configuration', () => {
it('should use INFO level by default', () => {
const logger = new ConsoleLogger('test');
expect(logger.isEnabled(LogLevel.DEBUG)).toBe(false);
expect(logger.isEnabled(LogLevel.INFO)).toBe(true);
});
it('should have sensible default config', () => {
expect(DEFAULT_CONSOLE_LOGGER_CONFIG.includeTimestamp).toBe(true);
expect(DEFAULT_CONSOLE_LOGGER_CONFIG.includeLevel).toBe(true);
expect(DEFAULT_CONSOLE_LOGGER_CONFIG.timestampFormat).toBe('short');
expect(DEFAULT_CONSOLE_LOGGER_CONFIG.prettyPrint).toBe(false);
});
});
});
describe('NullLogger', () => {
let consoleInfoSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
});
afterEach(() => {
vi.restoreAllMocks();
});
it('should not output anything', () => {
const logger = new NullLogger();
logger.debug('debug');
logger.info('info');
logger.warn('warn');
logger.error('error', new Error('test'));
expect(consoleInfoSpy).not.toHaveBeenCalled();
});
it('should return false for all isEnabled checks', () => {
const logger = new NullLogger();
expect(logger.isEnabled(LogLevel.DEBUG)).toBe(false);
expect(logger.isEnabled(LogLevel.INFO)).toBe(false);
expect(logger.isEnabled(LogLevel.WARN)).toBe(false);
expect(logger.isEnabled(LogLevel.ERROR)).toBe(false);
});
it('should return domain from getDomain', () => {
const logger = new NullLogger('my-domain');
expect(logger.getDomain()).toBe('my-domain');
});
it('should return itself from child', () => {
const logger = new NullLogger();
const child = logger.child({ context: 'value' });
expect(child).toBe(logger);
});
it('should use default domain when not specified', () => {
const logger = new NullLogger();
expect(logger.getDomain()).toBe('null');
});
});