/*
 * 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 FBSDKLoginKit
import XCTest

final class DependentAsObjectTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  private var defaultImplementationDependent: DefaultImplementationDependent!
  private var customImplementationDependent: CustomImplementationDependent!
  // swiftlint:enable implicitly_unwrapped_optional

  private let defaultDependencies = TestDependencies(value: 14)
  private let customDependencies = TestDependencies(value: 28)

  override func setUp() {
    super.setUp()

    defaultImplementationDependent = DefaultImplementationDependent()
    customImplementationDependent = CustomImplementationDependent()
  }

  override func tearDown() {
    defaultImplementationDependent = nil
    customImplementationDependent = nil

    super.tearDown()
  }

  func testMissingDependencies() {
    XCTAssertThrowsError(
      try defaultImplementationDependent.getDependencies(),
      .missingDependencies
    ) { error in
      XCTAssertEqual(
        String(describing: error),
        "The dependencies for the type 'DefaultImplementationDependent' or an instance of it have not been set",
        .missingDependencies
      )
    }
  }

  func testDefaultDependencies() {
    defaultImplementationDependent.defaultDependencies = defaultDependencies
    XCTAssertEqual(
      try defaultImplementationDependent.getDependencies(),
      defaultDependencies,
      .defaultDependencies
    )
  }

  func testConfiguredDependencies() {
    defaultImplementationDependent.defaultDependencies = defaultDependencies
    defaultImplementationDependent.configuredDependencies = customDependencies
    XCTAssertEqual(
      try defaultImplementationDependent.getDependencies(),
      customDependencies,
      .customDependencies
    )
  }

  func testDefaultSetDependencies() {
    defaultImplementationDependent.setDependencies(customDependencies)
    XCTAssertEqual(
      defaultImplementationDependent.configuredDependencies,
      customDependencies,
      .defaultSetDependenciesImplementation
    )
  }

  func testCustomSetDependencies() {
    customImplementationDependent.setDependencies(customDependencies)
    XCTAssertTrue(customImplementationDependent.wasSetDependenciesCalled, .customSetDependenciesImplementation)
  }

  func testDefaultResetDependencies() {
    defaultImplementationDependent.configuredDependencies = customDependencies
    defaultImplementationDependent.resetDependencies()
    XCTAssertNil(defaultImplementationDependent.configuredDependencies, .defaultResetDependenciesImplementation)
  }

  func testCustomResetDependencies() {
    customImplementationDependent.resetDependencies()
    XCTAssertTrue(customImplementationDependent.wasResetDependenciesCalled, .customResetDependenciesImplementation)
  }

  func testFailedDynamicMemberLookup() {
    XCTAssertNil(defaultImplementationDependent.value, .missingDependencyDynamicMemberLookup)
  }

  func testDynamicMemberLookup() {
    defaultImplementationDependent.defaultDependencies = customDependencies
    XCTAssertEqual(
      defaultImplementationDependent.value,
      customDependencies.value,
      .dynamicMemberLookup
    )
  }
}

// MARK: - Test Types

private struct TestDependencies: Equatable {
  let value: Int
}

private final class DefaultImplementationDependent: DependentAsObject {
  var configuredDependencies: TestDependencies?
  var defaultDependencies: TestDependencies?
}

private final class CustomImplementationDependent: DependentAsObject {
  var configuredDependencies: TestDependencies?
  var defaultDependencies: TestDependencies?

  var wasSetDependenciesCalled = false
  var wasResetDependenciesCalled = false

  func setDependencies(_ dependencies: TestDependencies) {
    wasSetDependenciesCalled = true
  }

  func resetDependencies() {
    wasResetDependenciesCalled = true
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let missingDependencies = """
    Attempting to get the missing dependencies of a dependent throws a missing dependencies error
    """
  static let defaultDependencies = """
    When a dependent's configured dependencies are missing, its default dependencies are provided
    """
  static let customDependencies = "When a dependent has configured dependencies, those dependencies are provided"

  static let defaultSetDependenciesImplementation = """
    A dependent has a default `setDependencies(_:)` implementation that sets its configured dependencies
    """
  static let customSetDependenciesImplementation = """
    A dependent can override the default `setDependencies(_:)` implementation
    """

  static let defaultResetDependenciesImplementation = """
    A dependent has a default `resetDependencies()` implementation that clears its configured dependencies
    """
  static let customResetDependenciesImplementation = """
    A dependent can override the default `resetDependencies()` implementation
    """

  static let missingDependencyDynamicMemberLookup = """
    When a dependent's dependencies are missing, dynamic lookup of a dependency as a property yields a nil value
    """
  static let dynamicMemberLookup = "The discrete dependencies of a dependent can be accessed dynamically as properties"
}


/*
 * 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 FBSDKLoginKit
import XCTest

final class DependentAsTypeTests: XCTestCase {

  private let defaultDependencies = TestDependencies(value: 14)
  private let customDependencies = TestDependencies(value: 28)

  override func setUp() {
    super.setUp()
    resetDependencies()
  }

  override func tearDown() {
    resetDependencies()
    super.tearDown()
  }

  private func resetDependencies() {
    DefaultImplementationDependent.configuredDependencies = nil
    DefaultImplementationDependent.defaultDependencies = nil

    CustomImplementationDependent.configuredDependencies = nil
    CustomImplementationDependent.wasSetDependenciesCalled = false
    CustomImplementationDependent.wasResetDependenciesCalled = false
  }

  func testMissingDependencies() {
    XCTAssertThrowsError(
      try DefaultImplementationDependent.getDependencies(),
      .missingDependencies
    ) { error in
      XCTAssertEqual(
        String(describing: error),
        "The dependencies for the type 'DefaultImplementationDependent' or an instance of it have not been set",
        .missingDependencies
      )
    }
  }

  func testDefaultDependencies() {
    DefaultImplementationDependent.defaultDependencies = defaultDependencies
    XCTAssertEqual(
      try DefaultImplementationDependent.getDependencies(),
      defaultDependencies,
      .defaultDependencies
    )
  }

  func testConfiguredDependencies() {
    DefaultImplementationDependent.defaultDependencies = defaultDependencies
    DefaultImplementationDependent.configuredDependencies = customDependencies
    XCTAssertEqual(
      try DefaultImplementationDependent.getDependencies(),
      customDependencies,
      .customDependencies
    )
  }

  func testDefaultSetDependencies() {
    DefaultImplementationDependent.setDependencies(customDependencies)
    XCTAssertEqual(
      DefaultImplementationDependent.configuredDependencies,
      customDependencies,
      .defaultSetDependenciesImplementation
    )
  }

  func testCustomSetDependencies() {
    CustomImplementationDependent.setDependencies(customDependencies)
    XCTAssertTrue(CustomImplementationDependent.wasSetDependenciesCalled, .customSetDependenciesImplementation)
  }

  func testDefaultResetDependencies() {
    DefaultImplementationDependent.configuredDependencies = customDependencies
    DefaultImplementationDependent.resetDependencies()
    XCTAssertNil(DefaultImplementationDependent.configuredDependencies, .defaultResetDependenciesImplementation)
  }

  func testCustomResetDependencies() {
    CustomImplementationDependent.resetDependencies()
    XCTAssertTrue(CustomImplementationDependent.wasResetDependenciesCalled, .customResetDependenciesImplementation)
  }

  func testFailedDynamicMemberLookup() {
    XCTAssertNil(DefaultImplementationDependent.value, .missingDependencyDynamicMemberLookup)
  }

  func testDynamicMemberLookup() {
    DefaultImplementationDependent.defaultDependencies = customDependencies
    XCTAssertEqual(
      DefaultImplementationDependent.value,
      customDependencies.value,
      .dynamicMemberLookup
    )
  }
}

// MARK: - Test Types

private struct TestDependencies: Equatable {
  let value: Int
}

private enum DefaultImplementationDependent: DependentAsType {
  static var configuredDependencies: TestDependencies?
  static var defaultDependencies: TestDependencies?
}

private enum CustomImplementationDependent: DependentAsType {
  static var configuredDependencies: TestDependencies?
  static var defaultDependencies: TestDependencies?

  static var wasSetDependenciesCalled = false
  static var wasResetDependenciesCalled = false

  static func setDependencies(_ dependencies: TestDependencies) {
    wasSetDependenciesCalled = true
  }

  static func resetDependencies() {
    wasResetDependenciesCalled = true
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let missingDependencies = """
    Attempting to get the missing dependencies of a dependent throws a missing type dependencies error
    """
  static let defaultDependencies = """
    When a dependent's configured dependencies are missing, its default dependencies are provided
    """
  static let customDependencies = "When a dependent has configured dependencies, those dependencies are provided"

  static let defaultSetDependenciesImplementation = """
    A dependent has a default `setDependencies(_:)` implementation that sets its configured dependencies
    """
  static let customSetDependenciesImplementation = """
    A dependent can override the default `setDependencies(_:)` implementation
    """

  static let defaultResetDependenciesImplementation = """
    A dependent has a default `resetDependencies()` implementation that clears its configured dependencies
    """
  static let customResetDependenciesImplementation = """
    A dependent can override the default `resetDependencies()` implementation
    """

  static let missingDependencyDynamicMemberLookup = """
    When a dependent's dependencies are missing, dynamic lookup of a dependency as a property yields a nil value
    """
  static let dynamicMemberLookup = "The discrete dependencies of a dependent can be accessed dynamically as properties"
}


/*
 * 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 FBSDKLoginKit

import XCTest

final class DeviceLoginCodeInfoTests: XCTestCase {

  enum Values {
    static let identifier = "abcd"
    static let identifier2 = "123"
    static let identifier3 = "123abc"
    static let loginCode = "abcd"
    static let loginCode2 = "123"
    static let loginCode3 = "123abc"
    static let verificationURL = URL(string: "https://www.facebook.com/some/test/url")! // swiftlint:disable:this force_unwrapping
    static let expirationDate = Date.distantFuture
    static let pollingInterval: UInt = 10
  }

  func testCreateValidDeviceLoginCodeShouldSucceed() {
    validateSuccessfulCreation(
      identifier: Values.identifier,
      loginCode: Values.loginCode
    )
    validateSuccessfulCreation(
      identifier: Values.identifier2,
      loginCode: Values.loginCode2
    )
    validateSuccessfulCreation(
      identifier: Values.identifier3,
      loginCode: Values.loginCode3
    )
  }

  func testMinimumPollingInterval() {
    let deviceLoginCodeInfo = DeviceLoginCodeInfo(
      identifier: Values.identifier,
      loginCode: Values.loginCode,
      verificationURL: Values.verificationURL,
      expirationDate: Values.expirationDate,
      pollingInterval: 4
    )
    XCTAssertEqual(deviceLoginCodeInfo.pollingInterval, 5)
  }

  func validateSuccessfulCreation(
    identifier: String,
    loginCode: String,
    _ file: StaticString = #file,
    _ line: UInt = #line
  ) {
    let deviceLoginCodeInfo = DeviceLoginCodeInfo(
      identifier: identifier,
      loginCode: loginCode,
      verificationURL: Values.verificationURL,
      expirationDate: Values.expirationDate,
      pollingInterval: Values.pollingInterval
    )
    XCTAssertEqual(deviceLoginCodeInfo.identifier, identifier, file: file, line: line)
    XCTAssertEqual(deviceLoginCodeInfo.loginCode, loginCode, file: file, line: line)
    XCTAssertEqual(deviceLoginCodeInfo.verificationURL, Values.verificationURL, file: file, line: line)
    XCTAssertEqual(deviceLoginCodeInfo.expirationDate, Values.expirationDate, file: file, line: line)
    XCTAssertEqual(deviceLoginCodeInfo.pollingInterval, Values.pollingInterval, 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 FBSDKLoginKit

import TestTools
import XCTest

final class DeviceLoginManagerResultTests: XCTestCase {

  func testCreateDeviceLoginManagerResultWithToken() {
    let accessToken = SampleAccessTokens.validToken
    let result = DeviceLoginManagerResult(token: accessToken, isCancelled: false)

    XCTAssertNotNil(result)
    XCTAssertEqual(result.accessToken, accessToken)
    XCTAssertFalse(result.isCancelled)
  }

  func testCreateDeviceLoginManagerResultWithNilToken() {
    let result = DeviceLoginManagerResult(token: nil, isCancelled: true)

    XCTAssertNotNil(result)
    XCTAssertNil(result.accessToken)
    XCTAssertTrue(result.isCancelled)
  }
}


/*
 * 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 FBSDKLoginKit

import TestTools
import XCTest

final class DeviceLoginManagerTests: XCTestCase {

  let fakeAppID = "123"
  let fakeClientToken = "abc"
  let permissions = ["email", "public_profile"]
  let redirectURL = URL(string: "https://www.example.com")! // swiftlint:disable:this force_unwrapping

  // swiftlint:disable implicitly_unwrapped_optional
  var poller: TestDevicePoller!
  var delegate: TestDeviceLoginManagerDelegate!
  var graphRequestFactory: TestGraphRequestFactory!
  var settings: TestSettings!
  var internalUtility: TestInternalUtility!
  var errorFactory: TestErrorFactory!
  var manager: DeviceLoginManager!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    poller = TestDevicePoller()
    delegate = TestDeviceLoginManagerDelegate()
    graphRequestFactory = TestGraphRequestFactory()
    settings = TestSettings()
    internalUtility = TestInternalUtility()
    errorFactory = TestErrorFactory()
    manager = DeviceLoginManager(
      permissions: permissions,
      enableSmartLogin: false
    )
    manager.setDependencies(
      .init(
        devicePoller: poller,
        errorFactory: errorFactory,
        graphRequestFactory: graphRequestFactory,
        internalUtility: internalUtility,
        settings: settings
      )
    )

    manager.redirectURL = redirectURL
    manager.delegate = delegate
    manager.codeInfo = sampleCodeInfo()
  }

  override func tearDown() {
    poller = nil
    delegate = nil
    graphRequestFactory = nil
    settings = nil
    internalUtility = nil
    errorFactory = nil
    manager = nil

    super.tearDown()
  }

  // MARK: Dependencies

  func testCreatingWithDependencies() throws {
    let dependencies = try manager.getDependencies()

    XCTAssertIdentical(
      dependencies.graphRequestFactory,
      graphRequestFactory,
      "A device login manager uses a provided graph request factory"
    )
    XCTAssertIdentical(
      dependencies.devicePoller as AnyObject,
      poller,
      "A device login manager uses a provided device poller"
    )
    XCTAssertIdentical(
      dependencies.settings,
      settings,
      "A device login manager uses a provided settings"
    )
    XCTAssertIdentical(
      dependencies.internalUtility,
      internalUtility,
      "A device login manager uses a provided internal utility"
    )
    XCTAssertIdentical(
      dependencies.errorFactory,
      errorFactory,
      "A device login manager uses a provided error factory"
    )
  }

  func testDefaultDependencies() throws {
    manager.resetDependencies()
    let dependencies = try manager.getDependencies()

    XCTAssertTrue(
      dependencies.graphRequestFactory is GraphRequestFactory,
      "A device login manager uses a concrete graph request factory by default"
    )
    XCTAssertTrue(
      dependencies.devicePoller is DevicePoller,
      "A device login manager uses a concrete device poller by default"
    )
    XCTAssertIdentical(
      dependencies.settings,
      Settings.shared,
      "A device login manager uses the shared settings by default"
    )
    XCTAssertIdentical(
      dependencies.internalUtility,
      InternalUtility.shared,
      "A device login manager uses the shared internal utility by default"
    )
    XCTAssertTrue(
      dependencies.errorFactory is _ErrorFactory,
      "A device login manager uses a concrete error factory by default"
    )
  }

  // MARK: Start

  func testStartGraphRequestCreation() throws {
    manager.start()

    let request = try XCTUnwrap(graphRequestFactory.capturedRequests.first)

    XCTAssertEqual(
      request.graphPath,
      "device/login",
      "Should create a graph request with the expected graph path"
    )
    XCTAssertEqual(
      request.parameters["scope"] as? String,
      permissions.joined(separator: ","),
      "Should create a graph request with the expected scope"
    )
    XCTAssertEqual(
      request.parameters["redirect_uri"] as? String,
      redirectURL.absoluteString,
      "Should create a graph request with the expected redirect URL"
    )
    XCTAssertNotNil(
      request.parameters["device_info"],
      "Should create a graph request with device info"
    )
  }

  func testStartGraphRequestCompleteWithError() throws {
    manager.start()

    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(nil, nil, NSError(domain: "foo", code: 0, userInfo: nil))

    XCTAssertEqual(delegate.capturedLoginManager, manager)
    XCTAssertNotNil(delegate.capturedError)
  }

  func testStartGraphRequestCompleteWithEmptyResponse() throws {
    manager.start()

    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(nil, [], nil)

    XCTAssertEqual(delegate.capturedLoginManager, manager)
    XCTAssertNotNil(delegate.capturedError)
  }

  func testStartGraphRequestCompleteWithCodeInfo() throws {
    manager.start()
    let expectedCodeInfo = sampleCodeInfo()
    let result = [
      "code": expectedCodeInfo.identifier,
      "user_code": expectedCodeInfo.loginCode,
      "verification_uri": expectedCodeInfo.verificationURL.absoluteString,
      "expires_in": String(expectedCodeInfo.expirationDate.timeIntervalSinceNow),
      "interval": expectedCodeInfo.pollingInterval,
    ] as [String: Any]

    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(nil, result, nil)

    XCTAssertEqual(delegate.capturedLoginManager, manager)
    XCTAssertNil(delegate.capturedError)
    XCTAssertNil(delegate.capturedResult)

    let codeInfo = try XCTUnwrap(delegate.capturedCodeInfo, "Should receive code info")

    XCTAssertEqual(codeInfo.identifier, expectedCodeInfo.identifier)
    XCTAssertEqual(codeInfo.loginCode, expectedCodeInfo.loginCode)
    XCTAssertEqual(codeInfo.verificationURL, expectedCodeInfo.verificationURL)
    XCTAssertEqual(
      codeInfo.expirationDate.timeIntervalSince1970,
      expectedCodeInfo.expirationDate.timeIntervalSince1970,
      accuracy: 1
    )
    XCTAssertEqual(codeInfo.pollingInterval, expectedCodeInfo.pollingInterval)
  }

  // MARK: _schedulePoll

  func testStatusGraphRequestCreation() throws {
    let tokenString = "sample-token"
    internalUtility.stubbedRequiredClientAccessToken = tokenString
    let codeInfo = sampleCodeInfo()
    manager.schedulePoll(interval: codeInfo.pollingInterval)

    XCTAssertEqual(poller.capturedInterval, codeInfo.pollingInterval)

    let request = try XCTUnwrap(graphRequestFactory.capturedRequests.first)
    XCTAssertEqual(
      request.graphPath,
      "device/login_status",
      "Should create a graph request with the expected graph path"
    )
    let parameters = request.parameters
    XCTAssertEqual(
      parameters["code"] as? String,
      sampleCodeInfo().identifier,
      "Should create a graph request with the expected code"
    )
    XCTAssertEqual(
      request.tokenString,
      tokenString
    )
  }

  func testStatusGraphRequestCompleteWithError() throws {
    manager.schedulePoll(interval: sampleCodeInfo().pollingInterval)

    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(nil, nil, NSError(domain: "foo", code: 0, userInfo: nil))
    XCTAssertEqual(delegate.capturedLoginManager, manager)
    XCTAssertNotNil(delegate.capturedError)
  }

  func testStatusGraphRequestCompleteWithNoToken() throws {
    manager.schedulePoll(interval: sampleCodeInfo().pollingInterval)
    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(nil, [], nil)
    XCTAssertEqual(delegate.capturedLoginManager, manager)
    XCTAssertNotNil(delegate.capturedError)
  }

  func testStatusGraphRequestCompleteWithAccessToken() throws {
    manager.schedulePoll(interval: sampleCodeInfo().pollingInterval)

    let result: [String: String] = [
      "access_token": SampleAccessTokens.validToken.tokenString,
      "expires_in": String(SampleAccessTokens.validToken.expirationDate.timeIntervalSinceNow),
      "data_access_expiration_time": String(
        SampleAccessTokens.validToken.dataAccessExpirationDate.timeIntervalSince1970
      ),
    ]
    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(nil, result, nil)

    let request = try XCTUnwrap(graphRequestFactory.capturedRequests.last)
    XCTAssertEqual(request.tokenString, SampleAccessTokens.validToken.tokenString)
    XCTAssertEqual(request.graphPath, "me")
  }

  func testSchedulePollAfterCancel() throws {
    manager.schedulePoll(interval: sampleCodeInfo().pollingInterval)

    manager.cancel()
    let result: [String: String] = [
      "access_token": SampleAccessTokens.validToken.tokenString,
    ]
    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(nil, result, nil)

    XCTAssertNil(delegate.capturedError)
    XCTAssertEqual(
      graphRequestFactory.capturedRequests.count,
      1,
      "Should not be making another graph request to fetch permissions"
    )
  }

  // MARK: _notifyToken

  func testNotifyTokenGraphRequestCreation() throws {
    manager.notifyDelegate(
      token: SampleAccessTokens.validToken.tokenString,
      expirationDate: SampleAccessTokens.validToken.expirationDate,
      dataAccessExpirationDate: SampleAccessTokens.validToken.dataAccessExpirationDate
    )

    let request = try XCTUnwrap(graphRequestFactory.capturedRequests.last)
    XCTAssertEqual(
      request.graphPath,
      "me",
      "Should create a graph request with the expected graph path"
    )
    let parameters = request.parameters
    XCTAssertEqual(
      parameters["fields"] as? String,
      "id,permissions",
      "Should create a graph request with the expected fields"
    )
    XCTAssertEqual(
      request.tokenString,
      SampleAccessTokens.validToken.tokenString,
      "Should create a graph request with the expected token string"
    )
  }

  func testNotifyTokenGraphRequestCompleteWithError() throws {
    manager.notifyDelegate(
      token: SampleAccessTokens.validToken.tokenString,
      expirationDate: SampleAccessTokens.validToken.expirationDate,
      dataAccessExpirationDate: SampleAccessTokens.validToken.dataAccessExpirationDate
    )
    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(nil, nil, NSError(domain: "foo", code: 0, userInfo: nil))

    XCTAssertEqual(delegate.capturedLoginManager, manager)
    XCTAssertNotNil(delegate.capturedError)
  }

  func testNotifyTokenGraphRequestCompleteWithNoUserID() throws {
    let result: [String: Any] = [
      "permissions": ["data": []],
    ]

    manager.notifyDelegate(
      token: SampleAccessTokens.validToken.tokenString,
      expirationDate: SampleAccessTokens.validToken.expirationDate,
      dataAccessExpirationDate: SampleAccessTokens.validToken.dataAccessExpirationDate
    )
    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(
      nil,
      result,
      nil
    )

    XCTAssertEqual(delegate.capturedLoginManager, manager)
    XCTAssertNotNil(delegate.capturedError)
  }

  func testNotifyTokenGraphRequestCompleteWithNoPermissions() throws {
    manager.notifyDelegate(
      token: SampleAccessTokens.validToken.tokenString,
      expirationDate: SampleAccessTokens.validToken.expirationDate,
      dataAccessExpirationDate: SampleAccessTokens.validToken.dataAccessExpirationDate
    )

    let result: [String: Any] = [
      "id": "123",
    ]
    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(
      nil,
      result,
      nil
    )

    XCTAssertEqual(delegate.capturedLoginManager, manager)
    XCTAssertNotNil(delegate.capturedError)
  }

  func testNotifyTokenGraphRequestCompleteWithPermissionsAndUserID() throws {
    manager.notifyDelegate(
      token: SampleAccessTokens.validToken.tokenString,
      expirationDate: SampleAccessTokens.validToken.expirationDate,
      dataAccessExpirationDate: SampleAccessTokens.validToken.dataAccessExpirationDate
    )

    let response: [String: Any] = [
      "id": "123",
      "permissions": [
        "marker": true,
      ],
    ]
    let granted = ["public_profile", "email"]
    let declined = ["user_friends"]
    let expired = ["user_birthday"]
    internalUtility.stubbedGrantedPermissions = granted
    internalUtility.stubbedDeclinedPermissions = declined
    internalUtility.stubbedExpiredPermissions = expired

    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(
      nil,
      response,
      nil
    )

    guard let loginResult = delegate.capturedResult else {
      XCTFail("Should receive a login result")
      return
    }
    XCTAssertFalse(loginResult.isCancelled)
    guard let token = loginResult.accessToken else {
      XCTFail("Should receive an AccessToken within login result")
      return
    }
    let marker = try XCTUnwrap(internalUtility.capturedExtractPermissionsResponse?["marker"] as? Bool)
    XCTAssertTrue(
      marker,
      "The response's permissions should be passed to the internal utility"
    )
    XCTAssertEqual(token.userID, "123")
    XCTAssertEqual(
      Set(token.permissions.map(\.name)),
      Set(granted)
    )
    XCTAssertEqual(
      Set(token.declinedPermissions.map(\.name)),
      Set(declined)
    )
    XCTAssertEqual(
      Set(token.expiredPermissions.map(\.name)),
      Set(expired)
    )
    XCTAssertEqual(token, AccessToken.current)
  }

  func testNotifyTokenWithNoTokenString() {
    manager.notifyDelegate(
      token: nil,
      expirationDate: nil,
      dataAccessExpirationDate: nil
    )

    XCTAssertEqual(graphRequestFactory.capturedRequests.count, 0)
    guard let loginResult = delegate.capturedResult else {
      XCTFail("Should receive an login result")
      return
    }
    XCTAssert(loginResult.isCancelled)
    XCTAssertNil(loginResult.accessToken)
  }

  // MARK: _processError

  func testProcessErrorAuthorizationPending() throws {
    manager.processError(
      NSError(
        domain: "foo",
        code: 0,
        userInfo: [GraphRequestErrorGraphErrorSubcodeKey: DeviceLoginError.authorizationPending.rawValue]
      )
    )

    let request = try XCTUnwrap(graphRequestFactory.capturedRequests.first)
    XCTAssertEqual(
      request.graphPath,
      "device/login_status",
      "Should create a graph request with the expected graph path"
    )
  }

  func testProcessErrorCodeExpired() throws {
    manager.processError(
      NSError(
        domain: "foo",
        code: 0,
        userInfo: [GraphRequestErrorGraphErrorSubcodeKey: DeviceLoginError.codeExpired.rawValue]
      )
    )

    XCTAssertEqual(delegate.capturedLoginManager, manager)
    XCTAssertNil(graphRequestFactory.capturedRequests.first)
    try assertCancelResult()
  }

  func testProcessErrorAuthorizationDeclined() throws {
    manager.processError(
      NSError(
        domain: "foo",
        code: 0,
        userInfo: [GraphRequestErrorGraphErrorSubcodeKey: DeviceLoginError.authorizationDeclined.rawValue]
      )
    )

    XCTAssertEqual(delegate.capturedLoginManager, manager)
    XCTAssertNil(graphRequestFactory.capturedRequests.first)
    try assertCancelResult()
  }

  func testProcessErrorExcessivePolling() throws {
    manager.processError(
      NSError(
        domain: "foo",
        code: 0,
        userInfo: [GraphRequestErrorGraphErrorSubcodeKey: DeviceLoginError.excessivePolling.rawValue]
      )
    )

    let request = try XCTUnwrap(graphRequestFactory.capturedRequests.first)
    XCTAssertEqual(
      request.graphPath,
      "device/login_status",
      "Should create a graph request with the expected graph path"
    )
  }

  // MARK: Helpers

  func sampleCodeInfo() -> DeviceLoginCodeInfo {
    DeviceLoginCodeInfo(
      identifier: "identifier",
      loginCode: "loginCode",
      verificationURL: URL(string: "https://www.facebook.com")!, // swiftlint:disable:this force_unwrapping
      expirationDate: Date.distantFuture,
      pollingInterval: 10
    )
  }

  func assertCancelResult(file: StaticString = #file, line: UInt = #line) throws {
    let loginResult = try XCTUnwrap(
      delegate.capturedResult,
      "Should receive an login result",
      file: file,
      line: line
    )

    XCTAssert(loginResult.isCancelled, file: file, line: line)
    XCTAssertNil(loginResult.accessToken, 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 FBSDKLoginKit

import XCTest

final class DevicePollerTests: XCTestCase {
  func testScheduleBlock() {
    let poller = DevicePoller()
    let expectation = self.expectation(description: name)

    func block() {
      expectation.fulfill()
    }

    poller.schedule(interval: 1, block: block)

    waitForExpectations(timeout: 2, handler: 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 FBSDKLoginKit

import XCTest

final class DeviceRequestsHelperTests: XCTestCase, NetServiceDelegate {

  func testGetDeviceInfo() throws {
    let currentDeviceInfo = try XCTUnwrap(getCurrentDeviceInfo())
    XCTAssertEqual(DeviceRequestsHelper.getDeviceInfo(), currentDeviceInfo, .hasEqualDeviceInfo)
  }

  func testStartAdvertisementService() {
    XCTAssertTrue(
      DeviceRequestsHelper.startAdvertisementService(loginCode: "123", delegate: self),
      .startsAdvertisementService
    )
    XCTAssertGreaterThan(DeviceRequestsHelper.mdnsAdvertisementServices.count, 0, .hasAddedService)
  }

  func testIsDelegateForAdvertisementService() throws {
    DeviceRequestsHelper.startAdvertisementService(loginCode: "123", delegate: self)
    let services = DeviceRequestsHelper.mdnsAdvertisementServices
    let service = try XCTUnwrap(services.object(forKey: self) as? NetService, .isDelegateForService)

    XCTAssertTrue(DeviceRequestsHelper.isDelegate(self, forAdvertisementService: service), .isDelegateForService)
  }

  func testCleanUpAdvertisementService() {
    DeviceRequestsHelper.startAdvertisementService(loginCode: "123", delegate: self)
    XCTAssertGreaterThan(DeviceRequestsHelper.mdnsAdvertisementServices.count, 0, .hasAddedService)
    DeviceRequestsHelper.cleanUpAdvertisementService(for: self)
    XCTAssertEqual(DeviceRequestsHelper.mdnsAdvertisementServices.count, 0, .hasRemoveService)
  }

  // MARK: - Helpers

  private func getCurrentDeviceInfo() -> String? {
    let model = UIDevice.current.model

    var systemInfo = utsname()
    uname(&systemInfo)
    let modelCode = withUnsafePointer(to: &systemInfo.machine) {
      $0.withMemoryRebound(to: CChar.self, capacity: 1) { ptr in String(validatingUTF8: ptr) }
    }

    guard let device = modelCode else { return nil }

    return """
      {"model":"\(model)","device":"\(device)"}
      """
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let hasEqualDeviceInfo = "Device info represents current device"
  static let startsAdvertisementService = "advertisement service starts with the login code provided"
  static let hasAddedService = "service is added to the collection of services"
  static let hasRemoveService = "service is removed from the collection of services"
  static let isDelegateForService = "service matches the service from the passed delegate"
}


/*
 * 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 FBSDKLoginKit

import XCTest

final class FBPermissionTests: XCTestCase {

  func testInvalidPermissions() {
    let rawPermissions = [
      "",
      "foo bar",
      "PUBLIC_PROFILE",
      "public profile",
      "public-profile",
      "123_abc",
    ]

    rawPermissions.forEach { rawPermission in
      let permission = FBPermission(string: rawPermission)
      XCTAssertNil(permission)
    }
  }

  func testValidPermissions() {
    let rawPermissions = [
      "email",
      "public_profile",
      "pages_manage_ads",
    ]

    rawPermissions.forEach { rawPermission in
      let permission = FBPermission(string: rawPermission)
      XCTAssertEqual(permission?.value, rawPermission)
    }
  }

  func testRawPermissionsFromPermissions() {
    let permissions = Set(
      [
        FBPermission(string: "email"),
        FBPermission(string: "public_profile"),
      ].compactMap { $0 }
    )
    let rawPermissions = FBPermission.rawPermissions(from: permissions)
    let expectedRawPermissions: Set = ["email", "public_profile"]
    XCTAssertEqual(rawPermissions, expectedRawPermissions)
  }

  func testPermissionsFromValidRawPermissions() {
    let rawPermissions: Set = ["email", "user_friends"]

    let permissions = FBPermission.permissions(fromRawPermissions: rawPermissions)
    let expectedPermissions = Set(
      [
        FBPermission(string: "email"),
        FBPermission(string: "user_friends"),
      ].compactMap { $0 }
    )
    XCTAssertEqual(permissions, expectedPermissions)
  }

  func testPermissionsFromInvalidRawPermissions() {
    let rawPermissions: Set = ["email", ""]

    let permissions = FBPermission.permissions(fromRawPermissions: rawPermissions)
    XCTAssertNil(permissions)
  }

  func testDescription() throws {
    let permission = try XCTUnwrap(FBPermission(string: "test_permission"))
    XCTAssertEqual(permission.description, permission.value, "A permission's description should be equal to its value")
  }

  func testEquality() throws {
    let permission = try XCTUnwrap(FBPermission(string: "test_permission"))
    XCTAssertEqual(permission, permission, "A permission should be equal to itself")

    let permission2 = try XCTUnwrap(FBPermission(string: "test_permission"))
    XCTAssertEqual(permission, permission2, "Permissions with equal string values should be equal")

    let string = permission.value as NSString
    XCTAssertNotEqual(permission, string, "Permissions can only be equal to other Permissions")
  }

  func testInequality() throws {
    let permission = try XCTUnwrap(FBPermission(string: "test_permission"))
    let permission2 = try XCTUnwrap(FBPermission(string: "different_permission"))
    XCTAssertNotEqual(permission, permission2, "Permissions with unequal string values should be unequal")
  }
}


/*
 * 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 FBSDKLoginKit

import TestTools
import XCTest

final class LoginButtonTests: XCTestCase {

  let validNonce = "abc123"
  // swiftlint:disable implicitly_unwrapped_optional
  var loginProvider: TestLoginProvider!
  var stringProvider: TestUserInterfaceStringProvider!
  var elementProvider: TestUserInterfaceElementProvider!
  var graphRequestFactory: TestGraphRequestFactory!
  var loginButton: FBLoginButton!
  var sampleToken: AuthenticationToken!
  var delegate: TestLoginButtonDelegate!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    loginProvider = TestLoginProvider()
    stringProvider = TestUserInterfaceStringProvider()
    elementProvider = TestUserInterfaceElementProvider()
    graphRequestFactory = TestGraphRequestFactory()
    sampleToken = AuthenticationToken(tokenString: "abc", nonce: "123")
    delegate = TestLoginButtonDelegate()
    loginButton = FBLoginButton(
      elementProvider: elementProvider,
      stringProvider: stringProvider,
      loginProvider: loginProvider,
      graphRequestFactory: graphRequestFactory
    )
    loginButton.delegate = delegate
    AccessToken.setCurrent(nil, shouldDispatchNotif: false)
    AuthenticationToken.current = nil
    Profile.current = nil
  }

  override func tearDown() {
    loginProvider = nil
    stringProvider = nil
    elementProvider = nil
    graphRequestFactory = nil
    sampleToken = nil
    loginButton = nil
    delegate = nil
    super.tearDown()
  }

  // MARK: - Dependencies

  func testDefaultDependencies() {
    let loginButton = FBLoginButton()
    XCTAssertIdentical(
      loginButton.elementProvider as AnyObject,
      InternalUtility.shared,
      .hasDefaultElementProvider
    )

    XCTAssertIdentical(
      loginButton.stringProvider as AnyObject,
      InternalUtility.shared,
      .hasDefaultStringProvider
    )

    XCTAssertTrue(
      loginButton.loginProvider is LoginManager,
      .hasDefaultLoginProvider
    )

    XCTAssertTrue(
      loginButton.graphRequestFactory is GraphRequestFactory,
      .hasDefaultGraphRequestFactory
    )
  }

  func testCustomDependencies() {
    XCTAssertIdentical(
      loginButton.elementProvider as AnyObject,
      elementProvider,
      .hasCustomElementProvider
    )

    XCTAssertIdentical(
      loginButton.stringProvider as AnyObject,
      stringProvider,
      .hasCustomStringProvider
    )

    XCTAssertIdentical(
      loginButton.loginProvider as AnyObject,
      loginProvider,
      .hasCustomLoginProvider
    )

    XCTAssertIdentical(
      loginButton.graphRequestFactory,
      graphRequestFactory,
      .hasCustomLoginProvider
    )
  }

  // MARK: - Nonce

  func testDefaultNonce() {
    XCTAssertNil(FBLoginButton().nonce, "Should not have a default nonce")
  }

  func testSettingInvalidNonce() {
    loginButton.nonce = "   "

    XCTAssertNil(
      loginButton.nonce,
      "Should not set an invalid nonce"
    )
  }

  func testSettingValidNonce() {
    loginButton.nonce = validNonce

    XCTAssertEqual(
      loginButton.nonce,
      validNonce,
      "Should set a valid nonce"
    )
  }

  func testLoginConfigurationWithoutNonce() {
    XCTAssertNotNil(
      loginButton.makeLoginConfiguration(),
      "Should be able to create a login configuration without a provided nonce"
    )
  }

  func testLoginConfigurationWithInvalidNonce() {
    loginButton.nonce = "   "

    XCTAssertNotNil(
      loginButton.makeLoginConfiguration(),
      "Should not create a login configuration with an invalid nonce"
    )
  }

  func testLoginConfigurationWithValidNonce() {
    loginButton.nonce = validNonce

    XCTAssertEqual(
      loginButton.makeLoginConfiguration()?.nonce,
      validNonce,
      "Should create a login configuration with valid nonce"
    )
  }

  // MARK: - Initial Content Update

  func testInitialContentUpdateWithInactiveAccessTokenWithProfile() {
    AccessToken.setCurrent(nil, shouldDispatchNotif: false)
    let profile = SampleUserProfiles.createValid()
    Profile.current = profile

    loginButton.initializeContent()

    XCTAssertTrue(
      loginButton.isSelected,
      .selectsButtonWithProfile
    )

    XCTAssertEqual(
      loginButton.userName,
      profile.name,
      .setsUserNameWithProfile
    )

    XCTAssertEqual(
      loginButton.userID,
      profile.userID,
      .setsUserIDWithProfile
    )
  }

  func testInitialContentUpdateWithActiveAccessTokenWithProfile() throws {

    AccessToken.setCurrent(SampleAccessTokens.validToken, shouldDispatchNotif: false)
    let profile = SampleUserProfiles.createValid()
    Profile.current = profile

    loginButton.initializeContent()

    let request = try XCTUnwrap(
      graphRequestFactory.capturedRequests.first,
      .createsRequest
    )

    let result = [
      "id": SampleAccessTokens.validToken.userID,
      "name": SampleUserProfiles.defaultName,
    ]

    let completion = try XCTUnwrap(
      graphRequestFactory.capturedRequests.first?.capturedCompletionHandler,
      .createsRequest
    )

    completion(
      nil,
      result,
      nil
    )

    XCTAssertTrue(
      loginButton.isSelected,
      .selectsButtonWithAccessToken
    )

    XCTAssertEqual(request.graphPath, "me", .createsRequest)

    XCTAssertEqual(
      request.parameters["fields"] as? String,
      "id,name",
      .createsRequestWithParameters
    )

    XCTAssertEqual(
      loginButton.userName,
      result["name"],
      .setsUserNameWithAccessToken
    )

    XCTAssertEqual(
      loginButton.userID,
      result["id"],
      .setsUserIDWithAccessToken
    )
  }

  func testInitialContentUpdateWithoutAccessTokenWithoutProfile() {
    AccessToken.setCurrent(nil, shouldDispatchNotif: false)
    Profile.current = nil

    loginButton.initializeContent()

    XCTAssertFalse(
      loginButton.isSelected,
      .doesNotSelectsButtonWithoutAccessToken
    )
  }

  // MARK: - Determining Authentication Status

  func testDeterminingAuthenticationWithAccessTokenWithoutAuthToken() {
    AccessToken.setCurrent(SampleAccessTokens.validToken, shouldDispatchNotif: false)

    XCTAssertTrue(
      loginButton.isAuthenticated,
      "Should consider a user authenticated if they have a current access token"
    )
  }

  func testDeterminingAuthenticationWithoutAccessTokenWithAuthToken() {
    AuthenticationToken.current = sampleToken

    XCTAssertTrue(
      loginButton.isAuthenticated,
      "Should consider a user authenticated if they have a current authentication token"
    )
  }

  // MARK: - Handling Notifications

  func testReceivingAccessTokenNotificationWithDidChangeUserIdKey() throws {
    let notification = Notification(
      name: .AccessTokenDidChange,
      object: nil,
      userInfo: [AccessTokenDidChangeUserIDKey: "foo"]
    )

    AccessToken.setCurrent(SampleAccessTokens.validToken, shouldDispatchNotif: false)
    loginButton.accessTokenDidChange(notification)

    let request = try XCTUnwrap(
      graphRequestFactory.capturedRequests.first,
      .createsRequest
    )

    let result = [
      "id": SampleAccessTokens.validToken.userID,
      "name": SampleUserProfiles.defaultName,
    ]

    let completion = try XCTUnwrap(
      graphRequestFactory.capturedRequests.first?.capturedCompletionHandler,
      .createsRequest
    )

    completion(
      nil,
      result,
      nil
    )

    XCTAssertTrue(
      loginButton.isSelected,
      .selectsButtonNotificationUserIdKey
    )

    XCTAssertEqual(request.graphPath, "me", .createsRequest)

    XCTAssertEqual(
      request.parameters["fields"] as? String,
      "id,name",
      .createsRequestWithParameters
    )

    XCTAssertEqual(
      loginButton.userName,
      result["name"],
      .setsUserNameWithNotificationUserIdKey
    )

    XCTAssertEqual(
      loginButton.userID,
      result["id"],
      .setsUserIDWithNotificationUserIdKey
    )
  }

  func testReceivingAccessTokenNotificationWithTokenDidExpireKey() {
    let notification = Notification(
      name: .AccessTokenDidChange,
      object: nil,
      userInfo: [AccessTokenDidExpireKey: "foo"]
    )

    loginButton.accessTokenDidChange(notification)

    XCTAssertFalse(
      loginButton.isSelected,
      .doesNotSelectsButtonWithExpiredKey
    )
  }

  func testReceivingAccessTokenNotificationWithoutRelevantUserInfo() {
    let notification = Notification(
      name: .AccessTokenDidChange,
      object: nil,
      userInfo: nil
    )

    loginButton.isSelected = true
    let profile = SampleUserProfiles.createValid(userID: SampleAccessTokens.validToken.userID)

    loginButton.updateContentForUser(profile)
    loginButton.accessTokenDidChange(notification)

    XCTAssertTrue(
      loginButton.isSelected,
      .doesNotChangeButtonStateWithoutUserInfo
    )

    XCTAssertEqual(
      loginButton.userName,
      profile.name,
      .doesNotChangeUserNameWithoutUserInfo
    )

    XCTAssertEqual(
      loginButton.userID,
      profile.userID,
      .doesNotChangeUserIDWithoutUserInfo
    )

    loginButton.isSelected = false
    loginButton.accessTokenDidChange(notification)

    XCTAssertFalse(
      loginButton.isSelected,
      .doesNotChangeButtonStateWithoutUserInfo
    )
  }

  func testReceivingProfileNotificationWithNoProfile() {
    let notification = Notification(
      name: .ProfileDidChange,
      object: nil,
      userInfo: nil
    )
    let oldUserName = loginButton.userName
    let oldUserId = loginButton.userID

    loginButton.profileDidChange(notification)

    XCTAssertFalse(
      loginButton.isSelected,
      .doesNotSelectsButtonWithNoProfileNotification
    )

    XCTAssertEqual(
      loginButton.userName,
      oldUserName,
      .doesNotChangeUserNameWithNoProfileNotification
    )

    XCTAssertEqual(
      loginButton.userID,
      oldUserId,
      .doesNotChangeUserIDWithNoProfileNotification
    )
  }

  func testReceivingProfileNotificationWithProfile() {
    let notification = Notification(
      name: .ProfileDidChange,
      object: nil,
      userInfo: nil
    )

    let profile = SampleUserProfiles.createValid()
    Profile.current = profile

    loginButton.profileDidChange(notification)

    XCTAssertTrue(
      loginButton.isSelected,
      .selectsButtonWithProfileNotification
    )

    XCTAssertEqual(
      loginButton.userName,
      profile.name,
      .setsUserNameWithProfileNotification
    )

    XCTAssertEqual(
      loginButton.userID,
      profile.userID,
      .setsUserIDWithProfileNotification
    )
  }

  func testReceivingProfileNotificationWithSameProfile() {
    let notification = Notification(
      name: .ProfileDidChange,
      object: nil,
      userInfo: nil
    )

    let profile = SampleUserProfiles.createValid()
    Profile.current = profile
    loginButton.updateContentForUser(profile)
    loginButton.profileDidChange(notification)

    XCTAssertTrue(
      loginButton.isSelected,
      .selectsButtonWithProfileNotification
    )

    XCTAssertEqual(
      loginButton.userName,
      profile.name,
      .doesNotChangeUserNameWithSameProfile
    )

    XCTAssertEqual(
      loginButton.userID,
      profile.userID,
      .doesNotChangeUserIdWithSameProfile
    )
  }

  // MARK: - Updating Content

  func testUpdatingContentWithMissingProfile() {
    loginButton.updateContentForUser(nil)

    XCTAssertFalse(
      loginButton.isSelected,
      "Should not be selected if there is not a profile"
    )
    XCTAssertNil(loginButton.userName)
    XCTAssertNil(loginButton.userID)
  }

  func testUpdatingContentWithProfile() {
    let profile = SampleUserProfiles.createValid()
    loginButton.updateContentForUser(profile)

    XCTAssertTrue(
      loginButton.isSelected,
      "Should be selected if there is a valid profile"
    )
    XCTAssertEqual(loginButton.userName, profile.name)
    XCTAssertEqual(loginButton.userID, profile.userID)
  }

  func testUpdatingContentForProfileWithNewId() {
    let profile = SampleUserProfiles.createValid(userID: "345")
    loginButton.updateContentForUser(SampleUserProfiles.createValid())
    loginButton.updateContentForUser(profile)

    XCTAssertTrue(
      loginButton.isSelected,
      .selectsButtonForProfileWithNewId
    )

    XCTAssertEqual(
      loginButton.userName,
      profile.name,
      .updatesUserNameForProfileWithNewId
    )

    XCTAssertEqual(
      loginButton.userID,
      profile.userID,
      .updatesUserIdForProfileWithNewId
    )
  }

  func testUpdatingContentForProfileWithNewName() {
    let profile = SampleUserProfiles.createValid(userID: "345")

    loginButton.updateContentForUser(SampleUserProfiles.createValid(userID: "345", name: "Paul Smith"))

    loginButton.updateContentForUser(profile)

    XCTAssertTrue(
      loginButton.isSelected,
      .selectsButtonForProfileWithNewName
    )

    XCTAssertEqual(
      loginButton.userName,
      profile.name,
      .updatesUserNameForProfileWithNewName
    )
    XCTAssertEqual(
      loginButton.userID,
      profile.userID,
      .updatesUserIdForProfileWithNewName
    )
  }

  func testUpdatingContentWithValidAccessToken() throws {
    AccessToken.setCurrent(SampleAccessTokens.validToken, shouldDispatchNotif: false)

    loginButton.updateContentForAccessToken()

    let request = try XCTUnwrap(
      graphRequestFactory.capturedRequests.first,
      .createsRequest
    )

    let result = [
      "id": SampleAccessTokens.validToken.userID,
      "name": SampleUserProfiles.defaultName,
    ]

    let completion = try XCTUnwrap(
      graphRequestFactory.capturedRequests.first?.capturedCompletionHandler,
      .createsRequest
    )

    completion(
      nil,
      result,
      nil
    )

    XCTAssertTrue(
      loginButton.isSelected,
      .selectsButtonWithAccessTokenUpdate
    )

    XCTAssertEqual(request.graphPath, "me", .createsRequest)
    XCTAssertEqual(
      request.parameters["fields"] as? String,
      "id,name",
      .createsRequestWithParameters
    )

    XCTAssertEqual(
      loginButton.userName,
      result["name"],
      .updatesUserNameWithAccessToken
    )

    XCTAssertEqual(
      loginButton.userID,
      result["id"],
      .updatesUserIDWithAccessToken
    )
  }

  func testUpdatingContentWithInvalidAccessToken() {
    AccessToken.setCurrent(SampleAccessTokens.expiredToken, shouldDispatchNotif: false)

    loginButton.updateContentForAccessToken()

    XCTAssertFalse(
      loginButton.isSelected,
      .doesNotSelectButtonWithInvalidAccessToken
    )
  }

  func testUpdatingContentWithIdenticalAccessToken() {
    // Make sure the username and id properties on button are set to the same values
    // as the access token. This is an easy way to do with without having to stub
    // a network call
    let profile = SampleUserProfiles.createValid(userID: SampleAccessTokens.validToken.userID)
    loginButton.updateContentForUser(profile)

    AccessToken.setCurrent(SampleAccessTokens.validToken, shouldDispatchNotif: false)

    loginButton.updateContentForAccessToken()

    XCTAssertTrue(
      loginButton.isSelected,
      .selectsButtonWithIdenticalAccessToken
    )

    XCTAssertEqual(
      loginButton.userName,
      profile.name,
      .doesNotChangeUserNameWithIdenticalAccessToken
    )

    XCTAssertEqual(
      loginButton.userID,
      profile.userID,
      .doesNotChangeUserIdWithIdenticalAccessToken
    )
  }

  // MARK: - Fetching Content

  func testFetchContentGraphRequestCreation() throws {
    loginButton.fetchAndSetContent()

    let request = try XCTUnwrap(graphRequestFactory.capturedRequests.first)
    XCTAssertEqual(request.graphPath, "me")
    XCTAssertEqual(request.parameters["fields"] as? String, "id,name")
  }

  func testFetchContentCompleteWithError() throws {
    AccessToken.current = SampleAccessTokens.validToken
    loginButton.fetchAndSetContent()

    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(
      nil,
      [
        "id": SampleAccessTokens.validToken.userID,
        "name": SampleUserProfiles.defaultName,
      ],
      NSError(domain: "foo", code: 0, userInfo: nil)
    )

    XCTAssertNil(loginButton.userID)
    XCTAssertNil(loginButton.userName)
  }

  func testFetchContentCompleteWithNilResponse() throws {
    loginButton.fetchAndSetContent()

    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(nil, nil, nil)

    XCTAssertNil(loginButton.userID)
    XCTAssertNil(loginButton.userName)
  }

  func testFetchContentCompleteWithEmptyResponse() throws {
    loginButton.fetchAndSetContent()

    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(nil, [], nil)

    XCTAssertNil(loginButton.userID)
    XCTAssertNil(loginButton.userName)
  }

  func testFetchContentCompleteWithMatchingUID() throws {
    AccessToken.current = SampleAccessTokens.validToken
    loginButton.fetchAndSetContent()

    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(
      nil,
      [
        "id": SampleAccessTokens.validToken.userID,
        "name": SampleUserProfiles.defaultName,
      ],
      nil
    )

    XCTAssertEqual(loginButton.userID, SampleAccessTokens.validToken.userID)
    XCTAssertEqual(loginButton.userName, SampleUserProfiles.defaultName)
  }

  func testFetchContentCompleteWithNonmatchingUID() throws {
    AccessToken.current = SampleAccessTokens.validToken
    loginButton.fetchAndSetContent()

    let completion = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completion(
      nil,
      [
        "id": name,
        "name": SampleUserProfiles.defaultName,
      ],
      nil
    )

    XCTAssertNil(loginButton.userID)
    XCTAssertNil(loginButton.userName)
  }

  // MARK: - Setting Messenger Page ID

  func testDefaultMessengerPageId() {
    XCTAssertNil(FBLoginButton().messengerPageId, "Should not have a default Messenger Page ID")
  }

  func testSettingMessengerPageId() {
    loginButton.messengerPageId = "1234"

    XCTAssertEqual(
      loginButton.messengerPageId,
      "1234",
      "Should set a valid Messenger Page ID"
    )
  }

  func testLoginConfigurationWithMessengerPageId() {
    loginButton.messengerPageId = "1234"

    XCTAssertNotNil(
      loginButton.makeLoginConfiguration(),
      "Should be able to create a configuration with Messenger Page Id"
    )
  }

  // MARK: - Setting Auth Type

  func testDefaultAuthType() {
    XCTAssertEqual(
      FBLoginButton().authType,
      LoginAuthType.rerequest,
      "Default auth_type should be rerequest"
    )
  }

  func testSettingAuthType() {
    loginButton.authType = .reauthorize

    XCTAssertEqual(
      loginButton.authType,
      .reauthorize,
      "Should set a valid auth type"
    )
  }

  func testLoginConfigurationWithAuthType() {
    loginButton.authType = .reauthorize

    XCTAssertNotNil(
      loginButton.makeLoginConfiguration(),
      "Should be able to create a configuration with auth type"
    )
    XCTAssertEqual(loginButton.makeLoginConfiguration()?.authType, .reauthorize)
  }

  func testLoginConfigurationWithNilAuthType() {
    loginButton.authType = nil

    XCTAssertNotNil(
      loginButton.makeLoginConfiguration(),
      "Should be able to create a configuration with nil auth type"
    )
    XCTAssertNil(loginButton.makeLoginConfiguration()?.authType)
  }

  func testLoginConfigurationWithNoAuthType() {
    XCTAssertNotNil(
      loginButton.makeLoginConfiguration(),
      "Should be able to create a configuration with default auth type"
    )
    XCTAssertEqual(loginButton.makeLoginConfiguration()?.authType, .rerequest)
  }

  // MARK: default audience

  func testDefaultAudience() {
    XCTAssertEqual(
      loginButton.defaultAudience,
      .friends,
      "Should have a default audience of friends"
    )
  }

  func testSettingDefaultAudience() {
    loginButton.defaultAudience = .onlyMe
    XCTAssertEqual(
      loginButton.defaultAudience,
      .onlyMe,
      "Should set the default audience to only me"
    )
    XCTAssertEqual(
      loginProvider.defaultAudience,
      .onlyMe,
      "Should set the default audience of the underlying login provider to only me"
    )
  }

  // MARK: login tracking

  func testDefaultLoginTracking() {
    XCTAssertEqual(
      loginButton.loginTracking,
      .enabled,
      "Should set the default login tracking to be enabled"
    )
  }

  func testSettingLoginTracking() {
    loginButton.loginTracking = .limited
    XCTAssertEqual(
      loginButton.loginTracking,
      .limited,
      "Should set the login tracking to limited"
    )
    XCTAssertEqual(
      loginButton.makeLoginConfiguration()?.tracking,
      .limited,
      "Should created a login configuration with the expected tracking"
    )
  }

  // MARK: Code Verifier

  func testSettingCodeVerifier() {
    let codeVerifier = CodeVerifier()
    loginButton.codeVerifier = codeVerifier
    XCTAssertEqual(
      loginButton.codeVerifier.value,
      codeVerifier.value,
      "Should set the code verifier to the expected value"
    )
    XCTAssertEqual(
      loginButton.makeLoginConfiguration()?.codeVerifier.value,
      codeVerifier.value,
      "Should create a login configuration with the expected code verifier"
    )
  }

  func testDefaultCodeVerifier() {
    XCTAssertNotNil(
      loginButton.codeVerifier,
      "Default code verifier should not be nil"
    )
    XCTAssertNotNil(
      loginButton.makeLoginConfiguration()?.codeVerifier,
      "Should create a login configuration with the default code verifier"
    )
  }

  // MARK: Button Press

  func testButtonPressNotAuthenticatedLoginNotAllowed() throws {
    delegate.shouldLogin = false

    loginButton.buttonPressed(self)

    XCTAssert(delegate.willLogin)

    XCTAssertNil(loginProvider.capturedCompletion)
    XCTAssertNil(loginProvider.capturedConfiguration)
  }

  func testButtonPressNotAuthenticatedLoginAllowed() throws {
    loginButton.buttonPressed(self)

    XCTAssert(delegate.willLogin)

    XCTAssertNotNil(loginProvider.capturedConfiguration)
    let completion = try XCTUnwrap(loginProvider.capturedCompletion)
    let granted = Set(SampleAccessTokens.validToken.permissions.map(\.name))
    let declined = Set(SampleAccessTokens.validToken.declinedPermissions.map(\.name))
    let result = LoginManagerLoginResult(
      token: SampleAccessTokens.validToken,
      authenticationToken: nil,
      isCancelled: false,
      grantedPermissions: granted,
      declinedPermissions: declined
    )
    completion(LoginResult(result: result, error: nil))

    assertEqualLoginManagerLoginResults(delegate.capturedResult, result, .delegateIsPassedResult)
  }

  func testButtonPressAuthenticated() throws {
    AuthenticationToken.current = sampleToken
    let rootVC = UIViewController()
    elementProvider.stubbedTopMostViewController = rootVC

    let window = UIWindow()
    window.rootViewController = rootVC
    window.makeKeyAndVisible()
    rootVC.view.addSubview(loginButton)

    loginButton.buttonPressed(self)

    let presentedVC = try XCTUnwrap(window.rootViewController?.presentedViewController, .showsAlertViewController)

    XCTAssertTrue(
      presentedVC is UIAlertController,
      .showsAlertViewController
    )
  }

  func testLogout() {
    loginButton.logout()
    XCTAssert(loginProvider.didLogout)
    XCTAssert(delegate.didLoggedOut)
  }

  // MARK: - Tooltip

  func testShowTooltipIfNeeded() {
    let button = FBLoginButton()
    button.tooltipBehavior = .forceDisplay

    let window = UIWindow()
    let rootVC = UIViewController()
    window.rootViewController = rootVC
    window.makeKeyAndVisible()
    rootVC.view.addSubview(button)

    if !rootVC.view.subviews.contains(where: { $0 is FBLoginTooltipView }) {
      XCTFail(.showsTooltip)
    }
  }

  func testShowTooltipIfNeededWithAuthenticated() {
    AuthenticationToken.current = sampleToken

    let button = FBLoginButton()
    button.tooltipBehavior = .forceDisplay

    let window = UIWindow()
    let rootVC = UIViewController()
    window.rootViewController = rootVC
    window.makeKeyAndVisible()
    rootVC.view.addSubview(button)

    if rootVC.view.subviews.contains(where: { $0 is FBLoginTooltipView }) {
      XCTFail(.doesNotShowTooltipForAuthenticated)
    }
  }

  func testShowTooltipIfNeededWithDisabledBehavior() {
    let button = FBLoginButton()
    button.tooltipBehavior = .disable

    let window = UIWindow()
    let rootVC = UIViewController()
    window.rootViewController = rootVC
    window.makeKeyAndVisible()
    rootVC.view.addSubview(button)

    if rootVC.view.subviews.contains(where: { $0 is FBLoginTooltipView }) {
      XCTFail(.doesNotShowTooltipIfDisabled)
    }
  }

  // MARK: - Layout

  func testImageRectForContentRect() {

    let contentRect = CGRect(x: 0, y: 0, width: 100, height: 100)
    let imageRect = loginButton.imageRect(forContentRect: contentRect)
    let expectedImageRect = CGRect(x: 6.0, y: 42.0, width: 16.0, height: 16.0)

    XCTAssertEqual(
      imageRect,
      expectedImageRect,
      .hasCustomImageFrame
    )
  }

  func testTitleRectForContentRect() {

    let contentRect = CGRect(x: 0, y: 0, width: 100, height: 100)
    loginButton.frame = contentRect
    let titleRect = loginButton.titleRect(forContentRect: contentRect)
    let expectedTitleRect = CGRect(x: 30.0, y: 0.0, width: 62.0, height: 100.0)

    XCTAssertEqual(
      titleRect,
      expectedTitleRect,
      .hasCustomTitleFrame
    )
  }

  // MARK: - Helpers

  private func assertEqualLoginManagerLoginResults(
    _ result1: LoginManagerLoginResult?,
    _ result2: LoginManagerLoginResult?,
    _ message: String,
    file: StaticString = #file,
    line: UInt = #line
  ) {
    XCTAssertEqual(result1?.token, result2?.token, message, file: file, line: line)
    XCTAssertEqual(result1?.authenticationToken, result2?.authenticationToken, message, file: file, line: line)
    XCTAssertEqual(result1?.isCancelled, result2?.isCancelled, message, file: file, line: line)
    XCTAssertEqual(result1?.grantedPermissions, result2?.grantedPermissions, message, file: file, line: line)
    XCTAssertEqual(result1?.declinedPermissions, result2?.declinedPermissions, message, file: file, line: line)
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let hasCustomImageFrame = """
    The image frame for a button should be vertically centered and \
    given left padding of 6 points and sized like the logo
    """
  static let hasCustomTitleFrame = """
    The title frame for a button should have a 8 points horizontal padding and no vertical padding. \
    Size should be as big as content frame minus paddings and image frame
    """
  static let showsTooltip = "Shows a tooltip if user is not authenticated or tooltip is not disabled"
  static let doesNotShowTooltipForAuthenticated = "Does not show a tooltip if user is authenticated"
  static let doesNotShowTooltipIfDisabled = "Does not show a tooltip if tooltip has been disabled"
  static let showsAlertViewController = """
    Shows an alert view controller when the login button is pressed, if the user is already authenticated
    """
  static let hasDefaultElementProvider = """
    Should have a default element provider dependency
    """
  static let hasDefaultStringProvider = """
    Should have a default string provider dependency
    """
  static let hasDefaultLoginProvider = """
    Should have a default login provider dependency
    """
  static let hasDefaultGraphRequestFactory = """
    Should have a default graph request factory dependency
    """
  static let hasCustomElementProvider = """
    Should have a custom element provider dependency
    """
  static let hasCustomStringProvider = """
    Should have a custom string provider dependency
    """
  static let hasCustomLoginProvider = """
    Should have a custom login provider dependency
    """
  static let hasCustomGraphRequestFactory = """
    Should have a custom graph request factory dependency
    """
  static let selectsButtonWithProfile = """
    Login button should be selected when initializing content \
    and using a profile
    """
  static let setsUserNameWithProfile = """
    User name should be set when initializing content \
    and using a profile
    """
  static let setsUserIDWithProfile = """
    User id should be set when initializing content \
    and using a profile
    """
  static let selectsButtonWithAccessToken = """
    Login button should be selected when initializing content \
    and using an access token
    """
  static let setsUserNameWithAccessToken = """
    User name should be set when initializing content \
    and using an access token
    """
  static let setsUserIDWithAccessToken = """
    User id should be set when initializing content \
    and using an access token
    """
  static let createsRequest = """
    Should create a request with the expected path
    """
  static let createsRequestWithParameters = """
    Should create a request with the expected parameters
    """
  static let doesNotSelectsButtonWithoutAccessToken = """
    Login button should not be selected when there is no access token \
    or current profile
    """
  static let selectsButtonNotificationUserIdKey = """
    Login button should be selected when we receive an access token \
    change notification with a user id key
    """
  static let setsUserNameWithNotificationUserIdKey = """
    User name should be equal to user name we received in the response \
    after we receive an access token change notification with \
    a user id key
    """
  static let setsUserIDWithNotificationUserIdKey = """
    User id should be equal to user id we received in the response \
    after we receive an access token change notification with \
    a user id key
    """
  static let doesNotSelectsButtonWithExpiredKey = """
    Login button should not be selected after we receive an \
    access token change notification with an expired key
    """
  static let doesNotChangeButtonStateWithoutUserInfo = """
    Login button should not change current state after we \
    receive an access token change notification with no user info
    """
  static let doesNotChangeUserNameWithoutUserInfo = """
    User name should not change after we receive an \
    access token change notification with no user info
    """
  static let doesNotChangeUserIDWithoutUserInfo = """
    User id should not change after we receive an \
    access token change notification with no user info
    """
  static let selectsButtonWithProfileNotification = """
    Login button should be selected after we receive a \
    profile change notification with a valid profile
    """
  static let setsUserNameWithProfileNotification = """
    User name should be changed after we receive a \
    profile change notification with a different profile
    """
  static let setsUserIDWithProfileNotification = """
    User id should be changed after we receive a \
    profile change notification with a different profile
    """
  static let doesNotSelectsButtonWithNoProfileNotification = """
    Login button should not be selected after we receive a \
    profile change notification and there is no profile
    """
  static let doesNotChangeUserNameWithNoProfileNotification = """
    User name should not change after we receive a \
    profile change notification and there is no profile
    """
  static let doesNotChangeUserIDWithNoProfileNotification = """
    User id should not change after we receive a \
    profile change notification and there is no profile
    """
  static let doesNotChangeUserNameWithSameProfile = """
    User name should change after we receive a \
    profile change notification with same profile
    """
  static let doesNotChangeUserIdWithSameProfile = """
    User id should change after we receive a \
    profile change notification with same profile
    """
  static let selectsButtonWithAccessTokenUpdate = """
    Login button should be selected when updating content \
    and using an access token
    """
  static let updatesUserNameWithAccessToken = """
    User name should be set when updating content \
    and using an access token
    """
  static let updatesUserIDWithAccessToken = """
    User id should be set when updating content \
    and using an access token
    """
  static let doesNotSelectButtonWithInvalidAccessToken = """
    Login button should not be selected when updating content \
    and using an invalid access token
    """
  static let selectsButtonWithIdenticalAccessToken = """
    Login button should be selected when attempting to update content \
    with an identical access token
    """
  static let doesNotChangeUserNameWithIdenticalAccessToken = """
    User name should not change when attempting to update content \
    with an identical access token
    """
  static let doesNotChangeUserIdWithIdenticalAccessToken = """
    User id should not change when attempting to update content \
    with an identical access token
    """
  static let selectsButtonForProfileWithNewId = """
    Login button should be selected when attempting to update content \
    with a profile with a new user id
    """
  static let updatesUserNameForProfileWithNewId = """
    User name should change when attempting to update content \
    with a profile with a new user id
    """
  static let updatesUserIdForProfileWithNewId = """
    User id should change when attempting to update content \
    with a profile with a new user id
    """
  static let selectsButtonForProfileWithNewName = """
    Login button should be selected when attempting to update content \
    with a profile with an identical user id but has a new name
    """
  static let updatesUserNameForProfileWithNewName = """
    User name should change when attempting to update content \
    with a profile with an identical user id has a new name
    """
  static let updatesUserIdForProfileWithNewName = """
    User id should change when attempting to update content \
    with a profile with an identical user id has a new name
    """
  static let delegateIsPassedResult = "The delegate is passed the login result"
}


/*
 * 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 FBSDKLoginKit

import TestTools
import XCTest

final class LoginCompleterFactoryTests: XCTestCase {

  func testCreatingCompleterWithDependencies() {
    let completer = LoginCompleterFactory().createLoginCompleter(
      urlParameters: [:],
      appID: name
    )
    XCTAssertTrue(
      completer is LoginURLCompleter,
      "Should create the expected concrete type of login completer"
    )
  }
}


/*
 * 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 FBSDKLoginKit

import XCTest

final class LoginConfigurationTests: XCTestCase {

  func testDefaults() {
    guard let configuration = LoginConfiguration() else {
      return XCTFail("Should be able to create a configuration with default arguments")
    }

    XCTAssertEqual(
      configuration.requestedPermissions,
      [],
      "A configuration should be created with default requested permissions"
    )
    XCTAssertEqual(
      configuration.tracking,
      .enabled,
      "Tracking should default to enabled when unspecified"
    )
    XCTAssertNotNil(
      configuration.nonce,
      "A configuration should be created with a default nonce"
    )
    XCTAssertNil(
      configuration.messengerPageId,
      "Messenger Page Id should default to nil when unspecified"
    )
    XCTAssertEqual(
      configuration.authType,
      .rerequest,
      "Auth Type should default to rerequest when unspecified"
    )
    XCTAssertNotNil(
      configuration.codeVerifier,
      "A configuration should be created with a default code verifier"
    )
  }

  func testCreatingWithNonceString() {
    let nonce = "12345"
    let configuration = LoginConfiguration(nonce: nonce)
    XCTAssertEqual(
      configuration?.nonce,
      nonce,
      "Should create a configuration with the provided nonce string"
    )
  }

  func testCreatingWithInvalidNonce() {
    XCTAssertNil(
      LoginConfiguration(nonce: " "),
      "Should not create a login configuration with an invalid nonce"
    )
  }

  func testCreatingWithTracking() {
    let preferences = [
      LoginTracking.enabled,
      .limited,
    ]
    preferences.forEach { preference in
      let configuration = LoginConfiguration(tracking: preference)
      XCTAssertEqual(
        configuration?.tracking,
        preference,
        "Should create a configuration with the provided tracking preference"
      )
    }
  }

  func testCreatingWithRequestedPermissions() {
    let permissions = Set([Permission.email, .userLikes])
    let configuration = LoginConfiguration(permissions: permissions)

    XCTAssertEqual(
      Set((configuration?.requestedPermissions.map { $0.value })!), // swiftlint:disable:this force_unwrapping
      Set(permissions.map { $0.name }),
      "Should create a configuration with the provided tracking preference"
    )
  }

  func testCreatingWithMessengerPageId() {
    let messengerPageId = "12345"
    let configuration = LoginConfiguration(messengerPageId: messengerPageId)
    XCTAssertEqual(
      configuration?.messengerPageId,
      messengerPageId,
      .createsConfigWithMessengerID
    )
  }

  func testCreatingWithPermissionsTrackingAndMessengerPageId() {
    let messengerPageId = "12345"
    let permissions = ["email"]

    let configuration = LoginConfiguration(
      permissions: permissions,
      tracking: .enabled,
      messengerPageId: messengerPageId
    )

    XCTAssertEqual(
      configuration?.requestedPermissions,
      FBPermission.permissions(fromRawPermissions: Set(permissions)),
      .createsConfigWithMessengerID
    )

    XCTAssertEqual(
      configuration?.tracking,
      LoginTracking.enabled,
      .createsConfigWithMessengerID
    )

    XCTAssertEqual(
      configuration?.messengerPageId,
      messengerPageId,
      .createsConfigWithMessengerID
    )
  }

  func testCreatingWithRerequestAuthType() {
    let authType = LoginAuthType.rerequest
    let configuration = LoginConfiguration(authType: authType)
    XCTAssertEqual(
      configuration?.authType,
      authType,
      .createsConfigWithAuthType
    )
  }

  func testCreatingWithPermissionsTrackingAndAuthType() {
    let authType = LoginAuthType.rerequest
    let permissions = ["email"]
    let messengerPageId = "12345"

    let configuration = LoginConfiguration(
      permissions: permissions,
      tracking: .enabled,
      messengerPageId: messengerPageId,
      authType: authType
    )

    XCTAssertEqual(
      configuration?.requestedPermissions,
      FBPermission.permissions(fromRawPermissions: Set(permissions)),
      .createsConfigWithAuthType
    )

    XCTAssertEqual(
      configuration?.tracking,
      LoginTracking.enabled,
      .createsConfigWithAuthType
    )

    XCTAssertEqual(
      configuration?.messengerPageId,
      messengerPageId,
      .createsConfigWithAuthType
    )

    XCTAssertEqual(
      configuration?.authType,
      authType,
      .createsConfigWithAuthType
    )
  }

  func testCreatingWithReauthorizeAuthType() {
    let authType = LoginAuthType.reauthorize
    let configuration = LoginConfiguration(authType: authType)
    XCTAssertEqual(
      configuration?.authType,
      authType,
      "Should create a configuration with the provided auth_type"
    )
  }

  func testCreatingWithNilAuthType() {
    let configuration = LoginConfiguration(authType: nil)
    XCTAssertNil(
      configuration?.authType,
      "Should treat a nil auth type as nil"
    )
  }

  func testCreatingWithCodeVerifier() {
    let codeVerifier = CodeVerifier()
    let configuration = LoginConfiguration(permissions: [], codeVerifier: codeVerifier)
    XCTAssertEqual(
      configuration?.codeVerifier.value,
      codeVerifier.value,
      "Should create a configuration with the provided code verifier"
    )
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let createsConfigWithAuthType = "Creates a configuration with the provided auth_type"
  static let createsConfigWithMessengerID = "Creates a configuration with the provided Messenger Page Id"
}


/*
 * 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 FBSDKLoginKit
import XCTest

// These tests can likely be simplified once we move to a pure Swift implementation and no longer
// need to have the error types conform to `CustomNSError`.
final class LoginErrorTests: XCTestCase {

  func testDomains() {
    XCTAssertEqual(LoginErrorDomain, "com.facebook.sdk.login", .domain)
    XCTAssertEqual(LoginError.errorDomain, LoginErrorDomain, .domainInLoginError)
    XCTAssertEqual(DeviceLoginError.errorDomain, LoginErrorDomain, .domainInDeviceLoginError)
  }

  // MARK: - LoginError

  func testLoginErrorCodes() {
    testLoginError(LoginError.reserved, code: .reserved, rawValue: 300)
    testLoginError(LoginError.unknown, code: .unknown, rawValue: 301)
    testLoginError(LoginError.passwordChanged, code: .passwordChanged, rawValue: 302)
    testLoginError(LoginError.userCheckpointed, code: .userCheckpointed, rawValue: 303)
    testLoginError(LoginError.userMismatch, code: .userMismatch, rawValue: 304)
    testLoginError(LoginError.unconfirmedUser, code: .unconfirmedUser, rawValue: 305)
    testLoginError(LoginError.systemAccountAppDisabled, code: .systemAccountAppDisabled, rawValue: 306)
    testLoginError(LoginError.systemAccountUnavailable, code: .systemAccountUnavailable, rawValue: 307)
    testLoginError(LoginError.badChallengeString, code: .badChallengeString, rawValue: 308)
    testLoginError(LoginError.invalidIDToken, code: .invalidIDToken, rawValue: 309)
    testLoginError(LoginError.missingAccessToken, code: .missingAccessToken, rawValue: 310)
  }

  func testLoginErrorCreationFromNSError() {
    let error = LoginError(_nsError: .sample)
    XCTAssertIdentical(error._nsError, NSError.sample, .nsErrorSource)
    XCTAssertEqual(error.errorCode, .sampleErrorCode, .usesNSErrorCode)
    XCTAssertEqual(error.errorUserInfo[.userInfoStringKey] as? String, .userInfoValue, .usesNSErrorUserInfo)
  }

  func testLoginErrorCreationFromCode() {
    let error = LoginError(.missingAccessToken)
    XCTAssertEqual(error.errorCode, LoginError.Code.missingAccessToken.rawValue, .usesCode)
  }

  func testLoginErrorCreationFromCodeAndUserInfo() {
    let error = LoginError(.missingAccessToken, userInfo: .sample)
    XCTAssertEqual(error.errorCode, LoginError.Code.missingAccessToken.rawValue, .usesCode)
    XCTAssertEqual(error.errorUserInfo[.userInfoStringKey] as? String, .userInfoValue, .usesUserInfo)
  }

  func testLoginErrorUnequalNSErrors() {
    XCTAssertNotEqual(
      LoginError(_nsError: .sample),
      LoginError(_nsError: .unequalToSample),
      .unequalNSErrors
    )
  }

  func testLoginErrorEqualNSErrors() {
    XCTAssertEqual(
      LoginError(_nsError: .sample),
      LoginError(_nsError: .sample),
      .equalNSErrors
    )
  }

  func testLoginErrorUnequalCodes() {
    XCTAssertNotEqual(
      LoginError(.missingAccessToken),
      LoginError(.badChallengeString),
      .unequalErrorCodes
    )
  }

  func testLoginErrorEqualCodes() {
    XCTAssertEqual(
      LoginError(.missingAccessToken),
      LoginError(.missingAccessToken),
      .equalErrorCodes
    )
  }

  // MARK: - DeviceLoginError

  func testDeviceLoginErrorCodes() {
    testDeviceLoginError(DeviceLoginError.excessivePolling, code: .excessivePolling, rawValue: 1349172)
    testDeviceLoginError(DeviceLoginError.authorizationDeclined, code: .authorizationDeclined, rawValue: 1349173)
    testDeviceLoginError(DeviceLoginError.authorizationPending, code: .authorizationPending, rawValue: 1349174)
    testDeviceLoginError(DeviceLoginError.codeExpired, code: .codeExpired, rawValue: 1349152)
  }

  func testDeviceLoginErrorCreationFromNSError() {
    let error = DeviceLoginError(_nsError: .sample)
    XCTAssertIdentical(error._nsError, NSError.sample, .nsErrorSource)
    XCTAssertEqual(error.errorCode, .sampleErrorCode, .usesNSErrorCode)
    XCTAssertEqual(error.errorUserInfo[.userInfoStringKey] as? String, .userInfoValue, .usesNSErrorUserInfo)
  }

  func testDeviceLoginErrorCreationFromCode() {
    let error = DeviceLoginError(.authorizationPending)
    XCTAssertEqual(error.errorCode, DeviceLoginError.Code.authorizationPending.rawValue, .usesCode)
  }

  func testDeviceLoginErrorCreationFromCodeAndUserInfo() {
    let error = DeviceLoginError(.authorizationPending, userInfo: .sample)
    XCTAssertEqual(error.errorCode, DeviceLoginError.Code.authorizationPending.rawValue, .usesCode)
    XCTAssertEqual(error.errorUserInfo[.userInfoStringKey] as? String, .userInfoValue, .usesUserInfo)
  }

  func testDeviceLoginErrorUnequalNSErrors() {
    XCTAssertNotEqual(
      DeviceLoginError(_nsError: .sample),
      DeviceLoginError(_nsError: .unequalToSample),
      .unequalNSErrors
    )
  }

  func testDeviceLoginErrorEqualNSErrors() {
    XCTAssertEqual(
      DeviceLoginError(_nsError: .sample),
      DeviceLoginError(_nsError: .sample),
      .equalNSErrors
    )
  }

  func testDeviceLoginErrorUnequalCodes() {
    XCTAssertNotEqual(
      DeviceLoginError(.authorizationDeclined),
      DeviceLoginError(.authorizationPending),
      .unequalErrorCodes
    )
  }

  func testDeviceLoginErrorEqualCodes() {
    XCTAssertEqual(
      DeviceLoginError(.codeExpired),
      DeviceLoginError(.codeExpired),
      .equalErrorCodes
    )
  }

  // MARK: - Helpers

  private func testLoginError(
    _ member: LoginError.Code,
    code: LoginError.Code,
    rawValue: LoginError.Code.RawValue,
    file: StaticString = #file,
    line: UInt = #line
  ) {
    let error = LoginError(member)
    XCTAssertEqual(error.errorCode, rawValue, .loginErrorCode(code, rawValue: rawValue), file: file, line: line)
    XCTAssertEqual(code.rawValue, rawValue, .loginErrorCode(code, rawValue: rawValue), file: file, line: line)
    XCTAssertEqual(member, code, .loginErrorMember(member, code: code), file: file, line: line)
  }

  private func testDeviceLoginError(
    _ member: DeviceLoginError.Code,
    code: DeviceLoginError.Code,
    rawValue: DeviceLoginError.Code.RawValue,
    file: StaticString = #file,
    line: UInt = #line
  ) {
    let error = DeviceLoginError(member)
    XCTAssertEqual(error.errorCode, rawValue, .deviceLoginErrorCode(code, rawValue: rawValue), file: file, line: line)
    XCTAssertEqual(code.rawValue, rawValue, .deviceLoginErrorCode(code, rawValue: rawValue), file: file, line: line)
    XCTAssertEqual(member, code, .deviceLoginErrorMember(member, code: code), file: file, line: line)
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let domain = "The LoginErrorDomain is 'com.facebook.sdk.login'"
  static let domainInLoginError = "The LoginError error domain should is equal to LoginErrorDomain"
  static let domainInDeviceLoginError = "The DeviceLoginError error domain should is equal to LoginErrorDomain"

  static let nsErrorSource = "A custom error created with an NSError keeps the NSError"
  static let usesNSErrorCode = "Creating a custom error with an NSError uses the NSError's code"
  static let usesNSErrorUserInfo = "Creating a custom error with an NSError uses the NSError's user info values"
  static let usesCode = "Creating a custom error a code uses that code"
  static let usesUserInfo = "Creating a custom error with user info values uses those values"

  static func loginErrorCode(_ code: LoginError.Code, rawValue: LoginError.Code.RawValue) -> String {
    "The \(String(describing: code)) login error code has a raw value of \(rawValue)"
  }

  static func loginErrorMember(_ member: LoginError.Code, code: LoginError.Code) -> String {
    "LoginError has a member with a code equal to \(String(describing: code))"
  }

  static func deviceLoginErrorCode(_ code: DeviceLoginError.Code, rawValue: DeviceLoginError.Code.RawValue) -> String {
    "The \(String(describing: code)) device login error code has a raw value of \(rawValue)"
  }

  static func deviceLoginErrorMember(_ member: DeviceLoginError.Code, code: DeviceLoginError.Code) -> String {
    "DeviceLoginError has a member with a code equal to \(String(describing: code))"
  }

  static let unequalNSErrors = "Errors with unequal NSErrors are unequal"
  static let equalNSErrors = "Errors with equal NSErrors are equal"
  static let unequalErrorCodes = "Errors with unequal error codes are unequal"
  static let equalErrorCodes = "Errors with equal error codes are equal"
}

// MARK: - Test Values

fileprivate extension NSError {
  static let sample = NSError(domain: .sampleErrorDomain, code: .sampleErrorCode, userInfo: .sample)
  static let unequalToSample = NSError(domain: .sampleErrorDomain, code: .sampleErrorCode + 1, userInfo: .sample)
}

fileprivate extension String {
  static let sampleErrorDomain = "THIS IS IGNORED"
  static let userInfoStringKey = "string"
  static let userInfoIntegerKey = "integer"
  static let userInfoValue = "sample string"
}

fileprivate extension Int {
  static let sampleErrorCode = 12_345_678
  static let userInfoValue = 1234
}

fileprivate extension Dictionary where Key == String, Value == Any {
  static var sample: Self {
    [
      .userInfoStringKey: String.userInfoValue,
      .userInfoIntegerKey: Int.userInfoValue,
    ]
  }
}