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

enum SampleContextDialogs {

  static func showCreateContextDialog(withDelegate delegate: ContextDialogDelegate) -> CreateContextDialog? {
    let content = CreateContextContent(playerID: "1234567890")
    let dialog = CreateContextDialog(
      content: content,
      windowFinder: TestWindowFinder(),
      delegate: delegate
    )
    _ = dialog.show()
    return dialog
  }

  static func showChooseContextDialogWithInvalidSizes(delegate: ContextDialogDelegate) -> ChooseContextDialog? {
    Settings.shared.appID = "abc123"
    let content = ChooseContextContent()
    content.minParticipants = 2
    content.maxParticipants = 1
    let dialog = ChooseContextDialog(content: content, delegate: delegate)

    return dialog
  }

  static func chooseContextDialogWithoutContentValues(delegate: ContextDialogDelegate) -> ChooseContextDialog? {
    let content = ChooseContextContent()
    let dialog = ChooseContextDialog(content: content, delegate: delegate)

    return dialog
  }

  static func chooseContextDialog(
    utility: InternalUtilityProtocol,
    delegate: ContextDialogDelegate
  ) -> ChooseContextDialog? {
    let content = ChooseContextContent()
    let dialog = ChooseContextDialog(content, delegate: delegate, internalUtility: utility)

    return dialog
  }
}


