/*
 * 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
@testable import FBSDKGamingServicesKit

import TestTools
import XCTest

final class CreateContextDialogTests: XCTestCase, ContextDialogDelegate {

  var dialogDidCompleteSuccessfully = false
  var dialogDidCancel = false
  var dialogError: NSError?

  override func setUp() {
    super.setUp()

    dialogDidCompleteSuccessfully = false
    dialogDidCancel = false
    dialogError = nil

    _WebDialog.setDependencies(
      .init(
        errorFactory: TestErrorFactory(),
        windowFinder: TestWindowFinder(window: UIWindow())
      )
    )
  }

  override func tearDown() {
    super.tearDown()

    GamingContext.current = nil
    _WebDialog.resetDependencies()
  }

  func testShowDialogWithInvalidContent() {
    let content = CreateContextContent(playerID: "")
    let dialog = CreateContextDialog(content: content, windowFinder: TestWindowFinder(), delegate: self)
    _ = dialog.show()

    XCTAssertNotNil(dialog)
    XCTAssertNotNil(dialogError)
    XCTAssertNil(dialog.currentWebDialog)
  }

  func testShowDialogWithValidContent() {
    let dialog = SampleContextDialogs.showCreateContextDialog(withDelegate: self)

    XCTAssertNotNil(dialog)
    XCTAssertNil(dialogError)
    XCTAssertNotNil(dialog?.currentWebDialog)
  }

  func testDialogCancelsWhenResultDoesNotContainContextID() throws {
    let dialog = SampleContextDialogs.showCreateContextDialog(withDelegate: self)

    let webDialogDelegate = try XCTUnwrap(dialog?.currentWebDialog as? WebDialogViewDelegate)
    let testWindowFinder = try XCTUnwrap(_WebDialog.getDependencies().windowFinder as? TestWindowFinder)
    let results = ["foo": name]
    webDialogDelegate.webDialogView(FBWebDialogView(), didCompleteWithResults: results)

    XCTAssertNotNil(webDialogDelegate)
    XCTAssertTrue(testWindowFinder.wasFindWindowCalled)
    XCTAssertFalse(dialogDidCompleteSuccessfully)
    XCTAssertTrue(dialogDidCancel)
    XCTAssertNil(dialogError)
  }

  func testDialogSuccessfullyCreatesGamingContext() throws {
    XCTAssertNil(GamingContext.current, "Should not have a context by default")

    let dialog = SampleContextDialogs.showCreateContextDialog(withDelegate: self)
    let webDialogDelegate = try XCTUnwrap(dialog?.currentWebDialog as? WebDialogViewDelegate)
    let testWindowFinder = try XCTUnwrap(_WebDialog.getDependencies().windowFinder as? TestWindowFinder)
    let resultContextIDKey = "context_id"
    let resultContextID = "1234"
    let results = [resultContextIDKey: resultContextID]

    webDialogDelegate.webDialogView(FBWebDialogView(), didCompleteWithResults: results)

    XCTAssertEqual(
      resultContextID,
      GamingContext.current?.identifier,
      "Should create a gaming context using the identifier from the web dialog result"
    )
    XCTAssertNotNil(webDialogDelegate)
    XCTAssertTrue(testWindowFinder.wasFindWindowCalled)
    XCTAssertNotNil(GamingContext.current?.identifier)
    XCTAssertTrue(dialogDidCompleteSuccessfully)
    XCTAssertFalse(dialogDidCancel)
    XCTAssertNil(dialogError)
  }

  func testDialogSuccessfullyUpdatesGamingContext() throws {
    GamingContext.current = GamingContext(identifier: "foo", size: 2)

    let dialog = SampleContextDialogs.showCreateContextDialog(withDelegate: self)
    let webDialogDelegate = try XCTUnwrap(dialog?.currentWebDialog as? WebDialogViewDelegate)
    let testWindowFinder = try XCTUnwrap(_WebDialog.getDependencies().windowFinder as? TestWindowFinder)
    let resultContextIDKey = "context_id"
    let resultContextID = "1234"
    let results = [resultContextIDKey: resultContextID]

    webDialogDelegate.webDialogView(FBWebDialogView(), didCompleteWithResults: results)

    XCTAssertEqual(
      resultContextID,
      GamingContext.current?.identifier,
      "Should update the current gaming context to use the identifer from the web dialog result"
    )
    XCTAssertNotNil(webDialogDelegate)
    XCTAssertTrue(testWindowFinder.wasFindWindowCalled)
    XCTAssertNotNil(GamingContext.current?.identifier)
    XCTAssertTrue(dialogDidCompleteSuccessfully)
    XCTAssertFalse(dialogDidCancel)
    XCTAssertNil(dialogError)
  }

  func testDialogCompletesWithServerError() throws {
    let dialog = SampleContextDialogs.showCreateContextDialog(withDelegate: self)
    let webDialogDelegate = try XCTUnwrap(dialog?.currentWebDialog as? WebDialogViewDelegate)
    let testWindowFinder = try XCTUnwrap(_WebDialog.getDependencies().windowFinder as? TestWindowFinder)
    let resultErrorCodeKey = "error_code"
    let resultErrorCode = 1234
    let resultErrorMessageKey = "error_message"
    let resultErrorMessage = "Webview error"
    let results = [resultErrorCodeKey: resultErrorCode, resultErrorMessageKey: resultErrorMessage] as [String: Any]
    webDialogDelegate.webDialogView(FBWebDialogView(), didCompleteWithResults: results)
    let error = try XCTUnwrap(dialogError)

    XCTAssertNotNil(webDialogDelegate)
    XCTAssertTrue(testWindowFinder.wasFindWindowCalled)
    XCTAssertNil(GamingContext.current?.identifier)
    XCTAssertEqual(resultErrorCode, error.code)
    XCTAssertEqual(resultErrorMessage, error.userInfo.values.first as? String)
    XCTAssertFalse(dialogDidCompleteSuccessfully)
    XCTAssertFalse(dialogDidCancel)
    XCTAssertNotNil(dialogError)
  }

  func testDialogCancels() throws {
    let dialog = SampleContextDialogs.showCreateContextDialog(withDelegate: self)
    let webDialogDelegate = try XCTUnwrap(dialog?.currentWebDialog as? WebDialogViewDelegate)
    let testWindowFinder = try XCTUnwrap(_WebDialog.getDependencies().windowFinder as? TestWindowFinder)

    webDialogDelegate.webDialogViewDidCancel(FBWebDialogView())

    XCTAssertNotNil(webDialogDelegate)
    XCTAssertTrue(testWindowFinder.wasFindWindowCalled)
    XCTAssertFalse(dialogDidCompleteSuccessfully)
    XCTAssertTrue(dialogDidCancel)
    XCTAssertNil(dialogError)
  }

  func testDialogFailsWithError() throws {
    let dialog = SampleContextDialogs.showCreateContextDialog(withDelegate: self)
    let webDialogDelegate = try XCTUnwrap(dialog?.currentWebDialog as? WebDialogViewDelegate)
    let testWindowFinder = try XCTUnwrap(_WebDialog.getDependencies().windowFinder as? TestWindowFinder)

    let error = NSError(domain: "Test", code: 1, userInfo: nil)
    webDialogDelegate.webDialogView(FBWebDialogView(), didFailWithError: error)

    XCTAssertNotNil(webDialogDelegate)
    XCTAssertTrue(testWindowFinder.wasFindWindowCalled)
    XCTAssertFalse(dialogDidCompleteSuccessfully)
    XCTAssertFalse(dialogDidCancel)
    XCTAssertNotNil(dialogError)
  }

  func contextDialogDidComplete(_ contextDialog: ContextWebDialog) {
    dialogDidCompleteSuccessfully = true
  }

  func contextDialog(_ contextDialog: ContextWebDialog, didFailWithError error: Error) {
    dialogError = error as NSError
  }

  func contextDialogDidCancel(_ contextDialog: ContextWebDialog) {
    dialogDidCancel = true
  }
}


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

final class FileHandleFactoryTests: XCTestCase {

  func testCreatingFileHandle() throws {
    let data = "foo".data(using: .utf8)
    let url = URL(
      fileURLWithPath: NSTemporaryDirectory(),
      isDirectory: true
    ).appendingPathComponent(name)

    FileManager.default.createFile(atPath: url.path, contents: data, attributes: nil)

    XCTAssertNotNil(
      try? FileHandleFactory().fileHandleForReading(from: url),
      "A file handle factory should be able to return a handle to a valid file"
    )
  }
}


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

@available(iOS 13.4, *)
final class FileHandleTests: XCTestCase {
  // swiftlint:disable:next implicitly_unwrapped_optional
  var fileHandle: FileHandle!

  override func setUp() async throws {
    try await super.setUp()

    let path = try XCTUnwrap(Bundle(for: FileHandleTests.self).path(forResource: "Sample", ofType: "txt"))
    fileHandle = try XCTUnwrap(FileHandle(forReadingAtPath: path))
  }

  override func tearDown() async throws {
    try fileHandle.close()
    fileHandle = nil

    try await super.tearDown()
  }

  func testSeekingToEndOfFile() throws {
    let expectedFileSize = try fileHandle.seekToEnd()
    let actualFileSize = fileHandle.fb_seekToEndOfFile()

    XCTAssertEqual(actualFileSize, expectedFileSize, .seekToEnd)
  }

  func testSeekingToFileOffset() throws {
    try fileHandle.seek(toOffset: 4)
    let expectedOffset = try fileHandle.offset()
    fileHandle.fb_seek(toFileOffset: 4)
    let actualOffset = try fileHandle.offset()

    XCTAssertEqual(actualOffset, expectedOffset, .seekToOffset)
  }

  func testReadingData() throws {
    let expectedData = try fileHandle.read(upToCount: 6)
    try fileHandle.seek(toOffset: 0)
    let actualData = fileHandle.fb_readData(ofLength: 6)

    XCTAssertEqual(actualData, expectedData, .readData)
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let seekToEnd = "A file handle can seek to the end of the file through an internal abstraction"
  static let seekToOffset = "A file handle can seek to an offset through an internal abstraction"
  static let readData = "A file handle can read data through an internal abstraction"
}


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

import FBSDKCoreKit
import TestTools
import XCTest

final class FriendFinderDialogTests: XCTestCase {
  let factory = TestGamingServiceControllerFactory()
  lazy var dialog = FriendFinderDialog(gamingServiceControllerFactory: factory)
  let bridgeAPIError = NSError(domain: ErrorDomain, code: CoreError.errorBridgeAPIInterruption.rawValue, userInfo: nil)

  override func setUp() {
    super.setUp()

    ApplicationDelegate.shared.application(UIApplication.shared, didFinishLaunchingWithOptions: [:])
    AccessToken.current = SampleAccessTokens.validToken
  }

  override func tearDown() {
    AccessToken.current = nil

    super.tearDown()
  }

  // MARK: - Dependencies

  func testDefaultDependencies() {
    XCTAssertTrue(
      FriendFinderDialog.shared.factory is GamingServiceControllerFactory,
      "Should use the expected default gaming service controller factory type by default"
    )
  }

  // MARK: - Launching Dialogs

  func testFailureWhenNoValidAccessTokenPresentAndAppIDIsNull() {
    AccessToken.current = nil
    Settings.shared.appID = nil

    var completionCalled = false
    dialog.launch { _, error in
      XCTAssertEqual(
        (error as NSError?)?.code,
        CoreError.errorAccessTokenRequired.rawValue,
        "Expected error requiring a valid access token"
      )
      completionCalled = true
    }

    XCTAssertTrue(completionCalled)
  }

  func testPresentationWhenTokenIsNilAndAppIDIsSet() {
    AccessToken.current = nil
    Settings.shared.appID = "appID"

    var didInvokeCompletion = false
    dialog.launch { success, _ in
      XCTAssertTrue(success)
      didInvokeCompletion = true
    }
    XCTAssertEqual(
      factory.controller.capturedArgument,
      Settings.shared.appID,
      "Should invoke the new controller with the app id in the sdk setting"
    )
    factory.capturedCompletion(true, nil, nil)
    XCTAssertTrue(didInvokeCompletion)
  }

  func testPresentationWhenTokenIsSetAndAppIDIsNil() {
    Settings.shared.appID = nil

    var didInvokeCompletion = false
    dialog.launch { _, _ in
      didInvokeCompletion = true
    }

    XCTAssertEqual(
      factory.capturedServiceType,
      .friendFinder,
      "Should create a controller with the expected service type"
    )
    XCTAssertNil(
      factory.capturedPendingResult,
      "Should not create a controller with a pending result"
    )
    XCTAssertEqual(
      factory.controller.capturedArgument,
      AccessToken.current?.appID,
      "Should invoke the new controller with the app id of the current access token"
    )
    XCTAssertNotNil(AccessToken.current?.appID)

    factory.capturedCompletion(true, nil, nil)

    XCTAssertTrue(didInvokeCompletion)
  }

  func testFailuresReturnAnError() {
    var didInvokeCompletion = false
    dialog.launch { success, error in
      XCTAssertFalse(success)
      XCTAssertEqual(
        (error as NSError?)?.code,
        CoreError.errorBridgeAPIInterruption.rawValue,
        "Expected errorBridgeAPIInterruption error"
      )
      didInvokeCompletion = true
    }

    XCTAssertEqual(
      factory.capturedServiceType,
      .friendFinder,
      "Should create a controller with the expected service type"
    )
    XCTAssertNil(
      factory.capturedPendingResult,
      "Should not create a controller with a pending result"
    )
    XCTAssertEqual(
      factory.controller.capturedArgument,
      AccessToken.current?.appID,
      "Should invoke the new controller with the app id of the current access token"
    )
    XCTAssertNotNil(AccessToken.current?.appID)

    factory.capturedCompletion(false, nil, bridgeAPIError)

    XCTAssertTrue(didInvokeCompletion)
  }

  func testHandlingOfCallbackURL() {
    var completionCalled = false
    dialog.launch { success, _ in
      XCTAssertTrue(success)
      completionCalled = true
    }

    factory.capturedCompletion(true, nil, nil)

    XCTAssertTrue(completionCalled)
  }
}


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

import FBSDKCoreKit
import XCTest

final class GameRequestContentTests: XCTestCase {
  func testProperties() {
    let content = Self.contentWithAllProperties()
    XCTAssertEqual(content.recipients, Self.recipients())
    XCTAssertEqual(content.message, Self.message())
    XCTAssertEqual(content.actionType, Self.actionType())
    XCTAssertEqual(content.objectID, Self.objectID())
    XCTAssertEqual(content.filters, Self.filters())
    XCTAssertEqual(content.recipientSuggestions, Self.recipientSuggestions())
    XCTAssertEqual(content.data, Self.data())
    XCTAssertEqual(content.title, Self.title())
  }

  func testEquatabilityOfCopy() {
    let content = Self.contentWithAllProperties()
    let contentCopy = Self.contentWithAllProperties()
    XCTAssertNotIdentical(contentCopy, content)
    XCTAssertEqual(contentCopy, content)
  }

  func testCoding() throws {
    let content = Self.contentWithAllProperties()
    let data = try NSKeyedArchiver.archivedData(withRootObject: content, requiringSecureCoding: true)
    let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
    unarchiver.requiresSecureCoding = true
    let unarchivedObject = unarchiver.decodeObject(
      of: GameRequestContent.self,
      forKey: NSKeyedArchiveRootObjectKey
    )
    XCTAssertEqual(unarchivedObject, content)
  }

  func testValidationWithMinimalProperties() {
    testValidationWithContent(
      content: Self.contentWithMinimalProperties()
    )
  }

  func testValidationWithManyProperties() {
    testValidationWithContent(
      content: Self.contentWithManyProperties()
    )
  }

  func testValidationWithNoProperties() {
    let content = GameRequestContent()
    testValidationWithContentAndErrorArgument(
      content: content,
      errorArgumentName: "message"
    )
  }

  func testValidationWithTo() {
    let content = Self.contentWithMinimalProperties()
    content.recipients = Self.recipients()
    testValidationWithContent(content: content)
  }

  func testValidationWithActionTypeSend() {
    let content = Self.contentWithMinimalProperties()
    content.actionType = .send
    testValidationWithContentAndErrorArgument(
      content: content,
      errorArgumentName: "objectID"
    )
  }

  func testValidationWithActionTypeSendAndobjectID() {
    let content = Self.contentWithMinimalProperties()
    content.actionType = .send
    content.objectID = Self.objectID()
    testValidationWithContent(content: content)
  }

  func testValidationWithActionTypeAskFor() {
    let content = Self.contentWithMinimalProperties()
    content.actionType = .askFor
    testValidationWithContentAndErrorArgument(
      content: content,
      errorArgumentName: "objectID"
    )
  }

  func testValidationWithActionTypeAskForAndobjectID() {
    let content = Self.contentWithMinimalProperties()
    content.actionType = .askFor
    content.objectID = Self.objectID()
    testValidationWithContent(content: content)
  }

  func testValidationWithActionTypeTurn() {
    let content = Self.contentWithMinimalProperties()
    content.actionType = .turn
    testValidationWithContent(content: content)
  }

  func testValidationWithActionTypeTurnAndobjectID() {
    let content = Self.contentWithMinimalProperties()
    content.actionType = .turn
    content.objectID = Self.objectID()
    testValidationWithContentAndErrorArgument(
      content: content,
      errorArgumentName: "objectID"
    )
  }

  func testValidationWithFilterAppUsers() {
    let content = Self.contentWithMinimalProperties()
    content.filters = .appUsers
    testValidationWithContent(content: content)
  }

  func testValidationWithFilterAppNonUsers() {
    let content = Self.contentWithMinimalProperties()
    content.filters = .appNonUsers
    testValidationWithContent(content: content)
  }

  func testValidationWithToAndFilters() {
    let content = Self.contentWithMinimalProperties()
    content.filters = Self.filters()
    content.recipients = Self.recipients()
    testValidationWithContentAndErrorArgument(
      content: content,
      errorArgumentName: "recipients"
    )
  }

  func testValidationWithToAndSuggestions() {
    let content = Self.contentWithMinimalProperties()
    content.recipients = Self.recipients()
    content.recipientSuggestions = Self.recipientSuggestions()
    testValidationWithContentAndErrorArgument(
      content: content,
      errorArgumentName: "recipients"
    )
  }

  func testValidationWithFiltersAndSuggestions() {
    let content = Self.contentWithMinimalProperties()
    content.filters = Self.filters()
    content.recipientSuggestions = Self.recipientSuggestions()
    testValidationWithContentAndErrorArgument(
      content: content,
      errorArgumentName: "recipientSuggestions"
    )
  }

  func testValidationWithToAndFiltersAndSuggestions() {
    let content = Self.contentWithMinimalProperties()
    content.filters = Self.filters()
    content.recipients = Self.recipients()
    content.recipientSuggestions = Self.recipientSuggestions()
    testValidationWithContentAndErrorArgument(
      content: content,
      errorArgumentName: "recipients"
    )
  }

  func testValidationWithLongData() {
    let content = Self.contentWithMinimalProperties()
    content.data = String(format: "%.254f", 1)
    testValidationWithContentAndErrorArgument(
      content: content,
      errorArgumentName: "data"
    )
  }

  func testValidationWithContent(
    content: GameRequestContent,
    file: String = #file,
    line: UInt = #line
  ) {
    XCTAssertNoThrow(try content.validate(options: []))
  }

  func testValidationWithContentAndErrorArgument(
    content: GameRequestContent,
    errorArgumentName: String,
    file: String = #file,
    line: UInt = #line
  ) {
    var catchBlockHit = false
    do {
      try content.validate(options: [])
      XCTFail("Expecting Error to be Thrown")
    } catch {
      XCTAssertEqual((error as NSError).userInfo[ErrorArgumentNameKey] as? String, errorArgumentName)
      catchBlockHit = true
    }

    XCTAssertTrue(catchBlockHit)
  }

  private static func contentWithMinimalProperties() -> GameRequestContent {
    let content = GameRequestContent()
    content.message = message()
    return content
  }

  private static func contentWithAllProperties() -> GameRequestContent {
    let content = GameRequestContent()
    content.actionType = actionType()
    content.data = data()
    content.filters = filters()
    content.message = message()
    content.objectID = objectID()
    content.recipientSuggestions = recipientSuggestions()
    content.title = title()
    content.recipients = recipients()
    return content
  }

  private static func contentWithManyProperties() -> GameRequestContent {
    let content = GameRequestContent()
    content.data = data()
    content.message = message()
    content.title = title()
    return content
  }

  private static func recipients() -> [String] {
    ["recipient-id-1", "recipient-id-2"]
  }

  private static func message() -> String {
    "Here is an awesome item for you!"
  }

  private static func actionType() -> GameRequestActionType {
    .send
  }

  private static func objectID() -> String {
    "id-of-an-awesome-item"
  }

  private static func filters() -> GameRequestFilter {
    .appUsers
  }

  private static func recipientSuggestions() -> [String] {
    ["suggested-recipient-id-1", "suggested-recipient-id-2"]
  }

  private static func data() -> String {
    "some-data-highly-important"
  }

  private static func title() -> String {
    "Send this awesome item to your friends!"
  }
}


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

import FBSDKCoreKit
import FBSDKShareKit
import TestTools
import XCTest

final class GameRequestDialogTests: XCTestCase {
  // swiftlint:disable implicitly_unwrapped_optional
  var dialog: GameRequestDialog!
  var content: GameRequestContent!
  var delegate: TestGameRequestDialogDelegate!
  var bridgeAPIRequestOpener: TestBridgeAPIRequestOpener!
  var errorFactory: TestErrorFactory!
  var gameRequestURLProvider: TestGameRequestURLProvider.Type!
  var internalUtility: TestInternalUtility!
  var logger: FBSDKGamingServicesKitTests.TestLogger.Type!
  var settings: TestSettings!
  var shareValidator: TestShareUtility.Type!
  var utility: TestGamingUtility.Type!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    content = GameRequestContent()
    delegate = TestGameRequestDialogDelegate()
    dialog = GameRequestDialog(content: content, delegate: delegate)

    bridgeAPIRequestOpener = TestBridgeAPIRequestOpener()
    errorFactory = TestErrorFactory()
    gameRequestURLProvider = TestGameRequestURLProvider.self
    internalUtility = TestInternalUtility()
    logger = FBSDKGamingServicesKitTests.TestLogger.self
    settings = TestSettings()
    shareValidator = TestShareUtility.self
    utility = TestGamingUtility.self

    dialog.setDependencies(
      .init(
        bridgeAPIRequestOpener: bridgeAPIRequestOpener,
        errorFactory: errorFactory,
        gameRequestURLProvider: gameRequestURLProvider,
        internalUtility: internalUtility,
        logger: logger,
        settings: settings,
        shareValidator: shareValidator,
        utility: utility
      )
    )
  }

  override func tearDown() {
    content = nil
    delegate = nil

    errorFactory = nil
    gameRequestURLProvider = nil
    internalUtility = nil
    logger = nil
    settings = nil
    shareValidator = nil
    bridgeAPIRequestOpener = nil
    utility = nil

    TestGameRequestURLProvider.reset()
    TestLogger.reset()
    TestShareUtility.reset()
    TestGamingUtility.reset()

    dialog = nil

    super.tearDown()
  }

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

    XCTAssertIdentical(
      dependencies.bridgeAPIRequestOpener,
      _BridgeAPI.shared,
      .Dependencies.defaultDependency("the shared BridgeAPI", for: "bridge API request opener")
    )
    XCTAssertTrue(
      dependencies.errorFactory is _ErrorFactory,
      .Dependencies.defaultDependency("a concrete error factory", for: "error factory")
    )
    XCTAssertIdentical(
      dependencies.gameRequestURLProvider as AnyObject,
      GameRequestURLProvider.self,
      .Dependencies.defaultDependency("GameRequestURLProvider", for: "game request URL provider")
    )
    XCTAssertIdentical(
      dependencies.internalUtility,
      InternalUtility.shared,
      .Dependencies.defaultDependency("the shared InternalUtility", for: "internal utility")
    )
    XCTAssertIdentical(
      dependencies.logger as AnyObject,
      _Logger.self,
      .Dependencies.defaultDependency("Logger", for: "logger")
    )
    XCTAssertIdentical(
      dependencies.settings,
      Settings.shared,
      .Dependencies.defaultDependency("the shared Settings", for: "settings")
    )
    XCTAssertTrue(
      dependencies.shareValidator is _ShareUtility.Type,
      .Dependencies.defaultDependency("_ShareUtility", for: "share validator")
    )
    XCTAssertIdentical(
      dependencies.utility as AnyObject,
      Utility.self,
      .Dependencies.defaultDependency("Utility", for: "utility")
    )
  }

  func testCustomDependencies() throws {
    let dependencies = try dialog.getDependencies()

    XCTAssertIdentical(
      dependencies.bridgeAPIRequestOpener,
      bridgeAPIRequestOpener,
      .Dependencies.customDependency(for: "bridge API request opener")
    )
    XCTAssertIdentical(
      dependencies.errorFactory,
      errorFactory,
      .Dependencies.customDependency(for: "error factory")
    )
    XCTAssertIdentical(
      dependencies.gameRequestURLProvider as AnyObject,
      gameRequestURLProvider,
      .Dependencies.customDependency(for: "game request URL provider")
    )
    XCTAssertIdentical(
      dependencies.internalUtility,
      internalUtility,
      .Dependencies.customDependency(for: "internal utility")
    )
    XCTAssertIdentical(
      dependencies.logger as AnyObject,
      logger,
      .Dependencies.customDependency(for: "logger")
    )
    XCTAssertIdentical(
      dependencies.settings,
      settings,
      .Dependencies.customDependency(for: "settings")
    )
    XCTAssertIdentical(
      dependencies.shareValidator as AnyObject,
      shareValidator,
      .Dependencies.customDependency(for: "share validator")
    )
    XCTAssertIdentical(
      dependencies.utility as AnyObject,
      utility,
      .Dependencies.customDependency(for: "utility")
    )
  }

  // MARK: - Launching

  private func setValidLaunchConditions() {
    utility.stubbedGraphDomain = "gaming"
    internalUtility.isFacebookAppInstalled = true
    content.message = "foo"
    gameRequestURLProvider.stubbedDeepLinkURL = SampleURLs.valid(path: "launching")
  }

  func testShowingWithLaunchConditions() {
    setValidLaunchConditions()

    _ = GameRequestDialog.show(dialog: dialog)

    // how to show that launch was called?
    XCTAssertEqual(
      bridgeAPIRequestOpener.capturedURL,
      gameRequestURLProvider.stubbedDeepLinkURL,
      "Should utilize the bridge api request opener when the launch conditions support it"
    )
  }

  func testShowingWithoutLaunchConditionForGraphDomain() {
    setValidLaunchConditions()
    utility.stubbedGraphDomain = "facebook"

    _ = GameRequestDialog.show(dialog: dialog)

    XCTAssertNil(
      bridgeAPIRequestOpener.capturedURL,
      "Should not utilize the bridge api request opener when the graph domain is not `gaming`"
    )
  }

  func testShowingWithoutLaunchConditionForFacebookAppInstall() {
    setValidLaunchConditions()
    internalUtility.isFacebookAppInstalled = false

    _ = GameRequestDialog.show(dialog: dialog)

    XCTAssertNil(
      bridgeAPIRequestOpener.capturedURL,
      "Should not utilize the bridge api request opener when the facebook app is not installed"
    )
  }

  func testShowingWithoutLaunchConditionForNonEmptyContentMessage() {
    setValidLaunchConditions()
    content.message = ""

    _ = GameRequestDialog.show(dialog: dialog)

    XCTAssertNil(
      bridgeAPIRequestOpener.capturedURL,
      "Should not utilize the bridge api request opener when the content message is empty"
    )
  }

  func testShowingWithoutLaunchConditionForMissingURL() {
    setValidLaunchConditions()
    gameRequestURLProvider.stubbedDeepLinkURL = nil

    _ = GameRequestDialog.show(dialog: dialog)

    XCTAssertNil(
      bridgeAPIRequestOpener.capturedURL,
      "Should not utilize the bridge api request opener when there is no URL to open"
    )
  }

  func testCompletingLaunchWithFailure() {
    setValidLaunchConditions()

    _ = GameRequestDialog.show(dialog: dialog)

    bridgeAPIRequestOpener.capturedHandler?(false, nil)

    XCTAssertNil(delegate.failureDialog, "Should not invoke the failure delegate method when success is false")
  }

  func testCompletingLaunchWithError() throws {
    setValidLaunchConditions()
    _ = GameRequestDialog.show(dialog: dialog)

    let error = SampleError()
    bridgeAPIRequestOpener.capturedHandler?(true, error)

    let expectedError = TestSDKError(
      type: .general,
      code: CoreError.errorBridgeAPIInterruption.rawValue,
      message: "Error occured while interacting with Gaming Services, Failed to open bridge.",
      underlyingError: error
    )
    let capturedError = try XCTUnwrap(
      delegate.failureError as? TestSDKError,
      "Should use the error factory to create an error"
    )

    XCTAssertIdentical(delegate.failureDialog, dialog)
    XCTAssertEqual(
      capturedError,
      expectedError,
      "Should use the error factory to create an error with the expected inputs"
    )
  }

  // MARK: - Showing

  private func setValidShowConditions() {
    internalUtility.stubbedTopMostViewController = UIViewController()
    content.message = "foo"
  }

  func testShowingWithValidShowConditions() throws {
    setValidShowConditions()

    _ = GameRequestDialog.show(dialog: dialog)

    let capturedRequest = try XCTUnwrap(
      bridgeAPIRequestOpener.capturedRequest,
      "Should invoke the bridge api request opener with a bridge api request"
    )

    XCTAssertEqual(
      capturedRequest.scheme,
      URLScheme.https.rawValue,
      "Should use the expected scheme"
    )
    XCTAssertEqual(
      capturedRequest.protocolType,
      .web,
      "Should use the expected protocol type"
    )
    XCTAssertEqual(
      capturedRequest.methodName,
      "apprequests",
      "Should use the expected method name"
    )
    let downcastRequest = try XCTUnwrap(
      capturedRequest as? _BridgeAPIRequest,
      "The request should be the expected type"
    )
    let parameters = try XCTUnwrap(
      downcastRequest.parameters as? [String: String],
      "The request should contain string to string parameters"
    )
    let expectedParameters = ["title": "", "object_id": "", "suggestions": "", "to": "", "message": "foo"]

    XCTAssertTrue(
      NSDictionary(dictionary: parameters).isEqual(to: expectedParameters),
      """
      Actual parameters should match the expected parameters. \n
      Actual parameters: \(parameters)
      Expected parameters: \(expectedParameters)
      """
    )
  }

  func testShowingWithMissingTopMostViewController() {
    setValidShowConditions()
    internalUtility.stubbedTopMostViewController = nil

    _ = GameRequestDialog.show(dialog: dialog)

    XCTAssertNil(
      bridgeAPIRequestOpener.capturedRequest,
      "Should not invoke the bridge api request opener without a topmost view controller"
    )
  }

  func testShowingWithEmptyContentMessage() {
    setValidShowConditions()
    content.message = ""

    _ = GameRequestDialog.show(dialog: dialog)

    XCTAssertNil(
      bridgeAPIRequestOpener.capturedRequest,
      "Should not invoke the bridge api request opener with an empty content message"
    )
  }

  // MARK: - URL Opening

  private func launchDialog() {
    setValidLaunchConditions()
    _ = GameRequestDialog.show(dialog: dialog)
  }

  private func createGamingURL(appID: String, queryItems: [URLQueryItem] = []) -> URL? {
    var components = URLComponents()
    components.scheme = "fb\(appID)"
    components.host = "game_requests"
    components.queryItems = queryItems

    return components.url
  }

  func testOpeningGamingURLWithParametersWhenAwaitingResult() throws {
    settings.appID = "123"
    let url = try XCTUnwrap(
      createGamingURL(
        appID: "123",
        queryItems: [
          .init(name: "request_id", value: "foo"),
          .init(name: "recipients", value: "1,2,3"),
        ]
      )
    )

    // Need to launch the dialog in order to set `isAwaitingResult`
    launchDialog()

    _ = dialog.application(nil, open: url, sourceApplication: nil, annotation: nil)

    XCTAssertNotNil(delegate.completionDialog, "Should open a url when the conditions are met")

    let results = try XCTUnwrap(delegate.completionResults)
    XCTAssertEqual(
      results["recipients"] as? [String],
      ["1", "2", "3"],
      "Should parse the recipients from a comma separated string"
    )
    XCTAssertEqual(
      results["request_id"] as? String,
      "foo",
      "Should parse the request ID from the url query parameters"
    )
  }

  func testOpeningGamingURLWithoutParametersWhenAwaitingResult() throws {
    settings.appID = "123"
    let url = try XCTUnwrap(createGamingURL(appID: "123"))

    // Need to launch the dialog in order to set `isAwaitingResult`
    launchDialog()

    _ = dialog.application(nil, open: url, sourceApplication: nil, annotation: nil)

    XCTAssertNil(
      delegate.completionDialog,
      "Should not invoke the completion delegate when there are no query items"
    )
    XCTAssertEqual(
      delegate.cancellationDialog,
      dialog,
      "Should invoke the cancellation delegate when opening a url and not awaiting a result"
    )
  }

  func testOpeningGamingURLWhenNotAwaitingResult() throws {
    settings.appID = "123"
    let url = try XCTUnwrap(createGamingURL(appID: "123"))

    _ = dialog.application(nil, open: url, sourceApplication: nil, annotation: nil)

    XCTAssertNil(
      delegate.completionDialog,
      "Should not invoke the completion delegate when not awaiting a result"
    )
    XCTAssertNil(
      delegate.cancellationDialog,
      "Should not invoke the cancellation delegate when not awaiting a result"
    )
  }

  func testOpeningNonGamingURL() {
    _ = dialog.application(
      nil,
      open: SampleURLs.valid,
      sourceApplication: nil,
      annotation: nil
    )

    XCTAssertNil(
      delegate.completionDialog,
      "Should not invoke the completion delegate"
    )
    XCTAssertNil(
      delegate.cancellationDialog,
      "Should not invoke the cancellation delegate"
    )
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  enum Dependencies {
    static func defaultDependency(_ dependency: String, for type: String) -> String {
      "A GameRequestDialog instance uses \(dependency) as its \(type) dependency by default"
    }

    static func customDependency(for type: String) -> String {
      "A GameRequestDialog instance uses a custom \(type) dependency when provided"
    }
  }
}


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

import FBSDKCoreKit
import TestTools
import XCTest

// swiftlint:disable:next type_name
final class GameRequestFrictionlessRecipientCacheTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  var cache: GameRequestFrictionlessRecipientCache!
  var graphRequestFactory: TestGraphRequestFactory!
  var notificationCenter: TestNotificationCenter!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    graphRequestFactory = TestGraphRequestFactory()
    notificationCenter = TestNotificationCenter()
    cache = GameRequestFrictionlessRecipientCache()
    cache.setDependencies(
      .init(
        graphRequestFactory: graphRequestFactory,
        notificationCenter: notificationCenter,
        accessTokenWallet: TestAccessTokenWallet.self
      )
    )
  }

  override func tearDown() {
    cache = nil
    graphRequestFactory = nil
    notificationCenter = nil
    TestAccessTokenWallet.reset()

    super.tearDown()
  }

  // MARK: Dependencies

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

    XCTAssertIdentical(
      dependencies.graphRequestFactory,
      graphRequestFactory,
      "A recipient cache uses a provided graph request factory"
    )
    XCTAssertIdentical(
      dependencies.notificationCenter,
      notificationCenter,
      "A recipient cache uses the provided notification center"
    )
    XCTAssertIdentical(
      dependencies.accessTokenWallet,
      TestAccessTokenWallet.self,
      "A recipient cache uses the provided access token wallet"
    )
  }

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

    XCTAssertTrue(
      dependencies.graphRequestFactory is GraphRequestFactory,
      "A cache uses graph request factory for its graph request factory protocol by default"
    )
    XCTAssertIdentical(
      dependencies.notificationCenter,
      NotificationCenter.default,
      "A cache uses the default notification center for its notification delivering dependency by default"
    )
    XCTAssertIdentical(
      dependencies.accessTokenWallet,
      AccessToken.self,
      "A cache uses the AccessToken for its access token wallet dependency by default"
    )
  }

  // MARK: - Updating Cache

  func testUpdatingEmptyCacheWithoutAccessToken() {
    initiateFrictionlessRequest(usesValidAccessToken: false)

    XCTAssertNil(
      graphRequestFactory.capturedGraphPath,
      "Should not create a graph request without an access token"
    )
    XCTAssertTrue(cache.recipientIDs.isEmpty, "Should not update the recipient identifiers")
  }

  func testUpdatingCacheWithoutAccessToken() throws {
    _ = try seedRecipientIdentifiers()

    TestAccessTokenWallet.current = nil

    initiateFrictionlessRequest(usesValidAccessToken: false)

    XCTAssertTrue(
      cache.recipientIDs.isEmpty,
      "Should clear any cached identifiers when attempting to update without an access token"
    )
  }

  func testUpdatingCacheWithAccessToken() throws {
    initiateFrictionlessRequest()
    let request = try XCTUnwrap(graphRequestFactory.capturedRequests.first)

    XCTAssertEqual(
      request.graphPath,
      "me/apprequestformerrecipients",
      "Should create a graph request with the expected path"
    )
    XCTAssertEqual(
      request.parameters as? [String: String],
      ["fields": ""],
      "Should create a graph request with the expected parameters"
    )
    XCTAssertEqual(
      request.flags,
      [.doNotInvalidateTokenOnError, .disableErrorRecovery],
      "Should create a graph request with the expected flags"
    )

    XCTAssertEqual(
      request.startCallCount,
      1,
      "Should start the graph request"
    )
  }

  func testCompletingUpdateWithUniqueIdentifiers() throws {
    _ = try seedRecipientIdentifiers()

    XCTAssertEqual(
      cache.recipientIDs,
      Set(SampleData.validRecipientIDs),
      "Should set the recipient identifiers from the response"
    )
  }

  func testCompletingUpdateWithDuplicateIdentifiers() throws {
    initiateFrictionlessRequest()
    let request = try XCTUnwrap(graphRequestFactory.capturedRequests.first)
    let result = createValidResult(withRecipientIDs: SampleData.duplicateRecipientIDs)

    request.capturedCompletionHandler?(nil, result, nil)

    XCTAssertEqual(
      cache.recipientIDs,
      Set(SampleData.validRecipientIDs),
      "Should omit duplicate entries when setting the recipient identifiers from the response"
    )
  }

  func testCompletingUpdateWithEmptyListOfIdentifiers() throws {
    let request = try seedRecipientIdentifiers()

    // Call the completion again with an empty list
    request.capturedCompletionHandler?(nil, createValidResult(withRecipientIDs: []), nil)

    XCTAssertTrue(
      cache.recipientIDs.isEmpty,
      "Should overwrite the existing identifiers with whatever identifiers are in the response"
    )

    // Call the completion again with a non-empty list
    request.capturedCompletionHandler?(nil, createValidResult(), nil)

    XCTAssertFalse(
      cache.recipientIDs.isEmpty,
      "Should overwrite the existing identifiers with whatever identifiers are in the response"
    )
  }

  func testCompletingUpdateWithError() throws {
    let request = try seedRecipientIdentifiers()

    // Call the completion again with an error
    request.capturedCompletionHandler?(nil, nil, SampleError())

    XCTAssertFalse(
      cache.recipientIDs.isEmpty,
      "Should not clear the cache when receiving a server error"
    )
  }

  func testCompletingUpdateWithInvalidData() throws {
    let request = try seedRecipientIdentifiers()

    // Call the completion again with invalid data
    let result = ["data": "bad"]
    request.capturedCompletionHandler?(nil, result, nil)

    XCTAssertTrue(
      cache.recipientIDs.isEmpty,
      "Should clear the cached identifiers when receiving a bad result"
    )
  }

  func testUpdatingWithoutFrictionlessUpdates() throws {
    _ = try seedRecipientIdentifiers()

    cache.update(results: [:])

    XCTAssertEqual(
      cache.recipientIDs,
      Set(SampleData.validRecipientIDs),
      "Updating with empty results should not affect the cached recipient identifiers"
    )
  }

  // MARK: - Frictionless Recipient Checking

  func testRecipientsAreFrictionlessWithoutRecipients() throws {
    _ = try seedRecipientIdentifiers()

    XCTAssertFalse(cache.recipientsAreFrictionless(nil), "Missing recipients are not frictionless")
  }

  func testRecipientsAreFrictionlessWithStringArrayOfRecipients() throws {
    _ = try seedRecipientIdentifiers()

    XCTAssertFalse(
      cache.recipientsAreFrictionless(["cde", "456"]),
      "Recipients that are not cached are not frictionless"
    )
    XCTAssertTrue(
      cache.recipientsAreFrictionless(SampleData.validRecipientIDs),
      "Cached recipients are frictionless"
    )
  }

  func testRecipientsAreFrictionlessWithCommaSeparatedStringOfRecipients() throws {
    _ = try seedRecipientIdentifiers()

    XCTAssertFalse(
      cache.recipientsAreFrictionless("cde,456"),
      "Recipients that are not cached are not frictionless"
    )
    XCTAssertTrue(
      cache.recipientsAreFrictionless(
        SampleData.validRecipientIDs.joined(separator: ",")
      ),
      "Cached recipients are frictionless"
    )
  }

  func testRecipientsAreFrictionlessWithInvalidTypeOfRecipients() throws {
    _ = try seedRecipientIdentifiers()

    [
      true,
      false,
      1,
      Set(["foo"]),
      ["name": "123"],
    ]
      .forEach { value in
        XCTAssertFalse(cache.recipientsAreFrictionless(value))
      }
  }

  // MARK: - Notification Handling

  func testSubscribesToAccessTokenChangeNotifications() throws {
    let invocation = try XCTUnwrap(
      notificationCenter.capturedAddObserverInvocations.first,
      "A notification should be observed upon initialization"
    )

    XCTAssertEqual(
      invocation,
      .init(observer: nil, name: .AccessTokenDidChange, selector: nil, object: nil),
      "Should observe the correct notification upon initialization"
    )
  }

  func testReceivingNotificationThatAccessTokenChanged() throws {
    _ = try seedRecipientIdentifiers()

    let notification = Notification(
      name: .AccessTokenDidChange,
      object: nil,
      userInfo: [AccessTokenDidChangeUserIDKey: true]
    )
    notificationCenter.capturedCompletions.first?(notification)

    XCTAssertTrue(cache.recipientIDs.isEmpty, "A change in access token should clear the cache.")

    XCTAssertEqual(
      graphRequestFactory.capturedRequests.count,
      2,
      "A change in access token should prompt a new request"
    )
  }

  func testReceivingNotificationThatAccessTokenDidNotChange() throws {
    _ = try seedRecipientIdentifiers()

    let notification = Notification(
      name: .AccessTokenDidChange,
      object: nil,
      userInfo: [AccessTokenDidChangeUserIDKey: false]
    )
    notificationCenter.capturedCompletions.first?(notification)

    XCTAssertFalse(
      cache.recipientIDs.isEmpty,
      "A change in access token that does not change the user ID should not clear the cache."
    )

    XCTAssertEqual(
      graphRequestFactory.capturedRequests.count,
      1,
      "A change in access token that does not change the user ID should not prompt a new request"
    )
  }

  func testReceivingNotificationThatAccessTokenExpired() throws {
    _ = try seedRecipientIdentifiers()

    let notification = Notification(name: .AccessTokenDidChange, object: nil, userInfo: [AccessTokenDidExpireKey: true])
    notificationCenter.capturedCompletions.first?(notification)

    XCTAssertFalse(
      cache.recipientIDs.isEmpty,
      "A change in access token that does not change the user ID should not clear the cache."
    )

    XCTAssertEqual(
      graphRequestFactory.capturedRequests.count,
      1,
      "A change in access token that does not change the user ID should not prompt a new request"
    )
  }

  // MARK: - Helpers

  private enum SampleData {
    static var validRecipientIDs: [String] {
      ["abc", "123"]
    }

    static var duplicateRecipientIDs: [String] {
      validRecipientIDs + validRecipientIDs
    }
  }

  private func createValidResult(
    withRecipientIDs recipientIDs: [String]? = SampleData.validRecipientIDs
  ) -> [String: Any] {
    [
      "data": [
        "recipient_id": recipientIDs,
      ],
    ]
  }

  private func initiateFrictionlessRequest(usesValidAccessToken: Bool = true) {
    if usesValidAccessToken {
      TestAccessTokenWallet.stubbedCurrentAccessToken = SampleAccessTokens.validToken
    }

    cache.update(results: ["updated_frictionless": true])
  }

  /// Seeds valid recipient identifiers on the cache and returns the test graph request
  private func seedRecipientIdentifiers(
    _ file: StaticString = #file,
    _ line: UInt = #line
  ) throws -> TestGraphRequest {
    initiateFrictionlessRequest()
    let request = try XCTUnwrap(
      graphRequestFactory.capturedRequests.first,
      file: file,
      line: line
    )
    request.capturedCompletionHandler?(nil, createValidResult(), nil)
    XCTAssertFalse(
      cache.recipientIDs.isEmpty,
      "Should set valid identifiers",
      file: file,
      line: line
    )

    return request
  }
}


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

import FBSDKCoreKit
import TestTools
import XCTest

final class GameRequestURLProviderTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  var internalUtility: TestTools.TestInternalUtility!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    internalUtility = .init()

    GameRequestURLProvider.setDependencies(
      .init(
        accessTokenWallet: TestAccessTokenWallet.self,
        authenticationTokenWallet: TestAuthenticationTokenWallet.self,
        appAvailabilityChecker: internalUtility
      )
    )
  }

  override func tearDown() {
    internalUtility = nil
    TestAccessTokenWallet.reset()
    TestAuthenticationTokenWallet.reset()
    GameRequestURLProvider.resetDependencies()

    super.tearDown()
  }

  // MARK: - Dependencies

  func testCustomDependencies() throws {
    let dependencies = try GameRequestURLProvider.getDependencies()

    XCTAssertIdentical(
      dependencies.accessTokenWallet,
      TestAccessTokenWallet.self,
      .Dependencies.customDependency(for: "access token wallet")
    )
    XCTAssertIdentical(
      dependencies.authenticationTokenWallet,
      TestAuthenticationTokenWallet.self,
      .Dependencies.customDependency(for: "authentication token wallet")
    )
    XCTAssertIdentical(
      dependencies.appAvailabilityChecker,
      internalUtility,
      .Dependencies.customDependency(for: "app availability checker")
    )
  }

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

    XCTAssertIdentical(
      dependencies.accessTokenWallet,
      AccessToken.self,
      .Dependencies.defaultDependency("AccessToken", for: "access token wallet")
    )
    XCTAssertIdentical(
      dependencies.authenticationTokenWallet,
      AuthenticationToken.self,
      .Dependencies.defaultDependency("AuthenticationToken", for: "authentication token wallet")
    )
    XCTAssertIdentical(
      dependencies.appAvailabilityChecker,
      InternalUtility.shared,
      .Dependencies.defaultDependency("InternalUtility", for: "app availability checker")
    )
  }

  // MARK: - Action Type Name

  func testActionTypeName() {
    [
      (GameRequestActionType.askFor, "askfor"),
      (.invite, "invite"),
      (.send, "send"),
      (.turn, "turn"),
    ]
      .forEach { pair in
        XCTAssertEqual(
          GameRequestURLProvider.actionTypeName(for: pair.0),
          pair.1,
          "The name of the action type: \(pair.0) should be: \(pair.1)"
        )
      }
    XCTAssertNil(
      GameRequestURLProvider.actionTypeName(for: .none),
      "The `none` action type should not have a name"
    )
  }

  func testSimpleFilterNames() {
    [
      (GameRequestFilter.appUsers, "app_users"),
      (.appNonUsers, "app_non_users"),
    ]
      .forEach { pair in
        XCTAssertEqual(
          GameRequestURLProvider.filtersName(for: pair.0),
          pair.1,
          "The name of the filter: \(pair.0) should be: \(pair.1)"
        )
      }
  }

  func testFilterNameForEverybodyWithoutGamingDomain() {
    TestAuthenticationTokenWallet.current = SampleAuthenticationToken.validToken(withGraphDomain: "facebook")

    XCTAssertNil(
      GameRequestURLProvider.filtersName(for: .everybody),
      "Should not have a filter name for `everybody` when the graph domain is not gaming"
    )
  }

  func testFilterNameForEverybodyWithGamingDomainWithoutFacebookAppInstalled() {
    TestAuthenticationTokenWallet.current = SampleAuthenticationToken.validToken(withGraphDomain: "gaming")
    internalUtility.isFacebookAppInstalled = false

    XCTAssertNil(
      GameRequestURLProvider.filtersName(for: .everybody),
      "Should not have a filter name for `everybody` when the facebook app is not installed"
    )
  }

  func testFilterNameForEverybodyWithGamingDomainWithFacebookAppInstalled() {
    TestAuthenticationTokenWallet.current = SampleAuthenticationToken.validToken(withGraphDomain: "gaming")
    internalUtility.isFacebookAppInstalled = true

    XCTAssertEqual(
      GameRequestURLProvider.filtersName(for: .everybody),
      "everybody",
      "Should have a filter name for `everybody` when the graph domain is gaming and the facebook app is installed"
    )
  }

  func testFilterNameForNoneFilter() {
    XCTAssertNil(GameRequestURLProvider.filtersName(for: .none), "Should not have a filter name for the `none` filter")
  }

  func testCreatingDeepLinkURLWithAccessToken() throws {
    TestAccessTokenWallet.current = SampleAccessTokens.validToken

    let rawQueries: [String: Any] = [
      "foo": "bar",
      "baz": 1,
    ]

    let url = try XCTUnwrap(
      GameRequestURLProvider.createDeepLinkURL(queryDictionary: rawQueries),
      "Should create a valid deep link url"
    )

    XCTAssertEqual(
      url.scheme,
      "https",
      "Should use the expected url scheme"
    )
    XCTAssertEqual(
      url.host,
      "fb.gg",
      "Should use the expected url host"
    )
    XCTAssertEqual(
      url.path,
      "/game_requestui/appID123",
      "The path should include the app ID from the access token"
    )
    XCTAssertEqual(
      url.query,
      "foo=bar",
      "Should omit any query items with non-String values"
    )
  }

  func testCreatingDeepLinkURLWithoutAccessToken() throws {
    let url = try XCTUnwrap(
      GameRequestURLProvider.createDeepLinkURL(queryDictionary: [:]),
      "Should create a valid deep link url"
    )

    XCTAssertEqual(
      url.path,
      "/game_requestui",
      "Should omit the app ID from the path when it is not available"
    )
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  enum Dependencies {
    static func defaultDependency(_ dependency: String, for type: String) -> String {
      "A GameRequestURLProvider type uses \(dependency) as its \(type) dependency by default"
    }

    static func customDependency(for type: String) -> String {
      "A GameRequestURLProvider type uses a custom \(type) dependency when provided"
    }
  }
}


/*
 * 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
@testable import FBSDKGamingServicesKit
import XCTest

final class GamingContextPayloadObserverTests: XCTestCase {
  // swiftlint:disable implicitly_unwrapped_optional
  var gamingContextDelegate: GamingContextPayloadObserverDelegate!
  var gamingContextObserver: GamingPayloadObserver!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    gamingContextDelegate = GamingContextPayloadObserverDelegate()
    gamingContextObserver = GamingPayloadObserver(delegate: gamingContextDelegate)
  }

  override func tearDown() {
    gamingContextDelegate = nil
    gamingContextObserver = nil

    super.tearDown()
  }

  // MARK: - GamingContextObserver

  func testCreatingGamingContextObserver() {
    XCTAssertTrue(
      gamingContextObserver.delegate === gamingContextDelegate,
      "Should store the delegate it was created with"
    )
    XCTAssertTrue(
      ApplicationDelegate.shared.applicationObservers.contains(gamingContextObserver),
      "Should observe the shared application delegate upon creation"
    )
  }

  func testGamingContextObserverOpeningInvalid() throws {
    let url = try XCTUnwrap(URL(string: "file://foo"))
    XCTAssertFalse(
      gamingContextObserver.application(
        UIApplication.shared,
        open: url,
        sourceApplication: nil,
        annotation: nil
      ),
      "Should not successfully open an invalid url"
    )

    XCTAssertFalse(
      gamingContextDelegate.wasGamingContextDelegateCalled,
      "Should not invoke the delegate method parsedGamingContextURLContaining for an invalid url"
    )
  }

  func testGamingContextObserverOpeningURLWithMissingKeys() throws {
    let url = try SampleUnparsedAppLinkURLs.missingKeys()

    XCTAssertFalse(
      gamingContextObserver.application(
        UIApplication.shared,
        open: url,
        sourceApplication: nil,
        annotation: nil
      ),
      "Should not successfully open a url with missing extras"
    )

    XCTAssertFalse(
      gamingContextDelegate.wasGamingContextDelegateCalled,
      "Should not invoke the delegate method parsedGamingContextURLContaining for an invalid url"
    )
  }

  func testOpeningURLWithMissingGameContextTokenID() throws {
    let url = try SampleUnparsedAppLinkURLs.create(contextTokenID: nil)
    XCTAssertFalse(
      gamingContextObserver.application(
        UIApplication.shared,
        open: url,
        sourceApplication: nil,
        annotation: nil
      ),
      "Should not successfully open a url with a missing game request ID"
    )
    XCTAssertFalse(
      gamingContextDelegate.wasGamingContextDelegateCalled,
      "Should not invoke the delegate method parsedGamingContextURLContaining for an invalid url"
    )
  }

  func testGamingContextObserverOpeningURLWithMissingPayload() throws {
    let url = try SampleUnparsedAppLinkURLs.create(payload: nil)
    XCTAssertFalse(
      gamingContextObserver.application(
        UIApplication.shared,
        open: url,
        sourceApplication: nil,
        annotation: nil
      ),
      "Should not successfully open a url with a missing payload"
    )
    XCTAssertFalse(
      gamingContextDelegate.wasGamingContextDelegateCalled,
      "Should not invoke the delegate method parsedGamingContextURLContaining for an invalid url"
    )
  }

  func testOpeningWithValidGamingContextURL() throws {
    let url = try SampleUnparsedAppLinkURLs.validGamingContextUrl()
    XCTAssertTrue(
      gamingContextObserver.application(
        UIApplication.shared,
        open: url,
        sourceApplication: nil,
        annotation: nil
      ),
      "Should successfully open a url with a valid payload"
    )

    XCTAssertTrue(
      gamingContextDelegate.wasGamingContextDelegateCalled,
      "Should invoke the delegate method parsedGamingContextURLContaining for a url with a valid payload"
    )
    XCTAssertEqual(
      gamingContextDelegate.capturedPayload?.payload,
      SampleUnparsedAppLinkURLs.Values.payload,
      "Should invoke the delegate with the expected payload"
    )
  }
}

final class GamingContextPayloadObserverDelegate: NSObject, GamingPayloadDelegate {
  var wasGamingContextDelegateCalled = false
  var capturedPayload: GamingPayload?

  func parsedGamingContextURLContaining(_ payload: GamingPayload) {
    wasGamingContextDelegateCalled = true
    capturedPayload = payload
  }
}


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

final class GamingContextTests: XCTestCase {

  func testCreating() throws {
    let context = try XCTUnwrap(GamingContext(identifier: name, size: 2))
    XCTAssertNotNil(
      context,
      "Should be able to create a context with a valid identifier"
    )
    XCTAssertEqual(
      context.identifier,
      name,
      "Should be able to create a context with a valid identifier"
    )
    XCTAssertEqual(
      context.size,
      2,
      "Should be able to create a context with a valid size"
    )
  }

  func testCreatingWithSizeLessThanZero() throws {
    let context = try XCTUnwrap(GamingContext(identifier: name, size: -2))
    XCTAssertNotNil(
      context,
      "Should be able to create a context with a valid identifier"
    )
    XCTAssertEqual(
      context.identifier,
      name,
      "Should be able to create a context with a valid identifier"
    )
    XCTAssertEqual(
      context.size,
      0,
      "Should not set size less than 0"
    )
  }

  func testCreatingWithEmptyIdentifier() {
    let context = GamingContext(identifier: "", size: 2)
    XCTAssertNil(
      context,
      "Should not be able to create a context with a invalid identifier"
    )
  }
}


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

import FBSDKCoreKit
import TestTools
import XCTest

@testable import FBSDKGamingServicesKit

final class GamingGroupIntegrationTests: XCTestCase {

  let factory = TestGamingServiceControllerFactory()
  let settings = TestSettings()
  lazy var integration = GamingGroupIntegration(
    settings: settings,
    serviceControllerFactory: factory
  )

  func testDefaultDependencies() {
    XCTAssertTrue(
      GamingGroupIntegration().settings is Settings,
      "Should have a default settings of the expected type"
    )
    XCTAssertTrue(
      GamingGroupIntegration().serviceControllerFactory is GamingServiceControllerFactory,
      "Should have a default service controller factory of the expected type"
    )
  }

  func testCustomDependencies() {
    XCTAssertEqual(
      integration.serviceControllerFactory as? TestGamingServiceControllerFactory,
      factory,
      "Should be able to create with a custom service controller factory"
    )
  }

  func testOpeningPageWithMissingIdentifier() {
    settings.appID = nil
    integration.openGroupPage { _, _ in }

    XCTAssertEqual(
      factory.controller.capturedArgument,
      settings.appID,
      "Should call the controller with the app identifier from the settings"
    )
  }

  func testOpeningPageWithIdentifier() {
    settings.appID = name
    integration.openGroupPage { _, _ in }

    XCTAssertEqual(
      factory.controller.capturedArgument,
      settings.appID,
      "Should call the controller with the app identifier from the settings"
    )
  }

  func testOpeningPageWithErrorAndFailure() throws {
    var didInvokeCompletion = false
    integration.openGroupPage { success, error in
      XCTAssertFalse(
        success,
        "Should not be considered successful if there is a service controller error"
      )
      XCTAssertTrue(
        error is SampleError,
        "Should pass through the error from the service controller"
      )
      didInvokeCompletion = true
    }

    let completion = try XCTUnwrap(factory.capturedCompletion)
    completion(false, nil, SampleError())

    XCTAssertTrue(didInvokeCompletion)
  }

  func testOpeningPageWithSuccessOrFailureOnly() throws {
    try [true, false].forEach { didSucceed in
      var didInvokeCompletion = false
      integration.openGroupPage { success, error in
        XCTAssertEqual(
          success,
          didSucceed,
          "Should pass through the success response from the service controller"
        )
        XCTAssertNil(error)
        didInvokeCompletion = true
      }

      let completion = try XCTUnwrap(factory.capturedCompletion)
      completion(didSucceed, nil, nil)

      XCTAssertTrue(didInvokeCompletion)
    }
  }

  func testOpeningPageWithSuccessAndError() throws {
    var didInvokeCompletion = false
    integration.openGroupPage { success, error in
      XCTAssertTrue(
        success,
        "Should pass through the success response from the service controller even if there is an error"
      )
      XCTAssertTrue(
        error is SampleError,
        "Should pass through the error from the service controller"
      )
      didInvokeCompletion = true
    }

    let completion = try XCTUnwrap(factory.capturedCompletion)
    completion(true, nil, SampleError())

    XCTAssertTrue(didInvokeCompletion)
  }
}


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

import FBSDKCoreKit
import TestTools
import XCTest

final class GamingImageUploaderTests: XCTestCase {

  let factory = TestGamingServiceControllerFactory()
  let connection = TestGraphRequestConnection()
  let graphConnectionFactory = TestGraphRequestConnectionFactory()
  lazy var uploader = GamingImageUploader(
    gamingServiceControllerFactory: factory,
    graphRequestConnectionFactory: graphConnectionFactory
  )
  lazy var configuration = createConfiguration(shouldLaunch: true)

  override func setUp() {
    super.setUp()

    AccessToken.current = SampleAccessTokens.validToken
    graphConnectionFactory.stubbedConnection = connection
  }

  override func tearDown() {
    AccessToken.current = nil

    super.tearDown()
  }

  // MARK: - Dependencies

  func testDefaultDependencies() {
    XCTAssertTrue(
      GamingImageUploader.shared.factory is GamingServiceControllerFactory,
      "Should use the expected default gaming service controller factory"
    )
    XCTAssertTrue(
      GamingImageUploader.shared.graphRequestConnectionFactory is GraphRequestConnectionFactory,
      "Should use the expected default graph request connection factory"
    )
  }

  func testCustomDependencies() {
    XCTAssertTrue(
      uploader.factory is TestGamingServiceControllerFactory,
      "Should use the expected gaming service controller factory"
    )
    XCTAssertTrue(
      uploader.graphRequestConnectionFactory is TestGraphRequestConnectionFactory,
      "Should use the expected graph request connection factory"
    )
  }

  // MARK: - Configuration

  func testValuesAreSavedToConfiguration() throws {
    let image = createSampleUIImage()
    let configuration = GamingImageUploaderConfiguration(
      image: image,
      caption: "Cool Photo",
      shouldLaunchMediaDialog: true
    )

    XCTAssertEqual(configuration.caption, "Cool Photo")
    XCTAssertEqual(configuration.image.pngData(), image.pngData())
    XCTAssertTrue(configuration.shouldLaunchMediaDialog)
  }

  // MARK: - Uploading

  func testFailureWhenNoValidAccessTokenPresent() {
    AccessToken.current = nil

    var wasCompletionCalled = false
    uploader.uploadImage(with: configuration) { _, _, error in
      XCTAssertEqual(
        (error as NSError?)?.code,
        CoreError.errorAccessTokenRequired.rawValue,
        "Expected error requiring a valid access token"
      )
      wasCompletionCalled = true
    }

    XCTAssertTrue(wasCompletionCalled)
  }

  func testGraphErrorsAreHandled() throws {
    var wasCompletionCalled = false
    uploader.uploadImage(with: configuration) { success, results, error in
      XCTAssertFalse(success)
      XCTAssertNil(results)
      XCTAssertEqual(
        (error as NSError?)?.code,
        CoreError.errorGraphRequestGraphAPI.rawValue,
        "Should indicate that the error was related to the graph request"
      )
      wasCompletionCalled = true
    }

    let completion = try XCTUnwrap(
      (graphConnectionFactory.stubbedConnection as? TestGraphRequestConnection)?.capturedCompletion
    )
    completion(nil, nil, SampleError())

    XCTAssertTrue(wasCompletionCalled)
  }

  func testGraphResponsesTriggerCompletionIfDialogNotRequested() throws {
    let expectedID = "111"
    let expectedResult = ["id": expectedID]
    let expectedDialogResult = [String: String]()
    let expectedError = NSError(domain: CoreError.errorDomain, code: CoreError.errorUnknown.rawValue, userInfo: nil)

    var wasCompletionInvoked = false
    uploader.uploadImage(with: configuration) { success, result, error in
      XCTAssertTrue(success)
      XCTAssertEqual(error as NSError?, expectedError)
      XCTAssertEqual(result as? [String: String], expectedDialogResult)
      wasCompletionInvoked = true
    }

    let completion = try XCTUnwrap(
      (graphConnectionFactory.stubbedConnection as? TestGraphRequestConnection)?.capturedCompletion
    )
    completion(nil, expectedResult, nil)

    factory.capturedCompletion(true, [:], expectedError)

    XCTAssertEqual(
      factory.capturedServiceType,
      .mediaAsset,
      "Should create a controller with the expected service type"
    )
    XCTAssertEqual(
      factory.capturedPendingResult as? [String: String],
      expectedResult,
      "Should create a controller with a pending result"
    )
    XCTAssertEqual(
      factory.controller.capturedArgument,
      expectedID,
      "Should invoke the new controller with the id from the result"
    )

    XCTAssertTrue(wasCompletionInvoked)
  }

  func testGraphResponsesDoNotTriggerCompletionIfDialogIsRequested() throws {
    var wasCompletionInvoked = false
    uploader.uploadImage(with: configuration) { _, _, _ in
      wasCompletionInvoked = true
    }

    let completion = try XCTUnwrap(
      (graphConnectionFactory.stubbedConnection as? TestGraphRequestConnection)?.capturedCompletion
    )
    completion(nil, ["id": "123"], nil)

    XCTAssertFalse(
      wasCompletionInvoked,
      "Callback should not have been called because there was more work to do"
    )
  }

  func testGraphResponsesTriggerDialogIfDialogIsRequested() throws {
    let expectedID = "111"
    let expectedResult = ["id": expectedID]

    var didInvokeCompletion = false
    uploader.uploadImage(with: configuration) { _, _, _ in
      didInvokeCompletion = true
    }

    let completion = try XCTUnwrap(
      (graphConnectionFactory.stubbedConnection as? TestGraphRequestConnection)?.capturedCompletion
    )
    completion(nil, expectedResult, nil)

    XCTAssertFalse(
      didInvokeCompletion,
      "Should not invoke the completion because a dialog is launched instead"
    )

    XCTAssertEqual(
      factory.capturedServiceType,
      .mediaAsset,
      "Should create a controller with the expected service type"
    )
    XCTAssertEqual(
      factory.capturedPendingResult as? [String: String],
      expectedResult,
      "Should not create a controller with a pending result"
    )
    XCTAssertEqual(
      factory.controller.capturedArgument,
      expectedID,
      "Should invoke the new controller with the id from the result"
    )

    factory.capturedCompletion(true, nil, nil)

    XCTAssertTrue(didInvokeCompletion)
  }

  func testUploadProgress() throws {
    let expectedResult = ["id": "foo"]
    var wasCompletionInvoked = false
    var wasProgressHandlerInvoked = false
    uploader.uploadImage(
      with: configuration,
      completion: { success, result, error in
        XCTAssertTrue(success)
        XCTAssertEqual(
          result?["id"] as? String,
          expectedResult["id"]
        )
        XCTAssertNil(error)
        wasCompletionInvoked = true
      },
      andProgressHandler: { bytesSent, totalBytesSent, totalBytesExpectedToSend in
        XCTAssertEqual(bytesSent, 123)
        XCTAssertEqual(totalBytesSent, 456)
        XCTAssertEqual(totalBytesExpectedToSend, 789)
        wasProgressHandlerInvoked = true
      }
    )

    connection.delegate?.requestConnection?(
      connection,
      didSendBodyData: 123,
      totalBytesWritten: 456,
      totalBytesExpectedToWrite: 789
    )
    XCTAssertTrue(wasProgressHandlerInvoked)

    let completion = try XCTUnwrap(
      (graphConnectionFactory.stubbedConnection as? TestGraphRequestConnection)?.capturedCompletion
    )
    completion(nil, expectedResult, nil)
    factory.capturedCompletion(true, expectedResult, nil)

    XCTAssertTrue(wasCompletionInvoked)
  }

  // MARK: - Helpers

  func createSampleUIImage() -> UIImage {
    UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
    UIColor.red.setFill()
    UIRectFill(CGRect(x: 0, y: 0, width: 1, height: 1))
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return image! // swiftlint:disable:this force_unwrapping
  }

  func createConfiguration(shouldLaunch: Bool) -> GamingImageUploaderConfiguration {
    GamingImageUploaderConfiguration(
      image: createSampleUIImage(),
      caption: "Cool Photo",
      shouldLaunchMediaDialog: shouldLaunch
    )
  }
}


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

import FBSDKCoreKit
import XCTest

final class GamingPayloadTests: XCTestCase {

  func testGamePayloadWithValidValues() throws {
    let url = try SampleUnparsedAppLinkURLs.validGameRequestUrl()
    let appLink = AppLinkURL(url: url)
    let gamingPayload = GamingPayload(URL: appLink)
    XCTAssertEqual("123", gamingPayload.gameRequestID)
    XCTAssertEqual("payload", gamingPayload.payload)
  }

  func testGamingPayloadWithNonAlphanumericStrings() throws {
    let url = try SampleUnparsedAppLinkURLs.create(payload: "{}{}{}", gameRequestID: "{}{}{}")
    let appLink = AppLinkURL(url: url)
    let gamingPayload = GamingPayload(URL: appLink)
    XCTAssertEqual(
      "{}{}{}",
      gamingPayload.gameRequestID,
      "Should be able to return a gameRequestID with non alphanumeric values"
    )
    XCTAssertEqual(
      "{}{}{}",
      gamingPayload.payload,
      "Should be able to return a payload with non alphanumeric values"
    )
  }

  func testGamingPayloadWithNilInPayload() throws {
    let url = try SampleUnparsedAppLinkURLs.create(payload: nil)
    let appLink = AppLinkURL(url: url)
    let gamingPayload = GamingPayload(URL: appLink)
    XCTAssertEqual(
      "123",
      gamingPayload.gameRequestID,
      "Should be able to return a gameRequestID with nil payload"
    )
    XCTAssertEqual("", gamingPayload.payload)
  }

  func testGamingPayloadWithNilInGameRequestID() throws {
    let url = try SampleUnparsedAppLinkURLs.create(gameRequestID: nil)
    let appLink = AppLinkURL(url: url)
    let gamingPayload = GamingPayload(URL: appLink)
    XCTAssertEqual("", gamingPayload.gameRequestID)
    XCTAssertEqual(
      "payload",
      gamingPayload.payload,
      "Should be able to return a payload with nil gameRequestID"
    )
  }

  func testGamingPayloadWithBothParamsNil() throws {
    let url = try SampleUnparsedAppLinkURLs.missingKeys()
    let appLink = AppLinkURL(url: url)
    let gamingPayload = GamingPayload(URL: appLink)
    XCTAssertEqual(
      "",
      gamingPayload.gameRequestID,
      "Should be able to return an empty payload with both nil params"
    )
    XCTAssertEqual(
      "",
      gamingPayload.payload,
      "Should be able to return an empty payload with both nil params"
    )
  }

  func testGamingPayloadWithExtraSpaceInString() throws {
    let url = try SampleUnparsedAppLinkURLs.create(
      payload: "  ",
      gameRequestID: "  "
    )
    let appLink = AppLinkURL(url: url)
    let gamingPayload = GamingPayload(URL: appLink)
    XCTAssertEqual(
      "  ",
      gamingPayload.gameRequestID,
      "Should be able to return a string with extra space in gameRequestID"
    )
    XCTAssertEqual(
      "  ",
      gamingPayload.payload,
      "Should be able to return a string with extra space in payload"
    )
  }
}


/*
 * 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
@testable import FBSDKGamingServicesKit

import XCTest

final class GamingRequestPayloadObserverTests: XCTestCase {

  let gameRequestDelegate = GameRequestPayloadObserverDelegate()
  lazy var gameRequestObserver = GamingPayloadObserver(delegate: gameRequestDelegate)
  var capturedPayload: GamingPayload?

  func testCreatingObserver() {
    XCTAssertTrue(
      gameRequestObserver.delegate === gameRequestDelegate,
      "Should store the delegate it was created with"
    )
    XCTAssertTrue(
      ApplicationDelegate.shared.applicationObservers.contains(gameRequestObserver),
      "Should observe the shared application delegate upon creation"
    )
  }

  func testOpeningInvalidURL() throws {
    let url = try XCTUnwrap(URL(string: "file://foo"))
    XCTAssertFalse(
      gameRequestObserver.application(
        UIApplication.shared,
        open: url,
        sourceApplication: nil,
        annotation: nil
      ),
      "Should not successfully open an invalid url"
    )

    XCTAssertFalse(
      gameRequestDelegate.wasGameRequestDelegateCalled,
      "Should not invoke the delegate method parsedGameRequestURLContaining for an invalid url"
    )
  }

  func testOpeningURLWithMissingKeys() throws {
    let url = try SampleUnparsedAppLinkURLs.missingKeys()

    XCTAssertFalse(
      gameRequestObserver.application(
        UIApplication.shared,
        open: url,
        sourceApplication: nil,
        annotation: nil
      ),
      "Should not successfully open a url with missing extras"
    )

    XCTAssertFalse(
      gameRequestDelegate.wasGameRequestDelegateCalled,
      "Should not invoke the delegate method parsedGameRequestURLContaining for an invalid url"
    )
  }

  func testOpeningURLWithMissingGameRequestID() throws {
    let url = try SampleUnparsedAppLinkURLs.create(gameRequestID: nil)
    XCTAssertFalse(
      gameRequestObserver.application(
        UIApplication.shared,
        open: url,
        sourceApplication: nil,
        annotation: nil
      ),
      "Should not successfully open a url with a missing game request ID"
    )
    XCTAssertFalse(
      gameRequestDelegate.wasGameRequestDelegateCalled,
      "Should not invoke the delegate method parsedGameRequestURLContaining for an invalid url"
    )
  }

  func testOpeningURLWithMissingPayload() throws {
    let url = try SampleUnparsedAppLinkURLs.create(payload: nil)
    XCTAssertFalse(
      gameRequestObserver.application(
        UIApplication.shared,
        open: url,
        sourceApplication: nil,
        annotation: nil
      ),
      "Should not successfully open a url with a missing payload"
    )
    XCTAssertFalse(
      gameRequestDelegate.wasGameRequestDelegateCalled,
      "Should not invoke the delegate method parsedGameRequestURLContaining for an invalid url"
    )
  }

  func testOpeningWithValidGameRequestURL() throws {
    let url = try SampleUnparsedAppLinkURLs.validGameRequestUrl()
    XCTAssertTrue(
      gameRequestObserver.application(
        UIApplication.shared,
        open: url,
        sourceApplication: nil,
        annotation: nil
      ),
      "Should successfully open a url with a valid payload"
    )
    XCTAssertTrue(
      gameRequestDelegate.wasGameRequestDelegateCalled,
      "Should invoke the delegate method parsedGameRequestURLContaining for a url with a valid payload"
    )
    XCTAssertEqual(
      gameRequestDelegate.capturedPayload?.payload,
      SampleUnparsedAppLinkURLs.Values.payload,
      "Should invoke the delegate with the expected payload"
    )
    XCTAssertEqual(
      gameRequestDelegate.capturedGameRequestID,
      SampleUnparsedAppLinkURLs.Values.gameRequestID,
      "Should invoke the delegate with the expected game request ID"
    )
  }
}

final class GameRequestPayloadObserverDelegate: NSObject, GamingPayloadDelegate {
  var wasGameRequestDelegateCalled = false
  var capturedGameRequestID: String?
  var capturedPayload: GamingPayload?

  func parsedGameRequestURLContaining(_ payload: GamingPayload, gameRequestID: String) {
    wasGameRequestDelegateCalled = true
    capturedGameRequestID = gameRequestID
    capturedPayload = payload
  }
}


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

import FBSDKCoreKit
import TestTools
import XCTest

final class GamingServiceControllerTests: XCTestCase {

  let urlOpener = TestURLOpener()
  let settings = TestSettings()
  let url = URL(string: "fb123://community/")! // swiftlint:disable:this force_unwrapping
  var capturedSuccess = false
  var capturedResults = [Any]()
  var serviceError: Error?
  lazy var gamingService = GamingServiceController(
    serviceType: .community,
    pendingResult: [:],
    urlOpener: urlOpener,
    settings: settings
  ) { success, _, error in
    self.capturedSuccess = success
    self.serviceError = error
  }

  override func setUp() {
    super.setUp()

    settings.appID = "123"
  }

  func testSuccessfullyOpeningURL() {
    _ = gamingService.application(.shared, open: url, sourceApplication: "", annotation: "")
    XCTAssertTrue(
      capturedSuccess,
      "Should complete successfully if the url is formatted correctly."
    )
  }

  func testIsNotGamingURLFromParameters() {
    let url = URL(string: "f://c/")
    let isGamingURL = gamingService.application(UIApplication.shared, open: url, sourceApplication: "", annotation: "")
    XCTAssertFalse(
      capturedSuccess,
      "Should not complete successfully if the url is formatted incorrectly."
    )
    XCTAssertFalse(isGamingURL)
  }

  func testInvalidGamingURLWithValidTypeNotMatchingURLSource() {
    gamingService = GamingServiceController(
      serviceType: .friendFinder,
      pendingResult: [:],
      urlOpener: urlOpener,
      settings: settings
    ) { success, _, error in
      self.capturedSuccess = success
      self.serviceError = error
    }

    _ = gamingService.application(.shared, open: url, sourceApplication: "", annotation: "")
    XCTAssertFalse(
      capturedSuccess,
      "Should not call the completionHandler with invalid format in the URL"
    )
  }

  func testIsAuthenticationURL() {
    XCTAssertFalse(gamingService.isAuthenticationURL(url))
  }

  func testAddErrorToCompletionHandler() {
    gamingService.handleBridgeAPIError(SampleError())
    XCTAssertNotNil(
      serviceError,
      "Should have an error in completion handler if we pass one"
    )
  }

  func testHandleBridgeAPIErrorWithNullCompletionHandler() {
    // calling applicationDidBecomeActive to make completionHandler null
    gamingService.applicationDidBecomeActive(UIApplication.shared)
    gamingService.handleBridgeAPIError(SampleError())
    XCTAssertNil(
      serviceError,
      "Error should be nil if completion handler is nil"
    )
  }

  func testInValidCallbackURLWithImproperString() {
    XCTAssertFalse(
      gamingService.isValidCallbackURL(url, forService: "{}{}{}"),
      "Should return false if service parameter doesn't match the service type"
    )
  }

  func testInValidCallbackURLWithImproperURL() {
    let inValidURL = URL(string: "antsarecool.com")! // swiftlint:disable:this force_unwrapping
    XCTAssertFalse(
      gamingService.isValidCallbackURL(inValidURL, forService: ""),
      "Should return false if url parameter doesn't match the fb and appID prefix"
    )
  }

  func testCallWithArgumentSuccess() throws {
    gamingService.call(withArgument: "")
    let handler = try XCTUnwrap(urlOpener.capturedRequests.first)
    handler(true, nil)

    XCTAssertNil(serviceError, "Should not have an error if success is true")
  }

  func testCallWithArgumentNoSuccess() throws {
    gamingService.call(withArgument: "")
    let handler = try XCTUnwrap(urlOpener.capturedRequests.first)
    handler(false, nil)

    XCTAssertNotNil(serviceError, "Should have an error if success is false")
  }

  func testCallWithArgumentNoSuccessAndError() throws {
    gamingService.call(withArgument: "")
    let handler = try XCTUnwrap(urlOpener.capturedRequests.first)
    handler(false, SampleError())

    XCTAssertNotNil(serviceError, "Should have an error if success is false")
  }

  func testCallWithArgumentSuccessAndErrpr() throws {
    gamingService.call(withArgument: "")
    let handler = try XCTUnwrap(urlOpener.capturedRequests.first)
    handler(true, SampleError())

    XCTAssertNil(serviceError, "Should not have an error if success is true")
  }
}


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

import XCTest

final class GamingVideoUploaderConfigurationTests: XCTestCase {

  let url = URL(string: "file://video.mp4")! // swiftlint:disable:this force_unwrapping
  let caption = "Cool Video"
  lazy var configuration = GamingVideoUploaderConfiguration(videoURL: url, caption: caption)

  func testValuesAreSavedToConfiguration() {
    XCTAssertEqual(configuration.caption, caption)
    XCTAssertEqual(configuration.videoURL, url)
  }
}


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

import FBSDKCoreKit
import TestTools
import XCTest

final class GamingVideoUploaderTests: XCTestCase {

  var videoURL = URL(string: "file://video.mp4")! // swiftlint:disable:this force_unwrapping
  lazy var configuration = createConfiguration(url: videoURL)
  let fileHandle = TestFileHandler()
  let fileHandleFactory = TestFileHandleFactory()
  let videoUploader = TestVideoUploader()
  let videoUploaderFactory = TestVideoUploaderFactory()
  lazy var uploader = GamingVideoUploader(
    fileHandleFactory: fileHandleFactory,
    videoUploaderFactory: videoUploaderFactory
  )

  override func setUp() {
    super.setUp()

    fileHandle.stubbedSeekToEndOfFile = UInt64.random(in: 1 ... 1000)
    fileHandleFactory.stubbedFileHandle = fileHandle
    videoUploaderFactory.stubbedVideoUploader = videoUploader

    AccessToken.current = SampleAccessTokens.validToken
  }

  func testDefaults() {
    XCTAssertTrue(
      GamingVideoUploader.shared.fileHandleFactory is FileHandleFactory,
      "Should have the expected file handle factory by default"
    )
    XCTAssertTrue(
      GamingVideoUploader.shared.videoUploaderFactory is VideoUploaderFactory,
      "Should have the expected video uploader factory by default"
    )
  }

  func testCreatingWithDependencies() {
    XCTAssertTrue(
      uploader.fileHandleFactory is TestFileHandleFactory,
      "Should be able to create an uploader with a custom file handle factory"
    )
    XCTAssertTrue(
      uploader.videoUploaderFactory is TestVideoUploaderFactory,
      "Should be able to create an uploader with a custom video uploader factory"
    )
  }

  func testFailureWhenNoValidAccessTokenPresent() {
    AccessToken.current = nil

    var wasCompletionCalled = false
    GamingVideoUploader.uploadVideo(configuration: configuration) { _, _, error in
      XCTAssertEqual(
        (error as NSError?)?.code,
        CoreError.errorAccessTokenRequired.rawValue,
        "Expected error requiring a valid access token"
      )
      wasCompletionCalled = true
    }

    XCTAssertTrue(wasCompletionCalled)
  }

  func testBadVideoURLFails() {
    videoURL = URL(string: "file://not-a-video.mp4")! // swiftlint:disable:this force_unwrapping

    var wasCompletionCalled = false
    GamingVideoUploader.uploadVideo(configuration: configuration) { _, _, error in
      XCTAssertEqual(
        (error as NSError?)?.code,
        CoreError.errorInvalidArgument.rawValue,
        "Expected error requiring a non nil video url"
      )
      wasCompletionCalled = true
    }
    XCTAssertTrue(wasCompletionCalled)
  }

  func testCreatesFileHandle() {
    uploader.uploadVideo(configuration: configuration) { _, _, _ in
      XCTFail("Should not invoke the completion handler")
    }

    XCTAssertEqual(
      fileHandleFactory.capturedURL,
      videoURL,
      "Should create a file handle with the url from the configuration"
    )
  }

  func testUploadingEmptyFile() {
    fileHandle.stubbedSeekToEndOfFile = 0
    fileHandleFactory.stubbedFileHandle = fileHandle

    var wasCompletionCalled = false
    uploader.uploadVideo(configuration: configuration) { _, _, error in
      XCTAssertEqual(
        (error as NSError?)?.code,
        CoreError.errorInvalidArgument.rawValue,
        "Expected error requiring a non empty video file"
      )
      wasCompletionCalled = true
    }
    XCTAssertTrue(wasCompletionCalled)
  }

  func testCreatesAndStartsUploader() {
    uploader.uploadVideo(configuration: configuration) { _, _, _ in
      XCTFail("Should not invoke the completion handler")
    }
    XCTAssertTrue(
      videoUploaderFactory.capturedDelegate is GamingVideoUploader,
      "Should create an uploader with the expected delegate type"
    )
    XCTAssertEqual(
      videoUploaderFactory.capturedVideoName,
      videoURL.lastPathComponent,
      "Should use the video url to derive the video name"
    )
    XCTAssertTrue(
      videoUploaderFactory.capturedParameters.isEmpty,
      "Should not create an uploader with parameters"
    )
    XCTAssertEqual(
      videoUploaderFactory.capturedVideoSize,
      UInt(fileHandle.fb_seekToEndOfFile()),
      "Should create an uploader with the size of the video being uploaded"
    )
    XCTAssertTrue(
      videoUploader.wasStartCalled,
      "Should start the upload"
    )
  }

  // MARK: Delegate methods

  func testUploadErrorsHandled() throws {
    var wasCompletionCalled = false
    uploader.uploadVideo(configuration: configuration) { _, _, error in
      XCTAssertTrue(error is SampleError)

      wasCompletionCalled = true
    }

    let delegate = try XCTUnwrap(videoUploaderFactory.capturedDelegate)
    let dummyUploader = VideoUploader(videoName: "dummy", videoSize: 0, parameters: [:], delegate: delegate)
    _ = delegate.videoUploader(dummyUploader, didFailWithError: SampleError())

    XCTAssertTrue(wasCompletionCalled)
  }

  func testVideoUploaderErrorOnUnsuccessful() throws {
    var wasCompletionCalled = false
    uploader.uploadVideo(configuration: configuration) { _, _, error in
      XCTAssertEqual(
        (error as NSError?)?.code,
        CoreError.errorUnknown.rawValue,
        "Should callback with an unknown error when the result indicates failure"
      )
      wasCompletionCalled = true
    }

    let delegate = try XCTUnwrap(videoUploaderFactory.capturedDelegate)
    let dummyUploader = VideoUploader(videoName: "dummy", videoSize: 0, parameters: [:], delegate: delegate)
    delegate.videoUploader(dummyUploader, didCompleteWithResults: ["success": false])

    XCTAssertTrue(wasCompletionCalled)
  }

  func testVideoUploaderSucceeds() throws {
    var wasCompletionCalled = false
    uploader.uploadVideo(configuration: configuration) { success, _, error in
      XCTAssertNil(error, "Should not receive an error when the uploader succeeds")
      XCTAssertTrue(success, "Should indicate success in the completion")
      wasCompletionCalled = true
    }

    let delegate = try XCTUnwrap(videoUploaderFactory.capturedDelegate)
    let dummyUploader = VideoUploader(videoName: "dummy", videoSize: 0, parameters: [:], delegate: delegate)
    delegate.videoUploader(dummyUploader, didCompleteWithResults: ["success": "1"])

    XCTAssertTrue(wasCompletionCalled)
  }

  func testVideoUploaderProgress() throws {
    var expectedBytesSent: Int64 = 0
    var expectedTotalSent: Int64 = 0
    var expectedTotalExpected: Int64 = 999
    var completionCallCount = 0

    let verifyProgress: GamingServiceProgressHandler = { bytesSent, totalSent, totalExpected in
      XCTAssertEqual(bytesSent, expectedBytesSent)
      XCTAssertEqual(totalSent, expectedTotalSent)
      XCTAssertEqual(totalExpected, expectedTotalExpected)
      completionCallCount += 1
    }
    fileHandle.stubbedSeekToEndOfFile = 999
    fileHandle.stubbedReadData = Data(Array(repeating: 1, count: 500))

    uploader.uploadVideo(
      configuration: configuration,
      completion: { _, _, _ in },
      progressHandler: verifyProgress
    )
    let delegate = try XCTUnwrap(videoUploaderFactory.capturedDelegate)
    let dummyUploader = VideoUploader(videoName: "dummy", videoSize: 0, parameters: [:], delegate: delegate)

    // Send first chunk of data
    _ = delegate.videoChunkData(for: dummyUploader, startOffset: 0, endOffset: 500)

    // Set expectations
    expectedBytesSent = 500
    expectedTotalSent = 500
    expectedTotalExpected = 999

    fileHandle.stubbedReadData = Data(Array(repeating: 1, count: 499))

    // Send second chunk of data
    _ = delegate.videoChunkData(for: dummyUploader, startOffset: 500, endOffset: 999)

    // Set expectations
    expectedBytesSent = 499
    expectedTotalSent = 999
    expectedTotalExpected = 999

    // Completing calls the progress handler with the final total bytes
    delegate.videoUploader(dummyUploader, didCompleteWithResults: ["success": true])

    XCTAssertEqual(completionCallCount, 3)
  }

  // MARK: - Helpers

  func createConfiguration(url: URL) -> GamingVideoUploaderConfiguration {
    GamingVideoUploaderConfiguration(videoURL: url, caption: "Cool Video")
  }
}


/*
 * 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
@testable import FBSDKGamingServicesKit
import TestTools
import XCTest

final class GamingWebDialogTests: XCTestCase {

  struct Success: GamingWebDialogSuccess {
    let payload: String?

    init(_ dict: [String: Any]) {
      payload = dict["payload"] as? String
    }
  }

  let payloadKey = "payload"
  let errorCodeKey = "error_code"
  let errorMessageKey = "error_message"

  // swiftlint:disable:next implicitly_unwrapped_optional
  var gamingWebDialog: GamingWebDialog<Success>!
  // swiftlint:disable:next implicitly_unwrapped_optional
  var webDialog: _WebDialog!

  var webDelegate: WebDialogDelegate { gamingWebDialog as WebDialogDelegate }
  var dialogCompleted = false
  var dialogFailed = false
  var error: Error?
  var payload: String?

  override func setUp() {
    super.setUp()
    error = nil
    payload = nil
    dialogCompleted = false
    dialogFailed = false

    gamingWebDialog = GamingWebDialog(name: "test")

    webDialog = _WebDialog(name: "Test")
    webDialog.delegate = webDelegate
    gamingWebDialog.dialog = webDialog
    gamingWebDialog.completion = { result in
      switch result {
      case let .success(success):
        self.dialogCompleted = true
        self.payload = success.payload
      case let .failure(error):
        self.dialogFailed = true
        self.error = error
      }
    }
  }

  func testDialogSucceedsWithPayload() throws {
    let testPayload = "test payload"

    gamingWebDialog.webDialog(webDialog, didCompleteWithResults: [payloadKey: testPayload])

    XCTAssertTrue(dialogCompleted)
    XCTAssertFalse(dialogFailed)
    XCTAssertNil(error)
    XCTAssertEqual(payload, testPayload)
  }

  func testDialogFailsWithError() throws {
    let testErrorCode = 418
    let testErrorMessage = "I'm a teapot"

    gamingWebDialog.webDialog(
      webDialog,
      didCompleteWithResults: [errorCodeKey: testErrorCode, errorMessageKey: testErrorMessage]
    )
    let error = try XCTUnwrap(error) as NSError

    XCTAssertFalse(dialogCompleted)
    XCTAssertTrue(dialogFailed)
    XCTAssertNotNil(error)
    XCTAssertEqual(error.code, testErrorCode)
    XCTAssertEqual(error.userInfo.values.first as? String, testErrorMessage)
  }

  func testDialogFailsWithUnknownError() throws {
    gamingWebDialog.webDialog(
      _WebDialog(name: "wrong dialog"),
      didCompleteWithResults: [:]
    )
    gamingWebDialog.show { _ in }
    let error = try XCTUnwrap(error) as NSError

    XCTAssertFalse(dialogCompleted)
    XCTAssertTrue(dialogFailed)
    XCTAssertNotNil(error)
  }

  func testDialogSucceedsEmptyWhenCanceled() throws {
    gamingWebDialog.webDialogDidCancel(webDialog)

    XCTAssertTrue(dialogCompleted)
    XCTAssertFalse(dialogFailed)
    XCTAssertNil(payload)
    XCTAssertNil(error)
  }
}


/*
 * 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
@testable import FBSDKGamingServicesKit

import TestTools
import XCTest

final class JoinTournamentDialogTests: XCTestCase {

  // swiftlint:disable:next implicitly_unwrapped_optional
  var joinDialog: JoinTournamentDialog!
  var dialogCompleted = false
  var dialogFailed = false
  var error: Error?
  var tournamentID: String?
  var payload: String?
  let tournamentKey = "tournament_id"
  let payloadKey = "payload"
  let expectedPayload = "test payload"
  let expectedTournamentID = "test tournament"

  override func setUp() {
    super.setUp()

    dialogCompleted = false
    dialogFailed = false
    error = nil
    tournamentID = nil
    payload = nil
    joinDialog = JoinTournamentDialog()
    joinDialog.dialog = _WebDialog(name: "Test")

    _WebDialog.setDependencies(
      .init(
        errorFactory: TestErrorFactory(),
        windowFinder: TestWindowFinder(window: UIWindow())
      )
    )
  }

  override func tearDown() {
    super.tearDown()

    _WebDialog.resetDependencies()
  }

  func testDialogSucceeds() throws {
    showSpecific()
    let result = [
      tournamentKey: expectedTournamentID,
      payloadKey: expectedPayload,
    ]
    joinDialog.dialog?.complete(with: result)
    XCTAssertNil(error)
    XCTAssertFalse(dialogFailed)
    XCTAssertTrue(dialogCompleted)
    XCTAssertEqual(payload, expectedPayload)
    XCTAssertEqual(tournamentID, expectedTournamentID)
  }

  func testDialogFailsWithoutTournamentID() throws {
    showSpecific()
    joinDialog.dialog?.complete(with: [:])

    XCTAssertFalse(dialogCompleted)
    XCTAssertTrue(dialogFailed)
    XCTAssertNotNil(error)
    XCTAssertNil(payload)
    XCTAssertNil(tournamentID)
  }

  func testDialogFailsWithError() throws {
    showSpecific()
    let error = NSError(domain: "I'm a teapot", code: 418)
    joinDialog.dialog?.fail(with: error)

    XCTAssertTrue(dialogFailed)
    XCTAssertFalse(dialogCompleted)
    XCTAssertNotNil(error)
    XCTAssertNil(payload)
    XCTAssertNil(tournamentID)
  }

  func testSuggestedSucceeds() throws {
    showSuggested()
    let result = [
      tournamentKey: expectedTournamentID,
      payloadKey: expectedPayload,
    ]
    joinDialog.dialog?.complete(with: result)
    XCTAssertNil(error)
    XCTAssertFalse(dialogFailed)
    XCTAssertTrue(dialogCompleted)
    XCTAssertEqual(payload, expectedPayload)
    XCTAssertEqual(tournamentID, expectedTournamentID)
  }

  func showSpecific() {
    joinDialog.showSpecific(tournamentID: expectedTournamentID, payload: expectedPayload) { result in
      switch result {
      case let .success(success):
        self.tournamentID = success.tournamentID
        self.payload = success.payload
        self.dialogCompleted = true
      case let .failure(error):
        self.error = error
        self.dialogFailed = true
      }
    }
  }

  func showSuggested() {
    joinDialog.showSuggested(payload: expectedPayload) { result in
      switch result {
      case let .success(success):
        self.tournamentID = success.tournamentID
        self.payload = success.payload
        self.dialogCompleted = true
      case let .failure(error):
        self.error = error
        self.dialogFailed = true
      }
    }
  }
}