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

import FBSDKCoreKit
import XCTest

final class ShareBridgeAPIRequestFactoryTests: XCTestCase {
  let factory = ShareBridgeAPIRequestFactory()

  func testFactoryMakesRequests() {
    XCTAssertNotNil(
      factory.bridgeAPIRequest(
        with: .web,
        scheme: URLScheme.https.rawValue,
        methodName: nil,
        parameters: nil,
        userInfo: 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.
 */

import FBSDKCoreKit
import Foundation
import TestTools

@objcMembers
final class TestBridgeAPIRequestFactory: NSObject, BridgeAPIRequestCreating {
  var capturedProtocolType: FBSDKBridgeAPIProtocolType?
  var capturedScheme: String?
  var capturedMethodName: String?
  var capturedParameters: [String: Any]?
  var capturedUserInfo: [String: Any]?
  var stubbedBridgeAPIRequest: TestBridgeAPIRequest?

  func bridgeAPIRequest(
    with protocolType: FBSDKBridgeAPIProtocolType,
    scheme: String,
    methodName: String?,
    parameters: [String: Any]? = nil,
    userInfo: [String: Any]? = nil
  ) -> BridgeAPIRequestProtocol? {
    capturedProtocolType = protocolType
    capturedScheme = scheme
    capturedMethodName = methodName
    capturedParameters = parameters
    capturedUserInfo = userInfo

    return stubbedBridgeAPIRequest
  }
}


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

final class TestBridgeAPIResponse: BridgeAPIResponse {

  var stubbedResponseParameters: [String: Any]?

  override var responseParameters: [String: Any]? {
    stubbedResponseParameters ?? super.responseParameters
  }
}


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

final class TestInternalURLOpener: NSObject, ShareInternalURLOpening {

  var canOpenURL = false
  var computeCanOpenURL: ((URL) -> Bool)?

  func canOpenURL(_ url: URL) -> Bool {
    computeCanOpenURL?(url) ?? canOpenURL
  }
}


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

import FBSDKCoreKit
import TestTools
import XCTest

final class AppInviteContentTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional force_unwrapping
  var content: AppInviteContent!
  let appLinkURL = URL(string: "https://fb.me/1595011414049078")!
  let appInvitePreviewImageURL = URL(string: "https://fbstatic-a.akamaihd.net/rsrc.php/v2/y6/r/YQEGe6GxI_M.png")!
  var validator: TestShareUtility.Type!
  var errorFactory: TestErrorFactory!
  // swiftlint:enable implicitly_unwrapped_optional force_unwrapping

  override func setUp() {
    super.setUp()

    validator = TestShareUtility.self
    validator.reset()
    errorFactory = TestErrorFactory()

    AppInviteContent.setDependencies(
      .init(
        validator: validator,
        errorFactory: errorFactory
      )
    )

    content = AppInviteContent(appLinkURL: appLinkURL)
    content.appInvitePreviewImageURL = appInvitePreviewImageURL
  }

  override func tearDown() {
    AppInviteContent.resetDependencies()

    validator.reset()
    validator = nil
    errorFactory = nil
    content = nil

    super.tearDown()
  }

  func testDefaultDependencies() throws {
    AppInviteContent.resetDependencies()

    let dependencies = try AppInviteContent.getDependencies()
    XCTAssertTrue(dependencies.validator is _ShareUtility.Type, .usesShareUtilityByDefault)
    XCTAssertTrue(dependencies.errorFactory is _ErrorFactory, .usesErrorFactoryByDefault)
  }

  func testCustomDependencies() throws {
    let dependencies = try AppInviteContent.getDependencies()
    XCTAssertTrue(dependencies.validator is TestShareUtility.Type, .usesCustomShareValidator)
    XCTAssertIdentical(dependencies.errorFactory as AnyObject, errorFactory, .usesCustomErrorFactory)
  }

  func testProperties() {
    XCTAssertEqual(content.appLinkURL, appLinkURL, .hasAppLinkURL)
    XCTAssertEqual(content.appInvitePreviewImageURL, appInvitePreviewImageURL, .hasAppInvitePreviewImageURL)
  }

  func testValidationWithValidContent() throws {
    XCTAssertNoThrow(try content.validate(options: []), .passesValidationWithValidContent)
  }

  func testValidationWithNilPreviewImageURL() {
    content = AppInviteContent(appLinkURL: appLinkURL)
    XCTAssertNoThrow(try content.validate(options: []), .passesValidationWithoutPreviewImageURL)
  }

  func testValidationWithNilPromotionTextNilPromotionCode() {
    content = AppInviteContent(appLinkURL: appLinkURL)
    XCTAssertNoThrow(try content.validate(options: []), .passesValidationWithoutPromotionTextAndPromotionCode)
  }

  func testValidationWithValidPromotionCodeNilPromotionText() {
    content.promotionCode = "XSKSK"

    XCTAssertThrowsError(
      try content.validate(options: []),
      .failsValidationWithPromotionCodeOnly
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsValidationWithPromotionCodeOnly)
      }

      XCTAssertEqual(sdkError.type, .invalidArgument, .failsValidationWithPromotionCodeOnly)
      XCTAssertEqual(sdkError.name, "promotionText", .failsValidationWithPromotionCodeOnly)
      XCTAssertEqual(sdkError.value as? String, content.promotionText, .failsValidationWithPromotionCodeOnly)
      XCTAssertEqual(
        sdkError.message,
        "Invalid value for promotionText; promotionText has to be between 1 and 80 characters long.",
        .failsValidationWithPromotionCodeOnly
      )
      XCTAssertNil(sdkError.underlyingError, .failsValidationWithPromotionCodeOnly)
    }
  }

  func testValidationWithValidPromotionTextNilPromotionCode() {
    content.promotionText = "Some Promo Text"
    XCTAssertNoThrow(try content.validate(options: []), .failsValidationWithPromotionTextOnly)
  }

  func testValidationWithBadPromotionTextLength() {
    content.promotionText = String(repeating: "_Invalid_promotionText", count: 30)

    XCTAssertThrowsError(
      try content.validate(options: []),
      .failsValidationWithShortPromotionText
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsValidationWithShortPromotionText)
      }

      XCTAssertEqual(sdkError.type, .invalidArgument, .failsValidationWithShortPromotionText)
      XCTAssertEqual(sdkError.name, "promotionText", .failsValidationWithShortPromotionText)
      XCTAssertEqual(sdkError.value as? String, content.promotionText, .failsValidationWithShortPromotionText)
      XCTAssertEqual(
        sdkError.message,
        "Invalid value for promotionText; promotionText has to be between 1 and 80 characters long.",
        .failsValidationWithShortPromotionText
      )
      XCTAssertNil(sdkError.underlyingError, .failsValidationWithShortPromotionText)
    }
  }

  func testValidationWithBadPromotionTextContent() {
    content.promotionText = "_Invalid_promotionText"

    XCTAssertThrowsError(
      try content.validate(options: []),
      .failsValidationWithBadPromotionTextContent
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsValidationWithBadPromotionTextContent)
      }

      XCTAssertEqual(sdkError.type, .invalidArgument, .failsValidationWithBadPromotionTextContent)
      XCTAssertEqual(sdkError.name, "promotionText", .failsValidationWithBadPromotionTextContent)
      XCTAssertEqual(sdkError.value as? String, content.promotionText, .failsValidationWithBadPromotionTextContent)
      XCTAssertEqual(
        sdkError.message,
        "Invalid value for promotionText; promotionText can contain only alphanumeric characters and spaces.",
        .failsValidationWithBadPromotionTextContent
      )
      XCTAssertNil(sdkError.underlyingError, .failsValidationWithBadPromotionTextContent)
    }
  }

  func testValidationWithBadPromotionCodeLength() {
    content.promotionText = "Some promo text"
    content.promotionCode = "_invalid promo_code"

    XCTAssertThrowsError(
      try content.validate(options: []),
      .failsValidationWithShortPromotionCode
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsValidationWithShortPromotionCode)
      }

      XCTAssertEqual(sdkError.type, .invalidArgument, .failsValidationWithShortPromotionCode)
      XCTAssertEqual(sdkError.name, "promotionCode", .failsValidationWithShortPromotionCode)
      XCTAssertEqual(sdkError.value as? String, content.promotionCode, .failsValidationWithShortPromotionCode)
      XCTAssertEqual(
        sdkError.message,
        """
        Invalid value for promotionCode; promotionCode has to be between 0 and 10 characters long \
        and is required when promoCode is set.
        """,
        .failsValidationWithShortPromotionCode
      )
      XCTAssertNil(sdkError.underlyingError, .failsValidationWithShortPromotionCode)
    }
  }

  func testValidationWithBadPromotionCodeContent() {
    content.promotionText = "Some promo text"
    content.promotionCode = "_invalid"

    XCTAssertThrowsError(
      try content.validate(options: []),
      .failsValidationWithBadPromotionCode
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsValidationWithBadPromotionCode)
      }

      XCTAssertEqual(sdkError.type, .invalidArgument, .failsValidationWithBadPromotionCode)
      XCTAssertEqual(sdkError.name, "promotionCode", .failsValidationWithBadPromotionCode)
      XCTAssertEqual(sdkError.value as? String, content.promotionCode, .failsValidationWithBadPromotionCode)
      XCTAssertEqual(
        sdkError.message,
        "Invalid value for promotionCode; promotionCode can contain only alphanumeric characters and spaces.",
        .failsValidationWithBadPromotionCode
      )
      XCTAssertNil(sdkError.underlyingError, .failsValidationWithBadPromotionCode)
    }
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let usesErrorFactoryByDefault = "The default error factory dependency should be a concrete _ErrorFactory"
  static let usesShareUtilityByDefault = "The default share validator dependency should be the _ShareUtility type"
  static let usesCustomErrorFactory = "The error factory dependency should be configurable"
  static let usesCustomShareValidator = "The share validator dependency should be configurable"

  static let hasAppLinkURL = "An app invite content has an app link URL"
  static let hasAppInvitePreviewImageURL = "An app invite content has an invite preview image URL"

  static let passesValidationWithValidContent = "Validation should pass with valid content"
  static let passesValidationWithoutPreviewImageURL = "Validation should pass without a preview image URL"
  static let passesValidationWithoutPromotionTextAndPromotionCode = """
    Validation should pass when missing both promotion text and promotion code
    """
  static let failsValidationWithPromotionCodeOnly = "Validation should fail with only a promotion code"
  static let failsValidationWithPromotionTextOnly = "Validation should fail with only promotion text"
  static let failsValidationWithShortPromotionText = "Validation should fail with promotion text that is too short"
  static let failsValidationWithBadPromotionTextContent = "Validation should fail with invalid promotion text content"
  static let failsValidationWithShortPromotionCode = "Validation should fail with a promotion code that is too short"
  static let failsValidationWithBadPromotionCode = "Validation should fail with invalid promotion code content"
}


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

