Skip to content

Commit d68bfc2

Browse files
committed
facebook sign tests
1 parent b0c15b1 commit d68bfc2

File tree

1 file changed

+280
-0
lines changed

1 file changed

+280
-0
lines changed
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
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 android.net.Uri
19+
import androidx.test.core.app.ApplicationProvider
20+
import com.facebook.AccessToken
21+
import com.facebook.FacebookException
22+
import com.firebase.ui.auth.compose.AuthException
23+
import com.firebase.ui.auth.compose.AuthState
24+
import com.firebase.ui.auth.compose.FirebaseAuthUI
25+
import com.firebase.ui.auth.compose.configuration.authUIConfiguration
26+
import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider
27+
import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider.Facebook.FacebookProfileData
28+
import com.firebase.ui.auth.compose.util.EmailLinkPersistenceManager
29+
import com.google.android.gms.tasks.TaskCompletionSource
30+
import com.google.common.truth.Truth.assertThat
31+
import com.google.firebase.FirebaseApp
32+
import com.google.firebase.FirebaseOptions
33+
import com.google.firebase.auth.AuthCredential
34+
import com.google.firebase.auth.AuthResult
35+
import com.google.firebase.auth.FirebaseAuth
36+
import com.google.firebase.auth.FirebaseUser
37+
import com.google.firebase.auth.FirebaseAuth.AuthStateListener
38+
import com.google.firebase.auth.FirebaseAuthUserCollisionException
39+
import kotlinx.coroutines.async
40+
import kotlinx.coroutines.flow.first
41+
import kotlinx.coroutines.test.runTest
42+
import org.junit.After
43+
import org.junit.Before
44+
import org.junit.Test
45+
import org.junit.runner.RunWith
46+
import org.mockito.Mock
47+
import org.mockito.Mockito.verify
48+
import org.mockito.Mockito.`when`
49+
import org.mockito.MockitoAnnotations
50+
import org.mockito.kotlin.any
51+
import org.mockito.kotlin.doAnswer
52+
import org.mockito.kotlin.doReturn
53+
import org.mockito.kotlin.doThrow
54+
import org.mockito.kotlin.mock
55+
import org.mockito.kotlin.spy
56+
import org.mockito.kotlin.whenever
57+
import org.robolectric.RobolectricTestRunner
58+
import org.robolectric.annotation.Config
59+
60+
/**
61+
* Unit tests for Facebook Authentication provider methods in FirebaseAuthUI.
62+
**/
63+
@RunWith(RobolectricTestRunner::class)
64+
@Config(manifest = Config.NONE)
65+
class FacebookAuthProviderFirebaseAuthUITest {
66+
67+
@Mock
68+
private lateinit var mockFirebaseAuth: FirebaseAuth
69+
70+
@Mock
71+
private lateinit var mockFBAuthCredentialProvider: AuthProvider.Facebook.CredentialProvider
72+
73+
private lateinit var firebaseApp: FirebaseApp
74+
private lateinit var applicationContext: Context
75+
76+
@Before
77+
fun setUp() {
78+
MockitoAnnotations.openMocks(this)
79+
80+
FirebaseAuthUI.clearInstanceCache()
81+
82+
applicationContext = ApplicationProvider.getApplicationContext()
83+
84+
FirebaseApp.getApps(applicationContext).forEach { app ->
85+
app.delete()
86+
}
87+
88+
firebaseApp = FirebaseApp.initializeApp(
89+
applicationContext,
90+
FirebaseOptions.Builder()
91+
.setApiKey("fake-api-key")
92+
.setApplicationId("fake-app-id")
93+
.setProjectId("fake-project-id")
94+
.build()
95+
)
96+
}
97+
98+
@After
99+
fun tearDown() {
100+
FirebaseAuthUI.clearInstanceCache()
101+
try {
102+
firebaseApp.delete()
103+
} catch (_: Exception) {
104+
// Ignore if already deleted
105+
}
106+
}
107+
108+
@Test
109+
fun `signInWithFacebook - successful sign in signs user in and emits Success authState`() = runTest {
110+
val authStateListeners = mutableListOf<AuthStateListener>()
111+
doAnswer { invocation ->
112+
val listener = invocation.getArgument<AuthStateListener>(0)
113+
authStateListeners += listener
114+
null
115+
}.whenever(mockFirebaseAuth).addAuthStateListener(any())
116+
doAnswer { invocation ->
117+
val listener = invocation.getArgument<AuthStateListener>(0)
118+
authStateListeners -= listener
119+
null
120+
}.whenever(mockFirebaseAuth).removeAuthStateListener(any())
121+
whenever(mockFirebaseAuth.currentUser).thenReturn(null)
122+
123+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
124+
val provider = spy(AuthProvider.Facebook(
125+
applicationId = "000000000000"
126+
))
127+
val config = authUIConfiguration {
128+
context = applicationContext
129+
providers {
130+
provider(provider)
131+
}
132+
}
133+
134+
val mockAccessToken = mock<AccessToken> {
135+
on { token } doReturn "random-token"
136+
}
137+
val mockCredential = mock<AuthCredential>()
138+
val mockUser = mock<FirebaseUser>()
139+
val mockAuthResult = mock<AuthResult>()
140+
whenever(mockAuthResult.user).thenReturn(mockUser)
141+
whenever(mockUser.isEmailVerified).thenReturn(true)
142+
whenever(mockUser.providerData).thenReturn(emptyList())
143+
val taskCompletionSource = TaskCompletionSource<AuthResult>()
144+
taskCompletionSource.setResult(mockAuthResult)
145+
whenever(mockFirebaseAuth.signInWithCredential(mockCredential))
146+
.thenReturn(taskCompletionSource.task)
147+
doReturn(
148+
FacebookProfileData(
149+
displayName = "Test User",
150+
email = "[email protected]",
151+
photoUrl = Uri.parse("https://someurl.com/photo.png")
152+
)
153+
).whenever(provider).fetchFacebookProfile(any())
154+
whenever(mockFBAuthCredentialProvider.getCredential("random-token"))
155+
.thenReturn(mockCredential)
156+
157+
val successStateDeferred = async {
158+
instance.authStateFlow().first { it is AuthState.Success }
159+
}
160+
161+
instance.signInWithFacebook(
162+
context = applicationContext,
163+
config = config,
164+
provider = provider,
165+
accessToken = mockAccessToken,
166+
credentialProvider = mockFBAuthCredentialProvider
167+
)
168+
169+
whenever(mockFirebaseAuth.currentUser).thenReturn(mockUser)
170+
authStateListeners.forEach { listener ->
171+
listener.onAuthStateChanged(mockFirebaseAuth)
172+
}
173+
174+
val successState = successStateDeferred.await() as AuthState.Success
175+
assertThat(successState.user).isEqualTo(mockUser)
176+
verify(mockFBAuthCredentialProvider).getCredential("random-token")
177+
verify(mockFirebaseAuth).signInWithCredential(mockCredential)
178+
}
179+
180+
@Test
181+
fun `signInWithFacebook - handles account collision by saving credential and emitting error`() = runTest {
182+
EmailLinkPersistenceManager.clear(applicationContext)
183+
EmailLinkPersistenceManager.saveEmail(
184+
context = applicationContext,
185+
email = "[email protected]",
186+
sessionId = "session-id",
187+
anonymousUserId = null
188+
)
189+
190+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
191+
val provider = spy(AuthProvider.Facebook(
192+
applicationId = "000000000000"
193+
))
194+
val config = authUIConfiguration {
195+
context = applicationContext
196+
providers {
197+
provider(provider)
198+
}
199+
}
200+
201+
val mockAccessToken = mock<AccessToken> {
202+
on { token } doReturn "collision-token"
203+
}
204+
val mockCredential = mock<AuthCredential>()
205+
val collisionException = mock<FirebaseAuthUserCollisionException> {
206+
on { errorCode } doReturn "ERROR_ACCOUNT_EXISTS_WITH_DIFFERENT_CREDENTIAL"
207+
on { email } doReturn "[email protected]"
208+
}
209+
val failingTask = TaskCompletionSource<AuthResult>()
210+
failingTask.setException(collisionException)
211+
whenever(mockFirebaseAuth.signInWithCredential(mockCredential))
212+
.thenReturn(failingTask.task)
213+
doReturn(null).whenever(provider).fetchFacebookProfile(any())
214+
whenever(mockFBAuthCredentialProvider.getCredential("collision-token"))
215+
.thenReturn(mockCredential)
216+
217+
try {
218+
instance.signInWithFacebook(
219+
context = applicationContext,
220+
config = config,
221+
provider = provider,
222+
accessToken = mockAccessToken,
223+
credentialProvider = mockFBAuthCredentialProvider
224+
)
225+
assertThat(false).isTrue()
226+
} catch (e: AuthException.AccountLinkingRequiredException) {
227+
assertThat(e.cause).isEqualTo(collisionException)
228+
val currentState = instance.authStateFlow().first { it is AuthState.Error }
229+
assertThat(currentState).isInstanceOf(AuthState.Error::class.java)
230+
val errorState = currentState as AuthState.Error
231+
assertThat(errorState.exception).isInstanceOf(AuthException.AccountLinkingRequiredException::class.java)
232+
233+
val sessionRecord = EmailLinkPersistenceManager.retrieveSessionRecord(applicationContext)
234+
assertThat(sessionRecord).isNotNull()
235+
assertThat(sessionRecord?.credentialForLinking).isNotNull()
236+
assertThat(sessionRecord?.credentialForLinking?.provider)
237+
.isEqualTo(provider.providerId)
238+
} finally {
239+
EmailLinkPersistenceManager.clear(applicationContext)
240+
}
241+
}
242+
243+
@Test
244+
fun `signInWithFacebook - converts FacebookException into AuthException`() = runTest {
245+
val instance = FirebaseAuthUI.create(firebaseApp, mockFirebaseAuth)
246+
val provider = spy(AuthProvider.Facebook(
247+
applicationId = "000000000000"
248+
))
249+
val config = authUIConfiguration {
250+
context = applicationContext
251+
providers {
252+
provider(provider)
253+
}
254+
}
255+
256+
val mockAccessToken = mock<AccessToken> {
257+
on { token } doReturn "error-token"
258+
}
259+
doAnswer {
260+
throw FacebookException("Graph error")
261+
}.whenever(provider).fetchFacebookProfile(any())
262+
263+
try {
264+
instance.signInWithFacebook(
265+
context = applicationContext,
266+
config = config,
267+
provider = provider,
268+
accessToken = mockAccessToken,
269+
credentialProvider = mockFBAuthCredentialProvider
270+
)
271+
assertThat(false).isTrue()
272+
} catch (e: AuthException) {
273+
val currentState = instance.authStateFlow().first { it is AuthState.Error }
274+
assertThat(currentState).isInstanceOf(AuthState.Error::class.java)
275+
val errorState = currentState as AuthState.Error
276+
assertThat(errorState.exception).isEqualTo(e)
277+
assertThat(e).isInstanceOf(AuthException.UnknownException::class.java)
278+
}
279+
}
280+
}

0 commit comments

Comments
 (0)