/* * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ import { describe, expect, test, vi } from 'vitest'; import type { LogRecord } from '@/logging/types.js'; type Formatter = ((value: unknown) => string) & { white?: Formatter }; const chalkMock = vi.hoisted(() => { const format = (name: string): Formatter => (value: unknown) => `<${name}>${String(value)}`; const bgRed = format('bgRed'); bgRed.white = format('bgRed.white'); const bgGreen = format('bgGreen'); bgGreen.white = format('bgGreen.white'); return { red: format('red'), yellow: format('yellow'), green: format('green'), gray: format('gray'), blue: format('blue'), white: format('white'), bold: format('bold'), bgRed, bgGreen, rgb: (red: number, green: number, blue: number) => format(`rgb:${red},${green},${blue}`), }; }); vi.mock('chalk', () => ({ default: chalkMock, })); import { PrettyConsoleBackend } from '@/logging/PrettyConsoleBackend.js'; /** 見やすい形式のテストで使う共通のログを作成します。 */ function createRecord(overrides: Partial = {}): LogRecord { return { level: 'info', message: 'message', context: [{ name: 'root' }], timestamp: '2025-01-02T03:04:05.678Z', loggerName: 'root', processId: 1234, isPrimary: true, workerId: null, ...overrides, }; } describe('PrettyConsoleBackend', () => { test.each([ { level: 'error', label: 'ERR ', message: 'message' }, { level: 'warn', label: 'WARN', message: 'message' }, { level: 'debug', label: 'VERB', message: 'message' }, { level: 'info', label: 'INFO', message: 'message' }, ] as const)('formats the $level label and message', ({ level, label, message }) => { const output = vi.fn(); const backend = new PrettyConsoleBackend({ output, withLogTime: () => false }); backend.write(createRecord({ level })); expect(output).toHaveBeenCalledWith(`${label} *\t[root]\t${message}`); }); test('formats legacy success as DONE while retaining the info severity', () => { const output = vi.fn(); const backend = new PrettyConsoleBackend({ output, withLogTime: () => false }); backend.write(createRecord({ level: 'info', compatibility: { legacyLevel: 'success' }, })); expect(output).toHaveBeenCalledWith('DONE *\t[root]\tmessage'); }); test('formats contexts from root to leaf using their configured colors', () => { const output = vi.fn(); const backend = new PrettyConsoleBackend({ output, withLogTime: () => false }); backend.write(createRecord({ context: [ { name: 'root', color: 'red' }, { name: 'child' }, { name: 'leaf', color: 'blue' }, ], })); expect(output).toHaveBeenCalledWith('INFO *\t[root child leaf]\tmessage'); }); test('prefixes the time derived from the record timestamp when enabled', () => { const output = vi.fn(); const backend = new PrettyConsoleBackend({ output, withLogTime: () => true }); backend.write(createRecord()); expect(output).toHaveBeenCalledWith('03:04:05 INFO *\t[root]\tmessage'); }); test('uses the worker id for a worker process', () => { const output = vi.fn(); const backend = new PrettyConsoleBackend({ output, withLogTime: () => false }); backend.write(createRecord({ isPrimary: false, workerId: 7 })); expect(output).toHaveBeenCalledWith('INFO 7\t[root]\tmessage'); }); test('bolds an important record and passes data as the second output argument', () => { const output = vi.fn(); const data = { detail: 'value' }; const backend = new PrettyConsoleBackend({ output, withLogTime: () => false }); backend.write(createRecord({ level: 'error', compatibility: { important: true, data }, })); expect(output).toHaveBeenCalledWith( 'ERR *\t[root]\tmessage', data, ); }); test('treats fatal records as important errors', () => { const output = vi.fn(); const backend = new PrettyConsoleBackend({ output, withLogTime: () => false }); backend.write(createRecord({ level: 'fatal' })); expect(output).toHaveBeenCalledWith('ERR *\t[root]\tmessage'); }); test('omits null data from output arguments', () => { const output = vi.fn(); const backend = new PrettyConsoleBackend({ output, withLogTime: () => false }); backend.write(createRecord({ compatibility: { data: null } })); expect(output).toHaveBeenCalledTimes(1); expect(output.mock.calls[0]).toHaveLength(1); }); test('passes structured event details as normalized output data', () => { const output = vi.fn(); const backend = new PrettyConsoleBackend({ output, withLogTime: () => false }); backend.write(createRecord({ eventName: 'api.endpoint.failed', attributes: { 'api.endpoint': 'notes/show' }, error: { type: 'TypeError', message: 'broken' }, })); expect(output).toHaveBeenCalledWith( 'INFO *\t[root]\tmessage', { eventName: 'api.endpoint.failed', attributes: { 'api.endpoint': 'notes/show' }, error: { type: 'TypeError', message: 'broken' }, }, ); }); });