/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

@testable import FBSDKCoreKit

import TestTools
import XCTest

final class AppLinkResolverTests: XCTestCase {
  enum Keys {
    static let appLinkURL = URL(string: "http://example.com/1234567890")! // swiftlint:disable:this force_unwrapping
    static let appLinkURL2 = URL(string: "http://example.com/0987654321")! // swiftlint:disable:this force_unwrapping
    static let appLinks = "app_links"
    static let iPhone = "iphone"
    static let iPad = "ipad"
    static let ios = "ios"
    static let android = "android"
    static let id = "id"
    static let appName = "app_name"
    static let appStoreId = "app_store_id"
    static let package = "package"
    static let url = "url"
    static let web = "web"
    static let shouldFallback = "should_fallback"
  }

  enum Values {
    static let appName = "Example"
    static let appStoreId = "123"
    static let appStoreId2 = "456"
    static let url = "example://things/1234567890"
    static let fallbackURL = "http://www.example.com/somethingelse"
    static let package = "com.example.app"
  }

  let linkValues1 = [
    Keys.appName: Values.appName,
    Keys.appStoreId: Values.appStoreId,
    Keys.url: Values.url,
  ]
  let linkValues2 = [
    Keys.appName: Values.appName,
    Keys.appStoreId: Values.appStoreId2,
    Keys.url: Values.url,
  ]
  let androidLinkValues = [
    Keys.appName: Values.appName,
    Keys.package: Values.package,
    Keys.url: Values.url,
  ]

  let queryParameters = [String: String]()

  var accessTokenProvider = TestAccessTokenWallet.self
  var resolved: AppLink?
  var error: Error?

  // swiftlint:disable implicitly_unwrapped_optional
  var resolver: AppLinkResolver!
  var builder: TestAppLinkResolverRequestBuilder!
  var clientTokenProvider: TestClientTokenProvider!
  var graphRequest: TestGraphRequest!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    builder = TestAppLinkResolverRequestBuilder()
    clientTokenProvider = TestClientTokenProvider(clientToken: "clienttoken")
    graphRequest = TestGraphRequest()
    builder.stubbedGraphRequest = graphRequest
    resolver = AppLinkResolver()

