11/* eslint-disable @typescript-eslint/no-non-null-assertion */
2+ import { EJSON } from 'bson' ;
23import { expect } from 'chai' ;
34import { inspect } from 'util' ;
45
@@ -34,6 +35,7 @@ import {
3435 ExpectedCommandEvent ,
3536 ExpectedError ,
3637 ExpectedEventsForClient ,
38+ ExpectedLogMessage ,
3739 ExpectedSdamEvent
3840} from './schema' ;
3941
@@ -73,12 +75,26 @@ export interface SessionLsidOperator {
7375export function isSessionLsidOperator ( value : unknown ) : value is SessionLsidOperator {
7476 return typeof value === 'object' && value != null && '$$sessionLsid' in value ;
7577}
78+ export interface MatchAsDocumentOperator {
79+ $$matchAsDocument : unknown ;
80+ }
81+ export function isMatchAsDocumentOperator ( value : unknown ) : value is MatchAsDocumentOperator {
82+ return typeof value === 'object' && value != null && '$$matchAsDocument' in value ;
83+ }
84+ export interface MatchAsRootOperator {
85+ $$matchAsRoot : unknown ;
86+ }
87+ export function isMatchAsRootOperator ( value : unknown ) : value is MatchAsRootOperator {
88+ return typeof value === 'object' && value != null && '$$matchAsRoot' in value ;
89+ }
7690
7791export const SpecialOperatorKeys = [
7892 '$$exists' ,
7993 '$$type' ,
8094 '$$matchesEntity' ,
8195 '$$matchesHexBytes' ,
96+ '$$matchAsRoot' ,
97+ '$$matchAsDocument' ,
8298 '$$unsetOrMatches' ,
8399 '$$sessionLsid'
84100] ;
@@ -89,7 +105,9 @@ export type SpecialOperator =
89105 | MatchesEntityOperator
90106 | MatchesHexBytesOperator
91107 | UnsetOrMatchesOperator
92- | SessionLsidOperator ;
108+ | SessionLsidOperator
109+ | MatchAsDocumentOperator
110+ | MatchAsRootOperator ;
93111
94112type KeysOfUnion < T > = T extends object ? keyof T : never ;
95113export type SpecialOperatorKey = KeysOfUnion < SpecialOperator > ;
@@ -100,7 +118,9 @@ export function isSpecialOperator(value: unknown): value is SpecialOperator {
100118 isMatchesEntityOperator ( value ) ||
101119 isMatchesHexBytesOperator ( value ) ||
102120 isUnsetOrMatchesOperator ( value ) ||
103- isSessionLsidOperator ( value )
121+ isSessionLsidOperator ( value ) ||
122+ isMatchAsRootOperator ( value ) ||
123+ isMatchAsDocumentOperator ( value )
104124 ) ;
105125}
106126
@@ -199,6 +219,10 @@ export function resultCheck(
199219 return ;
200220 }
201221
222+ if ( typeof actual !== 'object' ) {
223+ expect . fail ( 'Expected actual value to be an object' ) ;
224+ }
225+
202226 const expectedEntries = Object . entries ( expected ) ;
203227
204228 if ( Array . isArray ( expected ) ) {
@@ -294,8 +318,9 @@ export function specialCheck(
294318 // $$sessionLsid
295319 const session = entities . getEntity ( 'session' , expected . $$sessionLsid , false ) ;
296320 expect ( session , `Session ${ expected . $$sessionLsid } does not exist in entities` ) . to . exist ;
297- const entitySessionHex = session . id ! . id . buffer . toString ( 'hex' ) . toUpperCase ( ) ;
298- const actualSessionHex = actual . id . buffer . toString ( 'hex' ) . toUpperCase ( ) ;
321+ const entitySessionHex = session . id ! . id . toString ( 'hex' ) . toUpperCase ( ) ;
322+ const actualSessionHex = actual . id ! . toString ( 'hex' ) . toUpperCase ( ) ;
323+
299324 expect (
300325 entitySessionHex ,
301326 `Session entity ${ expected . $$sessionLsid } does not match lsid`
@@ -323,6 +348,25 @@ export function specialCheck(
323348 ejson `expected value at path ${ path . join ( '' ) } NOT to exist, but received ${ actual } `
324349 ) . to . be . false ;
325350 }
351+ } else if ( isMatchAsDocumentOperator ( expected ) ) {
352+ if ( typeof actual === 'string' ) {
353+ const actualDoc = EJSON . parse ( actual , { relaxed : false } ) ;
354+ resultCheck ( actualDoc , expected . $$matchAsDocument as any , entities , path , true ) ;
355+ } else {
356+ expect . fail (
357+ `Expected value at path '${ path . join ( '' ) } ' to be string, but received ${ inspect ( actual ) } `
358+ ) ;
359+ }
360+ } else if ( isMatchAsRootOperator ( expected ) ) {
361+ expect (
362+ typeof actual ,
363+ `Expected value at path '${ path . join ( '' ) } ' to be object, but received ${ inspect ( actual ) } `
364+ ) . to . equal ( 'object' ) ;
365+ expect ( typeof expected . $$matchAsRoot , 'Value of $$matchAsRoot must be an object' ) . to . equal (
366+ 'object'
367+ ) ;
368+
369+ resultCheck ( actual , expected . $$matchAsRoot as any , entities , path , false ) ;
326370 } else {
327371 expect . fail ( `Unknown special operator: ${ JSON . stringify ( expected ) } ` ) ;
328372 }
@@ -523,6 +567,44 @@ export function matchesEvents(
523567 }
524568}
525569
570+ export function compareLogs (
571+ expected : ExpectedLogMessage [ ] ,
572+ actual : ExpectedLogMessage [ ] ,
573+ entities : EntitiesMap
574+ ) : void {
575+ expect ( actual ) . to . have . lengthOf ( expected . length ) ;
576+
577+ for ( const [ index , actualLog ] of actual . entries ( ) ) {
578+ const rootPrefix = `expectLogMessages[${ index } ]` ;
579+ const expectedLog = expected [ index ] ;
580+
581+ // Check that log levels match
582+ expect ( actualLog ) . to . have . property ( 'level' , expectedLog . level ) ;
583+
584+ // Check that components match
585+ expect ( actualLog ) . to . have . property ( 'component' , expectedLog . component ) ;
586+
587+ // NOTE: The spec states that if the failureIsRedacted flag is present, we
588+ // must assert that a failure occurred.
589+ if ( expectedLog . failureIsRedacted !== undefined ) {
590+ expect ( expectedLog . failureIsRedacted ) . to . be . a ( 'boolean' ) ;
591+ expect ( actualLog . data . failure , 'Expected failure to exist' ) . to . exist ;
592+ if ( expectedLog . failureIsRedacted ) {
593+ // Assert that failure has been redacted
594+ expect ( actualLog . data . failure , 'Expected failure to have been redacted' ) . to . deep . equal ( { } ) ;
595+ } else {
596+ // Assert that failure has not been redacted
597+ expect (
598+ actualLog . data . failure ,
599+ 'Expected failure to have not been redacted'
600+ ) . to . not . deep . equal ( { } ) ;
601+ }
602+ }
603+
604+ resultCheck ( actualLog . data , expectedLog . data , entities , [ rootPrefix ] , false ) ;
605+ }
606+ }
607+
526608function isMongoCryptError ( err ) : boolean {
527609 if ( err . constructor . name === 'MongoCryptError' ) {
528610 return true ;
0 commit comments