mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import {beforeAll, describe, expect, jest, test} from '@jest/globals'
2import * as Sentry from '@sentry/react-native'
3import {nanoid} from 'nanoid/non-secure'
4
5import {Logger, LogLevel, sentryTransport} from '#/logger'
6
7jest.mock('#/env', () => ({
8 IS_TEST: true,
9 IS_DEV: false,
10 IS_PROD: false,
11 /*
12 * Forces debug mode for tests using the default logger. Most tests create
13 * their own logger instance.
14 */
15 LOG_LEVEL: 'debug',
16 LOG_DEBUG: '',
17}))
18
19jest.mock('@sentry/react-native', () => ({
20 addBreadcrumb: jest.fn(),
21 captureException: jest.fn(),
22 captureMessage: jest.fn(),
23}))
24
25beforeAll(() => {
26 jest.useFakeTimers()
27})
28
29describe('general functionality', () => {
30 test('default params', () => {
31 const logger = new Logger()
32 expect(logger.enabled).toBeFalsy()
33 expect(logger.level).toEqual(LogLevel.Debug) // mocked above
34 })
35
36 test('can override default params', () => {
37 const logger = new Logger({
38 enabled: true,
39 level: LogLevel.Info,
40 })
41 expect(logger.enabled).toBeTruthy()
42 expect(logger.level).toEqual(LogLevel.Info)
43 })
44
45 test('disabled logger does not report', () => {
46 const logger = new Logger({
47 enabled: false,
48 level: LogLevel.Debug,
49 })
50
51 const mockTransport = jest.fn()
52
53 logger.addTransport(mockTransport)
54 logger.debug('message')
55
56 expect(mockTransport).not.toHaveBeenCalled()
57 })
58
59 test('disablement', () => {
60 const logger = new Logger({
61 enabled: true,
62 level: LogLevel.Debug,
63 })
64
65 logger.disable()
66
67 const mockTransport = jest.fn()
68
69 logger.addTransport(mockTransport)
70 logger.debug('message')
71
72 expect(mockTransport).not.toHaveBeenCalled()
73 })
74
75 test('passing debug contexts automatically enables debug mode', () => {
76 const logger = new Logger({debug: 'specific'})
77 expect(logger.level).toEqual(LogLevel.Debug)
78 })
79
80 test('supports extra metadata', () => {
81 const timestamp = Date.now()
82 const logger = new Logger({enabled: true})
83
84 const mockTransport = jest.fn()
85
86 logger.addTransport(mockTransport)
87
88 const extra = {foo: true}
89 logger.warn('message', extra)
90
91 expect(mockTransport).toHaveBeenCalledWith(
92 LogLevel.Warn,
93 'message',
94 extra,
95 timestamp,
96 )
97 })
98
99 test('supports nullish/falsy metadata', () => {
100 const timestamp = Date.now()
101 const logger = new Logger({enabled: true})
102
103 const mockTransport = jest.fn()
104
105 const remove = logger.addTransport(mockTransport)
106
107 // @ts-expect-error testing the JS case
108 logger.warn('a', null)
109 expect(mockTransport).toHaveBeenCalledWith(
110 LogLevel.Warn,
111 'a',
112 {},
113 timestamp,
114 )
115
116 // @ts-expect-error testing the JS case
117 logger.warn('b', false)
118 expect(mockTransport).toHaveBeenCalledWith(
119 LogLevel.Warn,
120 'b',
121 {},
122 timestamp,
123 )
124
125 // @ts-expect-error testing the JS case
126 logger.warn('c', 0)
127 expect(mockTransport).toHaveBeenCalledWith(
128 LogLevel.Warn,
129 'c',
130 {},
131 timestamp,
132 )
133
134 remove()
135
136 logger.addTransport((level, message, metadata) => {
137 expect(typeof metadata).toEqual('object')
138 })
139
140 // @ts-expect-error testing the JS case
141 logger.warn('message', null)
142 })
143
144 test('sentryTransport', () => {
145 const message = 'message'
146 const timestamp = Date.now()
147 const sentryTimestamp = timestamp / 1000
148
149 sentryTransport(LogLevel.Debug, message, {}, timestamp)
150 expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
151 message,
152 data: {},
153 type: 'default',
154 level: LogLevel.Debug,
155 timestamp: sentryTimestamp,
156 })
157
158 sentryTransport(
159 LogLevel.Info,
160 message,
161 {type: 'info', prop: true},
162 timestamp,
163 )
164 expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
165 message,
166 data: {prop: true},
167 type: 'info',
168 level: LogLevel.Info,
169 timestamp: sentryTimestamp,
170 })
171
172 sentryTransport(LogLevel.Log, message, {}, timestamp)
173 expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
174 message,
175 data: {},
176 type: 'default',
177 level: 'debug', // Sentry bug, log becomes debug
178 timestamp: sentryTimestamp,
179 })
180 jest.runAllTimers()
181 expect(Sentry.captureMessage).toHaveBeenCalledWith(message, {
182 level: 'log',
183 tags: undefined,
184 extra: {},
185 })
186
187 sentryTransport(LogLevel.Warn, message, {}, timestamp)
188 expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
189 message,
190 data: {},
191 type: 'default',
192 level: 'warning',
193 timestamp: sentryTimestamp,
194 })
195 jest.runAllTimers()
196 expect(Sentry.captureMessage).toHaveBeenCalledWith(message, {
197 level: 'warning',
198 tags: undefined,
199 extra: {},
200 })
201
202 const e = new Error('error')
203 const tags = {
204 prop: 'prop',
205 }
206
207 sentryTransport(
208 LogLevel.Error,
209 e,
210 {
211 tags,
212 prop: true,
213 },
214 timestamp,
215 )
216
217 expect(Sentry.captureException).toHaveBeenCalledWith(e, {
218 tags,
219 extra: {
220 prop: true,
221 },
222 })
223 })
224
225 test('sentryTransport serializes errors', () => {
226 const message = 'message'
227 const timestamp = Date.now()
228 const sentryTimestamp = timestamp / 1000
229
230 sentryTransport(
231 LogLevel.Debug,
232 message,
233 {error: new Error('foo')},
234 timestamp,
235 )
236 expect(Sentry.addBreadcrumb).toHaveBeenCalledWith({
237 message,
238 data: {error: 'Error: foo'},
239 type: 'default',
240 level: LogLevel.Debug,
241 timestamp: sentryTimestamp,
242 })
243 })
244
245 test('add/remove transport', () => {
246 const timestamp = Date.now()
247 const logger = new Logger({enabled: true})
248 const mockTransport = jest.fn()
249
250 const remove = logger.addTransport(mockTransport)
251
252 logger.warn('warn')
253
254 remove()
255
256 logger.warn('warn')
257
258 // only called once bc it was removed
259 expect(mockTransport).toHaveBeenNthCalledWith(
260 1,
261 LogLevel.Warn,
262 'warn',
263 {},
264 timestamp,
265 )
266 })
267})
268
269describe('debug contexts', () => {
270 const mockTransport = jest.fn()
271
272 test('specific', () => {
273 const timestamp = Date.now()
274 const message = nanoid()
275 const logger = new Logger({
276 enabled: true,
277 debug: 'specific',
278 })
279
280 logger.addTransport(mockTransport)
281 logger.debug(message, {}, 'specific')
282
283 expect(mockTransport).toHaveBeenCalledWith(
284 LogLevel.Debug,
285 message,
286 {},
287 timestamp,
288 )
289 })
290
291 test('namespaced', () => {
292 const timestamp = Date.now()
293 const message = nanoid()
294 const logger = new Logger({
295 enabled: true,
296 debug: 'namespace*',
297 })
298
299 logger.addTransport(mockTransport)
300 logger.debug(message, {}, 'namespace')
301
302 expect(mockTransport).toHaveBeenCalledWith(
303 LogLevel.Debug,
304 message,
305 {},
306 timestamp,
307 )
308 })
309
310 test('ignores inactive', () => {
311 const timestamp = Date.now()
312 const message = nanoid()
313 const logger = new Logger({
314 enabled: true,
315 debug: 'namespace:foo:*',
316 })
317
318 logger.addTransport(mockTransport)
319 logger.debug(message, {}, 'namespace:bar:baz')
320
321 expect(mockTransport).not.toHaveBeenCalledWith(
322 LogLevel.Debug,
323 message,
324 {},
325 timestamp,
326 )
327 })
328})
329
330describe('supports levels', () => {
331 test('debug', () => {
332 const timestamp = Date.now()
333 const logger = new Logger({
334 enabled: true,
335 level: LogLevel.Debug,
336 })
337 const message = nanoid()
338 const mockTransport = jest.fn()
339
340 logger.addTransport(mockTransport)
341
342 logger.debug(message)
343 expect(mockTransport).toHaveBeenCalledWith(
344 LogLevel.Debug,
345 message,
346 {},
347 timestamp,
348 )
349
350 logger.info(message)
351 expect(mockTransport).toHaveBeenCalledWith(
352 LogLevel.Info,
353 message,
354 {},
355 timestamp,
356 )
357
358 logger.warn(message)
359 expect(mockTransport).toHaveBeenCalledWith(
360 LogLevel.Warn,
361 message,
362 {},
363 timestamp,
364 )
365
366 const e = new Error(message)
367 logger.error(e)
368 expect(mockTransport).toHaveBeenCalledWith(LogLevel.Error, e, {}, timestamp)
369 })
370
371 test('info', () => {
372 const timestamp = Date.now()
373 const logger = new Logger({
374 enabled: true,
375 level: LogLevel.Info,
376 })
377 const message = nanoid()
378 const mockTransport = jest.fn()
379
380 logger.addTransport(mockTransport)
381
382 logger.debug(message)
383 expect(mockTransport).not.toHaveBeenCalled()
384
385 logger.info(message)
386 expect(mockTransport).toHaveBeenCalledWith(
387 LogLevel.Info,
388 message,
389 {},
390 timestamp,
391 )
392 })
393
394 test('warn', () => {
395 const timestamp = Date.now()
396 const logger = new Logger({
397 enabled: true,
398 level: LogLevel.Warn,
399 })
400 const message = nanoid()
401 const mockTransport = jest.fn()
402
403 logger.addTransport(mockTransport)
404
405 logger.debug(message)
406 expect(mockTransport).not.toHaveBeenCalled()
407
408 logger.info(message)
409 expect(mockTransport).not.toHaveBeenCalled()
410
411 logger.warn(message)
412 expect(mockTransport).toHaveBeenCalledWith(
413 LogLevel.Warn,
414 message,
415 {},
416 timestamp,
417 )
418 })
419
420 test('error', () => {
421 const timestamp = Date.now()
422 const logger = new Logger({
423 enabled: true,
424 level: LogLevel.Error,
425 })
426 const message = nanoid()
427 const mockTransport = jest.fn()
428
429 logger.addTransport(mockTransport)
430
431 logger.debug(message)
432 expect(mockTransport).not.toHaveBeenCalled()
433
434 logger.info(message)
435 expect(mockTransport).not.toHaveBeenCalled()
436
437 logger.warn(message)
438 expect(mockTransport).not.toHaveBeenCalled()
439
440 const e = new Error('original message')
441 logger.error(e)
442 expect(mockTransport).toHaveBeenCalledWith(LogLevel.Error, e, {}, timestamp)
443 })
444})