import XCTest

final class CameraEffectArgumentsTests: XCTestCase {
  func testTypes() {
    let arguments = CameraEffectArguments()

    // Supported types
    arguments.set("1234", forKey: "string")
    XCTAssertEqual(arguments.string(forKey: "string"), "1234")
    arguments.set(["a", "b", "c"], forKey: "string_array")
    XCTAssertEqual(arguments.array(forKey: "string_array"), ["a", "b", "c"])
    arguments.set([], forKey: "empty_array")
    XCTAssertEqual(arguments.array(forKey: "empty_array"), [])
    XCTAssertNil(arguments.string(forKey: "nil_string"))
    XCTAssertNil(arguments.array(forKey: "nil_array"))
  }
}


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

final class CameraEffectTexturesTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  var textures: CameraEffectTextures!
  let key = "sample-key"
  var image: UIImage!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    image = UIImage()
    textures = CameraEffectTextures()
  }

  override func tearDown() {
    image = nil
    textures = nil

    super.tearDown()
  }

  func testAddingImage() {
    textures.set(image, forKey: key)
    XCTAssertIdentical(textures.image(forKey: key), image, .canAddImage)
  }

  func testReplacingImage() {
    textures.set(image, forKey: key)
    let newImage = UIImage()
    textures.set(newImage, forKey: key)

    XCTAssertIdentical(textures.image(forKey: key), newImage, .canReplaceImage)
  }

  func testClearingImage() {
    textures.set(image, forKey: key)
    textures.set(nil, forKey: key)
    XCTAssertNil(textures.image(forKey: key), .canClearImage)
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let canAddImage = "Can add an image by key in a camera effect textures"
  static let canReplaceImage = "Can replace an image for a key in a camera effect textures"
  static let canClearImage = "Can remove an image for a key in a camera effect textures"
}


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

final class ContentFlagsTests: XCTestCase {

  var flags: ContentFlags! // swiftlint:disable:this implicitly_unwrapped_optional

  func testDefaultFlags() {
    flags = ContentFlags()
    XCTAssertFalse(flags.containsMedia, .mediaFlagDefaultsToFalse)
    XCTAssertFalse(flags.containsPhotos, .photosFlagDefaultsToFalse)
    XCTAssertFalse(flags.containsVideos, .videosFlagDefaultsToFalse)
  }

  func testCustomFlags() {
    flags = allTypesFlags()
    XCTAssertTrue(flags.containsMedia, .canInitializeWithCustomFlags)
    XCTAssertTrue(flags.containsPhotos, .canInitializeWithCustomFlags)
    XCTAssertTrue(flags.containsVideos, .canInitializeWithCustomFlags)
  }

  func testContainingAllTypes() {
    flags = ContentFlags()
    XCTAssertFalse(flags.containsAllTypes, .doesNotContainAllTypes)

    flags = ContentFlags(containsMedia: true)
    XCTAssertFalse(flags.containsAllTypes, .doesNotContainAllTypes)

    flags = ContentFlags(containsPhotos: true)
    XCTAssertFalse(flags.containsAllTypes, .doesNotContainAllTypes)

    flags = ContentFlags(containsVideos: true)
    XCTAssertFalse(flags.containsAllTypes, .doesNotContainAllTypes)

    flags = allTypesFlags(containsMedia: false)
    XCTAssertFalse(flags.containsAllTypes, .doesNotContainAllTypes)

    flags = allTypesFlags(containsPhotos: false)
    XCTAssertFalse(flags.containsAllTypes, .doesNotContainAllTypes)

    flags = allTypesFlags(containsVideos: false)
    XCTAssertFalse(flags.containsAllTypes, .doesNotContainAllTypes)

    flags = allTypesFlags()
    XCTAssertTrue(flags.containsAllTypes, .containsAllTypes)
  }

  func testFlagwiseOrAssignment() {
    for _ in 1 ... 100 {
      let originalFlags = randomFlags()
      let otherFlags = randomFlags()
      let expectedFlags = ContentFlags(
        containsMedia: originalFlags.containsMedia || otherFlags.containsMedia,
        containsPhotos: originalFlags.containsPhotos || otherFlags.containsPhotos,
        containsVideos: originalFlags.containsVideos || otherFlags.containsVideos
      )
      flags = originalFlags
      flags |= otherFlags

      XCTAssertEqual(flags.containsMedia, expectedFlags.containsMedia, .flagwiseOrAssignment)
      XCTAssertEqual(flags.containsPhotos, expectedFlags.containsPhotos, .flagwiseOrAssignment)
      XCTAssertEqual(flags.containsVideos, expectedFlags.containsVideos, .flagwiseOrAssignment)
    }
  }

  // MARK: - Helpers

  private func allTypesFlags(
    containsMedia: Bool = true,
    containsPhotos: Bool = true,
    containsVideos: Bool = true
  ) -> ContentFlags {
    ContentFlags(
      containsMedia: containsMedia,
      containsPhotos: containsPhotos,
      containsVideos: containsVideos
    )
  }

  private func randomFlags() -> ContentFlags {
    ContentFlags(containsMedia: .random(), containsPhotos: .random(), containsVideos: .random())
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let mediaFlagDefaultsToFalse = "The flags should not indicate containing media by default"
  static let photosFlagDefaultsToFalse = "The flags should not indicate containing photos by default"
  static let videosFlagDefaultsToFalse = "The flags should not indicate containing videos by default"

  static let canInitializeWithCustomFlags = "Should be able to create flags with custom values"

  static let doesNotContainAllTypes = "Flags should only indicate containing all types if all values are true"
  static let containsAllTypes = "Flags should indicate containing all types if all values are true"

  static let flagwiseOrAssignment = "Should be able to combine two sets of flags using an OR assignment"
}


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

import XCTest

final class HashtagTests: XCTestCase {

  let validRawHashtag = "#ValidHashtag"
  var hashtag: Hashtag! // swiftlint:disable:this implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()
    hashtag = Hashtag(validRawHashtag)
  }

  override func tearDown() {
    hashtag = nil
    super.tearDown()
  }

  func testValidHashtag() {
    XCTAssertTrue(hashtag.isValid, .validHashtag)
  }

  func testInvalidHashtagWithSpaces() {
    let leadingSpace = Hashtag(" #LeadingSpaceIsInvalid")
    XCTAssertFalse(leadingSpace.isValid, .invalidHashtag)

    let trailingspace = Hashtag("#TrailingSpaceIsInvalid ")
    XCTAssertFalse(trailingspace.isValid, .invalidHashtag)

    let embeddedSpace = Hashtag("#No spaces in hashtags")
    XCTAssertFalse(embeddedSpace.isValid, .invalidHashtag)
  }

  func testStringRepresentation() {
    XCTAssertEqual(hashtag.stringRepresentation, validRawHashtag, .stringRepresentation)
  }

  func testDescription() {
    XCTAssertEqual(hashtag.description, validRawHashtag, .validDescription)

    let invalidRawHashtag = "# InvalidHashtag"
    hashtag = Hashtag(invalidRawHashtag)
    XCTAssertEqual(hashtag.description, "Invalid hashtag '\(invalidRawHashtag)'", .invalidDescription)
  }

  func testHashability() {
    XCTAssertEqual(hashtag.hash, validRawHashtag.hash, .hashIsStringHash)
  }

  func testEquatability() {
    let hashtag = Hashtag("#ToCopy")
    let copy = Hashtag("#ToCopy")
    XCTAssertEqual(copy, hashtag, .equalStrings)

    copy.stringRepresentation = "#ModifiedCopy"
    XCTAssertNotEqual(copy, hashtag, .unequalStrings)
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let validHashtag = "A hashtag with a valid string should be considered valid"
  static let invalidHashtag = "A hashtag must start with an octothorp and contain no spaces"

  static let stringRepresentation = "A hashtag's string representation should be the provided raw string"

  static let validDescription = "An valid hashtag's description should be its raw string representation"
  static let invalidDescription = """
    An invalid hashtag's description should be a string indicating the invalidity and containg \
    the invalid hashtag string
    """

  static let hashIsStringHash = "The hash value of a hashtag should be the hash of its raw string"

  static let equalStrings = "Hashtags with equal strings should be considered equal"
  static let unequalStrings = "Hashtags with unequal strings should be considered unequal"
}


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

@testable import FBSDKShareKit

import FBSDKCoreKit
import TestTools
import XCTest

final class ShareCameraEffectContentTests: XCTestCase {
  // swiftlint:disable implicitly_unwrapped_optional
  var content: ShareCameraEffectContent!
  var internalUtility: TestInternalUtility!
  var errorFactory: TestErrorFactory!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    internalUtility = TestInternalUtility()
    errorFactory = TestErrorFactory()

    ShareCameraEffectContent.setDependencies(
      .init(
        internalUtility: internalUtility,
        errorFactory: errorFactory
      )
    )

    content = ShareCameraEffectContent()
  }

  override func tearDown() {
    internalUtility = nil
    errorFactory = nil
    content = nil

    ShareCameraEffectContent.resetDependencies()

    super.tearDown()
  }

  func testDefaultDependencies() throws {
    ShareCameraEffectContent.resetDependencies()

    let dependencies = try ShareCameraEffectContent.getDependencies()
    XCTAssertIdentical(dependencies.internalUtility as AnyObject, InternalUtility.shared, .usesInternalUtilityByDefault)
    XCTAssertTrue(dependencies.errorFactory is _ErrorFactory, .usesConcreteErrorFactoryByDefault)
  }

  func testCustomDependencies() throws {
    let dependencies = try ShareCameraEffectContent.getDependencies()
    XCTAssertIdentical(dependencies.internalUtility as AnyObject, internalUtility, .usesCustomInternalUtility)
    XCTAssertIdentical(dependencies.errorFactory, errorFactory, .usesCustomErrorFactory)
  }

  func testInvalidEffectIDs() throws {
    try ["a", "1a", "12345x67890"]
      .forEach { effectID in
        content.effectID = effectID
        XCTAssertThrowsError(
          try content.validate(options: []),
          "An effect ID (\(effectID)) must only contain digits"
        )
      }
  }

