@@ -894,4 +894,153 @@ describe('CSOT driver tests', metadata, () => {
894894 } ) ;
895895 } ) ;
896896 } ) ;
897+
898+ describe ( 'when using an explicit session' , ( ) => {
899+ const metadata : MongoDBMetadataUI = {
900+ requires : { topology : [ 'replicaset' ] , mongodb : '>=4.4' }
901+ } ;
902+
903+ describe ( 'created for a withTransaction callback' , ( ) => {
904+ describe ( 'passing a timeoutMS and a session with a timeoutContext' , ( ) => {
905+ let client : MongoClient ;
906+
907+ beforeEach ( async function ( ) {
908+ client = this . configuration . newClient ( { timeoutMS : 123 } ) ;
909+ } ) ;
910+
911+ afterEach ( async function ( ) {
912+ await client . close ( ) ;
913+ } ) ;
914+
915+ it ( 'throws a validation error from the operation' , metadata , async ( ) => {
916+ // Drivers MUST raise a validation error if an explicit session with a timeout is used and
917+ // the timeoutMS option is set at the operation level for operations executed as part of a withTransaction callback.
918+
919+ const coll = client . db ( 'db' ) . collection ( 'coll' ) ;
920+
921+ const session = client . startSession ( ) ;
922+
923+ let insertError : Error | null = null ;
924+ const withTransactionError = await session
925+ . withTransaction ( async session => {
926+ insertError = await coll
927+ . insertOne ( { x : 1 } , { session, timeoutMS : 1234 } )
928+ . catch ( error => error ) ;
929+ throw insertError ;
930+ } )
931+ . catch ( error => error ) ;
932+
933+ expect ( insertError ) . to . be . instanceOf ( MongoInvalidArgumentError ) ;
934+ expect ( withTransactionError ) . to . be . instanceOf ( MongoInvalidArgumentError ) ;
935+ } ) ;
936+ } ) ;
937+ } ) ;
938+
939+ describe ( 'created manually' , ( ) => {
940+ describe ( 'passing a timeoutMS and a session with an inherited timeoutMS' , ( ) => {
941+ let client : MongoClient ;
942+
943+ beforeEach ( async function ( ) {
944+ client = this . configuration . newClient ( { timeoutMS : 123 } ) ;
945+ } ) ;
946+
947+ afterEach ( async function ( ) {
948+ await client . close ( ) ;
949+ } ) ;
950+
951+ it ( 'does not throw a validation error' , metadata , async ( ) => {
952+ const coll = client . db ( 'db' ) . collection ( 'coll' ) ;
953+ const session = client . startSession ( ) ;
954+ session . startTransaction ( ) ;
955+ await coll . insertOne ( { x : 1 } , { session, timeoutMS : 1234 } ) ;
956+ await session . abortTransaction ( ) ; // this uses the inherited timeoutMS, not the insert
957+ } ) ;
958+ } ) ;
959+ } ) ;
960+ } ) ;
961+
962+ describe ( 'Convenient Transactions' , ( ) => {
963+ /** Tests in this section MUST only run against replica sets and sharded clusters with server versions 4.4 or higher. */
964+ const metadata : MongoDBMetadataUI = {
965+ requires : { topology : [ 'replicaset' , 'sharded' ] , mongodb : '>=5.0' }
966+ } ;
967+
968+ describe ( 'when an operation fails inside withTransaction callback' , ( ) => {
969+ const failpoint : FailPoint = {
970+ configureFailPoint : 'failCommand' ,
971+ mode : { times : 2 } ,
972+ data : {
973+ failCommands : [ 'insert' , 'abortTransaction' ] ,
974+ blockConnection : true ,
975+ blockTimeMS : 600
976+ }
977+ } ;
978+
979+ beforeEach ( async function ( ) {
980+ if ( ! semver . satisfies ( this . configuration . version , '>=4.4' ) ) {
981+ this . skipReason = 'Requires server version 4.4+' ;
982+ this . skip ( ) ;
983+ }
984+ const internalClient = this . configuration . newClient ( ) ;
985+ await internalClient
986+ . db ( 'db' )
987+ . collection ( 'coll' )
988+ . drop ( )
989+ . catch ( ( ) => null ) ;
990+ await internalClient . db ( 'admin' ) . command ( failpoint ) ;
991+ await internalClient . close ( ) ;
992+ } ) ;
993+
994+ let client : MongoClient ;
995+
996+ afterEach ( async function ( ) {
997+ if ( semver . satisfies ( this . configuration . version , '>=4.4' ) ) {
998+ const internalClient = this . configuration . newClient ( ) ;
999+ await internalClient
1000+ . db ( 'admin' )
1001+ . command ( { configureFailPoint : 'failCommand' , mode : 'off' } ) ;
1002+ await internalClient . close ( ) ;
1003+ }
1004+ await client ?. close ( ) ;
1005+ } ) ;
1006+
1007+ it (
1008+ 'timeoutMS is refreshed for abortTransaction and the timeout error is thrown from the operation' ,
1009+ metadata ,
1010+ async function ( ) {
1011+ const commandsFailed = [ ] ;
1012+ const commandsStarted = [ ] ;
1013+
1014+ client = this . configuration
1015+ . newClient ( { timeoutMS : 500 , monitorCommands : true } )
1016+ . on ( 'commandStarted' , e => commandsStarted . push ( e . commandName ) )
1017+ . on ( 'commandFailed' , e => commandsFailed . push ( e . commandName ) ) ;
1018+
1019+ const coll = client . db ( 'db' ) . collection ( 'coll' ) ;
1020+
1021+ const session = client . startSession ( ) ;
1022+
1023+ let insertError : Error | null = null ;
1024+ const withTransactionError = await session
1025+ . withTransaction ( async session => {
1026+ insertError = await coll . insertOne ( { x : 1 } , { session } ) . catch ( error => error ) ;
1027+ throw insertError ;
1028+ } )
1029+ . catch ( error => error ) ;
1030+
1031+ try {
1032+ expect ( insertError ) . to . be . instanceOf ( MongoOperationTimeoutError ) ;
1033+ expect ( withTransactionError ) . to . be . instanceOf ( MongoOperationTimeoutError ) ;
1034+ expect ( commandsStarted , 'commands started' ) . to . deep . equal ( [
1035+ 'insert' ,
1036+ 'abortTransaction'
1037+ ] ) ;
1038+ expect ( commandsFailed , 'commands failed' ) . to . deep . equal ( [ 'insert' , 'abortTransaction' ] ) ;
1039+ } finally {
1040+ await session . endSession ( ) ;
1041+ }
1042+ }
1043+ ) ;
1044+ } ) ;
1045+ } ) ;
8971046} ) ;
0 commit comments