DailyView/Carthage/Checkouts/facebook-ios-sdk/FBSDKGamingServicesKit/FBSDKGamingServicesKitTests/GameRequestFrictionlessRecipientCacheTests.swift
2025-12-30 16:40:31 +08:00

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
}
}