  func testValidEffectIDs() throws {
    try ["", "1", "123", "1234567890"]
      .forEach { effectID in
        content.effectID = effectID
        XCTAssertNoThrow(
          try content.validate(options: []),
          "An effect ID (\(effectID)) must only contain digits"
        )
      }
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let usesInternalUtilityByDefault = """
    The default internal utility dependency should be the shared InternalUtility
    """
  static let usesConcreteErrorFactoryByDefault = """
    The default error factory dependency should be a concrete _ErrorFactory
    """
  static let usesCustomInternalUtility = "The internal utility dependency should be configurable"
  static let usesCustomErrorFactory = "The error factory dependency should be configurable"
}


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

import FBSDKCoreKit
import XCTest

final class ShareLinkContentTests: XCTestCase {

  func testProperties() {
    let content = ShareModelTestUtility.linkContent
    XCTAssertEqual(content.contentURL, ShareModelTestUtility.contentURL)
    XCTAssertEqual(content.hashtag, ShareModelTestUtility.hashtag)
    XCTAssertEqual(content.peopleIDs, ShareModelTestUtility.peopleIDs)
    XCTAssertEqual(content.placeID, ShareModelTestUtility.placeID)
    XCTAssertEqual(content.ref, ShareModelTestUtility.ref)
    XCTAssertEqual(content.quote, ShareModelTestUtility.quote)
  }

  func testValidationWithValidContent() {
    XCTAssertNoThrow(
      try _ShareUtility.validateShareContent(ShareModelTestUtility.linkContent)
    )
  }
}


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

import XCTest

final class ShareMediaContentTests: XCTestCase {
  func testProperties() {
    let mediaContentMedia = ShareModelTestUtility.mediaContent.media
    let media = ShareModelTestUtility.media

    for (item1, item2) in zip(media, mediaContentMedia) {
      if let photo1 = item1 as? SharePhoto,
         let photo2 = item2 as? SharePhoto {
        XCTAssertEqual(photo1, photo2)
      } else if let video1 = item1 as? ShareVideo,
                let video2 = item2 as? ShareVideo {
        XCTAssertEqual(video1, video2)
      } else {
        XCTFail("Unexpected type implementing the ShareMedia protocol. Item1: \(item1), Item2: \(item2)")
      }
    }
  }
}


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

enum ShareModelTestUtility {

  // swiftlint:disable force_unwrapping
  static let cameraEffectID = "1234567"
  static let contentURL = URL(string: "https://developers.facebook.com/")!
  static let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!
  static let hashtag = Hashtag("#ashtag")
  static let isPhotoUserGenerated = true
  static let linkContentDescription = "this is my status"
  static let linkContentTitle = "my status"
  static let linkImageURL = URL(string: "https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xpa1/t39.2178-6/851594_549760571770473_1178259000_n.png")!
  static let media: [ShareMedia] = [video, photoWithImage]
  static let peopleIDs = [String]()
  static let photoImage = generatedImage
  static let photoImageURL = URL(string: "https://fbstatic-a.akamaihd.net/rsrc.php/v2/yC/r/YRwxe7CPWSs.png")!
  static let photoWithFileURL = SharePhoto(imageURL: fileURL, isUserGenerated: isPhotoUserGenerated)
  static let photoWithImage = SharePhoto(image: photoImage, isUserGenerated: isPhotoUserGenerated)
  static let photoWithImageURL = SharePhoto(imageURL: photoImageURL, isUserGenerated: isPhotoUserGenerated)
  static let placeID = "141887372509674"
  static let previewPropertyName = "myObject"
  static let quote = "quote"
  static let ref = "myref"
  static let video = ShareVideo(videoURL: videoURL)
  static let videoURL = URL(string: "assets-library://asset/asset.mp4?id=86C6970B-1266-42D0-91E8-4E68127D3864&ext=mp4")!
  static let videoWithPreviewPhoto = ShareVideo(videoURL: videoURL, previewPhoto: photoWithImageURL)
  // swiftlint:enable force_unwrapping

  static var linkContent: ShareLinkContent {
    let linkContent = linkContentWithoutQuote
    linkContent.quote = quote
    return linkContent
  }

  static var linkContentWithoutQuote: ShareLinkContent {
    let linkContent = ShareLinkContent()
    linkContent.contentURL = contentURL
    linkContent.hashtag = hashtag
    linkContent.peopleIDs = peopleIDs
    linkContent.placeID = placeID
    linkContent.ref = ref
    return linkContent
  }

  static var photoContent: SharePhotoContent {
    let content = SharePhotoContent()
    content.contentURL = contentURL
    content.hashtag = hashtag
    content.peopleIDs = peopleIDs
    content.photos = photos
    content.placeID = placeID
    content.ref = ref
    return content
  }

  static var photos: [SharePhoto] {
    [
      SharePhoto(
        imageURL: SampleURLs.validPNG,
        isUserGenerated: false
      ),
      SharePhoto(
        imageURL: SampleURLs.validPNG,
        isUserGenerated: false
      ),
      SharePhoto(
        imageURL: SampleURLs.validPNG,
        isUserGenerated: true
      ),
    ]
  }

  static var photosWithFileURLs: [SharePhoto] {
    [
      photoWithFileURL,
    ]
  }

  // equality checks are pointer equality for UIImage, so just return the same instance each time
  static var photosWithImages = [
    SharePhoto(image: generatedImage, isUserGenerated: true),
    SharePhoto(image: generatedImage, isUserGenerated: true),
    SharePhoto(image: generatedImage, isUserGenerated: true),
  ]

  static var generatedImage: UIImage = {
    UIGraphicsBeginImageContext(CGSize(width: 10.0, height: 10.0))
    guard let context = UIGraphicsGetCurrentContext() else {
      fatalError("Must be able to get a current context")
    }
    UIColor.red.setFill()
    context.fill(CGRect(x: 0.0, y: 0.0, width: 5.0, height: 5.0))
    UIColor.green.setFill()
    context.fill(CGRect(x: 5.0, y: 0.0, width: 5.0, height: 5.0))
    UIColor.blue.setFill()
    context.fill(CGRect(x: 5.0, y: 5.0, width: 5.0, height: 5.0))
    UIColor.yellow.setFill()
    context.fill(CGRect(x: 0.0, y: 5.0, width: 5.0, height: 5.0))
    guard let imageRef = context.makeImage() else {
      fatalError("Must be able to make a cg image")
    }
    UIGraphicsEndImageContext()
    return UIImage(cgImage: imageRef)
  }()

  static var photoContentWithFileURLs: SharePhotoContent {
    let content = SharePhotoContent()
    content.contentURL = contentURL
    content.hashtag = hashtag
    content.peopleIDs = peopleIDs
    content.photos = photosWithFileURLs
    content.placeID = placeID
    content.ref = ref
    return content
  }

  static var photoContentWithImages: SharePhotoContent {
    let content = SharePhotoContent()
    content.contentURL = contentURL
    content.hashtag = hashtag
    content.peopleIDs = peopleIDs
    content.photos = photosWithImages
    content.placeID = placeID
    content.ref = ref
    return content
  }

  static var videoContentWithoutPreviewPhoto: ShareVideoContent {
    let content = ShareVideoContent()
    content.contentURL = contentURL
    content.hashtag = hashtag
    content.peopleIDs = peopleIDs
    content.placeID = placeID
    content.ref = ref
    content.video = video
    return content
  }

  static var videoContentWithPreviewPhoto: ShareVideoContent {
    let content = ShareVideoContent()
    content.contentURL = contentURL
    content.hashtag = hashtag
    content.peopleIDs = peopleIDs
    content.placeID = placeID
    content.ref = ref
    content.video = videoWithPreviewPhoto
    return content
  }

  static var mediaContent: ShareMediaContent {
    let content = ShareMediaContent()
    content.media = media
    return content
  }

  static var multiVideoMediaContent: ShareMediaContent {
    let content = ShareMediaContent()
    content.media = [video, video]
    return content
  }

  static var cameraEffectArguments: CameraEffectArguments {
    let arguments = CameraEffectArguments()
    arguments.set("A string argument", forKey: "stringArg1")
    arguments.set("Another string argument", forKey: "stringArg2")
    return arguments
  }

  static var cameraEffectContent: ShareCameraEffectContent {
    let content = ShareCameraEffectContent()
    content.effectID = cameraEffectID
    content.effectArguments = cameraEffectArguments
    return content
  }
}


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

import FBSDKCoreKit
import Photos
import XCTest

final class SharePhotoContentTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  var content: SharePhotoContent!
  var imageFinder: TestMediaLibrarySearcher!
  var validator: TestShareUtility.Type!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    imageFinder = TestMediaLibrarySearcher()
    validator = TestShareUtility.self
    validator.reset()

