mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
111 lines
4.4 KiB
TypeScript
111 lines
4.4 KiB
TypeScript
/**
|
||
* Unit tests for `gitnexus/src/cli/cli-message.ts`.
|
||
*
|
||
* cli-message is the helper for user-facing CLI banners and error guidance.
|
||
* The contract: each call writes plain text to stderr AND emits a
|
||
* structured pino record through the singleton logger.
|
||
*
|
||
* Tests verify both halves of the tee, plus shape contracts (newline
|
||
* handling, structured fields, tee survival across messages with
|
||
* embedded newlines).
|
||
*/
|
||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||
import { cliInfo, cliWarn, cliError, cliInfoKey } from '../../src/cli/cli-message.js';
|
||
import { setCliLanguage } from '../../src/cli/i18n/index.js';
|
||
import { _captureLogger, type LoggerCapture } from '../../src/core/logger.js';
|
||
|
||
describe('cli-message — stderr + logger tee', () => {
|
||
let cap: LoggerCapture;
|
||
let stderrSpy: ReturnType<typeof vi.spyOn>;
|
||
|
||
beforeEach(() => {
|
||
cap = _captureLogger();
|
||
stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
||
});
|
||
|
||
afterEach(() => {
|
||
setCliLanguage(null);
|
||
stderrSpy.mockRestore();
|
||
cap.restore();
|
||
});
|
||
|
||
it('cliInfo writes plain text to stderr and emits a structured info record', () => {
|
||
cliInfo('hello');
|
||
// Plain stderr write
|
||
const stderrCalls = stderrSpy.mock.calls.map(([chunk]) =>
|
||
typeof chunk === 'string' ? chunk : chunk.toString(),
|
||
);
|
||
expect(stderrCalls).toContain('hello\n');
|
||
// Structured logger record
|
||
const records = cap.records();
|
||
expect(records.some((r) => r.msg === 'hello' && r.level === 30)).toBe(true);
|
||
});
|
||
|
||
it('cliWarn writes to stderr and emits at warn level (40)', () => {
|
||
cliWarn('caution');
|
||
const stderrCalls = stderrSpy.mock.calls.map(([chunk]) =>
|
||
typeof chunk === 'string' ? chunk : chunk.toString(),
|
||
);
|
||
expect(stderrCalls).toContain('caution\n');
|
||
const records = cap.records();
|
||
expect(records.some((r) => r.msg === 'caution' && r.level === 40)).toBe(true);
|
||
});
|
||
|
||
it('cliError writes to stderr and emits at error level (50) with structured fields', () => {
|
||
cliError('boom', { code: 'EADDRINUSE', port: 4747 });
|
||
const stderrCalls = stderrSpy.mock.calls.map(([chunk]) =>
|
||
typeof chunk === 'string' ? chunk : chunk.toString(),
|
||
);
|
||
expect(stderrCalls).toContain('boom\n');
|
||
const records = cap.records();
|
||
const errorRecord = records.find((r) => r.msg === 'boom' && r.level === 50);
|
||
expect(errorRecord).toBeDefined();
|
||
expect(errorRecord?.code).toBe('EADDRINUSE');
|
||
expect(errorRecord?.port).toBe(4747);
|
||
});
|
||
|
||
it('does not double-newline an already-newlined message', () => {
|
||
cliInfo('already-terminated\n');
|
||
const stderrCalls = stderrSpy.mock.calls.map(([chunk]) =>
|
||
typeof chunk === 'string' ? chunk : chunk.toString(),
|
||
);
|
||
// Exactly one trailing \n, not two.
|
||
expect(stderrCalls).toContain('already-terminated\n');
|
||
expect(stderrCalls.includes('already-terminated\n\n')).toBe(false);
|
||
});
|
||
|
||
it('preserves embedded newlines in multi-line messages (does not split into multiple records)', () => {
|
||
cliError('line one\nline two\nline three');
|
||
const stderrCalls = stderrSpy.mock.calls.map(([chunk]) =>
|
||
typeof chunk === 'string' ? chunk : chunk.toString(),
|
||
);
|
||
// The whole multi-line block goes to stderr in one write, with a
|
||
// trailing newline appended.
|
||
expect(stderrCalls).toContain('line one\nline two\nline three\n');
|
||
// The structured record carries the full message as a single field.
|
||
const records = cap.records();
|
||
expect(records.some((r) => r.msg === 'line one\nline two\nline three' && r.level === 50)).toBe(
|
||
true,
|
||
);
|
||
});
|
||
|
||
it('cliInfoKey translates key-based messages before teeing', () => {
|
||
setCliLanguage('zh-CN');
|
||
cliInfoKey('list.title', { count: 3 });
|
||
const stderrCalls = stderrSpy.mock.calls.map(([chunk]) =>
|
||
typeof chunk === 'string' ? chunk : chunk.toString(),
|
||
);
|
||
expect(stderrCalls).toContain('已索引仓库(3)\n');
|
||
expect(cap.records().some((r) => r.msg === '已索引仓库(3)' && r.level === 30)).toBe(true);
|
||
});
|
||
|
||
it('handles an empty message — stderr gets a bare newline, logger gets msg:""', () => {
|
||
cliInfo('');
|
||
const stderrCalls = stderrSpy.mock.calls.map(([chunk]) =>
|
||
typeof chunk === 'string' ? chunk : chunk.toString(),
|
||
);
|
||
expect(stderrCalls).toContain('\n');
|
||
const records = cap.records();
|
||
expect(records.some((r) => r.msg === '' && r.level === 30)).toBe(true);
|
||
});
|
||
});
|