/*
 * 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 ShareTournamentDialogTests: XCTestCase, ShareTournamentDialogDelegate {

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

  let bridgeOpener = TestBridgeAPIRequestOpener()
  let expirationDate = DateFormatter.format(ISODateString: "2021-09-24T18:03:47+0000")
  lazy var validTournamentForUpdate = Tournament(
    identifier: "1234",
    endTime: expirationDate
  )
  lazy var tournamentConfiguration = TournamentConfig(
    title: "test",
    endTime: expirationDate,
    scoreType: .numeric,
    sortOrder: .higherIsBetter,
    payload: "test"
  )

  lazy var shareDialog = ShareTournamentDialog(
    delegate: self,
    urlOpener: bridgeOpener
  )

  override func setUp() {
    super.setUp()

    dialogDidCompleteSuccessfully = false
    dialogDidCancel = false
    dialogError = nil
    AccessToken.current = SampleAccessTokens.validToken
    AuthenticationToken.current = SampleAuthenticationToken.validToken(withGraphDomain: "gaming")
  }

  override func tearDown() {
    AccessToken.current = nil
    AuthenticationToken.current = nil

    super.tearDown()
  }

  func testShareDialogCreation() throws {
    let dialog = ShareTournamentDialog(delegate: self)
    XCTAssertNotNil(dialog.delegate)
  }

  // MARK: - Share Dialog Creating Tournament

  func testShareDialogTournamentCreateWithInvalidAccessToken() throws {
    AccessToken.current = nil
    let dialog = try XCTUnwrap(shareDialog)
    var caughtInvalidAccessToken = false
    do {
      try dialog.show(initialScore: 120, config: tournamentConfiguration)
    } catch ShareTournamentDialogError.invalidAccessToken {
      caughtInvalidAccessToken = true
    } catch {
      return XCTFail("Should not throw an error other than invalid access token, error received: \(error)")
    }

    XCTAssertTrue(caughtInvalidAccessToken, "Should catch error ShareTournamentDialogError.invalidAccessToken")
    XCTAssertFalse(dialogDidCompleteSuccessfully, "Dialog should not complete")
    XCTAssertFalse(dialogDidCancel, "Dialog should not cancel")
    XCTAssertNil(dialogError, "Dialog should not call delegate with error")
  }

  func testShareDialogTournamentCreateWithInvalidAuthToken() throws {
    AuthenticationToken.current = SampleAuthenticationToken.validToken(withGraphDomain: "notGaming")
    var caughtInvalidAuthToken = false
    let dialog = try XCTUnwrap(shareDialog)
    do {
      try dialog.show(initialScore: 120, config: tournamentConfiguration)
    } catch ShareTournamentDialogError.invalidAuthToken {
      caughtInvalidAuthToken = true
    } catch {
      return XCTFail("Should not throw an error other than invalid access token, error received: \(error)")
    }

    XCTAssertTrue(caughtInvalidAuthToken, "Should catch error ShareTournamentDialogError.invalidAuthToken")
    XCTAssertFalse(dialogDidCompleteSuccessfully, "Dialog should not complete")
    XCTAssertFalse(dialogDidCancel, "Dialog should not cancel")
    XCTAssertNil(dialogError, "Dialog should not call delegate with error")
  }

  func testShareDialogTournamentCreateBridgeFailure() throws {
    let dialog = try XCTUnwrap(shareDialog)
    _ = try dialog.show(initialScore: 120, config: tournamentConfiguration)
    guard let handler = bridgeOpener.capturedHandler else {
      return XCTFail("The bridge should be called with a valid success block handler")
    }

    handler(false, SampleError())

    XCTAssertFalse(dialogDidCompleteSuccessfully, "Dialog should not complete")
    XCTAssertFalse(dialogDidCancel, "Dialog should not cancel")
    guard case let .bridgeError(error) = dialogError, error is SampleError else {
      if let error = dialogError {
        XCTFail("Expecting bridge error but instead received:  \(error)) ")
      }
      return
    }
  }

  func testShareDialogTournamentCreateURLIsValid() throws {
    let dialog = try XCTUnwrap(shareDialog)
    _ = try dialog.show(initialScore: 120, config: tournamentConfiguration)
    guard let dialogURL = bridgeOpener.capturedURL else {
      return XCTFail("The bridge opener should be called with a valid url")
    }

    let query = try XCTUnwrap(dialogURL.query)
    XCTAssertEqual(dialogURL.scheme, URLScheme.https.rawValue)
    XCTAssertEqual(dialogURL.host, "fb.gg")
    XCTAssertEqual(dialogURL.path, "/me/instant_tournament/\(SampleAccessTokens.defaultAppID)")
    XCTAssertNotNil(query, "Query should not be null")
  }

  // MARK: - Share Dialog Updating Tournament

  func testUpdateShareDialogTournamentWithInvalidAuthToken() throws {
    AuthenticationToken.current = SampleAuthenticationToken.validToken(withGraphDomain: "notGaming")
    var caughtInvalidAuthToken = false
    do {
      try shareDialog.show(score: 120, tournament: validTournamentForUpdate)
    } catch ShareTournamentDialogError.invalidAuthToken {
      caughtInvalidAuthToken = true
    } catch {
      return XCTFail("Should not throw an error other than invalid access token, error received: \(error)")
    }

    XCTAssertTrue(caughtInvalidAuthToken, "Should catch error ShareTournamentDialogError.invalidAuthToken")
    XCTAssertFalse(dialogDidCompleteSuccessfully, "Dialog should not complete")
    XCTAssertFalse(dialogDidCancel, "Dialog should not cancel")
    XCTAssertNil(dialogError, "Dialog should not call delegate with error")
  }

  func testShowingUpdateDialogWithInvalidAccessToken() throws {
    AccessToken.current = nil
    var caughtInvalidAccessToken = false
    do {
      try shareDialog.show(score: 120, tournament: validTournamentForUpdate)
    } catch ShareTournamentDialogError.invalidAccessToken {
      caughtInvalidAccessToken = true
    } catch {
      return XCTFail("Should not throw an error other than invalid access token, error received: \(error)")
    }

    XCTAssertTrue(caughtInvalidAccessToken, "Should catch error ShareTournamentDialogError.invalidAccessToken")
    XCTAssertFalse(dialogDidCompleteSuccessfully, "Dialog should not complete")
    XCTAssertFalse(dialogDidCancel, "Dialog should not cancel")
    XCTAssertNil(dialogError, "Dialog should not call delegate with error")
  }

  func testUpdateDialogBridgeFailure() throws {
    _ = try shareDialog.show(score: 120, tournament: validTournamentForUpdate)
    guard let handler = bridgeOpener.capturedHandler else {
      return XCTFail("The bridge should be called with a valid success block handler")
    }

    handler(false, SampleError())

    XCTAssertFalse(dialogDidCompleteSuccessfully, "Dialog should not complete")
    XCTAssertFalse(dialogDidCancel, "Dialog should not cancel")
    guard case let .bridgeError(error) = dialogError, error is SampleError else {
      return XCTFail("Expecting bridge error but instead received: \(String(describing: dialogError)) ")
    }
  }

  func testUpdateDialogURLIsValid() throws {
    _ = try shareDialog.show(score: 120, tournament: validTournamentForUpdate)
    guard let dialogURL = bridgeOpener.capturedURL else {
      return XCTFail("The bridge opener should be called with a valid url")
    }

    XCTAssertEqual(dialogURL.scheme, URLScheme.https.rawValue)
    XCTAssertEqual(dialogURL.host, "fb.gg")
    XCTAssertEqual(dialogURL.path, "/me/instant_tournament/\(SampleAccessTokens.defaultAppID)")
    XCTAssertNotNil(dialogURL.query)
  }

  // MARK: - Share Dialog Updating Tournament with ID

  func testUpdateDialogWithInvalidTournamentID() throws {
    var caughtInvalidTournamentID = false
    do {
      try shareDialog.show(score: 1, tournamentID: "")
    } catch ShareTournamentDialogError.invalidTournamentID {
      caughtInvalidTournamentID = true
    } catch {
      return XCTFail("Should not throw an error other than invalid access token, error received: \(error)")
    }

    XCTAssertTrue(caughtInvalidTournamentID, "Should catch error ShareTournamentDialogError.invalidTournamentID")
    XCTAssertFalse(dialogDidCompleteSuccessfully, "Dialog should not complete")
    XCTAssertFalse(dialogDidCancel, "Dialog should not cancel")
    XCTAssertNil(dialogError, "Dialog should not call delegate with error")
  }

  func testUpdateDialogWithTournamentIDCreatesValidURL() throws {
    _ = try shareDialog.show(score: 1, tournamentID: "12345")
    guard let dialogURL = bridgeOpener.capturedURL else {
      return XCTFail("The bridge opener should be called with a valid url")
    }

    XCTAssertEqual(dialogURL.scheme, URLScheme.https.rawValue)
    XCTAssertEqual(dialogURL.host, "fb.gg")
    XCTAssertEqual(dialogURL.path, "/me/instant_tournament/\(SampleAccessTokens.defaultAppID)")
    XCTAssertNotNil(dialogURL.query)
  }

  func didComplete(dialog: ShareTournamentDialog, tournament: Tournament) {
    dialogDidCompleteSuccessfully = true
  }

  func didFail(withError error: Error, dialog: ShareTournamentDialog) {
    dialogError = error as? ShareTournamentDialogError
  }

  func didCancel(dialog: ShareTournamentDialog) {
    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 FBSDKCoreKit
import TestTools
import XCTest

final class SwitchContextContentTests: XCTestCase {

  lazy var content = SwitchContextContent(contextID: name)

  func testCreatingWithContextIdentifier() {
    XCTAssertEqual(
      content.contextTokenID,
      name,
      "Should store the context identifier it was created with"
    )
  }

  func testValidatingWithInvalidIdentifier() {
    content = SwitchContextContent(contextID: "")

    do {
      try content.validate()
      XCTFail("Content with an empty identifier should not be considered valid")
    } catch let error as NSError {
      XCTAssertEqual(error.domain, ErrorDomain)
      XCTAssertEqual(
        error.userInfo[ErrorDeveloperMessageKey] as? String,
        "The contextToken is required."
      )
    }
  }

  func testValidatingWithValidIdentifier() throws {
    XCTAssertNotNil(
      try? content.validate(),
      "Content with a non-empty identifier should be considered valid"
    )
  }

  func testEquatability() {
    XCTAssertEqual(content, content)

    let contentWithSameProperties = SwitchContextContent(contextID: name)

    XCTAssertEqual(content, contentWithSameProperties)

    let contentWithDifferentProperties = SwitchContextContent(contextID: "foo")

    XCTAssertNotEqual(content, contentWithDifferentProperties)
  }
}


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

final class SwitchContextDialogFactoryTests: XCTestCase {

  let content = SwitchContextContent(contextID: "123")
  let windowFinder = TestWindowFinder()
  let delegate = TestContextDialogDelegate()
  let tokenProvider = TestAccessTokenProvider()

  override func setUp() {
    super.setUp()

    TestAccessTokenProvider.reset()
  }

  override func tearDown() {
    TestAccessTokenProvider.reset()

    super.tearDown()
  }

  func testCreatingDialogWithAccessToken() throws {
    TestAccessTokenProvider.stubbedAccessToken = SampleAccessTokens.validToken
    let factory = SwitchContextDialogFactory(tokenProvider: TestAccessTokenProvider.self)

    let dialog = try XCTUnwrap(
      factory.makeSwitchContextDialog(
        content: content,
        windowFinder: windowFinder,
        delegate: delegate
      ) as? SwitchContextDialog,
      "Should create a context dialog of the expected concrete type"
    )

    XCTAssertEqual(
      dialog.dialogContent as? SwitchContextContent,
      content,
      "Should create the dialog with the expected content"
    )
    XCTAssertTrue(
      dialog.delegate === delegate,
      "Should create the dialog with the expected delegate"
    )
  }

  func testCreatingDialogWithMissingAccessToken() throws {
    let factory = SwitchContextDialogFactory(tokenProvider: TestAccessTokenProvider.self)
    var capturedError: ContextDialogPresenterError?
    var switchDialog: SwitchContextDialog?

    do {
      switchDialog = try factory.makeSwitchContextDialog(
        content: content,
        windowFinder: windowFinder,
        delegate: delegate
      ) as? SwitchContextDialog
    } catch {
      capturedError = error as? ContextDialogPresenterError
    }
    XCTAssertEqual(
      capturedError,
      ContextDialogPresenterError.invalidAccessToken,
      "Should throw ContextDialogPresenterError.invalidAccessToken error"
    )
    XCTAssertNil(
      switchDialog,
      "Should not create a context dialog with a missing access token"
    )
  }
}


/*
 * 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 SwitchContextDialogTests: XCTestCase, ContextDialogDelegate {

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

  // swiftlint:disable implicitly_unwrapped_optional
  var windowFinder: TestWindowFinder!
  var content: SwitchContextContent!
  var dialog: SwitchContextDialog!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    windowFinder = TestWindowFinder()
    content = SwitchContextContent(contextID: "1234567890")
    dialog = SwitchContextDialog(
      content: content,
      windowFinder: windowFinder,
      delegate: self
    )

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

  override func tearDown() {
    _WebDialog.resetDependencies()
    GamingContext.current = nil
    dialogError = nil
    windowFinder = nil
    content = nil
    dialog = nil
    _WebDialog.resetDependencies()
    super.tearDown()
  }

  func testShowDialogWithInvalidContent() {
    content = SwitchContextContent(contextID: "")
    dialog = SwitchContextDialog(content: content, windowFinder: windowFinder, delegate: self)
    _ = dialog.show()
    XCTAssertNotNil(dialog)
    XCTAssertNotNil(dialogError)
    XCTAssertNil(dialog.currentWebDialog)
  }

  func testShowingDialogWithInvalidContentType() throws {
    let invalidContent = ChooseContextContent()
    dialog = SwitchContextDialog(content: content, windowFinder: windowFinder, delegate: self)
    dialog.dialogContent = invalidContent
    _ = dialog.show()
    XCTAssertNotNil(dialog)
    let error = try XCTUnwrap(dialogError as? GamingServicesDialogError)
    XCTAssertEqual(error, .invalidContentType)
    XCTAssertNil(dialog.currentWebDialog)
  }

  func testShowDialogWithValidContent() {
    _ = dialog.show()

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

  func testDialogCancelsWhenResultDoesNotContainContextID() {
    _ = dialog.show()

    guard let webDialogDelegate = dialog.currentWebDialog else {
      return XCTFail("Web dialog should be a web dialog view delegate")
    }

    let results = ["foo": name]
    webDialogDelegate.webDialogView(FBWebDialogView(), didCompleteWithResults: results)

    XCTAssertNotNil(webDialogDelegate)
    XCTAssertFalse(dialogDidCompleteSuccessfully)
    XCTAssertTrue(dialogDidCancel)
    XCTAssertNil(dialogError)
  }

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

    _ = dialog.show()

    let webDialogDelegate = try XCTUnwrap(dialog.currentWebDialog)
    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)
    XCTAssertNotNil(GamingContext.current?.identifier)
    XCTAssertTrue(dialogDidCompleteSuccessfully)
    XCTAssertFalse(dialogDidCancel)
    XCTAssertNil(dialogError)
  }

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

    _ = dialog.show()

    let webDialogDelegate = try XCTUnwrap(dialog.currentWebDialog)
    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)
    XCTAssertNotNil(GamingContext.current?.identifier)
    XCTAssertTrue(dialogDidCompleteSuccessfully)
    XCTAssertFalse(dialogDidCancel)
    XCTAssertNil(dialogError)
  }

  func testDialogCompletesWithServerError() throws {
    _ = dialog.show()

    let webDialogDelegate = try XCTUnwrap(dialog.currentWebDialog)
    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)
    XCTAssertEqual(resultErrorCode, error.code)
    XCTAssertEqual(resultErrorMessage, error.userInfo.values.first as? String)
    XCTAssertFalse(dialogDidCompleteSuccessfully)
    XCTAssertFalse(dialogDidCancel)
  }

  func testDialogCancels() throws {
    _ = dialog.show()
    let webDialogDelegate = try XCTUnwrap(dialog.currentWebDialog)

    webDialogDelegate.webDialogViewDidCancel(FBWebDialogView())

    XCTAssertNotNil(webDialogDelegate)
    XCTAssertFalse(dialogDidCompleteSuccessfully)
    XCTAssertTrue(dialogDidCancel)
    XCTAssertNil(dialogError)
  }

  func testDialogFailsWithError() throws {
    _ = dialog.show()
    let webDialogDelegate = try XCTUnwrap(dialog.currentWebDialog)

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

    XCTAssertNotNil(webDialogDelegate)
    XCTAssertFalse(dialogDidCompleteSuccessfully)
    XCTAssertFalse(dialogDidCancel)
    XCTAssertNotNil(dialogError)
  }

  // MARK: - Delegate Methods

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

import FBSDKCoreKit
import TestTools

final class TestInternalUtility: InternalUtilityProtocol {
  var scheme: String?
  var host: String?
  var path: String?
  var queryParameters: [String: String]?
  var isFacebookAppInstalled = false
  var isUnity = false

  func url(withScheme scheme: String, host: String, path: String, queryParameters: [String: String]) throws -> URL {
    self.scheme = scheme
    self.host = host
    self.path = path
    self.queryParameters = queryParameters
    var returnUrlComponents = URLComponents()
    returnUrlComponents.scheme = scheme
    returnUrlComponents.host = host
    returnUrlComponents.path = path

    return returnUrlComponents.url ?? SampleURLs.valid
  }

  func appURL(
    withHost host: String,
    path: String,
    queryParameters: [String: String]
  ) throws -> URL {
    SampleURLs.valid
  }

  func facebookURL(
    hostPrefix: String,
    path: String,
    queryParameters: [String: String]
  ) throws -> URL {
    SampleURLs.valid
  }

  func registerTransientObject(_ object: Any) {}

  func unregisterTransientObject(_ object: Any) {}

  func checkRegisteredCanOpenURLScheme(_ urlScheme: String) {}

  func validateURLSchemes() {}

  func extendDictionary(withDataProcessingOptions parameters: NSMutableDictionary) {}

  func hexadecimalString(from data: Data) -> String? {
    nil
  }

  func validateAppID() {}

  func validateRequiredClientAccessToken() -> String {
    ""
  }

  func extractPermissions(
    fromResponse responseObject: [String: Any],
    grantedPermissions: NSMutableSet,
    declinedPermissions: NSMutableSet,
    expiredPermissions: NSMutableSet
  ) {}

  func validateFacebookReservedURLSchemes() {}

  func parameters(fromFBURL url: URL) -> [String: Any] {
    [:]
  }

  var bundleForStrings: Bundle { .main }

  var stubbedTopMostViewController: UIViewController?

  func topMostViewController() -> UIViewController? { stubbedTopMostViewController }
}

enum URLConstants {
  case mSite
  case appSwitch(appID: String)

  static let host = "fb.gg"
  var path: String {
    switch self {
    case .mSite:
      return "/dialog/choosecontext/"
    case let .appSwitch(appID):
      return String(format: "/dialog/choosecontext/%@/", appID)
    }
  }

  static let queryParameterFilter = "filter"
  static let queryParameterMinSize = "min_size"
  static let queryParameterMaxSize = "max_size"
  static let mSiteQueryParameterPath = "path"
  static let mSiteQueryParameterParams = "params"
}


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

final class TournamentFetcherTests: XCTestCase {

  let factory = TestGraphRequestFactory()
  lazy var fetcher = TournamentFetcher(graphRequestFactory: factory)

  enum Keys {
    static let tournamentID = "id"
    static let tournamentEndTime = "tournament_end_time"
    static let tournamentTitle = "tournament_title"
    static let tournamentPayload = "tournament_payload"
  }

  enum Values {
    static let date = DateFormatter.format(ISODateString: "2021-09-24T18:03:47+0000")
    static let tournamentID = "4227416214015447"
    static let tournamentEndTime = "2021-09-24T18:03:47+0000"
    static let tournamentTitle = "test title"
    static let tournamentPayload = "test payload"
  }

  override func setUp() {
    super.setUp()

    AuthenticationToken.current = SampleAuthenticationToken.validToken(withGraphDomain: "gaming")
    AccessToken.current = SampleAccessTokens.validToken
  }

  override func tearDown() {
    AuthenticationToken.current = nil
    AccessToken.current = nil

    super.tearDown()
  }

  func testDependencies() {
    let fetcher = TournamentFetcher()
    XCTAssertTrue(
      fetcher.graphRequestFactory is GraphRequestFactory,
      "Should have a default GraphRequestFactory of the expected type"
    )
  }

  func testCustomDependencies() {
    XCTAssertIdentical(
      fetcher.graphRequestFactory as AnyObject,
      factory,
      "Should be able to create with a custom graph request factory"
    )
  }

  func testFetchTournaments() throws {
    fetcher.fetchTournaments { _ in
      XCTFail("Should not reach here")
    }

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

    XCTAssertEqual(
      request.startCallCount,
      1,
      "Should start the request to fetch tournaments"
    )
    XCTAssertEqual(
      factory.capturedGraphPath,
      "user123/tournaments",
      "Should create a request with the expected graph path"
    )
    XCTAssertEqual(
      factory.capturedParameters as? [String: String],
      [:],
      "Should create a request with the expected parameters"
    )
  }

  func testFetchTournamentsAuthTokenWithInvalidGraphDomain() throws {
    var completionWasInvoked = false

    AuthenticationToken.current = SampleAuthenticationToken.validToken(withGraphDomain: "notGaming")
    fetcher.fetchTournaments { result in
      switch result {
      case let .failure(error):
        guard case .invalidAuthToken = error else {
          return XCTFail("Was expecting invalid auth token error, instead recieved:\(error)")
        }

      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }

    XCTAssert(completionWasInvoked)
  }

  func testFetchTournamentsWithoutAccessToken() throws {
    var completionWasInvoked = false

    AccessToken.current = nil
    fetcher.fetchTournaments { result in
      switch result {
      case let .failure(error):
        guard case .invalidAccessToken = error else {
          return XCTFail("Was expecting invalid access token error, instead recieved:\(error)")
        }

      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }

    XCTAssert(completionWasInvoked)
  }

  func testHandlingTournamentFetchError() throws {
    var completionWasInvoked = false
    fetcher.fetchTournaments { result in
      switch result {
      case let .failure(error):
        guard case let .server(serverError) = error else {
          return XCTFail("Should not be a decoding error")
        }

        XCTAssertTrue(serverError is SampleError)
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, nil, SampleError())

    XCTAssert(completionWasInvoked)
  }

  func testHandlingTournamentFetchInvalidGraphResponse() throws {
    var completionWasInvoked = false
    fetcher.fetchTournaments { result in
      switch result {
      case let .failure(error):
        guard case .decoding = error else {
          return XCTFail(
            "An graph response should complete with a decoding error, instead received: \(error)"
          )
        }
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, SampleTournamentResults.invalidGraphAPIResponse, nil)

    XCTAssert(completionWasInvoked)
  }

  func testHandlingTournamentFetchResultAndError() throws {
    var completionWasInvoked = false
    fetcher.fetchTournaments { result in
      switch result {
      case let .failure(error):
        guard case let .server(serverError) = error else {
          return XCTFail("Should not be a decoding error")
        }

        XCTAssertTrue(serverError is SampleError)
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, SampleTournamentResults.validPartial, SampleError())

    XCTAssert(completionWasInvoked)
  }

  func testHandlingTournamentFetchInvalidResult() throws {
    var completionWasInvoked = false
    fetcher.fetchTournaments { result in
      switch result {
      case let .failure(error):
        guard case .decoding = error else {
          return XCTFail(
            "An invalid result should complete with a decoding error, instead received: \(error)"
          )
        }
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, SampleTournamentResults.missingIdentifier, nil)

    XCTAssert(completionWasInvoked)
  }

  func testHandlingTournamentFetchValidResult() throws {
    let date = try XCTUnwrap(Values.date)
    let expectedTournament = Tournament(
      identifier: Values.tournamentID,
      endTime: date,
      title: Values.tournamentTitle,
      payload: Values.tournamentPayload
    )

    var completionWasInvoked = false
    fetcher.fetchTournaments { result in
      switch result {
      case let .failure(error):
        XCTFail("Unexpected error received: \(error)")
      case let .success(tournaments):
        guard let tournament = tournaments.first else {
          return XCTFail("Tournaments array was empty")
        }
        guard let expiration = tournament.endTime?.timeIntervalSince1970 else {
          return XCTFail("Tournament missing expiration")
        }

        XCTAssertEqual(tournament.identifier, expectedTournament.identifier)
        XCTAssertEqual(
          expiration,
          date.timeIntervalSince1970
        )
        XCTAssertEqual(tournament.title, expectedTournament.title)
        XCTAssertEqual(tournament.payload, expectedTournament.payload)
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, SampleTournamentResults.validFull, nil)

    XCTAssert(completionWasInvoked)
  }

  enum SampleTournamentResults {

    static let validPartial = [
      "data": [
        [
          Keys.tournamentTitle: Values.tournamentTitle,
          Keys.tournamentEndTime: Values.tournamentEndTime,
          Keys.tournamentID: Values.tournamentID,
        ],
      ],
    ]

    static let validFull = [
      "data": [
        [
          Keys.tournamentTitle: Values.tournamentTitle,
          Keys.tournamentEndTime: Values.tournamentEndTime,
          Keys.tournamentID: Values.tournamentID,
          Keys.tournamentPayload: Values.tournamentPayload,
        ],
      ],
    ]

    static let missingIdentifier = [
      "data": [
        [
          Keys.tournamentTitle: Values.tournamentTitle,
          Keys.tournamentEndTime: Values.tournamentEndTime,
        ],
      ],
    ]

    static let invalidGraphAPIResponse = ["not_data": []]
  }
}


/*
 * 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 TournamentTests: XCTestCase {

  enum Keys {
    static let tournamentID = "id"
    static let tournamentEndTime = "tournament_end_time"
    static let tournamentTitle = "tournament_title"
    static let tournamentPayload = "tournament_payload"
  }

  enum Values {
    static let tournamentID = "4227416214015447"
    static let tournamentEndTime = "2021-09-24T18:03:47+0000"
    static let tournamentTitle = "test title"
    static let tournamentPayload = "test payload"
  }

  func testCreatingWithMissingID() throws {
    let tournamentInfo: [String: Any] = [
      Keys.tournamentEndTime: Values.tournamentEndTime,
      Keys.tournamentTitle: Values.tournamentTitle,
      Keys.tournamentPayload: Values.tournamentPayload,
    ]
    let data = try JSONSerialization.data(withJSONObject: tournamentInfo, options: [])
    do {
      _ = try JSONDecoder().decode(Tournament.self, from: data)
      XCTFail("Should not create a tournament from data that is missing an ID")
    } catch {
      XCTAssert(
        error is DecodingError,
        "Should not decode a tournament from data that is missing an ID"
      )
    }
  }

  func testCreatingWithInvalidID() throws {
    let tournamentInfo: [String: Any] = [
      Keys.tournamentID: 123,
      Keys.tournamentEndTime: Values.tournamentEndTime,
      Keys.tournamentTitle: Values.tournamentTitle,
      Keys.tournamentPayload: Values.tournamentPayload,
    ]
    let data = try JSONSerialization.data(withJSONObject: tournamentInfo, options: [])
    do {
      _ = try JSONDecoder().decode(Tournament.self, from: data)
      XCTFail("Should not create a tournament from data with an invalid ID")
    } catch {
      XCTAssert(error is DecodingError)
    }
  }

  func testCreatingWithMissingEndTime() throws {
    let tournamentInfo: [String: Any] = [
      Keys.tournamentID: Values.tournamentID,
      Keys.tournamentTitle: Values.tournamentTitle,
      Keys.tournamentPayload: Values.tournamentPayload,
    ]
    let data = try JSONSerialization.data(withJSONObject: tournamentInfo, options: [])
    do {
      _ = try JSONDecoder().decode(Tournament.self, from: data)
      XCTFail("Should not create a tournament from data that is missing the ent time")
    } catch {
      XCTAssert(error is DecodingError)
    }
  }

  func testCreatingWithInvalidEndTime() throws {
    let tournamentInfo: [String: Any] = [
      Keys.tournamentID: Values.tournamentID,
      Keys.tournamentEndTime: 123,
      Keys.tournamentTitle: Values.tournamentTitle,
      Keys.tournamentPayload: Values.tournamentPayload,
    ]
    let data = try JSONSerialization.data(withJSONObject: tournamentInfo, options: [])
    do {
      _ = try JSONDecoder().decode(Tournament.self, from: data)
      XCTFail("Should not create a tournament from data with an invalid end time")
    } catch {
      XCTAssert(error is DecodingError)
    }
  }

  func testCreatingWithMissingTitle() throws {
    let tournamentInfo: [String: Any] = [
      Keys.tournamentID: Values.tournamentID,
      Keys.tournamentEndTime: Values.tournamentEndTime,
      Keys.tournamentPayload: Values.tournamentPayload,
    ]
    let data = try JSONSerialization.data(withJSONObject: tournamentInfo, options: [])
    do {
      let tournament = try JSONDecoder().decode(Tournament.self, from: data)
      XCTAssertNotNil(tournament, "Should be able to create a tournament with a missing title")
    } catch {
      XCTFail("Unexpected error received: \(error)")
    }
  }

  func testCreatingWithInvalidTitle() throws {
    let tournamentInfo: [String: Any] = [
      Keys.tournamentID: Values.tournamentID,
      Keys.tournamentEndTime: Values.tournamentEndTime,
      Keys.tournamentTitle: 123,
      Keys.tournamentPayload: Values.tournamentPayload,
    ]
    let data = try JSONSerialization.data(withJSONObject: tournamentInfo, options: [])
    do {
      _ = try JSONDecoder().decode(Tournament.self, from: data)
      XCTFail("Should not create a tournament from data with an invalid title")
    } catch {
      XCTAssert(error is DecodingError)
    }
  }

  func testCreatingWithMissingPayload() throws {
    let tournamentInfo: [String: Any] = [
      Keys.tournamentID: Values.tournamentID,
      Keys.tournamentEndTime: Values.tournamentEndTime,
      Keys.tournamentTitle: Values.tournamentTitle,
    ]
    let data = try JSONSerialization.data(withJSONObject: tournamentInfo, options: [])
    do {
      let tournament = try JSONDecoder().decode(Tournament.self, from: data)
      XCTAssertNotNil(tournament, "Should be able to create a tournament with a missing payload")
    } catch {
      XCTFail("Unexpected error received: \(error)")
    }
  }

  func testCreatingWithInvalidPayload() throws {
    let tournamentInfo: [String: Any] = [
      Keys.tournamentID: Values.tournamentID,
      Keys.tournamentEndTime: Values.tournamentEndTime,
      Keys.tournamentTitle: Values.tournamentTitle,
      Keys.tournamentPayload: 123,
    ]
    let data = try JSONSerialization.data(withJSONObject: tournamentInfo, options: [])
    do {
      _ = try JSONDecoder().decode(Tournament.self, from: data)
      XCTFail("Should not create a tournament from data with an invalid payload")
    } catch {
      XCTAssert(error is DecodingError)
    }
  }

  func testCreatingWithValidInfo() throws {
    let tournamentInfo: [String: Any] = [
      Keys.tournamentID: Values.tournamentID,
      Keys.tournamentEndTime: Values.tournamentEndTime,
      Keys.tournamentTitle: Values.tournamentTitle,
      Keys.tournamentPayload: Values.tournamentPayload,
    ]
    let data = try JSONSerialization.data(withJSONObject: tournamentInfo, options: [])
    do {
      let tournament = try JSONDecoder().decode(Tournament.self, from: data)
      XCTAssertNotNil(tournament)
    } catch {
      XCTFail("Unexpected error received: \(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 FBSDKGamingServicesKit

import FBSDKCoreKit
import TestTools
import XCTest

final class TournamentUpdaterTest: XCTestCase {
  let factory = TestGraphRequestFactory()
  let score = 10
  lazy var updater = TournamentUpdater(graphRequestFactory: factory)
  lazy var tournament = Tournament(identifier: "12345", endTime: Date(), title: "test", payload: nil)

  override func setUp() {
    super.setUp()

    AuthenticationToken.current = SampleAuthenticationToken.validToken(withGraphDomain: "gaming")
    AccessToken.current = SampleAccessTokens.validToken
  }

  override func tearDown() {
    AuthenticationToken.current = nil
    AccessToken.current = nil

    super.tearDown()
  }

  func testDependencies() {
    XCTAssertTrue(
      TournamentUpdater().graphRequestFactory is GraphRequestFactory,
      "Should have a default GraphRequestFactory of the expected type"
    )
  }

  func testCustomDependencies() {
    XCTAssertIdentical(
      updater.graphRequestFactory as AnyObject,
      factory,
      "Should be able to create with a custom graph request factory"
    )
  }

  func testUpdate() throws {
    updater.update(tournament: tournament, score: score) { _ in
      XCTFail("Should not reach here")
    }

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

    XCTAssertEqual(
      request.startCallCount,
      1,
      "Should start the request to update tournaments"
    )
    XCTAssertEqual(
      factory.capturedGraphPath,
      "\(tournament.identifier)/update_score",
      "Should create a request with the expected graph path"
    )
    XCTAssertEqual(
      factory.capturedParameters as? [String: Int],
      ["score": score],
      "Should create a request with the expected parameters"
    )
  }

  func testUpdateWithoutGamingDomainAuthToken() throws {
    var completionWasInvoked = false
    AuthenticationToken.current = SampleAuthenticationToken.validToken(withGraphDomain: "notGaming")
    updater.update(tournament: tournament, score: score) { result in
      switch result {
      case let .failure(error):
        guard case .invalidAuthToken = error else {
          return XCTFail("Should fail with invalid auth token error but instead failed with: \(error)")
        }
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }

    XCTAssert(completionWasInvoked)
  }

  func testHandlingUpdateError() throws {
    var completionWasInvoked = false
    updater.update(tournament: tournament, score: score) { result in
      switch result {
      case let .failure(error):
        guard case let .server(serverError) = error else {
          return XCTFail("Should not be a decoding error")
        }

        XCTAssertTrue(serverError is SampleError)
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, nil, SampleError())

    XCTAssert(completionWasInvoked)
  }

  func testHandlingUpdateInvalidResult() throws {
    var completionWasInvoked = false
    updater.update(tournament: tournament, score: score) { result in
      switch result {
      case let .failure(error):
        guard case .decoding = error else {
          return XCTFail("Should fail with decoding error but instead failed with: \(error)")
        }
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, TournamentUpdateGraphAPIResults.invalid, nil)

    XCTAssert(completionWasInvoked)
  }

  func testHandlingUpdateSuccessFalse() throws {
    var completionWasInvoked = false
    updater.update(tournament: tournament, score: score) { result in
      switch result {
      case let .failure(error):
        guard case .decoding = error else {
          return XCTFail("Should fail with decoding error but instead failed with: \(error)")
        }
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, TournamentUpdateGraphAPIResults.successFalse, nil)

    XCTAssert(completionWasInvoked)
  }

  func testHandlingUpdateSuccess() throws {
    var completionWasInvoked = false
    var didSucceed = false
    updater.update(tournament: tournament, score: score) { result in
      switch result {
      case let .failure(error):
        return XCTFail(
          "Expecting the request to succeed instead received: \(error)"
        )
      case .success:
        didSucceed = true
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, TournamentUpdateGraphAPIResults.successTrue, nil)

    XCTAssert(completionWasInvoked)
    XCTAssertTrue(didSucceed)
  }

  // Update with Tournament ID

  func testHandlingUpdateWithInvalidTournamentID() throws {
    var completionWasInvoked = false
    updater.update(tournamentID: "", score: 1) { result in
      switch result {
      case let .failure(error):
        guard case .invalidTournamentID = error else {
          return XCTFail("Should receive invalidTournamentID error but instead received: \(error)")
        }
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }

    XCTAssert(completionWasInvoked)
  }

  func testHandlingUpdateSuccessWithTournamentID() throws {
    var completionWasInvoked = false
    var didSucceed = false
    updater.update(tournamentID: "12345", score: score) { result in
      switch result {
      case let .failure(error):
        return XCTFail(
          "Expecting the request to succeed instead received: \(error)"
        )
      case .success:
        didSucceed = true
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, TournamentUpdateGraphAPIResults.successTrue, nil)

    XCTAssert(completionWasInvoked)
    XCTAssertTrue(didSucceed)
  }

  enum TournamentUpdateGraphAPIResults {
    static let successTrue = ["success": 1]
    static let successFalse = ["success": 0]
    static let invalid = ["not_success": "value"]
  }
}


/*
 * 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 VideoUploaderFactoryTests: XCTestCase, VideoUploaderDelegate {

  func testCreatingVideoUploader() {
    let uploader = VideoUploaderFactory().create(
      videoName: name,
      videoSize: 5,
      parameters: ["foo": "bar"],
      delegate: self
    )

    XCTAssertTrue(
      uploader is VideoUploader,
      "Should create the expected concrete video uploader"
    )

    XCTAssertTrue(
      uploader.delegate === self,
      "Should set the expected delegate on the uploader"
    )
  }

  // MARK: - VideoUploaderDelegate conformance

  func videoChunkData(for videoUploader: VideoUploader, startOffset: UInt, endOffset: UInt) -> Data? {
    Data()
  }

  func videoUploader(_ videoUploader: VideoUploader, didCompleteWithResults results: [String: Any]) {}
  func videoUploader(_ videoUploader: VideoUploader, didFailWithError error: 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 FBSDKGamingServicesKit

import FBSDKCoreKit
import TestTools
import XCTest

final class VideoUploaderTests: XCTestCase, VideoUploaderDelegate {

  let startOffset = "start_offset"
  let endOffset = "end_offset"

  var uploadDidCompleteSuccessfully = false
  var uploadError: NSError?
  var videoData: Data?

  func videoChunkData(
    for videoUploader: VideoUploader,
    startOffset: UInt,
    endOffset: UInt
  ) -> Data? {
    videoData = Data("Some Data".utf8)
    return videoData
  }

  func videoUploader(
    _ videoUploader: VideoUploader,
    didCompleteWithResults results: [String: Any]
  ) {
    uploadDidCompleteSuccessfully = true
  }

  func videoUploader(
    _ videoUploader: VideoUploader,
    didFailWithError error: Error
  ) {
    uploadError = error as NSError
  }

  let graphRequestFactory = TestGraphRequestFactory()
  lazy var videoUploader = VideoUploader(
    videoName: "Greatest Video",
    videoSize: 1,
    parameters: ["a": 1],
    delegate: self,
    graphRequestFactory: graphRequestFactory
  )

  override func setUp() {
    super.setUp()

    videoUploader.uploadSessionID = 1
  }

  func testCompleteParameterTypes() {
    XCTAssertNotNil(
      videoUploader.delegate,
      "VideoUploader should be able to be initialized with a delegate"
    )
    XCTAssertEqual(
      videoUploader.parameters as? [String: Int],
      ["a": 1],
      "VideoUploader should be able to be initialized with parameters"
    )
  }

  func testDefaultGraphNode() {
    XCTAssertEqual(
      videoUploader.graphNode,
      "me",
      "VideoUploader should have a default graph path upon initialization"
    )
  }

  func testGraphRequestPostFinishRequest() {
    videoUploader.start()
    videoUploader.postFinishRequest()
    XCTAssertEqual(
      graphRequestFactory.capturedGraphPath,
      "me/videos"
    )
    XCTAssertNotNil(graphRequestFactory.capturedHTTPMethod)
    XCTAssertNil(
      graphRequestFactory.capturedTokenString,
      "Request Provider should not be initialized with a token string"
    )
  }

  func testGraphRequestFromStartWithVideoSizeZero() {
    let graphRequestFactory = TestGraphRequestFactory()
    let videoUploader = VideoUploader(
      videoName: "Greatest Video",
      videoSize: 0,
      parameters: ["a": 1],
      delegate: self,
      graphRequestFactory: graphRequestFactory
    )
    videoUploader.start()
    XCTAssertNotNil(
      uploadError,
      "Video should not upload and invoke the delegate method with an error if video size is 0"
    )
  }

  func testCompletedUploadVideo() throws {
    let offsets = [startOffset: 1, endOffset: 1]
    videoUploader.startTransferRequest(withOffsetDictionary: offsets)
    let completionHandler = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completionHandler(nil, ["success": true], nil)
    XCTAssertTrue(
      uploadDidCompleteSuccessfully,
      "Should invoke the delegate method upon completion."
    )
  }

  func testErrorUploadingVideo() throws {
    let offsets = [startOffset: 1, endOffset: 1]
    videoUploader.startTransferRequest(withOffsetDictionary: offsets)
    let completionHandler = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completionHandler(nil, ["none": true], nil)
    XCTAssertFalse(
      uploadDidCompleteSuccessfully,
      "Should not complete upload if result object doesn't have FBSDK_GAMING_VIDEO_UPLOAD_SUCCESS as key"
    )
    XCTAssertNotNil(
      uploadError,
      "Should have error if result object doesn't have FBSDK_GAMING_VIDEO_UPLOAD_SUCCESS as key"
    )
  }

  func testTransferRequestFailWithErrorWhenEndOffsetNil() {
    let offsets = [startOffset: 1]
    videoUploader.startTransferRequest(withOffsetDictionary: offsets)
    XCTAssertFalse(
      uploadDidCompleteSuccessfully,
      "Should not consider a transfer successful if there is no end offset for the video"
    )
  }

  func testStartingTransferRequestWithInvalidOffsets() {
    let offsets = [startOffset: 1, endOffset: 0]
    videoUploader.startTransferRequest(withOffsetDictionary: offsets)
    XCTAssertFalse(
      uploadDidCompleteSuccessfully,
      "Should not complete if end is smaller than start"
    )
  }

  func testStartTransferRequestWithNewOffsetDictionaryFailWithError() throws {
    let offsets = [startOffset: 1, endOffset: 0]
    videoUploader.startTransferRequest(withNewOffset: offsets, data: Data("Some Data".utf8))

    let completionHandler = try XCTUnwrap(graphRequestFactory.capturedRequests.first?.capturedCompletionHandler)
    completionHandler(nil, ["none": true], SampleError())

    XCTAssertFalse(
      uploadDidCompleteSuccessfully,
      "Should not complete if error in completion handler"
    )
    XCTAssertNotNil(
      uploadError,
      "Should not complete if error in completion handler"
    )
  }

  func testExtractOffsetsFromResultDictionaryWithSameValues() {
    let results = [startOffset: "1", endOffset: "1"]
    let extractOffsets = videoUploader.extractOffsets(fromResultDictionary: results)
    XCTAssertEqual(
      extractOffsets?.count,
      2,
      "Should return dictionary if start and end are in offset parameter"
    )
  }

  func testExtractOffsetsFromResultDictionaryReturningShareResults() {
    let result = [startOffset: "3", endOffset: "7"]
    let extractOffsets = videoUploader.extractOffsets(fromResultDictionary: result)
    XCTAssertEqual(
      extractOffsets?.count,
      2,
      "Should return dictionary if end is larger than start"
    )
  }

  func testExtractOffsetsWithFuzzer() {
    for _ in 0 ... 100 {
      let result = [startOffset: Fuzzer.random, endOffset: Fuzzer.random]
      _ = videoUploader.extractOffsets(fromResultDictionary: result)
    }
  }

  func testExtractOffsetsFromResultDictionaryWithNegativeValues() {
    let result = [startOffset: "-3", endOffset: "-2"]
    let extractOffsets = videoUploader.extractOffsets(fromResultDictionary: result)
    XCTAssertEqual(
      extractOffsets?.count,
      2,
      "Should return dictionary if end is smaller than start even as negative values"
    )
  }

  func testExtractOffsetsFromResultDictionaryWithNilOffset() {
    let result = [startOffset: "3"]
    let extractOffsets = videoUploader.extractOffsets(fromResultDictionary: result)
    XCTAssertNil(
      extractOffsets,
      "Should return nil if end is missing"
    )
  }

  func testExtractOffsetsFromResultDictionaryInvalidValue() {
    let result = [startOffset: "3", endOffset: "2invalid"]
    let extractOffsets = videoUploader.extractOffsets(fromResultDictionary: result)
    XCTAssertNil(
      extractOffsets,
      "Should return nil if end is invalid value"
    )
  }

  func testExtractOffsetsFromResultDictionaryWithDescendingOrder() {
    let result = [startOffset: "3", endOffset: "2"]
    let extractOffsets = videoUploader.extractOffsets(fromResultDictionary: result)
    XCTAssertNil(
      extractOffsets,
      "Should return nil dictionary if end is smaller than start"
    )
  }
}


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

enum CustomUpdateContentObjects {

  static var validID = "12345"
  static var validMessage = "Hello"
  static var gifMedia = FacebookGIF(withUrl: URL(string: "www.test.com")!) // swiftlint:disable:this force_unwrapping
  static var invalidGifMedia = FacebookGIF(withUrl: URL(string: "")!) // swiftlint:disable:this force_unwrapping
  @available(iOS 13.0, *)
  static var validImage = UIImage(
    named: "customColorSilhouette",
    in: Bundle(for: CustomUpdateGraphAPIContentRemoteTests.self),
    with: nil
  )! // swiftlint:disable:this force_unwrapping

  @available(iOS 13.0, *)
  static let imageContentInvalidMessage = CustomUpdateContentImage(message: "", image: validImage)
  static let imageContentInvalidImage = CustomUpdateContentImage(message: validMessage, image: UIImage())
  @available(iOS 13.0, *)
  static let imageContentValid = CustomUpdateContentImage(message: validMessage, image: validImage)
  static let mediaContentInvalidMessage = CustomUpdateContentMedia(message: "", media: gifMedia)
  static let mediaContentInvalidMedia = CustomUpdateContentMedia(message: validMessage, media: invalidGifMedia)
  static let mediaContentValid = CustomUpdateContentMedia(message: validMessage, media: gifMedia)
}

enum CustomUpdateContentObjectsParameters {
  static let contextKEY = "context_token_id"
  static let textKey = "text"
  static let mediaKey = "media"
  static let imageKey = "image"

  static let contextValue = "12345"
  static let textValue = #"{"default":"Hello","localizations":{}}"#
  static let mediaValue = #"{"gif":{"url":"www.test.com"}}"#
  static let imageValue = "data:image/png;base64,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" // swiftlint:disable:this line_length
}


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

enum SampleUnparsedAppLinkURLs {
  enum AppLinkKeys {
    static let data = "al_applink_data"
    static let extras = "extras"
    static let payload = "payload"
    static let gameRequestId = "game_request_id"
    static let contextTokenID = "context_token_id"
  }

  enum Values {
    static let payload = "payload"
    static let gameRequestID = "123"
    static let contextTokenID = "123"
  }

  static func create(
    payload potentialPayload: String? = Values.payload,
    gameRequestID potentialGameRequestID: String? = Values.gameRequestID,
    contextTokenID potentialContextTokenID: String? = Values.contextTokenID
  ) throws -> URL {
    let payload = createAppLinkExtras(
      payload: potentialPayload,
      gameRequestID: potentialGameRequestID,
      contextTokenID: potentialContextTokenID
    )
    let url = try XCTUnwrap(createUrl(payload: payload))
    return url
  }

  static func validGameRequestUrl() throws -> URL {
    let payload = createAppLinkExtras(contextTokenID: nil)
    let url = try XCTUnwrap(createUrl(payload: payload))
    return url
  }

  static func validGamingContextUrl() throws -> URL {
    let payload = createAppLinkExtras(gameRequestID: nil)
    let url = try XCTUnwrap(createUrl(payload: payload))
    return url
  }

  static func missingKeys() throws -> URL {
    let payload = createAppLinkExtras(
      payload: nil,
      gameRequestID: nil
    )
    let url = try XCTUnwrap(createUrl(payload: payload))
    return url
  }

  // MARK: - Helpers

  static func createUrl(payload: [String: Any]) throws -> URL {
    let data = try JSONSerialization.data(withJSONObject: payload, options: [])
    let json = try XCTUnwrap(String(data: data, encoding: .utf8))
    var components = try XCTUnwrap(URLComponents(url: SampleURLs.valid, resolvingAgainstBaseURL: false))

    components.queryItems = [
      URLQueryItem(name: AppLinkKeys.data, value: json),
    ]

    return try XCTUnwrap(components.url)
  }

  static func createAppLinkExtras(
    payload potentialPayload: String? = Values.payload,
    gameRequestID potentialGameRequestID: String? = Values.gameRequestID,
    contextTokenID potentialContextTokenID: String? = Values.contextTokenID
  ) -> [String: Any] {
    var extras = [String: Any]()

    if let payload = potentialPayload {
      extras[AppLinkKeys.payload] = payload
    }
    if let gameRequestID = potentialGameRequestID {
      extras[AppLinkKeys.gameRequestId] = gameRequestID
    }
    if let contextTokenID = potentialContextTokenID {
      extras[AppLinkKeys.contextTokenID] = contextTokenID
    }

    return [AppLinkKeys.extras: extras]
  }
}


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

final class TestChooseContextDialogFactory: ChooseContextDialogMaking {
  let dialog = TestShowable()
  var wasMakeChooseContextDialogCalled = false
  var capturedDelegate: ContextDialogDelegate?

  func makeChooseContextDialog(
    content: ChooseContextContent,
    delegate: ContextDialogDelegate
  ) -> Showable {
    wasMakeChooseContextDialogCalled = true
    capturedDelegate = delegate

    return dialog
  }
}


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

final class TestContextDialogDelegate: NSObject, ContextDialogDelegate {
  func contextDialogDidComplete(_ contextDialog: ContextWebDialog) {}

  func contextDialog(_ contextDialog: ContextWebDialog, didFailWithError error: Error) {}

  func contextDialogDidCancel(_ contextDialog: ContextWebDialog) {}
}


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

final class TestCreateContextDialogFactory: CreateContextDialogMaking {
  let dialog = TestShowable()
  var wasMakeCreateContextDialogCalled = false
  var capturedDelegate: ContextDialogDelegate?
  var shouldCreateDialog = true

  func makeCreateContextDialog(
    content: CreateContextContent,
    windowFinder: _WindowFinding,
    delegate: ContextDialogDelegate
  ) -> Showable? {
    wasMakeCreateContextDialogCalled = true
    capturedDelegate = delegate

    return shouldCreateDialog ? dialog : nil
  }
}


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

@testable import FBSDKGamingServicesKit
import Foundation

final class TestFileHandleFactory: FileHandleCreating {

  var stubbedFileHandle = TestFileHandler()
  var capturedURL: URL?

  func fileHandleForReading(from url: URL) throws -> FileHandling {
    capturedURL = url

    return stubbedFileHandle
  }
}


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

final class TestFileHandler: FileHandling {
  var stubbedReadData = Data()
  var stubbedSeekToEndOfFile: UInt64 = 0
  var capturedFileOffset: UInt64 = 0

  func fb_seekToEndOfFile() -> UInt64 {
    stubbedSeekToEndOfFile
  }

  func fb_seek(toFileOffset offset: UInt64) {
    capturedFileOffset = offset
  }

  func fb_readData(ofLength length: Int) -> Data {
    stubbedReadData
  }
}


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

final class TestGameRequestDialogDelegate: GameRequestDialogDelegate {

  // MARK: Completion

  var completionDialog: GameRequestDialog?
  var completionResults: [String: Any]?

  func gameRequestDialog(
    _ gameRequestDialog: GameRequestDialog,
    didCompleteWithResults results: [String: Any]
  ) {
    completionDialog = gameRequestDialog
    completionResults = results
  }

  // MARK: Failure

  var failureDialog: GameRequestDialog?
  var failureError: Error?

  func gameRequestDialog(
    _ gameRequestDialog: GameRequestDialog,
    didFailWithError error: Error
  ) {
    failureDialog = gameRequestDialog
    failureError = error
  }

  // MARK: Cancellation

  var cancellationDialog: GameRequestDialog?

  func gameRequestDialogDidCancel(_ gameRequestDialog: GameRequestDialog) {
    cancellationDialog = gameRequestDialog
  }
}


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

final class TestGameRequestURLProvider: GameRequestURLProviding {
  static var stubbedDeepLinkURL: URL?

  static func createDeepLinkURL(queryDictionary: [String: Any]) -> URL? {
    stubbedDeepLinkURL
  }

  static func filtersName(for filters: GameRequestFilter) -> String? {
    nil
  }

  static func actionTypeName(for actionType: GameRequestActionType) -> String? {
    nil
  }

  static func reset() {
    stubbedDeepLinkURL = nil
  }
}


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

@testable import FBSDKGamingServicesKit
import Foundation

@objcMembers
final class TestGamingServiceController: GamingServiceControllerProtocol {
  var capturedArgument: String?

  func call(withArgument argument: String?) {
    capturedArgument = argument
  }
}

@objcMembers
final class TestGamingServiceControllerFactory: NSObject, GamingServiceControllerCreating {
  var capturedServiceType: GamingServiceType = .friendFinder
  var capturedCompletion: GamingServiceResultCompletion = { _, _, _ in }
  var capturedPendingResult: [String: Any]?
  var controller = TestGamingServiceController()

  func create(
    serviceType: GamingServiceType,
    pendingResult: [String: Any]?,
    completion: @escaping GamingServiceResultCompletion
  ) -> GamingServiceControllerProtocol {
    capturedServiceType = serviceType
    capturedCompletion = completion
    capturedPendingResult = pendingResult

    return controller
  }
}


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

final class TestGamingUtility: GamingUtility {
  static var stubbedGraphDomain: String?

  static func getGraphDomainFromToken() -> String? { stubbedGraphDomain }

  static func reset() {
    stubbedGraphDomain = nil
  }
}


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

@testable import FBSDKGamingServicesKit

import FBSDKCoreKit

final class TestLogger: GamingLogging {
  var contents = ""
  var loggingBehavior: LoggingBehavior

  init(loggingBehavior: LoggingBehavior) {
    self.loggingBehavior = loggingBehavior
  }

  static func singleShotLogEntry(_ loggingBehavior: LoggingBehavior, logEntry: String) {}

  func logEntry(_ logEntry: String) {}

  static func reset() {}
}


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

final class TestShareUtility: GamingShareValidating {
  static func validateRequiredValue(_ value: Any, named name: String) throws {}

  static func reset() {}
}


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

final class TestShowable: Showable {
  var wasShowCalled = false
  var canShow = false

  func show() -> Bool {
    wasShowCalled = true

    return canShow
  }
}


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

final class TestSwitchContextDialog: SwitchContextDialogProtocol {

  // MARK: Test Evidence

  var wasDidCompleteWithResultsCalled = false
  var wasDidFailWithErrorCalled = false
  var wasDidCancelCalled = false
  var wasShowCalled = false
  var wasValidateCalled = false
  var wasCreateWebDialogCalled = false

  // MARK: Protocol Conformance

  var delegate: ContextDialogDelegate?
  var dialogContent: ValidatableProtocol?
  var currentWebDialog: _WebDialog?

  func createWebDialogFrame(
    withWidth: CGFloat,
    height: CGFloat,
    windowFinder: _WindowFinding
  ) -> CGRect {
    wasCreateWebDialogCalled = true

    return .zero
  }

  func webDialogDidCancel(_ webDialog: _WebDialog) {
    wasDidCancelCalled = true
  }

  func webDialog(_ webDialog: _WebDialog, didFailWithError error: Error) {
    wasDidFailWithErrorCalled = true
  }

  func webDialog(_ webDialog: _WebDialog, didCompleteWithResults results: [String: Any]) {
    wasDidCompleteWithResultsCalled = true
  }

  func show() -> Bool {
    wasShowCalled = true

    return false
  }

  func validate() throws {
    wasValidateCalled = 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 FBSDKCoreKit

final class TestSwitchContextDialogFactory: SwitchContextDialogMaking {
  let dialog = TestShowable()
  var wasMakeSwitchContextDialogCalled = false
  var capturedDelegate: ContextDialogDelegate?
  var shouldCreateDialog = true

  func makeSwitchContextDialog(
    content: SwitchContextContent,
    windowFinder: _WindowFinding,
    delegate: ContextDialogDelegate
  ) -> Showable? {
    wasMakeSwitchContextDialogCalled = true
    capturedDelegate = delegate

    return shouldCreateDialog ? dialog : nil
  }
}


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

@testable import FBSDKGamingServicesKit
import Foundation

final class TestVideoUploader: VideoUploading {

  var delegate: VideoUploaderDelegate?
  var wasStartCalled = false

  func start() {
    wasStartCalled = 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 Foundation

final class TestVideoUploaderFactory: VideoUploaderCreating {

  var capturedVideoName: String?
  var capturedVideoSize: UInt?
  var capturedParameters = [String: Any]()
  var capturedDelegate: VideoUploaderDelegate?
  var stubbedVideoUploader = TestVideoUploader()

  func create(
    videoName: String,
    videoSize: UInt,
    parameters: [String: Any],
    delegate: VideoUploaderDelegate
  ) -> VideoUploading {
    capturedVideoName = videoName
    capturedVideoSize = videoSize
    capturedParameters = parameters
    capturedDelegate = delegate

    return stubbedVideoUploader
  }
}


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

@available(iOS 13.0, *)
final class CustomUpdateContentTests: XCTestCase {

  var validMessage = "text"
  var contextToken = "12345"
  var ctaText = "play"
  var payload = "data123"
  var localization = ["data123": "test"]

  var gif = FacebookGIF(withUrl: URL(string: "www.test.com")!) // swiftlint:disable:this force_unwrapping
  var validImage = UIImage(
    named: "customColorSilhouette",
    in: Bundle(for: CustomUpdateContentTests.self),
    with: nil
  )! // swiftlint:disable:this force_unwrapping

  func testCustomUpdateContentMediaInitWithOnlyRequiredValues() throws {
    let content = try XCTUnwrap(CustomUpdateContentMedia(
      message: validMessage,
      media: gif
    ))

    XCTAssertEqual(validMessage, content.message)
    XCTAssertEqual(gif, content.media as? FacebookGIF)
  }

  func testCustomUpdateContentMediaInitWithAllValues() throws {
    let content = try XCTUnwrap(CustomUpdateContentMedia(
      message: validMessage,
      media: gif,
      cta: ctaText,
      payload: payload,
      messageLocalization: localization,
      ctaLocalization: localization
    ))

    XCTAssertEqual(validMessage, content.message)
    XCTAssertEqual(gif, content.media as? FacebookGIF)
    XCTAssertEqual(ctaText, content.ctaText)
    XCTAssertEqual(payload, content.payload)
    XCTAssertEqual(localization, content.messageLocalization)
    XCTAssertEqual(localization, content.ctaLocalization)
  }

  func testCustomUpdateContentImageInitWithOnlyRequiredValues() throws {
    let content = try XCTUnwrap(CustomUpdateContentImage(
      message: validMessage,
      image: validImage
    ))

    XCTAssertEqual(validMessage, content.message)
    XCTAssertEqual(validImage, content.image)
  }

  func testCustomUpdateContentImageInitWithAllValues() throws {
    let content = try XCTUnwrap(CustomUpdateContentImage(
      message: validMessage,
      image: validImage,
      cta: ctaText,
      payload: payload,
      messageLocalization: localization,
      ctaLocalization: localization
    ))

    XCTAssertEqual(validMessage, content.message)
    XCTAssertEqual(validImage, content.image)
    XCTAssertEqual(ctaText, content.ctaText)
    XCTAssertEqual(payload, content.payload)
    XCTAssertEqual(localization, content.messageLocalization)
    XCTAssertEqual(localization, content.ctaLocalization)
  }
}


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

@available(iOS 13.0, *)
final class CustomUpdateGraphAPIContentRemoteTests: XCTestCase {
  let validContextTokenID = "12345"

  override func setUp() {
    super.setUp()
    GamingContext.current = nil
  }

  override func tearDown() {
    GamingContext.current = nil
    super.tearDown()
  }

  func testImageContentInitWithValidValues() throws {
    GamingContext.current = GamingContext(identifier: validContextTokenID, size: 0)
    var remoteContent: CustomUpdateGraphAPIContentRemote?

    do {
      remoteContent = try CustomUpdateGraphAPIContentRemote(
        customUpdateContentImage: CustomUpdateContentObjects.imageContentValid
      )
    } catch {
      XCTFail("Was not expecting an error thrown but got: \(error)")
    }
    let validContent = try XCTUnwrap(remoteContent)
    let image = try XCTUnwrap(validContent.image, "Should successfully create image content")

    XCTAssertEqual(validContent.text.defaultString, CustomUpdateContentObjects.validMessage)
    XCTAssertEqual(image, CustomUpdateContentObjects.validImage.pngData())
    XCTAssertEqual(
      validContent.contextTokenID,
      GamingContext.current?.identifier,
      "The context token identifier should be the same identifer as the current gaming context token id"
    )
  }

  func testImageContentInitWithInvalidMessage() throws {
    GamingContext.current = GamingContext(identifier: validContextTokenID, size: 0)
    var remoteContent: CustomUpdateGraphAPIContentRemote?

    do {
      remoteContent = try CustomUpdateGraphAPIContentRemote(
        customUpdateContentImage: CustomUpdateContentObjects.imageContentInvalidMessage
      )
    } catch CustomUpdateContentError.invalidMessage {
    } catch {
      return XCTFail("Should not throw an error other than invalid message: \(error)")
    }

    XCTAssertNil(remoteContent, "A remote content object should not be created with an invalid message")
  }

  func testImageContentInitWithInvalidImage() throws {
    GamingContext.current = GamingContext(identifier: validContextTokenID, size: 0)
    var remoteContent: CustomUpdateGraphAPIContentRemote?

    do {
      remoteContent = try CustomUpdateGraphAPIContentRemote(
        customUpdateContentImage: CustomUpdateContentObjects.imageContentInvalidImage
      )
    } catch CustomUpdateContentError.invalidImage {
    } catch {
      return XCTFail("Should not throw an error other than invalid image: \(error)")
    }

    XCTAssertNil(remoteContent, "A remote content object should not be created with an invalid image ")
  }

  func testImageContentInitWhenNotInGamingContext() throws {
    var remoteContent: CustomUpdateGraphAPIContentRemote?

    do {
      remoteContent = try CustomUpdateGraphAPIContentRemote(
        customUpdateContentImage: CustomUpdateContentObjects.imageContentValid
      )
    } catch CustomUpdateContentError.notInGameContext {
    } catch {
      return XCTFail("Should not throw an error other than not in game context: \(error)")
    }

    XCTAssertNil(remoteContent, "A remote content object should not be created if the current game context is empty")
  }

  func testMediaContentInitWithValidValues() throws {
    GamingContext.current = GamingContext(identifier: validContextTokenID, size: 0)
    var remoteContent: CustomUpdateGraphAPIContentRemote?

    do {
      remoteContent = try CustomUpdateGraphAPIContentRemote(
        customUpdateContentMedia: CustomUpdateContentObjects.mediaContentValid)
    } catch {
      XCTFail("Was not expecting an error thrown but got: \(error)")
    }
    let media = try XCTUnwrap(remoteContent?.media)

    XCTAssertEqual(remoteContent?.contextTokenID, GamingContext.current?.identifier)
    XCTAssertEqual(remoteContent?.text.defaultString, CustomUpdateContentObjects.validMessage)
    XCTAssertEqual(media.gif, CustomUpdateContentObjects.gifMedia)
  }

  func testMediaContentInitWithInvalidMessage() throws {
    GamingContext.current = GamingContext(identifier: validContextTokenID, size: 0)
    var remoteContent: CustomUpdateGraphAPIContentRemote?

    do {
      remoteContent = try CustomUpdateGraphAPIContentRemote(
        customUpdateContentMedia: CustomUpdateContentObjects.mediaContentInvalidMessage
      )
    } catch CustomUpdateContentError.invalidMessage {
    } catch {
      return XCTFail("Should not throw an error other than invalid message: \(error)")
    }

    XCTAssertNil(remoteContent, "A remote content object should not be created with an invalid message")
  }

  func testMediaContentInitWhenNotInGamingContext() throws {
    var remoteContent: CustomUpdateGraphAPIContentRemote?
    var didThrowNotInGamingContextError = false

    do {
      remoteContent = try CustomUpdateGraphAPIContentRemote(
        customUpdateContentMedia: CustomUpdateContentObjects.mediaContentValid
      )
    } catch CustomUpdateContentError.notInGameContext {
      didThrowNotInGamingContextError = true
    } catch {
      return XCTFail("Should not throw an error other than not in game context: \(error)")
    }

    XCTAssertNil(remoteContent, "A remote content object should if no gaming context is present")
    XCTAssertTrue(
      didThrowNotInGamingContextError,
      "Should throw a not in gaming context error when the user is not currently in gaming context"
    )
  }
}


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

@available(iOS 13.0, *)
final class CustomUpdateGraphRequestTests: XCTestCase {
  let factory = TestGraphRequestFactory()
  lazy var requester = CustomUpdateGraphRequest(graphRequestFactory: factory)
  var validContextTokenID = "12345"

  let validMediaContentParameterDictionary = [
    CustomUpdateContentObjectsParameters.contextKEY: CustomUpdateContentObjectsParameters.contextValue,
    CustomUpdateContentObjectsParameters.textKey: CustomUpdateContentObjectsParameters.textValue,
    CustomUpdateContentObjectsParameters.mediaKey: CustomUpdateContentObjectsParameters.mediaValue,
  ]

  let validImageContentParameterDictionary = [
    CustomUpdateContentObjectsParameters.contextKEY: CustomUpdateContentObjectsParameters.contextValue,
    CustomUpdateContentObjectsParameters.textKey: CustomUpdateContentObjectsParameters.textValue,
    CustomUpdateContentObjectsParameters.imageKey: CustomUpdateContentObjectsParameters.imageValue,
  ]

  override func setUp() {
    super.setUp()
    GamingContext.current = GamingContext(identifier: validContextTokenID, size: 0)
    AuthenticationToken.current = SampleAuthenticationToken.validToken(withGraphDomain: "gaming")
  }

  func testDependencies() {
    let requester = CustomUpdateGraphRequest()
    XCTAssertTrue(
      requester.graphRequestFactory is GraphRequestFactory,
      "Should have a default GraphRequestFactory of the expected type"
    )
  }

  func testCustomDependencies() {
    XCTAssertIdentical(
      requester.graphRequestFactory as AnyObject,
      factory,
      "Should be able to create with a custom graph request factory"
    )
  }

  // MARK: - CustomUpdateContentMedia

  func testPerformRequest() throws {
    try requester.request(content: CustomUpdateContentObjects.mediaContentValid) { _ in
      XCTFail("Should not reach here")
    }

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

    XCTAssertEqual(
      request.startCallCount,
      1,
      "Should start the custom update request"
    )
    XCTAssertEqual(
      factory.capturedGraphPath,
      "me/custom_update",
      "Should create a request with the expected graph path"
    )
    XCTAssertEqual(
      factory.capturedParameters as? [String: String],
      validMediaContentParameterDictionary,
      "Request should have the correct parameters"
    )
  }

  func testHandlingRequestInvalidMediaContentError() throws {
    var caughtContentError = false
    do {
      try requester.request(content: CustomUpdateContentObjects.mediaContentInvalidMessage) { _ in
        XCTFail("Should not succeed")
      }
    } catch CustomUpdateContentError.invalidMessage {
      caughtContentError = true
    }

    XCTAssertTrue(caughtContentError, "Should throw an invalid message content error")
  }

  func testHandlingRequestError() throws {
    var completionWasInvoked = false
    try requester.request(content: CustomUpdateContentObjects.mediaContentValid) { result in
      switch result {
      case let .failure(error):
        guard case let .server(serverError) = error else {
          return XCTFail("Should not be a decoding error")
        }
        XCTAssertTrue(serverError is SampleError)
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, nil, SampleError())

    XCTAssert(completionWasInvoked)
  }

  func testHandlingRequestInvalidResult() throws {
    var completionWasInvoked = false
    try requester.request(content: CustomUpdateContentObjects.mediaContentValid) { result in
      switch result {
      case let .failure(error):
        guard case .decoding = error else {
          return XCTFail(
            "Expected a decoding error, instead received: \(error)"
          )
        }
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, SampleCustomUpdateGraphAPIResults.invalid, nil)

    XCTAssert(completionWasInvoked)
  }

  func testHandlingRequestSuccessFalse() throws {
    var completionWasInvoked = false
    var didSucceed = false

    try requester.request(content: CustomUpdateContentObjects.mediaContentValid) { result in
      switch result {
      case let .failure(error):
        return XCTFail(
          "Expecting the request to succeed instead receieved: \(error)"
        )
      case let .success(succeed):
        didSucceed = succeed
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, SampleCustomUpdateGraphAPIResults.successFalse, nil)

    XCTAssert(completionWasInvoked)
    XCTAssertFalse(didSucceed)
  }

  func testHandlingRequestValidResult() throws {
    var completionWasInvoked = false
    var didSucceed = false

    try requester.request(content: CustomUpdateContentObjects.mediaContentValid) { result in
      switch result {
      case let .failure(error):
        return XCTFail(
          "Expecting the request to succeed instead receieved: \(error)"
        )
      case let .success(succeed):
        didSucceed = succeed
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, SampleCustomUpdateGraphAPIResults.successTrue, nil)

    XCTAssert(completionWasInvoked)
    XCTAssertTrue(didSucceed)
  }

  // MARK: - CustomUpdateContentImage

  func testPerformRequestWithImageContent() throws {
    try requester.request(content: CustomUpdateContentObjects.imageContentValid) { _ in
      XCTFail("Should not reach here")
    }

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

    XCTAssertEqual(
      request.startCallCount,
      1,
      "Should start the custom update request"
    )
    XCTAssertEqual(
      factory.capturedGraphPath,
      "me/custom_update",
      "Should create a request with the expected graph path"
    )
    XCTAssertEqual(
      factory.capturedParameters as? [String: String],
      validImageContentParameterDictionary,
      "Request should have the correct parameters"
    )
  }

  func testHandlingRequestInvalidImageContentError() throws {
    var caughtContentError = false
    do {
      try requester.request(content: CustomUpdateContentObjects.imageContentInvalidImage) { _ in
        XCTFail("Should not reach here")
      }
    } catch CustomUpdateContentError.invalidImage {
      caughtContentError = true
    }

    XCTAssertTrue(caughtContentError, "Should throw an invalid image content error")
  }

  func testHandlingRequestErrorWithImageContent() throws {
    var completionWasInvoked = false
    try requester.request(content: CustomUpdateContentObjects.imageContentValid) { result in
      switch result {
      case let .failure(error):
        guard case let .server(serverError) = error else {
          return XCTFail("Should not be a decoding error")
        }
        XCTAssertTrue(serverError is SampleError)
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, nil, SampleError())

    XCTAssert(completionWasInvoked)
  }

  func testHandlingRequestInvalidResultWithImageContent() throws {
    var completionWasInvoked = false
    try requester.request(content: CustomUpdateContentObjects.imageContentValid) { result in
      switch result {
      case let .failure(error):
        guard case .decoding = error else {
          return XCTFail(
            "Expected a decoding error, instead received: \(error)"
          )
        }
      case .success:
        XCTFail("Should not succeed")
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, SampleCustomUpdateGraphAPIResults.invalid, nil)

    XCTAssert(completionWasInvoked)
  }

  func testHandlingRequestSuccessFalseWithImageContent() throws {
    var completionWasInvoked = false
    var didSucceed = false

    try requester.request(content: CustomUpdateContentObjects.imageContentValid) { result in
      switch result {
      case let .failure(error):
        return XCTFail(
          "Expecting the request to succeed instead receieved: \(error)"
        )
      case let .success(succeed):
        didSucceed = succeed
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, SampleCustomUpdateGraphAPIResults.successFalse, nil)

    XCTAssert(completionWasInvoked)
    XCTAssertFalse(didSucceed)
  }

  func testHandlingRequestValidResultWithImageContent() throws {
    var completionWasInvoked = false
    var didSucceed = false

    try requester.request(content: CustomUpdateContentObjects.imageContentValid) { result in
      switch result {
      case let .failure(error):
        return XCTFail(
          "Expecting the request to succeed instead receieved: \(error)"
        )
      case let .success(succeed):
        didSucceed = succeed
      }
      completionWasInvoked = true
    }
    let completion = try XCTUnwrap(factory.capturedRequests.first?.capturedCompletionHandler)

    completion(nil, SampleCustomUpdateGraphAPIResults.successTrue, nil)

    XCTAssert(completionWasInvoked)
    XCTAssertTrue(didSucceed)
  }

  enum SampleCustomUpdateGraphAPIResults {
    static let successTrue = ["success": 1]
    static let successFalse = ["success": 0]
    static let invalid = ["not_success": "value"]
  }
}


/*
 * 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 CustomUpdateLocalizedTextTests: XCTestCase {
  let fakeLocalization = ["es_pa": "spanish"]

  func testIntilization() {
    let text = CustomUpdateLocalizedText(defaultString: "test", localizations: fakeLocalization)

    XCTAssertNotNil(text)
    XCTAssertEqual(text?.localizations, fakeLocalization)
  }

  func testIntilizationWithEmptyString() {
    XCTAssertNil(CustomUpdateLocalizedText(defaultString: "", localizations: [:]))
  }
}


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

final class CustomUpdateMediaTests: XCTestCase {
  let fakeGIF = FacebookGIF(withUrl: SampleURLs.valid)
  let fakeVideo = FacebookVideo(withUrl: SampleURLs.valid)

  func testIntilizationWithGif() throws {
    let media = try XCTUnwrap(CustomUpdateMedia(media: fakeGIF))
    let gif = try XCTUnwrap(media.gif)

    XCTAssertNotNil(media)
    XCTAssertNil(media.video)
    XCTAssertEqual(gif.url, fakeGIF.url)
  }

  func testIntilizationWithVideo() throws {
    let media = try XCTUnwrap(CustomUpdateMedia(media: fakeVideo))
    let video = try XCTUnwrap(media.video)

    XCTAssertNotNil(media)
    XCTAssertNil(media.gif)
    XCTAssertEqual(video.url, fakeVideo.url)
  }
}

extension CustomUpdateMedia {

  static func == (lhs: CustomUpdateMedia, rhs: CustomUpdateMedia) -> Bool {
    if lhs.gif != rhs.gif || lhs.video != rhs.video {
      return false
    }
    return true
  }
}