    SharePhotoContent.setDependencies(
      .init(
        imageFinder: imageFinder,
        validator: TestShareUtility.self
      )
    )
  }

  override func tearDown() {
    imageFinder = nil
    validator.reset()
    validator = nil
    SharePhotoContent.resetDependencies()
    content = nil

    super.tearDown()
  }

  func testDefaultDependencies() throws {
    SharePhotoContent.resetDependencies()

    let dependencies = try SharePhotoContent.getDependencies()
    XCTAssertIdentical(dependencies.imageFinder as AnyObject, PHImageManager.default(), .usesPHImageManagerByDefault)
    XCTAssertTrue(dependencies.validator is _ShareUtility.Type, .usesShareUtilityByDefault)
  }

  func testCustomDependencies() throws {
    let dependencies = try SharePhotoContent.getDependencies()
    XCTAssertIdentical(dependencies.imageFinder as AnyObject, imageFinder, .usesCustomImageFinder)
    XCTAssertTrue(dependencies.validator is TestShareUtility.Type, .usesCustomShareValidator)
  }

  func testProperties() {
    let photos = makeSamplePhotos()
    content = makeSamplePhotoContent(photos: photos)

    XCTAssertEqual(content.contentURL, .content)
    XCTAssertEqual(content.peopleIDs, [.person1, .person2])
    XCTAssertEqual(content.placeID, .place)
    XCTAssertEqual(content.ref, .ref)

    XCTAssertEqual(content.photos.count, photos.count)
    zip(content.photos, photos).forEach { photo1, photo2 in
      XCTAssertEqual(photo1.imageURL, photo2.imageURL)
      XCTAssertEqual(photo1.isUserGenerated, photo2.isUserGenerated)
    }
  }

  func testValidationWithValidContent() {
    content = makeSamplePhotoContent(photos: [.regularImage])

    XCTAssertNoThrow(try content.validate(options: []))
    XCTAssertEqual(validator.validateArrayArray as? [SharePhoto], content.photos, .validationValidatesPhotos)
    XCTAssertEqual(validator.validateArrayMinCount, 1, .validationValidatesPhotos)
    XCTAssertEqual(validator.validateArrayMaxCount, 6, .validationValidatesPhotos)
    XCTAssertEqual(validator.validateArrayName, "photos", .validationValidatesPhotos)
  }

  func testValidationWithoutPhotos() {
    validator.validateArrayShouldThrow = true
    content = SharePhotoContent()

    XCTAssertThrowsError(try content.validate(options: []), .validationValidatesPhotos)
    XCTAssertEqual(validator.validateArrayArray as? [SharePhoto], content.photos, .validationValidatesPhotos)
    XCTAssertEqual(validator.validateArrayMinCount, 1, .validationValidatesPhotos)
    XCTAssertEqual(validator.validateArrayMaxCount, 6, .validationValidatesPhotos)
    XCTAssertEqual(validator.validateArrayName, "photos", .validationValidatesPhotos)
  }

  func testBridgeParameters() throws {
    let photos = makeSamplePhotos()
    content = makeSamplePhotoContent(photos: photos)
    imageFinder.stubbedFindImageImage = .forAsset

    let parameters = content.addParameters([:], options: [])

    let parameterPhotos = try XCTUnwrap(parameters["photos"] as? [UIImage], .imagesAreAddedToParameters)
    XCTAssertEqual(parameterPhotos.count, 3, .imagesAreAddedToParameters)

    let localImageData = UIImage.local.jpegData(compressionQuality: 1)
    XCTAssertTrue(
      parameterPhotos.contains { $0.jpegData(compressionQuality: 1) == localImageData },
      .imagesAreAddedToParameters
    )
    XCTAssertTrue(parameterPhotos.contains(.generated), .imagesAreAddedToParameters)
    XCTAssertTrue(parameterPhotos.contains(.forAsset), .imagesAreAddedToParameters)
    XCTAssertIdentical(imageFinder.findImageAsset, PHAsset.sample, .searchesForPHAssetImages)
  }

  // MARK: - Helpers

  private func makeSamplePhotoContent(photos: [SharePhoto]) -> SharePhotoContent {
    let content = SharePhotoContent()
    content.contentURL = .content
    content.peopleIDs = [.person1, .person2]
    content.photos = photos
    content.placeID = .place
    content.ref = .ref
    return content
  }

  private func makeSamplePhotos() -> [SharePhoto] {
    [
      .asset,
      .localURL,
      .remoteURL,
      .regularImage,
    ]
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let usesPHImageManagerByDefault = "The default image finder dependency should be the default PHImageManager"
  static let usesShareUtilityByDefault = "The default share validator dependency should be the _ShareUtility type"
  static let usesCustomImageFinder = "The image finder dependency should be configurable"
  static let usesCustomShareValidator = "The share validator dependency should be configurable"

  static let validationValidatesPhotos = """
    Validating a share photo content should validate its photos using its validator
    """

  static let imagesAreAddedToParameters = """
    Adding bridge parameters for share photo content should add images for all photos except those with remote URLs
    """
  static let searchesForPHAssetImages = """
    PHAsset images for bridge parameters should be found through the photo library searcher
    """
}

// MARK: - Test Values

fileprivate extension SharePhoto {
  static let asset = SharePhoto(photoAsset: .sample, isUserGenerated: true)
  static let localURL = SharePhoto(imageURL: .localImage, isUserGenerated: true)
  static let remoteURL = SharePhoto(imageURL: .remoteImage, isUserGenerated: true)
  static let regularImage = SharePhoto(image: .generated, isUserGenerated: true)
}

fileprivate extension URL {
  // swiftlint:disable force_unwrapping
  static let remoteImage = URL(string: "https://fbstatic-a.akamaihd.net/rsrc.php/v2/yC/r/YRwxe7CPWSs.png")!
  static let localImage = Bundle(for: SharePhotoContentTests.self)
    .url(forResource: "dog-or-muffin", withExtension: "jpeg")!
  static let content = URL(string: "https://developers.facebook.com/")!
  // swiftlint:enable force_unwrapped
}

fileprivate extension UIImage {
  static let generated = ShareModelTestUtility.generatedImage
  static let local = UIImage(contentsOfFile: URL.localImage.path)! // swiftlint:disable:this force_unwrapping
  static let forAsset = ShareModelTestUtility.generatedImage
}

fileprivate extension String {
  static let person1 = "1234"
  static let person2 = "5678"
  static let place = "9876"
  static let ref = "sample-ref"
}

fileprivate extension PHAsset {
  static let sample = PHAsset()
}


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

import FBSDKCoreKit
import Photos
import TestTools
import XCTest

final class SharePhotoTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  var photo: SharePhoto!
  var errorFactory: TestErrorFactory!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    errorFactory = TestErrorFactory()
    SharePhoto.setDependencies(.init(errorFactory: errorFactory))
  }

  override func tearDown() {
    errorFactory = nil
    SharePhoto.resetDependencies()
    photo = nil

    super.tearDown()
  }

  func testDefaultDependencies() throws {
    SharePhoto.resetDependencies()

    let dependencies = try SharePhoto.getDependencies()
    XCTAssertTrue(dependencies.errorFactory is _ErrorFactory, .usesConcreteErrorFactoryByDefault)
  }

  func testCustomDependencies() throws {
    let dependencies = try SharePhoto.getDependencies()
    XCTAssertIdentical(dependencies.errorFactory, errorFactory, .usesCustomErrorFactory)
  }

  func testCreatingWithImage() {
    photo = SharePhoto(image: UIImage(), isUserGenerated: true)
    XCTAssertNotNil(photo.image, .hasOnlyImageSource)
    XCTAssertNil(photo.photoAsset, .hasOnlyImageSource)
    XCTAssertNil(photo.imageURL, .hasOnlyImageSource)
  }

  func testCreatingWithAsset() {
    photo = SharePhoto(photoAsset: PHAsset(), isUserGenerated: true)
    XCTAssertNotNil(photo.photoAsset, .hasOnlyAssetSource)
    XCTAssertNil(photo.image, .hasOnlyAssetSource)
    XCTAssertNil(photo.imageURL, .hasOnlyAssetSource)
  }

  func testCreatingWithURL() {
    photo = SharePhoto(imageURL: SampleURLs.valid, isUserGenerated: true)
    XCTAssertNotNil(photo.imageURL, .hasOnlyURLSource)
    XCTAssertNil(photo.image, .hasOnlyURLSource)
    XCTAssertNil(photo.photoAsset, .hasOnlyURLSource)
  }

  func testChangingSource() {
    photo = SharePhoto(image: UIImage(), isUserGenerated: true)

    photo.photoAsset = PHAsset()
    XCTAssertNil(photo.image, .otherSourcesClearedWhenChangingSource)

    photo.imageURL = SampleURLs.valid
    XCTAssertNil(photo.photoAsset, .otherSourcesClearedWhenChangingSource)

    photo.image = UIImage()
    XCTAssertNil(photo.imageURL, .otherSourcesClearedWhenChangingSource)
  }

  func testValidatingWithoutSource() {
    photo = SharePhoto()

    XCTAssertThrowsError(try photo.validate(options: []), .failsValidationWithoutSource) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsValidationWithoutSource)
      }

      XCTAssertEqual(sdkError.domain, ShareErrorDomain, .failsValidationWithoutSource)
      XCTAssertEqual(sdkError.name, "photo", .failsValidationWithoutSource)
      XCTAssertIdentical(sdkError.value as AnyObject, photo, .failsValidationWithoutSource)
      XCTAssertEqual(
        sdkError.message,
        "Must have an asset, image, or imageURL value.",
        .failsValidationWithoutSource
      )
      XCTAssertNil(sdkError.underlyingError, .failsValidationWithoutSource)
    }
  }

  func testPhotoImageURLValidationFailureWithoutURL() {
    photo = SharePhoto()

    XCTAssertThrowsError(
      try photo.validate(options: .photoImageURL),
      .failsPhotoImageURLOptionValidationWithoutURL
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsPhotoImageURLOptionValidationWithoutURL)
      }

      XCTAssertEqual(sdkError.domain, ShareErrorDomain, .failsPhotoImageURLOptionValidationWithoutURL)
      XCTAssertEqual(sdkError.name, "photo", .failsPhotoImageURLOptionValidationWithoutURL)
      XCTAssertIdentical(sdkError.value as AnyObject, photo, .failsPhotoImageURLOptionValidationWithoutURL)
      XCTAssertEqual(
        sdkError.message,
        "imageURL is required.",
        .failsPhotoImageURLOptionValidationWithoutURL
      )
      XCTAssertNil(sdkError.underlyingError, .failsPhotoImageURLOptionValidationWithoutURL)
    }
  }

  func testPhotoImageURLValidationFailureWithoutFileURL() {
    photo = SharePhoto(imageURL: .localImage, isUserGenerated: true)

    XCTAssertThrowsError(
      try photo.validate(options: .photoImageURL),
      .failsPhotoImageURLOptionValidationWithoutRemoteURL
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsPhotoImageURLOptionValidationWithoutRemoteURL)
      }

      XCTAssertEqual(sdkError.domain, ShareErrorDomain, .failsPhotoImageURLOptionValidationWithoutRemoteURL)
      XCTAssertEqual(sdkError.name, "imageURL", .failsPhotoImageURLOptionValidationWithoutRemoteURL)
      XCTAssertEqual(sdkError.value as? URL, .localImage, .failsPhotoImageURLOptionValidationWithoutRemoteURL)
      XCTAssertEqual(
        sdkError.message,
        "Cannot refer to a local file resource.",
        .failsPhotoImageURLOptionValidationWithoutRemoteURL
      )
      XCTAssertNil(sdkError.underlyingError, .failsPhotoImageURLOptionValidationWithoutRemoteURL)
    }
  }

  func testPhotoImageURLValidationSuccess() {
    photo = SharePhoto(imageURL: .remoteImage, isUserGenerated: true)
    XCTAssertNoThrow(try photo.validate(options: .photoImageURL), .passesPhotoImageURLOptionValidation)
  }

  func testAssetValidationFailureWithoutImageMediaType() {
    let asset = TestPHAsset()
    photo = SharePhoto(photoAsset: asset, isUserGenerated: true)

    XCTAssertThrowsError(
      try photo.validate(options: []),
      .failsAssetValidationWithoutImageMediaType
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsAssetValidationWithoutImageMediaType)
      }

      XCTAssertEqual(sdkError.domain, ShareErrorDomain, .failsAssetValidationWithoutImageMediaType)
      XCTAssertEqual(sdkError.name, "photoAsset", .failsAssetValidationWithoutImageMediaType)
      XCTAssertIdentical(sdkError.value as AnyObject, asset, .failsAssetValidationWithoutImageMediaType)
      XCTAssertEqual(
        sdkError.message,
        "Must refer to a photo or other static image.",
        .failsAssetValidationWithoutImageMediaType
      )
      XCTAssertNil(sdkError.underlyingError, .failsAssetValidationWithoutImageMediaType)
    }
  }

  func testAssetValidationSuccess() {
    let asset = TestPHAsset()
    asset.stubbedMediaType = .image
    photo = SharePhoto(photoAsset: asset, isUserGenerated: true)

    XCTAssertNoThrow(try photo.validate(options: []), .passesAssetValidation)
  }

  func testURLValidationFailure() {
    photo = SharePhoto(imageURL: .remoteImage, isUserGenerated: true)

    XCTAssertThrowsError(
      try photo.validate(options: []),
      .failsURLValidationWithoutFileURL
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsURLValidationWithoutFileURL)
      }

      XCTAssertEqual(sdkError.domain, ShareErrorDomain, .failsURLValidationWithoutFileURL)
      XCTAssertEqual(sdkError.name, "imageURL", .failsURLValidationWithoutFileURL)
      XCTAssertEqual(sdkError.value as? URL, .remoteImage, .failsURLValidationWithoutFileURL)
      XCTAssertEqual(
        sdkError.message,
        "Must refer to a local file resource.",
        .failsURLValidationWithoutFileURL
      )
      XCTAssertNil(sdkError.underlyingError, .failsURLValidationWithoutFileURL)
    }
  }

  func testURLValidationSuccess() {
    photo = SharePhoto(imageURL: .localImage, isUserGenerated: true)
    XCTAssertNoThrow(try photo.validate(options: []), .passesURLValidation)
  }

  func testImageValidation() {
    let image = UIImage()
    photo = SharePhoto(image: image, isUserGenerated: true)
    XCTAssertNoThrow(try photo.validate(options: []), .passesImageValidation)
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let usesConcreteErrorFactoryByDefault = """
    The default error factory dependency should be a concrete _ErrorFactory
    """
  static let usesCustomErrorFactory = "The error factory dependency should be configurable"

  static let hasOnlyImageSource = "A photo with an image source should only have an image source"
  static let hasOnlyAssetSource = "A photo with an asset source should only have an asset source"
  static let hasOnlyURLSource = "A photo with a URL source should only have a URL source"
  static let otherSourcesClearedWhenChangingSource = "Changing a photo source should clear other sources"

  static let failsValidationWithoutSource = "Validating a photo without a source should throw an error"
  static let failsPhotoImageURLOptionValidationWithoutURL = """
    Validating a photo with the image photo URL option should fail without a URL
    """
  static let failsPhotoImageURLOptionValidationWithoutRemoteURL = """
    Validating a photo with the image photo URL option should fail without a remote URL
    """
  static let passesPhotoImageURLOptionValidation = """
    Validating a photo with the image photo URL option should pass with a file URL
    """
  static let failsAssetValidationWithoutImageMediaType = """
    Validating an asset photo without the image media type should throw an error
    """
  static let passesAssetValidation = "Validating an asset photo with the image media type should pass"
  static let failsURLValidationWithoutFileURL = "Validating a URL photo should fail without a file URL"
  static let passesURLValidation = "Validating a URL photo should pass with a file URL"
  static let passesImageValidation = "Validating an image photo should pass"
}

