description: state change errors are correctly handled

schemaVersion: '1.4'

runOnRequirements:
  - topologies: [ load-balanced ]

_yamlAnchors:
  observedEvents: &observedEvents
    - connectionCreatedEvent
    - connectionReadyEvent
    - connectionCheckedOutEvent
    - connectionCheckOutFailedEvent
    - connectionCheckedInEvent
    - connectionClosedEvent
    - poolClearedEvent

createEntities:
  - client:
      id: &failPointClient failPointClient
      useMultipleMongoses: false
  - client:
      id: &singleClient singleClient
      useMultipleMongoses: false
      uriOptions:
        appname: &singleClientAppName lbSDAMErrorTestClient
        retryWrites: false
      observeEvents: *observedEvents
  - database:
      id: &singleDB singleDB
      client: *singleClient
      databaseName: &singleDBName singleDB
  - collection:
      id: &singleColl singleColl
      database: *singleDB
      collectionName: &singleCollName singleColl
  - client:
      id: &multiClient multiClient
      useMultipleMongoses: true
      uriOptions:
        retryWrites: false
      observeEvents: *observedEvents
  - database:
      id: &multiDB multiDB
      client: *multiClient
      databaseName: &multiDBName multiDB
  - collection:
      id: &multiColl multiColl
      database: *multiDB
      collectionName: &multiCollName multiColl

initialData:
  - collectionName: *singleCollName
    databaseName: *singleDBName
    documents:
      - _id: 1
      - _id: 2
      - _id: 3
  - collectionName: *multiCollName
    databaseName: *multiDBName
    documents:
      - _id: 1
      - _id: 2
      - _id: 3

tests:
  - description: only connections for a specific serviceId are closed when pools are cleared
    runOnRequirements:
    # This test assumes that two sequential connections receive different serviceIDs.
    # Sequential connections to a serverless instance may receive the same serviceID.
    - serverless: forbid
    operations:
      # Create two cursors to force two connections.
      - name: createFindCursor
        object: *multiColl
        arguments:
          filter: {}
          batchSize: 2
        saveResultAsEntity: &cursor0 cursor0
      - name: createFindCursor
        object: *multiColl
        arguments:
          filter: {}
          batchSize: 2
        saveResultAsEntity: &cursor1 cursor1
      # Close both cursors to return the connections to the pool.
      - name: close
        object: *cursor0
      - name: close
        object: *cursor1
      # Fail an operation with a state change error.
      - name: failPoint
        object: testRunner
        arguments:
          client: *multiClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: [insert]
              errorCode: &errorCode 11600 # InterruptedAtShutdown
      - name: insertOne
        object: *multiColl
        arguments:
          document: { x: 1 }
        expectError:
          errorCode: *errorCode
      # Do another operation to ensure the relevant connection has been closed.
      - name: insertOne
        object: *multiColl
        arguments:
          document: { x: 1 }
    expectEvents:
      - client: *multiClient
        eventType: cmap
        events:
          # Create cursors.
          - connectionCreatedEvent: {}
          - connectionReadyEvent: {}
          - connectionCheckedOutEvent: {}
          - connectionCreatedEvent: {}
          - connectionReadyEvent: {}
          - connectionCheckedOutEvent: {}
          # Close cursors.
          - connectionCheckedInEvent: {}
          - connectionCheckedInEvent: {}
          # Set failpoint.
          - connectionCheckedOutEvent: {}
          - connectionCheckedInEvent: {}
          # First insertOne.
          - connectionCheckedOutEvent: {}
          - poolClearedEvent: {}
          - connectionCheckedInEvent: {}
          - connectionClosedEvent:
              reason: stale
          # Second insertOne.
          - connectionCheckedOutEvent: {}
          - connectionCheckedInEvent: {}

  # This test uses singleClient to ensure that connection attempts are routed
  # to the same mongos on which the failpoint is set.
  - description: errors during the initial connection hello are ignored
    runOnRequirements:
      # Server version 4.9+ is needed to set a fail point on the initial
      # connection handshake with the appName filter due to SERVER-49336.
      - minServerVersion: '4.9'
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: [isMaster, hello]
              closeConnection: true
              appName: *singleClientAppName
      - name: insertOne
        object: *singleColl
        arguments:
          document: { x: 1 }
        expectError:
          isClientError: true
    expectEvents:
      - client: *singleClient
        eventType: cmap
        events:
          - connectionCreatedEvent: {}
          - connectionClosedEvent:
              reason: error
          - connectionCheckOutFailedEvent:
              reason: connectionError

  - description: errors during authentication are processed
    runOnRequirements:
      - auth: true
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 1 }
            data:
              failCommands: [saslContinue]
              closeConnection: true
              appName: *singleClientAppName
      - name: insertOne
        object: *singleColl
        arguments:
          document: { x: 1 }
        expectError:
          isClientError: true
    expectEvents:
      - client: *singleClient
        eventType: cmap
        events:
          - connectionCreatedEvent: {}
          - poolClearedEvent: {}
          - connectionClosedEvent:
              reason: error
          - connectionCheckOutFailedEvent:
              reason: connectionError

  - description: stale errors are ignored
    operations:
      - name: failPoint
        object: testRunner
        arguments:
          client: *failPointClient
          failPoint:
            configureFailPoint: failCommand
            mode: { times: 2 }
            data:
              failCommands: [getMore]
              closeConnection: true
      # Force two connections to be checked out from the pool.
      - name: createFindCursor
        object: *singleColl
        arguments:
          filter: {}
          batchSize: 2
        saveResultAsEntity: &cursor0 cursor0
      - name: createFindCursor
        object: *singleColl
        arguments:
          filter: {}
          batchSize: 2
        saveResultAsEntity: &cursor1 cursor1
      # Iterate cursor0 three times to force a network error.
      - name: iterateUntilDocumentOrError
        object: *cursor0
      - name: iterateUntilDocumentOrError
        object: *cursor0
      - name: iterateUntilDocumentOrError
        object: *cursor0
        expectError:
          isClientError: true
      - name: close
        object: *cursor0
      # Iterate cursor1 three times to force a network error.
      - name: iterateUntilDocumentOrError
        object: *cursor1
      - name: iterateUntilDocumentOrError
        object: *cursor1
      - name: iterateUntilDocumentOrError
        object: *cursor1
        expectError:
          isClientError: true
      - name: close
        object: *cursor1
    expectEvents:
      - client: *singleClient
        eventType: cmap
        events:
          # Events for creating both cursors.
          - connectionCreatedEvent: {}
          - connectionReadyEvent: {}
          - connectionCheckedOutEvent: {}
          - connectionCreatedEvent: {}
          - connectionReadyEvent: {}
          - connectionCheckedOutEvent: {}
          # Events for iterating and closing the first cursor.  The failed
          # getMore should cause a poolClearedEvent to be published.
          - poolClearedEvent: {}
          - connectionCheckedInEvent: {}
          - connectionClosedEvent: {}
          # Events for iterating and closing the second cursor. The failed
          # getMore should not clear the pool because the connection's
          # generation number is stale.
          - connectionCheckedInEvent: {}
          - connectionClosedEvent: {}
