@leanstacks/lambda-utils 0.1.0-alpha.3 → 0.1.0-alpha.5

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.
@@ -0,0 +1,400 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import pino from 'pino';
3
+ import { CloudwatchLogFormatter, pinoLambdaDestination, StructuredLogFormatter } from 'pino-lambda';
4
+ import { Logger, withRequestTracking } from './logger';
5
+
6
+ // Mock pino-lambda module
7
+ jest.mock('pino-lambda');
8
+
9
+ // Mock pino module
10
+ jest.mock('pino');
11
+
12
+ describe('Logger', () => {
13
+ // Setup and cleanup
14
+ beforeEach(() => {
15
+ jest.clearAllMocks();
16
+ });
17
+
18
+ afterEach(() => {
19
+ jest.clearAllMocks();
20
+ });
21
+
22
+ describe('withRequestTracking', () => {
23
+ it('should be exported from logger module', () => {
24
+ // Arrange
25
+ // withRequestTracking is exported from logger.ts and is the result of calling lambdaRequestTracker()
26
+ // from pino-lambda. Jest mocks mean it will be the mocked value.
27
+
28
+ // Act & Assert
29
+ // We just verify that it was exported (defined by the import statement at the top)
30
+ // The actual functionality of lambdaRequestTracker is tested in pino-lambda
31
+ expect(typeof withRequestTracking === 'function' || withRequestTracking === undefined).toBe(true);
32
+ });
33
+ });
34
+
35
+ describe('constructor', () => {
36
+ it('should create Logger with default configuration', () => {
37
+ // Arrange
38
+ const mockLogger = { info: jest.fn() };
39
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
40
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
41
+
42
+ // Act
43
+ const logger = new Logger();
44
+
45
+ // Assert
46
+ expect(logger).toBeDefined();
47
+ });
48
+
49
+ it('should create Logger with custom enabled configuration', () => {
50
+ // Arrange
51
+ const mockLogger = { info: jest.fn() };
52
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
53
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
54
+
55
+ // Act
56
+ const logger = new Logger({ enabled: false });
57
+ const _instance = logger.instance;
58
+
59
+ // Assert
60
+ expect(pino).toHaveBeenCalledWith(
61
+ expect.objectContaining({
62
+ enabled: false,
63
+ }),
64
+ expect.anything(),
65
+ );
66
+ });
67
+
68
+ it('should create Logger with custom log level configuration', () => {
69
+ // Arrange
70
+ const mockLogger = { info: jest.fn() };
71
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
72
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
73
+
74
+ // Act
75
+ const logger = new Logger({ level: 'debug' });
76
+ const _instance = logger.instance;
77
+
78
+ // Assert
79
+ expect(pino).toHaveBeenCalledWith(
80
+ expect.objectContaining({
81
+ level: 'debug',
82
+ }),
83
+ expect.anything(),
84
+ );
85
+ });
86
+
87
+ it('should create Logger with custom format configuration (json)', () => {
88
+ // Arrange
89
+ const mockLogger = { info: jest.fn() };
90
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
91
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
92
+
93
+ // Act
94
+ const logger = new Logger({ format: 'json' });
95
+ const _instance = logger.instance;
96
+
97
+ // Assert
98
+ expect(StructuredLogFormatter).toHaveBeenCalled();
99
+ });
100
+
101
+ it('should create Logger with custom format configuration (text)', () => {
102
+ // Arrange
103
+ const mockLogger = { info: jest.fn() };
104
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
105
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
106
+
107
+ // Act
108
+ const logger = new Logger({ format: 'text' });
109
+ const _instance = logger.instance;
110
+
111
+ // Assert
112
+ expect(CloudwatchLogFormatter).toHaveBeenCalled();
113
+ });
114
+
115
+ it('should merge provided config with defaults', () => {
116
+ // Arrange
117
+ const mockLogger = { info: jest.fn() };
118
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
119
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
120
+
121
+ // Act
122
+ const logger = new Logger({ level: 'error' });
123
+ const _instance = logger.instance;
124
+
125
+ // Assert
126
+ expect(pino).toHaveBeenCalledWith(
127
+ expect.objectContaining({
128
+ enabled: true,
129
+ level: 'error',
130
+ }),
131
+ expect.anything(),
132
+ );
133
+ });
134
+ });
135
+
136
+ describe('instance getter', () => {
137
+ it('should return a Pino logger instance', () => {
138
+ // Arrange
139
+ const mockLogger = { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() };
140
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
141
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
142
+ const logger = new Logger();
143
+
144
+ // Act
145
+ const instance = logger.instance;
146
+
147
+ // Assert
148
+ expect(instance).toBe(mockLogger);
149
+ expect(instance).toHaveProperty('info');
150
+ expect(instance).toHaveProperty('warn');
151
+ expect(instance).toHaveProperty('error');
152
+ expect(instance).toHaveProperty('debug');
153
+ });
154
+
155
+ it('should create logger instance only once (lazy initialization)', () => {
156
+ // Arrange
157
+ const mockLogger = { info: jest.fn() };
158
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
159
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
160
+ const logger = new Logger();
161
+
162
+ // Act
163
+ const instance1 = logger.instance;
164
+ const instance2 = logger.instance;
165
+
166
+ // Assert
167
+ expect(instance1).toBe(instance2);
168
+ expect(pino).toHaveBeenCalledTimes(1);
169
+ });
170
+
171
+ it('should configure pino with enabled flag', () => {
172
+ // Arrange
173
+ const mockLogger = { info: jest.fn() };
174
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
175
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
176
+
177
+ // Act
178
+ const logger = new Logger({ enabled: false });
179
+ const _instance = logger.instance;
180
+
181
+ // Assert
182
+ expect(pino).toHaveBeenCalledWith(
183
+ expect.objectContaining({
184
+ enabled: false,
185
+ }),
186
+ expect.anything(),
187
+ );
188
+ });
189
+
190
+ it('should configure pino with log level', () => {
191
+ // Arrange
192
+ const mockLogger = { info: jest.fn() };
193
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
194
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
195
+
196
+ // Act
197
+ const logger = new Logger({ level: 'warn' });
198
+ const _instance = logger.instance;
199
+
200
+ // Assert
201
+ expect(pino).toHaveBeenCalledWith(
202
+ expect.objectContaining({
203
+ level: 'warn',
204
+ }),
205
+ expect.anything(),
206
+ );
207
+ });
208
+
209
+ it('should call pinoLambdaDestination with selected formatter', () => {
210
+ // Arrange
211
+ const mockLogger = { info: jest.fn() };
212
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
213
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
214
+
215
+ // Act
216
+ const logger = new Logger({ format: 'json' });
217
+ const _instance = logger.instance;
218
+
219
+ // Assert
220
+ expect(pinoLambdaDestination).toHaveBeenCalledWith(
221
+ expect.objectContaining({
222
+ formatter: expect.any(StructuredLogFormatter),
223
+ }),
224
+ );
225
+ });
226
+
227
+ it('should use StructuredLogFormatter when format is json', () => {
228
+ // Arrange
229
+ const mockLogger = { info: jest.fn() };
230
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
231
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
232
+
233
+ // Act
234
+ const logger = new Logger({ format: 'json' });
235
+ const _instance = logger.instance;
236
+
237
+ // Assert
238
+ expect(StructuredLogFormatter).toHaveBeenCalled();
239
+ });
240
+
241
+ it('should use CloudwatchLogFormatter when format is text', () => {
242
+ // Arrange
243
+ const mockLogger = { info: jest.fn() };
244
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
245
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
246
+
247
+ // Act
248
+ const logger = new Logger({ format: 'text' });
249
+ const _instance = logger.instance;
250
+
251
+ // Assert
252
+ expect(CloudwatchLogFormatter).toHaveBeenCalled();
253
+ });
254
+ });
255
+
256
+ describe('Logger configurations', () => {
257
+ it('should support all log levels', () => {
258
+ // Arrange
259
+ const mockLogger = { info: jest.fn() };
260
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
261
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
262
+ const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['debug', 'info', 'warn', 'error'];
263
+
264
+ // Act & Assert
265
+ levels.forEach((level) => {
266
+ jest.clearAllMocks();
267
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
268
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
269
+
270
+ const logger = new Logger({ level });
271
+ const _instance = logger.instance;
272
+
273
+ expect(pino).toHaveBeenCalledWith(
274
+ expect.objectContaining({
275
+ level,
276
+ }),
277
+ expect.anything(),
278
+ );
279
+ });
280
+ });
281
+
282
+ it('should support both json and text formats', () => {
283
+ // Arrange
284
+ const mockLogger = { info: jest.fn() };
285
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
286
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
287
+
288
+ // Act
289
+ const jsonLogger = new Logger({ format: 'json' });
290
+ const _jsonInstance = jsonLogger.instance;
291
+ const structuredFormatterCallCount = (StructuredLogFormatter as jest.Mock).mock.calls.length;
292
+
293
+ jest.clearAllMocks();
294
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
295
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
296
+
297
+ const textLogger = new Logger({ format: 'text' });
298
+ const _textInstance = textLogger.instance;
299
+
300
+ // Assert
301
+ expect(structuredFormatterCallCount).toBeGreaterThan(0);
302
+ expect(CloudwatchLogFormatter).toHaveBeenCalled();
303
+ });
304
+
305
+ it('should support enabled and disabled logging', () => {
306
+ // Arrange
307
+ const mockLogger = { info: jest.fn() };
308
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
309
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
310
+
311
+ // Act
312
+ const enabledLogger = new Logger({ enabled: true });
313
+ const _enabledInstance = enabledLogger.instance;
314
+ const firstCallArgs = jest.mocked(pino).mock.calls[0];
315
+
316
+ jest.clearAllMocks();
317
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
318
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
319
+
320
+ const disabledLogger = new Logger({ enabled: false });
321
+ const _disabledInstance = disabledLogger.instance;
322
+ const secondCallArgs = jest.mocked(pino).mock.calls[0];
323
+
324
+ // Assert
325
+ expect(firstCallArgs[0]).toEqual(expect.objectContaining({ enabled: true }));
326
+ expect(secondCallArgs[0]).toEqual(expect.objectContaining({ enabled: false }));
327
+ });
328
+ });
329
+
330
+ describe('integration scenarios', () => {
331
+ it('should create multiple logger instances with different configurations', () => {
332
+ // Arrange
333
+ const mockLogger1 = { info: jest.fn(), level: 'debug' };
334
+ const mockLogger2 = { info: jest.fn(), level: 'error' };
335
+ jest
336
+ .mocked(pino)
337
+ .mockReturnValueOnce(mockLogger1 as unknown as any)
338
+ .mockReturnValueOnce(mockLogger2 as unknown as any);
339
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
340
+
341
+ // Act
342
+ const debugLogger = new Logger({ level: 'debug', format: 'json' });
343
+ const errorLogger = new Logger({ level: 'error', format: 'text' });
344
+
345
+ const instance1 = debugLogger.instance;
346
+ const instance2 = errorLogger.instance;
347
+
348
+ // Assert
349
+ expect(instance1).toBe(mockLogger1);
350
+ expect(instance2).toBe(mockLogger2);
351
+ expect(pino).toHaveBeenCalledTimes(2);
352
+ });
353
+
354
+ it('should handle partial configuration overrides', () => {
355
+ // Arrange
356
+ const mockLogger = { info: jest.fn() };
357
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
358
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
359
+
360
+ // Act
361
+ const logger = new Logger({ level: 'warn' });
362
+ const _instance = logger.instance;
363
+
364
+ // Assert - should have custom level but default enabled and format
365
+ expect(pino).toHaveBeenCalledWith(
366
+ expect.objectContaining({
367
+ enabled: true,
368
+ level: 'warn',
369
+ }),
370
+ expect.anything(),
371
+ );
372
+ expect(StructuredLogFormatter).toHaveBeenCalled();
373
+ });
374
+
375
+ it('should handle full configuration override', () => {
376
+ // Arrange
377
+ const mockLogger = { info: jest.fn() };
378
+ jest.mocked(pino).mockReturnValue(mockLogger as unknown as any);
379
+ (pinoLambdaDestination as jest.Mock).mockReturnValue({});
380
+
381
+ // Act
382
+ const logger = new Logger({
383
+ enabled: false,
384
+ level: 'error',
385
+ format: 'text',
386
+ });
387
+ const _instance = logger.instance;
388
+
389
+ // Assert
390
+ expect(pino).toHaveBeenCalledWith(
391
+ expect.objectContaining({
392
+ enabled: false,
393
+ level: 'error',
394
+ }),
395
+ expect.anything(),
396
+ );
397
+ expect(CloudwatchLogFormatter).toHaveBeenCalled();
398
+ });
399
+ });
400
+ });