// MARK: - Test Values

fileprivate extension URL {
  // swiftlint:disable force_unwrapping
  static let remoteImage = URL(string: "https://facebook.com/myPhoto.png")!
  static let localImage = URL(string: "file:///Users/anyone/myPhoto.png")!
  // swiftlint:enable force_unwrapping
}


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

import FBSDKCoreKit
import Photos
import TestTools
import UIKit
import XCTest

final class ShareVideoContentTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  var content: ShareVideoContent!
  var validator: TestShareUtility.Type!
  var mediaLibrarySearcher: TestMediaLibrarySearcher!
  var errorFactory: TestErrorFactory!
  var testError: TestSDKError!
  // swiftlint:enable implicitly_unwrapped_optional

  let originalParameters = ["original": "value"]

  override func setUp() {
    super.setUp()

    validator = TestShareUtility.self
    validator.reset()
    mediaLibrarySearcher = TestMediaLibrarySearcher()
    ShareVideoContent.setDependencies(
      .init(
        validator: TestShareUtility.self,
        mediaLibrarySearcher: mediaLibrarySearcher
      )
    )

    errorFactory = TestErrorFactory()
    testError = TestSDKError(type: .unknown)
    errorFactory.stubbedError = testError
    ShareVideo.setDependencies(.init(errorFactory: errorFactory))
  }

  override func tearDown() {
    ShareVideo.resetDependencies()
    ShareVideoContent.resetDependencies()
    validator.reset()
    validator = nil
    mediaLibrarySearcher = nil
    errorFactory = nil
    testError = nil
    content = nil

    super.tearDown()
  }

  func testDefaultDependencies() throws {
    ShareVideoContent.resetDependencies()

    let dependencies = try ShareVideoContent.getDependencies()
    XCTAssertTrue(
      dependencies.validator is _ShareUtility.Type,
      .usesShareUtilityAsShareValidatorByDefault
    )
    XCTAssertIdentical(
      dependencies.mediaLibrarySearcher as AnyObject,
      PHImageManager.default(),
      .usesPHImageManagerAsMediaLibrarySearcherByDefault
    )
  }

  func testCustomDependencies() throws {
    let dependencies = try ShareVideoContent.getDependencies()
    XCTAssertTrue(dependencies.validator is TestShareUtility.Type, .usesCustomShareValidator)
    XCTAssertIdentical(
      dependencies.mediaLibrarySearcher as AnyObject,
      mediaLibrarySearcher,
      .usesCustomMediaLibrarySearcher
    )
  }

  func testProperties() {
    content = .allProperties

    XCTAssertIdentical(content.video as AnyObject, ShareVideo.plain, .hasVideo)
    XCTAssertEqual(content.contentURL, .content, .hasContentURL)
    XCTAssertIdentical(content.hashtag as AnyObject, Hashtag.sample, .hasHashtag)
    XCTAssertEqual(content.peopleIDs, .peopleIDs, .hasPeopleIDs)
    XCTAssertEqual(content.placeID, .placeID, .hasPlaceID)
    XCTAssertEqual(content.ref, .ref, .hasRef)
    XCTAssertEqual(content.pageID, .pageID, .hasPageID)
    XCTAssertNotNil(content.shareUUID, .hasShareUUID)
  }

  // MARK: - Validation

  func testValidationWithInvalidContent() throws {
    content = ShareVideoContent()

    XCTAssertThrowsError(try content.validate(options: []), .validationValidatesVideo) { _ in
      XCTAssertIdentical(validator.validateRequiredValueValue as? ShareVideo, content.video, .validationValidatesVideo)
      XCTAssertEqual(validator.validateRequiredValueName, "video", .validationValidatesVideo)
    }
  }

  func testValidationWithValidContent() throws {
    content = .valid(.withAssetURLSource)

    XCTAssertNoThrow(try content.validate(options: []), .validationValidatesVideo)
    XCTAssertIdentical(validator.validateRequiredValueValue as? ShareVideo, content.video, .validationValidatesVideo)
    XCTAssertEqual(validator.validateRequiredValueName, "video", .validationValidatesVideo)
  }

  // MARK: - Bridge Parameters

  func testInvalidVideoBridgeParameters() throws {
    content = ShareVideoContent()
    let parameters = content.addParameters(originalParameters, options: [])

    try validateOriginalParameters(in: parameters)
    try validateEmptyVideoParameters(in: parameters, .invalidVideoParameters)
  }

  // MARK: Asset Source

  func testAssetSourceBridgeParametersWithAssetOption() throws {
    content = .valid(.withAssetSource)
    let parameters = content.addParameters(originalParameters, options: .videoAsset)

    try validateOriginalParameters(in: parameters)
    let videoParameters = try getVideoParameters(from: parameters, message: .assetIdentifierParameter)
    let identifier = try XCTUnwrap(videoParameters["assetIdentifier"] as? String, .assetIdentifierParameter)
    XCTAssertEqual(identifier, PHAsset.testAsset.localIdentifier, .assetIdentifierParameter)
  }

  func testAssetSourceBridgeParametersWithoutAssetOption() throws {
    content = .valid(.withAssetSource)
    mediaLibrarySearcher.stubbedGetVideoURL = .asset
    let parameters = content.addParameters(originalParameters, options: [])

    try validateOriginalParameters(in: parameters)
    XCTAssertIdentical(mediaLibrarySearcher.getVideoURLAsset, ShareVideo.withAssetSource.videoAsset, .assetURLParameter)
    let videoParameters = try getVideoParameters(from: parameters, message: .assetURLParameter)
    let url = try XCTUnwrap(videoParameters["assetURL"] as? URL, .assetURLParameter)
    XCTAssertEqual(url, .asset, .assetURLParameter)
  }

  // MARK: Data Source

  func testDataSourceBridgeParametersWithoutDataOption() throws {
    content = .valid(.withDataSource)
    let parameters = content.addParameters(originalParameters, options: [])

    try validateOriginalParameters(in: parameters)
    try validateEmptyVideoParameters(in: parameters, .dataVideoParametersWithoutDataOption)
  }

  func testDataSourceBridgeParametersWithDataOption() throws {
    content = .valid(.withDataSource)
    let parameters = content.addParameters(originalParameters, options: [.videoData])

    try validateOriginalParameters(in: parameters)
    let videoParameters = try getVideoParameters(from: parameters, message: .dataVideoParametersWithDataOption)
    let data = try XCTUnwrap(videoParameters["data"] as? Data, .dataVideoParametersWithDataOption)
    XCTAssertEqual(data, .video, .dataVideoParametersWithDataOption)
  }

  // MARK: URL Source

  func testRemoteURLSourceBridgeParameters() throws {
    content = .valid(.withRemoteURLSource)
    let parameters = content.addParameters(originalParameters, options: [])

    try validateOriginalParameters(in: parameters)
    try validateEmptyVideoParameters(in: parameters, .remoteURLVideoParameters)
  }

  func testURLSourceBridgeParametersWithAssetURL() throws {
    content = .valid(.withAssetURLSource)
    let parameters = content.addParameters(originalParameters, options: [])

    try validateOriginalParameters(in: parameters)
    let videoParameters = try getVideoParameters(from: parameters, message: .urlVideoWithAssetURLParameters)
    let url = try XCTUnwrap(videoParameters["assetURL"] as? URL, .urlVideoWithAssetURLParameters)
    XCTAssertEqual(url, .asset, .urlVideoWithAssetURLParameters)
  }

  func testURLSourceBridgeParametersWithFileURLWithoutDataOption() throws {
    content = .valid(.withFileURLSource)
    let parameters = content.addParameters(originalParameters, options: [])

    try validateOriginalParameters(in: parameters)
    try validateEmptyVideoParameters(in: parameters, .fileURLVideoParametersWithoutDataOption)
  }

  func testVideoURLBridgeParametersWithFileURLWithDataOption() throws {
    content = .valid(.withFileURLSource)
    let parameters = content.addParameters(originalParameters, options: [.videoData])

    try validateOriginalParameters(in: parameters)
    let videoParameters = try getVideoParameters(from: parameters, message: .fileURLVideoParametersWithDataOption)
    let data = try XCTUnwrap(videoParameters["data"] as? Data, .fileURLVideoParametersWithDataOption)
    XCTAssertEqual(data, .video, .fileURLVideoParametersWithDataOption)
  }

  func testMissingPreviewPhotoBridgeParameters() throws {
    content = .withoutPreviewPhoto
    let parameters = content.addParameters(originalParameters, options: [])

    let videoParameters = try getVideoParameters(from: parameters, message: .missingPreviewPhotoParameter)
    XCTAssertNil(videoParameters["previewPhoto"], .missingPreviewPhotoParameter)
  }

  func testPreviewPhotoBridgeParameters() throws {
    content = .withPreviewPhoto
    let parameters = content.addParameters(originalParameters, options: [])

    let videoParameters = try getVideoParameters(from: parameters, message: .previewPhotoParameter)
    let photo = try XCTUnwrap(videoParameters["previewPhoto"] as? SharePhoto, .previewPhotoParameter)
    XCTAssertIdentical(photo, content.video.previewPhoto, .previewPhotoParameter)
  }

  // MARK: - Helpers

  private func validateOriginalParameters(
    in parameters: [String: Any],
    file: StaticString = #file,
    line: UInt = #line
  ) throws {
    let value = try XCTUnwrap(parameters["original"] as? String, .maintainsOriginalParameters, file: file, line: line)
    XCTAssertEqual(value, "value", .maintainsOriginalParameters, file: file, line: line)
  }

  private func getVideoParameters(
    from parameters: [String: Any],
    message: String,
    file: StaticString = #file,
    line: UInt = #line
  ) throws -> [String: Any] {
    try XCTUnwrap(parameters["video"] as? [String: Any], message, file: file, line: line)
  }

  private func validateEmptyVideoParameters(
    in parameters: [String: Any],
    _ message: String,
    file: StaticString = #file,
    line: UInt = #line
  ) throws {
    let videoParameters = try XCTUnwrap(parameters["video"] as? [String: Any], message, file: file, line: line)
    XCTAssertTrue(videoParameters.isEmpty, message, file: file, line: line)
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let usesShareUtilityAsShareValidatorByDefault = """
    The default share validator dependency should be the _ShareUtility type
    """
  static let usesPHImageManagerAsMediaLibrarySearcherByDefault = """
    The default media library searching dependency should be the default PHImageManager
    """
  static let usesCustomShareValidator = "The share validator dependency should be configurable"
  static let usesCustomMediaLibrarySearcher = "The media library searching dependency should be configurable"

  static let hasVideo = "A share video content has a video"
  static let hasContentURL = "A share video content can have a content URL"
  static let hasHashtag = "A share video content can have a hashtag"
  static let hasPeopleIDs = "A share video content has people IDs"
  static let hasPlaceID = "A share video content can have a place ID"
  static let hasRef = "A share video content can have a ref"
  static let hasPageID = "A share video content can have a page ID"
  static let hasShareUUID = "A share video content has a share UUID"

  static let validationValidatesVideo = """
    Validating a share video content should validate its video using its validator
    """

  static let maintainsOriginalParameters = "A share video content should maintain the original parameters"
  static let invalidVideoParameters = "An invalid video should not provide any parameters"
  static let assetIdentifierParameter = """
    A video with an asset source should provide the asset's identifier when using the video asset option
    """
  static let assetURLParameter = """
    A video with an asset source should provide the asset's URL when not using the video asset option
    """
  static let dataVideoParametersWithoutDataOption = "A video with a data source should not provide any parameters"
  static let dataVideoParametersWithDataOption = "A video with a data source should provide the video's data"
  static let urlVideoWithAssetURLParameters = "A video with an asset source should provide the asset's data"
  static let fileURLVideoParametersWithoutDataOption = """
    A video with a file URL should not provide any parameters without the video data option
    """
  static let fileURLVideoParametersWithDataOption = "A video with a file URL should provide the video's data"
  static let remoteURLVideoParameters = "A video with a remote URL source should not provide any parameters"
  static let missingPreviewPhotoParameter = """
    A video without a preview photo should not include a preview photo parameter
    """
  static let previewPhotoParameter = "A video with a preview photo should include a preview photo parameter"
}

