@@ -134,83 +134,114 @@ TYPE_MAP.set(
134
134
actual => ( typeof actual === 'number' && Number . isInteger ( actual ) ) || Long . isLong ( actual )
135
135
) ;
136
136
137
+ /**
138
+ * resultCheck
139
+ *
140
+ * @param actual - the actual result
141
+ * @param expected - the expected result
142
+ * @param entities - the EntitiesMap associated with the test
143
+ * @param path - an array of strings representing the 'path' in the document down to the current
144
+ * value. For example, given `{ a: { b: { c: 4 } } }`, when evaluating `{ c: 4 }`, the path
145
+ * will look like: `['a', 'b']`. Used to print useful error messages when assertions fail.
146
+ * @param checkExtraKeys - a boolean value that determines how keys present on the `actual` object but
147
+ * not on the `expected` object are treated. When set to `true`, any extra keys on the
148
+ * `actual` object will throw an error
149
+ */
137
150
export function resultCheck (
138
151
actual : Document ,
139
152
expected : Document | number | string | boolean ,
140
153
entities : EntitiesMap ,
141
154
path : string [ ] = [ ] ,
142
- depth = 0
155
+ checkExtraKeys = false
143
156
) : void {
157
+ function checkNestedDocuments ( key : string , value : any , checkExtraKeys : boolean ) {
158
+ if ( key === 'sort' ) {
159
+ // TODO: This is a workaround that works because all sorts in the specs
160
+ // are objects with one key; ideally we'd want to adjust the spec definitions
161
+ // to indicate whether order matters for any given key and set general
162
+ // expectations accordingly (see NODE-3235)
163
+ expect ( Object . keys ( value ) ) . to . have . lengthOf ( 1 ) ;
164
+ expect ( actual [ key ] ) . to . be . instanceOf ( Map ) ;
165
+ expect ( actual [ key ] . size ) . to . equal ( 1 ) ;
166
+ const expectedSortKey = Object . keys ( value ) [ 0 ] ;
167
+ expect ( actual [ key ] ) . to . have . all . keys ( expectedSortKey ) ;
168
+ const objFromActual = { [ expectedSortKey ] : actual [ key ] . get ( expectedSortKey ) } ;
169
+ resultCheck ( objFromActual , value , entities , path , checkExtraKeys ) ;
170
+ } else {
171
+ resultCheck ( actual [ key ] , value , entities , path , checkExtraKeys ) ;
172
+ }
173
+ }
174
+
144
175
if ( typeof expected === 'object' && expected ) {
145
176
// Expected is an object
146
177
// either its a special operator or just an object to check equality against
147
178
148
179
if ( isSpecialOperator ( expected ) ) {
149
180
// Special operation check is a base condition
150
181
// specialCheck may recurse depending upon the check ($$unsetOrMatches)
151
- specialCheck ( actual , expected , entities , path , depth ) ;
182
+ specialCheck ( actual , expected , entities , path , checkExtraKeys ) ;
152
183
return ;
184
+ }
185
+
186
+ const expectedEntries = Object . entries ( expected ) ;
187
+
188
+ if ( Array . isArray ( expected ) ) {
189
+ if ( ! Array . isArray ( actual ) ) {
190
+ expect . fail (
191
+ `expected value at ${ path . join ( '.' ) } to be an array, but received ${ inspect ( actual ) } `
192
+ ) ;
193
+ }
194
+ for ( const [ index , value ] of expectedEntries ) {
195
+ path . push ( `[${ index } ]` ) ;
196
+ checkNestedDocuments ( index , value , false ) ;
197
+ path . pop ( ) ;
198
+ }
153
199
} else {
154
- // Just a plain object, however this object can contain special operations
155
- // So we need to recurse over each key,value
156
- const expectedEntries = Object . entries ( expected ) ;
200
+ for ( const [ key , value ] of expectedEntries ) {
201
+ path . push ( `.${ key } ` ) ;
202
+ checkNestedDocuments ( key , value , true ) ;
203
+ path . pop ( ) ;
204
+ }
157
205
158
- if ( depth > 1 ) {
206
+ if ( checkExtraKeys ) {
159
207
expect ( actual , `Expected actual to exist at ${ path . join ( '' ) } ` ) . to . exist ;
160
208
const actualKeys = Object . keys ( actual ) ;
161
209
const expectedKeys = Object . keys ( expected ) ;
162
210
// Don't check for full key set equality because some of the actual keys
163
211
// might be e.g. $$unsetOrMatches, which can be omitted.
164
- expect (
165
- actualKeys . filter ( key => ! expectedKeys . includes ( key ) ) ,
166
- `[${ Object . keys ( actual ) } ] has more than the expected keys: [${ Object . keys ( expected ) } ]`
167
- ) . to . have . lengthOf ( 0 ) ;
168
- }
212
+ const extraKeys = actualKeys . filter ( key => ! expectedKeys . includes ( key ) ) ;
169
213
170
- for ( const [ key , value ] of expectedEntries ) {
171
- path . push ( Array . isArray ( expected ) ? `[${ key } ]` : `.${ key } ` ) ; // record what key we're at
172
- depth += 1 ;
173
- if ( key === 'sort' ) {
174
- // TODO: This is a workaround that works because all sorts in the specs
175
- // are objects with one key; ideally we'd want to adjust the spec definitions
176
- // to indicate whether order matters for any given key and set general
177
- // expectations accordingly (see NODE-3235)
178
- expect ( Object . keys ( value ) ) . to . have . lengthOf ( 1 ) ;
179
- expect ( actual [ key ] ) . to . be . instanceOf ( Map ) ;
180
- expect ( actual [ key ] . size ) . to . equal ( 1 ) ;
181
- const expectedSortKey = Object . keys ( value ) [ 0 ] ;
182
- expect ( actual [ key ] ) . to . have . all . keys ( expectedSortKey ) ;
183
- const objFromActual = { [ expectedSortKey ] : actual [ key ] . get ( expectedSortKey ) } ;
184
- resultCheck ( objFromActual , value , entities , path , depth ) ;
185
- } else {
186
- resultCheck ( actual [ key ] , value , entities , path , depth ) ;
214
+ if ( extraKeys . length > 0 ) {
215
+ expect . fail (
216
+ `object has more keys than expected. \n\tactual: [${ actualKeys } ] \n\texpected: [${ expectedKeys } ]`
217
+ ) ;
187
218
}
188
- depth -= 1 ;
189
- path . pop ( ) ; // if the recursion was successful we can drop the tested key
190
219
}
191
220
}
221
+
222
+ return ;
223
+ }
224
+
225
+ // Here's our recursion base case
226
+ // expected is: number | Long | string | boolean | null
227
+ if ( Long . isLong ( actual ) && typeof expected === 'number' ) {
228
+ // Long requires special equality check
229
+ expect ( actual . equals ( expected ) ) . to . be . true ;
230
+ } else if ( Long . isLong ( expected ) && typeof actual === 'number' ) {
231
+ // Long requires special equality check
232
+ expect ( expected . equals ( actual ) ) . to . be . true ;
233
+ } else if ( Number . isNaN ( actual ) && Number . isNaN ( expected ) ) {
234
+ // in JS, NaN isn't equal to NaN but we want to not fail if we have two NaN
235
+ } else if (
236
+ typeof expected === 'number' &&
237
+ typeof actual === 'number' &&
238
+ expected === 0 &&
239
+ actual === 0
240
+ ) {
241
+ // case to handle +0 and -0
242
+ expect ( Object . is ( expected , actual ) ) . to . be . true ;
192
243
} else {
193
- // Here's our recursion base case
194
- // expected is: number | Long | string | boolean | null
195
- if ( Long . isLong ( actual ) && typeof expected === 'number' ) {
196
- // Long requires special equality check
197
- expect ( actual . equals ( expected ) ) . to . be . true ;
198
- } else if ( Long . isLong ( expected ) && typeof actual === 'number' ) {
199
- // Long requires special equality check
200
- expect ( expected . equals ( actual ) ) . to . be . true ;
201
- } else if ( Number . isNaN ( actual ) && Number . isNaN ( expected ) ) {
202
- // in JS, NaN isn't equal to NaN but we want to not fail if we have two NaN
203
- } else if (
204
- typeof expected === 'number' &&
205
- typeof actual === 'number' &&
206
- expected === 0 &&
207
- actual === 0
208
- ) {
209
- // case to handle +0 and -0
210
- expect ( Object . is ( expected , actual ) ) . to . be . true ;
211
- } else {
212
- expect ( actual ) . to . equal ( expected ) ;
213
- }
244
+ expect ( actual ) . to . equal ( expected ) ;
214
245
}
215
246
}
216
247
@@ -219,16 +250,12 @@ export function specialCheck(
219
250
expected : SpecialOperator ,
220
251
entities : EntitiesMap ,
221
252
path : string [ ] = [ ] ,
222
- depth = 0
253
+ checkExtraKeys : boolean
223
254
) : boolean {
224
255
if ( isUnsetOrMatchesOperator ( expected ) ) {
225
- // $$unsetOrMatches
226
256
if ( actual === null || actual === undefined ) return ;
227
- else {
228
- depth += 1 ;
229
- resultCheck ( actual , expected . $$unsetOrMatches , entities , path , depth ) ;
230
- depth -= 1 ;
231
- }
257
+
258
+ resultCheck ( actual , expected . $$unsetOrMatches , entities , path , checkExtraKeys ) ;
232
259
} else if ( isMatchesEntityOperator ( expected ) ) {
233
260
// $$matchesEntity
234
261
const entity = entities . get ( expected . $$matchesEntity ) ;
@@ -318,6 +345,60 @@ function failOnMismatchedCount(
318
345
) ;
319
346
}
320
347
348
+ function compareCommandStartedEvents (
349
+ actual : CommandStartedEvent ,
350
+ expected : ExpectedCommandEvent [ 'commandStartedEvent' ] ,
351
+ entities : EntitiesMap ,
352
+ prefix : string
353
+ ) {
354
+ if ( expected . command ) {
355
+ resultCheck ( actual . command , expected . command , entities , [ `${ prefix } .command` ] ) ;
356
+ }
357
+ if ( expected . commandName ) {
358
+ expect (
359
+ expected . commandName ,
360
+ `expected ${ prefix } .commandName to equal ${ expected . commandName } but received ${ actual . commandName } `
361
+ ) . to . equal ( actual . commandName ) ;
362
+ }
363
+ if ( expected . databaseName ) {
364
+ expect (
365
+ expected . databaseName ,
366
+ `expected ${ prefix } .databaseName to equal ${ expected . databaseName } but received ${ actual . databaseName } `
367
+ ) . to . equal ( actual . databaseName ) ;
368
+ }
369
+ }
370
+
371
+ function compareCommandSucceededEvents (
372
+ actual : CommandSucceededEvent ,
373
+ expected : ExpectedCommandEvent [ 'commandSucceededEvent' ] ,
374
+ entities : EntitiesMap ,
375
+ prefix : string
376
+ ) {
377
+ if ( expected . reply ) {
378
+ resultCheck ( actual . reply , expected . reply , entities , [ prefix ] ) ;
379
+ }
380
+ if ( expected . commandName ) {
381
+ expect (
382
+ expected . commandName ,
383
+ `expected ${ prefix } .commandName to equal ${ expected . commandName } but received ${ actual . commandName } `
384
+ ) . to . equal ( actual . commandName ) ;
385
+ }
386
+ }
387
+
388
+ function compareCommandFailedEvents (
389
+ actual : CommandFailedEvent ,
390
+ expected : ExpectedCommandEvent [ 'commandFailedEvent' ] ,
391
+ entities : EntitiesMap ,
392
+ prefix : string
393
+ ) {
394
+ if ( expected . commandName ) {
395
+ expect (
396
+ expected . commandName ,
397
+ `expected ${ prefix } .commandName to equal ${ expected . commandName } but received ${ actual . commandName } `
398
+ ) . to . equal ( actual . commandName ) ;
399
+ }
400
+ }
401
+
321
402
function compareEvents (
322
403
actual : CommandEvent [ ] | CmapEvent [ ] ,
323
404
expected : ( ExpectedCommandEvent & ExpectedCmapEvent ) [ ] ,
@@ -328,22 +409,31 @@ function compareEvents(
328
409
}
329
410
for ( const [ index , actualEvent ] of actual . entries ( ) ) {
330
411
const expectedEvent = expected [ index ] ;
412
+ const rootPrefix = `events[${ index } ]` ;
331
413
332
414
if ( expectedEvent . commandStartedEvent ) {
333
- expect ( actualEvent ) . to . be . instanceOf ( CommandStartedEvent ) ;
334
- resultCheck ( actualEvent , expectedEvent . commandStartedEvent , entities , [
335
- `events[${ index } ].commandStartedEvent`
336
- ] ) ;
415
+ const path = `${ rootPrefix } .commandStartedEvent` ;
416
+ if ( ! ( actualEvent instanceof CommandStartedEvent ) ) {
417
+ expect . fail ( `expected ${ path } to be instanceof CommandStartedEvent` ) ;
418
+ }
419
+ compareCommandStartedEvents ( actualEvent , expectedEvent . commandStartedEvent , entities , path ) ;
337
420
} else if ( expectedEvent . commandSucceededEvent ) {
338
- expect ( actualEvent ) . to . be . instanceOf ( CommandSucceededEvent ) ;
339
- resultCheck ( actualEvent , expectedEvent . commandSucceededEvent , entities , [
340
- `events[${ index } ].commandSucceededEvent`
341
- ] ) ;
421
+ const path = `${ rootPrefix } .commandSucceededEvent` ;
422
+ if ( ! ( actualEvent instanceof CommandSucceededEvent ) ) {
423
+ expect . fail ( `expected ${ path } to be instanceof CommandSucceededEvent` ) ;
424
+ }
425
+ compareCommandSucceededEvents (
426
+ actualEvent ,
427
+ expectedEvent . commandSucceededEvent ,
428
+ entities ,
429
+ path
430
+ ) ;
342
431
} else if ( expectedEvent . commandFailedEvent ) {
343
- expect ( actualEvent ) . to . be . instanceOf ( CommandFailedEvent ) ;
344
- expect ( actualEvent )
345
- . to . have . property ( 'commandName' )
346
- . that . equals ( expectedEvent . commandFailedEvent . commandName ) ;
432
+ const path = `${ rootPrefix } .commandFailedEvent` ;
433
+ if ( ! ( actualEvent instanceof CommandFailedEvent ) ) {
434
+ expect . fail ( `expected ${ path } to be instanceof CommandFailedEvent` ) ;
435
+ }
436
+ compareCommandFailedEvents ( actualEvent , expectedEvent . commandFailedEvent , entities , path ) ;
347
437
} else if ( expectedEvent . connectionClosedEvent ) {
348
438
expect ( actualEvent ) . to . be . instanceOf ( ConnectionClosedEvent ) ;
349
439
if ( expectedEvent . connectionClosedEvent . hasServiceId ) {
@@ -354,8 +444,10 @@ function compareEvents(
354
444
if ( expectedEvent . poolClearedEvent . hasServiceId ) {
355
445
expect ( actualEvent ) . property ( 'serviceId' ) . to . exist ;
356
446
}
357
- } else if ( ! validEmptyCmapEvent ( expectedEvent , actualEvent ) ) {
358
- expect . fail ( `Events must be one of the known types, got ${ inspect ( actualEvent ) } ` ) ;
447
+ } else if ( validEmptyCmapEvent ( expectedEvent , actualEvent ) ) {
448
+ return ;
449
+ } else {
450
+ expect . fail ( `Encountered unexpected event - ${ inspect ( actualEvent ) } ` ) ;
359
451
}
360
452
}
361
453
}
0 commit comments