diff --git a/Package.resolved b/Package.resolved index 9e562b2..c6ccdd9 100644 --- a/Package.resolved +++ b/Package.resolved @@ -37,6 +37,15 @@ "version": "1.0.1" } }, + { + "package": "OHHTTPStubs", + "repositoryURL": "https://github.com/AliSoftware/OHHTTPStubs", + "state": { + "branch": null, + "revision": "e92b5a5746ef16add2a1424f1fc19529d9a75cde", + "version": "9.0.0" + } + }, { "package": "Path.swift", "repositoryURL": "https://github.com/mxcl/Path.swift.git", diff --git a/Package.swift b/Package.swift index cf45f0f..a8b4f00 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,8 @@ let package = Package( .package(name: "PMKFoundation", url: "https://github.com/PromiseKit/Foundation.git", .upToNextMinor(from: "3.3.1")), .package(url: "https://github.com/scinfu/SwiftSoup.git", .upToNextMinor(from: "2.0.0")), .package(url: "https://github.com/mxcl/LegibleError.git", .upToNextMinor(from: "1.0.1")), - .package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", .upToNextMinor(from: "3.2.0")) + .package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", .upToNextMinor(from: "3.2.0")), + .package(url: "https://github.com/AliSoftware/OHHTTPStubs", .upToNextMinor(from: "9.0.0")), ], targets: [ .target( @@ -47,6 +48,9 @@ let package = Package( ]), .testTarget( name: "AppleAPITests", - dependencies: ["AppleAPI"]), + dependencies: ["AppleAPI", .product(name: "OHHTTPStubsSwift", package: "OHHTTPStubs")], + resources: [ + .copy("Fixtures"), + ]), ] ) diff --git a/Sources/AppleAPI/Client.swift b/Sources/AppleAPI/Client.swift index b3cc7c1..9cb0db3 100644 --- a/Sources/AppleAPI/Client.swift +++ b/Sources/AppleAPI/Client.swift @@ -8,7 +8,7 @@ public class Client { public init() {} - public enum Error: Swift.Error, LocalizedError { + public enum Error: Swift.Error, LocalizedError, Equatable { case invalidSession case invalidUsernameOrPassword(username: String) case unexpectedSignInResponse(statusCode: Int, message: String?) diff --git a/Tests/AppleAPITests/AppleAPITests.swift b/Tests/AppleAPITests/AppleAPITests.swift index 97f3574..f39a72d 100644 --- a/Tests/AppleAPITests/AppleAPITests.swift +++ b/Tests/AppleAPITests/AppleAPITests.swift @@ -1,4 +1,6 @@ import XCTest +import OHHTTPStubs +import OHHTTPStubsSwift import PromiseKit import PMKFoundation @testable import AppleAPI @@ -12,4 +14,91 @@ final class AppleAPITests: XCTestCase { override func setUp() { } + + override func tearDown() { + HTTPStubs.removeAllStubs() + super.tearDown() + } + + func test_Login_2FA_Succeeds() { + stub(condition: isAbsoluteURLString(URL.itcServiceKey.absoluteString)) { _ in + fixture(filePath: Bundle.module.path(forResource: "ITCServiceKey", ofType: "json", inDirectory: "Fixtures/Login_2FA_Succeeds")!, + headers: ["Content-Type": "application/json"]) + } + stub(condition: isAbsoluteURLString(URL.signIn.absoluteString)) { _ in + fixture(filePath: Bundle.module.path(forResource: "SignIn", ofType: "json", inDirectory: "Fixtures/Login_2FA_Succeeds")!, + headers: ["Content-Type": "application/json"]) + } + stub(condition: isAbsoluteURLString(URL.authOptions.absoluteString)) { _ in + fixture(filePath: Bundle.module.path(forResource: "AuthOptions", ofType: "json", inDirectory: "Fixtures/Login_2FA_Succeeds")!, + headers: ["Content-Type": "application/json"]) + } + stub(condition: isAbsoluteURLString(URL.submitSecurityCode.absoluteString)) { _ in + HTTPStubsResponse(data: Data(), statusCode: 204, headers: ["Content-Type": "application/json"]) + } + stub(condition: isAbsoluteURLString(URL.trust.absoluteString)) { _ in + HTTPStubsResponse(data: Data(), statusCode: 204, headers: nil) + } + stub(condition: isAbsoluteURLString(URL.olympusSession.absoluteString)) { _ in + fixture(filePath: Bundle.module.path(forResource: "OlympusSession", ofType: "json", inDirectory: "Fixtures/Login_2FA_Succeeds")!, + headers: ["Content-Type": "application/json"]) + } + + let expectation = self.expectation(description: "promise fulfills") + + let client = Client() + client.login(accountName: "test@example.com", password: "ABC123") + .tap { result in + guard case .fulfilled = result else { + XCTFail("login rejected") + return + } + expectation.fulfill() + } + .cauterize() + + wait(for: [expectation], timeout: 1.0) + } + + func test_Login_2FA_IncorrectPassword() { + stub(condition: isAbsoluteURLString(URL.itcServiceKey.absoluteString)) { _ in + fixture(filePath: Bundle.module.path(forResource: "ITCServiceKey", ofType: "json", inDirectory: "Fixtures/Login_2FA_IncorrectPassword")!, + headers: ["Content-Type": "application/json"]) + } + stub(condition: isAbsoluteURLString(URL.signIn.absoluteString)) { _ in + fixture(filePath: Bundle.module.path(forResource: "SignIn", ofType: "json", inDirectory: "Fixtures/Login_2FA_IncorrectPassword")!, + status: 401, + headers: ["Content-Type": "application/json"]) + } + stub(condition: isAbsoluteURLString(URL.authOptions.absoluteString)) { _ in + fixture(filePath: Bundle.module.path(forResource: "AuthOptions", ofType: "json", inDirectory: "Fixtures/Login_2FA_IncorrectPassword")!, + headers: ["Content-Type": "application/json"]) + } + stub(condition: isAbsoluteURLString(URL.submitSecurityCode.absoluteString)) { _ in + HTTPStubsResponse(data: Data(), statusCode: 204, headers: ["Content-Type": "application/json"]) + } + stub(condition: isAbsoluteURLString(URL.trust.absoluteString)) { _ in + HTTPStubsResponse(data: Data(), statusCode: 204, headers: nil) + } + stub(condition: isAbsoluteURLString(URL.olympusSession.absoluteString)) { _ in + fixture(filePath: Bundle.module.path(forResource: "OlympusSession", ofType: "json", inDirectory: "Fixtures/Login_2FA_IncorrectPassword")!, + headers: ["Content-Type": "application/json"]) + } + + let expectation = self.expectation(description: "promise rejects") + + let client = Client() + client.login(accountName: "test@example.com", password: "ABC123") + .tap { result in + guard case .rejected(let error as AppleAPI.Client.Error) = result else { + XCTFail("login fulfilled, but should have rejected with .invalidUsernameOrPassword error") + return + } + XCTAssertEqual(error, AppleAPI.Client.Error.invalidUsernameOrPassword(username: "test@example.com")) + expectation.fulfill() + } + .cauterize() + + wait(for: [expectation], timeout: 1.0) + } } diff --git a/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/AuthOptions.json b/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/AuthOptions.json new file mode 100644 index 0000000..f521ff8 --- /dev/null +++ b/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/AuthOptions.json @@ -0,0 +1,34 @@ +{ + "trustedPhoneNumbers" : [ { + "obfuscatedNumber" : "(•••) •••-••00", + "pushMode" : "sms", + "numberWithDialCode" : "+1 (•••) •••-••00", + "id" : 1 + } ], + "securityCode" : { + "length" : 6, + "tooManyCodesSent" : false, + "tooManyCodesValidated" : false, + "securityCodeLocked" : false, + "securityCodeCooldown" : false + }, + "authenticationType" : "hsa2", + "recoveryUrl" : "https://iforgot.apple.com/phone/add?prs_account_nm=test%40example.com&autoSubmitAccount=true&appId=142", + "cantUsePhoneNumberUrl" : "https://iforgot.apple.com/iforgot/phone/add?context=cantuse&prs_account_nm=test%40example.com&autoSubmitAccount=true&appId=142", + "recoveryWebUrl" : "https://iforgot.apple.com/password/verify/appleid?prs_account_nm=test%40example.com&autoSubmitAccount=true&appId=142", + "repairPhoneNumberUrl" : "https://gsa.apple.com/appleid/account/manage/repair/verify/phone", + "repairPhoneNumberWebUrl" : "https://appleid.apple.com/widget/account/repair?#!repair", + "aboutTwoFactorAuthenticationUrl" : "https://support.apple.com/kb/HT204921", + "autoVerified" : false, + "showAutoVerificationUI" : false, + "managedAccount" : false, + "trustedPhoneNumber" : { + "obfuscatedNumber" : "(•••) •••-••00", + "pushMode" : "sms", + "numberWithDialCode" : "+1 (•••) •••-••00", + "id" : 1 + }, + "hsa2Account" : true, + "restrictedAccount" : false, + "supportsRecovery" : true +} diff --git a/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/ITCServiceKey.json b/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/ITCServiceKey.json new file mode 100644 index 0000000..33c00bf --- /dev/null +++ b/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/ITCServiceKey.json @@ -0,0 +1,4 @@ +{ + "authServiceUrl" : "https://idmsa.apple.com/appleauth", + "authServiceKey" : "NNNNN" +} diff --git a/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/OlympusSession.json b/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/OlympusSession.json new file mode 100644 index 0000000..07fb0cb --- /dev/null +++ b/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/OlympusSession.json @@ -0,0 +1,128 @@ +{ + "user" : { + "fullName" : "Test User", + "firstName" : "Test", + "lastName" : "User", + "emailAddress" : "test@example.com", + "prsId" : "000000000" + }, + "provider" : { + "providerId" : 00000, + "name" : "Test User", + "contentTypes" : [ "SOFTWARE" ], + "subType" : "INDIVIDUAL", + "pla" : [ { + "id" : "1BC01216-52D4-43DC-8555-195F4454C348", + "version" : "5014", + "types" : [ "contractContentTypeDisplay.iOSFreeApps", "contractContentTypeDisplay.MacOSXFreeApplications" ], + "contractCountryOfOrigins" : [ "CAN" ] + } ] + }, + "theme" : "APPSTORE_CONNECT", + "availableProviders" : [ { + "providerId" : 000000, + "name" : "Test User", + "contentTypes" : [ "SOFTWARE" ], + "subType" : "INDIVIDUAL" + } ], + "backingType" : "ITC", + "backingTypes" : [ "ITC" ], + "roles" : [ "ADMIN", "LEGAL" ], + "unverifiedRoles" : [ ], + "featureFlags" : [ "showWwdrUserRoles", "adpRad", "apiKeys" ], + "agreeToTerms" : true, + "termsSignatures" : [ "ASC", "RAD" ], + "modules" : [ { + "key" : "Apps", + "name" : "ITC.HomePage.Apps.IconText", + "localizedName" : "My Apps", + "url" : "https://appstoreconnect.apple.com/apps", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/Apps@2x.d3ce493e56172e92aed6.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "AppAnalytics", + "name" : "ITC.HomePage.AppAnalytics.IconText", + "localizedName" : "App Analytics", + "url" : "https://analytics.itunes.apple.com/", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/AppAnalytics@2x.e19f711d943cb42d65b2.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "SalesTrends", + "name" : "ITC.HomePage.SalesTrends.IconText", + "localizedName" : "Sales and Trends", + "url" : "https://appstoreconnect.apple.com/trends", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/SalesTrends@2x.b1f802112426525d990a.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "FinancialReports", + "name" : "ITC.HomePage.FinancialReports.IconText", + "localizedName" : "Payments and Financial Reports", + "url" : "https://appstoreconnect.apple.com/itc/payments_and_financial_reports", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/FinancialReports@2x.a7b266a5136cc65c643b.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "Account", + "name" : "ITC.HomePage.Account.IconText", + "localizedName" : "Users and Access", + "url" : "https://appstoreconnect.apple.com/access/users", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/ManageUsers@2x.81511f3933fb2fb4b20d.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "ContractsTaxBanking", + "name" : "ITC.HomePage.ContractsTaxBanking.IconText", + "localizedName" : "Agreements, Tax, and Banking", + "url" : "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/da/jumpTo?page=contracts", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/ContractsTaxBanking@2x.74466eb8570dd797602e.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "Resources", + "name" : "ITC.HomePage.Resources.IconText", + "localizedName" : "Resources and Help", + "url" : "https://developer.apple.com/app-store-connect/", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/Resources@2x.3c8d0d8c08e876cf9470.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + } ], + "helpLinks" : [ { + "key" : "AllAsc", + "url" : "https://help.apple.com/app-store-connect/", + "localizedText" : "App Store Connect Resources" + }, { + "key" : "Xcode", + "url" : "https://help.apple.com/xcode/mac/current/", + "localizedText" : "Xcode Help" + }, { + "key" : "SupportContact", + "url" : "https://developer.apple.com/support/", + "localizedText" : "Support and Contact" + } ], + "userProfile" : [ { + "key" : "signIn", + "url" : "https://appstoreconnect.apple.com/login", + "localizedText" : "Sign In" + }, { + "key" : "personalDetails", + "url" : "https://appstoreconnect.apple.com/access/users/07E3E586-44B1-48D3-BF8D-430F754F1BAA/settings", + "localizedText" : "Edit Profile" + }, { + "key" : "signOut", + "url" : "https://appstoreconnect.apple.com/logout", + "localizedText" : "Sign Out" + } ], + "pccDto" : null, + "publicUserId" : "07E3E586-44B1-48D3-BF8D-430F754F1BAA", + "ofacState" : null +} diff --git a/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/SignIn.json b/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/SignIn.json new file mode 100644 index 0000000..18a6cc0 --- /dev/null +++ b/Tests/AppleAPITests/Fixtures/Login_2FA_IncorrectPassword/SignIn.json @@ -0,0 +1,7 @@ +{ + "serviceErrors" : [ { + "code" : "-20101", + "message" : "Your Apple ID or password was incorrect.", + "suppressDismissal" : false + } ] +} diff --git a/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/AuthOptions.json b/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/AuthOptions.json new file mode 100644 index 0000000..f521ff8 --- /dev/null +++ b/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/AuthOptions.json @@ -0,0 +1,34 @@ +{ + "trustedPhoneNumbers" : [ { + "obfuscatedNumber" : "(•••) •••-••00", + "pushMode" : "sms", + "numberWithDialCode" : "+1 (•••) •••-••00", + "id" : 1 + } ], + "securityCode" : { + "length" : 6, + "tooManyCodesSent" : false, + "tooManyCodesValidated" : false, + "securityCodeLocked" : false, + "securityCodeCooldown" : false + }, + "authenticationType" : "hsa2", + "recoveryUrl" : "https://iforgot.apple.com/phone/add?prs_account_nm=test%40example.com&autoSubmitAccount=true&appId=142", + "cantUsePhoneNumberUrl" : "https://iforgot.apple.com/iforgot/phone/add?context=cantuse&prs_account_nm=test%40example.com&autoSubmitAccount=true&appId=142", + "recoveryWebUrl" : "https://iforgot.apple.com/password/verify/appleid?prs_account_nm=test%40example.com&autoSubmitAccount=true&appId=142", + "repairPhoneNumberUrl" : "https://gsa.apple.com/appleid/account/manage/repair/verify/phone", + "repairPhoneNumberWebUrl" : "https://appleid.apple.com/widget/account/repair?#!repair", + "aboutTwoFactorAuthenticationUrl" : "https://support.apple.com/kb/HT204921", + "autoVerified" : false, + "showAutoVerificationUI" : false, + "managedAccount" : false, + "trustedPhoneNumber" : { + "obfuscatedNumber" : "(•••) •••-••00", + "pushMode" : "sms", + "numberWithDialCode" : "+1 (•••) •••-••00", + "id" : 1 + }, + "hsa2Account" : true, + "restrictedAccount" : false, + "supportsRecovery" : true +} diff --git a/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/ITCServiceKey.json b/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/ITCServiceKey.json new file mode 100644 index 0000000..33c00bf --- /dev/null +++ b/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/ITCServiceKey.json @@ -0,0 +1,4 @@ +{ + "authServiceUrl" : "https://idmsa.apple.com/appleauth", + "authServiceKey" : "NNNNN" +} diff --git a/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/OlympusSession.json b/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/OlympusSession.json new file mode 100644 index 0000000..07fb0cb --- /dev/null +++ b/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/OlympusSession.json @@ -0,0 +1,128 @@ +{ + "user" : { + "fullName" : "Test User", + "firstName" : "Test", + "lastName" : "User", + "emailAddress" : "test@example.com", + "prsId" : "000000000" + }, + "provider" : { + "providerId" : 00000, + "name" : "Test User", + "contentTypes" : [ "SOFTWARE" ], + "subType" : "INDIVIDUAL", + "pla" : [ { + "id" : "1BC01216-52D4-43DC-8555-195F4454C348", + "version" : "5014", + "types" : [ "contractContentTypeDisplay.iOSFreeApps", "contractContentTypeDisplay.MacOSXFreeApplications" ], + "contractCountryOfOrigins" : [ "CAN" ] + } ] + }, + "theme" : "APPSTORE_CONNECT", + "availableProviders" : [ { + "providerId" : 000000, + "name" : "Test User", + "contentTypes" : [ "SOFTWARE" ], + "subType" : "INDIVIDUAL" + } ], + "backingType" : "ITC", + "backingTypes" : [ "ITC" ], + "roles" : [ "ADMIN", "LEGAL" ], + "unverifiedRoles" : [ ], + "featureFlags" : [ "showWwdrUserRoles", "adpRad", "apiKeys" ], + "agreeToTerms" : true, + "termsSignatures" : [ "ASC", "RAD" ], + "modules" : [ { + "key" : "Apps", + "name" : "ITC.HomePage.Apps.IconText", + "localizedName" : "My Apps", + "url" : "https://appstoreconnect.apple.com/apps", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/Apps@2x.d3ce493e56172e92aed6.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "AppAnalytics", + "name" : "ITC.HomePage.AppAnalytics.IconText", + "localizedName" : "App Analytics", + "url" : "https://analytics.itunes.apple.com/", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/AppAnalytics@2x.e19f711d943cb42d65b2.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "SalesTrends", + "name" : "ITC.HomePage.SalesTrends.IconText", + "localizedName" : "Sales and Trends", + "url" : "https://appstoreconnect.apple.com/trends", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/SalesTrends@2x.b1f802112426525d990a.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "FinancialReports", + "name" : "ITC.HomePage.FinancialReports.IconText", + "localizedName" : "Payments and Financial Reports", + "url" : "https://appstoreconnect.apple.com/itc/payments_and_financial_reports", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/FinancialReports@2x.a7b266a5136cc65c643b.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "Account", + "name" : "ITC.HomePage.Account.IconText", + "localizedName" : "Users and Access", + "url" : "https://appstoreconnect.apple.com/access/users", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/ManageUsers@2x.81511f3933fb2fb4b20d.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "ContractsTaxBanking", + "name" : "ITC.HomePage.ContractsTaxBanking.IconText", + "localizedName" : "Agreements, Tax, and Banking", + "url" : "https://appstoreconnect.apple.com/WebObjects/iTunesConnect.woa/da/jumpTo?page=contracts", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/ContractsTaxBanking@2x.74466eb8570dd797602e.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + }, { + "key" : "Resources", + "name" : "ITC.HomePage.Resources.IconText", + "localizedName" : "Resources and Help", + "url" : "https://developer.apple.com/app-store-connect/", + "iconUrl" : "https://appstoreconnect.apple.com/static/img/ico_homepage/themed/apps/Resources@2x.3c8d0d8c08e876cf9470.png", + "down" : false, + "visible" : true, + "hasNotifications" : false + } ], + "helpLinks" : [ { + "key" : "AllAsc", + "url" : "https://help.apple.com/app-store-connect/", + "localizedText" : "App Store Connect Resources" + }, { + "key" : "Xcode", + "url" : "https://help.apple.com/xcode/mac/current/", + "localizedText" : "Xcode Help" + }, { + "key" : "SupportContact", + "url" : "https://developer.apple.com/support/", + "localizedText" : "Support and Contact" + } ], + "userProfile" : [ { + "key" : "signIn", + "url" : "https://appstoreconnect.apple.com/login", + "localizedText" : "Sign In" + }, { + "key" : "personalDetails", + "url" : "https://appstoreconnect.apple.com/access/users/07E3E586-44B1-48D3-BF8D-430F754F1BAA/settings", + "localizedText" : "Edit Profile" + }, { + "key" : "signOut", + "url" : "https://appstoreconnect.apple.com/logout", + "localizedText" : "Sign Out" + } ], + "pccDto" : null, + "publicUserId" : "07E3E586-44B1-48D3-BF8D-430F754F1BAA", + "ofacState" : null +} diff --git a/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/SignIn.json b/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/SignIn.json new file mode 100644 index 0000000..10fb36e --- /dev/null +++ b/Tests/AppleAPITests/Fixtures/Login_2FA_Succeeds/SignIn.json @@ -0,0 +1,3 @@ +{ + "authType" : "hsa2" +}