@hz-9/a5-core 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +15 -0
  3. package/dist/all.d.ts +676 -0
  4. package/dist/const/index.d.ts +38 -0
  5. package/dist/const/index.js +46 -0
  6. package/dist/core/__import-reflect-metadata.d.ts +1 -0
  7. package/dist/core/__import-reflect-metadata.js +4 -0
  8. package/dist/core/a5-application.d.ts +154 -0
  9. package/dist/core/a5-application.js +315 -0
  10. package/dist/core/a5-console-logger.d.ts +163 -0
  11. package/dist/core/a5-console-logger.js +354 -0
  12. package/dist/core/a5-factory.d.ts +21 -0
  13. package/dist/core/a5-factory.js +49 -0
  14. package/dist/core/index.d.ts +3 -0
  15. package/dist/core/index.js +20 -0
  16. package/dist/index.d.ts +7 -0
  17. package/dist/index.js +24 -0
  18. package/dist/interface/base.d.ts +18 -0
  19. package/dist/interface/base.js +3 -0
  20. package/dist/interface/http.d.ts +16 -0
  21. package/dist/interface/http.js +3 -0
  22. package/dist/interface/index.d.ts +3 -0
  23. package/dist/interface/index.js +20 -0
  24. package/dist/interface/provide-token.d.ts +21 -0
  25. package/dist/interface/provide-token.js +3 -0
  26. package/dist/middleware/a5-console-logger.middleware.d.ts +9 -0
  27. package/dist/middleware/a5-console-logger.middleware.js +58 -0
  28. package/dist/middleware/index.d.ts +1 -0
  29. package/dist/middleware/index.js +18 -0
  30. package/dist/module/config/index.d.ts +18 -0
  31. package/dist/module/config/index.js +36 -0
  32. package/dist/module/config/interface.d.ts +9 -0
  33. package/dist/module/config/interface.js +15 -0
  34. package/dist/module/index.d.ts +2 -0
  35. package/dist/module/index.js +19 -0
  36. package/dist/module/log/index.d.ts +18 -0
  37. package/dist/module/log/index.js +44 -0
  38. package/dist/module/log/interface.d.ts +9 -0
  39. package/dist/module/log/interface.js +15 -0
  40. package/dist/plugins/index.d.ts +1 -0
  41. package/dist/plugins/index.js +18 -0
  42. package/dist/plugins/nanoid.d.ts +1 -0
  43. package/dist/plugins/nanoid.js +6 -0
  44. package/dist/test/integration/core/a5-factory.integration.spec.d.ts +1 -0
  45. package/dist/test/integration/core/a5-factory.integration.spec.js +99 -0
  46. package/dist/test/integration/core/with-logger-module.integration.spec.d.ts +1 -0
  47. package/dist/test/integration/core/with-logger-module.integration.spec.js +401 -0
  48. package/dist/test/unit/core/a5-application.unit.spec.d.ts +1 -0
  49. package/dist/test/unit/core/a5-application.unit.spec.js +450 -0
  50. package/dist/test/unit/core/a5-console-logger.unit.spec.d.ts +1 -0
  51. package/dist/test/unit/core/a5-console-logger.unit.spec.js +998 -0
  52. package/dist/test/unit/middleware/a5-console-logger.middleware.unit.spec.d.ts +1 -0
  53. package/dist/test/unit/middleware/a5-console-logger.middleware.unit.spec.js +379 -0
  54. package/dist/test/unit/util/a5.util.unit.spec.d.ts +1 -0
  55. package/dist/test/unit/util/a5.util.unit.spec.js +109 -0
  56. package/dist/test/unit/util/color.util.unit.spec.d.ts +1 -0
  57. package/dist/test/unit/util/color.util.unit.spec.js +277 -0
  58. package/dist/test/unit/util/logo.util.unit.spec.d.ts +1 -0
  59. package/dist/test/unit/util/logo.util.unit.spec.js +202 -0
  60. package/dist/test/unit/util/run-env.util.unit.spec.d.ts +1 -0
  61. package/dist/test/unit/util/run-env.util.unit.spec.js +183 -0
  62. package/dist/util/a5.util.d.ts +27 -0
  63. package/dist/util/a5.util.js +41 -0
  64. package/dist/util/color.util.d.ts +26 -0
  65. package/dist/util/color.util.js +62 -0
  66. package/dist/util/index.d.ts +5 -0
  67. package/dist/util/index.js +22 -0
  68. package/dist/util/load-package.util.d.ts +29 -0
  69. package/dist/util/load-package.util.js +71 -0
  70. package/dist/util/logo.util.d.ts +31 -0
  71. package/dist/util/logo.util.js +59 -0
  72. package/dist/util/run-env.util.d.ts +28 -0
  73. package/dist/util/run-env.util.js +48 -0
  74. package/logo +7 -0
  75. package/package.json +96 -0
