description: "timeoutMS behaves correctly for non-tailable cursors"

schemaVersion: "1.26"

runOnRequirements:
  - minServerVersion: "4.4"

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

initialData:
  - collectionName: *collectionName
    databaseName: *databaseName
    documents:
      - { _id: 0 }
      - { _id: 1 }
      - { _id: 2 }
  - collectionName: &aggregateOutputCollectionName aggregateOutputColl
    databaseName: *databaseName
    documents: []

tests:
  # If timeoutMode is explicitly set to CURSOR_LIFETIME, the timeout should apply to the initial command.
  # This should also be the case if timeoutMode is unset, but this is already tested in global-timeoutMS.yml.
  - description: "timeoutMS applied to find if timeoutMode is cursor_lifetime"
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: ["find"]
              blockConnection: true
              blockTimeMS: 250
      - name: find
        object: *collection
        arguments:
          filter: {}
          timeoutMode: cursorLifetime
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *databaseName
              command:
                find: *collectionName
                maxTimeMS: { $$type: ["int", "long"] }

  # If timeoutMode is unset, it should default to CURSOR_LIFETIME and the time remaining after the find succeeds should
  # be applied to the getMore.
  - description: "remaining timeoutMS applied to getMore if timeoutMode is unset"
    operations:
      # Block find/getMore for 15ms.
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 2 }
            data:
              failCommands: ["find", "getMore"]
              blockConnection: true
              blockTimeMS: 125
      # Run a find with timeoutMS=200 and batchSize=1 to force two batches, which will cause a find and a getMore to be
      # sent. Both will block for 125ms, so together they will go over the timeout.
      - name: find
        object: *collection
        arguments:
          filter: {}
          timeoutMS: 200
          batchSize: 2
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *databaseName
              command:
                find: *collectionName
                maxTimeMS: { $$type: ["int", "long"] }
          - commandStartedEvent:
              commandName: getMore
              databaseName: *databaseName
              command:
                getMore: { $$type: ["int", "long"] }
                collection: *collectionName
                maxTimeMS: { $$exists: false }

  # Same test as above, but with timeoutMode explicitly set to CURSOR_LIFETIME.
  - description: "remaining timeoutMS applied to getMore if timeoutMode is cursor_lifetime"
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 2 }
            data:
              failCommands: ["find", "getMore"]
              blockConnection: true
              blockTimeMS: 150
      - name: find
        object: *collection
        arguments:
          filter: {}
          timeoutMode: cursorLifetime
          timeoutMS: 200
          batchSize: 2
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *databaseName
              command:
                find: *collectionName
                maxTimeMS: { $$type: ["int", "long"] }
          - commandStartedEvent:
              commandName: getMore
              databaseName: *databaseName
              command:
                getMore: { $$type: ["int", "long"] }
                collection: *collectionName
                maxTimeMS: { $$exists: false }

  # If timeoutMode=ITERATION, timeoutMS should apply to the initial find command and the command shouldn't have a
  # maxTimeMS field.
  - description: "timeoutMS applied to find if timeoutMode is iteration"
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: ["find"]
              blockConnection: true
              blockTimeMS: 250
      - name: find
        object: *collection
        arguments:
          filter: {}
          timeoutMode: iteration
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *databaseName
              command:
                find: *collectionName
                maxTimeMS: { $$exists: false }

  # If timeoutMode=ITERATION, timeoutMS applies separately to the initial find and the getMore on the cursor. Neither
  # command should have a maxTimeMS field. This is a success test. The "find" is executed with timeoutMS=200 and both
  # "find" and "getMore" commands are blocked for 125ms each. Neither exceeds the timeout, so iteration succeeds.
  - description: "timeoutMS is refreshed for getMore if timeoutMode is iteration - success"
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 2 }
            data:
              failCommands: ["find", "getMore"]
              blockConnection: true
              blockTimeMS: 125
      - name: find
        object: *collection
        arguments:
          filter: {}
          timeoutMode: iteration
          timeoutMS: 200
          batchSize: 2
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *databaseName
              command:
                find: *collectionName
                maxTimeMS: { $$exists: false }
          - commandStartedEvent:
              commandName: getMore
              databaseName: *databaseName
              command:
                getMore: { $$type: ["int", "long"] }
                collection: *collectionName
                maxTimeMS: { $$exists: false }

  # If timeoutMode=ITERATION, timeoutMS applies separately to the initial find and the getMore on the cursor. Neither
  # command should have a maxTimeMS field. This is a failure test. The "find" inherits timeoutMS=200 and "getMore"
  # commands are blocked for 250ms, causing iteration to fail with a timeout error.
  - description: "timeoutMS is refreshed for getMore if timeoutMode is iteration - failure"
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: ["getMore"]
              blockConnection: true
              blockTimeMS: 250
      - name: find
        object: *collection
        arguments:
          filter: {}
          timeoutMode: iteration
          batchSize: 2
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *databaseName
              command:
                find: *collectionName
                maxTimeMS: { $$exists: false }
          - commandStartedEvent:
              commandName: getMore
              databaseName: *databaseName
              command:
                getMore: { $$type: ["int", "long"] }
                collection: *collectionName
                maxTimeMS: { $$exists: false }

  - description: "aggregate with $out errors if timeoutMode is iteration"
    operations:
      - name: aggregate
        object: *collection
        arguments:
          pipeline:
            - $out: *aggregateOutputCollectionName
          timeoutMS: 100
          timeoutMode: iteration
        expectError:
          isClientError: true
    expectEvents:
      - client: *client
        events: []

  - description: "aggregate with $merge errors if timeoutMode is iteration"
    operations:
      - name: aggregate
        object: *collection
        arguments:
          pipeline:
            - $merge: *aggregateOutputCollectionName
          timeoutMS: 100
          timeoutMode: iteration
        expectError:
          isClientError: true
    expectEvents:
      - client: *client
        events: []
