Skip to content

Commit f3f49c7

Browse files
committed
add account linking error dialog test
1 parent 5aae024 commit f3f49c7

File tree

3 files changed

+132
-2
lines changed

3 files changed

+132
-2
lines changed

auth/src/main/java/com/firebase/ui/auth/compose/configuration/auth_provider/EmailAuthProvider+FirebaseAuthUI.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ internal suspend fun FirebaseAuthUI.createOrLinkUserWithEmailAndPassword(
166166
val accountLinkingException = AuthException.AccountLinkingRequiredException(
167167
message = "An account already exists with this email. " +
168168
"Please sign in with your existing account.",
169-
email = e.email,
169+
email = e.email ?: email,
170170
credential = if (canUpgrade) {
171171
e.updatedCredential ?: pendingCredential
172172
} else {

auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/email/EmailAuthScreen.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,9 @@ fun EmailAuthScreen(
263263
}
264264
)
265265

266-
if (isErrorDialogVisible.value) {
266+
if (isErrorDialogVisible.value &&
267+
(authState as AuthState.Error).exception !is AuthException.AccountLinkingRequiredException
268+
) {
267269
ErrorRecoveryDialog(
268270
error = when ((authState as AuthState.Error).exception) {
269271
is AuthException -> (authState as AuthState.Error).exception as AuthException

e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/AnonymousAuthScreenTest.kt

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringPr
3333
import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider
3434
import com.firebase.ui.auth.compose.testutil.AUTH_STATE_WAIT_TIMEOUT_MS
3535
import com.firebase.ui.auth.compose.testutil.EmulatorAuthApi
36+
import com.firebase.ui.auth.compose.testutil.ensureFreshUser
3637
import com.google.common.truth.Truth.assertThat
3738
import com.google.firebase.FirebaseApp
3839
import com.google.firebase.FirebaseOptions
@@ -275,6 +276,133 @@ class AnonymousAuthScreenTest {
275276
.isEqualTo(email)
276277
}
277278

279+
@Test
280+
fun `anonymous upgrade with existing email shows dialog with AccountLinking message`() {
281+
val name = "Existing User"
282+
val email = "[email protected]"
283+
val password = "Password123!"
284+
285+
// Step 1: Create an email/password account first
286+
println("TEST: Creating email/password account...")
287+
ensureFreshUser(authUI, email, password)
288+
println("TEST: Email/password account created")
289+
290+
// Step 2: Sign out
291+
authUI.auth.signOut()
292+
shadowOf(Looper.getMainLooper()).idle()
293+
assertThat(authUI.auth.currentUser).isNull()
294+
println("TEST: Signed out, but email account still exists in emulator")
295+
296+
// Step 3: Sign in anonymously
297+
val configuration = authUIConfiguration {
298+
context = applicationContext
299+
providers {
300+
provider(AuthProvider.Anonymous)
301+
provider(
302+
AuthProvider.Email(
303+
emailLinkActionCodeSettings = null,
304+
passwordValidationRules = emptyList()
305+
)
306+
)
307+
}
308+
isAnonymousUpgradeEnabled = true
309+
}
310+
311+
var currentAuthState: AuthState = AuthState.Idle
312+
313+
composeTestRule.setContent {
314+
TestAuthScreen(configuration = configuration)
315+
val authState by authUI.authStateFlow().collectAsState(AuthState.Idle)
316+
currentAuthState = authState
317+
}
318+
319+
composeTestRule.waitForIdle()
320+
shadowOf(Looper.getMainLooper()).idle()
321+
322+
println("TEST: Clicking anonymous sign-in button...")
323+
composeTestRule.onNodeWithText(stringProvider.signInAnonymously)
324+
.assertIsDisplayed()
325+
.performClick()
326+
327+
composeTestRule.waitForIdle()
328+
shadowOf(Looper.getMainLooper()).idle()
329+
330+
// Wait for anonymous auth to complete
331+
println("TEST: Waiting for anonymous auth...")
332+
composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) {
333+
shadowOf(Looper.getMainLooper()).idle()
334+
println("TEST: Auth state: $currentAuthState")
335+
currentAuthState is AuthState.Success
336+
}
337+
338+
assertThat(authUI.auth.currentUser!!.isAnonymous).isTrue()
339+
val anonymousUserUID = authUI.auth.currentUser!!.uid
340+
println("TEST: Anonymous user UID: $anonymousUserUID")
341+
342+
// Step 4: Try to upgrade with existing email
343+
println("TEST: Clicking 'Upgrade with Email' button...")
344+
composeTestRule.onNodeWithText("Upgrade with Email")
345+
.assertIsDisplayed()
346+
.performClick()
347+
348+
composeTestRule.waitForIdle()
349+
shadowOf(Looper.getMainLooper()).idle()
350+
351+
// Navigate to sign-up
352+
println("TEST: Navigating to sign-up...")
353+
composeTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase())
354+
.assertIsDisplayed()
355+
.performClick()
356+
357+
// Enter the existing email
358+
println("TEST: Entering existing email...")
359+
composeTestRule.onNodeWithText(stringProvider.emailHint)
360+
.assertIsDisplayed()
361+
.performTextInput(email)
362+
composeTestRule.onNodeWithText(stringProvider.nameHint)
363+
.assertIsDisplayed()
364+
.performTextInput(name)
365+
composeTestRule.onNodeWithText(stringProvider.passwordHint)
366+
.performScrollTo()
367+
.assertIsDisplayed()
368+
.performTextInput(password)
369+
composeTestRule.onNodeWithText(stringProvider.confirmPasswordHint)
370+
.performScrollTo()
371+
.assertIsDisplayed()
372+
.performTextInput(password)
373+
374+
// Click sign-up button
375+
println("TEST: Clicking sign-up button...")
376+
composeTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase())
377+
.performScrollTo()
378+
.assertIsDisplayed()
379+
.performClick()
380+
381+
composeTestRule.waitForIdle()
382+
shadowOf(Looper.getMainLooper()).idle()
383+
384+
// Step 5: Wait for error state (AccountLinkingRequiredException)
385+
println("TEST: Waiting for AccountLinkingRequiredException...")
386+
composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) {
387+
shadowOf(Looper.getMainLooper()).idle()
388+
println("TEST: Auth state: $currentAuthState")
389+
currentAuthState is AuthState.Error
390+
}
391+
392+
// Step 6: Verify ErrorRecoveryDialog is displayed
393+
println("TEST: Verifying ErrorRecoveryDialog is displayed...")
394+
composeTestRule.onNodeWithText(stringProvider.errorDialogTitle)
395+
.assertIsDisplayed()
396+
397+
// Verify exception
398+
assertThat(currentAuthState).isInstanceOf(AuthState.Error::class.java)
399+
val errorState = currentAuthState as AuthState.Error
400+
assertThat(errorState.exception).isInstanceOf(AuthException.AccountLinkingRequiredException::class.java)
401+
402+
val linkingException = errorState.exception as AuthException.AccountLinkingRequiredException
403+
assertThat(linkingException.email).isEqualTo(email)
404+
}
405+
278406
@Composable
279407
private fun TestAuthScreen(configuration: AuthUIConfiguration) {
280408
composeTestRule.waitForIdle()

0 commit comments

Comments
 (0)