// MARK: - Test values

fileprivate extension URL {
  // swiftlint:disable force_unwrapping
  static let content = URL(string: "https://developers.facebook.com/")!
  static let remote = URL(string: "https://developers.facebook.com/")!
  static let asset = URL(
    string: "assets-library://asset/asset.mp4?id=86C6970B-1266-42D0-91E8-4E68127D3864&ext=mp4"
  )!
  static let local = Bundle(for: ShareVideoContentTests.self)
    .url(forResource: "dog-or-muffin", withExtension: "jpeg")!
  static let remotePhoto = URL(string: "https://fbstatic-a.akamaihd.net/rsrc.php/v2/yC/r/YRwxe7CPWSs.png")!
  // swiftlint:enable force_unwrapping
}

fileprivate extension Hashtag {
  static let sample = Hashtag("#sample")
}

fileprivate extension String {
  static let placeID = "141887372509674"
  static let ref = "sample-ref"
  static let pageID = "12345"
}

fileprivate extension Array where Element == String {
  static let peopleIDs = ["person1", "person2"]
}

fileprivate extension Data {
  static let video = try! Data(contentsOf: .local, options: .mappedIfSafe) // swiftlint:disable:this force_try
}

fileprivate extension ShareVideo {
  static let plain = ShareVideo()
  static let withAssetSource = ShareVideo(videoAsset: .testAsset)
  static let withDataSource = ShareVideo(data: .video)
  static let withRemoteURLSource = ShareVideo(videoURL: .remote)
  static let withAssetURLSource = ShareVideo(videoURL: .asset)
  static let withFileURLSource = ShareVideo(videoURL: .local)
}

fileprivate extension PHAsset {
  static let testAsset: TestPHAsset = {
    let asset = TestPHAsset()
    asset.stubbedLocalIdentifier = "identifier"
    return asset
  }()
}

fileprivate extension SharePhoto {
  static let sample = SharePhoto(imageURL: .remotePhoto, isUserGenerated: true)
}

fileprivate extension ShareVideoContent {
  static let allProperties: ShareVideoContent = {
    let content = ShareVideoContent()
    content.video = .plain
    content.contentURL = .content
    content.hashtag = .sample
    content.peopleIDs = .peopleIDs
    content.placeID = .placeID
    content.ref = .ref
    content.pageID = .pageID
    return content
  }()

  static func valid(_ video: ShareVideo) -> ShareVideoContent {
    let content = ShareVideoContent()
    content.video = video
    return content
  }

  static let withoutPreviewPhoto = ShareVideoContent()

  static let withPreviewPhoto: ShareVideoContent = {
    let video = ShareVideo()
    video.previewPhoto = .sample
    let content = ShareVideoContent()
    content.video = video
    return content
  }()
}


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

import FBSDKCoreKit
import Photos
import TestTools
import XCTest

