description: "timeoutMS behaves correctly for tailable awaitData cursors"

schemaVersion: "1.9"

runOnRequirements:
  - minServerVersion: "4.4"
    serverless: forbid # Capped collections are not allowed for serverless.

createEntities:
  - client:
      id: &failPointClient failPointClient
      useMultipleMongoses: false
  - client:
      id: &client client
      uriOptions:
        timeoutMS: 200
      useMultipleMongoses: false
      observeEvents:
        - commandStartedEvent
  - database:
      id: &database database
      client: *client
      databaseName: &databaseName test
  - collection:
      id: &collection collection
      database: *database
      collectionName: &collectionName coll

initialData:
  - collectionName: *collectionName
    databaseName: *databaseName
    createOptions:
      capped: true
      size: 500
    documents:
      - { _id: 0 }
      - { _id: 1 }

tests:
  - description: "error if timeoutMode is cursor_lifetime"
    operations:
      - name: find
        object: *collection
        arguments:
          filter: {}
          timeoutMode: cursorLifetime
          cursorType: tailableAwait
        expectError:
          isClientError: true

  - description: "error on find if maxAwaitTimeMS is greater than timeoutMS"
    operations:
      - name: find
        object: *collection
        arguments:
          filter: {}
          cursorType: tailableAwait
          timeoutMS: 5
          maxAwaitTimeMS: 10
        expectError:
          isClientError: true
          isTimeoutError: false

  - description: "error on aggregate if maxAwaitTimeMS is greater than timeoutMS"
    operations:
      - name: aggregate
        object: *collection
        arguments:
          pipeline: []
          timeoutMS: 5
          maxAwaitTimeMS: 10
        expectError:
          isClientError: true
          isTimeoutError: false

  - description: "error on watch if maxAwaitTimeMS is greater than timeoutMS"
    operations:
      - name: createChangeStream
        object: *collection
        arguments:
          pipeline: []
          timeoutMS: 5
          maxAwaitTimeMS: 10
        expectError:
          isClientError: true
          isTimeoutError: false

  - description: "error on find if maxAwaitTimeMS is equal to timeoutMS"
    operations:
      - name: find
        object: *collection
        arguments:
          filter: {}
          cursorType: tailableAwait
          timeoutMS: 5
          maxAwaitTimeMS: 5
        expectError:
          isClientError: true
          isTimeoutError: false

  - description: "error on aggregate if maxAwaitTimeMS is equal to timeoutMS"
    operations:
      - name: aggregate
        object: *collection
        arguments:
          pipeline: []
          timeoutMS: 5
          maxAwaitTimeMS: 5
        expectError:
          isClientError: true
          isTimeoutError: false

  - description: "error on watch if maxAwaitTimeMS is equal to timeoutMS"
    operations:
      - name: createChangeStream
        object: *collection
        arguments:
          pipeline: []
          timeoutMS: 5
          maxAwaitTimeMS: 5
        expectError:
          isClientError: true
          isTimeoutError: false

  - description: "timeoutMS applied to find"
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: ["find"]
              blockConnection: true
              blockTimeMS: 300
      - name: find
        object: *collection
        arguments:
          filter: {}
          cursorType: tailableAwait
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *databaseName
              command:
                find: *collectionName
                tailable: true
                awaitData: true
                maxTimeMS: { $$exists: true }

  # If maxAwaitTimeMS is not set, timeoutMS should be refreshed for the getMore and the getMore should not have a
  # maxTimeMS field.
  - description: "timeoutMS is refreshed for getMore if maxAwaitTimeMS is not set"
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 2 }
            data:
              failCommands: ["find", "getMore"]
              blockConnection: true
              blockTimeMS: 150
      - name: createFindCursor
        object: *collection
        arguments:
          filter: {}
          cursorType: tailableAwait
          timeoutMS: 250
          batchSize: 1
        saveResultAsEntity: &tailableCursor tailableCursor
      # Iterate twice to force a getMore. The first iteration will return the document from the first batch and the
      # second will do a getMore.
      - name: iterateUntilDocumentOrError
        object: *tailableCursor
      - name: iterateUntilDocumentOrError
        object: *tailableCursor
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *databaseName
              command:
                find: *collectionName
                tailable: true
                awaitData: true
                maxTimeMS: { $$exists: true }
          - commandStartedEvent:
              commandName: getMore
              databaseName: *databaseName
              command:
                getMore: { $$type: ["int", "long"] }
                collection: *collectionName
                maxTimeMS: { $$exists: false }

  # If maxAwaitTimeMS is set for the initial command, timeoutMS should still be refreshed for the getMore and the
  # getMore command should have a maxTimeMS field.
  - description: "timeoutMS is refreshed for getMore if maxAwaitTimeMS is set"
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 2 }
            data:
              failCommands: ["find", "getMore"]
              blockConnection: true
              blockTimeMS: 150
      - name: createFindCursor
        object: *collection
        arguments:
          filter: {}
          cursorType: tailableAwait
          timeoutMS: 250
          batchSize: 1
          maxAwaitTimeMS: 1
        saveResultAsEntity: &tailableCursor tailableCursor
      # Iterate twice to force a getMore.
      - name: iterateUntilDocumentOrError
        object: *tailableCursor
      - name: iterateUntilDocumentOrError
        object: *tailableCursor
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *databaseName
              command:
                find: *collectionName
                tailable: true
                awaitData: true
                maxTimeMS: { $$exists: true }
          - commandStartedEvent:
              commandName: getMore
              databaseName: *databaseName
              command:
                getMore: { $$type: ["int", "long"] }
                collection: *collectionName
                maxTimeMS: 1

  # The timeoutMS value should be refreshed for getMore's. This is a failure test. The find inherits timeoutMS=200 from
  # the collection and the getMore blocks for 250ms, causing iteration to fail with a timeout error.
  - description: "timeoutMS is refreshed for getMore - failure"
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: ["getMore"]
              blockConnection: true
              blockTimeMS: 250
      - name: createFindCursor
        object: *collection
        arguments:
          filter: {}
          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: find
              databaseName: *databaseName
              command:
                find: *collectionName
                tailable: true
                awaitData: true
                maxTimeMS: { $$exists: true }
          - commandStartedEvent:
              commandName: getMore
              databaseName: *databaseName
              command:
                getMore: { $$type: ["int", "long"] }
                collection: *collectionName

  - description: "apply remaining timeoutMS if less than maxAwaitTimeMS"
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: ["getMore"]
              blockConnection: true
              blockTimeMS: 30
      - name: createFindCursor
        object: *collection
        arguments:
          filter: { _id:  1 }
          cursorType: tailableAwait
          batchSize: 1
          maxAwaitTimeMS: 100
          timeoutMS: 200
        saveResultAsEntity: &tailableCursor tailableCursor
      - name: iterateOnce
        object: *tailableCursor
      - name: iterateUntilDocumentOrError
        object: *tailableCursor
        expectError:
          isTimeoutError: true
    expectEvents:
      - client: *client
        ignoreExtraEvents: true
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *databaseName
          - commandStartedEvent:
              commandName: getMore
              databaseName: *databaseName
              command:
                maxTimeMS: { $$lte: 100 }
          - commandStartedEvent:
              commandName: getMore
              databaseName: *databaseName
              command:
                maxTimeMS: { $$lte: 70 }

  - description: "apply maxAwaitTimeMS if less than remaining timeout"
    operations:
      - name: createFindCursor
        object: *collection
        arguments:
          filter: {}
          cursorType: tailableAwait
          batchSize: 1
          maxAwaitTimeMS: 100
          timeoutMS: 200
        saveResultAsEntity: &tailableCursor tailableCursor
      # Iterate twice to force a getMore.
      - name: iterateOnce
        object: *tailableCursor
      - name: iterateOnce
        object: *tailableCursor
    expectEvents:
      - client: *client
        events:
          - commandStartedEvent:
              commandName: find
              databaseName: *databaseName
          - commandStartedEvent:
              commandName: getMore
              databaseName: *databaseName
              command:
                maxTimeMS: { $$lte: 100 }

