400 lines
11 KiB
Swift
400 lines
11 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 TestTools
|
|
import XCTest
|
|
|
|
// swiftlint:disable:next type_name
|
|
final class GameRequestFrictionlessRecipientCacheTests: XCTestCase {
|
|
|
|
// swiftlint:disable implicitly_unwrapped_optional
|
|
var cache: GameRequestFrictionlessRecipientCache!
|
|
var graphRequestFactory: TestGraphRequestFactory!
|
|
var notificationCenter: TestNotificationCenter!
|
|
// swiftlint:enable implicitly_unwrapped_optional
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
|
|
graphRequestFactory = TestGraphRequestFactory()
|
|
notificationCenter = TestNotificationCenter()
|
|
cache = GameRequestFrictionlessRecipientCache()
|
|
cache.setDependencies(
|
|
.init(
|
|
graphRequestFactory: graphRequestFactory,
|
|
notificationCenter: notificationCenter,
|
|
accessTokenWallet: TestAccessTokenWallet.self
|
|
)
|
|
)
|
|
}
|
|
|
|
override func tearDown() {
|
|
cache = nil
|
|
graphRequestFactory = nil
|
|
notificationCenter = nil
|
|
TestAccessTokenWallet.reset()
|
|
|
|
super.tearDown()
|
|
}
|
|
|
|
// MARK: Dependencies
|
|
|
|
func testCreatingWithDependencies() throws {
|
|
let dependencies = try cache.getDependencies()
|
|
|
|
XCTAssertIdentical(
|
|
dependencies.graphRequestFactory,
|
|
graphRequestFactory,
|
|
"A recipient cache uses a provided graph request factory"
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.notificationCenter,
|
|
notificationCenter,
|
|
"A recipient cache uses the provided notification center"
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.accessTokenWallet,
|
|
TestAccessTokenWallet.self,
|
|
"A recipient cache uses the provided access token wallet"
|
|
)
|
|
}
|
|
|
|
func testDefaultDependencies() throws {
|
|
cache.resetDependencies()
|
|
let dependencies = try cache.getDependencies()
|
|
|
|
XCTAssertTrue(
|
|
dependencies.graphRequestFactory is GraphRequestFactory,
|
|
"A cache uses graph request factory for its graph request factory protocol by default"
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.notificationCenter,
|
|
NotificationCenter.default,
|
|
"A cache uses the default notification center for its notification delivering dependency by default"
|
|
)
|
|
XCTAssertIdentical(
|
|
dependencies.accessTokenWallet,
|
|
AccessToken.self,
|
|
"A cache uses the AccessToken for its access token wallet dependency by default"
|
|
)
|
|
}
|
|
|
|
// MARK: - Updating Cache
|
|
|
|
func testUpdatingEmptyCacheWithoutAccessToken() {
|
|
initiateFrictionlessRequest(usesValidAccessToken: false)
|
|
|
|
XCTAssertNil(
|
|
graphRequestFactory.capturedGraphPath,
|
|
"Should not create a graph request without an access token"
|
|
)
|
|
XCTAssertTrue(cache.recipientIDs.isEmpty, "Should not update the recipient identifiers")
|
|
}
|
|
|
|
func testUpdatingCacheWithoutAccessToken() throws {
|
|
_ = try seedRecipientIdentifiers()
|
|
|
|
TestAccessTokenWallet.current = nil
|
|
|
|
initiateFrictionlessRequest(usesValidAccessToken: false)
|
|
|
|
XCTAssertTrue(
|
|
cache.recipientIDs.isEmpty,
|
|
"Should clear any cached identifiers when attempting to update without an access token"
|
|
)
|
|
}
|
|
|
|
func testUpdatingCacheWithAccessToken() throws {
|
|
initiateFrictionlessRequest()
|
|
let request = try XCTUnwrap(graphRequestFactory.capturedRequests.first)
|
|
|
|
XCTAssertEqual(
|
|
request.graphPath,
|
|
"me/apprequestformerrecipients",
|
|
"Should create a graph request with the expected path"
|
|
)
|
|
XCTAssertEqual(
|
|
request.parameters as? [String: String],
|
|
["fields": ""],
|
|
"Should create a graph request with the expected parameters"
|
|
)
|
|
XCTAssertEqual(
|
|
request.flags,
|
|
[.doNotInvalidateTokenOnError, .disableErrorRecovery],
|
|
"Should create a graph request with the expected flags"
|
|
)
|
|
|
|
XCTAssertEqual(
|
|
request.startCallCount,
|
|
1,
|
|
"Should start the graph request"
|
|
)
|
|
}
|
|
|
|
func testCompletingUpdateWithUniqueIdentifiers() throws {
|
|
_ = try seedRecipientIdentifiers()
|
|
|
|
XCTAssertEqual(
|
|
cache.recipientIDs,
|
|
Set(SampleData.validRecipientIDs),
|
|
"Should set the recipient identifiers from the response"
|
|
)
|
|
}
|
|
|
|
func testCompletingUpdateWithDuplicateIdentifiers() throws {
|
|
initiateFrictionlessRequest()
|
|
let request = try XCTUnwrap(graphRequestFactory.capturedRequests.first)
|
|
let result = createValidResult(withRecipientIDs: SampleData.duplicateRecipientIDs)
|
|
|
|
request.capturedCompletionHandler?(nil, result, nil)
|
|
|
|
XCTAssertEqual(
|
|
cache.recipientIDs,
|
|
Set(SampleData.validRecipientIDs),
|
|
"Should omit duplicate entries when setting the recipient identifiers from the response"
|
|
)
|
|
}
|
|
|
|
func testCompletingUpdateWithEmptyListOfIdentifiers() throws {
|
|
let request = try seedRecipientIdentifiers()
|
|
|
|
// Call the completion again with an empty list
|
|
request.capturedCompletionHandler?(nil, createValidResult(withRecipientIDs: []), nil)
|
|
|
|
XCTAssertTrue(
|
|
cache.recipientIDs.isEmpty,
|
|
"Should overwrite the existing identifiers with whatever identifiers are in the response"
|
|
)
|
|
|
|
// Call the completion again with a non-empty list
|
|
request.capturedCompletionHandler?(nil, createValidResult(), nil)
|
|
|
|
XCTAssertFalse(
|
|
cache.recipientIDs.isEmpty,
|
|
"Should overwrite the existing identifiers with whatever identifiers are in the response"
|
|
)
|
|
}
|
|
|
|
func testCompletingUpdateWithError() throws {
|
|
let request = try seedRecipientIdentifiers()
|
|
|
|
// Call the completion again with an error
|
|
request.capturedCompletionHandler?(nil, nil, SampleError())
|
|
|
|
XCTAssertFalse(
|
|
cache.recipientIDs.isEmpty,
|
|
"Should not clear the cache when receiving a server error"
|
|
)
|
|
}
|
|
|
|
func testCompletingUpdateWithInvalidData() throws {
|
|
let request = try seedRecipientIdentifiers()
|
|
|
|
// Call the completion again with invalid data
|
|
let result = ["data": "bad"]
|
|
request.capturedCompletionHandler?(nil, result, nil)
|
|
|
|
XCTAssertTrue(
|
|
cache.recipientIDs.isEmpty,
|
|
"Should clear the cached identifiers when receiving a bad result"
|
|
)
|
|
}
|
|
|
|
func testUpdatingWithoutFrictionlessUpdates() throws {
|
|
_ = try seedRecipientIdentifiers()
|
|
|
|
cache.update(results: [:])
|
|
|
|
XCTAssertEqual(
|
|
cache.recipientIDs,
|
|
Set(SampleData.validRecipientIDs),
|
|
"Updating with empty results should not affect the cached recipient identifiers"
|
|
)
|
|
}
|
|
|
|
// MARK: - Frictionless Recipient Checking
|
|
|
|
func testRecipientsAreFrictionlessWithoutRecipients() throws {
|
|
_ = try seedRecipientIdentifiers()
|
|
|
|
XCTAssertFalse(cache.recipientsAreFrictionless(nil), "Missing recipients are not frictionless")
|
|
}
|
|
|
|
func testRecipientsAreFrictionlessWithStringArrayOfRecipients() throws {
|
|
_ = try seedRecipientIdentifiers()
|
|
|
|
XCTAssertFalse(
|
|
cache.recipientsAreFrictionless(["cde", "456"]),
|
|
"Recipients that are not cached are not frictionless"
|
|
)
|
|
XCTAssertTrue(
|
|
cache.recipientsAreFrictionless(SampleData.validRecipientIDs),
|
|
"Cached recipients are frictionless"
|
|
)
|
|
}
|
|
|
|
func testRecipientsAreFrictionlessWithCommaSeparatedStringOfRecipients() throws {
|
|
_ = try seedRecipientIdentifiers()
|
|
|
|
XCTAssertFalse(
|
|
cache.recipientsAreFrictionless("cde,456"),
|
|
"Recipients that are not cached are not frictionless"
|
|
)
|
|
XCTAssertTrue(
|
|
cache.recipientsAreFrictionless(
|
|
SampleData.validRecipientIDs.joined(separator: ",")
|
|
),
|
|
"Cached recipients are frictionless"
|
|
)
|
|
}
|
|
|
|
func testRecipientsAreFrictionlessWithInvalidTypeOfRecipients() throws {
|
|
_ = try seedRecipientIdentifiers()
|
|
|
|
[
|
|
true,
|
|
false,
|
|
1,
|
|
Set(["foo"]),
|
|
["name": "123"],
|
|
]
|
|
.forEach { value in
|
|
XCTAssertFalse(cache.recipientsAreFrictionless(value))
|
|
}
|
|
}
|
|
|
|
// MARK: - Notification Handling
|
|
|
|
func testSubscribesToAccessTokenChangeNotifications() throws {
|
|
let invocation = try XCTUnwrap(
|
|
notificationCenter.capturedAddObserverInvocations.first,
|
|
"A notification should be observed upon initialization"
|
|
)
|
|
|
|
XCTAssertEqual(
|
|
invocation,
|
|
.init(observer: nil, name: .AccessTokenDidChange, selector: nil, object: nil),
|
|
"Should observe the correct notification upon initialization"
|
|
)
|
|
}
|
|
|
|
func testReceivingNotificationThatAccessTokenChanged() throws {
|
|
_ = try seedRecipientIdentifiers()
|
|
|
|
let notification = Notification(
|
|
name: .AccessTokenDidChange,
|
|
object: nil,
|
|
userInfo: [AccessTokenDidChangeUserIDKey: true]
|
|
)
|
|
notificationCenter.capturedCompletions.first?(notification)
|
|
|
|
XCTAssertTrue(cache.recipientIDs.isEmpty, "A change in access token should clear the cache.")
|
|
|
|
XCTAssertEqual(
|
|
graphRequestFactory.capturedRequests.count,
|
|
2,
|
|
"A change in access token should prompt a new request"
|
|
)
|
|
}
|
|
|
|
func testReceivingNotificationThatAccessTokenDidNotChange() throws {
|
|
_ = try seedRecipientIdentifiers()
|
|
|
|
let notification = Notification(
|
|
name: .AccessTokenDidChange,
|
|
object: nil,
|
|
userInfo: [AccessTokenDidChangeUserIDKey: false]
|
|
)
|
|
notificationCenter.capturedCompletions.first?(notification)
|
|
|
|
XCTAssertFalse(
|
|
cache.recipientIDs.isEmpty,
|
|
"A change in access token that does not change the user ID should not clear the cache."
|
|
)
|
|
|
|
XCTAssertEqual(
|
|
graphRequestFactory.capturedRequests.count,
|
|
1,
|
|
"A change in access token that does not change the user ID should not prompt a new request"
|
|
)
|
|
}
|
|
|
|
func testReceivingNotificationThatAccessTokenExpired() throws {
|
|
_ = try seedRecipientIdentifiers()
|
|
|
|
let notification = Notification(name: .AccessTokenDidChange, object: nil, userInfo: [AccessTokenDidExpireKey: true])
|
|
notificationCenter.capturedCompletions.first?(notification)
|
|
|
|
XCTAssertFalse(
|
|
cache.recipientIDs.isEmpty,
|
|
"A change in access token that does not change the user ID should not clear the cache."
|
|
)
|
|
|
|
XCTAssertEqual(
|
|
graphRequestFactory.capturedRequests.count,
|
|
1,
|
|
"A change in access token that does not change the user ID should not prompt a new request"
|
|
)
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private enum SampleData {
|
|
static var validRecipientIDs: [String] {
|
|
["abc", "123"]
|
|
}
|
|
|
|
static var duplicateRecipientIDs: [String] {
|
|
validRecipientIDs + validRecipientIDs
|
|
}
|
|
}
|
|
|
|
private func createValidResult(
|
|
withRecipientIDs recipientIDs: [String]? = SampleData.validRecipientIDs
|
|
) -> [String: Any] {
|
|
[
|
|
"data": [
|
|
"recipient_id": recipientIDs,
|
|
],
|
|
]
|
|
}
|
|
|
|
private func initiateFrictionlessRequest(usesValidAccessToken: Bool = true) {
|
|
if usesValidAccessToken {
|
|
TestAccessTokenWallet.stubbedCurrentAccessToken = SampleAccessTokens.validToken
|
|
}
|
|
|
|
cache.update(results: ["updated_frictionless": true])
|
|
}
|
|
|
|
/// Seeds valid recipient identifiers on the cache and returns the test graph request
|
|
private func seedRecipientIdentifiers(
|
|
_ file: StaticString = #file,
|
|
_ line: UInt = #line
|
|
) throws -> TestGraphRequest {
|
|
initiateFrictionlessRequest()
|
|
let request = try XCTUnwrap(
|
|
graphRequestFactory.capturedRequests.first,
|
|
file: file,
|
|
line: line
|
|
)
|
|
request.capturedCompletionHandler?(nil, createValidResult(), nil)
|
|
XCTAssertFalse(
|
|
cache.recipientIDs.isEmpty,
|
|
"Should set valid identifiers",
|
|
file: file,
|
|
line: line
|
|
)
|
|
|
|
return request
|
|
}
|
|
}
|