@@ -0,0 +1,998 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const a5_console_logger_1 = require("../../../core/a5-console-logger");
4
+ const color_util_1 = require("../../../util/color.util");
5
+ // Test helper class to access protected members
6
+ class TestableA5ConsoleLogger extends a5_console_logger_1.A5ConsoleLogger {
7
+ static getLastTimestampAt() {
8
+ return a5_console_logger_1.A5ConsoleLogger.lastTimestampAt;
9
+ }
10
+ static setLastTimestampAt(value) {
11
+ a5_console_logger_1.A5ConsoleLogger.lastTimestampAt = value;
12
+ }
13
+ getOriginalContext() {
14
+ return this.originalContext;
15
+ }
16
+ getInternalContext() {
17
+ return this.context;
18
+ }
19
+ getOptions() {
20
+ return this.options;
21
+ }
22
+ testPrintMessages(messages, context, logLevel = 'log', writeStreamType) {
23
+ return this.printMessages(messages, context, logLevel, writeStreamType);
24
+ }
25
+ testFormatMessage(options) {
26
+ return this.formatMessage(options);
27
+ }
28
+ testFormatMessageStructure(options) {
29
+ return this.formatMessageStructure(options);
30
+ }
31
+ testFormatPrefix() {
32
+ return this.formatPrefix();
33
+ }
34
+ testFormatPid() {
35
+ return this.formatPid();
36
+ }
37
+ testFormatLogLevel(logLevel) {
38
+ return this.formatLogLevel(logLevel);
39
+ }
40
+ testFormatTimestamp(dateMilliseconds) {
41
+ return this.formatTimestamp(dateMilliseconds);
42
+ }
43
+ testFormatContext(context) {
44
+ return this.formatContext(context);
45
+ }
46
+ testStringifyMessage(message, logLevel) {
47
+ return this.stringifyMessage(message, logLevel);
48
+ }
49
+ testColorize(message, logLevel) {
50
+ return this.colorize(message, logLevel);
51
+ }
52
+ testPrintStackTrace(stack) {
53
+ return this.printStackTrace(stack);
54
+ }
55
+ testUpdateAndGetTimestampDiff() {
56
+ return this.updateAndGetTimestampDiff();
57
+ }
58
+ testFormatTimestampDiff(timestampDiff) {
59
+ return this.formatTimestampDiff(timestampDiff);
60
+ }
61
+ testContextNeedApologeticReplace(context) {
62
+ return this._contextNeedApologeticReplace(context);
63
+ }
64
+ testContextApologeticReplace(context) {
65
+ return this._contextApologeticReplace(context);
66
+ }
67
+ testMessageApologeticReplace(message) {
68
+ return this._messageApologeticReplace(message);
69
+ }
70
+ testGetContextAndMessagesToPrint(args) {
71
+ return super.getContextAndMessagesToPrint(args);
72
+ }
73
+ testGetContextAndStackAndMessagesToPrint(args) {
74
+ return super.getContextAndStackAndMessagesToPrint(args);
75
+ }
76
+ testIsStackFormat(stack) {
77
+ return super.isStackFormat(stack);
78
+ }
79
+ testRemoveLastUndefined(args) {
80
+ return super.removeLastUndefined(args);
81
+ }
82
+ testGetColorByLogLevel(level) {
83
+ return super.getColorByLogLevel(level);
84
+ }
85
+ }
86
+ describe('A5ConsoleLogger Unit Test', () => {
87
+ const toClearLines = (lines) => lines.map(([text]) => color_util_1.ColorUtil.clear(text.trim()));
88
+ // eslint-disable-next-line init-declarations
89
+ let logger;
90
+ // eslint-disable-next-line init-declarations
91
+ let processStdoutWriteSpy;
92
+ // eslint-disable-next-line init-declarations
93
+ let processStderrWriteSpy;
94
+ beforeEach(async () => {
95
+ processStdoutWriteSpy = jest.spyOn(process.stdout, 'write').mockImplementation();
96
+ processStderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation();
97
+ logger = new TestableA5ConsoleLogger();
98
+ });
99
+ afterEach(() => {
100
+ processStdoutWriteSpy.mockRestore();
101
+ processStderrWriteSpy.mockRestore();
102
+ });
103
+ describe('[[Static Protected Properties]]', () => {
104
+ describe('[[Properties]]', () => {
105
+ describe('lastTimestampAt', () => {
106
+ it('should be undefined initially', () => {
107
+ TestableA5ConsoleLogger.setLastTimestampAt(undefined);
108
+ expect(TestableA5ConsoleLogger.getLastTimestampAt()).toBeUndefined();
109
+ });
110
+ it('should be settable to a number', () => {
111
+ const timestamp = Date.now();
112
+ TestableA5ConsoleLogger.setLastTimestampAt(timestamp);
113
+ expect(TestableA5ConsoleLogger.getLastTimestampAt()).toBe(timestamp);
114
+ });
115
+ it('should be settable to undefined', () => {
116
+ TestableA5ConsoleLogger.setLastTimestampAt(123456);
117
+ TestableA5ConsoleLogger.setLastTimestampAt(undefined);
118
+ expect(TestableA5ConsoleLogger.getLastTimestampAt()).toBeUndefined();
119
+ });
120
+ it('should persist across logger instances', () => {
121
+ const timestamp = 1000000;
122
+ TestableA5ConsoleLogger.setLastTimestampAt(timestamp);
123
+ // Create new instances to verify static property persistence
124
+ new TestableA5ConsoleLogger();
125
+ new TestableA5ConsoleLogger();
126
+ expect(TestableA5ConsoleLogger.getLastTimestampAt()).toBe(timestamp);
127
+ });
128
+ it('should handle zero value', () => {
129
+ TestableA5ConsoleLogger.setLastTimestampAt(0);
130
+ expect(TestableA5ConsoleLogger.getLastTimestampAt()).toBe(0);
131
+ });
132
+ it('should handle negative values', () => {
133
+ TestableA5ConsoleLogger.setLastTimestampAt(-1000);
134
+ expect(TestableA5ConsoleLogger.getLastTimestampAt()).toBe(-1000);
135
+ });
136
+ it('should handle maximum safe integer', () => {
137
+ TestableA5ConsoleLogger.setLastTimestampAt(Number.MAX_SAFE_INTEGER);
138
+ expect(TestableA5ConsoleLogger.getLastTimestampAt()).toBe(Number.MAX_SAFE_INTEGER);
139
+ });
140
+ });
141
+ });
142
+ });
143
+ describe('[[Constructor]]', () => {
144
+ describe('[[Constructor]]', () => {
145
+ describe('[[Default]]', () => {
146
+ it('should create instance with default options', () => {
147
+ const defaultLogger = new TestableA5ConsoleLogger();
148
+ expect(defaultLogger).toBeInstanceOf(a5_console_logger_1.A5ConsoleLogger);
149
+ expect(defaultLogger.getOptions()).toMatchObject({
150
+ logLevels: ['log', 'error', 'warn', 'debug', 'verbose', 'fatal'],
151
+ timestamp: false,
152
+ });
153
+ expect(defaultLogger.getOriginalContext()).toBe('');
154
+ expect(defaultLogger.getInternalContext()).toBe('');
155
+ });
156
+ });
157
+ describe('options', () => {
158
+ it('should accept custom context', () => {
159
+ const options = { context: 'TestContext' };
160
+ const contextLogger = new TestableA5ConsoleLogger(options);
161
+ expect(contextLogger.getOriginalContext()).toBe('TestContext');
162
+ expect(contextLogger.getInternalContext()).toBe('TestContext');
163
+ });
164
+ it('should accept empty string context', () => {
165
+ const options = { context: '' };
166
+ const contextLogger = new TestableA5ConsoleLogger(options);
167
+ expect(contextLogger.getOriginalContext()).toBe('');
168
+ expect(contextLogger.getInternalContext()).toBe('');
169
+ });
170
+ it('should accept undefined context', () => {
171
+ const options = { context: undefined };
172
+ const contextLogger = new TestableA5ConsoleLogger(options);
173
+ expect(contextLogger.getOriginalContext()).toBe('');
174
+ expect(contextLogger.getInternalContext()).toBe('');
175
+ });
176
+ it('should accept custom logLevels', () => {
177
+ const options = { logLevels: ['error', 'warn'] };
178
+ const levelsLogger = new TestableA5ConsoleLogger(options);
179
+ expect(levelsLogger.getOptions().logLevels).toEqual(['error', 'warn']);
180
+ });
181
+ it('should handle empty logLevels array', () => {
182
+ const options = { logLevels: [] };
183
+ const levelsLogger = new TestableA5ConsoleLogger(options);
184
+ expect(levelsLogger.getOptions().logLevels).toEqual([]);
185
+ });
186
+ it('should handle undefined logLevels', () => {
187
+ const options = { logLevels: undefined };
188
+ const levelsLogger = new TestableA5ConsoleLogger(options);
189
+ expect(levelsLogger.getOptions().logLevels).toEqual(['log', 'error', 'warn', 'debug', 'verbose', 'fatal']);
190
+ });
191
+ it('should accept custom timestamp option true', () => {
192
+ const options = { timestamp: true };
193
+ const timestampLogger = new TestableA5ConsoleLogger(options);
194
+ expect(timestampLogger.getOptions().timestamp).toBe(true);
195
+ });
196
+ it('should accept custom timestamp option false', () => {
197
+ const options = { timestamp: false };
198
+ const timestampLogger = new TestableA5ConsoleLogger(options);
199
+ expect(timestampLogger.getOptions().timestamp).toBe(false);
200
+ });
201
+ it('should handle undefined timestamp', () => {
202
+ const options = { timestamp: undefined };
203
+ const timestampLogger = new TestableA5ConsoleLogger(options);
204
+ expect(timestampLogger.getOptions().timestamp).toBe(false);
205
+ });
206
+ it('should handle empty options object', () => {
207
+ const emptyLogger = new TestableA5ConsoleLogger({});
208
+ expect(emptyLogger.getOptions()).toMatchObject({
209
+ logLevels: ['log', 'error', 'warn', 'debug', 'verbose', 'fatal'],
210
+ timestamp: false,
211
+ });
212
+ });
213
+ it('should handle complex options', () => {
214
+ const options = {
215
+ context: 'ComplexTest',
216
+ logLevels: ['debug', 'verbose'],
217
+ timestamp: true,
218
+ };
219
+ const complexLogger = new TestableA5ConsoleLogger(options);
220
+ expect(complexLogger.getOriginalContext()).toBe('ComplexTest');
221
+ expect(complexLogger.getInternalContext()).toBe('ComplexTest');
222
+ expect(complexLogger.getOptions().logLevels).toEqual(['debug', 'verbose']);
223
+ expect(complexLogger.getOptions().timestamp).toBe(true);
224
+ });
225
+ });
226
+ });
227
+ });
228
+ describe('[[Protected Properties]]', () => {
229
+ describe('[[Properties]]', () => {
230
+ describe('originalContext', () => {
231
+ it('should return empty string for default constructor', () => {
232
+ expect(logger.getOriginalContext()).toBe('');
233
+ });
234
+ it('should return the context set in constructor', () => {
235
+ const testLogger = new TestableA5ConsoleLogger({ context: 'TestCtx' });
236
+ expect(testLogger.getOriginalContext()).toBe('TestCtx');
237
+ });
238
+ it('should not change when context is updated', () => {
239
+ const testLogger = new TestableA5ConsoleLogger({ context: 'Original' });
240
+ testLogger.setContext('Modified');
241
+ expect(testLogger.getOriginalContext()).toBe('Original');
242
+ expect(testLogger.getInternalContext()).toBe('Modified');
243
+ });
244
+ });
245
+ describe('context', () => {
246
+ it('should return empty string for default constructor', () => {
247
+ expect(logger.getInternalContext()).toBe('');
248
+ });
249
+ it('should return the context set in constructor', () => {
250
+ const testLogger = new TestableA5ConsoleLogger({ context: 'TestCtx' });
251
+ expect(testLogger.getInternalContext()).toBe('TestCtx');
252
+ });
253
+ it('should change when setContext is called', () => {
254
+ logger.setContext('NewContext');
255
+ expect(logger.getInternalContext()).toBe('NewContext');
256
+ });
257
+ });
258
+ describe('options', () => {
259
+ it('should return default options for default constructor', () => {
260
+ const options = logger.getOptions();
261
+ expect(options).toMatchObject({
262
+ logLevels: ['log', 'error', 'warn', 'debug', 'verbose', 'fatal'],
263
+ timestamp: false,
264
+ });
265
+ });
266
+ it('should return custom options when provided', () => {
267
+ const customOptions = { logLevels: ['error'], timestamp: true };
268
+ const testLogger = new TestableA5ConsoleLogger(customOptions);
269
+ expect(testLogger.getOptions()).toMatchObject(customOptions);
270
+ });
271
+ });
272
+ });
273
+ });
274
+ describe('[[Protected Methods]]', () => {
275
+ describe('printMessages', () => {
276
+ describe('messages', () => {
277
+ it('should print multiple messages', () => {
278
+ const messages = ['message1', 'message2'];
279
+ logger.testPrintMessages(messages, 'TestContext', 'log', 'stdout');
280
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(2);
281
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
282
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG \[TestContext\s+\] message1$/);
283
+ expect(lines[1]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG \[TestContext\s+\] message2$/);
284
+ });
285
+ it('should print single message', () => {
286
+ logger.testPrintMessages(['single message'], 'TestContext');
287
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
288
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
289
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG \[TestContext\s+\] single message$/);
290
+ });
291
+ it('should handle empty array', () => {
292
+ logger.testPrintMessages([], 'TestContext');
293
+ expect(processStdoutWriteSpy).not.toHaveBeenCalled();
294
+ });
295
+ });
296
+ describe('context', () => {
297
+ it('should use provided context', () => {
298
+ logger.testPrintMessages(['test'], 'ProvidedContext');
299
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
300
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
301
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG \[ProvidedContex\] test$/);
302
+ });
303
+ it('should handle empty context', () => {
304
+ logger.testPrintMessages(['test'], '');
305
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
306
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
307
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG test$/);
308
+ });
309
+ });
310
+ describe('logLevel', () => {
311
+ it('should default to log level', () => {
312
+ logger.testPrintMessages(['test'], 'Context');
313
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
314
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
315
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG \[Context\s+\] test$/);
316
+ });
317
+ it('should use provided log level', () => {
318
+ logger.testPrintMessages(['test'], 'Context', 'error');
319
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
320
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
321
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+ERROR \[Context\s+\] test$/);
322
+ });
323
+ });
324
+ describe('writeStreamType', () => {
325
+ it('should default to stdout', () => {
326
+ logger.testPrintMessages(['test'], 'Context');
327
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
328
+ expect(processStderrWriteSpy).not.toHaveBeenCalled();
329
+ });
330
+ it('should write to stderr when specified', () => {
331
+ logger.testPrintMessages(['test'], 'Context', 'error', 'stderr');
332
+ expect(processStderrWriteSpy).toHaveBeenCalledTimes(1);
333
+ expect(processStdoutWriteSpy).not.toHaveBeenCalled();
334
+ const lines = toClearLines(processStderrWriteSpy.mock.calls);
335
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+ERROR \[Context\s+\] test$/);
336
+ });
337
+ });
338
+ });
339
+ describe('formatMessage', () => {
340
+ describe('options', () => {
341
+ it('should format message with all components', () => {
342
+ const options = { logLevel: 'log', context: 'TestCtx', message: 'test msg' };
343
+ const result = logger.testFormatMessage(options);
344
+ expect(result).toContain('[A5]');
345
+ expect(result).toContain('LOG');
346
+ expect(result).toContain('TestCtx');
347
+ expect(result).toContain('test msg');
348
+ expect(result).toContain('\n');
349
+ });
350
+ it('should handle empty context', () => {
351
+ const options = { logLevel: 'warn', context: '', message: 'warning' };
352
+ const result = logger.testFormatMessage(options);
353
+ expect(result).toContain('WARN');
354
+ expect(result).toContain('warning');
355
+ });
356
+ it('should handle object message', () => {
357
+ const options = { logLevel: 'debug', context: 'Debug', message: { key: 'value' } };
358
+ const result = logger.testFormatMessage(options);
359
+ expect(result).toContain('Object:');
360
+ expect(result).toContain('"key": "value"');
361
+ });
362
+ });
363
+ });
364
+ describe('formatMessageStructure', () => {
365
+ describe('options', () => {
366
+ it('should return complete message structure', () => {
367
+ const options = { logLevel: 'info', context: 'TestCtx', message: 'test' };
368
+ const result = logger.testFormatMessageStructure(options);
369
+ expect(result).toHaveProperty('prefix', '[A5]');
370
+ expect(result).toHaveProperty('pid');
371
+ expect(result).toHaveProperty('timestamp');
372
+ expect(result).toHaveProperty('timestampStr');
373
+ expect(result).toHaveProperty('logLevel', ' INFO');
374
+ expect(result).toHaveProperty('context', '[TestCtx ]');
375
+ expect(result.output).toContain('test'); // Output includes color codes
376
+ expect(result).toHaveProperty('timestampDiff');
377
+ expect(result).toHaveProperty('timestampDiffStr');
378
+ });
379
+ it('should handle different log levels', () => {
380
+ const errorOptions = { logLevel: 'error', context: 'Err', message: 'error msg' };
381
+ const result = logger.testFormatMessageStructure(errorOptions);
382
+ expect(result.logLevel).toBe(' ERROR');
383
+ });
384
+ });
385
+ });
386
+ describe('formatPrefix', () => {
387
+ describe('[[Default]]', () => {
388
+ it('should return A5 prefix', () => {
389
+ const result = logger.testFormatPrefix();
390
+ expect(result).toBe('[A5]');
391
+ });
392
+ });
393
+ });
394
+ describe('formatPid', () => {
395
+ describe('[[Default]]', () => {
396
+ it('should return process PID padded to 6 characters', () => {
397
+ const result = logger.testFormatPid();
398
+ expect(result).toHaveLength(6);
399
+ expect(result).toContain(process.pid.toString());
400
+ });
401
+ });
402
+ });
403
+ describe('formatLogLevel', () => {
404
+ describe('logLevel', () => {
405
+ it('should format log level with uppercase and padding', () => {
406
+ expect(logger.testFormatLogLevel('log')).toBe(' LOG');
407
+ expect(logger.testFormatLogLevel('error')).toBe(' ERROR');
408
+ expect(logger.testFormatLogLevel('warn')).toBe(' WARN');
409
+ expect(logger.testFormatLogLevel('debug')).toBe(' DEBUG');
410
+ expect(logger.testFormatLogLevel('verbose')).toBe('VERBOSE');
411
+ expect(logger.testFormatLogLevel('fatal')).toBe(' FATAL');
412
+ });
413
+ });
414
+ });
415
+ describe('formatTimestamp', () => {
416
+ describe('dateMilliseconds', () => {
417
+ it('should format timestamp in ISO format', () => {
418
+ const timestamp = new Date('2023-12-25T10:30:45.123Z').getTime();
419
+ const result = logger.testFormatTimestamp(timestamp);
420
+ expect(result).toMatch(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}/);
421
+ });
422
+ it('should handle current timestamp', () => {
423
+ const now = Date.now();
424
+ const result = logger.testFormatTimestamp(now);
425
+ expect(result).toBeTruthy();
426
+ expect(result.length).toBeGreaterThan(20);
427
+ });
428
+ });
429
+ });
430
+ describe('formatContext', () => {
431
+ describe('context', () => {
432
+ it('should return empty string for empty context', () => {
433
+ const result = logger.testFormatContext('');
434
+ expect(result).toBe('');
435
+ });
436
+ it('should format context with brackets and padding', () => {
437
+ const result = logger.testFormatContext('Test');
438
+ expect(result).toBe('[Test ]');
439
+ });
440
+ it('should truncate long context to 14 characters', () => {
441
+ const longContext = 'VeryLongContextThatExceeds14Characters';
442
+ const result = logger.testFormatContext(longContext);
443
+ expect(result).toBe('[VeryLongContex]'); // Truncates to first 14 chars
444
+ expect(result.length).toBe(16); // [14 chars + brackets]
445
+ });
446
+ it('should pad short context to 14 characters', () => {
447
+ const result = logger.testFormatContext('ABC');
448
+ expect(result).toBe('[ABC ]');
449
+ expect(result.length).toBe(16);
450
+ });
451
+ });
452
+ });
453
+ describe('stringifyMessage', () => {
454
+ describe('message', () => {
455
+ it('should return string as is with color', () => {
456
+ const result = logger.testStringifyMessage('simple text', 'log');
457
+ expect(result).toContain('simple text');
458
+ });
459
+ it('should format object with JSON', () => {
460
+ const obj = { name: 'test', value: 123 };
461
+ const result = logger.testStringifyMessage(obj, 'debug');
462
+ expect(result).toContain('Object:');
463
+ expect(result).toContain('"name": "test"');
464
+ expect(result).toContain('"value": 123');
465
+ });
466
+ it('should format array with JSON', () => {
467
+ const arr = [1, 2, 3];
468
+ const result = logger.testStringifyMessage(arr, 'verbose');
469
+ expect(result).toContain('Object:');
470
+ expect(result).toContain('[\n 1,\n 2,\n 3\n]');
471
+ });
472
+ it('should call function and stringify result', () => {
473
+ const fn = () => 'function result';
474
+ const result = logger.testStringifyMessage(fn, 'log');
475
+ expect(result).toContain('function result');
476
+ });
477
+ it('should handle class constructor', () => {
478
+ class TestClass {
479
+ }
480
+ const result = logger.testStringifyMessage(TestClass, 'log');
481
+ expect(result).toContain('TestClass');
482
+ });
483
+ it('should handle bigint values in objects', () => {
484
+ const objWithBigInt = { id: BigInt(123456789012345) };
485
+ const result = logger.testStringifyMessage(objWithBigInt, 'log');
486
+ expect(result).toContain('"123456789012345"');
487
+ });
488
+ });
489
+ });
490
+ describe('colorize', () => {
491
+ describe('message', () => {
492
+ it('should apply color based on log level', () => {
493
+ const message = 'test message';
494
+ const result = logger.testColorize(message, 'error');
495
+ expect(result).toBeTruthy();
496
+ });
497
+ });
498
+ });
499
+ describe('printStackTrace', () => {
500
+ describe('stack', () => {
501
+ it('should not write anything for undefined stack', () => {
502
+ logger.testPrintStackTrace(undefined);
503
+ expect(processStderrWriteSpy).not.toHaveBeenCalled();
504
+ });
505
+ it('should write stack trace to stderr', () => {
506
+ const { stack } = new Error();
507
+ logger.testPrintStackTrace(stack);
508
+ expect(processStderrWriteSpy).toHaveBeenCalledTimes(1);
509
+ expect(processStderrWriteSpy).toHaveBeenCalledWith(`${stack}\n`);
510
+ });
511
+ });
512
+ });
513
+ describe('updateAndGetTimestampDiff', () => {
514
+ describe('[[Default]]', () => {
515
+ it('should return null when timestamp is disabled', () => {
516
+ const logger2 = new TestableA5ConsoleLogger({ timestamp: false });
517
+ const result = logger2.testUpdateAndGetTimestampDiff();
518
+ expect(result).toBeNull();
519
+ });
520
+ it('should return timestamp diff when enabled', () => {
521
+ const logger2 = new TestableA5ConsoleLogger({ timestamp: true });
522
+ TestableA5ConsoleLogger.setLastTimestampAt(undefined);
523
+ const result1 = logger2.testUpdateAndGetTimestampDiff();
524
+ expect(result1).toBe(0); // First call returns 0 as diff from initialization
525
+ const result2 = logger2.testUpdateAndGetTimestampDiff();
526
+ expect(result2).toBeGreaterThanOrEqual(0);
527
+ });
528
+ });
529
+ });
530
+ describe('formatTimestampDiff', () => {
531
+ describe('timestampDiff', () => {
532
+ it('should return empty string for null', () => {
533
+ const result = logger.testFormatTimestampDiff(null);
534
+ expect(result).toBe('');
535
+ });
536
+ it('should format timestamp diff with ms suffix', () => {
537
+ const result = logger.testFormatTimestampDiff(150);
538
+ expect(result).toBe('+150ms');
539
+ });
540
+ it('should handle zero diff', () => {
541
+ const result = logger.testFormatTimestampDiff(0);
542
+ expect(result).toBe('+0ms');
543
+ });
544
+ });
545
+ });
546
+ describe('getContextAndMessagesToPrint', () => {
547
+ describe('args', () => {
548
+ it('should return default context for single argument', () => {
549
+ logger.setContext('DefaultCtx');
550
+ const result = logger.testGetContextAndMessagesToPrint(['message']);
551
+ expect(result.context).toBe('DefaultCtx');
552
+ expect(result.messages).toEqual(['message']);
553
+ });
554
+ it('should extract context from last string argument', () => {
555
+ const result = logger.testGetContextAndMessagesToPrint(['msg1', 'msg2', 'CustomContext']);
556
+ expect(result.context).toBe('CustomContext');
557
+ expect(result.messages).toEqual(['msg1', 'msg2']);
558
+ });
559
+ it('should use default context when last argument is not string', () => {
560
+ logger.setContext('DefaultCtx');
561
+ const result = logger.testGetContextAndMessagesToPrint(['msg1', 'msg2', 123]);
562
+ expect(result.context).toBe('DefaultCtx');
563
+ expect(result.messages).toEqual(['msg1', 'msg2', 123]);
564
+ });
565
+ it('should handle empty args', () => {
566
+ logger.setContext('DefaultCtx');
567
+ const result = logger.testGetContextAndMessagesToPrint([]);
568
+ expect(result.context).toBe('DefaultCtx');
569
+ expect(result.messages).toEqual([]);
570
+ });
571
+ });
572
+ });
573
+ describe('getContextAndStackAndMessagesToPrint', () => {
574
+ describe('args', () => {
575
+ it('should identify stack trace in 2-argument case', () => {
576
+ const { stack } = new Error();
577
+ const result = logger.testGetContextAndStackAndMessagesToPrint(['error msg', stack]);
578
+ expect(result.messages).toEqual(['error msg']);
579
+ expect(result.stack).toBe(stack);
580
+ expect(result.context).toBe(logger.getInternalContext());
581
+ });
582
+ it('should treat second argument as context when not stack format', () => {
583
+ const result = logger.testGetContextAndStackAndMessagesToPrint(['error msg', 'ErrorContext']);
584
+ expect(result.messages).toEqual(['error msg']);
585
+ expect(result.context).toBe('ErrorContext');
586
+ expect(result.stack).toBeUndefined();
587
+ });
588
+ it('should extract stack from last message', () => {
589
+ const { stack } = new Error();
590
+ const result = logger.testGetContextAndStackAndMessagesToPrint(['msg1', 'msg2', stack, 'Context']);
591
+ expect(result.messages).toEqual(['msg1', 'msg2']);
592
+ expect(result.context).toBe('Context');
593
+ expect(result.stack).toBe(stack);
594
+ });
595
+ });
596
+ });
597
+ describe('isStackFormat', () => {
598
+ describe('stack', () => {
599
+ it('should return true for valid stack format', () => {
600
+ const { stack } = new Error();
601
+ const result = logger.testIsStackFormat(stack);
602
+ expect(result).toBe(true);
603
+ });
604
+ it('should return false for invalid stack format', () => {
605
+ const invalidStack = 'Just a simple error message';
606
+ const result = logger.testIsStackFormat(invalidStack);
607
+ expect(result).toBe(false);
608
+ });
609
+ it('should return false for non-string input', () => {
610
+ expect(logger.testIsStackFormat(123)).toBe(false);
611
+ expect(logger.testIsStackFormat(null)).toBe(false);
612
+ expect(logger.testIsStackFormat(undefined)).toBe(false);
613
+ expect(logger.testIsStackFormat({})).toBe(false);
614
+ });
615
+ });
616
+ });
617
+ describe('removeLastUndefined', () => {
618
+ describe('args', () => {
619
+ it('should remove trailing undefined values', () => {
620
+ const result = logger.testRemoveLastUndefined(['a', 'b', undefined, undefined]);
621
+ expect(result).toEqual(['a', 'b']);
622
+ });
623
+ it('should keep non-undefined values', () => {
624
+ const result = logger.testRemoveLastUndefined(['a', 'b', 'c']);
625
+ expect(result).toEqual(['a', 'b', 'c']);
626
+ });
627
+ it('should handle mixed undefined positions', () => {
628
+ const result = logger.testRemoveLastUndefined(['a', undefined, 'b', undefined]);
629
+ expect(result).toEqual(['a', undefined, 'b']);
630
+ });
631
+ it('should handle all undefined', () => {
632
+ const result = logger.testRemoveLastUndefined([undefined, undefined]);
633
+ expect(result).toEqual([]);
634
+ });
635
+ it('should handle empty array', () => {
636
+ const result = logger.testRemoveLastUndefined([]);
637
+ expect(result).toEqual([]);
638
+ });
639
+ });
640
+ });
641
+ describe('getColorByLogLevel', () => {
642
+ describe('level', () => {
643
+ it('should return appropriate color functions', () => {
644
+ expect(logger.testGetColorByLogLevel('debug')).toBeDefined();
645
+ expect(logger.testGetColorByLogLevel('warn')).toBeDefined();
646
+ expect(logger.testGetColorByLogLevel('error')).toBeDefined();
647
+ expect(logger.testGetColorByLogLevel('verbose')).toBeDefined();
648
+ expect(logger.testGetColorByLogLevel('fatal')).toBeDefined();
649
+ expect(logger.testGetColorByLogLevel('log')).toBeDefined();
650
+ });
651
+ it('should return function that transforms text', () => {
652
+ const colorFn = logger.testGetColorByLogLevel('error');
653
+ const result = colorFn('test text');
654
+ expect(typeof result).toBe('string');
655
+ });
656
+ });
657
+ });
658
+ describe('_contextNeedApologeticReplace', () => {
659
+ describe('context', () => {
660
+ it('should return true for NestFactory context', () => {
661
+ const result = logger.testContextNeedApologeticReplace('NestFactory');
662
+ expect(result).toBe(true);
663
+ });
664
+ it('should return true for NestApplication context', () => {
665
+ const result = logger.testContextNeedApologeticReplace('NestApplication');
666
+ expect(result).toBe(true);
667
+ });
668
+ it('should return false for other contexts', () => {
669
+ expect(logger.testContextNeedApologeticReplace('CustomContext')).toBe(false);
670
+ expect(logger.testContextNeedApologeticReplace('SomeOtherContext')).toBe(false);
671
+ expect(logger.testContextNeedApologeticReplace('')).toBe(false);
672
+ });
673
+ });
674
+ });
675
+ describe('_contextApologeticReplace', () => {
676
+ describe('context', () => {
677
+ it('should replace NestFactory with A5Factory', () => {
678
+ const result = logger.testContextApologeticReplace('NestFactory');
679
+ expect(result).toBe('A5Factory');
680
+ });
681
+ it('should replace NestApplication with A5Application', () => {
682
+ const result = logger.testContextApologeticReplace('NestApplication');
683
+ expect(result).toBe('A5Application');
684
+ });
685
+ });
686
+ });
687
+ describe('_messageApologeticReplace', () => {
688
+ describe('message', () => {
689
+ it('should replace Nest startup message', () => {
690
+ const result = logger.testMessageApologeticReplace('Starting Nest application...');
691
+ expect(result).toBe('Starting A5 application...');
692
+ });
693
+ it('should replace Nest success message', () => {
694
+ const result = logger.testMessageApologeticReplace('Nest application successfully started');
695
+ expect(result).toBe('A5 application successfully started');
696
+ });
697
+ it('should replace Nest microservice message', () => {
698
+ const result = logger.testMessageApologeticReplace('Nest microservice successfully started');
699
+ expect(result).toBe('A5 microservice successfully started');
700
+ });
701
+ it('should return original message for non-matching strings', () => {
702
+ const originalMsg = 'Custom application message';
703
+ const result = logger.testMessageApologeticReplace(originalMsg);
704
+ expect(result).toBe(originalMsg);
705
+ });
706
+ it('should return non-string messages unchanged', () => {
707
+ expect(logger.testMessageApologeticReplace(123)).toBe(123);
708
+ expect(logger.testMessageApologeticReplace(null)).toBe(null);
709
+ expect(logger.testMessageApologeticReplace(undefined)).toBe(undefined);
710
+ expect(logger.testMessageApologeticReplace({ key: 'value' })).toEqual({ key: 'value' });
711
+ });
712
+ });
713
+ });
714
+ });
715
+ describe('[[Public Methods]]', () => {
716
+ describe('log', () => {
717
+ describe('message', () => {
718
+ it('should log simple string message', () => {
719
+ logger.log('test message');
720
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
721
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
722
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG test message$/);
723
+ });
724
+ it('should log object message', () => {
725
+ const testObj = { key: 'value' };
726
+ logger.log(testObj);
727
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
728
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
729
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG Object:/);
730
+ expect(lines[0]).toContain(JSON.stringify(testObj, null, 2));
731
+ });
732
+ it('should log number message', () => {
733
+ logger.log(123);
734
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
735
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
736
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG 123$/);
737
+ });
738
+ it('should log boolean message', () => {
739
+ logger.log(true);
740
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
741
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
742
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG true$/);
743
+ });
744
+ it('should log null message', () => {
745
+ logger.log(null);
746
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
747
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
748
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG null$/);
749
+ });
750
+ it('should log undefined message', () => {
751
+ logger.log(undefined);
752
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
753
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
754
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG undefined$/);
755
+ });
756
+ it('should log array message', () => {
757
+ const testArray = [1, 2, 3];
758
+ logger.log(testArray);
759
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
760
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
761
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG Object:/);
762
+ expect(lines[0]).toContain(JSON.stringify(testArray, null, 2));
763
+ });
764
+ it('should log function message by calling it', () => {
765
+ const testFn = () => 'function result';
766
+ logger.log(testFn);
767
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
768
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
769
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG function result$/);
770
+ });
771
+ it('should log class constructor by name', () => {
772
+ class TestClass {
773
+ }
774
+ logger.log(TestClass);
775
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
776
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
777
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG TestClass$/);
778
+ });
779
+ });
780
+ describe('context', () => {
781
+ it('should use instance context when no context provided', () => {
782
+ logger.setContext('TestContext');
783
+ logger.log('test message');
784
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
785
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
786
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG \[TestContext\s+\] test message$/);
787
+ });
788
+ it('should use provided context', () => {
789
+ logger.log('test message', 'ProvidedContext');
790
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
791
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
792
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+LOG \[ProvidedContex\] test message$/);
793
+ });
794
+ it('should not log when log level is disabled', () => {
795
+ logger.setLogLevels(['error']);
796
+ logger.log('test message');
797
+ expect(processStdoutWriteSpy).not.toHaveBeenCalled();
798
+ });
799
+ });
800
+ });
801
+ describe('error', () => {
802
+ describe('message', () => {
803
+ it('should log error message to stderr', () => {
804
+ logger.error('error message');
805
+ expect(processStderrWriteSpy).toHaveBeenCalledTimes(1);
806
+ const lines = toClearLines(processStderrWriteSpy.mock.calls);
807
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+ERROR error message$/);
808
+ });
809
+ it('should not log when error level is disabled', () => {
810
+ logger.setLogLevels([]); // empty array means no levels enabled
811
+ logger.error('error message');
812
+ expect(processStderrWriteSpy).not.toHaveBeenCalled();
813
+ });
814
+ });
815
+ describe('stack', () => {
816
+ it('should log stack trace when provided', () => {
817
+ const { stack } = new Error();
818
+ logger.error('error message', stack);
819
+ expect(processStderrWriteSpy).toHaveBeenCalledTimes(2); // message + stack
820
+ const { calls } = processStderrWriteSpy.mock;
821
+ const lines = toClearLines([calls[0]]);
822
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+ERROR error message$/);
823
+ expect(calls[1][0]).toBe(`${stack}\n`);
824
+ });
825
+ it('should handle string context parameter', () => {
826
+ logger.error('error message', 'ErrorContext');
827
+ expect(processStderrWriteSpy).toHaveBeenCalledTimes(1);
828
+ const lines = toClearLines(processStderrWriteSpy.mock.calls);
829
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+ERROR \[ErrorContext\s+\] error message$/);
830
+ });
831
+ it('should handle both stack and context', () => {
832
+ const { stack } = new Error();
833
+ logger.error('error message', stack, 'ErrorContext');
834
+ expect(processStderrWriteSpy).toHaveBeenCalledTimes(2);
835
+ const { calls } = processStderrWriteSpy.mock;
836
+ const lines = toClearLines([calls[0]]);
837
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+ERROR \[ErrorContext\s+\] error message$/);
838
+ expect(calls[1][0]).toBe(`${stack}\n`);
839
+ });
840
+ });
841
+ });
842
+ describe('warn', () => {
843
+ describe('message', () => {
844
+ it('should log warning message', () => {
845
+ logger.warn('warning message');
846
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
847
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
848
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+WARN warning message$/);
849
+ });
850
+ it('should not log when warn level is disabled', () => {
851
+ logger.setLogLevels(['error']);
852
+ logger.warn('warning message');
853
+ expect(processStdoutWriteSpy).not.toHaveBeenCalled();
854
+ });
855
+ });
856
+ });
857
+ describe('debug', () => {
858
+ describe('message', () => {
859
+ it('should log debug message', () => {
860
+ logger.debug('debug message');
861
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
862
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
863
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+DEBUG debug message$/);
864
+ });
865
+ it('should not log when debug level is disabled', () => {
866
+ logger.setLogLevels(['error']);
867
+ logger.debug('debug message');
868
+ expect(processStdoutWriteSpy).not.toHaveBeenCalled();
869
+ });
870
+ });
871
+ });
872
+ describe('verbose', () => {
873
+ describe('message', () => {
874
+ it('should log verbose message', () => {
875
+ logger.verbose('verbose message');
876
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
877
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
878
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+VERBOSE verbose message$/);
879
+ });
880
+ it('should not log when verbose level is disabled', () => {
881
+ logger.setLogLevels(['error']);
882
+ logger.verbose('verbose message');
883
+ expect(processStdoutWriteSpy).not.toHaveBeenCalled();
884
+ });
885
+ });
886
+ });
887
+ describe('fatal', () => {
888
+ describe('message', () => {
889
+ it('should log fatal message', () => {
890
+ logger.fatal('fatal message');
891
+ expect(processStdoutWriteSpy).toHaveBeenCalledTimes(1);
892
+ const lines = toClearLines(processStdoutWriteSpy.mock.calls);
893
+ expect(lines[0]).toMatch(/^\[A5\] \d+\s+- \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\s+FATAL fatal message$/);
894
+ });
895
+ it('should not log when fatal level is disabled', () => {
896
+ logger.setLogLevels([]); // empty array means no levels enabled
897
+ logger.fatal('fatal message');
898
+ expect(processStdoutWriteSpy).not.toHaveBeenCalled();
899
+ });
900
+ });
901
+ });
902
+ describe('setLogLevels', () => {
903
+ describe('levels', () => {
904
+ it('should set log levels to provided array', () => {
905
+ const customLevels = ['error', 'warn'];
906
+ logger.setLogLevels(customLevels);
907
+ expect(logger.getOptions().logLevels).toEqual(customLevels);
908
+ });
909
+ it('should accept empty array', () => {
910
+ logger.setLogLevels([]);
911
+ expect(logger.getOptions().logLevels).toEqual([]);
912
+ });
913
+ it('should accept single level', () => {
914
+ logger.setLogLevels(['error']);
915
+ expect(logger.getOptions().logLevels).toEqual(['error']);
916
+ });
917
+ it('should accept all levels', () => {
918
+ const allLevels = ['log', 'error', 'warn', 'debug', 'verbose', 'fatal'];
919
+ logger.setLogLevels(allLevels);
920
+ expect(logger.getOptions().logLevels).toEqual(allLevels);
921
+ });
922
+ });
923
+ });
924
+ describe('setContext', () => {
925
+ describe('context', () => {
926
+ it('should set context to provided string', () => {
927
+ logger.setContext('NewContext');
928
+ expect(logger.getInternalContext()).toBe('NewContext');
929
+ });
930
+ it('should accept empty string', () => {
931
+ logger.setContext('');
932
+ expect(logger.getInternalContext()).toBe('');
933
+ });
934
+ it('should accept long context string', () => {
935
+ const longContext = 'VeryLongContextThatExceeds14Characters';
936
+ logger.setContext(longContext);
937
+ expect(logger.getInternalContext()).toBe(longContext);
938
+ });
939
+ });
940
+ });
941
+ describe('getContext', () => {
942
+ describe('[[Default]]', () => {
943
+ it('should return current context', () => {
944
+ logger.setContext('TestGetContext');
945
+ expect(logger.getContext()).toBe('TestGetContext');
946
+ });
947
+ it('should return empty string for default', () => {
948
+ const defaultLogger = new TestableA5ConsoleLogger();
949
+ expect(defaultLogger.getContext()).toBe('');
950
+ });
951
+ });
952
+ });
953
+ describe('resetContext', () => {
954
+ describe('[[Default]]', () => {
955
+ it('should reset to original context', () => {
956
+ const originalLogger = new TestableA5ConsoleLogger({ context: 'Original' });
957
+ originalLogger.setContext('Modified');
958
+ expect(originalLogger.getInternalContext()).toBe('Modified');
959
+ originalLogger.resetContext();
960
+ expect(originalLogger.getInternalContext()).toBe('Original');
961
+ });
962
+ it('should reset to empty string when no original context', () => {
963
+ logger.setContext('Modified');
964
+ expect(logger.getInternalContext()).toBe('Modified');
965
+ logger.resetContext();
966
+ expect(logger.getInternalContext()).toBe('');
967
+ });
968
+ });
969
+ });
970
+ describe('isLevelEnabled', () => {
971
+ describe('level', () => {
972
+ it('should return true for enabled levels', () => {
973
+ logger.setLogLevels(['error', 'warn']);
974
+ expect(logger.isLevelEnabled('error')).toBe(true);
975
+ expect(logger.isLevelEnabled('warn')).toBe(true);
976
+ });
977
+ it('should return false for disabled levels', () => {
978
+ logger.setLogLevels(['error']);
979
+ expect(logger.isLevelEnabled('warn')).toBe(false);
980
+ expect(logger.isLevelEnabled('debug')).toBe(false);
981
+ });
982
+ it('should handle all log levels correctly', () => {
983
+ const allLevels = ['log', 'error', 'warn', 'debug', 'verbose', 'fatal'];
984
+ logger.setLogLevels(allLevels);
985
+ allLevels.forEach((level) => {
986
+ expect(logger.isLevelEnabled(level)).toBe(true);
987
+ });
988
+ });
989
+ it('should return false when no levels enabled', () => {
990
+ logger.setLogLevels([]);
991
+ expect(logger.isLevelEnabled('error')).toBe(false);
992
+ expect(logger.isLevelEnabled('log')).toBe(false);
993
+ });
994
+ });
995
+ });
996
+ });
997
+ });
998
+ //# sourceMappingURL=a5-console-logger.unit.spec.js.map