@@ -140,7 +140,10 @@ class AWSAuthConfirmSignUpAPITests: BasePluginTest {
140140 let initialStateError = AuthState . configured (
141141 . signedOut( . init( lastKnownUserName: nil ) ) ,
142142 . configured,
143- . error( . service( error: AuthError . service ( " Unknown error " , " Unknown error " ) ) )
143+ . error(
144+ . service( error: AuthError . service ( " Unknown error " , " Unknown error " ) ) ,
145+ . init( username: " username " , session: " sessio " )
146+ )
144147 )
145148
146149 let authPluginError = configureCustomPluginWith (
@@ -280,7 +283,10 @@ class AWSAuthConfirmSignUpAPITests: BasePluginTest {
280283 let initialStateError = AuthState . configured (
281284 . signedOut( . init( lastKnownUserName: nil ) ) ,
282285 . configured,
283- . error( . service( error: AuthError . service ( " Unknown error " , " Unknown error " ) ) )
286+ . error(
287+ . service( error: AuthError . service ( " Unknown error " , " Unknown error " ) ) ,
288+ . init( username: " username " , session: " sessio " )
289+ )
284290 )
285291
286292 let authPluginError = configureCustomPluginWith (
@@ -595,4 +601,227 @@ class AWSAuthConfirmSignUpAPITests: BasePluginTest {
595601 XCTAssertEqual ( awsCognitoAuthError, expectedCognitoError)
596602 }
597603 }
604+
605+ // MARK: - Session Caching Tests
606+
607+ /// Given: Configured auth machine in `.error` sign up state with a cached session for matching username
608+ /// When: `Auth.confirmSignUp(for:confirmationCode:options:)` is invoked with the same username
609+ /// Then: The cached session is used and confirm sign up completes with `.completeAutoSignIn`
610+ func testConfirmSignUpUsesSessionFromErrorStateWithMatchingUsername( ) async throws {
611+ let cachedSession = " cached-session-from-error "
612+ let mockIdentityProvider = MockIdentityProvider (
613+ mockConfirmSignUpResponse: { request in
614+ // Verify the cached session is passed through
615+ XCTAssertEqual ( request. session, cachedSession)
616+ return . init( session: " new-session " )
617+ }
618+ )
619+
620+ let initialStateError = AuthState . configured (
621+ . signedOut( . init( lastKnownUserName: nil ) ) ,
622+ . configured,
623+ . error(
624+ . service( error: AuthError . service ( " Previous error " , " Recovery " ) ) ,
625+ . init( username: " jeffb " , session: cachedSession)
626+ )
627+ )
628+
629+ let authPluginError = configureCustomPluginWith (
630+ userPool: { mockIdentityProvider } ,
631+ initialState: initialStateError
632+ )
633+
634+ let result = try await authPluginError. confirmSignUp (
635+ for: " jeffb " ,
636+ confirmationCode: " 123456 " ,
637+ options: options
638+ )
639+
640+ guard case . completeAutoSignIn( let session) = result. nextStep else {
641+ XCTFail ( " Result should be .completeAutoSignIn for next step " )
642+ return
643+ }
644+ XCTAssertTrue ( result. isSignUpComplete, " Sign up result should be complete " )
645+ XCTAssertEqual ( session, " new-session " )
646+ }
647+
648+ /// Given: Configured auth machine in `.error` sign up state with a cached session for different username
649+ /// When: `Auth.confirmSignUp(for:confirmationCode:options:)` is invoked with a different username
650+ /// Then: The cached session is NOT used
651+ func testConfirmSignUpDoesNotUseSessionFromErrorStateWithDifferentUsername( ) async throws {
652+ let cachedSession = " cached-session-from-error "
653+ let mockIdentityProvider = MockIdentityProvider (
654+ mockConfirmSignUpResponse: { request in
655+ // Verify the cached session is NOT passed through
656+ XCTAssertNil ( request. session)
657+ return . init( session: " new-session " )
658+ }
659+ )
660+
661+ let initialStateError = AuthState . configured (
662+ . signedOut( . init( lastKnownUserName: nil ) ) ,
663+ . configured,
664+ . error(
665+ . service( error: AuthError . service ( " Previous error " , " Recovery " ) ) ,
666+ . init( username: " different-user " , session: cachedSession)
667+ )
668+ )
669+
670+ let authPluginError = configureCustomPluginWith (
671+ userPool: { mockIdentityProvider } ,
672+ initialState: initialStateError
673+ )
674+
675+ let result = try await authPluginError. confirmSignUp (
676+ for: " jeffb " ,
677+ confirmationCode: " 123456 " ,
678+ options: options
679+ )
680+
681+ guard case . completeAutoSignIn( let session) = result. nextStep else {
682+ XCTFail ( " Result should be .completeAutoSignIn for next step " )
683+ return
684+ }
685+ XCTAssertTrue ( result. isSignUpComplete, " Sign up result should be complete " )
686+ XCTAssertEqual ( session, " new-session " )
687+ }
688+
689+ /// Given: Configured auth machine in `.error` sign up state with nil session
690+ /// When: `Auth.confirmSignUp(for:confirmationCode:options:)` is invoked
691+ /// Then: No session is passed and confirm sign up completes successfully
692+ func testConfirmSignUpFromErrorStateWithNilSession( ) async throws {
693+ let mockIdentityProvider = MockIdentityProvider (
694+ mockConfirmSignUpResponse: { request in
695+ XCTAssertNil ( request. session)
696+ return . init( )
697+ }
698+ )
699+
700+ let initialStateError = AuthState . configured (
701+ . signedOut( . init( lastKnownUserName: nil ) ) ,
702+ . configured,
703+ . error(
704+ . service( error: AuthError . service ( " Previous error " , " Recovery " ) ) ,
705+ . init( username: " jeffb " , session: nil )
706+ )
707+ )
708+
709+ let authPluginError = configureCustomPluginWith (
710+ userPool: { mockIdentityProvider } ,
711+ initialState: initialStateError
712+ )
713+
714+ let result = try await authPluginError. confirmSignUp (
715+ for: " jeffb " ,
716+ confirmationCode: " 123456 " ,
717+ options: options
718+ )
719+
720+ guard case . done = result. nextStep else {
721+ XCTFail ( " Result should be .done for next step " )
722+ return
723+ }
724+ XCTAssertTrue ( result. isSignUpComplete, " Sign up result should be complete " )
725+ }
726+
727+ /// Given: Configured auth machine in `.awaitingUserConfirmation` with a session
728+ /// When: `Auth.confirmSignUp(for:confirmationCode:options:)` is invoked with matching username
729+ /// Then: The cached session is used for auto sign-in
730+ func testConfirmSignUpUsesSessionFromAwaitingUserConfirmationState( ) async throws {
731+ let cachedSession = " cached-session-awaiting "
732+ let mockIdentityProvider = MockIdentityProvider (
733+ mockConfirmSignUpResponse: { request in
734+ XCTAssertEqual ( request. session, cachedSession)
735+ return . init( session: " new-session " )
736+ }
737+ )
738+
739+ let initialState = AuthState . configured (
740+ . signedOut( . init( lastKnownUserName: nil ) ) ,
741+ . configured,
742+ . awaitingUserConfirmation(
743+ SignUpEventData ( username: " jeffb " , session: cachedSession) ,
744+ . init( . confirmUser( ) )
745+ )
746+ )
747+
748+ let authPlugin = configureCustomPluginWith (
749+ userPool: { mockIdentityProvider } ,
750+ initialState: initialState
751+ )
752+
753+ let result = try await authPlugin. confirmSignUp (
754+ for: " jeffb " ,
755+ confirmationCode: " 123456 " ,
756+ options: options
757+ )
758+
759+ guard case . completeAutoSignIn( let session) = result. nextStep else {
760+ XCTFail ( " Result should be .completeAutoSignIn for next step " )
761+ return
762+ }
763+ XCTAssertTrue ( result. isSignUpComplete, " Sign up result should be complete " )
764+ XCTAssertEqual ( session, " new-session " )
765+ }
766+
767+ /// Given: Configured auth machine transitions to error state during confirm sign up
768+ /// When: The error state contains session data
769+ /// Then: The session is preserved in the error state for potential retry
770+ func testSessionPreservedInErrorStateDuringConfirmSignUp( ) async throws {
771+ let initialSession = " initial-session "
772+ let mockIdentityProvider = MockIdentityProvider (
773+ mockConfirmSignUpResponse: { _ in
774+ throw AWSCognitoIdentityProvider . CodeMismatchException ( )
775+ }
776+ )
777+
778+ let initialState = AuthState . configured (
779+ . signedOut( . init( lastKnownUserName: nil ) ) ,
780+ . configured,
781+ . awaitingUserConfirmation(
782+ SignUpEventData ( username: " jeffb " , session: initialSession) ,
783+ . init( . confirmUser( ) )
784+ )
785+ )
786+
787+ let authPlugin = configureCustomPluginWith (
788+ userPool: { mockIdentityProvider } ,
789+ initialState: initialState
790+ )
791+
792+ do {
793+ _ = try await authPlugin. confirmSignUp (
794+ for: " jeffb " ,
795+ confirmationCode: " wrong-code " ,
796+ options: options
797+ )
798+ XCTFail ( " Should throw error " )
799+ } catch {
800+ guard let authError = error as? AuthError else {
801+ XCTFail ( " Should throw Auth error " )
802+ return
803+ }
804+
805+ guard case . service = authError else {
806+ XCTFail ( " Auth error should be of type service error " )
807+ return
808+ }
809+
810+ // Verify the state machine is now in error state
811+ let currentState = await authPlugin. authStateMachine. currentState
812+ guard case . configured( _, _, let signUpState) = currentState else {
813+ XCTFail ( " Should be in configured state " )
814+ return
815+ }
816+
817+ guard case . error( _, let data) = signUpState else {
818+ XCTFail ( " Should be in error state " )
819+ return
820+ }
821+
822+ // Verify session is preserved in error state
823+ XCTAssertEqual ( data. session, initialSession)
824+ XCTAssertEqual ( data. username, " jeffb " )
825+ }
826+ }
598827}
0 commit comments