    AppLinkResolver.setDependencies(
      .init(
        requestBuilder: builder,
        clientTokenProvider: clientTokenProvider,
        accessTokenProvider: accessTokenProvider
      )
    )
  }

  override func tearDown() {
    resolver = nil
    builder = nil
    clientTokenProvider = nil
    graphRequest = nil

    TestAccessTokenWallet.reset()
    AppLinkResolver.resetDependencies()

    super.tearDown()
  }

  func testResolvingWithPhoneIdiom() {
    builder.stubbedIdiomSpecificField = Keys.iPhone

    let result = [
      Keys.appLinkURL.absoluteString: [
        Keys.appLinks: [
          Keys.iPhone: [linkValues2],
          Keys.ios: [linkValues1],
        ],
        Keys.id: Keys.appLinkURL.absoluteString,
      ],
    ]

    resolver.appLink(from: Keys.appLinkURL) {
      self.resolved = $0
      self.error = $1
    }

    graphRequest.capturedCompletionHandler?(nil, result, nil)

    XCTAssertNil(error)

    guard let link = resolved else {
      return XCTFail("Must have a resolved app link")
    }
    XCTAssertEqual(link.sourceURL, Keys.appLinkURL)
    XCTAssertEqual(link.targets[0].appStoreId, Values.appStoreId2)
    XCTAssertEqual(link.targets[1].appStoreId, Values.appStoreId)
  }

  func testResolvingWithPadIdiom() {
    builder.stubbedIdiomSpecificField = Keys.iPad

    let result = [
      Keys.appLinkURL.absoluteString: [
        Keys.appLinks: [
          Keys.iPad: [linkValues2],
          Keys.ios: [linkValues1],
        ],
        Keys.id: Keys.appLinkURL.absoluteString,
      ],
    ]

    resolver.appLink(from: Keys.appLinkURL) {
      self.resolved = $0
      self.error = $1
    }

    graphRequest.capturedCompletionHandler?(nil, result, nil)

    XCTAssertNil(error)

    guard let link = resolved else {
      return XCTFail("Must have a resolved app link")
    }
    XCTAssertEqual(link.sourceURL, Keys.appLinkURL)
    XCTAssertEqual(link.targets[0].appStoreId, Values.appStoreId2)
    XCTAssertEqual(link.targets[1].appStoreId, Values.appStoreId)
  }

  func testResolvingIgnoresAndroidLinks() {
    builder.stubbedIdiomSpecificField = Keys.iPad

    let result = [
      Keys.appLinkURL.absoluteString: [
        Keys.appLinks: [
          Keys.android: [androidLinkValues],
          Keys.ios: [linkValues1],
        ],
        Keys.id: Keys.appLinkURL.absoluteString,
      ],
    ]

    resolver.appLink(from: Keys.appLinkURL) {
      self.resolved = $0
      self.error = $1
    }

    graphRequest.capturedCompletionHandler?(nil, result, nil)

    XCTAssertNil(error)
    XCTAssertEqual(resolved?.targets[0].appStoreId, Values.appStoreId)
  }

  func testResolvingWithoutTargets() {
    builder.stubbedIdiomSpecificField = Keys.iPad

    let result = [
      Keys.appLinkURL.absoluteString: [
        Keys.id: Keys.appLinkURL.absoluteString,
      ],
    ]

    resolver.appLink(from: Keys.appLinkURL) {
      self.resolved = $0
      self.error = $1
    }

    graphRequest.capturedCompletionHandler?(nil, result, nil)

    XCTAssertNil(error)

    guard let link = resolved else {
      return XCTFail("Must have a resolved app link")
    }
    XCTAssertEqual(link.sourceURL, Keys.appLinkURL)
    XCTAssertTrue(link.targets.isEmpty)
  }

  func testResolvingWithMultipleURLs() {
    builder.stubbedIdiomSpecificField = Keys.iPad

    let result = [
      Keys.appLinkURL.absoluteString: [
        Keys.id: Keys.appLinkURL.absoluteString,
      ],
      Keys.appLinkURL2.absoluteString: [
        Keys.id: Keys.appLinkURL2.absoluteString,
      ],
    ]

    var resolved: [URL: AppLink]?
    var error: Error?
    resolver.appLinks(from: [Keys.appLinkURL, Keys.appLinkURL2]) {
      resolved = $0
      error = $1
    }

    graphRequest.capturedCompletionHandler?(nil, result, nil)

    XCTAssertNil(error)

    guard let links = resolved else {
      return XCTFail("Must have a resolved app link")
    }
    XCTAssertEqual(links.count, 2)
    XCTAssertEqual(links[Keys.appLinkURL]?.sourceURL, Keys.appLinkURL)
    XCTAssertEqual(links[Keys.appLinkURL2]?.sourceURL, Keys.appLinkURL2)
  }

  func testResolvingSetsFallbackIfNotSpecified() {
    builder.stubbedIdiomSpecificField = Keys.iPad

    let result = [
      Keys.appLinkURL.absoluteString: [
        Keys.id: Keys.appLinkURL.absoluteString,
      ],
    ]

    resolver.appLink(from: Keys.appLinkURL) {
      self.resolved = $0
      self.error = $1
    }

    graphRequest.capturedCompletionHandler?(nil, result, nil)

    XCTAssertNil(error)

    guard let link = resolved else {
      return XCTFail("Must have a resolved app link")
    }
    XCTAssertEqual(link.webURL, Keys.appLinkURL)
  }

  func testResolvingSetsFallbackIfSpecified() throws {
    builder.stubbedIdiomSpecificField = Keys.iPad

    let result = [
      Keys.appLinkURL.absoluteString: [
        Keys.appLinks: [
          Keys.web: [
            Keys.url: Values.fallbackURL,
            Keys.shouldFallback: true,
          ],
        ],
        Keys.id: Keys.appLinkURL.absoluteString,
      ],
    ]

    resolver.appLink(from: Keys.appLinkURL) {
      self.resolved = $0
      self.error = $1
    }

    graphRequest.capturedCompletionHandler?(nil, result, nil)

    XCTAssertNil(error)

    XCTAssertEqual(resolved?.webURL?.absoluteString, Values.fallbackURL)
  }

  func testResolvingUsesSourceAsFallbackIfSpecified() throws {
    builder.stubbedIdiomSpecificField = Keys.iPad

    let result = [
      Keys.appLinkURL.absoluteString: [
        Keys.appLinks: [
          Keys.web: [
            Keys.shouldFallback: true,
          ],
        ],
        Keys.id: Keys.appLinkURL.absoluteString,
      ],
    ]

    resolver.appLink(from: Keys.appLinkURL) {
      self.resolved = $0
      self.error = $1
    }

    graphRequest.capturedCompletionHandler?(nil, result, nil)

    XCTAssertNil(error)

    XCTAssertEqual(resolved?.webURL, Keys.appLinkURL)
  }

  func testResolvingSetsNoFallbackIfSpecified() throws {
    builder.stubbedIdiomSpecificField = Keys.iPad

    let result = [
      Keys.appLinkURL.absoluteString: [
        Keys.appLinks: [
          Keys.web: [
            Keys.url: Values.fallbackURL,
            Keys.shouldFallback: false,
          ],
        ],
        Keys.id: Keys.appLinkURL.absoluteString,
      ],
    ]

    resolver.appLink(from: Keys.appLinkURL) {
      self.resolved = $0
      self.error = $1
    }

    graphRequest.capturedCompletionHandler?(nil, result, nil)

    XCTAssertNil(error)
    XCTAssertNil(resolved?.webURL)
  }

  func testResolvingWithError() {

    resolver.appLink(from: Keys.appLinkURL) {
      self.resolved = $0
      self.error = $1
    }

    graphRequest.capturedCompletionHandler?(nil, nil, SampleError())

    XCTAssertNotNil(error)
    XCTAssertNil(resolved)
  }

  func testReadsFromCache() {
    let link = AppLink(sourceURL: Keys.appLinkURL, targets: [], webURL: nil)
    resolver.cachedAppLinks = [Keys.appLinkURL: link]

    resolver.appLink(from: Keys.appLinkURL) {
      self.resolved = $0
      self.error = $1
    }

    XCTAssertNil(graphRequest.capturedCompletionHandler)
    XCTAssertNil(error)
    XCTAssertEqual(resolved, link)
  }

  func testSetsCache() throws {
    let result = [
      Keys.appLinkURL.absoluteString: [
        Keys.id: Keys.appLinkURL.absoluteString,
      ],
    ]

    resolver.appLink(from: Keys.appLinkURL) { _, _ in }

    graphRequest.capturedCompletionHandler?(nil, result, nil)

    XCTAssertNotNil(resolver.cachedAppLinks[Keys.appLinkURL])
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

@testable import FBSDKCoreKit

import TestTools
import XCTest

final class AppLinkURLFactoryTests: XCTestCase {

  func testCreatingAppLinkURL() {
    let factory = AppLinkURLFactory()
    XCTAssertTrue(
      factory.createAppLinkURL(with: SampleURLs.valid) is AppLinkURL,
      "Should create an app link url of the expected concrete type"
    )
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

@testable import FBSDKCoreKit

import TestTools
import XCTest

final class WebViewAppLinkResolverTests: XCTestCase {

  var result: [String: Any]?
  var error: Error?
  let data = "foo".data(using: .utf8)! // swiftlint:disable:this force_unwrapping
  let sessionProvider = TestSessionProvider()
  let errorFactory = TestErrorFactory()
  lazy var resolver = WebViewAppLinkResolver(
    sessionProvider: sessionProvider,
    errorFactory: errorFactory
  )

  // MARK: - Dependencies

  func testDefaultDependencies() throws {
    resolver = WebViewAppLinkResolver.shared
    XCTAssertTrue(
      resolver.sessionProvider === URLSession.shared,
      "Should use the shared system session by default"
    )

    let reporter = try _ErrorFactory.getDependencies().reporter
    XCTAssertTrue(
      reporter === ErrorReporter.shared,
      "Should use the shared error reporter by default"
    )
  }

  func testConfiguringDependencies() {
    XCTAssertTrue(
      resolver.sessionProvider === sessionProvider,
      "Should be able to create with a session provider"
    )
    XCTAssertTrue(
      resolver.errorFactory === errorFactory,
      "Should be able to create with an error factory"
    )
  }

  // MARK: - Redirecting

  func testFollowRedirectsURL() {
    let task = TestSessionDataTask()
    sessionProvider.stubbedDataTask = task
    resolver.followRedirects(SampleURLs.valid) { _, _ in }

    XCTAssertEqual(
      sessionProvider.capturedRequest?.url,
      SampleURLs.valid,
      "Should create a url request with the provided url"
    )
    XCTAssertEqual(
      sessionProvider.capturedRequest?.allHTTPHeaderFields?.contains { key, value in
        key == "Prefer-Html-Meta-Tags" && value == "al"
      },
      true,
      "Should include a header for which html meta tags to prefer"
    )
    XCTAssertEqual(
      task.resumeCallCount,
      1,
      "Should start the data task to follow redirects"
    )
  }

  func testFollowRedirectsWithErrorOnly() {
    resolver.followRedirects(SampleURLs.valid) { potentialResult, potentialError in
      self.result = potentialResult
      self.error = potentialError
    }

    sessionProvider.capturedCompletion?(nil, nil, SampleError())

    XCTAssertNil(
      result,
      "Should not call the redirect completion with a result if there is also an error"
    )
    XCTAssertTrue(
      error is SampleError,
      "Should call the completion with the error from the redirect"
    )
  }

  func testFollowRedirectWithHTTPResponseOnly() throws {
    resolver.followRedirects(SampleURLs.valid) { potentialResult, potentialError in
      self.result = potentialResult
      self.error = potentialError
    }

    sessionProvider.capturedCompletion?(
      nil,
      SampleHTTPURLResponses.validStatusCode,
      nil
    )

    XCTAssertNil(
      result,
      "Should not have a result if there is no response data"
    )

    let assertionMessage = "Should call the completion with an error indicating the missing data"
    let testError = try XCTUnwrap(error as? TestSDKError, assertionMessage)
    XCTAssertEqual(testError.type, .unknown, assertionMessage)
    XCTAssertEqual(
      testError.message,
      "Invalid network response - missing data",
      assertionMessage
    )
  }

  func testFollowRedirectsWithValidHTTPResponse() {
    resolver.followRedirects(SampleURLs.valid) { potentialResult, potentialError in
      self.result = potentialResult
      self.error = potentialError
    }

    sessionProvider.capturedCompletion?(
      data,
      SampleHTTPURLResponses.validStatusCode,
      nil
    )

    validateResult(
      result: result,
      data: data,
      response: SampleHTTPURLResponses.validStatusCode,
      error: error
    )
  }

  func testFollowRedirectsWithRedirectingHTTPResponseMissingLocationURL() {
    // Just testing the upper and lower bounds
    [300, 399].forEach { code in
      sessionProvider.dataTaskCallCount = 0
      resolver.followRedirects(SampleURLs.valid) { potentialResult, potentialError in
        self.result = potentialResult
        self.error = potentialError
      }

      sessionProvider.capturedCompletion?(
        data,
        SampleHTTPURLResponses.valid(statusCode: code),
        nil
      )

      XCTAssertEqual(
        sessionProvider.dataTaskCallCount,
        2,
        "Should create a second data task for the url redirect"
      )
    }
  }

  func testFollowRedirectsWithRedirectingHTTPResponseIncludingLocationURL() {
    let redirectURL = SampleURLs.valid(path: "redirected")
    resolver.followRedirects(SampleURLs.valid) { potentialResult, potentialError in
      self.result = potentialResult
      self.error = potentialError
    }

    sessionProvider.capturedCompletion?(
      data,
      SampleHTTPURLResponses.valid(
        statusCode: 300,
        headerFields: ["Location": redirectURL.absoluteString]
      ),
      nil
    )

    XCTAssertEqual(
      sessionProvider.dataTaskCallCount,
      2,
      "Should create a second data task for the url redirect"
    )
    XCTAssertEqual(
      sessionProvider.capturedRequest?.url,
      redirectURL,
      "The second request should be to the redirect url"
    )
  }

  // MARK: - Building Applink from AppLinkData

  func testBuildingLinkFromEmptyData() {
    let link = resolver.appLink(
      fromALData: [:],
      destination: SampleURLs.valid
    )
    XCTAssertEqual(
      link.sourceURL,
      SampleURLs.valid,
      "Should use the destination as the source url for the app link"
    )
    XCTAssertEqual(
      link.webURL,
      SampleURLs.valid,
      "The web url should default to the destination"
    )
    XCTAssertTrue(link.targets.isEmpty, "Should not have any targets by default")
  }

  func testBuildingLinkWithInvalidAppLinkData() {
    let link = resolver.appLink(
      fromALData: SampleAppLinkResolverData.invalid,
      destination: SampleURLs.valid
    )
    XCTAssertEqual(
      link.sourceURL,
      SampleURLs.valid,
      "Should use the destination as the source url for the app link"
    )
    XCTAssertEqual(
      link.webURL,
      SampleURLs.valid,
      "The web url should default to the destination"
    )
    XCTAssertEqual(
      link.targets.count,
      2,
      "Should create a target for each platform link"
    )
    guard let target = link.targets.first else {
      return XCTFail("Should create targets")
    }
    XCTAssertNil(
      target.url,
      "Should create a target even if the url is invalid"
    )
    XCTAssertTrue(
      target.appName.isEmpty,
      "Should be able to create a target without an app name"
    )
    XCTAssertNil(
      target.appStoreId,
      "Should create a target even if the app store ID is invalid"
    )
  }

  func testBuildingLinkWithShouldNotFallbackToWebURL() {
    ["no", "false", "0"].forEach { fallbackValue in
      let link = resolver.appLink(
        fromALData: SampleAppLinkResolverData.withShouldFallback(fallbackValue),
        destination: SampleURLs.valid
      )
      XCTAssertEqual(
        link.sourceURL,
        SampleURLs.valid,
        "Should use the destination as the source url for the app link"
      )
      XCTAssertNil(
        link.webURL,
        "The web url should be nil when the fallback is falsy"
      )
      XCTAssertTrue(
        link.targets.isEmpty,
        "Should not create targets for either platform"
      )
    }
  }

  func testBuildingLinkWithShouldFallbackToWebURL() {
    ["yes", "true", "1"].forEach { fallbackValue in
      let link = resolver.appLink(
        fromALData: SampleAppLinkResolverData.withShouldFallback(fallbackValue),
        destination: SampleURLs.valid
      )
      XCTAssertEqual(
        link.sourceURL,
        SampleURLs.valid,
        "Should use the destination as the source url for the app link"
      )
      XCTAssertEqual(
        link.webURL?.absoluteString,
        SampleAppLinkResolverData.urlString,
        "The web url should be used when the fallback is truthy"
      )
      XCTAssertTrue(
        link.targets.isEmpty,
        "Should not create targets for either platform"
      )
    }
  }

  // MARK: - Helpers

  func validateResult(
    result: [String: Any]?,
    data: Data,
    response: HTTPURLResponse,
    error: Error?,
    file: StaticString = #file,
    line: UInt = #line
  ) {
    XCTAssertEqual(
      result?["response"] as? HTTPURLResponse,
      response,
      "Should include the http response in the result",
      file: file,
      line: line
    )
    XCTAssertEqual(
      result?["data"] as? Data,
      data,
      "Should include the data in the result",
      file: file,
      line: line
    )
    XCTAssertNil(
      error,
      "Should not call the completion with an error and a result",
      file: file,
      line: line
    )
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

@testable import FBSDKCoreKit

import XCTest

final class Base64Tests: XCTestCase {

  func runTests(_ testsDict: [String: String]) {
    for (plainString, base64String) in testsDict {
      XCTAssertEqual(Base64.encode(plainString), base64String)
      XCTAssertEqual(Base64.decode(as: base64String), plainString)
    }
  }

  func testRFC4648TestVectors() {
    let testsDict = [
      "": "",
      "f": "Zg==",
      "fo": "Zm8=",
      "foo": "Zm9v",
      "foob": "Zm9vYg==",
      "fooba": "Zm9vYmE=",
      "foobar": "Zm9vYmFy",
    ]
    runTests(testsDict)
  }

  func testDecodeVariations() {
    XCTAssertEqual(Base64.decode(as: "aGVsbG8gd29ybGQh"), "hello world!")
    XCTAssertEqual(Base64.decode(as: "a  GVs\tb\r\nG8gd2\n9y\rbGQ h"), "hello world!")
    XCTAssertEqual(Base64.decode(as: "aGVsbG8gd29ybGQh"), "hello world!")
    XCTAssertEqual(Base64.decode(as: "aGVs#bG8*gd^29yb$GQh"), "hello world!")
  }

  func testEncodeDecode() {
    let testsDict = [
      "Hello World": "SGVsbG8gV29ybGQ=",
      "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!%^&*(){}[]": "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY3ODkwISVeJiooKXt9W10=", // swiftlint:disable:this line_length
      "\n\t Line with control characters\r\n": "CgkgTGluZSB3aXRoIGNvbnRyb2wgY2hhcmFjdGVycw0K",
    ]
    runTests(testsDict)
  }

  func testBase64URLEncode() {
    let urlString = "https://www.example.com/some-path-with-dashes-in-it/"
    let encodedString = Base64.base64(fromBase64Url: urlString)
    XCTAssertEqual(encodedString, "https://www.example.com/some+path+with+dashes+in+it/")
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

@testable import FBSDKCoreKit

// swiftlint:disable:next swiftlint_disable_without_this_or_next
// swiftlint:disable line_length
@available(iOS 13.0, *)
extension BridgeAPITests {

  // MARK: - URL Opening

  func testOpenUrlShouldStopPropagationWithPendingURL() {
    let urlOpener = FBSDKLoginManager()
    urlOpener.stubShouldStopPropagationOfURL(sampleURL, withValue: true)
    urlOpener.stubbedCanOpenURL = true

    api.pendingURLOpener = urlOpener

    XCTAssertTrue(
      api.application(
        UIApplication.shared,
        open: sampleURL,
        sourceApplication: sampleSource,
        annotation: sampleAnnotation
      ),
      "Should early exit when the opener stops propagation"
    )

    XCTAssertNil(
      urlOpener.capturedCanOpenURL,
      "Should not check if a url can be opened when exiting early"
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithoutSafariVcWhileDismissingSafariVcWithoutAuthSessionCompletionHandlerAbleToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      hasSafariViewController: false,
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: true,
      expectedAuthCancelCallCount: 1,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithoutSafariVcWhileDismissingSafariVcWithoutAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      hasSafariViewController: false,
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: false,
      expectedAuthCancelCallCount: 1,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithoutSafariVcWhileDismissingSafariVcWithAuthSessionCompletionHandlerAbleToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      hasSafariViewController: false,
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: true,
      expectedAuthCancelCallCount: 1,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithoutSafariVcWhileDismissingSafariVcWithAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      hasSafariViewController: false,
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: true,
      canHandleBridgeApiResponse: false,
      expectedAuthCancelCallCount: 1,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: true
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithoutSafariVcWhilePresentingSafariVcWithoutAuthSessionCompletionHandlerAbleToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      hasSafariViewController: false,
      isDismissingSafariViewController: false,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: true,
      expectedAuthCancelCallCount: 1,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithoutSafariVcWhilePresentingSafariVcWithoutAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      hasSafariViewController: false,
      isDismissingSafariViewController: false,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: false,
      expectedAuthCancelCallCount: 1,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithoutSafariVcWhilePresentingSafariVcWithAuthSessionCompletionHandlerAbleToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      hasSafariViewController: false,
      isDismissingSafariViewController: false,
      authSessionCompletionHandlerExists: true,
      canHandleBridgeApiResponse: true,
      expectedAuthCancelCallCount: 1,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: true
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithoutSafariVcWhilePresentingSafariVcWithAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      hasSafariViewController: false,
      isDismissingSafariViewController: false,
      authSessionCompletionHandlerExists: true,
      canHandleBridgeApiResponse: false,
      expectedAuthCancelCallCount: 1,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: true
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithSafariVcWhileDismissingSafariVcWithoutAuthSessionCompletionHandlerAbleToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: true,
      expectedIsDismissingSafariVc: true,
      expectedAuthSessionCompletionExists: false
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithSafariVcWhileDismissingSafariVcWithoutAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: false,
      expectedIsDismissingSafariVc: true,
      expectedAuthSessionCompletionExists: false
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithSafariVcWhileDismissingSafariVcWithAuthSessionCompletionHandlerAbleToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: true,
      canHandleBridgeApiResponse: true,
      expectedIsDismissingSafariVc: true,
      expectedAuthSessionCompletionExists: true
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithSafariVcWhileDismissingSafariVcWithAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: true,
      canHandleBridgeApiResponse: false,
      expectedIsDismissingSafariVc: true,
      expectedAuthSessionCompletionExists: true
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithSafariVcWhilePresentingSafariVcWithoutAuthSessionCompletionHandlerAbleToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      isDismissingSafariViewController: false,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: true,
      expectedIsDismissingSafariVc: true,
      expectedAuthSessionCompletionExists: false
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithSafariVcWhilePresentingSafariVcWithoutAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      isDismissingSafariViewController: false,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: false,
      expectedIsDismissingSafariVc: true,
      expectedAuthSessionCompletionExists: false
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithSafariVcWhilePresentingSafariVcWithAuthSessionCompletionHandlerAbleToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      isDismissingSafariViewController: false,
      authSessionCompletionHandlerExists: true,
      canHandleBridgeApiResponse: true,
      expectedIsDismissingSafariVc: true,
      expectedAuthSessionCompletionExists: true
    )
  }

  func testOpenUrl_PendingUrlCanOpenUrlWithSafariVcWhilePresentingSafariVcWithAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpen(
      pendingUrlCanOpenUrl: true,
      isDismissingSafariViewController: false,
      authSessionCompletionHandlerExists: true,
      canHandleBridgeApiResponse: false,
      expectedIsDismissingSafariVc: true,
      expectedAuthSessionCompletionExists: true
    )
  }

  func testOpenUrl_PendingUrlCannotOpenUrlWithoutSafariVcWhileDismissingSafariVcWithoutAuthSessionCompletionHandlerAbleToHandleBridgeApiResponse() {
    verifyOpenURLWithoutSafariVc(
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: true,
      canHandleBridgeApiResponse: false,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false,
      expectedReturnValue: false
    )
  }

  func testOpenUrl_PendingUrlCannotOpenUrlWithoutSafariVcWhileDismissingSafariVcWithoutAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpenURLWithoutSafariVc(
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: false,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false,
      expectedReturnValue: false
    )
  }

  func testOpenUrl_PendingUrlCannotOpenUrlWithoutSafariVcWhileDismissingSafariVcWithAuthSessionCompletionHandlerAbleToHandleBridgeApiResponse() {
    verifyOpenURLWithoutSafariVc(
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: true,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false,
      expectedReturnValue: true
    )
  }

  func testOpenUrl_PendingUrlCannotOpenUrlWithoutSafariVcWhileDismissingSafariVcWithAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpenURLWithoutSafariVc(
      isDismissingSafariViewController: true,
      authSessionCompletionHandlerExists: true,
      canHandleBridgeApiResponse: false,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false,
      expectedReturnValue: false
    )
  }

  func testOpenUrl_PendingUrlCannotOpenUrlWithoutSafariVcWhilePresentingSafariVcWithoutAuthSessionCompletionHandlerAbleToHandleBridgeApiResponse() {
    verifyOpenURLWithoutSafariVc(
      isDismissingSafariViewController: false,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: true,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false,
      expectedReturnValue: true
    )
  }

  func testOpenUrl_PendingUrlCannotOpenUrlWithoutSafariVcWhilePresentingSafariVcWithoutAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpenURLWithoutSafariVc(
      isDismissingSafariViewController: false,
      authSessionCompletionHandlerExists: false,
      canHandleBridgeApiResponse: false,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false,
      expectedReturnValue: false
    )
  }

  func testOpenUrl_PendingUrlCannotOpenUrlWithoutSafariVcWhilePresentingSafariVcWithAuthSessionCompletionHandlerUnableToHandleBridgeApiResponse() {
    verifyOpenURLWithoutSafariVc(
      isDismissingSafariViewController: false,
      authSessionCompletionHandlerExists: true,
      canHandleBridgeApiResponse: false,
      expectedIsDismissingSafariVc: false,
      expectedAuthSessionCompletionExists: false,
      expectedReturnValue: false
    )
  }

  // MARK: - Helpers

  /// Assumes should not stop propagation of url
  func verifyOpen( // swiftlint:disable:this function_parameter_count
    pendingUrlCanOpenUrl: Bool,
    hasSafariViewController: Bool,
    isDismissingSafariViewController: Bool,
    authSessionCompletionHandlerExists: Bool,
    canHandleBridgeApiResponse: Bool,
    expectedAuthCancelCallCount: Int,
    expectedIsDismissingSafariVc: Bool,
    expectedAuthSessionCompletionExists: Bool,
    file: StaticString = #file,
    line: UInt = #line
  ) {
    verifyOpen(
      pendingUrlCanOpenUrl: pendingUrlCanOpenUrl,
      hasSafariViewController: hasSafariViewController,
      isDismissingSafariViewController: isDismissingSafariViewController,
      authSessionCompletionHandlerExists: authSessionCompletionHandlerExists,
      canHandleBridgeApiResponse: canHandleBridgeApiResponse,
      expectedAuthSessionCompletionHandlerUrl: nil,
      expectedAuthSessionCompletionHandlerError: nil,
      expectAuthSessionCompletionHandlerInvoked: false,
      expectedAuthCancelCallCount: expectedAuthCancelCallCount,
      expectedAuthSessionExists: false,
      expectedAuthSessionCompletionExists: expectedAuthSessionCompletionExists,
      expectedCanOpenUrlCalledWithUrl: createURL(canHandleBridgeApiResponse: canHandleBridgeApiResponse),
      expectedCanOpenUrlSource: sampleSource,
      expectedCanOpenUrlAnnotation: sampleAnnotation,
      expectedOpenUrlUrl: createURL(canHandleBridgeApiResponse: canHandleBridgeApiResponse),
      expectedOpenUrlSource: sampleSource,
      expectedOpenUrlAnnotation: sampleAnnotation,
      expectedPendingUrlOpenExists: false,
      expectedIsDismissingSafariVc: expectedIsDismissingSafariVc,
      expectedSafariVcExists: hasSafariViewController,
      expectedReturnValue: true,
      file: file,
      line: line
    )
  }

  /// Assumes should not stop propagation of url
  /// Assumes SafariViewController is not nil
  func verifyOpen( // swiftlint:disable:this function_parameter_count
    pendingUrlCanOpenUrl: Bool,
    isDismissingSafariViewController: Bool,
    authSessionCompletionHandlerExists: Bool,
    canHandleBridgeApiResponse: Bool,
    expectedIsDismissingSafariVc: Bool,
    expectedAuthSessionCompletionExists: Bool,
    file: StaticString = #file,
    line: UInt = #line
  ) {
    verifyOpen(
      pendingUrlCanOpenUrl: pendingUrlCanOpenUrl,
      hasSafariViewController: true,
      isDismissingSafariViewController: isDismissingSafariViewController,
      authSessionCompletionHandlerExists: authSessionCompletionHandlerExists,
      canHandleBridgeApiResponse: canHandleBridgeApiResponse,
      expectedAuthSessionCompletionHandlerUrl: nil,
      expectedAuthSessionCompletionHandlerError: nil,
      expectAuthSessionCompletionHandlerInvoked: false,
      expectedAuthCancelCallCount: 0,
      expectedAuthSessionExists: true,
      expectedAuthSessionCompletionExists: expectedAuthSessionCompletionExists,
      expectedCanOpenUrlCalledWithUrl: createURL(canHandleBridgeApiResponse: canHandleBridgeApiResponse),
      expectedCanOpenUrlSource: sampleSource,
      expectedCanOpenUrlAnnotation: sampleAnnotation,
      expectedOpenUrlUrl: nil,
      expectedOpenUrlSource: nil,
      expectedOpenUrlAnnotation: nil,
      expectedPendingUrlOpenExists: true,
      expectedIsDismissingSafariVc: expectedIsDismissingSafariVc,
      expectedSafariVcExists: false,
      expectedReturnValue: true,
      file: file,
      line: line
    )
  }

  /// Assumes should not stop propagation of url
  /// Assumes Pending Url cannot open
  /// Assumes SafariViewController is nil
  func verifyOpenURLWithoutSafariVc( // swiftlint:disable:this function_parameter_count
    isDismissingSafariViewController: Bool,
    authSessionCompletionHandlerExists: Bool,
    canHandleBridgeApiResponse: Bool,
    expectedIsDismissingSafariVc: Bool,
    expectedAuthSessionCompletionExists: Bool,
    expectedReturnValue: Bool,
    file: StaticString = #file,
    line: UInt = #line
  ) {
    verifyOpen(
      pendingUrlCanOpenUrl: false,
      hasSafariViewController: false,
      isDismissingSafariViewController: isDismissingSafariViewController,
      authSessionCompletionHandlerExists: authSessionCompletionHandlerExists,
      canHandleBridgeApiResponse: canHandleBridgeApiResponse,
      expectedAuthSessionCompletionHandlerUrl: createURL(canHandleBridgeApiResponse: canHandleBridgeApiResponse),
      expectedAuthSessionCompletionHandlerError: makeLoginInterruptionError(canHandleBridgeApiResponse: canHandleBridgeApiResponse),
      expectAuthSessionCompletionHandlerInvoked: true,
      expectedAuthCancelCallCount: 1,
      expectedAuthSessionExists: false,
      expectedAuthSessionCompletionExists: expectedAuthSessionCompletionExists,
      expectedCanOpenUrlCalledWithUrl: createURL(canHandleBridgeApiResponse: canHandleBridgeApiResponse),
      expectedCanOpenUrlSource: sampleSource,
      expectedCanOpenUrlAnnotation: sampleAnnotation,
      expectedOpenUrlUrl: createURL(canHandleBridgeApiResponse: canHandleBridgeApiResponse),
      expectedOpenUrlSource: sampleSource,
      expectedOpenUrlAnnotation: sampleAnnotation,
      expectedPendingUrlOpenExists: false,
      expectedIsDismissingSafariVc: expectedIsDismissingSafariVc,
      expectedSafariVcExists: false,
      expectedReturnValue: expectedReturnValue,
      file: file,
      line: line
    )
  }

  // swiftlint:disable:next function_parameter_count
  func verifyOpen(
    pendingUrlCanOpenUrl: Bool,
    hasSafariViewController: Bool,
    isDismissingSafariViewController: Bool,
    authSessionCompletionHandlerExists: Bool,
    canHandleBridgeApiResponse: Bool,
    expectedAuthSessionCompletionHandlerUrl: URL?,
    expectedAuthSessionCompletionHandlerError: Error?,
    expectAuthSessionCompletionHandlerInvoked: Bool,
    expectedAuthCancelCallCount: Int,
    expectedAuthSessionExists: Bool,
    expectedAuthSessionCompletionExists: Bool,
    expectedCanOpenUrlCalledWithUrl: URL?,
    expectedCanOpenUrlSource: String?,
    expectedCanOpenUrlAnnotation: String?,
    expectedOpenUrlUrl: URL?,
    expectedOpenUrlSource: String?,
    expectedOpenUrlAnnotation: String?,
    expectedPendingUrlOpenExists: Bool,
    expectedIsDismissingSafariVc: Bool,
    expectedSafariVcExists: Bool,
    expectedReturnValue: Bool,
    file: StaticString = #file,
    line: UInt = #line
  ) {
    let urlOpener = FBSDKLoginManager()
    let authSessionSpy = AuthenticationSessionSpy(
      url: sampleURL,
      callbackURLScheme: nil
    ) { _, _ in
      XCTFail(
        "Should not invoke the completion for the authentication session",
        file: file,
        line: line
      )
    }

    urlOpener.stubShouldStopPropagationOfURL(sampleURL, withValue: false)
    urlOpener.stubbedCanOpenURL = pendingUrlCanOpenUrl

    api.pendingURLOpener = urlOpener

    if hasSafariViewController {
      api.safariViewController = TestSafariViewController(url: sampleURL)
    }
    api.isDismissingSafariViewController = isDismissingSafariViewController
    api.authenticationSessionState = .none
    api.pendingRequest = makeSampleBridgeAPIRequest()
    api.authenticationSession = authSessionSpy

    var capturedAuthSessionCompletionHandlerURL: URL?
    var capturedAuthSessionCompletionHandlerError: Error?
    if authSessionCompletionHandlerExists {
      api.authenticationSessionCompletionHandler = { callbackURL, error in
        capturedAuthSessionCompletionHandlerURL = callbackURL
        capturedAuthSessionCompletionHandlerError = error
      }
    }

    api.pendingRequestCompletionBlock = { _ in
      XCTFail(
        "Should not invoke the pending request completion block",
        file: file,
        line: line
      )
    }

    if canHandleBridgeApiResponse {
      appURLSchemeProvider.appURLScheme = URLScheme.http.rawValue
      api.pendingRequestCompletionBlock = nil
    } else {
      appURLSchemeProvider.appURLScheme = "foo"
    }

    let urlToOpen = (canHandleBridgeApiResponse ? validBridgeResponseURL : sampleURL)
    let returnValue = api.application(
      UIApplication.shared,
      open: urlToOpen,
      sourceApplication: sampleSource,
      annotation: sampleAnnotation
    )
    XCTAssertEqual(
      returnValue,
      expectedReturnValue,
      "The return value for the overall method should be \(expectedReturnValue)",
      file: file,
      line: line
    )

    if authSessionCompletionHandlerExists, expectAuthSessionCompletionHandlerInvoked {
      XCTAssertEqual(
        capturedAuthSessionCompletionHandlerURL,
        expectedAuthSessionCompletionHandlerUrl,
        "Should invoke the authentication session completion handler with the expected URL",
        file: file,
        line: line
      )
      XCTAssertEqual(
        capturedAuthSessionCompletionHandlerError as NSError?,
        expectedAuthSessionCompletionHandlerError as NSError?,
        "Should invoke the authentication session completion handler with the expected error",
        file: file,
        line: line
      )
    } else {
      XCTAssertNil(
        capturedAuthSessionCompletionHandlerURL,
        "Should not invoke the authentication session completion handler",
        file: file,
        line: line
      )
      XCTAssertNil(
        capturedAuthSessionCompletionHandlerError,
        "Should not invoke the authentication session completion handler",
        file: file,
        line: line
      )
    }

    if expectedAuthSessionExists {
      XCTAssertNotNil(
        api.authenticationSession,
        "The authentication session should not be nil",
        file: file,
        line: line
      )
    } else {
      XCTAssertNil(
        api.authenticationSession,
        "The authentication session should be nil",
        file: file,
        line: line
      )
    }

    if expectedAuthSessionCompletionExists {
      XCTAssertNotNil(
        api.authenticationSessionCompletionHandler,
        "The authentication session completion handler should not be nil",
        file: file,
        line: line
      )
    } else {
      XCTAssertNil(
        api.authenticationSessionCompletionHandler,
        "The authentication session completion handler should be nil",
        file: file,
        line: line
      )
    }

    XCTAssertEqual(
      authSessionSpy.cancelCallCount,
      expectedAuthCancelCallCount,
      "The authentication session should be cancelled the expected number of times",
      file: file,
      line: line
    )
    XCTAssertEqual(
      api.authenticationSessionState,
      .none,
      "The authentication session state should not change",
      file: file,
      line: line
    )
    XCTAssertEqual(
      urlOpener.capturedCanOpenURL,
      expectedCanOpenUrlCalledWithUrl,
      "The url opener's can open url method should be called with the expected URL",
      file: file,
      line: line
    )
    XCTAssertEqual(
      urlOpener.capturedCanOpenSourceApplication,
      expectedCanOpenUrlSource,
      "The url opener's can open url method should be called with the expected source application",
      file: file,
      line: line
    )
    XCTAssertEqual(
      urlOpener.capturedCanOpenAnnotation,
      expectedCanOpenUrlAnnotation,
      "The url opener's can open url method should be called with the expected annotation",
      file: file,
      line: line
    )

    XCTAssertEqual(
      FBSDKLoginManager.capturedOpenURL,
      expectedOpenUrlUrl,
      "The url opener's open url method should be called with the expected URL",
      file: file,
      line: line
    )
    XCTAssertEqual(
      FBSDKLoginManager.capturedSourceApplication,
      expectedOpenUrlSource,
      "The url opener's open url method should be called with the expected source application",
      file: file,
      line: line
    )
    XCTAssertEqual(
      FBSDKLoginManager.capturedAnnotation,
      expectedOpenUrlAnnotation,
      "The url opener's open url method should be called with the expected annotation",
      file: file,
      line: line
    )

    if pendingUrlCanOpenUrl {
      XCTAssertNotNil(
        api.pendingRequest,
        "The pending request should be nil",
        file: file,
        line: line
      )
    } else {
      XCTAssertNil(
        api.pendingRequest,
        "The pending request should be nil",
        file: file,
        line: line
      )
      XCTAssertNil(
        api.pendingRequestCompletionBlock,
        "The pending request completion block should be nil",
        file: file,
        line: line
      )
    }

    if expectedPendingUrlOpenExists {
      XCTAssertNotNil(
        api.pendingURLOpener,
        "The reference to the url opener should not be nil",
        file: file,
        line: line
      )
    } else {
      XCTAssertNil(
        api.pendingURLOpener,
        "The reference to the url opener should be nil",
        file: file,
        line: line
      )
    }

    if expectedSafariVcExists {
      XCTAssertNotNil(
        api.safariViewController,
        "Safari view controller should not be nil",
        file: file,
        line: line
      )
    } else {
      XCTAssertNil(
        api.safariViewController,
        "Safari view controller should be nil",
        file: file,
        line: line
      )
    }

    XCTAssertEqual(
      api.isDismissingSafariViewController,
      expectedIsDismissingSafariVc,
      "Should set isDismissingSafariViewController to the expected value",
      file: file,
      line: line
    )
  }

  func createURL(canHandleBridgeApiResponse: Bool) -> URL {
    canHandleBridgeApiResponse ? validBridgeResponseURL : sampleURL
  }

  func makeSampleBridgeAPIRequest() -> _BridgeAPIRequest {
    _BridgeAPIRequest(
      protocolType: .web,
      scheme: .https,
      methodName: nil,
      parameters: nil,
      userInfo: nil
    )! // swiftlint:disable:this force_unwrapping
  }

  func makeLoginInterruptionError(canHandleBridgeApiResponse: Bool) -> Error {
    let url = createURL(canHandleBridgeApiResponse: canHandleBridgeApiResponse)
    let errorMessage = "Login attempt cancelled by alternate call to openURL from: \(url)"

    return errorFactory.error(
      code: CoreError.errorBridgeAPIInterruption.rawValue,
      userInfo: [ErrorLocalizedDescriptionKey: errorMessage],
      message: errorMessage,
      underlyingError: nil
    )
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

@testable import FBSDKCoreKit

@available(iOS 13.0, *)
extension BridgeAPITests {

  // MARK: - Setting Session Completion Handler

  func testInvokingAuthSessionCompletionHandlerFromHandlerWithValidURLWithoutError() {
    var capturedSuccesses = [Bool]()
    var capturedErrors = [Error?]()
    let handler: SuccessBlock = { success, error in
      capturedSuccesses.append(success)
      capturedErrors.append(error)
    }
    api.authenticationSession = AuthenticationSessionSpy.makeDefaultSpy()
    api.authenticationSessionState = .started
    api.setSessionCompletionHandler(calling: handler)

    api.authenticationSessionCompletionHandler?(sampleURL, nil)

    XCTAssertEqual(
      capturedSuccesses,
      [true, false],
      "Should complete each callback with the expected status"
    )
    XCTAssertNil(
      capturedErrors[0], // using array literal here since `first` would return an `Error??` instead of an `Error?`
      "Should complete the first callback without an error"
    )
    XCTAssertEqual(
      capturedErrors.last as? NSError,
      makeLoginCancellationError(url: sampleURL) as NSError?,
      "Should complete with the expected error"
    )

    verifyAuthenticationPropertiesReset()
  }

  func testInvokingAuthSessionCompletionHandlerFromHandlerWithInvalidURLWithoutError() {
    let url = URL(string: " ")

    var capturedSuccesses = [Bool]()
    var capturedErrors = [Error?]()
    let handler: SuccessBlock = { success, error in
      capturedSuccesses.append(success)
      capturedErrors.append(error)
    }
    api.authenticationSession = AuthenticationSessionSpy.makeDefaultSpy()
    api.authenticationSessionState = .started
    api.setSessionCompletionHandler(calling: handler)

    api.authenticationSessionCompletionHandler?(url, nil)

    XCTAssertEqual(
      capturedSuccesses.count,
      1,
      "Should only invoke the completion once"
    )
    XCTAssertFalse(
      capturedSuccesses[0],
      "Completing with an invalid url should invoke the handler with a failure"
    )
    XCTAssertNil(
      capturedErrors[0],
      "Completing with an invalid url should not invoke the handler with an error"
    )
    verifyAuthenticationPropertiesReset()
  }

  func testInvokingAuthSessionCompletionHandlerFromHandlerWithoutURLWithoutError() {
    var capturedSuccesses = [Bool]()
    var capturedErrors = [Error?]()
    let handler: SuccessBlock = { success, error in
      capturedSuccesses.append(success)
      capturedErrors.append(error)
    }
    api.authenticationSession = AuthenticationSessionSpy.makeDefaultSpy()
    api.authenticationSessionState = .started

    api.setSessionCompletionHandler(calling: handler)
    api.authenticationSessionCompletionHandler?(nil, nil)

    XCTAssertEqual(
      capturedSuccesses.count,
      1,
      "Should only invoke the completion once"
    )
    XCTAssertFalse(
      capturedSuccesses[0],
      "Completing with a missing url should invoke the handler with a failure"
    )
    XCTAssertNil(
      capturedErrors[0],
      "Completing with a missing url should not invoke the handler with an error"
    )
    verifyAuthenticationPropertiesReset()
  }

  func testInvokingAuthSessionCompletionHandlerFromHandlerWithValidURLWithError() {
    var capturedSuccesses = [Bool]()
    var capturedErrors = [Error?]()
    let handler: SuccessBlock = { success, error in
      capturedSuccesses.append(success)
      capturedErrors.append(error)
    }
    api.authenticationSession = AuthenticationSessionSpy.makeDefaultSpy()
    api.authenticationSessionState = .started
    api.setSessionCompletionHandler(calling: handler)

    api.authenticationSessionCompletionHandler?(sampleURL, SampleError())

    XCTAssertEqual(
      capturedSuccesses.count,
      1,
      "Should only invoke the completion once"
    )
    XCTAssertFalse(
      capturedSuccesses[0],
      "Completing with an error and a URL should invoke the handler with a failure"
    )
    XCTAssertTrue(
      capturedErrors[0] is SampleError,
      "Completing with an error and a URL should invoke the handler with that same error"
    )
    verifyAuthenticationPropertiesReset()
  }

  func testInvokingAuthSessionCompletionHandlerFromHandlerWithInvalidURLWithError() {
    let url = URL(string: " ")

    var capturedSuccesses = [Bool]()
    var capturedErrors = [Error?]()
    let handler: SuccessBlock = { success, error in
      capturedSuccesses.append(success)
      capturedErrors.append(error)
    }
    api.authenticationSession = AuthenticationSessionSpy.makeDefaultSpy()
    api.authenticationSessionState = .started
    api.setSessionCompletionHandler(calling: handler)

    api.authenticationSessionCompletionHandler?(url, SampleError())

    XCTAssertEqual(
      capturedSuccesses.count,
      1,
      "Should only invoke the completion once"
    )
    XCTAssertFalse(
      capturedSuccesses[0],
      "Completing with an error and an invalid URL should invoke the handler with a failure"
    )
    XCTAssertTrue(
      capturedErrors[0] is SampleError,
      "Completing with an error and an invalid URL should invoke the handler with that same error"
    )
    verifyAuthenticationPropertiesReset()
  }

  func testInvokingAuthSessionCompletionHandlerFromHandlerWithoutURLWithError() {
    var capturedSuccesses = [Bool]()
    var capturedErrors = [Error?]()
    let handler: SuccessBlock = { success, error in
      capturedSuccesses.append(success)
      capturedErrors.append(error)
    }
    api.authenticationSession = AuthenticationSessionSpy.makeDefaultSpy()
    api.authenticationSessionState = .started
    api.setSessionCompletionHandler(calling: handler)

    api.authenticationSessionCompletionHandler?(nil, SampleError())

    XCTAssertEqual(
      capturedSuccesses.count,
      1,
      "Should only invoke the completion once"
    )
    XCTAssertFalse(
      capturedSuccesses[0],
      "Completing with an error and a missing URL should invoke the handler with a failure"
    )
    XCTAssertTrue(
      capturedErrors[0] is SampleError,
      "Completing with an error and a missing URL should invoke the handler with that same error"
    )
    verifyAuthenticationPropertiesReset()
  }

  // MARK: - Helpers

  func makeLoginCancellationError(url: URL) -> Error {
    let errorMessage = "Login attempt cancelled by alternate call to openURL from: \(url)"
    return errorFactory.error(
      code: CoreError.errorBridgeAPIInterruption.rawValue,
      userInfo: [ErrorLocalizedDescriptionKey: errorMessage],
      message: errorMessage,
      underlyingError: nil
    )
  }

  func verifyAuthenticationPropertiesReset() {
    XCTAssertNil(api.authenticationSession)
    XCTAssertNil(api.authenticationSessionCompletionHandler)
    XCTAssertEqual(api.authenticationSessionState, .none)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

@testable import FBSDKCoreKit

import Foundation
import SafariServices
import XCTest

final class BridgeAPIOpenBridgeRequestTests: XCTestCase {

  let sampleUrl = URL(string: "http://example.com")
  let urlOpener = TestInternalURLOpener(canOpenURL: true)
  let bridgeAPIResponseFactory = TestBridgeAPIResponseFactory()
  let appURLSchemeProvider = TestInternalUtility()
  let logger = TestLogger(loggingBehavior: .developerErrors)
  lazy var api = _BridgeAPI(
    logger: logger,
    urlOpener: urlOpener,
    bridgeAPIResponseFactory: bridgeAPIResponseFactory,
    appURLSchemeProvider: appURLSchemeProvider,
    errorFactory: TestErrorFactory()
  )

  // MARK: - URL Opening

  func testOpeningBridgeRequestWithRequestUrlUsingSafariVcWithFromVc() {
    let spy = ViewControllerSpy.makeDefaultSpy()
    let request = TestBridgeAPIRequest(url: sampleUrl)
    api.open(
      request,
      useSafariViewController: true,
      from: spy,
      completionBlock: uninvokedCompletionHandler()
    )

    XCTAssertTrue(api.pendingRequest === request)
    XCTAssertNotNil(api.pendingRequestCompletionBlock)

    XCTAssertNil(
      bridgeAPIResponseFactory.capturedResponseURL,
      "Should not create a bridge response"
    )
    XCTAssertEqual(
      api.safariViewController?.delegate as? _BridgeAPI,
      api,
      "Should create a safari controller with the bridge as its delegate"
    )
  }

  func testOpeningBridgeRequestWithRequestUrlUsingSafariVcWithoutFromVc() {
    let request = TestBridgeAPIRequest(url: sampleUrl)
    api.open(
      request,
      useSafariViewController: true,
      from: nil,
      completionBlock: uninvokedCompletionHandler()
    )

    XCTAssertTrue(api.pendingRequest === request)
    XCTAssertNotNil(api.pendingRequestCompletionBlock)

    XCTAssertEqual(
      api.logger.contents,
      "There are no valid ViewController to present SafariViewController with"
    )
  }

  func testOpeningBridgeRequestWithRequestUrlNotUsingSafariVcWithFromVc() {
    let spy = ViewControllerSpy.makeDefaultSpy()
    let request = TestBridgeAPIRequest(url: sampleUrl)
    api.open(
      request,
      useSafariViewController: false,
      from: spy,
      completionBlock: uninvokedCompletionHandler()
    )

    XCTAssertTrue(api.pendingRequest === request)
    XCTAssertNotNil(api.pendingRequestCompletionBlock)
  }

  func testOpeningBridgeRequestWithRequestUrlNotUsingSafariVcWithoutFromVc() {
    let request = TestBridgeAPIRequest(url: sampleUrl)
    api.open(
      request,
      useSafariViewController: false,
      from: nil,
      completionBlock: uninvokedCompletionHandler()
    )

    XCTAssertTrue(api.pendingRequest === request)
    XCTAssertNotNil(api.pendingRequestCompletionBlock)
  }

  func testOpeningBridgeRequestWithoutRequestUrlUsingSafariVcWithFromVc() {
    let spy = ViewControllerSpy.makeDefaultSpy()
    let request = TestBridgeAPIRequest(url: nil)

    let completionHandler: BridgeAPIResponseBlock = { response in
      XCTAssertEqual(
        response.request as? TestBridgeAPIRequest,
        request,
        "Should call the completion with a response that includes the original request"
      )
      XCTAssertTrue(
        response.error is FakeBridgeAPIRequestError,
        "Should call the completion with an error if the request cannot provide a url"
      )
    }

    api.open(
      request,
      useSafariViewController: true,
      from: spy,
      completionBlock: completionHandler
    )

    XCTAssertNil(
      urlOpener.capturedOpenURL,
      "Should not try to open a url when the request cannot provide one"
    )
    assertPendingPropertiesNotSet()
  }

  func testOpeningBridgeRequestWithoutRequestUrlUsingSafariVcWithoutFromVc() {
    let request = TestBridgeAPIRequest(url: nil)

    let completionHandler: BridgeAPIResponseBlock = { response in
      XCTAssertEqual(
        response.request as? TestBridgeAPIRequest,
        request,
        "Should call the completion with a response that includes the original request"
      )
      XCTAssertTrue(
        response.error is FakeBridgeAPIRequestError,
        "Should call the completion with an error if the request cannot provide a url"
      )
    }
    api.open(
      request,
      useSafariViewController: true,
      from: nil,
      completionBlock: completionHandler
    )
    XCTAssertNil(
      urlOpener.capturedOpenURL,
      "Should not try to open a url when the request cannot provide one"
    )
    assertPendingPropertiesNotSet()
  }

  func testOpeningBridgeRequestWithoutRequestUrlNotUsingSafariVcWithFromVc() {
    let spy = ViewControllerSpy.makeDefaultSpy()
    let request = TestBridgeAPIRequest(url: nil)

    let completionHandler: BridgeAPIResponseBlock = { response in
      XCTAssertEqual(
        response.request as? TestBridgeAPIRequest,
        request,
        "Should call the completion with a response that includes the original request"
      )
      XCTAssertTrue(
        response.error is FakeBridgeAPIRequestError,
        "Should call the completion with an error if the request cannot provide a url"
      )
    }

    api.open(
      request,
      useSafariViewController: false,
      from: spy,
      completionBlock: completionHandler
    )
    XCTAssertNil(
      urlOpener.capturedOpenURL,
      "Should not try to open a url when the request cannot provide one"
    )
    assertPendingPropertiesNotSet()
  }

  func testOpeningBridgeRequestWithoutRequestUrlNotUsingSafariVcWithoutFromVc() {
    let request = TestBridgeAPIRequest(url: nil)

    let completionHandler: BridgeAPIResponseBlock = { response in
      XCTAssertEqual(
        response.request as? TestBridgeAPIRequest,
        request,
        "Should call the completion with a response that includes the original request"
      )
      XCTAssertTrue(
        response.error is FakeBridgeAPIRequestError,
        "Should call the completion with an error if the request cannot provide a url"
      )
    }
    api.open(
      request,
      useSafariViewController: false,
      from: nil,
      completionBlock: completionHandler
    )
    XCTAssertNil(
      urlOpener.capturedOpenURL,
      "Should not try to open a url when the request cannot provide one"
    )
    assertPendingPropertiesNotSet()
  }

  // MARK: - Helpers

  func assertPendingPropertiesNotSet() {
    XCTAssertNil(
      api.pendingRequest,
      "Should not set a pending request if the bridge request does not have a request url"
    )
    XCTAssertNil(
      api.pendingRequestCompletionBlock,
      "Should not set a pending request completion block if the bridge request does not have a request url"
    )
  }

  func uninvokedCompletionHandler() -> BridgeAPIResponseBlock {
    { _ in
      XCTFail("Should not invoke the completion handler")
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

@testable import FBSDKCoreKit

import TestTools
import XCTest

final class BridgeAPIOpenUrlWithSafariTests: XCTestCase {
  let loginManager = FBSDKLoginManager()

  let logger = TestLogger(loggingBehavior: .developerErrors)
  let urlOpener = TestInternalURLOpener(canOpenURL: true)
  let bridgeAPIResponseFactory = TestBridgeAPIResponseFactory()
  let appURLSchemeProvider = TestInternalUtility()
  let sampleUrl = SampleURLs.valid
  let errorFactory = TestErrorFactory()

  lazy var api = _BridgeAPI(
    logger: logger,
    urlOpener: urlOpener,
    bridgeAPIResponseFactory: bridgeAPIResponseFactory,
    appURLSchemeProvider: appURLSchemeProvider,
    errorFactory: errorFactory
  )

  override func setUp() {
    super.setUp()

    FBSDKLoginManager.resetTestEvidence()
  }

  // MARK: - Url Opening

  func testWithNonHttpUrlScheme() {
    api.isExpectingBackground = true // So we can check that it's unchanged
    let nonHTTPUrl = URL(string: "file://example.com")! // swiftlint:disable:this force_unwrapping
    api.openURLWithSafariViewController(
      url: nonHTTPUrl,
      sender: nil,
      from: nil,
      handler: uninvokedSuccessBlock()
    )

    XCTAssertEqual(
      urlOpener.capturedOpenURL,
      nonHTTPUrl,
      "Should try to open a url with a non http scheme"
    )
    XCTAssertTrue(
      api.isExpectingBackground,
      "Should not modify whether the background is expected to change"
    )
    XCTAssertNil(
      api.pendingURLOpener,
      "Should not set a pending url opener"
    )
  }

  func testWithAuthenticationURL() {
    loginManager.stubbedIsAuthenticationURL = true
    api.isExpectingBackground = true

    api.openURLWithSafariViewController(
      url: sampleUrl,
      sender: loginManager,
      from: nil,
      handler: uninvokedSuccessBlock()
    )
    XCTAssertNil(
      urlOpener.capturedOpenURL,
      "Should not try to open an authentication url when safari controller is specified"
    )
    XCTAssertNotNil(api.authenticationSessionCompletionHandler)
    XCTAssertNotNil(api.authenticationSession)
    assertExpectingBackgroundAndPendingUrlOpener()
  }

  func testWithNonAuthenticationURLWithSafariControllerAvailable() {
    loginManager.stubbedIsAuthenticationURL = false
    api.isExpectingBackground = true

    api.openURLWithSafariViewController(
      url: sampleUrl,
      sender: loginManager,
      from: nil,
      handler: uninvokedSuccessBlock()
    )
    XCTAssertNil(
      urlOpener.capturedOpenURL,
      "Should not try to open an authentication url when safari controller is specified"
    )
    XCTAssertNil(api.authenticationSessionCompletionHandler)
    XCTAssertNil(api.authenticationSession)
    assertExpectingBackgroundAndPendingUrlOpener()
  }

  func testWithoutFromViewController() {
    loginManager.stubbedIsAuthenticationURL = false
    api.isExpectingBackground = true

    api.openURLWithSafariViewController(
      url: sampleUrl,
      sender: loginManager,
      from: nil,
      handler: uninvokedSuccessBlock()
    )
    XCTAssertNil(
      urlOpener.capturedOpenURL,
      "Should not try to open a url when the request cannot provide one"
    )
    XCTAssertNil(api.authenticationSessionCompletionHandler)
    XCTAssertNil(api.authenticationSession)
    XCTAssertEqual(
      logger.capturedContents,
      "There are no valid ViewController to present SafariViewController with"
    )
  }

  func testWithFromViewControllerMissingTransitionCoordinator() {
    let spy = ViewControllerSpy.makeDefaultSpy()
    loginManager.stubbedIsAuthenticationURL = false
    api.isExpectingBackground = true
    var didInvokeHandler = false
    let handler: SuccessBlock = { success, error in
      XCTAssertTrue(success, "Should call the handler with success")
      XCTAssertNil(error, "Should not call the handler with an error")
      didInvokeHandler = true
    }

    api.openURLWithSafariViewController(
      url: sampleUrl,
      sender: loginManager,
      from: spy,
      handler: handler
    )

    let safariVc = api.safariViewController

    XCTAssertNotNil(
      safariVc,
      "Should create and set a safari view controller for display"
    )
    XCTAssertEqual(
      safariVc?.modalPresentationStyle,
      .overFullScreen,
      "Should set the correct modal presentation style"
    )
    XCTAssertEqual(
      safariVc?.delegate as? _BridgeAPI,
      api,
      "Should set the safari view controller delegate to the bridge api"
    )
    XCTAssertEqual(
      spy.capturedPresentViewController,
      safariVc?.parent,
      "Should present the view controller containing the safari view controller"
    )
    XCTAssertTrue(
      spy.capturedPresentViewControllerAnimated,
      "Should animate presenting the safari view controller"
    )
    XCTAssertNil(
      spy.capturedPresentViewControllerCompletion,
      "Should not pass a completion handler to the safari vc presentation"
    )
    XCTAssertNil(
      urlOpener.capturedOpenURL,
      "Should not try to open a url when the request cannot provide one"
    )
    XCTAssertNil(api.authenticationSessionCompletionHandler)
    XCTAssertNil(api.authenticationSession)
    assertExpectingBackgroundAndPendingUrlOpener()
    XCTAssertTrue(didInvokeHandler)
  }

  func testWithFromViewControllerWithTransitionCoordinator() {
    let spy = ViewControllerSpy.makeDefaultSpy()
    let coordinator = TestViewControllerTransitionCoordinator()
    spy.stubbedTransitionCoordinator = coordinator
    loginManager.stubbedIsAuthenticationURL = false
    api.isExpectingBackground = true
    var didInvokeHandler = false
    let handler: SuccessBlock = { success, error in
      XCTAssertTrue(success, "Should call the handler with success")
      XCTAssertNil(error, "Should not call the handler with an error")
      didInvokeHandler = true
    }
    api.openURLWithSafariViewController(
      url: sampleUrl,
      sender: loginManager,
      from: spy,
      handler: handler
    )
    // swiftlint:disable:next force_unwrapping
    coordinator.capturedAnimateAlongsideTransitionCompletion!(
      TestViewControllerTransitionCoordinator()
    )

    XCTAssertNil(
      urlOpener.capturedOpenURL,
      "Should not try to open a url when the request cannot provide one"
    )

    XCTAssertNil(api.authenticationSessionCompletionHandler)
    XCTAssertNil(api.authenticationSession)

    let safariVc = api.safariViewController

    XCTAssertNotNil(
      safariVc,
      "Should create and set a safari view controller for display"
    )
    XCTAssertEqual(
      safariVc?.modalPresentationStyle,
      .overFullScreen,
      "Should set the correct modal presentation style"
    )
    XCTAssertEqual(
      safariVc?.delegate as? _BridgeAPI,
      api,
      "Should set the safari view controller delegate to the bridge api"
    )
    XCTAssertEqual(
      spy.capturedPresentViewController,
      safariVc?.parent,
      "Should present the view controller containing the safari view controller"
    )
    XCTAssertTrue(
      spy.capturedPresentViewControllerAnimated,
      "Should animate presenting the safari view controller"
    )
    XCTAssertNil(
      spy.capturedPresentViewControllerCompletion,
      "Should not pass a completion handler to the safari vc presentation"
    )
    assertExpectingBackgroundAndPendingUrlOpener()
    XCTAssertTrue(didInvokeHandler)
  }

  func assertExpectingBackgroundAndPendingUrlOpener(
    file: StaticString = #file,
    line: UInt = #line
  ) {
    XCTAssertFalse(
      api.isExpectingBackground,
      "Should set expecting background to false",
      file: file,
      line: line
    )

    XCTAssertTrue(
      api.pendingURLOpener === loginManager,
      "Should set the pending url opener to the passed in sender",
      file: file,
      line: line
    )
  }

  func uninvokedSuccessBlock() -> SuccessBlock {
    { _, _ in
      XCTFail("Should not invoke the completion handler")
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

@testable import FBSDKCoreKit

import TestTools
import UIKit
import XCTest

final class BridgeAPIRequestTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  var internalURLOpener: TestInternalURLOpener!
  var internalUtility: TestInternalUtility!
  var settings: TestSettings!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    internalURLOpener = TestInternalURLOpener(canOpenURL: true)
    internalUtility = TestInternalUtility()
    settings = TestSettings()

    _BridgeAPIRequest.configure(
      internalURLOpener: internalURLOpener,
      internalUtility: internalUtility,
      settings: settings
    )
  }

  override func tearDown() {
    _BridgeAPIRequest.resetClassDependencies()

    internalURLOpener = nil
    internalUtility = nil
    settings = nil

    super.tearDown()
  }

  private func makeRequest(
    protocolType: FBSDKBridgeAPIProtocolType = .web,
    scheme: URLScheme = .https
  ) -> _BridgeAPIRequest? {
    _BridgeAPIRequest(
      protocolType: protocolType,
      scheme: scheme,
      methodName: "methodName",
      parameters: ["parameter": "value"],
      userInfo: ["key": "value"]
    )
  }

  func testDefaultClassDependencies() throws {
    _BridgeAPIRequest.resetClassDependencies()
    _ = makeRequest()

    XCTAssertNil(_BridgeAPIRequest.settings, "Should not have a default settings")
    XCTAssertNil(_BridgeAPIRequest.internalUtility, "Should not have a default internal utility")
    XCTAssertNil(_BridgeAPIRequest.internalURLOpener, "Should not have a default internal url opener")
  }

  func testRequestProtocolConformance() {
    XCTAssertTrue(
      (_BridgeAPIRequest.self as Any) is BridgeAPIRequestProtocol.Type,
      "_BridgeAPIRequest should conform to the expected protocol"
    )
  }

  func testOpenableURL() {
    XCTAssertNotNil(
      makeRequest(protocolType: .native, scheme: .facebookAPI),
      "BridgeAPIRequests should only be created for openable URLs"
    )
  }

  func testProperties() throws {
    let request = try XCTUnwrap(makeRequest())

    XCTAssertEqual(request.protocolType, .web, "A request should use the provided protocol type")
    XCTAssertTrue(
      request.protocol is BridgeAPIProtocolWebV1,
      "A request should use a protocol based on its protocol type"
    )
    XCTAssertEqual(request.scheme, .https, "A request should use the provided scheme")
    XCTAssertEqual(request.methodName, "methodName", "A request should use the provided method name")

    let parametersMessage = "A request should use the provided parameters"
    let parameters = try XCTUnwrap(request.parameters, parametersMessage)
    XCTAssertEqual(parameters.count, 1, parametersMessage)
    XCTAssertEqual(parameters["parameter"] as? String, "value", parametersMessage)

    let userInfoMessage = "A request should use the provided user info"
    let userInfo = try XCTUnwrap(request.userInfo, userInfoMessage)
    XCTAssertEqual(userInfo.count, 1, userInfoMessage)
    XCTAssertEqual(userInfo["key"] as? String, "value", userInfoMessage)
  }

  func testUnopenableRequestURL() throws {
    let request = try XCTUnwrap(makeRequest())
    internalURLOpener.canOpenURL = false

    XCTAssertThrowsError(
      try request.requestURL(),
      "Unopenable request URLs should not be provided"
    )
  }

  func testCopying() throws {
    let request = try XCTUnwrap(makeRequest())
    let copy = try XCTUnwrap(request.copy() as AnyObject)
    XCTAssertTrue(request === copy, "Instances should be provided as copies of themselves")
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

@testable import FBSDKCoreKit

import TestTools
import XCTest

final class BridgeAPIResponseFactoryTests: XCTestCase {

  let factory = _BridgeAPIResponseFactory()
  let request = TestBridgeAPIRequest(url: SampleURLs.valid)
  let error = SampleError()
  lazy var response = factory.createResponse(
    request: request,
    error: error
  )

  func testCreatingResponseWithError() {
    response = factory.createResponse(
      request: request,
      error: error
    )
    XCTAssertEqual(
      response.request as? TestBridgeAPIRequest,
      request,
      "Should set the request on the response"
    )
    XCTAssertTrue(
      response.error is SampleError,
      "Should set the error on the request"
    )
  }

  func testCreatingCancelledResponse() {
    response = factory.createResponseCancelled(request: request)

    XCTAssertEqual(
      response.request as? TestBridgeAPIRequest,
      request,
      "Should set the request on the response"
    )
    XCTAssertTrue(
      response.isCancelled,
      "Should create a cancelled response"
    )
  }

  func testCreatingWithRequestResponseAndSourceApplication() throws {
    request.protocol = TestBridgeAPIProtocol()
    response = try factory.createResponse(
      request: request,
      responseURL: SampleURLs.valid,
      sourceApplication: "foo"
    )

    XCTAssertEqual(
      response.request as? TestBridgeAPIRequest,
      request,
      "Should set the request on the response"
    )
    XCTAssertFalse(
      response.isCancelled,
      "Should not create a cancelled response"
    )
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the license found in the
 * LICENSE file in the root directory of this source tree.
 */

@testable import FBSDKCoreKit

import TestTools
import XCTest

final class BridgeAPIResponseTests: XCTestCase {
  let request = TestBridgeAPIRequest(url: SampleURLs.valid)
  let bridgeProtocol = TestBridgeAPIProtocol()
  let queryItems = [URLQueryItem(name: "foo", value: "bar")]
  lazy var responseURL = SampleURLs.valid(queryItems: queryItems)

  func testCreatingWithMinimalInput() {
    let response = BridgeAPIResponse(request: request, error: SampleError())

    XCTAssertFalse(
      response.isCancelled,
      "The response should not be cancelled by default"
    )
    XCTAssertEqual(
      response.request as? TestBridgeAPIRequest,
      request,
      "Should store the request it was created with"
    )
    XCTAssertNil(
      response.responseParameters,
      "Should not have response parameters by default"
    )
  }

  func testCreatingCancelledWithRequest() {
    let response = BridgeAPIResponse(cancelledWith: request)

    XCTAssertTrue(
      response.isCancelled,
      "The response should be cancelled upon creation"
    )
    XCTAssertEqual(
      response.request as? TestBridgeAPIRequest,
      request,
      "Should store the request it was created with"
    )
    XCTAssertNil(
      response.responseParameters,
      "Should not have parameters when creating as cancelled"
    )
    XCTAssertNil(
      response.error,
      "Should not have an error reference by default"
    )
  }

  func testCreatingWithInvalidProtocolSourceApplicationCombinations() {
    request.protocol = bridgeProtocol
    [
      FBSDKBridgeAPIProtocolType.native,
      .web,
    ]
      .forEach { protocolType in
        request.protocolType = protocolType

        XCTAssertNil(
          try? BridgeAPIResponse(
            request: request,
            responseURL: responseURL,
            sourceApplication: "foo",
            osVersionComparer: TestProcessInfo(stubbedOperatingSystemCheckResult: false)
          ),
          "Should not create a response when the source application is invalid for the protocol type"
        )
      }
  }

  func testCreatingWithValidProtocolAndSourceApplicationCombinations() {
    request.protocol = bridgeProtocol

    let pairs: [(type: FBSDKBridgeAPIProtocolType, sources: [String])] = [
      (.native, ["com.facebook.foo", ".com.facebook.foo"]),
      (.web, ["com.apple.mobilesafari", "com.apple.SafariViewService"]),
    ]

    pairs.forEach { pair in
      request.protocolType = pair.type
      pair.sources.forEach { source in
        let response = try? BridgeAPIResponse(
          request: request,
          responseURL: responseURL,
          sourceApplication: source,
          osVersionComparer: TestProcessInfo(stubbedOperatingSystemCheckResult: false)
        )
        XCTAssertNotNil(
          response,
          """
          Should create a response when the source application \(source)
          is valid for the protocol type \(String(describing: pair.type))
          """
        )
      }
    }
  }

  func testCreatingWithValidResponseParameters() {
    request.actionID = "123"
    request.protocol = bridgeProtocol

    _ = try? BridgeAPIResponse(
      request: request,
      responseURL: responseURL,
      sourceApplication: "foo"
    )
    XCTAssertEqual(
      bridgeProtocol.capturedResponseActionID,
      request.actionID,
      "Should use the action ID from the request to generate the response"
    )
    XCTAssertNotNil(
      bridgeProtocol.capturedResponseCancelledRef,
      "Should call the response helper with an error reference"
    )
    XCTAssertEqual(
      bridgeProtocol.capturedResponseQueryParameters as? [String: String],
      ["foo": "bar"],
      "Should use the query items from the response url to create the bridge response"
    )
  }
}