description: "timeoutMS behaves correctly for the withTransaction API"

schemaVersion: "1.26"

runOnRequirements:
  - minServerVersion: "4.4"
    topologies: ["replicaset", "sharded"]

createEntities:
  - client:
      id: &failPointClient failPointClient
      useMultipleMongoses: false
  - client:
      id: &client client
      uriOptions:
        timeoutMS: 500
        minPoolSize: 1
      awaitMinPoolSizeMS: 10000
      useMultipleMongoses: false
      observeEvents:
        - commandStartedEvent
        - commandFailedEvent
  - database:
      id: &database database
      client: *client
      databaseName: &databaseName test
  - collection:
      id: &collection collection
      database: *database
      collectionName: &collectionName coll
  - session:
      id: &session session
      client: *client

initialData:
  - collectionName: *collectionName
    databaseName: *databaseName
    documents: []

tests:
  - description: "withTransaction raises a client-side error if timeoutMS is overridden inside the callback"
    operations:
      - name: withTransaction
        object: *session
        arguments:
          callback:
            - name: insertOne
              object: *collection
              arguments:
                document: { _id: 1 }
                session: *session
                timeoutMS: 100
              expectError:
                isClientError: true
        expectError:
          isClientError: true
    expectEvents:
      # The only operation run fails with a client-side error, so there should be no events for the client.
      - client: *client
        events: []

  - description: "timeoutMS is not refreshed for each operation in the callback"
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 2 }
            data:
              failCommands: ["insert"]
              blockConnection: true
              blockTimeMS: 300
      - name: withTransaction
        object: *session
        arguments:
          callback:
            - name: insertOne
              object: *collection
              arguments:
                document: { _id: 1 }
                session: *session
            - name: insertOne
              object: *collection
              arguments:
                document: { _id: 2 }
                session: *session
              expectError:
                isTimeoutError: true
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: insert
              databaseName: *databaseName
              command:
                insert: *collectionName
                maxTimeMS: { $$type: ["int", "long"] }
          - commandStartedEvent:
              commandName: insert
              databaseName: *databaseName
              command:
                insert: *collectionName
                maxTimeMS: { $$type: ["int", "long"] }
          - commandFailedEvent:
              commandName: insert
          - commandStartedEvent:
              commandName: abortTransaction
              databaseName: admin
              command:
                abortTransaction: 1
                maxTimeMS: { $$type: [ "int", "long" ] }

  # This test verifies that when withTransaction encounters transient transaction errors it does not
  # throw the transient transaction error when the timeout is exceeded, but instead surfaces a timeout error after
  # exhausting the retry attempts within the specified timeout.
  # The timeout error thrown contains as a cause the last transient error encountered.
  - description: "withTransaction surfaces a timeout after exhausting transient transaction retries, retaining the last transient error as the timeout cause."
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: alwaysOn
            data:
              failCommands: ["insert"]
              blockConnection: true
              blockTimeMS: 25
              errorCode: 24
              errorLabels: ["TransientTransactionError"]

      - name: withTransaction
        object: *session
        arguments:
          callback:
            - name: insertOne
              object: *collection
              arguments:
                document: { _id: 1 }
                session: *session
              expectError:
                isError: true
        expectError:
          isTimeoutError: true

    # Verify that multiple insert (at least 2) attempts occurred due to TransientTransactionError retries
    # The exact number of events depends on timing and retry backoff, but there should be at least:
    # - 2 commandStartedEvent for insert (initial + at least one retry)
    # - 2 commandFailedEvent for insert (corresponding failures)
    expectEvents:
      - client: *client
        ignoreExtraEvents: true
        events:
          # First insert attempt
          - commandStartedEvent:
              commandName: insert
          - commandFailedEvent:
              commandName: insert
          - commandStartedEvent:
              commandName: abortTransaction
          - commandFailedEvent:
              commandName: abortTransaction

          # Second insert attempt (retry due to TransientTransactionError)
          - commandStartedEvent:
              commandName: insert
          - commandFailedEvent:
              commandName: insert
          - commandStartedEvent:
              commandName: abortTransaction
          - commandFailedEvent:
              commandName: abortTransaction
