@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.
- package/.turbo/turbo-lint.log +7 -0
- package/.turbo/turbo-test.log +1376 -0
- package/README.md +419 -0
- package/package.json +29 -0
- package/src/auth/index.js +127 -0
- package/src/auth/password.js +9 -0
- package/src/auth.test.js +90 -0
- package/src/authorization.js +235 -0
- package/src/authorization.test.js +314 -0
- package/src/cache.js +137 -0
- package/src/cache.test.js +217 -0
- package/src/csrf.js +118 -0
- package/src/csrf.test.js +157 -0
- package/src/email.js +140 -0
- package/src/email.test.js +259 -0
- package/src/error-reporter.js +266 -0
- package/src/error-reporter.test.js +236 -0
- package/src/errors.js +192 -0
- package/src/errors.test.js +128 -0
- package/src/index.js +32 -0
- package/src/logger.js +182 -0
- package/src/logger.test.js +287 -0
- package/src/mailhog.js +43 -0
- package/src/queue/index.js +214 -0
- package/src/rate-limiter.js +253 -0
- package/src/rate-limiter.test.js +130 -0
- package/src/redis.js +96 -0
- package/src/testing/factories.js +93 -0
- package/src/testing/helpers.js +266 -0
- package/src/testing/index.js +46 -0
- package/src/testing/mocks.js +480 -0
- package/src/testing/testing.test.js +543 -0
- package/src/types.js +74 -0
- package/src/utils.js +219 -0
- package/src/utils.test.js +193 -0
- package/src/validation.js +26 -0
- package/test-imports.mjs +21 -0
|
@@ -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
|
+
# [2m18:43:11.931[0m [31mERROR[0m [2m[quark][0m test error [2m{"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)"}[0m
|
|
571
|
+
# [2m18:43:11.933[0m [31mERROR[0m [2m[quark][0m test [2m{"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)"}[0m
|
|
572
|
+
# [2m18:43:11.933[0m [33mWARN [0m [2m[quark][0m hello [2m{"extra":true}[0m
|
|
573
|
+
# [2m18:43:11.934[0m [32mINFO [0m [2m[quark][0m hello
|
|
574
|
+
# [2m18:43:11.934[0m [31mERROR[0m [2m[quark][0m test [2m{"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)"}[0m
|
|
575
|
+
# [2m18:43:11.935[0m [31mERROR[0m [2m[quark][0m test [2m{"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)"}[0m
|
|
576
|
+
# [2m18:43:11.935[0m [31mERROR[0m [2m[quark][0m custom test [2m{"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)"}[0m
|
|
577
|
+
# [2m18:43:11.936[0m [31mERROR[0m [2m[quark][0m test [2m{"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)"}[0m
|
|
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
|
+
# [2m18:43:11.905[0m [31mERROR[0m [2m[quark][0m test [2m{"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)"}[0m
|
|
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
|