Skip to content

Commit c80b763

Browse files
committed
unit tests for anonymous sign in
1 parent 1c1efb2 commit c80b763

File tree

2 files changed

+434
-125
lines changed

2 files changed

+434
-125
lines changed
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
/*
2+
* Copyright 2025 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
* express or implied. See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.firebase.ui.auth.compose.configuration.auth_provider
16+
17+
import android.content.Context
18+
import androidx.test.core.app.ApplicationProvider
19+
import com.firebase.ui.auth.compose.AuthException
20+
import com.firebase.ui.auth.compose.AuthState
21+
import com.firebase.ui.auth.compose.FirebaseAuthUI
22+
import com.firebase.ui.auth.compose.configuration.authUIConfiguration
23+
import com.google.android.gms.tasks.TaskCompletionSource
24+
import com.google.common.truth.Truth.assertThat
25+
import com.google.firebase.FirebaseApp
26+
import com.google.firebase.FirebaseNetworkException
27+
import com.google.firebase.FirebaseOptions
28+
import com.google.firebase.auth.AuthCredential
29+
import com.google.firebase.auth.AuthResult
30+
import com.google.firebase.auth.EmailAuthProvider
31+
import com.google.firebase.auth.FirebaseAuth
32+
import com.google.firebase.auth.FirebaseAuthUserCollisionException
33+
import com.google.firebase.auth.FirebaseUser
34+
import kotlinx.coroutines.CancellationException
35+
import kotlinx.coroutines.flow.first
36+
import kotlinx.coroutines.test.runTest
37+
import org.junit.After
38+
import org.junit.Before
39+
import org.junit.Test
40+
import org.junit.runner.RunWith
41+
import org.mockito.ArgumentMatchers
42+
import org.mockito.Mock
43+
import org.mockito.Mockito.mock
44+
import org.mockito.Mockito.verify
45+
import org.mockito.Mockito.`when`
46+
import org.mockito.MockitoAnnotations
47+
import org.robolectric.RobolectricTestRunner
48+
import org.robolectric.annotation.Config
49+
50+
@RunWith(RobolectricTestRunner::class)
51+
@Config(manifest = Config.NONE)
52+
class AnonymousAuthProviderFirebaseAuthUITest {
53+
54+
@Mock
55+
private lateinit var mockFirebaseAuth: FirebaseAuth
56+
57+
private lateinit var firebaseApp: FirebaseApp
58+
private lateinit var applicationContext: Context
59+
60+
@Before
61+
fun setUp() {
62+
MockitoAnnotations.openMocks(this)
63+
64+
FirebaseAuthUI.clearInstanceCache()
65+
66+
applicationContext = ApplicationProvider.getApplicationContext()
67+
68+
FirebaseApp.getApps(applicationContext).forEach { app ->
69+
app.delete()
70+
}
71+
72+
firebaseApp = FirebaseApp.initializeApp(
73+
applicationContext,
74+
FirebaseOptions.Builder()
75+
.setApiKey("fake-api-key")
76+
.setApplicationId("fake-app-id")
77+
.setProjectId("fake-project-id")
78+
.build()
79+
)
80+
}
81+
82+
@After
83+
fun tearDown() {
84+
FirebaseAuthUI.clearInstanceCache()
85+
try {
86+
firebaseApp.delete()
87+
} catch (_: Exception) {
88+
// Ignore if already deleted
89+
}
90+
}
91+
92+
// =============================================================================================
93+
// signInAnonymously Tests
94+
// =============================================================================================
95+
96+
@Test
97+
fun `signInAnonymously - successful anonymous sign in`() = runTest {
98+
val mockUser = mock(FirebaseUser::class.java)
99+
`when`(mockUser.isAnonymous).thenReturn(true)
100+
val mockAuthResult = mock(AuthResult::class.java)
101+
`when`(mockAuthResult.user).thenReturn(mockUser)
102+
val taskCompletionSource = TaskCompletionSource<AuthResult>()
103+
taskCompletionSource.setResult(mockAuthResult)
104+
`when`(mockFirebaseAuth.signInAnonymously())
105+
.thenReturn(taskCompletionSource.task)
106+
107+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
108+
109+
instance.signInAnonymously()
110+
111+
verify(mockFirebaseAuth).signInAnonymously()
112+
113+
val finalState = instance.authStateFlow().first { it is AuthState.Idle }
114+
assertThat(finalState).isInstanceOf(AuthState.Idle::class.java)
115+
}
116+
117+
@Test
118+
fun `signInAnonymously - handles network error`() = runTest {
119+
val networkException = FirebaseNetworkException("Network error")
120+
val taskCompletionSource = TaskCompletionSource<AuthResult>()
121+
taskCompletionSource.setException(networkException)
122+
`when`(mockFirebaseAuth.signInAnonymously())
123+
.thenReturn(taskCompletionSource.task)
124+
125+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
126+
127+
try {
128+
instance.signInAnonymously()
129+
assertThat(false).isTrue() // Should not reach here
130+
} catch (e: AuthException.NetworkException) {
131+
assertThat(e.cause).isEqualTo(networkException)
132+
}
133+
134+
val currentState = instance.authStateFlow().first { it is AuthState.Error }
135+
assertThat(currentState).isInstanceOf(AuthState.Error::class.java)
136+
val errorState = currentState as AuthState.Error
137+
assertThat(errorState.exception).isInstanceOf(AuthException.NetworkException::class.java)
138+
}
139+
140+
@Test
141+
fun `signInAnonymously - handles cancellation`() = runTest {
142+
val cancellationException = CancellationException("Operation cancelled")
143+
val taskCompletionSource = TaskCompletionSource<AuthResult>()
144+
taskCompletionSource.setException(cancellationException)
145+
`when`(mockFirebaseAuth.signInAnonymously())
146+
.thenReturn(taskCompletionSource.task)
147+
148+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
149+
150+
try {
151+
instance.signInAnonymously()
152+
assertThat(false).isTrue() // Should not reach here
153+
} catch (e: AuthException.AuthCancelledException) {
154+
assertThat(e.message).contains("cancelled")
155+
assertThat(e.cause).isInstanceOf(CancellationException::class.java)
156+
}
157+
158+
val currentState = instance.authStateFlow().first { it is AuthState.Error }
159+
assertThat(currentState).isInstanceOf(AuthState.Error::class.java)
160+
val errorState = currentState as AuthState.Error
161+
assertThat(errorState.exception).isInstanceOf(AuthException.AuthCancelledException::class.java)
162+
}
163+
164+
@Test
165+
fun `signInAnonymously - handles generic exception`() = runTest {
166+
val genericException = RuntimeException("Something went wrong")
167+
val taskCompletionSource = TaskCompletionSource<AuthResult>()
168+
taskCompletionSource.setException(genericException)
169+
`when`(mockFirebaseAuth.signInAnonymously())
170+
.thenReturn(taskCompletionSource.task)
171+
172+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
173+
174+
try {
175+
instance.signInAnonymously()
176+
assertThat(false).isTrue() // Should not reach here
177+
} catch (e: AuthException.UnknownException) {
178+
assertThat(e.cause).isEqualTo(genericException)
179+
}
180+
181+
val currentState = instance.authStateFlow().first { it is AuthState.Error }
182+
assertThat(currentState).isInstanceOf(AuthState.Error::class.java)
183+
val errorState = currentState as AuthState.Error
184+
assertThat(errorState.exception).isInstanceOf(AuthException.UnknownException::class.java)
185+
}
186+
187+
// =============================================================================================
188+
// Anonymous Account Upgrade Tests
189+
// =============================================================================================
190+
191+
@Test
192+
fun `Upgrade anonymous account with email and password when isAnonymousUpgradeEnabled`() = runTest {
193+
val mockAnonymousUser = mock(FirebaseUser::class.java)
194+
`when`(mockAnonymousUser.isAnonymous).thenReturn(true)
195+
`when`(mockFirebaseAuth.currentUser).thenReturn(mockAnonymousUser)
196+
197+
val taskCompletionSource = TaskCompletionSource<AuthResult>()
198+
taskCompletionSource.setResult(mock(AuthResult::class.java))
199+
`when`(mockAnonymousUser.linkWithCredential(ArgumentMatchers.any(AuthCredential::class.java)))
200+
.thenReturn(taskCompletionSource.task)
201+
202+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
203+
val emailProvider = AuthProvider.Email(
204+
emailLinkActionCodeSettings = null,
205+
passwordValidationRules = emptyList()
206+
)
207+
val config = authUIConfiguration {
208+
context = applicationContext
209+
providers {
210+
provider(AuthProvider.Anonymous)
211+
provider(emailProvider)
212+
}
213+
isAnonymousUpgradeEnabled = true
214+
}
215+
216+
instance.createOrLinkUserWithEmailAndPassword(
217+
context = applicationContext,
218+
config = config,
219+
provider = emailProvider,
220+
name = null,
221+
email = "[email protected]",
222+
password = "Pass@123"
223+
)
224+
225+
verify(mockAnonymousUser).linkWithCredential(ArgumentMatchers.any(AuthCredential::class.java))
226+
}
227+
228+
@Test
229+
fun `Upgrade anonymous account throws AccountLinkingRequiredException on collision`() = runTest {
230+
val mockAnonymousUser = mock(FirebaseUser::class.java)
231+
`when`(mockAnonymousUser.isAnonymous).thenReturn(true)
232+
`when`(mockAnonymousUser.email).thenReturn(null)
233+
`when`(mockFirebaseAuth.currentUser).thenReturn(mockAnonymousUser)
234+
235+
val collisionException = mock(FirebaseAuthUserCollisionException::class.java)
236+
`when`(collisionException.errorCode).thenReturn("ERROR_EMAIL_ALREADY_IN_USE")
237+
`when`(collisionException.email).thenReturn("[email protected]")
238+
239+
val taskCompletionSource = TaskCompletionSource<AuthResult>()
240+
taskCompletionSource.setException(collisionException)
241+
`when`(mockAnonymousUser.linkWithCredential(ArgumentMatchers.any(AuthCredential::class.java)))
242+
.thenReturn(taskCompletionSource.task)
243+
244+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
245+
val emailProvider = AuthProvider.Email(
246+
emailLinkActionCodeSettings = null,
247+
passwordValidationRules = emptyList()
248+
)
249+
val config = authUIConfiguration {
250+
context = applicationContext
251+
providers {
252+
provider(AuthProvider.Anonymous)
253+
provider(emailProvider)
254+
}
255+
isAnonymousUpgradeEnabled = true
256+
}
257+
258+
try {
259+
instance.createOrLinkUserWithEmailAndPassword(
260+
context = applicationContext,
261+
config = config,
262+
provider = emailProvider,
263+
name = null,
264+
email = "[email protected]",
265+
password = "Pass@123"
266+
)
267+
assertThat(false).isTrue() // Should not reach here
268+
} catch (e: AuthException.AccountLinkingRequiredException) {
269+
assertThat(e.cause).isEqualTo(collisionException)
270+
assertThat(e.email).isEqualTo("[email protected]")
271+
assertThat(e.credential).isNotNull()
272+
}
273+
274+
val currentState = instance.authStateFlow().first { it is AuthState.Error }
275+
assertThat(currentState).isInstanceOf(AuthState.Error::class.java)
276+
val errorState = currentState as AuthState.Error
277+
assertThat(errorState.exception).isInstanceOf(AuthException.AccountLinkingRequiredException::class.java)
278+
}
279+
280+
@Test
281+
fun `Upgrade anonymous account with credential when isAnonymousUpgradeEnabled`() = runTest {
282+
val mockAnonymousUser = mock(FirebaseUser::class.java)
283+
`when`(mockAnonymousUser.isAnonymous).thenReturn(true)
284+
`when`(mockFirebaseAuth.currentUser).thenReturn(mockAnonymousUser)
285+
286+
val credential = EmailAuthProvider.getCredential("[email protected]", "Pass@123")
287+
val mockAuthResult = mock(AuthResult::class.java)
288+
`when`(mockAuthResult.user).thenReturn(mockAnonymousUser)
289+
val taskCompletionSource = TaskCompletionSource<AuthResult>()
290+
taskCompletionSource.setResult(mockAuthResult)
291+
`when`(mockAnonymousUser.linkWithCredential(credential))
292+
.thenReturn(taskCompletionSource.task)
293+
294+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
295+
val emailProvider = AuthProvider.Email(
296+
emailLinkActionCodeSettings = null,
297+
passwordValidationRules = emptyList()
298+
)
299+
val config = authUIConfiguration {
300+
context = applicationContext
301+
providers {
302+
provider(AuthProvider.Anonymous)
303+
provider(emailProvider)
304+
}
305+
isAnonymousUpgradeEnabled = true
306+
}
307+
308+
val result = instance.signInAndLinkWithCredential(
309+
config = config,
310+
credential = credential
311+
)
312+
313+
assertThat(result).isNotNull()
314+
verify(mockAnonymousUser).linkWithCredential(credential)
315+
}
316+
}

0 commit comments

Comments
 (0)