FBSDKLoginKit/FBSDKLoginKitTests/LoginCompletionTests.swift (841 lines of code) (raw):

/* * 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 FBSDKLoginKit import FBSDKCoreKit import FBSDKCoreKit_Basics import TestTools import XCTest final class LoginCompletionTests: XCTestCase { enum Keys { static let accessToken = "access_token" static let dataAccessExpiration = "data_access_expiration_time" static let deniedScopes = "denied_scopes" static let expires = "expires" static let expiresAt = "expires_at" static let expiresIn = "expires_in" static let grantedScopes = "granted_scopes" static let graphDomain = "graph_domain" static let idToken = "id_token" static let nonce = "nonce" static let userID = "user_id" static let code = "code" static let error = "error" } enum Values { static let appID = "1234567" static let idToken = "abc123" static let nonce = "some_nonce" static let codeVerifier = "some_code_verifier" static let redirectURL = "https://www.example.com" static let accessToken = "some_token" } // swiftlint:disable implicitly_unwrapped_optional var authenticationTokenFactory: TestAuthenticationTokenFactory! var graphRequestFactory: TestGraphRequestFactory! var internalUtility: TestInternalUtility! // swiftlint:enable implicitly_unwrapped_optional override func setUp() { super.setUp() LoginURLCompleter.reset() authenticationTokenFactory = TestAuthenticationTokenFactory() graphRequestFactory = TestGraphRequestFactory() internalUtility = TestInternalUtility() internalUtility.stubbedAppURL = URL(string: Values.redirectURL) } override func tearDown() { LoginURLCompleter.reset() super.tearDown() } // MARK: Creation func testDefaultProfileProvider() { XCTAssertTrue( LoginURLCompleter.profileFactory is ProfileFactory, "Should have the expected concrete profile provider" ) } func testSettingProfileProvider() { let provider = TestProfileFactory(stubbedProfile: SampleUserProfiles.createValid()) LoginURLCompleter.profileFactory = provider XCTAssertTrue( LoginURLCompleter.profileFactory === provider, "Should be able to inject a profile provider" ) } func testInitWithAccessTokenWithIDToken() { let parameters = SampleRawLoginCompletionParameters.withAccessTokenWithIDToken let completer = createLoginCompleter(parameters: parameters, appID: Values.appID) verifyParameters(actual: completer.parameters, expected: parameters) } func testInitWithAccessToken() { let parameters = SampleRawLoginCompletionParameters.withAccessToken let completer = createLoginCompleter(parameters: parameters, appID: Values.appID) verifyParameters(actual: completer.parameters, expected: parameters) } func testInitWithNonce() { let parameters = SampleRawLoginCompletionParameters.withNonce let completer = createLoginCompleter(parameters: parameters, appID: Values.appID) verifyParameters(actual: completer.parameters, expected: parameters) } func testInitWithCode() { let parameters = SampleRawLoginCompletionParameters.withCode let completer = createLoginCompleter(parameters: parameters, appID: Values.appID) verifyParameters(actual: completer.parameters, expected: parameters) } func testInitWithIDToken() { let parameters = SampleRawLoginCompletionParameters.withIDToken let completer = createLoginCompleter(parameters: parameters, appID: Values.appID) verifyParameters(actual: completer.parameters, expected: parameters) } func testInitWithStringExpirations() { let parameters = SampleRawLoginCompletionParameters.withStringExpirations let completer = createLoginCompleter(parameters: parameters, appID: Values.appID) verifyParameters(actual: completer.parameters, expected: parameters) } func testInitWithoutAccessTokenWithoutIDTokenWithoutCode() { let parameters = SampleRawLoginCompletionParameters.withoutAccessTokenWithoutIDTokenWithoutCode let completer = createLoginCompleter(parameters: parameters, appID: Values.appID) verifyEmptyParameters(completer.parameters) } func testInitWithEmptyStrings() { let parameters = SampleRawLoginCompletionParameters.withEmptyStrings let completer = createLoginCompleter(parameters: parameters, appID: Values.appID) verifyEmptyParameters(completer.parameters) } func testInitWithEmptyParameters() { let completer = createLoginCompleter(parameters: [:], appID: Values.appID) verifyEmptyParameters(completer.parameters) } func testInitWithError() { let parameters = SampleRawLoginCompletionParameters.withError let completer = createLoginCompleter( parameters: parameters, appID: Values.appID ) XCTAssertNotNil(completer.parameters.error) } func testInitWithFuzzyParameters() { (0 ..< 100).forEach { _ in let parameters = SampleRawLoginCompletionParameters.defaultParameters if let fuzzyParameters = Fuzzer.randomize(json: parameters) as? [String: Any] { _ = createLoginCompleter(parameters: fuzzyParameters, appID: Values.appID) } } } // MARK: Completion func testCompleteWithNonceGraphRequestCreation() throws { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withNonce, appID: Values.appID ) let handler: LoginCompletionParametersBlock = { _ in } completer.completeLogin(handler: handler) XCTAssertNil(completer.parameters.error) XCTAssertNil(authenticationTokenFactory.capturedTokenString) let capturedRequest = try XCTUnwrap(graphRequestFactory.capturedRequests.first) XCTAssertEqual( capturedRequest.graphPath, "oauth/access_token", "Should create a graph request with the expected graph path" ) XCTAssertEqual( capturedRequest.parameters["grant_type"] as? String, "fb_exchange_nonce", "Should create a graph request with the expected grant type parameter" ) XCTAssertEqual( capturedRequest.parameters["fb_exchange_nonce"] as? String, completer.parameters.nonceString, "Should create a graph request with the expected nonce parameter" ) XCTAssertEqual( capturedRequest.parameters["client_id"] as? String, Values.appID, "Should create a graph request with the expected app id parameter" ) XCTAssertEqual( capturedRequest.parameters["fields"] as? String, "", "Should create a graph request with the expected fields parameter" ) XCTAssertEqual( capturedRequest.flags, [.doNotInvalidateTokenOnError, .disableErrorRecovery], "The graph request should not invalidate the token on error or disable error recovery" ) } func testNonceExchangeCompletionWithError() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withNonce, appID: Values.appID ) var completionWasInvoked = false var capturedParameters: LoginCompletionParameters? completer.completeLogin { parameters in capturedParameters = parameters completionWasInvoked = true } graphRequestFactory.capturedRequests.first?.capturedCompletionHandler?(nil, nil, SampleError()) XCTAssertEqual( completer.parameters, capturedParameters, "Should call the completion with the provided parameters" ) XCTAssertTrue( completer.parameters.error is SampleError, "Should pass through the error from the graph request" ) XCTAssertTrue(completionWasInvoked) } func testNonceExchangeCompletionWithAccessTokenString() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withNonce, appID: Values.appID ) let stubbedResult = [Keys.accessToken: name] var completionWasInvoked = false var capturedParameters: LoginCompletionParameters? completer.completeLogin { parameters in capturedParameters = parameters completionWasInvoked = true } graphRequestFactory.capturedRequests.first?.capturedCompletionHandler?(nil, stubbedResult, nil) XCTAssertEqual( completer.parameters, capturedParameters, "Should call the completion with the provided parameters" ) XCTAssertEqual( completer.parameters.accessTokenString, name, "Should set the access token string from the graph request's result" ) XCTAssertTrue(completionWasInvoked) } func testNonceExchangeCompletionWithAccessTokenStringAndAuthenticationTokenString() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withNonce, appID: Values.appID ) let nonce = Values.nonce let stubbedResult = [ Keys.accessToken: name, Keys.idToken: Values.idToken, ] completer.completeLogin( handler: { _ in }, nonce: nonce, codeVerifier: Values.codeVerifier ) graphRequestFactory.capturedRequests.first?.capturedCompletionHandler?(nil, stubbedResult, nil) XCTAssertEqual( completer.parameters.accessTokenString, name, "Should set the access token string from the graph request's result" ) XCTAssertEqual( completer.parameters.authenticationTokenString, Values.idToken, "Should set the authentication token string from the graph request's result" ) XCTAssertEqual( authenticationTokenFactory.capturedTokenString, Values.idToken, "Should call AuthenticationTokenFactory with the expected token string" ) XCTAssertEqual( authenticationTokenFactory.capturedNonce, nonce, "Should call AuthenticationTokenFactory with the expected nonce" ) } func testNonceExchangeWithRandomResults() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withNonce, appID: Values.appID ) let stubbedResult: [String: Any] = [ Keys.accessToken: name, "expires_in": "10000", "data_access_expiration_time": 1, ] var completionWasInvoked = false completer.completeLogin { _ in // Basically just making sure that nothing crashes here when we feed it garbage results completionWasInvoked = true } (0 ..< 100).forEach { _ in let mangledResult = stubbedResult let parameters = Fuzzer.randomize(json: mangledResult) graphRequestFactory.capturedRequests.first?.capturedCompletionHandler?(nil, parameters, nil) XCTAssertTrue(completionWasInvoked) completionWasInvoked = false } } func testCompleteWithCodeGraphRequestCreation() throws { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withCode, appID: Values.appID ) let handler: LoginCompletionParametersBlock = { _ in } completer.completeLogin( handler: handler, nonce: Values.nonce, codeVerifier: Values.codeVerifier ) XCTAssertNil(completer.parameters.error) XCTAssertNil(authenticationTokenFactory.capturedTokenString) let capturedRequest = try XCTUnwrap(graphRequestFactory.capturedRequests.first) XCTAssertEqual( capturedRequest.graphPath, "oauth/access_token", "Should create a graph request with the expected graph path" ) XCTAssertEqual( capturedRequest.parameters["client_id"] as? String, Values.appID, "Should create a graph request with the expected app ID" ) XCTAssertEqual( capturedRequest.parameters["redirect_uri"] as? String, Values.redirectURL, "Should create a graph request with the expected redirect URL" ) XCTAssertEqual( capturedRequest.parameters["code_verifier"] as? String, Values.codeVerifier, "Should create a graph request with the expected code verifier parameter" ) XCTAssertEqual( capturedRequest.parameters[Keys.code] as? String, SampleRawLoginCompletionParameters.withCode[Keys.code] as? String, "Should create a graph request with the expected code parameter" ) XCTAssertEqual( capturedRequest.flags, [.doNotInvalidateTokenOnError, .disableErrorRecovery], "The graph request should not invalidate the token on error or disable error recovery" ) } func testCodeExchangeCompletionWithGraphError() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withCode, appID: Values.appID ) var completionWasInvoked = false var capturedParameters: LoginCompletionParameters? let handler: LoginCompletionParametersBlock = { parameters in capturedParameters = parameters completionWasInvoked = true } completer.completeLogin( handler: handler, nonce: Values.nonce, codeVerifier: Values.codeVerifier ) graphRequestFactory.capturedRequests.first?.capturedCompletionHandler?(nil, nil, SampleError()) XCTAssertEqual( completer.parameters, capturedParameters, "Should call the completion with the provided parameters" ) XCTAssertTrue( completer.parameters.error is SampleError, "Should pass through the error from the graph request" ) XCTAssertTrue(completionWasInvoked) } func testCodeExchangeCompletionWithError() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withCode, appID: Values.appID ) let stubbedResult = [Keys.error: name] var completionWasInvoked = false var capturedParameters: LoginCompletionParameters? let handler: LoginCompletionParametersBlock = { parameters in capturedParameters = parameters completionWasInvoked = true } completer.completeLogin( handler: handler, nonce: Values.nonce, codeVerifier: Values.codeVerifier ) graphRequestFactory.capturedRequests.first?.capturedCompletionHandler?(nil, stubbedResult, nil) XCTAssertEqual( completer.parameters, capturedParameters, "Should call the completion with the provided parameters" ) XCTAssertNotNil( completer.parameters.error, "Should set error from the graph request's result" ) XCTAssertTrue(completionWasInvoked) } func testCodeExchangeCompletionWithAccessTokenString() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withCode, appID: Values.appID ) let stubbedResult = [Keys.accessToken: name] var completionWasInvoked = false var capturedParameters: LoginCompletionParameters? let handler: LoginCompletionParametersBlock = { parameters in capturedParameters = parameters completionWasInvoked = true } completer.completeLogin( handler: handler, nonce: Values.nonce, codeVerifier: Values.codeVerifier ) graphRequestFactory.capturedRequests.first?.capturedCompletionHandler?(nil, stubbedResult, nil) XCTAssertEqual( completer.parameters, capturedParameters, "Should call the completion with the provided parameters" ) XCTAssertEqual( completer.parameters.accessTokenString, name, "Should set the access token string from the graph request's result" ) XCTAssertTrue(completionWasInvoked) } func testCodeExchangeCompletionWithAccessTokenStringAndAuthenticationTokenString() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withCode, appID: Values.appID ) let stubbedResult = [ Keys.accessToken: Values.accessToken, Keys.idToken: Values.idToken, ] completer.completeLogin( handler: { _ in }, nonce: Values.nonce, codeVerifier: Values.codeVerifier ) graphRequestFactory.capturedRequests.first?.capturedCompletionHandler?(nil, stubbedResult, nil) XCTAssertEqual( completer.parameters.accessTokenString, Values.accessToken, "Should set the access token string from the graph request's result" ) XCTAssertEqual( completer.parameters.authenticationTokenString, Values.idToken, "Should set the authentication token string from the graph request's result" ) XCTAssertEqual( authenticationTokenFactory.capturedTokenString, Values.idToken, "Should call AuthenticationTokenFactory with the expected token string" ) XCTAssertEqual( authenticationTokenFactory.capturedNonce, Values.nonce, "Should call AuthenticationTokenFactory with the expected nonce" ) } func testCodeExchangeWithRandomResults() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withCode, appID: Values.appID ) let stubbedResult: [String: Any] = [ Keys.accessToken: Values.accessToken, "expires_in": "10000", "data_access_expiration_time": 1, ] var completionWasInvoked = false completer.completeLogin( handler: { _ in // Basically just making sure that nothing crashes here when we feed it garbage results completionWasInvoked = true }, nonce: Values.nonce, codeVerifier: Values.codeVerifier ) (0 ..< 100).forEach { _ in let mangledResult = stubbedResult let parameters = Fuzzer.randomize(json: mangledResult) graphRequestFactory.capturedRequests.first?.capturedCompletionHandler?(nil, parameters, nil) XCTAssertTrue(completionWasInvoked) completionWasInvoked = false } } func testCompleteWithAuthenticationTokenWithoutNonce() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withIDToken, appID: Values.appID ) completer.completeLogin { _ in } XCTAssertNotNil(completer.parameters.error) XCTAssertEqual(graphRequestFactory.capturedRequests.count, 0) XCTAssertNil(authenticationTokenFactory.capturedTokenString) } func testCompleteWithAuthenticationTokenWithNonce() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withIDToken, appID: Values.appID ) let nonce = Values.nonce completer.completeLogin( handler: { _ in }, nonce: nonce, codeVerifier: Values.codeVerifier ) XCTAssertNil(completer.parameters.error) XCTAssertEqual(graphRequestFactory.capturedRequests.count, 0) XCTAssertEqual( authenticationTokenFactory.capturedTokenString, SampleRawLoginCompletionParameters.withIDToken[Keys.idToken] as? String, "Should call AuthenticationTokenFactory with the expected token string" ) XCTAssertEqual( authenticationTokenFactory.capturedNonce, nonce, "Should call AuthenticationTokenFactory with the expected nonce" ) } func testAuthenticationTokenCreationCompleteWithEmptyResult() { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withIDToken, appID: Values.appID ) var completionWasInvoked = false var capturedParameters: LoginCompletionParameters? completer.completeLogin { parameters in capturedParameters = parameters completionWasInvoked = true } authenticationTokenFactory.capturedCompletion?(nil) XCTAssertNotNil(capturedParameters?.error) XCTAssertNil(capturedParameters?.authenticationToken) XCTAssert( completionWasInvoked, "Handler should be invoked" ) } func testAuthenticationTokenCreationCompleteWithToken() throws { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withIDToken, appID: Values.appID ) let nonce = Values.nonce var completionWasInvoked = false var capturedParameters: LoginCompletionParameters? completer.completeLogin( handler: { parameters in capturedParameters = parameters completionWasInvoked = true }, nonce: nonce, codeVerifier: Values.codeVerifier ) let tokenString = try XCTUnwrap(SampleRawLoginCompletionParameters.withIDToken[Keys.idToken] as? String) let token = AuthenticationToken( tokenString: tokenString, nonce: nonce ) authenticationTokenFactory.capturedCompletion?(token) XCTAssertNil(completer.parameters.error) let capturedToken = try XCTUnwrap(capturedParameters?.authenticationToken) XCTAssertEqual( capturedToken.tokenString, SampleRawLoginCompletionParameters.withIDToken[Keys.idToken] as? String ) XCTAssertEqual(capturedToken.nonce, nonce) XCTAssert( completionWasInvoked, "Handler should be invoked" ) } func testCompleteWithAccessToken() throws { let completer = createLoginCompleter( parameters: SampleRawLoginCompletionParameters.withAccessToken, appID: Values.appID ) var completionWasInvoked = false var capturedParameters: LoginCompletionParameters? completer.completeLogin( handler: { parameters in capturedParameters = parameters completionWasInvoked = true }, nonce: Values.nonce, codeVerifier: Values.codeVerifier ) let parameters = try XCTUnwrap(capturedParameters) verifyParameters( actual: parameters, expected: SampleRawLoginCompletionParameters.withAccessToken ) XCTAssertTrue(completionWasInvoked, "Handler should be invoked") XCTAssertNil(completer.parameters.error) XCTAssertEqual(graphRequestFactory.capturedRequests.count, 0) XCTAssertNil(authenticationTokenFactory.capturedTokenString) } func testCompleteWithEmptyParameters() { let completer = createLoginCompleter(parameters: [:], appID: Values.appID) var completionWasInvoked = false completer.completeLogin( handler: { _ in completionWasInvoked = true }, nonce: Values.nonce, codeVerifier: Values.codeVerifier ) XCTAssert(completionWasInvoked, "Handler should be invoked") XCTAssertNil(completer.parameters.error) XCTAssertEqual(graphRequestFactory.capturedRequests.count, 0) XCTAssertNil(authenticationTokenFactory.capturedTokenString) } // MARK: Profile func testCreateProfileWithClaims() throws { let factory = TestProfileFactory(stubbedProfile: SampleUserProfiles.createValid()) LoginURLCompleter.profileFactory = factory let claim = try XCTUnwrap( AuthenticationTokenClaims( jti: "some_jti", iss: "some_iss", aud: "some_aud", nonce: Values.nonce, exp: 1234, iat: 1234, sub: "some_sub", name: "some_name", givenName: "first", middleName: "middle", familyName: "last", email: "example@example.com", picture: "www.facebook.com", userFriends: ["123", "456"], userBirthday: "01/01/1990", userAgeRange: ["min": 21], userHometown: ["id": "112724962075996", "name": "Martinez, California"], userLocation: ["id": "110843418940484", "name": "Seattle, Washington"], userGender: "male", userLink: "facebook.com" ) ) LoginURLCompleter.profile(with: claim) XCTAssertEqual( factory.capturedUserID, claim.sub, "Should request a profile with the claims sub as the user identifier" ) XCTAssertEqual( factory.capturedName, claim.name, "Should request a profile using the name from the claims" ) XCTAssertEqual( factory.capturedFirstName, claim.givenName, "Should request a profile using the first name from the claims" ) XCTAssertEqual( factory.capturedMiddleName, claim.middleName, "Should request a profile using the middle name from the claims" ) XCTAssertEqual( factory.capturedLastName, claim.familyName, "Should request a profile using the last name from the claims" ) XCTAssertEqual( factory.capturedImageURL?.absoluteString, claim.picture, "Should request an image URL from the claims" ) XCTAssertEqual( factory.capturedEmail, claim.email, "Should request a profile using the email from the claims" ) XCTAssertEqual( factory.capturedFriendIDs, claim.userFriends, "Should request a profile using the friend identifiers from the claims" ) // @lint-ignore FBOBJCDISCOURAGEDFUNCTION let formatter = DateFormatter() formatter.dateFormat = "MM/dd/yyyy" let capturedBirthday = try XCTUnwrap(factory.capturedBirthday) XCTAssertEqual( formatter.string(from: capturedBirthday), claim.userBirthday, "Should request a profile using the user birthday from the claims" ) let rawAgeRange = try XCTUnwrap(claim.userAgeRange) XCTAssertEqual( factory.capturedAgeRange, UserAgeRange(from: rawAgeRange), "Should request a profile using the user age range from the claims" ) let rawHometownLocation = try XCTUnwrap(claim.userHometown) XCTAssertEqual( factory.capturedHometown, Location(from: rawHometownLocation), "Should request a profile using the user hometown from the claims" ) let rawUserLocation = try XCTUnwrap(claim.userLocation) XCTAssertEqual( factory.capturedLocation, Location(from: rawUserLocation), "Should request a profile using the user location from the claims" ) XCTAssertEqual( factory.capturedGender, claim.userGender, "Should request a profile using the gender from the claims" ) let rawUserLink = try XCTUnwrap(claim.userLink) XCTAssertEqual( factory.capturedLinkURL, URL(string: rawUserLink), "Should request a profile using the link from the claims" ) XCTAssertTrue( factory.capturedIsLimited, "Should request a profile with limited information" ) } // MARK: - Helpers func createLoginCompleter(parameters: [String: Any], appID: String) -> LoginURLCompleter { LoginURLCompleter( urlParameters: parameters, appID: appID, authenticationTokenCreator: authenticationTokenFactory, graphRequestFactory: graphRequestFactory, internalUtility: internalUtility ) } func verifyParameters( actual: LoginCompletionParameters, expected: [String: Any], _ file: StaticString = #file, _ line: UInt = #line ) { XCTAssertEqual( actual.accessTokenString, expected[Keys.accessToken] as? String, file: file, line: line ) XCTAssertEqual( actual.authenticationTokenString, expected[Keys.idToken] as? String, file: file, line: line ) XCTAssertEqual( actual.appID, Values.appID, file: file, line: line ) XCTAssertEqual( actual.challenge, SampleRawLoginCompletionParameters.fakeChallenge, file: file, line: line ) if let rawGrantedPermissions = expected[Keys.grantedScopes] as? String { let grantedPermissions = Set( rawGrantedPermissions .split(separator: ",") .compactMap { FBPermission(string: String($0)) } ) XCTAssertEqual( actual.permissions, grantedPermissions, file: file, line: line ) } if let rawDeclinedPermissions = expected[Keys.deniedScopes] as? String { let declinedPermissions = Set( rawDeclinedPermissions .split(separator: ",") .compactMap { FBPermission(string: String($0)) } ) XCTAssertEqual( actual.declinedPermissions, declinedPermissions, file: file, line: line ) } XCTAssertEqual( actual.userID, expected[Keys.userID] as? String, file: file, line: line ) XCTAssertEqual( actual.graphDomain, expected[Keys.graphDomain] as? String, file: file, line: line ) if let expectedExpires = expected[Keys.expires] as? Double, let expires = actual.expirationDate?.timeIntervalSince1970 { XCTAssertEqual( expires, expectedExpires, accuracy: 100, file: file, line: line ) } if let expectedExpiresAt = expected[Keys.expiresAt] as? Double, let expiresAt = actual.expirationDate?.timeIntervalSince1970 { XCTAssertEqual( expiresAt, expectedExpiresAt, accuracy: 100, file: file, line: line ) } if let expectedExpiresIn = expected[Keys.expiresIn] as? Double, let expiresIn = actual.expirationDate?.timeIntervalSinceNow { XCTAssertEqual( expiresIn, expectedExpiresIn, accuracy: 100, file: file, line: line ) } if let expectedDataAccessExpiration = expected[Keys.dataAccessExpiration] as? TimeInterval, let dataExpiration = actual.dataAccessExpirationDate?.timeIntervalSince1970 { XCTAssertEqual( dataExpiration, expectedDataAccessExpiration, accuracy: 100, file: file, line: line ) } XCTAssertEqual( actual.nonceString, expected[Keys.nonce] as? String, file: file, line: line ) XCTAssertNil(actual.error, file: file, line: line) } func verifyEmptyParameters( _ parameters: LoginCompletionParameters, _ file: StaticString = #file, _ line: UInt = #line ) { XCTAssertNil(parameters.accessTokenString, file: file, line: line) XCTAssertNil(parameters.authenticationTokenString, file: file, line: line) XCTAssertNil(parameters.appID, file: file, line: line) XCTAssertNil(parameters.challenge, file: file, line: line) XCTAssertNil(parameters.permissions, file: file, line: line) XCTAssertNil(parameters.declinedPermissions, file: file, line: line) XCTAssertNil(parameters.userID, file: file, line: line) XCTAssertNil(parameters.graphDomain, file: file, line: line) XCTAssertNil(parameters.expirationDate, file: file, line: line) XCTAssertNil(parameters.dataAccessExpirationDate, file: file, line: line) XCTAssertNil(parameters.nonceString, file: file, line: line) XCTAssertNil(parameters.error, file: file, line: line) } }