@unchainedshop/logger 4.3.4 → 4.5.0

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.
@@ -1,725 +0,0 @@
1
- import { describe, it, beforeEach, afterEach, mock } from 'node:test';
2
- import assert from 'node:assert';
3
- import { createLogger, resetLoggerInitialization } from '../src/createLogger.js';
4
-
5
- describe('createLogger', () => {
6
- const originalEnv = process.env;
7
- let consoleOutput: string[] = [];
8
- const originalConsole = {
9
- log: console.log,
10
- warn: console.warn,
11
- error: console.error,
12
- info: console.info,
13
- debug: console.debug,
14
- };
15
-
16
- beforeEach(() => {
17
- // Reset environment variables before each test
18
- process.env = { ...originalEnv };
19
- // Reset logger initialization state
20
- resetLoggerInitialization();
21
-
22
- // Capture console output
23
- consoleOutput = [];
24
- const captureOutput = (...args: any[]) => {
25
- // Join all arguments into a single string
26
- const output = args
27
- .map((arg) => {
28
- if (typeof arg === 'string') {
29
- return arg;
30
- } else if (arg === undefined) {
31
- return 'undefined';
32
- } else if (arg === null) {
33
- return 'null';
34
- } else {
35
- try {
36
- return JSON.stringify(arg);
37
- } catch {
38
- return String(arg);
39
- }
40
- }
41
- })
42
- .join(' ');
43
- consoleOutput.push(output);
44
- };
45
-
46
- console.log = mock.fn(captureOutput);
47
- console.warn = mock.fn(captureOutput);
48
- console.error = mock.fn(captureOutput);
49
- console.info = mock.fn(captureOutput);
50
- console.debug = mock.fn(captureOutput);
51
- });
52
-
53
- afterEach(() => {
54
- // Restore original environment
55
- process.env = originalEnv;
56
- // Restore console
57
- console.log = originalConsole.log;
58
- console.warn = originalConsole.warn;
59
- console.error = originalConsole.error;
60
- console.info = originalConsole.info;
61
- console.debug = originalConsole.debug;
62
- });
63
-
64
- describe('Basic functionality', () => {
65
- it('should create logger with default format (unchained)', () => {
66
- const logger = createLogger('test-module');
67
- assert(logger);
68
- assert(typeof logger.debug === 'function');
69
- assert(typeof logger.info === 'function');
70
- assert(typeof logger.warn === 'function');
71
- assert(typeof logger.error === 'function');
72
- });
73
-
74
- it('should create logger with json format', () => {
75
- process.env.UNCHAINED_LOG_FORMAT = 'json';
76
- const logger = createLogger('test-module-json');
77
- assert(logger);
78
-
79
- // Clear previous output
80
- consoleOutput = [];
81
-
82
- // The JSON logger expects two arguments: message and metadata object
83
- logger.info('test message', { metadata: 'value' });
84
-
85
- // Check if we got output
86
- assert(consoleOutput.length > 0, 'No console output captured');
87
-
88
- // The output should be a single JSON string
89
- const lastOutput = consoleOutput[consoleOutput.length - 1];
90
-
91
- // Verify JSON format output
92
- const parsed = JSON.parse(lastOutput);
93
- assert(parsed.level === 'INFO');
94
- assert(parsed.name === 'test-module-json');
95
- assert(parsed.message === 'test message');
96
- assert(parsed.metadata === 'value');
97
- assert(parsed.timestamp); // Should have a timestamp
98
- });
99
-
100
- it('should throw error with invalid log format', () => {
101
- process.env.UNCHAINED_LOG_FORMAT = 'invalid';
102
- assert.throws(() => {
103
- createLogger('test-module-invalid');
104
- }, /UNCHAINED_LOG_FORMAT is invalid/);
105
- });
106
- });
107
-
108
- describe('DEBUG environment variable patterns', () => {
109
- it('should respect simple DEBUG pattern', () => {
110
- process.env.DEBUG = 'test-debug-module';
111
- process.env.LOG_LEVEL = 'info';
112
-
113
- const logger = createLogger('test-debug-module');
114
- logger.debug('debug message');
115
-
116
- // Debug message should appear
117
- assert(consoleOutput.some((output) => output.includes('debug message')));
118
- });
119
-
120
- it('should handle wildcard patterns', () => {
121
- process.env.DEBUG = 'test-*';
122
- process.env.LOG_LEVEL = 'info';
123
-
124
- const logger1 = createLogger('test-module-1');
125
- const logger2 = createLogger('test-module-2');
126
- const logger3 = createLogger('other-module');
127
-
128
- logger1.debug('debug1');
129
- logger2.debug('debug2');
130
- logger3.debug('debug3');
131
-
132
- // Only test-* modules should output debug
133
- assert(consoleOutput.some((output) => output.includes('debug1')));
134
- assert(consoleOutput.some((output) => output.includes('debug2')));
135
- assert(!consoleOutput.some((output) => output.includes('debug3')));
136
- });
137
-
138
- it('should handle exclusion patterns', () => {
139
- process.env.DEBUG = 'test-*,-test-exclude';
140
- process.env.LOG_LEVEL = 'info';
141
-
142
- const includedLogger = createLogger('test-included');
143
- const excludedLogger = createLogger('test-exclude');
144
-
145
- includedLogger.debug('included');
146
- excludedLogger.debug('excluded');
147
-
148
- assert(consoleOutput.some((output) => output.includes('included')));
149
- assert(!consoleOutput.some((output) => output.includes('excluded')));
150
- });
151
-
152
- it('should handle multiple patterns', () => {
153
- process.env.DEBUG = 'auth-*,payment-*,-payment-debug';
154
- process.env.LOG_LEVEL = 'info';
155
-
156
- const authLogger = createLogger('auth-service');
157
- const paymentLogger = createLogger('payment-service');
158
- const paymentDebugLogger = createLogger('payment-debug');
159
- const otherLogger = createLogger('other-service');
160
-
161
- authLogger.debug('auth debug');
162
- paymentLogger.debug('payment debug');
163
- paymentDebugLogger.debug('payment-debug debug');
164
- otherLogger.debug('other debug');
165
-
166
- assert(consoleOutput.some((output) => output.includes('auth debug')));
167
- assert(consoleOutput.some((output) => output.includes('payment debug')));
168
- assert(!consoleOutput.some((output) => output.includes('payment-debug debug')));
169
- assert(!consoleOutput.some((output) => output.includes('other debug')));
170
- });
171
-
172
- it('should handle colon in module names', () => {
173
- process.env.DEBUG = 'module:*';
174
-
175
- const logger1 = createLogger('module:sub1');
176
- const logger2 = createLogger('module:sub2');
177
- const logger3 = createLogger('other-module');
178
-
179
- logger1.debug('debug1');
180
- logger2.debug('debug2');
181
- logger3.debug('debug3');
182
-
183
- assert(consoleOutput.some((output) => output.includes('debug1')));
184
- assert(consoleOutput.some((output) => output.includes('debug2')));
185
- assert(!consoleOutput.some((output) => output.includes('debug3')));
186
- });
187
- });
188
-
189
- describe('Circular dependencies in JSON format', () => {
190
- it('should handle circular references in objects', () => {
191
- process.env.UNCHAINED_LOG_FORMAT = 'json';
192
- const logger = createLogger('circular-test');
193
-
194
- const obj1: any = { name: 'obj1' };
195
- const obj2: any = { name: 'obj2', ref: obj1 };
196
- obj1.ref = obj2; // Create circular reference
197
-
198
- consoleOutput = [];
199
- logger.info('circular object', { data: obj1 });
200
-
201
- // Should not throw and should produce valid JSON
202
- assert(consoleOutput.length > 0, 'No console output captured');
203
- const lastOutput = consoleOutput[consoleOutput.length - 1];
204
-
205
- assert.doesNotThrow(() => JSON.parse(lastOutput));
206
-
207
- const parsed = JSON.parse(lastOutput);
208
- assert(parsed.message === 'circular object');
209
- assert(parsed.data.name === 'obj1');
210
- // Circular reference should be replaced with "[Circular]"
211
- assert(parsed.data.ref.ref === '[Circular]');
212
- });
213
-
214
- it('should handle self-referencing objects', () => {
215
- process.env.UNCHAINED_LOG_FORMAT = 'json';
216
- const logger = createLogger('self-ref-test');
217
-
218
- const obj: any = { name: 'self' };
219
- obj.self = obj;
220
-
221
- consoleOutput = [];
222
- logger.info('self reference', { data: obj });
223
-
224
- const lastOutput = consoleOutput[consoleOutput.length - 1];
225
- assert.doesNotThrow(() => JSON.parse(lastOutput));
226
-
227
- const parsed = JSON.parse(lastOutput);
228
- assert(parsed.data.name === 'self');
229
- assert(parsed.data.self === '[Circular]');
230
- });
231
-
232
- it('should handle deeply nested circular references', () => {
233
- process.env.UNCHAINED_LOG_FORMAT = 'json';
234
- const logger = createLogger('deep-circular-test');
235
-
236
- const obj: any = {
237
- level1: {
238
- level2: {
239
- level3: {},
240
- },
241
- },
242
- };
243
- obj.level1.level2.level3.circular = obj;
244
-
245
- consoleOutput = [];
246
- logger.info('deep circular', { data: obj });
247
-
248
- const lastOutput = consoleOutput[consoleOutput.length - 1];
249
- assert.doesNotThrow(() => JSON.parse(lastOutput));
250
-
251
- const parsed = JSON.parse(lastOutput);
252
- assert(parsed.data.level1.level2.level3.circular === '[Circular]');
253
- });
254
- });
255
-
256
- describe('Error object logging', () => {
257
- it('should log Error objects in unchained format', () => {
258
- const logger = createLogger('error-test');
259
- const error = new Error('Test error message');
260
-
261
- logger.error('Error occurred', error);
262
-
263
- assert(consoleOutput.some((output) => output.includes('Error occurred')));
264
- });
265
-
266
- it('should log Error objects in JSON format', () => {
267
- process.env.UNCHAINED_LOG_FORMAT = 'json';
268
- const logger = createLogger('error-json-test');
269
-
270
- const error = new Error('Test error message');
271
- error.stack = 'Error: Test error message\n at Test.fn';
272
-
273
- consoleOutput = [];
274
- logger.error('Error occurred', { error });
275
-
276
- const lastOutput = consoleOutput[consoleOutput.length - 1];
277
- const parsed = JSON.parse(lastOutput);
278
- assert(parsed.message === 'Error occurred');
279
- assert(parsed.level === 'ERROR');
280
- // Error objects are serialized as empty objects by default in JSON
281
- assert(typeof parsed.error === 'object');
282
- assert(Object.keys(parsed.error).length === 0);
283
- });
284
-
285
- it('should handle custom error properties', () => {
286
- process.env.UNCHAINED_LOG_FORMAT = 'json';
287
- const logger = createLogger('custom-error-test');
288
-
289
- class CustomError extends Error {
290
- code: string;
291
- statusCode: number;
292
-
293
- constructor(message: string, code: string, statusCode: number) {
294
- super(message);
295
- this.code = code;
296
- this.statusCode = statusCode;
297
- }
298
- }
299
-
300
- const error = new CustomError('Custom error', 'ERR_CUSTOM', 400);
301
-
302
- consoleOutput = [];
303
- logger.error('Custom error occurred', { error, additionalInfo: 'extra' });
304
-
305
- const lastOutput = consoleOutput[consoleOutput.length - 1];
306
- const parsed = JSON.parse(lastOutput);
307
- assert(parsed.additionalInfo === 'extra');
308
- assert(parsed.level === 'ERROR');
309
- // Error objects including custom errors are serialized as empty objects
310
- assert(typeof parsed.error === 'object');
311
- });
312
-
313
- it('should handle Error with circular references', () => {
314
- process.env.UNCHAINED_LOG_FORMAT = 'json';
315
- const logger = createLogger('circular-error-test');
316
-
317
- const error: any = new Error('Circular error');
318
- error.circular = error;
319
-
320
- consoleOutput = [];
321
- logger.error('Circular error', { error });
322
-
323
- const lastOutput = consoleOutput[consoleOutput.length - 1];
324
- assert.doesNotThrow(() => JSON.parse(lastOutput));
325
-
326
- const parsed = JSON.parse(lastOutput);
327
- assert(parsed.level === 'ERROR');
328
- // Error is serialized as empty object, circular property is ignored
329
- assert(typeof parsed.error === 'object');
330
- });
331
- });
332
-
333
- describe('Edge cases and special values', () => {
334
- it('should handle null values', () => {
335
- process.env.UNCHAINED_LOG_FORMAT = 'json';
336
- const logger = createLogger('null-test');
337
-
338
- consoleOutput = [];
339
- logger.info('null value', { value: null });
340
-
341
- const lastOutput = consoleOutput[consoleOutput.length - 1];
342
- const parsed = JSON.parse(lastOutput);
343
- assert(parsed.value === null);
344
- assert(parsed.message === 'null value');
345
- });
346
-
347
- it('should handle undefined values', () => {
348
- process.env.UNCHAINED_LOG_FORMAT = 'json';
349
- const logger = createLogger('undefined-test');
350
-
351
- consoleOutput = [];
352
- logger.info('undefined value', { value: undefined });
353
-
354
- const lastOutput = consoleOutput[consoleOutput.length - 1];
355
- const parsed = JSON.parse(lastOutput);
356
- // undefined should be omitted in JSON
357
- assert(!('value' in parsed));
358
- assert(parsed.message === 'undefined value');
359
- });
360
-
361
- it('should handle NaN and Infinity', () => {
362
- process.env.UNCHAINED_LOG_FORMAT = 'json';
363
- const logger = createLogger('nan-infinity-test');
364
-
365
- consoleOutput = [];
366
- logger.info('special numbers', {
367
- nan: NaN,
368
- infinity: Infinity,
369
- negInfinity: -Infinity,
370
- });
371
-
372
- const lastOutput = consoleOutput[consoleOutput.length - 1];
373
- const parsed = JSON.parse(lastOutput);
374
- // NaN and Infinity are serialized as null by safe-stable-stringify
375
- assert(parsed.nan === null);
376
- assert(parsed.infinity === null);
377
- assert(parsed.negInfinity === null);
378
- });
379
-
380
- it('should handle BigInt values', () => {
381
- process.env.UNCHAINED_LOG_FORMAT = 'json';
382
- const logger = createLogger('bigint-test');
383
-
384
- consoleOutput = [];
385
- logger.info('bigint value', { value: BigInt('9007199254740993') });
386
-
387
- const lastOutput = consoleOutput[consoleOutput.length - 1];
388
- const parsed = JSON.parse(lastOutput);
389
- // BigInt is serialized as string by safe-stable-stringify
390
- assert(typeof parsed.value === 'string');
391
- assert(parsed.value === '9007199254740993');
392
- });
393
-
394
- it('should handle Symbol values', () => {
395
- process.env.UNCHAINED_LOG_FORMAT = 'json';
396
- const logger = createLogger('symbol-test');
397
-
398
- const sym = Symbol('test');
399
-
400
- consoleOutput = [];
401
- logger.info('symbol value', { value: sym });
402
-
403
- const lastOutput = consoleOutput[consoleOutput.length - 1];
404
- const parsed = JSON.parse(lastOutput);
405
- // Symbols should be omitted in JSON
406
- assert(!('value' in parsed));
407
- });
408
-
409
- it('should handle functions', () => {
410
- process.env.UNCHAINED_LOG_FORMAT = 'json';
411
- const logger = createLogger('function-test');
412
-
413
- const fn = () => 'test';
414
-
415
- consoleOutput = [];
416
- logger.info('function value', { value: fn });
417
-
418
- const lastOutput = consoleOutput[consoleOutput.length - 1];
419
- const parsed = JSON.parse(lastOutput);
420
- // Functions should be omitted in JSON
421
- assert(!('value' in parsed));
422
- });
423
-
424
- it('should handle Date objects', () => {
425
- process.env.UNCHAINED_LOG_FORMAT = 'json';
426
- const logger = createLogger('date-test');
427
-
428
- const date = new Date('2023-01-01T00:00:00.000Z');
429
-
430
- consoleOutput = [];
431
- logger.info('date value', { value: date });
432
-
433
- const lastOutput = consoleOutput[consoleOutput.length - 1];
434
- const parsed = JSON.parse(lastOutput);
435
- // Dates are serialized as ISO strings
436
- assert(parsed.value === '2023-01-01T00:00:00.000Z');
437
- });
438
-
439
- it('should handle RegExp objects', () => {
440
- process.env.UNCHAINED_LOG_FORMAT = 'json';
441
- const logger = createLogger('regexp-test');
442
-
443
- const regex = /test/gi;
444
-
445
- consoleOutput = [];
446
- logger.info('regex value', { value: regex });
447
-
448
- const lastOutput = consoleOutput[consoleOutput.length - 1];
449
- const parsed = JSON.parse(lastOutput);
450
- // RegExp objects are serialized as empty objects in JSON
451
- assert(typeof parsed.value === 'object' && Object.keys(parsed.value).length === 0);
452
- });
453
-
454
- it('should handle empty strings', () => {
455
- process.env.UNCHAINED_LOG_FORMAT = 'json';
456
- const logger = createLogger('empty-string-test');
457
-
458
- consoleOutput = [];
459
- logger.info('', { emptyKey: '' });
460
-
461
- const lastOutput = consoleOutput[consoleOutput.length - 1];
462
- const parsed = JSON.parse(lastOutput);
463
- assert(parsed.message === '');
464
- assert(parsed.emptyKey === '');
465
- });
466
-
467
- it('should handle arrays with mixed types', () => {
468
- process.env.UNCHAINED_LOG_FORMAT = 'json';
469
- const logger = createLogger('mixed-array-test');
470
-
471
- consoleOutput = [];
472
- logger.info('mixed array', {
473
- array: [1, 'string', null, undefined, { nested: true }, [1, 2, 3]],
474
- });
475
-
476
- const lastOutput = consoleOutput[consoleOutput.length - 1];
477
- const parsed = JSON.parse(lastOutput);
478
- assert(Array.isArray(parsed.array));
479
- assert(parsed.array[0] === 1);
480
- assert(parsed.array[1] === 'string');
481
- assert(parsed.array[2] === null);
482
- assert(parsed.array[3] === null); // undefined becomes null in JSON
483
- assert(parsed.array[4].nested === true);
484
- assert(Array.isArray(parsed.array[5]));
485
- });
486
- });
487
-
488
- describe('Different log levels', () => {
489
- it('should respect LOG_LEVEL environment variable', () => {
490
- process.env.LOG_LEVEL = 'error';
491
- const logger = createLogger('log-level-test');
492
-
493
- logger.debug('debug message');
494
- logger.info('info message');
495
- logger.warn('warn message');
496
- logger.error('error message');
497
-
498
- // Only error messages should appear
499
- assert(!consoleOutput.some((output) => output.includes('debug message')));
500
- assert(!consoleOutput.some((output) => output.includes('info message')));
501
- assert(!consoleOutput.some((output) => output.includes('warn message')));
502
- assert(consoleOutput.some((output) => output.includes('error message')));
503
- });
504
-
505
- it('should handle all log levels', () => {
506
- process.env.LOG_LEVEL = 'verbose';
507
- const logger = createLogger('all-levels-test');
508
-
509
- logger.trace('trace message');
510
- logger.debug('debug message');
511
- logger.info('info message');
512
- logger.warn('warn message');
513
- logger.error('error message');
514
-
515
- // All messages should appear
516
- assert(consoleOutput.some((output) => output.includes('trace message')));
517
- assert(consoleOutput.some((output) => output.includes('debug message')));
518
- assert(consoleOutput.some((output) => output.includes('info message')));
519
- assert(consoleOutput.some((output) => output.includes('warn message')));
520
- assert(consoleOutput.some((output) => output.includes('error message')));
521
- });
522
-
523
- it('should handle case-insensitive log levels', () => {
524
- process.env.LOG_LEVEL = 'WaRn';
525
- const logger = createLogger('case-test');
526
-
527
- consoleOutput = [];
528
- logger.info('info message');
529
- logger.warn('warn message');
530
- logger.error('error message');
531
-
532
- assert(!consoleOutput.some((output) => output.includes('info message')));
533
- assert(consoleOutput.some((output) => output.includes('warn message')));
534
- assert(consoleOutput.some((output) => output.includes('error message')));
535
- });
536
- });
537
-
538
- describe('Multiple logger instances', () => {
539
- it('should maintain separate loggers for different modules', () => {
540
- process.env.DEBUG = 'module1';
541
-
542
- const logger1 = createLogger('module1');
543
- const logger2 = createLogger('module2');
544
-
545
- logger1.debug('module1 debug');
546
- logger2.debug('module2 debug');
547
-
548
- assert(consoleOutput.some((output) => output.includes('module1 debug')));
549
- assert(!consoleOutput.some((output) => output.includes('module2 debug')));
550
- });
551
-
552
- it('should share format configuration across instances', () => {
553
- process.env.UNCHAINED_LOG_FORMAT = 'json';
554
-
555
- consoleOutput = [];
556
- const logger1 = createLogger('json-module1');
557
- const logger2 = createLogger('json-module2');
558
-
559
- logger1.info('message1');
560
- logger2.info('message2');
561
-
562
- // Both should output JSON
563
- assert(consoleOutput.length === 2);
564
-
565
- const parsed1 = JSON.parse(consoleOutput[0]);
566
- const parsed2 = JSON.parse(consoleOutput[1]);
567
-
568
- assert(parsed1.name === 'json-module1');
569
- assert(parsed1.message === 'message1');
570
- assert(parsed1.level === 'INFO');
571
-
572
- assert(parsed2.name === 'json-module2');
573
- assert(parsed2.message === 'message2');
574
- assert(parsed2.level === 'INFO');
575
- });
576
- });
577
-
578
- describe('Security', () => {
579
- it('should guard against prototype pollution via __proto__', () => {
580
- process.env.UNCHAINED_LOG_FORMAT = 'json';
581
- const logger = createLogger('prototype-pollution-test');
582
-
583
- const maliciousPayload = JSON.parse('{"__proto__": {"polluted": "value"}}');
584
-
585
- consoleOutput = [];
586
- logger.info('test', maliciousPayload);
587
-
588
- const lastOutput = consoleOutput[consoleOutput.length - 1];
589
- const parsed = JSON.parse(lastOutput);
590
-
591
- // Should not pollute Object prototype
592
- assert.strictEqual('polluted' in Object.prototype, false);
593
- assert.strictEqual('polluted' in {}, false);
594
- // The dangerous __proto__ key should be filtered out
595
- // Check that we only have expected keys
596
- const keys = Object.keys(parsed);
597
- assert.strictEqual(keys.includes('__proto__'), false);
598
- });
599
-
600
- it('should guard against prototype pollution via constructor', () => {
601
- process.env.UNCHAINED_LOG_FORMAT = 'json';
602
- const logger = createLogger('constructor-pollution-test');
603
-
604
- const maliciousPayload = { constructor: { polluted: 'value' } };
605
-
606
- consoleOutput = [];
607
- logger.info('test', maliciousPayload);
608
-
609
- const lastOutput = consoleOutput[consoleOutput.length - 1];
610
- const parsed = JSON.parse(lastOutput);
611
-
612
- // The dangerous constructor key should be filtered out
613
- const keys = Object.keys(parsed);
614
- assert.strictEqual(keys.includes('constructor'), false);
615
- // Verify the constructor wasn't overridden with the malicious payload
616
- assert.notStrictEqual(parsed.constructor, maliciousPayload.constructor);
617
- });
618
-
619
- it('should guard against prototype pollution via prototype', () => {
620
- process.env.UNCHAINED_LOG_FORMAT = 'json';
621
- const logger = createLogger('prototype-key-test');
622
-
623
- const maliciousPayload = { prototype: { polluted: 'value' } };
624
-
625
- consoleOutput = [];
626
- logger.info('test', maliciousPayload);
627
-
628
- const lastOutput = consoleOutput[consoleOutput.length - 1];
629
- const parsed = JSON.parse(lastOutput);
630
-
631
- // The dangerous prototype key should be filtered out
632
- const keys = Object.keys(parsed);
633
- assert.strictEqual(keys.includes('prototype'), false);
634
- });
635
-
636
- it('should allow safe keys while blocking dangerous ones', () => {
637
- process.env.UNCHAINED_LOG_FORMAT = 'json';
638
- const logger = createLogger('mixed-keys-test');
639
-
640
- const mixedPayload = {
641
- safeKey: 'safe value',
642
- __proto__: { dangerous: 'value' },
643
- anotherSafe: 123,
644
- constructor: { dangerous: 'value' },
645
- validKey: true,
646
- prototype: { dangerous: 'value' },
647
- };
648
-
649
- consoleOutput = [];
650
- logger.info('test', mixedPayload);
651
-
652
- const lastOutput = consoleOutput[consoleOutput.length - 1];
653
- const parsed = JSON.parse(lastOutput);
654
-
655
- // Safe keys should be present
656
- assert.strictEqual(parsed.safeKey, 'safe value');
657
- assert.strictEqual(parsed.anotherSafe, 123);
658
- assert.strictEqual(parsed.validKey, true);
659
-
660
- // Dangerous keys should be filtered out
661
- const keys = Object.keys(parsed);
662
- assert.strictEqual(keys.includes('__proto__'), false);
663
- assert.strictEqual(keys.includes('constructor'), false);
664
- assert.strictEqual(keys.includes('prototype'), false);
665
- });
666
- });
667
-
668
- describe('Format edge cases', () => {
669
- it('should handle very long messages', () => {
670
- process.env.UNCHAINED_LOG_FORMAT = 'json';
671
- const logger = createLogger('long-message-test');
672
-
673
- consoleOutput = [];
674
- const longMessage = 'x'.repeat(10000);
675
- logger.info(longMessage);
676
-
677
- const lastOutput = consoleOutput[consoleOutput.length - 1];
678
- const parsed = JSON.parse(lastOutput);
679
- assert(parsed.message.length === 10000);
680
- assert(parsed.message === longMessage);
681
- });
682
-
683
- it('should handle special characters in messages', () => {
684
- process.env.UNCHAINED_LOG_FORMAT = 'json';
685
- const logger = createLogger('special-chars-test');
686
-
687
- consoleOutput = [];
688
- logger.info('Special chars: \n\r\t"\'\\', {
689
- data: 'Line 1\nLine 2\tTabbed',
690
- });
691
-
692
- const lastOutput = consoleOutput[consoleOutput.length - 1];
693
- const parsed = JSON.parse(lastOutput);
694
- assert(parsed.message.includes('\n'));
695
- assert(parsed.message.includes('\r'));
696
- assert(parsed.message.includes('\t'));
697
- assert(parsed.message.includes('"'));
698
- assert(parsed.message.includes("'"));
699
- assert(parsed.message.includes('\\'));
700
- assert(parsed.data.includes('\n'));
701
- assert(parsed.data.includes('\t'));
702
- });
703
-
704
- it('should handle unicode characters', () => {
705
- process.env.UNCHAINED_LOG_FORMAT = 'json';
706
- const logger = createLogger('unicode-test');
707
-
708
- consoleOutput = [];
709
- logger.info('Unicode: 🔥 emoji, 中文, العربية', {
710
- emoji: '🚀',
711
- chinese: '你好',
712
- arabic: 'مرحبا',
713
- });
714
-
715
- const lastOutput = consoleOutput[consoleOutput.length - 1];
716
- const parsed = JSON.parse(lastOutput);
717
- assert(parsed.message.includes('🔥'));
718
- assert(parsed.message.includes('中文'));
719
- assert(parsed.message.includes('العربية'));
720
- assert(parsed.emoji === '🚀');
721
- assert(parsed.chinese === '你好');
722
- assert(parsed.arabic === 'مرحبا');
723
- });
724
- });
725
- });