description: runCursorCommand

schemaVersion: '1.9'

runOnRequirements:
  - minServerVersion: "4.4"

createEntities:
  - client:
      id: &failPointClient failPointClient
      useMultipleMongoses: false
  - client:
      id: &commandClient commandClient
      useMultipleMongoses: false
      observeEvents: [commandStartedEvent, commandSucceededEvent]
  - client:
      id: &client client
      useMultipleMongoses: false
      observeEvents: [commandStartedEvent]
      ignoreCommandMonitoringEvents: [killCursors]
  - database: # For tests that need success event assertions
      id: &commandDb commandDb
      client: *commandClient
      databaseName: *commandDb
  - database:
      id: &db db
      client: *client
      databaseName: *db
  - collection:
      id: &collection collection
      database: *db
      collectionName: *collection

initialData:
  - collectionName: *collection
    databaseName: *db
    documents: &documents
      - { _id: 1, x: 11 }
      - { _id: 2, x: 22 }
      - { _id: 3, x: 33 }
      - { _id: 4, x: 44 }
      - { _id: 5, x: 55 }

tests:
  - description: errors if timeoutMode is set without timeoutMS
    operations:
      - name: runCursorCommand
        object: *db
        arguments:
          commandName: find
          command: { find: *collection }
          timeoutMode: cursorLifetime
        expectError:
          isClientError: true

  - description: error if timeoutMode is cursorLifetime and cursorType is tailableAwait
    operations:
      - name: runCursorCommand
        object: *db
        arguments:
          commandName: find
          command: { find: *collection }
          timeoutMode: cursorLifetime
          cursorType: tailableAwait
        expectError:
          isClientError: true

  # 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: Non-tailable cursor lifetime remaining timeoutMS applied to getMore if timeoutMode is unset
    runOnRequirements:
      - serverless: forbid
    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: 60
      # Run a find with timeoutMS less than double our failPoint blockTimeMS and
      # batchSize less than the total document count will cause a find and a getMore to be sent.
      # Both will block for 60ms so together they will go over the timeout.
      - name: runCursorCommand
        object: *db
        arguments:
          commandName: find
          timeoutMS: 100
          command: { find: *collection, batchSize: 2 }
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              command:
                find: *collection
                maxTimeMS: { $$type: [int, long] }
          - commandStartedEvent:
              commandName: getMore
              command:
                getMore: { $$type: [int, long] }
                collection: *collection
                maxTimeMS: { $$exists: true }

  # 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=100 and "getMore"
  # commands are blocked for 60ms, causing iteration to fail with a timeout error.
  - description: Non=tailable cursor iteration timeoutMS is refreshed for getMore if timeoutMode is iteration - failure
    runOnRequirements:
      - serverless: forbid
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: ["getMore"]
              blockConnection: true
              blockTimeMS: 60
      - name: runCursorCommand
        object: *db
        arguments:
          commandName: find
          command: { find: *collection, batchSize: 2 }
          timeoutMode: iteration
          timeoutMS: 100
          batchSize: 2
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *db
              command:
                find: *collection
                maxTimeMS: { $$exists: false }
          - commandStartedEvent:
              commandName: getMore
              databaseName: *db
              command:
                getMore: { $$type: ["int", "long"] }
                collection: *collection
                maxTimeMS: { $$exists: false }

  # The timeoutMS option should apply separately to the initial "find" and each getMore. This is a failure test. The
  # find inherits timeoutMS=100 from the collection and the getMore command blocks for 60ms, causing iteration to fail
  # with a timeout error.
  - description: Tailable cursor iteration timeoutMS is refreshed for getMore - failure
    runOnRequirements:
      - serverless: forbid
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: ["getMore"]
              blockConnection: true
              blockTimeMS: 60
      - name: dropCollection
        object: *db
        arguments:
          collection: &cappedCollection cappedCollection
      - name: createCollection
        object: *db
        arguments:
          collection: *cappedCollection
          capped: true
          size: 4096
          max: 3
        saveResultAsEntity: *cappedCollection
      - name: insertMany
        object: *cappedCollection
        arguments:
          documents:
            - { _id: 1, x: 11 }
            - { _id: 2, x: 22 }
      - name: createCommandCursor
        object: *db
        arguments:
          commandName: find
          command: { find: *cappedCollection, batchSize: 1, tailable: true }
          timeoutMode: iteration
          timeoutMS: 100
          batchSize: 1
          cursorType: tailable
        saveResultAsEntity: &tailableCursor tailableCursor
      # Iterate the cursor twice: the first iteration will return the document from the batch in the find and the
      # second will do a getMore.
      - name: iterateUntilDocumentOrError
        object: *tailableCursor
      - name: iterateUntilDocumentOrError
        object: *tailableCursor
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: drop
          - commandStartedEvent:
              commandName: create
          - commandStartedEvent:
              commandName: insert
          - commandStartedEvent:
              commandName: find
              databaseName: *db
              command:
                find: *cappedCollection
                tailable: true
                awaitData: { $$exists: false }
                maxTimeMS: { $$exists: false }
          - commandStartedEvent:
              commandName: getMore
              databaseName: *db
              command:
                getMore: { $$type: ["int", "long"] }
                collection: *cappedCollection
                maxTimeMS: { $$exists: false }

  # The timeoutMS value should be refreshed for getMore's. This is a failure test. The find inherits timeoutMS=10 from
  # the collection and the getMore blocks for 15ms, causing iteration to fail with a timeout error.
  - description: Tailable cursor awaitData iteration timeoutMS is refreshed for getMore - failure
    runOnRequirements:
      - serverless: forbid
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: ["getMore"]
              blockConnection: true
              blockTimeMS: 60
      - name: dropCollection
        object: *db
        arguments:
          collection: &cappedCollection cappedCollection
      - name: createCollection
        object: *db
        arguments:
          collection: *cappedCollection
          capped: true
          size: 4096
          max: 3
        saveResultAsEntity: *cappedCollection
      - name: insertMany
        object: *cappedCollection
        arguments:
          documents: [ { foo: bar }, { fizz: buzz } ]
      - name: createCommandCursor
        object: *db
        arguments:
          command: { find: *cappedCollection, tailable: true, awaitData: true }
          cursorType: tailableAwait
          batchSize: 1
        saveResultAsEntity: &tailableCursor tailableCursor
      # Iterate twice to force a getMore.
      - name: iterateUntilDocumentOrError
        object: *tailableCursor
      - name: iterateUntilDocumentOrError
        object: *tailableCursor
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: drop
          - commandStartedEvent:
              commandName: create
          - commandStartedEvent:
              commandName: insert
          - commandStartedEvent:
              commandName: find
              databaseName: *db
              command:
                find: *cappedCollection
                tailable: true
                awaitData: true
                maxTimeMS: { $$exists: true }
          - commandStartedEvent:
              commandName: getMore
              databaseName: *db
              command:
                getMore: { $$type: ["int", "long"] }
                collection: *cappedCollection