final class ShareVideoTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  var video: ShareVideo!
  var errorFactory: TestErrorFactory!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    errorFactory = TestErrorFactory()
    ShareVideo.setDependencies(.init(errorFactory: errorFactory))
  }

  override func tearDown() {
    errorFactory = nil
    ShareVideo.resetDependencies()
    video = nil

    super.tearDown()
  }

  func testDefaultDependencies() throws {
    ShareVideo.resetDependencies()

    let dependencies = try ShareVideo.getDependencies()
    XCTAssertTrue(dependencies.errorFactory is _ErrorFactory, .usesConcreteErrorFactoryByDefault)
  }

  func testCustomDependencies() throws {
    let dependencies = try ShareVideo.getDependencies()
    XCTAssertIdentical(dependencies.errorFactory, errorFactory, .usesCustomErrorFactory)
  }

  func testCreatingWithData() {
    video = ShareVideo(data: Data())
    XCTAssertNotNil(video.data, .hasOnlyDataSource)
    XCTAssertNil(video.videoAsset, .hasOnlyDataSource)
    XCTAssertNil(video.videoURL, .hasOnlyDataSource)
  }

  func testCreatingWithAsset() {
    video = ShareVideo(videoAsset: PHAsset())
    XCTAssertNotNil(video.videoAsset, .hasOnlyAssetSource)
    XCTAssertNil(video.data, .hasOnlyAssetSource)
    XCTAssertNil(video.videoURL, .hasOnlyAssetSource)
  }

  func testCreatingWithURL() {
    video = ShareVideo(videoURL: SampleURLs.valid)
    XCTAssertNotNil(video.videoURL, .hasOnlyURLSource)
    XCTAssertNil(video.data, .hasOnlyURLSource)
    XCTAssertNil(video.videoAsset, .hasOnlyURLSource)
  }

  func testChangingSource() {
    video = ShareVideo(data: Data(), previewPhoto: SharePhoto())
    XCTAssertNotNil(video.previewPhoto, .canHavePreviewPhoto)

    video.videoAsset = PHAsset()
    XCTAssertNil(video.data, .otherSourcesClearedWhenChangingSource)
    XCTAssertNil(video.previewPhoto, .previewPhotoClearedWhenChangingSource)

    // Add back preview photo
    video.previewPhoto = SharePhoto()
    XCTAssertNotNil(video.previewPhoto, .canHavePreviewPhoto)

    video.videoURL = SampleURLs.valid
    XCTAssertNil(video.videoAsset, .otherSourcesClearedWhenChangingSource)
    XCTAssertNil(video.previewPhoto, .previewPhotoClearedWhenChangingSource)

    // Add back preview photo
    video.previewPhoto = SharePhoto()
    XCTAssertNotNil(video.previewPhoto, .canHavePreviewPhoto)

    video.data = Data()
    XCTAssertNil(video.videoURL, .otherSourcesClearedWhenChangingSource)
    XCTAssertNil(video.previewPhoto, .previewPhotoClearedWhenChangingSource)
  }

  func testValidatingWithoutSource() {
    video = ShareVideo()

    XCTAssertThrowsError(try video.validate(options: []), .failsValidationWithoutSource) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsValidationWithoutSource)
      }

      XCTAssertEqual(sdkError.domain, ShareErrorDomain, .failsValidationWithoutSource)
      XCTAssertEqual(sdkError.name, "video", .failsValidationWithoutSource)
      XCTAssertIdentical(sdkError.value as AnyObject, video, .failsValidationWithoutSource)
      XCTAssertEqual(
        sdkError.message,
        "Must have an asset, data, or videoURL value.",
        .failsValidationWithoutSource
      )
      XCTAssertNil(sdkError.underlyingError, .failsValidationWithoutSource)
    }
  }

  func testValidationFailureWithDataSource() {
    let data = Data()
    video = ShareVideo(data: data, previewPhoto: nil)

    XCTAssertThrowsError(
      try video.validate(options: []),
      .failsBadOptionsValidationWithDataSource
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsBadOptionsValidationWithDataSource)
      }

      XCTAssertEqual(sdkError.domain, ShareErrorDomain, .failsBadOptionsValidationWithDataSource)
      XCTAssertEqual(sdkError.name, "data", .failsBadOptionsValidationWithDataSource)
      XCTAssertEqual(sdkError.value as? Data, data, .failsBadOptionsValidationWithDataSource)
      XCTAssertEqual(
        sdkError.message,
        "Cannot share video data.",
        .failsBadOptionsValidationWithDataSource
      )
      XCTAssertNil(sdkError.underlyingError, .failsBadOptionsValidationWithDataSource)
    }
  }

  func testValidationSuccessWithDataSource() {
    video = ShareVideo(data: Data(), previewPhoto: nil)
    XCTAssertNoThrow(try video.validate(options: .videoData), .passesValidationWithDataSource)
  }

  func testValidationFailureWithAssetSource() {
    let asset = TestPHAsset()
    asset.stubbedMediaType = .audio
    video = ShareVideo(videoAsset: asset, previewPhoto: nil)

    XCTAssertThrowsError(
      try video.validate(options: []),
      .failsBadMediaTypeValidationWithAssetSource
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsBadMediaTypeValidationWithAssetSource)
      }

      XCTAssertEqual(sdkError.domain, ShareErrorDomain, .failsBadMediaTypeValidationWithAssetSource)
      XCTAssertEqual(sdkError.name, "videoAsset", .failsBadMediaTypeValidationWithAssetSource)
      XCTAssertIdentical(sdkError.value as AnyObject, asset, .failsBadMediaTypeValidationWithAssetSource)
      XCTAssertEqual(
        sdkError.message,
        "Must refer to a video file.",
        .failsBadMediaTypeValidationWithAssetSource
      )
      XCTAssertNil(sdkError.underlyingError, .failsBadMediaTypeValidationWithAssetSource)
    }
  }

  func testValidationSuccessWithAssetSource() {
    let asset = TestPHAsset()
    asset.stubbedMediaType = .video
    video = ShareVideo(videoAsset: asset, previewPhoto: nil)

    XCTAssertNoThrow(try video.validate(options: .videoData), .passesValidationWithAssetSource)
  }

  func testValidationFailureWithRemoteURLSource() {
    let url = URL.remoteVideo
    video = ShareVideo(videoURL: url, previewPhoto: nil)

    XCTAssertThrowsError(
      try video.validate(options: []),
      .failsValidationWithRemoteURLSource
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsValidationWithRemoteURLSource)
      }

      XCTAssertEqual(sdkError.domain, ShareErrorDomain, .failsValidationWithRemoteURLSource)
      XCTAssertEqual(sdkError.name, "videoURL", .failsValidationWithRemoteURLSource)
      XCTAssertEqual(sdkError.value as? URL, url, .failsValidationWithRemoteURLSource)
      XCTAssertEqual(
        sdkError.message,
        "Must refer to an asset file.",
        .failsValidationWithRemoteURLSource
      )
      XCTAssertNil(sdkError.underlyingError, .failsValidationWithRemoteURLSource)
    }
  }

  func testValidationFailureWithFileURLSource() {
    let url = URL.localVideo
    video = ShareVideo(videoURL: url, previewPhoto: nil)

    XCTAssertThrowsError(
      try video.validate(options: []),
      .failsValidationWithFileURLSource
    ) { error in
      guard let sdkError = error as? TestSDKError else {
        return XCTFail(.failsValidationWithFileURLSource)
      }

      XCTAssertEqual(sdkError.domain, ShareErrorDomain, .failsValidationWithFileURLSource)
      XCTAssertEqual(sdkError.name, "videoURL", .failsValidationWithFileURLSource)
      XCTAssertEqual(sdkError.value as? URL, url, .failsValidationWithFileURLSource)
      XCTAssertEqual(
        sdkError.message,
        "Must refer to an asset file.",
        .failsValidationWithFileURLSource
      )
      XCTAssertNil(sdkError.underlyingError, .failsValidationWithFileURLSource)
    }
  }

  func testValidationSuccessWithAssetLibraryURLSource() throws {
    video = ShareVideo(videoURL: .videoAsset, previewPhoto: nil)
    XCTAssertNoThrow(try video.validate(options: []))
  }

  func testValidationSuccessWithFileURLSource() throws {
    video = ShareVideo(videoURL: .localVideo, previewPhoto: nil)
    XCTAssertNoThrow(try video.validate(options: .videoData))
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let usesConcreteErrorFactoryByDefault = """
    The default error factory dependency should be a concrete _ErrorFactory
    """
  static let usesCustomErrorFactory = "The error factory dependency should be configurable"

  static let hasOnlyDataSource = "A video with a data source should only have a data source"
  static let hasOnlyAssetSource = "A video with an asset source should only have an asset source"
  static let hasOnlyURLSource = "A video with a URL source should only have a URL source"
  static let canHavePreviewPhoto = "A video should be able to have a preview photo"
  static let otherSourcesClearedWhenChangingSource = "Changing a video source should clear other sources"
  static let previewPhotoClearedWhenChangingSource = "Changing a video source should clear the preview photo"

  static let failsValidationWithoutSource = "Validating a video without a source should throw an error"
  static let failsBadOptionsValidationWithDataSource = """
    Validating a data source video without using the video data bridge option should throw an error
    """
  static let passesValidationWithDataSource = """
    Validating a data source video with the video data bridge option should not throw an error
    """
  static let failsBadMediaTypeValidationWithAssetSource = """
    Validating an asset source video without the video media type should throw an error
    """
  static let passesValidationWithAssetSource = """
    Validating an asset source video with the video media type should not throw an error
    """
  static let failsValidationWithRemoteURLSource = """
    Validating a URL source video with a remote URL should throw an error
    """
  static let failsValidationWithFileURLSource = """
    Validating a URL source video with a file URL without using the video data bridge option should throw an error
    """
  static let passesValidationWithAssetLibraryURLSource = """
    Validating a URL source video with an asset library URL should not throw an error
    """
  static let passesValidationWithFileURLSource = """
    Validating a URL source video with a file URL and the video data bridge option should not throw an error
    """
}

// MARK: - Test Values

fileprivate extension URL {
  // swiftlint:disable force_unwrapping
  static let videoAsset = URL(string: "assets-library://blah-blah-blah/video.mpg")!
  static let remoteVideo = URL(string: "https://facebook.com/myVideo.mpg")!
  static let localVideo = URL(string: "file:///Users/anyone/myVideo.mpg")!
  // swiftlint:enable force_unwrapping
}


