1616package com.amplifyframework.ui.authenticator
1717
1818import android.app.Application
19+ import com.amplifyframework.auth.AuthUserAttributeKey.email
20+ import com.amplifyframework.auth.AuthUserAttributeKey.emailVerified
1921import com.amplifyframework.auth.MFAType
2022import com.amplifyframework.auth.result.step.AuthSignInStep
23+ import com.amplifyframework.ui.authenticator.auth.VerificationMechanism
2124import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep
2225import com.amplifyframework.ui.authenticator.util.AmplifyResult
26+ import com.amplifyframework.ui.authenticator.util.AmplifyResult.Success
2327import com.amplifyframework.ui.authenticator.util.AuthConfigurationResult
2428import com.amplifyframework.ui.authenticator.util.AuthProvider
2529import com.amplifyframework.ui.testing.CoroutineTestRule
@@ -50,15 +54,16 @@ class AuthenticatorViewModelTest {
5054
5155 @Before
5256 fun setup () {
53- coEvery { authProvider.getConfiguration() } returns AuthConfigurationResult .Valid (mockk(relaxed = true ))
57+ coEvery { authProvider.getConfiguration() } returns mockAmplifyAuthConfiguration()
58+ coEvery { authProvider.getCurrentUser() } returns Success (mockUser())
5459 }
5560
5661// region start tests
5762
5863 @Test
5964 fun `start only executes once` () = runTest {
60- viewModel.start(mockAuthConfiguration ())
61- viewModel.start(mockAuthConfiguration ())
65+ viewModel.start(mockAuthenticatorConfiguration ())
66+ viewModel.start(mockAuthenticatorConfiguration ())
6267 advanceUntilIdle()
6368
6469 // fetchAuthSession only called by the first start
@@ -71,7 +76,7 @@ class AuthenticatorViewModelTest {
7176 fun `missing configuration results in an error` () = runTest {
7277 coEvery { authProvider.getConfiguration() } returns AuthConfigurationResult .Missing
7378
74- viewModel.start(mockAuthConfiguration ())
79+ viewModel.start(mockAuthenticatorConfiguration ())
7580 advanceUntilIdle()
7681
7782 coVerify(exactly = 0 ) { authProvider.fetchAuthSession() }
@@ -82,7 +87,7 @@ class AuthenticatorViewModelTest {
8287 fun `invalid configuration results in an error` () = runTest {
8388 coEvery { authProvider.getConfiguration() } returns AuthConfigurationResult .Invalid (" Invalid" )
8489
85- viewModel.start(mockAuthConfiguration ())
90+ viewModel.start(mockAuthenticatorConfiguration ())
8691 advanceUntilIdle()
8792
8893 coVerify(exactly = 0 ) { authProvider.fetchAuthSession() }
@@ -93,7 +98,7 @@ class AuthenticatorViewModelTest {
9398 fun `fetchAuthSession error during start results in an error` () = runTest {
9499 coEvery { authProvider.fetchAuthSession() } returns AmplifyResult .Error (mockAuthException())
95100
96- viewModel.start(mockAuthConfiguration ())
101+ viewModel.start(mockAuthenticatorConfiguration ())
97102 advanceUntilIdle()
98103
99104 coVerify(exactly = 1 ) { authProvider.fetchAuthSession() }
@@ -102,10 +107,10 @@ class AuthenticatorViewModelTest {
102107
103108 @Test
104109 fun `getCurrentUser error during start results in an error` () = runTest {
105- coEvery { authProvider.fetchAuthSession() } returns AmplifyResult . Success (mockAuthSession(isSignedIn = true ))
110+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = true ))
106111 coEvery { authProvider.getCurrentUser() } returns AmplifyResult .Error (mockAuthException())
107112
108- viewModel.start(mockAuthConfiguration ())
113+ viewModel.start(mockAuthenticatorConfiguration ())
109114 advanceUntilIdle()
110115
111116 coVerify(exactly = 1 ) {
@@ -117,10 +122,10 @@ class AuthenticatorViewModelTest {
117122
118123 @Test
119124 fun `when already signed in during start the initial state should be signed in` () = runTest {
120- coEvery { authProvider.fetchAuthSession() } returns AmplifyResult . Success (mockAuthSession(isSignedIn = true ))
121- coEvery { authProvider.getCurrentUser() } returns AmplifyResult . Success (mockAuthUser())
125+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = true ))
126+ coEvery { authProvider.getCurrentUser() } returns Success (mockAuthUser())
122127
123- viewModel.start(mockAuthConfiguration ())
128+ viewModel.start(mockAuthenticatorConfiguration ())
124129 advanceUntilIdle()
125130
126131 coVerify(exactly = 1 ) {
@@ -132,9 +137,9 @@ class AuthenticatorViewModelTest {
132137
133138 @Test
134139 fun `initial step is SignIn` () = runTest {
135- coEvery { authProvider.fetchAuthSession() } returns AmplifyResult . Success (mockAuthSession(isSignedIn = false ))
140+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
136141
137- viewModel.start(mockAuthConfiguration (initialStep = AuthenticatorStep .SignIn ))
142+ viewModel.start(mockAuthenticatorConfiguration (initialStep = AuthenticatorStep .SignIn ))
138143 advanceUntilIdle()
139144
140145 viewModel.currentStep shouldBe AuthenticatorStep .SignIn
@@ -145,97 +150,179 @@ class AuthenticatorViewModelTest {
145150
146151 @Test
147152 fun `TOTPSetup next step shows error if totpSetupDetails is null` () = runTest {
148- coEvery { authProvider.fetchAuthSession() } returns AmplifyResult . Success (mockAuthSession(isSignedIn = false ))
149- coEvery { authProvider.signIn(any(), any()) } returns AmplifyResult . Success (
153+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
154+ coEvery { authProvider.signIn(any(), any()) } returns Success (
150155 mockSignInResult(
151156 signInStep = AuthSignInStep .CONTINUE_SIGN_IN_WITH_TOTP_SETUP ,
152157 totpSetupDetails = null
153158 )
154159 )
155160
156- viewModel.start(mockAuthConfiguration (initialStep = AuthenticatorStep .SignIn ))
161+ viewModel.start(mockAuthenticatorConfiguration (initialStep = AuthenticatorStep .SignIn ))
157162
158163 viewModel.signIn(" username" , " password" )
159164 viewModel.currentStep shouldBe AuthenticatorStep .Error
160165 }
161166
162167 @Test
163168 fun `TOTPSetup next step shows SignInContinueWithTotpSetup screen` () = runTest {
164- coEvery { authProvider.fetchAuthSession() } returns AmplifyResult . Success (mockAuthSession(isSignedIn = false ))
165- coEvery { authProvider.signIn(any(), any()) } returns AmplifyResult . Success (
169+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
170+ coEvery { authProvider.signIn(any(), any()) } returns Success (
166171 mockSignInResult(
167172 signInStep = AuthSignInStep .CONTINUE_SIGN_IN_WITH_TOTP_SETUP ,
168173 totpSetupDetails = mockk(relaxed = true )
169174 )
170175 )
171176
172- viewModel.start(mockAuthConfiguration (initialStep = AuthenticatorStep .SignIn ))
177+ viewModel.start(mockAuthenticatorConfiguration (initialStep = AuthenticatorStep .SignIn ))
173178
174179 viewModel.signIn(" username" , " password" )
175180 viewModel.currentStep shouldBe AuthenticatorStep .SignInContinueWithTotpSetup
176181 }
177182
178183 @Test
179184 fun `TOTP Code next step shows the SignInConfirmTotpCode screen` () = runTest {
180- coEvery { authProvider.fetchAuthSession() } returns AmplifyResult . Success (mockAuthSession(isSignedIn = false ))
181- coEvery { authProvider.signIn(any(), any()) } returns AmplifyResult . Success (
185+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
186+ coEvery { authProvider.signIn(any(), any()) } returns Success (
182187 mockSignInResult(signInStep = AuthSignInStep .CONFIRM_SIGN_IN_WITH_TOTP_CODE )
183188 )
184189
185- viewModel.start(mockAuthConfiguration (initialStep = AuthenticatorStep .SignIn ))
190+ viewModel.start(mockAuthenticatorConfiguration (initialStep = AuthenticatorStep .SignIn ))
186191
187192 viewModel.signIn(" username" , " password" )
188193 viewModel.currentStep shouldBe AuthenticatorStep .SignInConfirmTotpCode
189194 }
190195
191196 @Test
192197 fun `MFA selection next step shows error if allowedMFATypes is null` () = runTest {
193- coEvery { authProvider.fetchAuthSession() } returns AmplifyResult . Success (mockAuthSession(isSignedIn = false ))
194- coEvery { authProvider.signIn(any(), any()) } returns AmplifyResult . Success (
198+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
199+ coEvery { authProvider.signIn(any(), any()) } returns Success (
195200 mockSignInResult(
196201 signInStep = AuthSignInStep .CONTINUE_SIGN_IN_WITH_MFA_SELECTION ,
197202 allowedMFATypes = null
198203 )
199204 )
200205
201- viewModel.start(mockAuthConfiguration (initialStep = AuthenticatorStep .SignIn ))
206+ viewModel.start(mockAuthenticatorConfiguration (initialStep = AuthenticatorStep .SignIn ))
202207
203208 viewModel.signIn(" username" , " password" )
204209 viewModel.currentStep shouldBe AuthenticatorStep .Error
205210 }
206211
207212 @Test
208213 fun `MFA selection next step shows error if allowedMFATypes is empty` () = runTest {
209- coEvery { authProvider.fetchAuthSession() } returns AmplifyResult . Success (mockAuthSession(isSignedIn = false ))
210- coEvery { authProvider.signIn(any(), any()) } returns AmplifyResult . Success (
214+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
215+ coEvery { authProvider.signIn(any(), any()) } returns Success (
211216 mockSignInResult(
212217 signInStep = AuthSignInStep .CONTINUE_SIGN_IN_WITH_MFA_SELECTION ,
213218 allowedMFATypes = emptySet()
214219 )
215220 )
216221
217- viewModel.start(mockAuthConfiguration (initialStep = AuthenticatorStep .SignIn ))
222+ viewModel.start(mockAuthenticatorConfiguration (initialStep = AuthenticatorStep .SignIn ))
218223
219224 viewModel.signIn(" username" , " password" )
220225 viewModel.currentStep shouldBe AuthenticatorStep .Error
221226 }
222227
223228 @Test
224229 fun `MFA Selection next step shows the SignInContinueWithMfaSelection screen` () = runTest {
225- coEvery { authProvider.fetchAuthSession() } returns AmplifyResult . Success (mockAuthSession(isSignedIn = false ))
226- coEvery { authProvider.signIn(any(), any()) } returns AmplifyResult . Success (
230+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
231+ coEvery { authProvider.signIn(any(), any()) } returns Success (
227232 mockSignInResult(
228233 signInStep = AuthSignInStep .CONTINUE_SIGN_IN_WITH_MFA_SELECTION ,
229234 allowedMFATypes = setOf (MFAType .TOTP , MFAType .SMS )
230235 )
231236 )
232237
233- viewModel.start(mockAuthConfiguration (initialStep = AuthenticatorStep .SignIn ))
238+ viewModel.start(mockAuthenticatorConfiguration (initialStep = AuthenticatorStep .SignIn ))
234239
235240 viewModel.signIn(" username" , " password" )
236241 viewModel.currentStep shouldBe AuthenticatorStep .SignInContinueWithMfaSelection
237242 }
238243
244+ @Test
245+ fun `user attribute verification screen is shown if user has no verified attributes` () = runTest {
246+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
247+ coEvery { authProvider.signIn(any(), any()) } returns Success (mockSignInResult())
248+ coEvery { authProvider.getConfiguration() } returns mockAmplifyAuthConfiguration(
249+ verificationMechanisms = setOf (VerificationMechanism .Email )
250+ )
251+ coEvery { authProvider.fetchUserAttributes() } returns Success (
252+ mockUserAttributes(email() to " email" , emailVerified() to " false" )
253+ )
254+
255+ viewModel.start(mockAuthenticatorConfiguration())
256+ viewModel.signIn(" username" , " password" )
257+
258+ viewModel.currentStep shouldBe AuthenticatorStep .VerifyUser
259+ }
260+
261+ @Test
262+ fun `user attribute verification screen is not shown if user has verified attributes` () = runTest {
263+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
264+ coEvery { authProvider.signIn(any(), any()) } returns Success (mockSignInResult())
265+ coEvery { authProvider.getConfiguration() } returns mockAmplifyAuthConfiguration(
266+ verificationMechanisms = setOf (VerificationMechanism .Email )
267+ )
268+ coEvery { authProvider.fetchUserAttributes() } returns Success (
269+ mockUserAttributes(email() to " email" , emailVerified() to " true" ) // email is already verified
270+ )
271+
272+ viewModel.start(mockAuthenticatorConfiguration())
273+ viewModel.signIn(" username" , " password" )
274+
275+ viewModel.currentStep shouldBe AuthenticatorStep .SignedIn
276+ }
277+
278+ @Test
279+ fun `user attribute verification screen is not shown if there are no verification mechanisms` () = runTest {
280+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
281+ coEvery { authProvider.signIn(any(), any()) } returns Success (mockSignInResult())
282+ coEvery { authProvider.getConfiguration() } returns mockAmplifyAuthConfiguration(
283+ verificationMechanisms = emptySet() // no verification mechanisms
284+ )
285+ coEvery { authProvider.fetchUserAttributes() } returns Success (
286+ mockUserAttributes(email() to " email" , emailVerified() to " false" )
287+ )
288+
289+ viewModel.start(mockAuthenticatorConfiguration())
290+ viewModel.signIn(" username" , " password" )
291+
292+ viewModel.currentStep shouldBe AuthenticatorStep .SignedIn
293+ }
294+
295+ @Test
296+ fun `user attribute verification screen is not shown if cannot fetch user attributes` () = runTest {
297+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
298+ coEvery { authProvider.signIn(any(), any()) } returns Success (mockSignInResult())
299+ coEvery { authProvider.getConfiguration() } returns mockAmplifyAuthConfiguration(
300+ verificationMechanisms = setOf (VerificationMechanism .Email )
301+ )
302+ // cannot fetch user attributes
303+ coEvery { authProvider.fetchUserAttributes() } returns AmplifyResult .Error (mockk(relaxed = true ))
304+
305+ viewModel.start(mockAuthenticatorConfiguration())
306+ viewModel.signIn(" username" , " password" )
307+
308+ viewModel.currentStep shouldBe AuthenticatorStep .SignedIn
309+ }
310+
311+ @Test
312+ fun `user attribute verification screen is not shown if user does not have the required attributes` () = runTest {
313+ coEvery { authProvider.fetchAuthSession() } returns Success (mockAuthSession(isSignedIn = false ))
314+ coEvery { authProvider.signIn(any(), any()) } returns Success (mockSignInResult())
315+ coEvery { authProvider.getConfiguration() } returns mockAmplifyAuthConfiguration(
316+ verificationMechanisms = setOf (VerificationMechanism .Email )
317+ )
318+ coEvery { authProvider.fetchUserAttributes() } returns Success (mockUserAttributes()) // no email attribute
319+
320+ viewModel.start(mockAuthenticatorConfiguration())
321+ viewModel.signIn(" username" , " password" )
322+
323+ viewModel.currentStep shouldBe AuthenticatorStep .SignedIn
324+ }
325+
239326// endregion
240327// region helpers
241328 private val AuthenticatorViewModel .currentStep: AuthenticatorStep
0 commit comments