From 18a8c701faadb72b2a26fec8672d842d3e2b9cd4 Mon Sep 17 00:00:00 2001 From: Harshdeep Singh <6162866+harsh62@users.noreply.github.com> Date: Wed, 2 Oct 2024 20:58:47 -0400 Subject: [PATCH] adding integration tests wave 1 --- .../AuthHostApp.xcodeproj/project.pbxproj | 31 ++ .../AWSAuthBaseTest.swift | 83 +++++- .../Helpers/AuthSignInHelper.swift | 20 +- .../EmailMFATests/EmailMFAOnlyTests.swift | 69 +++++ ...EmailMFAWithAllMFATypesRequiredTests.swift | 272 ++++++++++++++++++ 5 files changed, 469 insertions(+), 6 deletions(-) create mode 100644 AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/MFATests/EmailMFATests/EmailMFAOnlyTests.swift create mode 100644 AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/MFATests/EmailMFATests/EmailMFAWithAllMFATypesRequiredTests.swift diff --git a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.pbxproj b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.pbxproj index 2444c39e1a..14a1cccaff 100644 --- a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.pbxproj +++ b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.pbxproj @@ -61,6 +61,13 @@ 485CB5C027B61F1E006CCEC7 /* SignedOutAuthSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485CB5BC27B61F1D006CCEC7 /* SignedOutAuthSessionTests.swift */; }; 485CB5C127B61F1E006CCEC7 /* AuthSignOutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485CB5BD27B61F1D006CCEC7 /* AuthSignOutTests.swift */; }; 485CB5C227B61F1E006CCEC7 /* AuthSRPSignInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 485CB5BE27B61F1D006CCEC7 /* AuthSRPSignInTests.swift */; }; + 487C40232CACF303009CF221 /* EmailMFAWithAllMFATypesRequiredTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 487C40222CACF2FD009CF221 /* EmailMFAWithAllMFATypesRequiredTests.swift */; }; + 487C40242CACF303009CF221 /* EmailMFAWithAllMFATypesRequiredTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 487C40222CACF2FD009CF221 /* EmailMFAWithAllMFATypesRequiredTests.swift */; }; + 487C40252CACF303009CF221 /* EmailMFAWithAllMFATypesRequiredTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 487C40222CACF2FD009CF221 /* EmailMFAWithAllMFATypesRequiredTests.swift */; }; + 487C40382CACFD50009CF221 /* AWSAPIPlugin in Frameworks */ = {isa = PBXBuildFile; productRef = 487C40372CACFD50009CF221 /* AWSAPIPlugin */; }; + 487C403F2CADE8DA009CF221 /* EmailMFAOnlyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 487C403E2CADE88F009CF221 /* EmailMFAOnlyTests.swift */; }; + 487C40402CADE8DA009CF221 /* EmailMFAOnlyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 487C403E2CADE88F009CF221 /* EmailMFAOnlyTests.swift */; }; + 487C40412CADE8DA009CF221 /* EmailMFAOnlyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 487C403E2CADE88F009CF221 /* EmailMFAOnlyTests.swift */; }; 48916F382A412B2800E3E1B1 /* TOTPSetupWhenAuthenticatedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48916F372A412B2800E3E1B1 /* TOTPSetupWhenAuthenticatedTests.swift */; }; 48916F3A2A412CEE00E3E1B1 /* TOTPHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48916F392A412CEE00E3E1B1 /* TOTPHelper.swift */; }; 48916F3C2A42333E00E3E1B1 /* MFAPreferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48916F3B2A42333E00E3E1B1 /* MFAPreferenceTests.swift */; }; @@ -196,6 +203,8 @@ 485CB5BC27B61F1D006CCEC7 /* SignedOutAuthSessionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignedOutAuthSessionTests.swift; sourceTree = ""; }; 485CB5BD27B61F1D006CCEC7 /* AuthSignOutTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthSignOutTests.swift; sourceTree = ""; }; 485CB5BE27B61F1D006CCEC7 /* AuthSRPSignInTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthSRPSignInTests.swift; sourceTree = ""; }; + 487C40222CACF2FD009CF221 /* EmailMFAWithAllMFATypesRequiredTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailMFAWithAllMFATypesRequiredTests.swift; sourceTree = ""; }; + 487C403E2CADE88F009CF221 /* EmailMFAOnlyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailMFAOnlyTests.swift; sourceTree = ""; }; 48916F372A412B2800E3E1B1 /* TOTPSetupWhenAuthenticatedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPSetupWhenAuthenticatedTests.swift; sourceTree = ""; }; 48916F392A412CEE00E3E1B1 /* TOTPHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPHelper.swift; sourceTree = ""; }; 48916F3B2A42333E00E3E1B1 /* MFAPreferenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFAPreferenceTests.swift; sourceTree = ""; }; @@ -232,6 +241,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 487C40382CACFD50009CF221 /* AWSAPIPlugin in Frameworks */, B4B9F45828F47C0A004F346F /* Amplify in Frameworks */, B4B9F45A28F47C0A004F346F /* AWSCognitoAuthPlugin in Frameworks */, ); @@ -432,9 +442,19 @@ name = Packages; sourceTree = ""; }; + 487C403D2CADBC37009CF221 /* EmailMFATests */ = { + isa = PBXGroup; + children = ( + 487C403E2CADE88F009CF221 /* EmailMFAOnlyTests.swift */, + 487C40222CACF2FD009CF221 /* EmailMFAWithAllMFATypesRequiredTests.swift */, + ); + path = EmailMFATests; + sourceTree = ""; + }; 48916F362A412AF800E3E1B1 /* MFATests */ = { isa = PBXGroup; children = ( + 487C403D2CADBC37009CF221 /* EmailMFATests */, 48916F372A412B2800E3E1B1 /* TOTPSetupWhenAuthenticatedTests.swift */, 48916F3B2A42333E00E3E1B1 /* MFAPreferenceTests.swift */, 48599D492A429893009DE21C /* MFASignInTests.swift */, @@ -534,6 +554,7 @@ packageProductDependencies = ( B4B9F45728F47C0A004F346F /* Amplify */, B4B9F45928F47C0A004F346F /* AWSCognitoAuthPlugin */, + 487C40372CACFD50009CF221 /* AWSAPIPlugin */, ); productName = AuthHostApp; productReference = 485CB53A27B614CE006CCEC7 /* AuthHostApp.app */; @@ -819,6 +840,7 @@ 21F762B22BD6B1AA0048845A /* SignedOutAuthSessionTests.swift in Sources */, 21F762B32BD6B1AA0048845A /* AuthSignInHelper.swift in Sources */, 21F762B42BD6B1AA0048845A /* FederatedSessionTests.swift in Sources */, + 487C40402CADE8DA009CF221 /* EmailMFAOnlyTests.swift in Sources */, 21F762B52BD6B1AA0048845A /* AuthCustomSignInTests.swift in Sources */, 21F762B62BD6B1AA0048845A /* AuthEventIntegrationTests.swift in Sources */, 21F762B72BD6B1AA0048845A /* AuthEnvironmentHelper.swift in Sources */, @@ -832,6 +854,7 @@ 21F762BF2BD6B1AA0048845A /* MFASignInTests.swift in Sources */, 21F762C02BD6B1AA0048845A /* SignedInAuthSessionTests.swift in Sources */, 21F762C12BD6B1AA0048845A /* AuthSignUpTests.swift in Sources */, + 487C40252CACF303009CF221 /* EmailMFAWithAllMFATypesRequiredTests.swift in Sources */, 21F762C22BD6B1AA0048845A /* AuthConfirmResetPasswordTests.swift in Sources */, 21F762C32BD6B1AA0048845A /* AuthDeleteUserTests.swift in Sources */, ); @@ -851,6 +874,7 @@ buildActionMask = 2147483647; files = ( 485CB5B927B61F10006CCEC7 /* AuthSessionHelper.swift in Sources */, + 487C403F2CADE8DA009CF221 /* EmailMFAOnlyTests.swift in Sources */, 681DFEAB28E747B80000C36A /* AsyncTesting.swift in Sources */, 485CB5C227B61F1E006CCEC7 /* AuthSRPSignInTests.swift in Sources */, 9737C7502880BFD600DA0D2B /* AuthForgetDeviceTests.swift in Sources */, @@ -863,6 +887,7 @@ 48E3AB3128E52590004EE395 /* GetCurrentUserTests.swift in Sources */, 48916F3A2A412CEE00E3E1B1 /* TOTPHelper.swift in Sources */, 21CFD7C62C7524570071C70F /* AppSyncSignerTests.swift in Sources */, + 487C40232CACF303009CF221 /* EmailMFAWithAllMFATypesRequiredTests.swift in Sources */, 485CB5B127B61EAC006CCEC7 /* AWSAuthBaseTest.swift in Sources */, 485CB5C027B61F1E006CCEC7 /* SignedOutAuthSessionTests.swift in Sources */, 485CB5BA27B61F10006CCEC7 /* AuthSignInHelper.swift in Sources */, @@ -914,6 +939,7 @@ 681B76AC2A3CBBAE004B59D9 /* AWSAuthBaseTest.swift in Sources */, 681B76AD2A3CBBAE004B59D9 /* SignedOutAuthSessionTests.swift in Sources */, 681B76AE2A3CBBAE004B59D9 /* AuthSignInHelper.swift in Sources */, + 487C40412CADE8DA009CF221 /* EmailMFAOnlyTests.swift in Sources */, 681B76AF2A3CBBAE004B59D9 /* FederatedSessionTests.swift in Sources */, 681B76B02A3CBBAE004B59D9 /* AuthCustomSignInTests.swift in Sources */, 681B76B12A3CBBAE004B59D9 /* AuthEventIntegrationTests.swift in Sources */, @@ -927,6 +953,7 @@ 681B76B92A3CBBAE004B59D9 /* SignedInAuthSessionTests.swift in Sources */, 681B76BA2A3CBBAE004B59D9 /* AuthSignUpTests.swift in Sources */, 681B76BB2A3CBBAE004B59D9 /* AuthConfirmResetPasswordTests.swift in Sources */, + 487C40242CACF303009CF221 /* EmailMFAWithAllMFATypesRequiredTests.swift in Sources */, 48BCE8942A54564C0012C3CD /* MFASignInTests.swift in Sources */, 681B76BC2A3CBBAE004B59D9 /* AuthDeleteUserTests.swift in Sources */, ); @@ -1479,6 +1506,10 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ + 487C40372CACFD50009CF221 /* AWSAPIPlugin */ = { + isa = XCSwiftPackageProductDependency; + productName = AWSAPIPlugin; + }; 681B76992A3CBA97004B59D9 /* Amplify */ = { isa = XCSwiftPackageProductDependency; productName = Amplify; diff --git a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/AWSAuthBaseTest.swift b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/AWSAuthBaseTest.swift index 69668eee0d..7bbef1de02 100644 --- a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/AWSAuthBaseTest.swift +++ b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/AWSAuthBaseTest.swift @@ -34,8 +34,10 @@ class AWSAuthBaseTest: XCTestCase { var amplifyConfiguration: AmplifyConfiguration! var amplifyOutputs: AmplifyOutputsData! + var onlyUseGen2Configuration = false + var useGen2Configuration: Bool { - ProcessInfo.processInfo.arguments.contains("GEN2") + ProcessInfo.processInfo.arguments.contains("GEN2") || onlyUseGen2Configuration } override func setUp() async throws { @@ -46,6 +48,7 @@ class AWSAuthBaseTest: XCTestCase { override func tearDown() async throws { try await super.tearDown() + subscription?.cancel() await Amplify.reset() } @@ -113,6 +116,84 @@ class AWSAuthBaseTest: XCTestCase { XCTFail("Amplify configuration failed") } } + + // Dictionary to store MFA codes with usernames as keys + var mfaCodeDictionary: [String: String] = [:] + var subscription: AmplifyAsyncThrowingSequence>? = nil + + let document: String = """ + subscription OnCreateMfaInfo { + onCreateMfaInfo { + username + code + expirationTime + } + } + """ + + /// Function to create a subscription and store MFA codes in a dictionary + func createMFASubscription() { + subscription = Amplify.API.subscribe(request: .init(document: document, responseType: [String: JSONValue].self)) + + // Create the subscription and listen for MFA code events + Task { + do { + guard let subscription = subscription else { return } + for try await subscriptionEvent in subscription { + switch subscriptionEvent { + case .connection(let subscriptionConnectionState): + print("Subscription connect state is \(subscriptionConnectionState)") + case .data(let result): + switch result { + case .success(let mfaCodeResult): + print("Successfully got MFA code from subscription: \(mfaCodeResult)") + if let eventUsername = mfaCodeResult["onCreateMfaInfo"]?.asObject?["username"]?.stringValue, + let code = mfaCodeResult["onCreateMfaInfo"]?.asObject?["code"]?.stringValue { + // Store the code in the dictionary for the given username + mfaCodeDictionary[eventUsername] = code + } + case .failure(let error): + print("Got failed result with \(error.errorDescription)") + } + } + } + } catch { + print("Subscription terminated with error: \(error)") + } + } + } + + /// Test that waits for the MFA code using XCTestExpectation + func waitForMFACode(for username: String) async throws -> String? { + let expectation = XCTestExpectation(description: "Wait for MFA code") + expectation.expectedFulfillmentCount = 1 + + let task = Task { () -> String? in + var code: String? + for _ in 0..<30 { // Poll for the code, max 30 times (once per second) + if let mfaCode = mfaCodeDictionary[username] { + code = mfaCode + expectation.fulfill() // Fulfill the expectation when the value is found + break + } + try await Task.sleep(nanoseconds: 1_000_000_000) // Sleep for 1 second + } + return code + } + + // Wait for expectation or timeout after 30 seconds + let result = await XCTWaiter.fulfillment(of: [expectation], timeout: 30) + + if result == .timedOut { + // Task cancels if timed out + task.cancel() + subscription?.cancel() + return nil + } + + subscription?.cancel() + return try await task.value + } } class TestConfigHelper { diff --git a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/Helpers/AuthSignInHelper.swift b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/Helpers/AuthSignInHelper.swift index 1dc43decc6..625f72c58a 100644 --- a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/Helpers/AuthSignInHelper.swift +++ b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/Helpers/AuthSignInHelper.swift @@ -22,10 +22,20 @@ enum AuthSignInHelper { password: String, email: String, phoneNumber: String? = nil) async throws -> Bool { + return try await signUpUserReturningResult(username: username, password: password, email: email, phoneNumber: phoneNumber).isSignUpComplete + } + + static func signUpUserReturningResult( + username: String, + password: String, + email: String? = nil, + phoneNumber: String? = nil) async throws -> AuthSignUpResult { + + var userAttributes: [AuthUserAttribute] = [] - var userAttributes = [ - AuthUserAttribute(.email, value: email) - ] + if let email = email { + userAttributes.append(AuthUserAttribute(.email, value: email)) + } if let phoneNumber = phoneNumber { userAttributes.append(AuthUserAttribute(.phoneNumber, value: phoneNumber)) @@ -34,7 +44,7 @@ enum AuthSignInHelper { let options = AuthSignUpRequest.Options( userAttributes: userAttributes) let result = try await Amplify.Auth.signUp(username: username, password: password, options: options) - return result.isSignUpComplete + return result } static func signInUser(username: String, password: String) async throws -> AuthSignInResult { @@ -46,7 +56,7 @@ enum AuthSignInHelper { password: String, email: String, phoneNumber: String? = nil) async throws -> Bool { - let signedUp = try await AuthSignInHelper.signUpUser( + let signedUp: Bool = try await AuthSignInHelper.signUpUser( username: username, password: password, email: email, diff --git a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/MFATests/EmailMFATests/EmailMFAOnlyTests.swift b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/MFATests/EmailMFATests/EmailMFAOnlyTests.swift new file mode 100644 index 0000000000..b0f5e789d9 --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/MFATests/EmailMFATests/EmailMFAOnlyTests.swift @@ -0,0 +1,69 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import Amplify +import AWSCognitoAuthPlugin +import AWSAPIPlugin + +// MFA Required +// - Email +class EmailMFAOnlyTests: AWSAuthBaseTest { + + override func setUp() async throws { + onlyUseGen2Configuration = true + // Use a custom configuration these tests + amplifyOutputsFile = "testconfiguration/amplify_outputs" + + let awsApiPlugin = AWSAPIPlugin() + try Amplify.add(plugin: awsApiPlugin) + try await super.setUp() + AuthSessionHelper.clearSession() + } + + override func tearDown() async throws { + try await super.tearDown() + AuthSessionHelper.clearSession() + } + + /// Test a signIn with valid inputs getting continueSignInWithEmailMFASetup challenge + /// + /// - Given: Given an auth plugin with mocked service. + /// + /// - When: + /// - I invoke signIn with valid values + /// - Then: + /// - I should get a .continueSignInWithEmailMFASetup response + /// + //Requires only Email MFA to be enabled + func disabled_testSuccessfulEmailMFASetupStep() async { + + do { + let uniqueId = UUID().uuidString + let username = "integTest\(uniqueId)" + let password = "Pp123@\(uniqueId)" + + _ = try await AuthSignInHelper.signUpUserReturningResult( + username: username, + password: password) + + let options = AuthSignInRequest.Options() + let result = try await Amplify.Auth.signIn( + username: username, + password: password, + options: options) + + guard case .continueSignInWithEmailMFASetup = result.nextStep else { + XCTFail("Result should be .continueSignInWithEmailMFASetup for next step, instead got: \(result.nextStep)") + return + } + XCTAssertFalse(result.isSignedIn, "Signin result should be complete") + } catch { + XCTFail("Received failure with error \(error)") + } + } +} diff --git a/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/MFATests/EmailMFATests/EmailMFAWithAllMFATypesRequiredTests.swift b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/MFATests/EmailMFATests/EmailMFAWithAllMFATypesRequiredTests.swift new file mode 100644 index 0000000000..f07079d45c --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/MFATests/EmailMFATests/EmailMFAWithAllMFATypesRequiredTests.swift @@ -0,0 +1,272 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import Amplify +import AWSCognitoAuthPlugin +import AWSAPIPlugin + +// MFA Required +// - Email +// - TOTP +// - SMS +class EmailMFAWithAllMFATypesRequiredTests: AWSAuthBaseTest { + + override func setUp() async throws { + // run these tests only with Gen2 + onlyUseGen2Configuration = true + // Use a custom configuration these tests + amplifyOutputsFile = "testconfiguration/AWSCognitoAuthEmailMFAWithAllMFATypesRequired-amplify_outputs" + + let awsApiPlugin = AWSAPIPlugin() + try Amplify.add(plugin: awsApiPlugin) + try await super.setUp() + AuthSessionHelper.clearSession() + } + + override func tearDown() async throws { + try await super.tearDown() + AuthSessionHelper.clearSession() + } + + /// Test a signIn with valid inputs getting continueSignInWithMFASetupSelection challenge + /// + /// - Given: Given an auth plugin with mocked service. + /// + /// - When: + /// - I invoke signIn with valid values + /// - Then: + /// - I should get a .continueSignInWithMFASetupSelection response + /// + func testSuccessfulMFASetupSelectionStep() async { + + let options = AuthSignInRequest.Options() + + do { + let uniqueId = UUID().uuidString + let username = "integTest\(uniqueId)" + let password = "Pp123@\(uniqueId)" + + _ = try await AuthSignInHelper.signUpUserReturningResult( + username: username, + password: password) + + let result = try await Amplify.Auth.signIn( + username: username, + password: password, + options: options) + guard case .continueSignInWithMFASetupSelection(let mfaTypes) = result.nextStep else { + XCTFail("Result should be .continueSignInWithMFASetupSelection for next step") + return + } + XCTAssertTrue(mfaTypes.contains(.totp)) + XCTAssertTrue(mfaTypes.contains(.email)) + XCTAssertFalse(mfaTypes.contains(.sms)) + XCTAssertFalse(result.isSignedIn, "Signin result should be complete") + } catch { + XCTFail("Received failure with error \(error)") + } + } + + /// Test a signIn with valid inputs getting confirmSignInWithEmailMFACode challenge + /// + /// - Given: Given an auth plugin with mocked service. + /// + /// - When: + /// - I invoke signIn with valid values + /// - Then: + /// - I should get a .confirmSignInWithEmailMFACode response + /// + func testSuccessfulEmailMFACodeStep() async { + + do { + createMFASubscription() + let uniqueId = UUID().uuidString + let username = "\(uniqueId)@integTest.com" + let password = "Pp123@\(uniqueId)" + + _ = try await AuthSignInHelper.signUpUserReturningResult( + username: username, + password: password, + email: username) + + let result = try await Amplify.Auth.signIn( + username: username, + password: password, + options: AuthSignInRequest.Options()) + + guard case .confirmSignInWithEmailMFACode(let codeDetails) = result.nextStep else { + XCTFail("Result should be .confirmSignInWithEmailMFACode for next step, instead got: \(result.nextStep)") + return + } + if case .email(let destination) = codeDetails.destination { + XCTAssertNotNil(destination) + } else { + XCTFail("Destination should be email") + } + XCTAssertFalse(result.isSignedIn, "Signin result should be complete") + + // step 2: confirm sign in + guard let mfaCode = try await waitForMFACode(for: username.lowercased()) else { + XCTFail("failed to retrieve the mfa code") + return + } + + let confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: mfaCode, + options: .init()) + guard case .done = confirmSignInResult.nextStep else { + XCTFail("Result should be .done for next step") + return + } + XCTAssertTrue(confirmSignInResult.isSignedIn, "Signin result should NOT be complete") + } catch { + XCTFail("Received failure with error \(error)") + } + } + + + + /// Test a signIn with valid inputs getting continueSignInWithMFASetupSelection challenge + /// + /// - Given: Given an auth plugin with mocked service. + /// + /// - When: + /// - I invoke signIn with valid values + /// - Then: + /// - I should get a .continueSignInWithMFASetupSelection response + /// + func testConfirmSignInForEmailMFASetupSelectionStep() async { + + do { + createMFASubscription() + let uniqueId = UUID().uuidString + let username = "\(uniqueId)" + let password = "Pp123@\(uniqueId)" + + _ = try await AuthSignInHelper.signUpUserReturningResult( + username: username, + password: password) + + // Step 1: initiate sign in + let result = try await Amplify.Auth.signIn( + username: username, + password: password, + options: AuthSignInRequest.Options()) + guard case .continueSignInWithMFASetupSelection(let mfaTypes) = result.nextStep else { + XCTFail("Result should be .continueSignInWithMFASetupSelection for next step") + return + } + XCTAssertTrue(mfaTypes.contains(.totp)) + XCTAssertTrue(mfaTypes.contains(.email)) + XCTAssertFalse(mfaTypes.contains(.sms)) + XCTAssertFalse(result.isSignedIn, "Signin result should be complete") + + // Step 2: select email to continue setting up + var confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: MFAType.email.challengeResponse) + guard case .continueSignInWithEmailMFASetup = confirmSignInResult.nextStep else { + XCTFail("Result should be .continueSignInWithEmailMFASetup but got: \(confirmSignInResult.nextStep)") + return + } + + // Step 3: pass an email to setup + confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: username + "@integTest.com") + guard case .confirmSignInWithEmailMFACode(let deliveryDetails) = confirmSignInResult.nextStep else { + XCTFail("Result should be .continueSignInWithEmailMFASetup but got: \(confirmSignInResult.nextStep)") + return + } + if case .email(let destination) = deliveryDetails.destination { + XCTAssertNotNil(destination) + } else { + XCTFail("Destination should be email") + } + + XCTAssertFalse(result.isSignedIn, "Signin result should be complete") + + // step 4: confirm sign in + guard let mfaCode = try await waitForMFACode(for: username.lowercased()) else { + XCTFail("failed to retrieve the mfa code") + return + } + confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: mfaCode, + options: .init()) + guard case .done = confirmSignInResult.nextStep else { + XCTFail("Result should be .done for next step") + return + } + XCTAssertTrue(confirmSignInResult.isSignedIn, "Signin result should NOT be complete") + + } catch { + XCTFail("Received failure with error \(error)") + } + } + + /// Test a signIn with valid inputs getting continueSignInWithMFASetupSelection challenge + /// + /// - Given: Given an auth plugin with mocked service. + /// + /// - When: + /// - I invoke signIn with valid values + /// - Then: + /// - I should get a .continueSignInWithMFASetupSelection response + /// + func testConfirmSignInForTOTPMFASetupSelectionStep() async { + do { + + let uniqueId = UUID().uuidString + let username = "\(uniqueId)" + let password = "Pp123@\(uniqueId)" + + _ = try await AuthSignInHelper.signUpUserReturningResult( + username: username, + password: password) + + // Step 1: initiate sign in + let result = try await Amplify.Auth.signIn( + username: username, + password: password, + options: AuthSignInRequest.Options()) + guard case .continueSignInWithMFASetupSelection(let mfaTypes) = result.nextStep else { + XCTFail("Result should be .continueSignInWithMFASetupSelection for next step") + return + } + XCTAssertTrue(mfaTypes.contains(.totp)) + XCTAssertTrue(mfaTypes.contains(.email)) + XCTAssertFalse(mfaTypes.contains(.sms)) + XCTAssertFalse(result.isSignedIn, "Signin result should be complete") + + // Step 2: continue sign in by selecting TOTP for set up + var confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: MFAType.totp.challengeResponse) + guard case .continueSignInWithTOTPSetup(let totpDetails) = confirmSignInResult.nextStep else { + XCTFail("Result should be .continueSignInWithEmailMFASetup but got: \(confirmSignInResult.nextStep)") + return + } + XCTAssertNotNil(totpDetails.sharedSecret) + XCTAssertNotNil(totpDetails.username) + XCTAssertFalse(result.isSignedIn, "Signin result should be complete") + + // Step 3: complete sign in by verifying TOTP set up + let totpCode = TOTPHelper.generateTOTPCode(sharedSecret: totpDetails.sharedSecret) + let pluginOptions = AWSAuthConfirmSignInOptions(friendlyDeviceName: "device") + confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: totpCode, + options: .init(pluginOptions: pluginOptions)) + guard case .done = confirmSignInResult.nextStep else { + XCTFail("Result should be .done for next step") + return + } + XCTAssertTrue(confirmSignInResult.isSignedIn, "Signin result should NOT be complete") + + } catch { + XCTFail("Received failure with error \(error)") + } + } +}