/*
 * 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 FBSDKShareKit
import Photos
import UIKit

final class TestMediaLibrarySearcher: MediaLibrarySearching {

  // MARK: - Requesting images

  var stubbedFindImageImage: UIImage?
  var findImageAsset: PHAsset?

  func fb_findImage(for asset: PHAsset) throws -> UIImage {
    findImageAsset = asset

    struct UnstubbedImageError: Error {}
    guard let image = stubbedFindImageImage else {
      throw UnstubbedImageError()
    }

    return image
  }

  // MARK: - Getting video URLs

  var stubbedGetVideoURL: URL?
  var getVideoURLAsset: PHAsset?

  func fb_getVideoURL(for asset: PHAsset) throws -> URL {
    getVideoURLAsset = asset

    struct UnstubbedURLError: Error {}
    guard let url = stubbedGetVideoURL else {
      throw UnstubbedURLError()
    }

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

import Photos

final class TestPHAsset: PHAsset {
  var stubbedMediaType: PHAssetMediaType?

  override var mediaType: PHAssetMediaType {
    stubbedMediaType ?? super.mediaType
  }

  var stubbedLocalIdentifier: String?

  override var localIdentifier: String {
    stubbedLocalIdentifier ?? super.localIdentifier
  }
}


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

final class PHImageManagerSearchingTests: XCTestCase {

  // swiftlint:disable implicitly_unwrapped_optional
  var imageManager: TestPHImageManager!
  var asset: PHAsset!
  // swiftlint:enable implicitly_unwrapped_optional

  override func setUp() {
    super.setUp()

    imageManager = TestPHImageManager()
    asset = PHAsset()
  }

  override func tearDown() {
    imageManager = nil
    asset = nil

    super.tearDown()
  }

  // MARK: - Requesting images

  func testFindImageRequest() throws {
    _ = try? imageManager.fb_findImage(for: asset)

    XCTAssertIdentical(imageManager.requestImageAsset, asset, .forwardsCallToRequestImage)
    XCTAssertEqual(imageManager.requestImageTargetSize, PHImageManagerMaximumSize, .targetSizeIsMaximum)
    XCTAssertEqual(imageManager.requestImageContentMode, .default, .contentModeIsDefault)

    let options = try XCTUnwrap(imageManager.requestImageOptions, .forwardsCallToRequestImage)
    XCTAssertEqual(options.resizeMode, .exact, .resizeModeIsExact)
    XCTAssertEqual(options.deliveryMode, .highQualityFormat, .deliveryModeIsHighQualityFormat)
    XCTAssertTrue(options.isSynchronous, .imageRequestIsSynchronous)
  }

  func testFindImageFailure() {
    XCTAssertThrowsError(
      try imageManager.fb_findImage(for: asset),
      .throwsErrorWhenImageNotAvailable
    ) { anyError in
      guard let error = anyError as? PHImageManager.MediaLibrarySearchError else {
        return XCTFail(.throwsErrorWhenImageNotAvailable)
      }

      XCTAssertIdentical(error.asset, asset, .throwsErrorWhenImageNotAvailable)
    }
  }

  func testFindImageSuccess() throws {
    let expectedImage = UIImage()
    imageManager.stubbedRequestImageImage = expectedImage

    var image: UIImage?
    XCTAssertNoThrow(
      image = try imageManager.fb_findImage(for: asset),
      .returnsImageWhenAvailable
    )

    XCTAssertIdentical(image, expectedImage, .returnsImageWhenAvailable)
  }

  // MARK: - Getting video URLs

  func testGetVideoURLRequest() throws {
    _ = try? imageManager.fb_getVideoURL(for: asset)

    XCTAssertIdentical(imageManager.requestAVAssetAsset, asset, .forwardsCallToRequestAVAsset)

    let options = try XCTUnwrap(imageManager.requestAVAssetOptions, .forwardsCallToRequestAVAsset)
    XCTAssertEqual(options.version, .current, .versionIsCurrent)
    XCTAssertEqual(options.deliveryMode, .automatic, .deliveryModeIsAutomatic)
    XCTAssertTrue(options.isNetworkAccessAllowed, .networkAccessAllowed)
  }

  func testGetVideoURLAssetFailure() {
    asset = .withLocalIdentifier

    XCTAssertThrowsError(
      try imageManager.fb_getVideoURL(for: asset),
      .throwsErrorWhenVideoURLAssetNotAvailable
    ) { anyError in
      guard let error = anyError as? PHImageManager.MediaLibrarySearchError else {
        return XCTFail(.throwsErrorWhenVideoURLAssetNotAvailable)
      }

      XCTAssertIdentical(error.asset, asset, .throwsErrorWhenVideoURLAssetNotAvailable)
    }
  }

  func testGetVideoURLFileFailure() {
    asset = .withLocalIdentifier
    imageManager.stubbedGetVideoURLAsset = AVURLAsset.remote

    XCTAssertThrowsError(
      try imageManager.fb_getVideoURL(for: asset),
      .throwsErrorWhenVideoURLAssetNotAvailable
    ) { anyError in
      guard let error = anyError as? PHImageManager.MediaLibrarySearchError else {
        return XCTFail(.throwsErrorWhenVideoURLAssetNotAvailable)
      }

      XCTAssertIdentical(error.asset, asset, .throwsErrorWhenVideoURLAssetNotAvailable)
    }
  }

  func testGetVideoURLIdentifierFailure() {
    asset = .withoutLocalIdentifier
    imageManager.stubbedGetVideoURLAsset = AVURLAsset.local

    XCTAssertThrowsError(
      try imageManager.fb_getVideoURL(for: asset),
      .throwsErrorWhenVideoURLIdentifierNotAvailable
    ) { anyError in
      guard let error = anyError as? PHImageManager.MediaLibrarySearchError else {
        return XCTFail(.throwsErrorWhenVideoURLIdentifierNotAvailable)
      }

      XCTAssertIdentical(error.asset, asset, .throwsErrorWhenVideoURLIdentifierNotAvailable)
    }
  }

  func testGetVideoURLSuccess() throws {
    asset = .withLocalIdentifier
    imageManager.stubbedGetVideoURLAsset = AVURLAsset.local

    var url: URL?
    XCTAssertNoThrow(
      url = try imageManager.fb_getVideoURL(for: asset),
      .returnsVideoURLWhenAvailable
    )

    let expectedURL = """
      assets-library://asset/asset.\(String.urlAssetPathExtension)?id=\(String.uuid)&ext=\(String.urlAssetPathExtension)
      """
    XCTAssertEqual(url?.absoluteString, expectedURL, .returnsVideoURLWhenAvailable)
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

fileprivate extension String {
  static let forwardsCallToRequestImage = """
    A PHImageManager should forward image-finding requests to its image request method
    """
  static let targetSizeIsMaximum = "The maximum image size should be requested"
  static let contentModeIsDefault = "The default content mode should be requested"
  static let resizeModeIsExact = "The exact resize mode should be requested"
  static let deliveryModeIsHighQualityFormat = "The high quality format delivery mode should be requested"
  static let imageRequestIsSynchronous = "Image request calls should be synchronous"

  static let throwsErrorWhenImageNotAvailable = "An error should be thrown when an image is not available for an asset"
  static let returnsImageWhenAvailable = "An asset's image should be returned when available"

  static let forwardsCallToRequestAVAsset = """
    A PHImageManager should forward video URL requests to its video asset request method
    """
  static let versionIsCurrent = "The version should be current"
  static let deliveryModeIsAutomatic = "The delivery mode should be automatic"
  static let networkAccessAllowed = "Network access should be allowed"

  static let throwsErrorWhenVideoURLAssetNotAvailable = """
    An error should be thrown when a URL asset is not available for a video asset
    """
  static let throwsErrorWhenVideoURLFileURLNotAvailable = """
    An error should be thrown when a file URL is not available for a video asset
    """
  static let throwsErrorWhenVideoURLIdentifierNotAvailable = """
    An error should be thrown when a local identifier is not available for a video asset
    """
  static let returnsVideoURLWhenAvailable = "A URL should be returned when available for a video asset"
}

// MARK: - Test Values

fileprivate extension String {
  static let uuid = UUID().uuidString
  static let localIdentifier = "\(uuid)/more-stuff"
  static let urlAssetPathExtension = "ext"
  static let urlAssetURL = "file:///somewhere/over.\(urlAssetPathExtension)"
}

fileprivate extension URL {
  // swiftlint:disable force_unwrapping
  static let video = URL(string: "https://facebook.com")!
  static let remote = URL(string: "https://facebook.com")!
  static let local = URL(string: "file:///local/video.\(String.urlAssetPathExtension)")!
  // swiftlint:enable force_unwrapping
}

fileprivate extension AVURLAsset {
  static let remote = AVURLAsset(url: .remote)
  static let local = AVURLAsset(url: .local)
}

fileprivate extension PHAsset {
  static let withLocalIdentifier: PHAsset = {
    let asset = TestPHAsset()
    asset.stubbedLocalIdentifier = .localIdentifier
    return asset
  }()

  static let withoutLocalIdentifier: PHAsset = {
    let asset = TestPHAsset()
    asset.stubbedLocalIdentifier = ""
    return asset
  }()
}


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

final class TestAVURLAsset: AVURLAsset {
  var stubbedURL: URL?

  override var url: URL {
    stubbedURL ?? super.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.
 */

import Photos
import UIKit

// This test double simply subclasses `PHImageManager` in order to capture calls to itself
final class TestPHImageManager: PHImageManager {

  // MARK: - Requesting images

  var requestImageAsset: PHAsset?
  var requestImageTargetSize: CGSize?
  var requestImageContentMode: PHImageContentMode?
  var requestImageOptions: PHImageRequestOptions?
  var stubbedRequestImageImage: UIImage?
  var stubbedRequestImageInfo: [AnyHashable: Any]?

  override func requestImage(
    for asset: PHAsset,
    targetSize: CGSize,
    contentMode: PHImageContentMode,
    options: PHImageRequestOptions?,
    resultHandler: @escaping (UIImage?, [AnyHashable: Any]?) -> Void
  ) -> PHImageRequestID {
    requestImageAsset = asset
    requestImageTargetSize = targetSize
    requestImageContentMode = contentMode
    requestImageOptions = options

    resultHandler(stubbedRequestImageImage, stubbedRequestImageInfo)

    return PHImageRequestID(14)
  }

  // MARK: - Getting video URLs

  typealias RequestAVAssetResultHandler = (AVAsset?, AVAudioMix?, [AnyHashable: Any]?) -> Void

  var requestAVAssetAsset: PHAsset?
  var requestAVAssetOptions: PHVideoRequestOptions?
  var stubbedGetVideoURLAsset: AVAsset?
  var stubbedGetVideoURLAudioMix: AVAudioMix?
  var stubbedGetVideoURLInfo: [AnyHashable: Any]?

  override func requestAVAsset(
    forVideo asset: PHAsset,
    options: PHVideoRequestOptions?,
    resultHandler: @escaping RequestAVAssetResultHandler
  ) -> PHImageRequestID {
    requestAVAssetAsset = asset
    requestAVAssetOptions = options

    resultHandler(
      stubbedGetVideoURLAsset,
      stubbedGetVideoURLAudioMix,
      stubbedGetVideoURLInfo
    )

    return PHImageRequestID(14)
  }
}


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

final class DependentAsTypeTests: XCTestCase {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// MARK: - Test Types

private struct TestDependencies: Equatable {
  let value: Int
}

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

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

  static var wasSetDependenciesCalled = false
  static var wasResetDependenciesCalled = false

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

  static func resetDependencies() {
    wasResetDependenciesCalled = true
  }
}

// swiftformat:disable extensionaccesscontrol

// MARK: - Assumptions

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

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

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

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


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

@testable import FBSDKShareKit

import XCTest

final class ShareUtilityTests: XCTestCase {
  func testShareLinkContentValidationWithNilValues() throws {
    let content = ShareLinkContent()
    XCTAssertNotNil(content.shareUUID)
    XCTAssertNoThrow(try content.validate(options: []))
  }

  func testShareLinkContentValidationWithValidValues() throws {
    let content = ShareLinkContent()
    content.contentURL = ShareModelTestUtility.contentURL
    content.peopleIDs = []
    content.placeID = ShareModelTestUtility.placeID
    content.ref = ShareModelTestUtility.ref
    XCTAssertNotNil(content.shareUUID)
    XCTAssertNoThrow(try content.validate(options: []))
  }

  func testShareLinkContentParameters() {
    let content = ShareLinkContent()
    content.contentURL = ShareModelTestUtility.contentURL
    XCTAssertNotNil(content.shareUUID)
    let parameters = _ShareUtility.bridgeParameters(for: content, options: [], shouldFailOnDataError: true)
    XCTAssertEqual(
      content.contentURL,
      parameters["messenger_link"] as? URL,
      "Incorrect messenger_link param."
    )
  }
}


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

import FBSDKCoreKit

final class TestShareEventLogger: ShareEventLogging {
  var logInternalEventName: AppEvents.Name?
  var logInternalEventParameters: [AppEvents.ParameterName: Any]?
  var logInternalEventIsImplicitlyLogged: Bool? // swiftlint:disable:this discouraged_optional_boolean
  var logInternalEventAccessToken: AccessToken?

  func logInternalEvent(
    _ eventName: AppEvents.Name,
    parameters: [AppEvents.ParameterName: Any]?,
    isImplicitlyLogged: Bool,
    accessToken: AccessToken?
  ) {
    logInternalEventName = eventName
    logInternalEventParameters = parameters
    logInternalEventIsImplicitlyLogged = isImplicitlyLogged
    logInternalEventAccessToken = accessToken
  }
}