@techstream/quark-core 1.1.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.
@@ -0,0 +1,1376 @@
1
+
2
+ > @techstream/quark-core@1.1.0 test /Users/david/Projects/quark/packages/core
3
+ > node --test $(find src -name '*.test.js')
4
+
5
+ TAP version 13
6
+ # Subtest: Auth Module
7
+ # Subtest: createAuthConfig returns valid config
8
+ ok 1 - createAuthConfig returns valid config
9
+ ---
10
+ duration_ms: 0.59825
11
+ ...
12
+ # Subtest: createAuthConfig throws when secret is missing
13
+ ok 2 - createAuthConfig throws when secret is missing
14
+ ---
15
+ duration_ms: 0.971959
16
+ ...
17
+ # Subtest: createAuthConfig with custom options
18
+ ok 3 - createAuthConfig with custom options
19
+ ---
20
+ duration_ms: 0.418417
21
+ ...
22
+ # Subtest: isAuthenticated returns true for valid session
23
+ ok 4 - isAuthenticated returns true for valid session
24
+ ---
25
+ duration_ms: 0.14075
26
+ ...
27
+ # Subtest: isAuthenticated returns false for invalid session
28
+ ok 5 - isAuthenticated returns false for invalid session
29
+ ---
30
+ duration_ms: 0.152584
31
+ ...
32
+ # Subtest: getUserId extracts user ID
33
+ ok 6 - getUserId extracts user ID
34
+ ---
35
+ duration_ms: 0.248208
36
+ ...
37
+ # Subtest: getUserId returns null when missing
38
+ ok 7 - getUserId returns null when missing
39
+ ---
40
+ duration_ms: 0.11875
41
+ ...
42
+ # Subtest: getUserEmail extracts user email
43
+ ok 8 - getUserEmail extracts user email
44
+ ---
45
+ duration_ms: 0.397875
46
+ ...
47
+ # Subtest: getUserEmail returns null when missing
48
+ ok 9 - getUserEmail returns null when missing
49
+ ---
50
+ duration_ms: 0.396833
51
+ ...
52
+ # Subtest: requireAuth returns user ID for authenticated session
53
+ ok 10 - requireAuth returns user ID for authenticated session
54
+ ---
55
+ duration_ms: 0.364417
56
+ ...
57
+ # Subtest: requireAuth throws error for unauthenticated session
58
+ ok 11 - requireAuth throws error for unauthenticated session
59
+ ---
60
+ duration_ms: 1.319792
61
+ ...
62
+ 1..11
63
+ ok 1 - Auth Module
64
+ ---
65
+ duration_ms: 7.596
66
+ ...
67
+ # Subtest: authorization
68
+ # Subtest: can()
69
+ # Subtest: returns true for admin with wildcard permissions
70
+ ok 1 - returns true for admin with wildcard permissions
71
+ ---
72
+ duration_ms: 1.981833
73
+ ...
74
+ # Subtest: returns true for editor with post actions
75
+ ok 2 - returns true for editor with post actions
76
+ ---
77
+ duration_ms: 0.387
78
+ ...
79
+ # Subtest: returns true for editor reading users
80
+ ok 3 - returns true for editor reading users
81
+ ---
82
+ duration_ms: 0.103625
83
+ ...
84
+ # Subtest: returns false for editor trying to delete users
85
+ ok 4 - returns false for editor trying to delete users
86
+ ---
87
+ duration_ms: 0.206708
88
+ ...
89
+ # Subtest: returns false for viewer trying to write
90
+ ok 5 - returns false for viewer trying to write
91
+ ---
92
+ duration_ms: 0.325583
93
+ ...
94
+ # Subtest: returns true for viewer reading posts and users
95
+ ok 6 - returns true for viewer reading posts and users
96
+ ---
97
+ duration_ms: 0.29475
98
+ ...
99
+ # Subtest: owner can update their own resource
100
+ ok 7 - owner can update their own resource
101
+ ---
102
+ duration_ms: 0.118
103
+ ...
104
+ # Subtest: owner can delete their own resource
105
+ ok 8 - owner can delete their own resource
106
+ ---
107
+ duration_ms: 0.066542
108
+ ...
109
+ # Subtest: non-owner cannot update another user's resource
110
+ ok 9 - non-owner cannot update another user's resource
111
+ ---
112
+ duration_ms: 0.168083
113
+ ...
114
+ # Subtest: ownership does not apply for non-owner actions
115
+ ok 10 - ownership does not apply for non-owner actions
116
+ ---
117
+ duration_ms: 0.20525
118
+ ...
119
+ # Subtest: applies defaultRole when user has no role
120
+ ok 11 - applies defaultRole when user has no role
121
+ ---
122
+ duration_ms: 0.063917
123
+ ...
124
+ 1..11
125
+ ok 1 - can()
126
+ ---
127
+ duration_ms: 4.926791
128
+ type: 'suite'
129
+ ...
130
+ # Subtest: authorize()
131
+ # Subtest: throws ForbiddenError when denied
132
+ ok 1 - throws ForbiddenError when denied
133
+ ---
134
+ duration_ms: 0.982416
135
+ ...
136
+ # Subtest: does not throw when allowed
137
+ ok 2 - does not throw when allowed
138
+ ---
139
+ duration_ms: 0.119834
140
+ ...
141
+ # Subtest: includes user and action information in error message
142
+ ok 3 - includes user and action information in error message
143
+ ---
144
+ duration_ms: 0.271875
145
+ ...
146
+ 1..3
147
+ ok 2 - authorize()
148
+ ---
149
+ duration_ms: 1.439833
150
+ type: 'suite'
151
+ ...
152
+ # Subtest: hasPermission()
153
+ # Subtest: returns true for exact match
154
+ ok 1 - returns true for exact match
155
+ ---
156
+ duration_ms: 0.095417
157
+ ...
158
+ # Subtest: returns false for no match
159
+ ok 2 - returns false for no match
160
+ ---
161
+ duration_ms: 0.055166
162
+ ...
163
+ # Subtest: returns true with resource wildcard
164
+ ok 3 - returns true with resource wildcard
165
+ ---
166
+ duration_ms: 0.078584
167
+ ...
168
+ # Subtest: returns true with action wildcard
169
+ ok 4 - returns true with action wildcard
170
+ ---
171
+ duration_ms: 0.052917
172
+ ...
173
+ # Subtest: returns false for unknown role
174
+ ok 5 - returns false for unknown role
175
+ ---
176
+ duration_ms: 0.050917
177
+ ...
178
+ 1..5
179
+ ok 3 - hasPermission()
180
+ ---
181
+ duration_ms: 1.659917
182
+ type: 'suite'
183
+ ...
184
+ # Subtest: getRolePermissions()
185
+ # Subtest: returns permissions for a valid role
186
+ ok 1 - returns permissions for a valid role
187
+ ---
188
+ duration_ms: 0.200041
189
+ ...
190
+ # Subtest: returns empty array for unknown role
191
+ ok 2 - returns empty array for unknown role
192
+ ---
193
+ duration_ms: 0.051
194
+ ...
195
+ 1..2
196
+ ok 4 - getRolePermissions()
197
+ ---
198
+ duration_ms: 0.290208
199
+ type: 'suite'
200
+ ...
201
+ # Subtest: addRole() and removeRole()
202
+ # Subtest: addRole() makes new role available
203
+ ok 1 - addRole() makes new role available
204
+ ---
205
+ duration_ms: 0.079959
206
+ ...
207
+ # Subtest: removeRole() removes a role
208
+ ok 2 - removeRole() removes a role
209
+ ---
210
+ duration_ms: 1.089333
211
+ ...
212
+ 1..2
213
+ ok 5 - addRole() and removeRole()
214
+ ---
215
+ duration_ms: 1.580583
216
+ type: 'suite'
217
+ ...
218
+ # Subtest: extendPolicy()
219
+ # Subtest: merges new roles into existing policy
220
+ ok 1 - merges new roles into existing policy
221
+ ---
222
+ duration_ms: 0.301833
223
+ ...
224
+ # Subtest: preserves existing roles when extending
225
+ ok 2 - preserves existing roles when extending
226
+ ---
227
+ duration_ms: 0.070334
228
+ ...
229
+ # Subtest: can override defaultRole
230
+ ok 3 - can override defaultRole
231
+ ---
232
+ duration_ms: 0.057875
233
+ ...
234
+ 1..3
235
+ ok 6 - extendPolicy()
236
+ ---
237
+ duration_ms: 0.477916
238
+ type: 'suite'
239
+ ...
240
+ # Subtest: requireRole()
241
+ # Subtest: does not throw when session user has an allowed role
242
+ ok 1 - does not throw when session user has an allowed role
243
+ ---
244
+ duration_ms: 0.102375
245
+ ...
246
+ # Subtest: throws ForbiddenError when session user role is not allowed
247
+ ok 2 - throws ForbiddenError when session user role is not allowed
248
+ ---
249
+ duration_ms: 0.082125
250
+ ...
251
+ # Subtest: throws ForbiddenError when session has no role
252
+ ok 3 - throws ForbiddenError when session has no role
253
+ ---
254
+ duration_ms: 0.074042
255
+ ...
256
+ # Subtest: throws ForbiddenError when session has no user
257
+ ok 4 - throws ForbiddenError when session has no user
258
+ ---
259
+ duration_ms: 0.072792
260
+ ...
261
+ 1..4
262
+ ok 7 - requireRole()
263
+ ---
264
+ duration_ms: 0.376833
265
+ type: 'suite'
266
+ ...
267
+ # Subtest: withAuthorization()
268
+ # Subtest: calls handler when authorized
269
+ ok 1 - calls handler when authorized
270
+ ---
271
+ duration_ms: 0.622167
272
+ ...
273
+ # Subtest: throws ForbiddenError when not authorized
274
+ ok 2 - throws ForbiddenError when not authorized
275
+ ---
276
+ duration_ms: 0.183916
277
+ ...
278
+ # Subtest: throws ForbiddenError when no user in session
279
+ ok 3 - throws ForbiddenError when no user in session
280
+ ---
281
+ duration_ms: 0.099125
282
+ ...
283
+ # Subtest: passes context from getContext to authorization
284
+ ok 4 - passes context from getContext to authorization
285
+ ---
286
+ duration_ms: 0.086416
287
+ ...
288
+ 1..4
289
+ ok 8 - withAuthorization()
290
+ ---
291
+ duration_ms: 1.056459
292
+ type: 'suite'
293
+ ...
294
+ # Subtest: default instance
295
+ # Subtest: exports a working default authorization instance
296
+ ok 1 - exports a working default authorization instance
297
+ ---
298
+ duration_ms: 0.07275
299
+ ...
300
+ 1..1
301
+ ok 9 - default instance
302
+ ---
303
+ duration_ms: 0.103833
304
+ type: 'suite'
305
+ ...
306
+ # Subtest: defaultPolicy
307
+ # Subtest: is exported and has expected structure
308
+ ok 1 - is exported and has expected structure
309
+ ---
310
+ duration_ms: 0.111291
311
+ ...
312
+ 1..1
313
+ ok 10 - defaultPolicy
314
+ ---
315
+ duration_ms: 0.146625
316
+ type: 'suite'
317
+ ...
318
+ 1..10
319
+ ok 2 - authorization
320
+ ---
321
+ duration_ms: 13.10925
322
+ type: 'suite'
323
+ ...
324
+ # Subtest: createCache
325
+ # Subtest: get
326
+ # Subtest: returns null for a missing key
327
+ ok 1 - returns null for a missing key
328
+ ---
329
+ duration_ms: 0.583625
330
+ ...
331
+ # Subtest: returns parsed JSON for an existing key
332
+ ok 2 - returns parsed JSON for an existing key
333
+ ---
334
+ duration_ms: 0.405417
335
+ ...
336
+ # Subtest: returns null for invalid JSON
337
+ ok 3 - returns null for invalid JSON
338
+ ---
339
+ duration_ms: 0.171625
340
+ ...
341
+ 1..3
342
+ ok 1 - get
343
+ ---
344
+ duration_ms: 1.559584
345
+ type: 'suite'
346
+ ...
347
+ # Subtest: set
348
+ # Subtest: round-trips JSON values through set/get
349
+ ok 1 - round-trips JSON values through set/get
350
+ ---
351
+ duration_ms: 0.919625
352
+ ...
353
+ # Subtest: passes TTL to Redis expire
354
+ ok 2 - passes TTL to Redis expire
355
+ ---
356
+ duration_ms: 0.51375
357
+ ...
358
+ # Subtest: uses defaultTTL when no TTL is provided
359
+ ok 3 - uses defaultTTL when no TTL is provided
360
+ ---
361
+ duration_ms: 0.252459
362
+ ...
363
+ 1..3
364
+ ok 2 - set
365
+ ---
366
+ duration_ms: 2.268833
367
+ type: 'suite'
368
+ ...
369
+ # Subtest: del
370
+ # Subtest: removes a key
371
+ ok 1 - removes a key
372
+ ---
373
+ duration_ms: 0.243625
374
+ ...
375
+ 1..1
376
+ ok 3 - del
377
+ ---
378
+ duration_ms: 0.35725
379
+ type: 'suite'
380
+ ...
381
+ # Subtest: invalidate
382
+ # Subtest: deletes all keys matching a pattern
383
+ ok 1 - deletes all keys matching a pattern
384
+ ---
385
+ duration_ms: 2.748333
386
+ ...
387
+ # Subtest: does nothing when no keys match
388
+ ok 2 - does nothing when no keys match
389
+ ---
390
+ duration_ms: 0.234625
391
+ ...
392
+ 1..2
393
+ ok 4 - invalidate
394
+ ---
395
+ duration_ms: 3.074875
396
+ type: 'suite'
397
+ ...
398
+ # Subtest: getOrSet
399
+ # Subtest: calls factory on cache miss and caches the result
400
+ ok 1 - calls factory on cache miss and caches the result
401
+ ---
402
+ duration_ms: 0.152875
403
+ ...
404
+ # Subtest: returns cached value without calling factory on hit
405
+ ok 2 - returns cached value without calling factory on hit
406
+ ---
407
+ duration_ms: 0.225792
408
+ ...
409
+ 1..2
410
+ ok 5 - getOrSet
411
+ ---
412
+ duration_ms: 0.417959
413
+ type: 'suite'
414
+ ...
415
+ # Subtest: wrap
416
+ # Subtest: creates a cached function
417
+ ok 1 - creates a cached function
418
+ ---
419
+ duration_ms: 0.14725
420
+ ...
421
+ # Subtest: uses keyGenerator when provided
422
+ ok 2 - uses keyGenerator when provided
423
+ ---
424
+ duration_ms: 0.112458
425
+ ...
426
+ # Subtest: caches different args separately
427
+ ok 3 - caches different args separately
428
+ ---
429
+ duration_ms: 0.1055
430
+ ...
431
+ 1..3
432
+ ok 6 - wrap
433
+ ---
434
+ duration_ms: 0.40875
435
+ type: 'suite'
436
+ ...
437
+ 1..6
438
+ ok 3 - createCache
439
+ ---
440
+ duration_ms: 9.401209
441
+ type: 'suite'
442
+ ...
443
+ # Subtest: CSRF Module
444
+ # Subtest: generateCsrfToken creates a secure token
445
+ ok 1 - generateCsrfToken creates a secure token
446
+ ---
447
+ duration_ms: 1.917583
448
+ ...
449
+ # Subtest: validateCsrfToken accepts valid token
450
+ ok 2 - validateCsrfToken accepts valid token
451
+ ---
452
+ duration_ms: 0.190167
453
+ ...
454
+ # Subtest: validateCsrfToken rejects missing header token
455
+ ok 3 - validateCsrfToken rejects missing header token
456
+ ---
457
+ duration_ms: 4.459917
458
+ ...
459
+ # Subtest: validateCsrfToken rejects missing session token
460
+ ok 4 - validateCsrfToken rejects missing session token
461
+ ---
462
+ duration_ms: 0.173167
463
+ ...
464
+ # Subtest: validateCsrfToken rejects mismatched tokens
465
+ ok 5 - validateCsrfToken rejects mismatched tokens
466
+ ---
467
+ duration_ms: 0.402875
468
+ ...
469
+ # Subtest: validateCsrfToken rejects tokens of different lengths
470
+ ok 6 - validateCsrfToken rejects tokens of different lengths
471
+ ---
472
+ duration_ms: 0.3295
473
+ ...
474
+ # Subtest: requireCsrfToken skips GET requests
475
+ ok 7 - requireCsrfToken skips GET requests
476
+ ---
477
+ duration_ms: 0.3365
478
+ ...
479
+ # Subtest: requireCsrfToken skips NextAuth routes
480
+ ok 8 - requireCsrfToken skips NextAuth routes
481
+ ---
482
+ duration_ms: 0.446958
483
+ ...
484
+ # Subtest: requireCsrfToken validates POST with cookie token
485
+ ok 9 - requireCsrfToken validates POST with cookie token
486
+ ---
487
+ duration_ms: 0.6065
488
+ ...
489
+ # Subtest: requireCsrfToken throws when cookie is missing
490
+ ok 10 - requireCsrfToken throws when cookie is missing
491
+ ---
492
+ duration_ms: 1.87775
493
+ ...
494
+ # Subtest: withCsrfProtection wraps handler
495
+ ok 11 - withCsrfProtection wraps handler
496
+ ---
497
+ duration_ms: 0.192291
498
+ ...
499
+ 1..11
500
+ ok 4 - CSRF Module
501
+ ---
502
+ duration_ms: 13.338875
503
+ ...
504
+ # Subtest: Email Service
505
+ # Subtest: createEmailService returns object with sendEmail method
506
+ ok 1 - createEmailService returns object with sendEmail method
507
+ ---
508
+ duration_ms: 0.557292
509
+ ...
510
+ # Subtest: SMTP provider: uses Mailhog defaults when no SMTP_HOST set
511
+ ok 2 - SMTP provider: uses Mailhog defaults when no SMTP_HOST set
512
+ ---
513
+ duration_ms: 0.091125
514
+ ...
515
+ # Subtest: SMTP provider: uses explicit SMTP_HOST/SMTP_PORT when set
516
+ ok 3 - SMTP provider: uses explicit SMTP_HOST/SMTP_PORT when set
517
+ ---
518
+ duration_ms: 0.068416
519
+ ...
520
+ # Subtest: Resend provider: throws if RESEND_API_KEY missing
521
+ ok 4 - Resend provider: throws if RESEND_API_KEY missing
522
+ ---
523
+ duration_ms: 0.379291
524
+ ...
525
+ # Subtest: Resend provider: calls fetch with correct URL, headers, and body
526
+ ok 5 - Resend provider: calls fetch with correct URL, headers, and body
527
+ ---
528
+ duration_ms: 1.266584
529
+ ...
530
+ # Subtest: Resend provider: throws on non-ok response
531
+ ok 6 - Resend provider: throws on non-ok response
532
+ ---
533
+ duration_ms: 0.213209
534
+ ...
535
+ # Subtest: Resend provider: handles non-JSON error response
536
+ ok 7 - Resend provider: handles non-JSON error response
537
+ ---
538
+ duration_ms: 0.194
539
+ ...
540
+ # Subtest: default provider is smtp
541
+ ok 8 - default provider is smtp
542
+ ---
543
+ duration_ms: 0.20625
544
+ ...
545
+ # Subtest: respects options.from override
546
+ ok 9 - respects options.from override
547
+ ---
548
+ duration_ms: 0.27
549
+ ...
550
+ # Subtest: input validation: rejects empty 'to'
551
+ ok 10 - input validation: rejects empty 'to'
552
+ ---
553
+ duration_ms: 0.21125
554
+ ...
555
+ # Subtest: input validation: rejects empty 'subject'
556
+ ok 11 - input validation: rejects empty 'subject'
557
+ ---
558
+ duration_ms: 0.07875
559
+ ...
560
+ # Subtest: input validation: rejects empty 'html'
561
+ ok 12 - input validation: rejects empty 'html'
562
+ ---
563
+ duration_ms: 0.063958
564
+ ...
565
+ 1..12
566
+ ok 5 - Email Service
567
+ ---
568
+ duration_ms: 6.881417
569
+ ...
570
+ # 18:43:11.931 ERROR [quark] test error {"error":{"name":"AppError","message":"test error","code":"INTERNAL_ERROR","statusCode":500,"timestamp":"2026-02-14T18:43:11.930Z"},"context":{"requestId":"123","breadcrumbs":[]},"stack":"Error: test error\\n at TestContext.<anonymous> (file:///Users/david/Projects/quark/packages/core/src/error-reporter.test.js:61:18)\\n at Test.runInAsyncScope (node:async_hooks:211:14)\\n at Test.run (node:internal/test_runner/test:979:25)\\n at async Promise.all (index 0)\\n at async Suite.run (node:internal/test_runner/test:1358:7)\\n at async Suite.processPendingSubtests (node:internal/test_runner/test:677:7)"}
571
+ # 18:43:11.933 ERROR [quark] test {"error":{"name":"AppError","message":"test","code":"INTERNAL_ERROR","statusCode":500,"timestamp":"2026-02-14T18:43:11.933Z"},"context":{"breadcrumbs":[]},"stack":"Error: test\\n at file:///Users/david/Projects/quark/packages/core/src/error-reporter.test.js:81:46\\n at getActual (node:assert:840:5)\\n at Function.doesNotThrow (node:assert:1006:32)\\n at TestContext.<anonymous> (file:///Users/david/Projects/quark/packages/core/src/error-reporter.test.js:81:11)\\n at Test.runInAsyncScope (node:async_hooks:211:14)\\n at Test.run (node:internal/test_runner/test:979:25)\\n at async Suite.processPendingSubtests (node:internal/test_runner/test:677:7)"}
572
+ # 18:43:11.933 WARN  [quark] hello {"extra":true}
573
+ # 18:43:11.934 INFO  [quark] hello
574
+ # 18:43:11.934 ERROR [quark] test {"error":{"name":"AppError","message":"test","code":"INTERNAL_ERROR","statusCode":500,"timestamp":"2026-02-14T18:43:11.934Z"},"context":{"user":{"id":"u1","email":"a@b.com"},"breadcrumbs":[]},"stack":"Error: test\\n at TestContext.<anonymous> (file:///Users/david/Projects/quark/packages/core/src/error-reporter.test.js:121:20)\\n at Test.runInAsyncScope (node:async_hooks:211:14)\\n at Test.run (node:internal/test_runner/test:979:25)\\n at async Promise.all (index 0)\\n at async Suite.run (node:internal/test_runner/test:1358:7)\\n at async Suite.processPendingSubtests (node:internal/test_runner/test:677:7)"}
575
+ # 18:43:11.935 ERROR [quark] test {"error":{"name":"AppError","message":"test","code":"INTERNAL_ERROR","statusCode":500,"timestamp":"2026-02-14T18:43:11.935Z"},"context":{"breadcrumbs":[{"message":"clicked button","category":"ui","data":{"buttonId":"submit"},"timestamp":"2026-02-14T18:43:11.935Z"},{"message":"navigated","category":"navigation","data":{},"timestamp":"2026-02-14T18:43:11.935Z"}]},"stack":"Error: test\\n at TestContext.<anonymous> (file:///Users/david/Projects/quark/packages/core/src/error-reporter.test.js:154:20)\\n at Test.runInAsyncScope (node:async_hooks:211:14)\\n at Test.run (node:internal/test_runner/test:979:25)\\n at async Promise.all (index 0)\\n at async Suite.run (node:internal/test_runner/test:1358:7)\\n at async Suite.processPendingSubtests (node:internal/test_runner/test:677:7)"}
576
+ # 18:43:11.935 ERROR [quark] custom test {"error":{"name":"AppError","message":"custom test","code":"INTERNAL_ERROR","statusCode":500,"timestamp":"2026-02-14T18:43:11.935Z"},"context":{"route":"/api/test","user":{"id":"u1"},"breadcrumbs":[{"message":"init","category":"app","data":{},"timestamp":"2026-02-14T18:43:11.935Z"}]},"stack":"Error: custom test\\n at TestContext.<anonymous> (file:///Users/david/Projects/quark/packages/core/src/error-reporter.test.js:208:18)\\n at Test.runInAsyncScope (node:async_hooks:211:14)\\n at Test.run (node:internal/test_runner/test:979:25)\\n at async Promise.all (index 0)\\n at async Suite.run (node:internal/test_runner/test:1358:7)\\n at async Suite.processPendingSubtests (node:internal/test_runner/test:677:7)"}
577
+ # 18:43:11.936 ERROR [quark] test {"error":{"name":"AppError","message":"test","code":"INTERNAL_ERROR","statusCode":500,"timestamp":"2026-02-14T18:43:11.936Z"},"context":{"breadcrumbs":[]},"stack":"Error: test\\n at file:///Users/david/Projects/quark/packages/core/src/error-reporter.test.js:233:46\\n at getActual (node:assert:840:5)\\n at Function.doesNotThrow (node:assert:1006:32)\\n at TestContext.<anonymous> (file:///Users/david/Projects/quark/packages/core/src/error-reporter.test.js:233:11)\\n at Test.runInAsyncScope (node:async_hooks:211:14)\\n at Test.run (node:internal/test_runner/test:979:25)\\n at async Suite.processPendingSubtests (node:internal/test_runner/test:677:7)"}
578
+ # Subtest: ErrorReporter
579
+ # Subtest: default instance
580
+ # Subtest: has console adapter registered by default
581
+ ok 1 - has console adapter registered by default
582
+ ---
583
+ duration_ms: 0.615417
584
+ ...
585
+ # Subtest: global singleton has console adapter
586
+ ok 2 - global singleton has console adapter
587
+ ---
588
+ duration_ms: 0.15525
589
+ ...
590
+ 1..2
591
+ ok 1 - default instance
592
+ ---
593
+ duration_ms: 1.220041
594
+ type: 'suite'
595
+ ...
596
+ # Subtest: use()
597
+ # Subtest: adds an adapter
598
+ ok 1 - adds an adapter
599
+ ---
600
+ duration_ms: 14.186458
601
+ ...
602
+ # Subtest: throws if adapter has no name
603
+ ok 2 - throws if adapter has no name
604
+ ---
605
+ duration_ms: 14.95325
606
+ ...
607
+ # Subtest: throws if adapter has no report function
608
+ ok 3 - throws if adapter has no report function
609
+ ---
610
+ duration_ms: 0.965625
611
+ ...
612
+ 1..3
613
+ ok 2 - use()
614
+ ---
615
+ duration_ms: 30.70625
616
+ type: 'suite'
617
+ ...
618
+ # Subtest: report()
619
+ # Subtest: calls all registered adapters
620
+ ok 1 - calls all registered adapters
621
+ ---
622
+ duration_ms: 4.345083
623
+ ...
624
+ # Subtest: swallows adapter errors without throwing
625
+ ok 2 - swallows adapter errors without throwing
626
+ ---
627
+ duration_ms: 0.391333
628
+ ...
629
+ 1..2
630
+ ok 3 - report()
631
+ ---
632
+ duration_ms: 5.039125
633
+ type: 'suite'
634
+ ...
635
+ # Subtest: captureMessage()
636
+ # Subtest: calls adapters that support captureMessage
637
+ ok 1 - calls adapters that support captureMessage
638
+ ---
639
+ duration_ms: 0.185458
640
+ ...
641
+ # Subtest: skips adapters without captureMessage
642
+ ok 2 - skips adapters without captureMessage
643
+ ---
644
+ duration_ms: 0.235125
645
+ ...
646
+ 1..2
647
+ ok 4 - captureMessage()
648
+ ---
649
+ duration_ms: 0.474333
650
+ type: 'suite'
651
+ ...
652
+ # Subtest: setUser()
653
+ # Subtest: includes user in subsequent reports
654
+ ok 1 - includes user in subsequent reports
655
+ ---
656
+ duration_ms: 0.863166
657
+ ...
658
+ # Subtest: forwards user to adapters with setUser
659
+ ok 2 - forwards user to adapters with setUser
660
+ ---
661
+ duration_ms: 0.093583
662
+ ...
663
+ 1..2
664
+ ok 5 - setUser()
665
+ ---
666
+ duration_ms: 1.005292
667
+ type: 'suite'
668
+ ...
669
+ # Subtest: addBreadcrumb()
670
+ # Subtest: stores breadcrumbs and attaches them to report context
671
+ ok 1 - stores breadcrumbs and attaches them to report context
672
+ ---
673
+ duration_ms: 0.219041
674
+ ...
675
+ # Subtest: circular buffer keeps max 50 breadcrumbs
676
+ ok 2 - circular buffer keeps max 50 breadcrumbs
677
+ ---
678
+ duration_ms: 0.185958
679
+ ...
680
+ # Subtest: forwards breadcrumbs to adapters that support it
681
+ ok 3 - forwards breadcrumbs to adapters that support it
682
+ ---
683
+ duration_ms: 0.083292
684
+ ...
685
+ 1..3
686
+ ok 6 - addBreadcrumb()
687
+ ---
688
+ duration_ms: 0.534708
689
+ type: 'suite'
690
+ ...
691
+ # Subtest: custom adapter receives correct data
692
+ # Subtest: passes error and enriched context
693
+ ok 1 - passes error and enriched context
694
+ ---
695
+ duration_ms: 0.152375
696
+ ...
697
+ 1..1
698
+ ok 7 - custom adapter receives correct data
699
+ ---
700
+ duration_ms: 0.181833
701
+ type: 'suite'
702
+ ...
703
+ # Subtest: createSentryAdapter()
704
+ # Subtest: returns a valid adapter shape
705
+ ok 1 - returns a valid adapter shape
706
+ ---
707
+ duration_ms: 0.141541
708
+ ...
709
+ # Subtest: can be registered without errors
710
+ ok 2 - can be registered without errors
711
+ ---
712
+ duration_ms: 0.115792
713
+ ...
714
+ 1..2
715
+ ok 8 - createSentryAdapter()
716
+ ---
717
+ duration_ms: 0.292791
718
+ type: 'suite'
719
+ ...
720
+ 1..8
721
+ ok 6 - ErrorReporter
722
+ ---
723
+ duration_ms: 39.971083
724
+ type: 'suite'
725
+ ...
726
+ # 18:43:11.905 ERROR [quark] test {"error":{"name":"ValidationError","message":"test","code":"VALIDATION_ERROR","statusCode":400,"timestamp":"2026-02-14T18:43:11.904Z","details":null},"context":{"userId":"123","action":"test","breadcrumbs":[]},"stack":"ValidationError: test\\n at TestContext.<anonymous> (file:///Users/david/Projects/quark/packages/core/src/errors.test.js:124:17)\\n at Test.runInAsyncScope (node:async_hooks:211:14)\\n at Test.run (node:internal/test_runner/test:979:25)\\n at Test.start (node:internal/test_runner/test:877:17)\\n at TestContext.test (node:internal/test_runner/test:307:20)\\n at TestContext.<anonymous> (file:///Users/david/Projects/quark/packages/core/src/errors.test.js:122:10)\\n at async Test.run (node:internal/test_runner/test:980:9)\\n at async startSubtestAfterBootstrap (node:internal/test_runner/harness:296:3)"}
727
+ # Subtest: Error Module
728
+ # Subtest: AppError has correct properties
729
+ ok 1 - AppError has correct properties
730
+ ---
731
+ duration_ms: 2.939125
732
+ ...
733
+ # Subtest: AppError.toJSON() serializes correctly
734
+ ok 2 - AppError.toJSON() serializes correctly
735
+ ---
736
+ duration_ms: 0.107292
737
+ ...
738
+ # Subtest: ValidationError has status 400
739
+ ok 3 - ValidationError has status 400
740
+ ---
741
+ duration_ms: 0.078916
742
+ ...
743
+ # Subtest: UnauthorizedError has status 401
744
+ ok 4 - UnauthorizedError has status 401
745
+ ---
746
+ duration_ms: 0.314958
747
+ ...
748
+ # Subtest: ForbiddenError has status 403
749
+ ok 5 - ForbiddenError has status 403
750
+ ---
751
+ duration_ms: 0.06525
752
+ ...
753
+ # Subtest: NotFoundError has status 404
754
+ ok 6 - NotFoundError has status 404
755
+ ---
756
+ duration_ms: 0.114583
757
+ ...
758
+ # Subtest: ConflictError has status 409
759
+ ok 7 - ConflictError has status 409
760
+ ---
761
+ duration_ms: 0.055833
762
+ ...
763
+ # Subtest: RateLimitError has status 429
764
+ ok 8 - RateLimitError has status 429
765
+ ---
766
+ duration_ms: 0.910167
767
+ ...
768
+ # Subtest: DatabaseError wraps database errors
769
+ ok 9 - DatabaseError wraps database errors
770
+ ---
771
+ duration_ms: 0.522709
772
+ ...
773
+ # Subtest: ServiceError includes service name
774
+ ok 10 - ServiceError includes service name
775
+ ---
776
+ duration_ms: 0.393792
777
+ ...
778
+ # Subtest: getErrorMessage extracts from various types
779
+ ok 11 - getErrorMessage extracts from various types
780
+ ---
781
+ duration_ms: 0.148209
782
+ ...
783
+ # Subtest: getStatusCode returns correct code
784
+ ok 12 - getStatusCode returns correct code
785
+ ---
786
+ duration_ms: 0.211375
787
+ ...
788
+ # Subtest: normalizeError converts to AppError
789
+ ok 13 - normalizeError converts to AppError
790
+ ---
791
+ duration_ms: 0.150834
792
+ ...
793
+ # Subtest: normalizeError handles string errors
794
+ ok 14 - normalizeError handles string errors
795
+ ---
796
+ duration_ms: 0.057125
797
+ ...
798
+ # Subtest: normalizeError handles non-Error objects
799
+ ok 15 - normalizeError handles non-Error objects
800
+ ---
801
+ duration_ms: 0.048333
802
+ ...
803
+ # Subtest: logError handles errors with context
804
+ ok 16 - logError handles errors with context
805
+ ---
806
+ duration_ms: 3.156375
807
+ ...
808
+ 1..16
809
+ ok 7 - Error Module
810
+ ---
811
+ duration_ms: 11.603708
812
+ ...
813
+ # Subtest: Logger
814
+ # Subtest: creates logger with default options
815
+ ok 1 - creates logger with default options
816
+ ---
817
+ duration_ms: 2.076334
818
+ ...
819
+ # Subtest: default logger instance exists with name 'quark'
820
+ ok 2 - default logger instance exists with name 'quark'
821
+ ---
822
+ duration_ms: 1.256958
823
+ ...
824
+ # Subtest: log level filtering: debug hidden at info level
825
+ ok 3 - log level filtering: debug hidden at info level
826
+ ---
827
+ duration_ms: 0.34725
828
+ ...
829
+ # Subtest: log level filtering: debug visible at debug level
830
+ ok 4 - log level filtering: debug visible at debug level
831
+ ---
832
+ duration_ms: 0.33025
833
+ ...
834
+ # Subtest: respects LOG_LEVEL env var
835
+ ok 5 - respects LOG_LEVEL env var
836
+ ---
837
+ duration_ms: 0.175042
838
+ ...
839
+ # Subtest: child logger inherits parent context
840
+ ok 6 - child logger inherits parent context
841
+ ---
842
+ duration_ms: 0.129333
843
+ ...
844
+ # Subtest: child logger merges context without mutating parent
845
+ ok 7 - child logger merges context without mutating parent
846
+ ---
847
+ duration_ms: 0.167417
848
+ ...
849
+ # Subtest: structured output contains required fields
850
+ ok 8 - structured output contains required fields
851
+ ---
852
+ duration_ms: 0.10875
853
+ ...
854
+ # Subtest: error and fatal use console.error
855
+ ok 9 - error and fatal use console.error
856
+ ---
857
+ duration_ms: 1.472333
858
+ ...
859
+ # Subtest: warn uses console.warn
860
+ ok 10 - warn uses console.warn
861
+ ---
862
+ duration_ms: 0.293
863
+ ...
864
+ # Subtest: dev mode outputs colorized non-JSON string
865
+ ok 11 - dev mode outputs colorized non-JSON string
866
+ ---
867
+ duration_ms: 0.218959
868
+ ...
869
+ # Subtest: call-site data overrides default context
870
+ ok 12 - call-site data overrides default context
871
+ ---
872
+ duration_ms: 0.101
873
+ ...
874
+ 1..12
875
+ ok 8 - Logger
876
+ ---
877
+ duration_ms: 7.448875
878
+ type: 'suite'
879
+ ...
880
+ # Subtest: requestLogger
881
+ # Subtest: creates child logger with request fields
882
+ ok 1 - creates child logger with request fields
883
+ ---
884
+ duration_ms: 0.169
885
+ ...
886
+ # Subtest: generates requestId when header is missing
887
+ ok 2 - generates requestId when header is missing
888
+ ---
889
+ duration_ms: 2.116334
890
+ ...
891
+ 1..2
892
+ ok 9 - requestLogger
893
+ ---
894
+ duration_ms: 2.339875
895
+ type: 'suite'
896
+ ...
897
+ # Subtest: Rate Limiter Module
898
+ # Subtest: createRateLimiter creates memory limiter by default
899
+ ok 1 - createRateLimiter creates memory limiter by default
900
+ ---
901
+ duration_ms: 0.432958
902
+ ...
903
+ # Subtest: createRateLimiter throws error for Redis without client
904
+ ok 2 - createRateLimiter throws error for Redis without client
905
+ ---
906
+ duration_ms: 0.195541
907
+ ...
908
+ # Subtest: Memory rate limiter allows requests under limit
909
+ ok 3 - Memory rate limiter allows requests under limit
910
+ ---
911
+ duration_ms: 0.162959
912
+ ...
913
+ # Subtest: Memory rate limiter blocks requests over limit
914
+ ok 4 - Memory rate limiter blocks requests over limit
915
+ ---
916
+ duration_ms: 0.80725
917
+ ...
918
+ # Subtest: Memory rate limiter resets after window
919
+ ok 5 - Memory rate limiter resets after window
920
+ ---
921
+ duration_ms: 22.261875
922
+ ...
923
+ # Subtest: Memory rate limiter can reset a key
924
+ ok 6 - Memory rate limiter can reset a key
925
+ ---
926
+ duration_ms: 0.312458
927
+ ...
928
+ # Subtest: Memory rate limiter can clear all keys
929
+ ok 7 - Memory rate limiter can clear all keys
930
+ ---
931
+ duration_ms: 0.115416
932
+ ...
933
+ # Subtest: RATE_LIMIT_PRESETS have correct structure
934
+ ok 8 - RATE_LIMIT_PRESETS have correct structure
935
+ ---
936
+ duration_ms: 0.157292
937
+ ...
938
+ # Subtest: createRateLimitMiddleware returns a function
939
+ ok 9 - createRateLimitMiddleware returns a function
940
+ ---
941
+ duration_ms: 0.238542
942
+ ...
943
+ # Subtest: createRateLimitMiddleware works with API preset
944
+ ok 10 - createRateLimitMiddleware works with API preset
945
+ ---
946
+ duration_ms: 0.198292
947
+ ...
948
+ # Subtest: Cleanup interval is started for memory limiter
949
+ ok 11 - Cleanup interval is started for memory limiter
950
+ ---
951
+ duration_ms: 0.525708
952
+ ...
953
+ 1..11
954
+ ok 10 - Rate Limiter Module
955
+ ---
956
+ duration_ms: 27.643167
957
+ ...
958
+ # Subtest: Factories
959
+ # Subtest: createTestUser returns correct shape with defaults
960
+ ok 1 - createTestUser returns correct shape with defaults
961
+ ---
962
+ duration_ms: 0.47975
963
+ ...
964
+ # Subtest: createTestUser applies overrides
965
+ ok 2 - createTestUser applies overrides
966
+ ---
967
+ duration_ms: 0.079375
968
+ ...
969
+ # Subtest: createTestUser generates unique IDs
970
+ ok 3 - createTestUser generates unique IDs
971
+ ---
972
+ duration_ms: 0.068708
973
+ ...
974
+ # Subtest: createTestPost returns correct shape with defaults
975
+ ok 4 - createTestPost returns correct shape with defaults
976
+ ---
977
+ duration_ms: 0.076208
978
+ ...
979
+ # Subtest: createTestPost applies overrides including boolean false
980
+ ok 5 - createTestPost applies overrides including boolean false
981
+ ---
982
+ duration_ms: 0.060333
983
+ ...
984
+ # Subtest: createTestPost keeps published false when explicitly set
985
+ ok 6 - createTestPost keeps published false when explicitly set
986
+ ---
987
+ duration_ms: 0.110083
988
+ ...
989
+ # Subtest: createTestSession returns session with embedded user
990
+ ok 7 - createTestSession returns session with embedded user
991
+ ---
992
+ duration_ms: 2.819166
993
+ ...
994
+ # Subtest: createTestSession applies user overrides
995
+ ok 8 - createTestSession applies user overrides
996
+ ---
997
+ duration_ms: 0.615709
998
+ ...
999
+ # Subtest: createTestSession applies top-level overrides
1000
+ ok 9 - createTestSession applies top-level overrides
1001
+ ---
1002
+ duration_ms: 0.31925
1003
+ ...
1004
+ 1..9
1005
+ ok 11 - Factories
1006
+ ---
1007
+ duration_ms: 5.724625
1008
+ ...
1009
+ # Subtest: Mock Prisma
1010
+ # Subtest: records method calls with args
1011
+ ok 1 - records method calls with args
1012
+ ---
1013
+ duration_ms: 0.469834
1014
+ ...
1015
+ # Subtest: returns null by default for unconfigured methods
1016
+ ok 2 - returns null by default for unconfigured methods
1017
+ ---
1018
+ duration_ms: 0.745875
1019
+ ...
1020
+ # Subtest: returns configured values via mockReturn
1021
+ ok 3 - returns configured values via mockReturn
1022
+ ---
1023
+ duration_ms: 0.072625
1024
+ ...
1025
+ # Subtest: supports function return values
1026
+ ok 4 - supports function return values
1027
+ ---
1028
+ duration_ms: 0.053958
1029
+ ...
1030
+ # Subtest: supports overrides in constructor
1031
+ ok 5 - supports overrides in constructor
1032
+ ---
1033
+ duration_ms: 0.055167
1034
+ ...
1035
+ # Subtest: reset clears calls and return values
1036
+ ok 6 - reset clears calls and return values
1037
+ ---
1038
+ duration_ms: 0.065792
1039
+ ...
1040
+ # Subtest: handles multiple models independently
1041
+ ok 7 - handles multiple models independently
1042
+ ---
1043
+ duration_ms: 0.103083
1044
+ ...
1045
+ 1..7
1046
+ ok 12 - Mock Prisma
1047
+ ---
1048
+ duration_ms: 1.910667
1049
+ ...
1050
+ # Subtest: Mock Request
1051
+ # Subtest: has correct defaults
1052
+ ok 1 - has correct defaults
1053
+ ---
1054
+ duration_ms: 0.082458
1055
+ ...
1056
+ # Subtest: headers.get is case-insensitive
1057
+ ok 2 - headers.get is case-insensitive
1058
+ ---
1059
+ duration_ms: 0.056458
1060
+ ...
1061
+ # Subtest: returns null for missing headers
1062
+ ok 3 - returns null for missing headers
1063
+ ---
1064
+ duration_ms: 0.032084
1065
+ ...
1066
+ # Subtest: json() returns body
1067
+ ok 4 - json() returns body
1068
+ ---
1069
+ duration_ms: 0.045667
1070
+ ...
1071
+ # Subtest: cookies.get returns cookie object
1072
+ ok 5 - cookies.get returns cookie object
1073
+ ---
1074
+ duration_ms: 0.051167
1075
+ ...
1076
+ # Subtest: cookies.get returns undefined for missing cookie
1077
+ ok 6 - cookies.get returns undefined for missing cookie
1078
+ ---
1079
+ duration_ms: 0.033833
1080
+ ...
1081
+ # Subtest: nextUrl is a URL object
1082
+ ok 7 - nextUrl is a URL object
1083
+ ---
1084
+ duration_ms: 0.067458
1085
+ ...
1086
+ 1..7
1087
+ ok 13 - Mock Request
1088
+ ---
1089
+ duration_ms: 0.528334
1090
+ ...
1091
+ # Subtest: Mock Response
1092
+ # Subtest: has default status 200
1093
+ ok 1 - has default status 200
1094
+ ---
1095
+ duration_ms: 0.059625
1096
+ ...
1097
+ # Subtest: json() sets body and content-type
1098
+ ok 2 - json() sets body and content-type
1099
+ ---
1100
+ duration_ms: 0.098292
1101
+ ...
1102
+ # Subtest: redirect() sets status and location
1103
+ ok 3 - redirect() sets status and location
1104
+ ---
1105
+ duration_ms: 0.037333
1106
+ ...
1107
+ # Subtest: cookies can be set and retrieved
1108
+ ok 4 - cookies can be set and retrieved
1109
+ ---
1110
+ duration_ms: 0.044833
1111
+ ...
1112
+ 1..4
1113
+ ok 14 - Mock Response
1114
+ ---
1115
+ duration_ms: 0.349125
1116
+ ...
1117
+ # Subtest: Mock Redis
1118
+ # Subtest: get/set stores and retrieves values
1119
+ ok 1 - get/set stores and retrieves values
1120
+ ---
1121
+ duration_ms: 0.110875
1122
+ ...
1123
+ # Subtest: get returns null for missing keys
1124
+ ok 2 - get returns null for missing keys
1125
+ ---
1126
+ duration_ms: 0.039
1127
+ ...
1128
+ # Subtest: initialData pre-populates the store
1129
+ ok 3 - initialData pre-populates the store
1130
+ ---
1131
+ duration_ms: 0.035792
1132
+ ...
1133
+ # Subtest: del removes keys and returns count
1134
+ ok 4 - del removes keys and returns count
1135
+ ---
1136
+ duration_ms: 0.060333
1137
+ ...
1138
+ # Subtest: keys filters by prefix pattern
1139
+ ok 5 - keys filters by prefix pattern
1140
+ ---
1141
+ duration_ms: 0.1545
1142
+ ...
1143
+ # Subtest: incr increments numeric values
1144
+ ok 6 - incr increments numeric values
1145
+ ---
1146
+ duration_ms: 0.052708
1147
+ ...
1148
+ # Subtest: exists returns count of existing keys
1149
+ ok 7 - exists returns count of existing keys
1150
+ ---
1151
+ duration_ms: 0.056583
1152
+ ...
1153
+ # Subtest: pipeline batches commands
1154
+ ok 8 - pipeline batches commands
1155
+ ---
1156
+ duration_ms: 0.110125
1157
+ ...
1158
+ # Subtest: records all method calls
1159
+ ok 9 - records all method calls
1160
+ ---
1161
+ duration_ms: 0.038958
1162
+ ...
1163
+ # Subtest: reset clears store and calls
1164
+ ok 10 - reset clears store and calls
1165
+ ---
1166
+ duration_ms: 0.050625
1167
+ ...
1168
+ # Subtest: ping returns PONG
1169
+ ok 11 - ping returns PONG
1170
+ ---
1171
+ duration_ms: 0.035041
1172
+ ...
1173
+ 1..11
1174
+ ok 15 - Mock Redis
1175
+ ---
1176
+ duration_ms: 1.41575
1177
+ ...
1178
+ # Subtest: captureConsole
1179
+ # Subtest: captures console output and restores
1180
+ ok 1 - captures console output and restores
1181
+ ---
1182
+ duration_ms: 0.094375
1183
+ ...
1184
+ # Subtest: captures info and debug
1185
+ ok 2 - captures info and debug
1186
+ ---
1187
+ duration_ms: 0.038
1188
+ ...
1189
+ 1..2
1190
+ ok 16 - captureConsole
1191
+ ---
1192
+ duration_ms: 0.197125
1193
+ ...
1194
+ # Subtest: createTestContext
1195
+ # Subtest: saves and restores env variables
1196
+ ok 1 - saves and restores env variables
1197
+ ---
1198
+ duration_ms: 0.091209
1199
+ ...
1200
+ # Subtest: deletes env var when set to undefined
1201
+ ok 2 - deletes env var when set to undefined
1202
+ ---
1203
+ duration_ms: 0.048917
1204
+ ...
1205
+ # Subtest: runs cleanup functions in LIFO order
1206
+ ok 3 - runs cleanup functions in LIFO order
1207
+ ---
1208
+ duration_ms: 0.056875
1209
+ ...
1210
+ # Subtest: cleanup runs async cleanup functions
1211
+ ok 4 - cleanup runs async cleanup functions
1212
+ ---
1213
+ duration_ms: 11.051375
1214
+ ...
1215
+ 1..4
1216
+ ok 17 - createTestContext
1217
+ ---
1218
+ duration_ms: 11.384916
1219
+ ...
1220
+ # Subtest: waitFor
1221
+ # Subtest: resolves when assertion passes immediately
1222
+ ok 1 - resolves when assertion passes immediately
1223
+ ---
1224
+ duration_ms: 0.074583
1225
+ ...
1226
+ # Subtest: resolves when assertion eventually passes
1227
+ ok 2 - resolves when assertion eventually passes
1228
+ ---
1229
+ duration_ms: 21.530291
1230
+ ...
1231
+ # Subtest: throws last error on timeout
1232
+ ok 3 - throws last error on timeout
1233
+ ---
1234
+ duration_ms: 110.640542
1235
+ ...
1236
+ 1..3
1237
+ ok 18 - waitFor
1238
+ ---
1239
+ duration_ms: 132.498
1240
+ ...
1241
+ # Subtest: assertThrows
1242
+ # Subtest: passes when async function throws expected error
1243
+ ok 1 - passes when async function throws expected error
1244
+ ---
1245
+ duration_ms: 0.15625
1246
+ ...
1247
+ # Subtest: fails when function does not throw
1248
+ ok 2 - fails when function does not throw
1249
+ ---
1250
+ duration_ms: 0.297459
1251
+ ...
1252
+ # Subtest: fails when error class does not match
1253
+ ok 3 - fails when error class does not match
1254
+ ---
1255
+ duration_ms: 0.086583
1256
+ ...
1257
+ # Subtest: fails when message pattern does not match
1258
+ ok 4 - fails when message pattern does not match
1259
+ ---
1260
+ duration_ms: 0.116541
1261
+ ...
1262
+ # Subtest: works with string message pattern
1263
+ ok 5 - works with string message pattern
1264
+ ---
1265
+ duration_ms: 0.0435
1266
+ ...
1267
+ # Subtest: works without ErrorClass (just message)
1268
+ ok 6 - works without ErrorClass (just message)
1269
+ ---
1270
+ duration_ms: 0.042375
1271
+ ...
1272
+ 1..6
1273
+ ok 19 - assertThrows
1274
+ ---
1275
+ duration_ms: 0.974333
1276
+ ...
1277
+ # Subtest: Utils Module
1278
+ # Subtest: retryAsync succeeds on first attempt
1279
+ ok 1 - retryAsync succeeds on first attempt
1280
+ ---
1281
+ duration_ms: 0.316708
1282
+ ...
1283
+ # Subtest: retryAsync retries on failure
1284
+ ok 2 - retryAsync retries on failure
1285
+ ---
1286
+ duration_ms: 32.389917
1287
+ ...
1288
+ # Subtest: retryAsync throws after max attempts
1289
+ ok 3 - retryAsync throws after max attempts
1290
+ ---
1291
+ duration_ms: 31.83275
1292
+ ...
1293
+ # Subtest: sleep delays execution
1294
+ ok 4 - sleep delays execution
1295
+ ---
1296
+ duration_ms: 51.634333
1297
+ ...
1298
+ # Subtest: validateEnv checks required variables
1299
+ ok 5 - validateEnv checks required variables
1300
+ ---
1301
+ duration_ms: 0.182375
1302
+ ...
1303
+ # Subtest: validateEnv throws on missing variables
1304
+ ok 6 - validateEnv throws on missing variables
1305
+ ---
1306
+ duration_ms: 0.149208
1307
+ ...
1308
+ # Subtest: deepMerge merges objects
1309
+ ok 7 - deepMerge merges objects
1310
+ ---
1311
+ duration_ms: 0.426375
1312
+ ...
1313
+ # Subtest: deepMerge handles nested objects
1314
+ ok 8 - deepMerge handles nested objects
1315
+ ---
1316
+ duration_ms: 0.222959
1317
+ ...
1318
+ # Subtest: isObject identifies objects
1319
+ ok 9 - isObject identifies objects
1320
+ ---
1321
+ duration_ms: 0.204666
1322
+ ...
1323
+ # Subtest: normalizeErrorMessage handles various types
1324
+ ok 10 - normalizeErrorMessage handles various types
1325
+ ---
1326
+ duration_ms: 0.147834
1327
+ ...
1328
+ # Subtest: randomString generates random strings
1329
+ ok 11 - randomString generates random strings
1330
+ ---
1331
+ duration_ms: 0.169959
1332
+ ...
1333
+ # Subtest: randomString respects length parameter
1334
+ ok 12 - randomString respects length parameter
1335
+ ---
1336
+ duration_ms: 0.047542
1337
+ ...
1338
+ # Subtest: sanitizeId converts to valid IDs
1339
+ ok 13 - sanitizeId converts to valid IDs
1340
+ ---
1341
+ duration_ms: 0.135167
1342
+ ...
1343
+ # Subtest: formatBytes formats file sizes
1344
+ ok 14 - formatBytes formats file sizes
1345
+ ---
1346
+ duration_ms: 0.05175
1347
+ ...
1348
+ # Subtest: debounce delays function execution
1349
+ ok 15 - debounce delays function execution
1350
+ ---
1351
+ duration_ms: 50.813583
1352
+ ...
1353
+ # Subtest: memoize caches function results
1354
+ ok 16 - memoize caches function results
1355
+ ---
1356
+ duration_ms: 0.155459
1357
+ ...
1358
+ # Subtest: memoize respects TTL
1359
+ ok 17 - memoize respects TTL
1360
+ ---
1361
+ duration_ms: 50.572083
1362
+ ...
1363
+ 1..17
1364
+ ok 20 - Utils Module
1365
+ ---
1366
+ duration_ms: 221.208417
1367
+ ...
1368
+ 1..20
1369
+ # tests 227
1370
+ # suites 29
1371
+ # pass 227
1372
+ # fail 0
1373
+ # cancelled 0
1374
+ # skipped 0
1375
+ # todo 0
1376
+ # duration_ms 501.633584