473 lines
14 KiB
Swift
473 lines
14 KiB
Swift
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
@testable import FBSDKGamingServicesKit
|
|
|
|
import FBSDKCoreKit
|
|
import FBSDKShareKit
|
|
import TestTools
|
|
import XCTest
|
|
|
|
final class GameRequestDialogTests: XCTestCase {
|
|
// swiftlint:disable implicitly_unwrapped_optional
|
|
var dialog: GameRequestDialog!
|
|
var content: GameRequestContent!
|
|
var delegate: TestGameRequestDialogDelegate!
|
|
var bridgeAPIRequestOpener: TestBridgeAPIRequestOpener!
|
|
var errorFactory: TestErrorFactory!
|
|
var gameRequestURLProvider: TestGameRequestURLProvider.Type!
|
|
var internalUtility: TestInternalUtility!
|
|
var logger: FBSDKGamingServicesKitTests.TestLogger.Type!
|
|
var settings: TestSettings!
|
|
var shareValidator: TestShareUtility.Type!
|
|
var utility: TestGamingUtility.Type!
|
|
// swiftlint:enable implicitly_unwrapped_optional
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
|
|
content = GameRequestContent()
|
|
delegate = TestGameRequestDialogDelegate()
|
|
dialog = GameRequestDialog(content: content, delegate: delegate)
|
|
|
|
bridgeAPIRequestOpener = TestBridgeAPIRequestOpener()
|
|
errorFactory = TestErrorFactory()
|
|
gameRequestURLProvider = TestGameRequestURLProvider.self
|
|
internalUtility = TestInternalUtility()
|
|
logger = FBSDKGamingServicesKitTests.TestLogger.self
|
|
settings = TestSettings()
|
|
shareValidator = TestShareUtility.self
|
|
utility = TestGamingUtility.self
|
|
|
|
dialog.setDependencies(
|
|
.init(
|
|
bridgeAPIRequestOpener: bridgeAPIRequestOpener,
|
|
errorFactory: errorFactory,
|
|
gameRequestURLProvider: gameRequestURLProvider,
|
|
internalUtility: internalUtility,
|
|
logger: logger,
|
|
settings: settings,
|
|
shareValidator: shareValidator,
|
|
utility: utility
|
|
)
|
|
)
|
|
}
|
|
|
|
override func tearDown() {
|
|
content = nil
|
|
delegate = nil
|
|
|
|
errorFactory = nil
|
|
gameRequestURLProvider = nil
|
|
internalUtility = nil
|
|
logger = nil
|
|
settings = nil
|
|
shareValidator = nil
|
|
bridgeAPIRequestOpener = nil
|
|
utility = nil
|
|
|
|
TestGameRequestURLProvider.reset()
|
|
TestLogger.reset()
|
|
TestShareUtility.reset()
|
|
TestGamingUtility.reset()
|
|
|
|
dialog = nil
|
|
|
|
super.tearDown()
|
|
}
|
|
|
|
func testDefaultDependencies() throws {
|
|
dialog.resetDependencies()
|
|
let dependencies = try dialog.getDependencies()
|
|
|
|
XCTAssertIdentical(
|
|
dependencies.bridgeAPIRequestOpener,
|
|
_BridgeAPI.shared,
|
|
.Dependencies.defaultDependency("the shared BridgeAPI", for: "bridge API request opener")
|
|
)
|
|
XCTAssertTrue(
|
|
dependencies.errorFactory is _ErrorFactory,
|
|
.Dependencies.defaultDependency("a concrete error factory", for: "error factory")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.gameRequestURLProvider as AnyObject,
|
|
GameRequestURLProvider.self,
|
|
.Dependencies.defaultDependency("GameRequestURLProvider", for: "game request URL provider")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.internalUtility,
|
|
InternalUtility.shared,
|
|
.Dependencies.defaultDependency("the shared InternalUtility", for: "internal utility")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.logger as AnyObject,
|
|
_Logger.self,
|
|
.Dependencies.defaultDependency("Logger", for: "logger")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.settings,
|
|
Settings.shared,
|
|
.Dependencies.defaultDependency("the shared Settings", for: "settings")
|
|
)
|
|
XCTAssertTrue(
|
|
dependencies.shareValidator is _ShareUtility.Type,
|
|
.Dependencies.defaultDependency("_ShareUtility", for: "share validator")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.utility as AnyObject,
|
|
Utility.self,
|
|
.Dependencies.defaultDependency("Utility", for: "utility")
|
|
)
|
|
}
|
|
|
|
func testCustomDependencies() throws {
|
|
let dependencies = try dialog.getDependencies()
|
|
|
|
XCTAssertIdentical(
|
|
dependencies.bridgeAPIRequestOpener,
|
|
bridgeAPIRequestOpener,
|
|
.Dependencies.customDependency(for: "bridge API request opener")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.errorFactory,
|
|
errorFactory,
|
|
.Dependencies.customDependency(for: "error factory")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.gameRequestURLProvider as AnyObject,
|
|
gameRequestURLProvider,
|
|
.Dependencies.customDependency(for: "game request URL provider")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.internalUtility,
|
|
internalUtility,
|
|
.Dependencies.customDependency(for: "internal utility")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.logger as AnyObject,
|
|
logger,
|
|
.Dependencies.customDependency(for: "logger")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.settings,
|
|
settings,
|
|
.Dependencies.customDependency(for: "settings")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.shareValidator as AnyObject,
|
|
shareValidator,
|
|
.Dependencies.customDependency(for: "share validator")
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.utility as AnyObject,
|
|
utility,
|
|
.Dependencies.customDependency(for: "utility")
|
|
)
|
|
}
|
|
|
|
// MARK: - Launching
|
|
|
|
private func setValidLaunchConditions() {
|
|
utility.stubbedGraphDomain = "gaming"
|
|
internalUtility.isFacebookAppInstalled = true
|
|
content.message = "foo"
|
|
gameRequestURLProvider.stubbedDeepLinkURL = SampleURLs.valid(path: "launching")
|
|
}
|
|
|
|
func testShowingWithLaunchConditions() {
|
|
setValidLaunchConditions()
|
|
|
|
_ = GameRequestDialog.show(dialog: dialog)
|
|
|
|
// how to show that launch was called?
|
|
XCTAssertEqual(
|
|
bridgeAPIRequestOpener.capturedURL,
|
|
gameRequestURLProvider.stubbedDeepLinkURL,
|
|
"Should utilize the bridge api request opener when the launch conditions support it"
|
|
)
|
|
}
|
|
|
|
func testShowingWithoutLaunchConditionForGraphDomain() {
|
|
setValidLaunchConditions()
|
|
utility.stubbedGraphDomain = "facebook"
|
|
|
|
_ = GameRequestDialog.show(dialog: dialog)
|
|
|
|
XCTAssertNil(
|
|
bridgeAPIRequestOpener.capturedURL,
|
|
"Should not utilize the bridge api request opener when the graph domain is not `gaming`"
|
|
)
|
|
}
|
|
|
|
func testShowingWithoutLaunchConditionForFacebookAppInstall() {
|
|
setValidLaunchConditions()
|
|
internalUtility.isFacebookAppInstalled = false
|
|
|
|
_ = GameRequestDialog.show(dialog: dialog)
|
|
|
|
XCTAssertNil(
|
|
bridgeAPIRequestOpener.capturedURL,
|
|
"Should not utilize the bridge api request opener when the facebook app is not installed"
|
|
)
|
|
}
|
|
|
|
func testShowingWithoutLaunchConditionForNonEmptyContentMessage() {
|
|
setValidLaunchConditions()
|
|
content.message = ""
|
|
|
|
_ = GameRequestDialog.show(dialog: dialog)
|
|
|
|
XCTAssertNil(
|
|
bridgeAPIRequestOpener.capturedURL,
|
|
"Should not utilize the bridge api request opener when the content message is empty"
|
|
)
|
|
}
|
|
|
|
func testShowingWithoutLaunchConditionForMissingURL() {
|
|
setValidLaunchConditions()
|
|
gameRequestURLProvider.stubbedDeepLinkURL = nil
|
|
|
|
_ = GameRequestDialog.show(dialog: dialog)
|
|
|
|
XCTAssertNil(
|
|
bridgeAPIRequestOpener.capturedURL,
|
|
"Should not utilize the bridge api request opener when there is no URL to open"
|
|
)
|
|
}
|
|
|
|
func testCompletingLaunchWithFailure() {
|
|
setValidLaunchConditions()
|
|
|
|
_ = GameRequestDialog.show(dialog: dialog)
|
|
|
|
bridgeAPIRequestOpener.capturedHandler?(false, nil)
|
|
|
|
XCTAssertNil(delegate.failureDialog, "Should not invoke the failure delegate method when success is false")
|
|
}
|
|
|
|
func testCompletingLaunchWithError() throws {
|
|
setValidLaunchConditions()
|
|
_ = GameRequestDialog.show(dialog: dialog)
|
|
|
|
let error = SampleError()
|
|
bridgeAPIRequestOpener.capturedHandler?(true, error)
|
|
|
|
let expectedError = TestSDKError(
|
|
type: .general,
|
|
code: CoreError.errorBridgeAPIInterruption.rawValue,
|
|
message: "Error occured while interacting with Gaming Services, Failed to open bridge.",
|
|
underlyingError: error
|
|
)
|
|
let capturedError = try XCTUnwrap(
|
|
delegate.failureError as? TestSDKError,
|
|
"Should use the error factory to create an error"
|
|
)
|
|
|
|
XCTAssertIdentical(delegate.failureDialog, dialog)
|
|
XCTAssertEqual(
|
|
capturedError,
|
|
expectedError,
|
|
"Should use the error factory to create an error with the expected inputs"
|
|
)
|
|
}
|
|
|
|
// MARK: - Showing
|
|
|
|
private func setValidShowConditions() {
|
|
internalUtility.stubbedTopMostViewController = UIViewController()
|
|
content.message = "foo"
|
|
}
|
|
|
|
func testShowingWithValidShowConditions() throws {
|
|
setValidShowConditions()
|
|
|
|
_ = GameRequestDialog.show(dialog: dialog)
|
|
|
|
let capturedRequest = try XCTUnwrap(
|
|
bridgeAPIRequestOpener.capturedRequest,
|
|
"Should invoke the bridge api request opener with a bridge api request"
|
|
)
|
|
|
|
XCTAssertEqual(
|
|
capturedRequest.scheme,
|
|
URLSchemeEnum.https.rawValue,
|
|
"Should use the expected scheme"
|
|
)
|
|
XCTAssertEqual(
|
|
capturedRequest.protocolType,
|
|
.web,
|
|
"Should use the expected protocol type"
|
|
)
|
|
XCTAssertEqual(
|
|
capturedRequest.methodName,
|
|
"apprequests",
|
|
"Should use the expected method name"
|
|
)
|
|
let downcastRequest = try XCTUnwrap(
|
|
capturedRequest as? _BridgeAPIRequest,
|
|
"The request should be the expected type"
|
|
)
|
|
let parameters = try XCTUnwrap(
|
|
downcastRequest.parameters as? [String: String],
|
|
"The request should contain string to string parameters"
|
|
)
|
|
let expectedParameters = ["title": "", "object_id": "", "suggestions": "", "to": "", "message": "foo"]
|
|
|
|
XCTAssertTrue(
|
|
NSDictionary(dictionary: parameters).isEqual(to: expectedParameters),
|
|
"""
|
|
Actual parameters should match the expected parameters. \n
|
|
Actual parameters: \(parameters)
|
|
Expected parameters: \(expectedParameters)
|
|
"""
|
|
)
|
|
}
|
|
|
|
func testShowingWithMissingTopMostViewController() {
|
|
setValidShowConditions()
|
|
internalUtility.stubbedTopMostViewController = nil
|
|
|
|
_ = GameRequestDialog.show(dialog: dialog)
|
|
|
|
XCTAssertNil(
|
|
bridgeAPIRequestOpener.capturedRequest,
|
|
"Should not invoke the bridge api request opener without a topmost view controller"
|
|
)
|
|
}
|
|
|
|
func testShowingWithEmptyContentMessage() {
|
|
setValidShowConditions()
|
|
content.message = ""
|
|
|
|
_ = GameRequestDialog.show(dialog: dialog)
|
|
|
|
XCTAssertNil(
|
|
bridgeAPIRequestOpener.capturedRequest,
|
|
"Should not invoke the bridge api request opener with an empty content message"
|
|
)
|
|
}
|
|
|
|
// MARK: - URL Opening
|
|
|
|
private func launchDialog() {
|
|
setValidLaunchConditions()
|
|
_ = GameRequestDialog.show(dialog: dialog)
|
|
}
|
|
|
|
private func createGamingURL(appID: String, queryItems: [URLQueryItem] = []) -> URL? {
|
|
var components = URLComponents()
|
|
components.scheme = "fb\(appID)"
|
|
components.host = "game_requests"
|
|
components.queryItems = queryItems
|
|
|
|
return components.url
|
|
}
|
|
|
|
func testOpeningGamingURLWithParametersWhenAwaitingResult() throws {
|
|
settings.appID = "123"
|
|
let url = try XCTUnwrap(
|
|
createGamingURL(
|
|
appID: "123",
|
|
queryItems: [
|
|
.init(name: "request_id", value: "foo"),
|
|
.init(name: "recipients", value: "1,2,3"),
|
|
]
|
|
)
|
|
)
|
|
|
|
// Need to launch the dialog in order to set `isAwaitingResult`
|
|
launchDialog()
|
|
|
|
_ = dialog.application(nil, open: url, sourceApplication: nil, annotation: nil)
|
|
|
|
XCTAssertNotNil(delegate.completionDialog, "Should open a url when the conditions are met")
|
|
|
|
let results = try XCTUnwrap(delegate.completionResults)
|
|
XCTAssertEqual(
|
|
results["recipients"] as? [String],
|
|
["1", "2", "3"],
|
|
"Should parse the recipients from a comma separated string"
|
|
)
|
|
XCTAssertEqual(
|
|
results["request_id"] as? String,
|
|
"foo",
|
|
"Should parse the request ID from the url query parameters"
|
|
)
|
|
}
|
|
|
|
func testOpeningGamingURLWithoutParametersWhenAwaitingResult() throws {
|
|
settings.appID = "123"
|
|
let url = try XCTUnwrap(createGamingURL(appID: "123"))
|
|
|
|
// Need to launch the dialog in order to set `isAwaitingResult`
|
|
launchDialog()
|
|
|
|
_ = dialog.application(nil, open: url, sourceApplication: nil, annotation: nil)
|
|
|
|
XCTAssertNil(
|
|
delegate.completionDialog,
|
|
"Should not invoke the completion delegate when there are no query items"
|
|
)
|
|
XCTAssertEqual(
|
|
delegate.cancellationDialog,
|
|
dialog,
|
|
"Should invoke the cancellation delegate when opening a url and not awaiting a result"
|
|
)
|
|
}
|
|
|
|
func testOpeningGamingURLWhenNotAwaitingResult() throws {
|
|
settings.appID = "123"
|
|
let url = try XCTUnwrap(createGamingURL(appID: "123"))
|
|
|
|
_ = dialog.application(nil, open: url, sourceApplication: nil, annotation: nil)
|
|
|
|
XCTAssertNil(
|
|
delegate.completionDialog,
|
|
"Should not invoke the completion delegate when not awaiting a result"
|
|
)
|
|
XCTAssertNil(
|
|
delegate.cancellationDialog,
|
|
"Should not invoke the cancellation delegate when not awaiting a result"
|
|
)
|
|
}
|
|
|
|
func testOpeningNonGamingURL() {
|
|
_ = dialog.application(
|
|
nil,
|
|
open: SampleURLs.valid,
|
|
sourceApplication: nil,
|
|
annotation: nil
|
|
)
|
|
|
|
XCTAssertNil(
|
|
delegate.completionDialog,
|
|
"Should not invoke the completion delegate"
|
|
)
|
|
XCTAssertNil(
|
|
delegate.cancellationDialog,
|
|
"Should not invoke the cancellation delegate"
|
|
)
|
|
}
|
|
}
|
|
|
|
// swiftformat:disable extensionaccesscontrol
|
|
|
|
// MARK: - Assumptions
|
|
|
|
fileprivate extension String {
|
|
enum Dependencies {
|
|
static func defaultDependency(_ dependency: String, for type: String) -> String {
|
|
"A GameRequestDialog instance uses \(dependency) as its \(type) dependency by default"
|
|
}
|
|
|
|
static func customDependency(for type: String) -> String {
|
|
"A GameRequestDialog instance uses a custom \(type) dependency when provided"
|
|
}
|
|
}
|
|
}
|