Skip to content

Commit ce902c1

Browse files
committed
refactor(auth): extract PushTokenProvider into shared/push module
Decouple AuthManager from FirebaseMessaging by introducing a PushTokenProvider interface in a new shared/push module, with the Firebase implementation living in shared/notifications. This eliminates static mocking of FirebaseMessaging in AuthManagerTest.
1 parent c3a2ac3 commit ce902c1

11 files changed

Lines changed: 73 additions & 57 deletions

File tree

apps/flipcash/shared/authentication/build.gradle.kts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,13 @@ android {
77
}
88

99
dependencies {
10-
implementation(platform(libs.firebase.bom))
11-
implementation(libs.firebase.messaging)
12-
implementation(libs.bugsnag)
13-
1410
implementation(libs.androidx.credentials)
1511
implementation(libs.androidx.credentials.play.auth)
1612
implementation(libs.androidx.datastore)
1713

1814
implementation(project(":apps:flipcash:shared:appsettings"))
1915
implementation(project(":apps:flipcash:shared:persistence:provider"))
16+
implementation(project(":apps:flipcash:shared:push"))
2017
implementation(project(":apps:flipcash:shared:featureflags"))
2118
implementation(project(":apps:flipcash:shared:tokens"))
2219
implementation(project(":apps:flipcash:shared:userflags"))

apps/flipcash/shared/authentication/src/main/kotlin/com/flipcash/app/auth/AuthManager.kt

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package com.flipcash.app.auth
22

33
import androidx.core.app.NotificationManagerCompat
4-
import com.bugsnag.android.Bugsnag
54
import com.flipcash.app.appsettings.AppSettingsCoordinator
65
import com.flipcash.app.auth.internal.credentials.LookupResult
76
import com.flipcash.app.auth.internal.credentials.PassphraseCredentialManager
8-
import com.flipcash.app.auth.internal.extensions.token
97
import com.flipcash.app.featureflags.FeatureFlagController
108
import com.flipcash.app.persistence.PersistenceProvider
9+
import com.flipcash.app.push.PushTokenProvider
1110
import com.flipcash.app.tokens.TokenCoordinator
1211
import com.flipcash.app.userflags.UserFlagsCoordinator
1312
import com.flipcash.services.controllers.AccountController
@@ -18,11 +17,9 @@ import com.flipcash.shared.authentication.BuildConfig
1817
import com.getcode.crypt.MnemonicPhrase
1918
import com.getcode.opencode.controllers.TokenController
2019
import com.getcode.opencode.model.core.ID
20+
import com.getcode.utils.TraceManager
2121
import com.getcode.utils.TraceType
2222
import com.getcode.utils.trace
23-
import com.google.firebase.Firebase
24-
import com.google.firebase.messaging.FirebaseMessaging
25-
import com.google.firebase.messaging.messaging
2623
import kotlinx.coroutines.CoroutineScope
2724
import kotlinx.coroutines.Dispatchers
2825
import kotlinx.coroutines.coroutineScope
@@ -37,6 +34,7 @@ class AuthManager @Inject constructor(
3734
private val notificationManager: NotificationManagerCompat,
3835
private val accountController: AccountController,
3936
private val pushController: PushController,
37+
private val pushTokenProvider: PushTokenProvider,
4038
private val tokenCoordinator: TokenCoordinator,
4139
private val persistence: PersistenceProvider,
4240
private val featureFlagController: FeatureFlagController,
@@ -212,7 +210,7 @@ class AuthManager @Inject constructor(
212210
private suspend fun resetStateForUser() {
213211
// Fire-and-forget slow network operations to avoid blocking navigation
214212
launch {
215-
FirebaseMessaging.getInstance().deleteToken()
213+
pushTokenProvider.deleteToken()
216214
pushController.deleteTokens()
217215
}
218216
notificationManager.cancelAll()
@@ -223,15 +221,15 @@ class AuthManager @Inject constructor(
223221
appSettings.reset()
224222
userFlags.clearAll()
225223

226-
if (!BuildConfig.DEBUG) Bugsnag.setUser(null, null, null)
224+
if (!BuildConfig.DEBUG) TraceManager.userId = null
227225
}
228226

229227
private suspend fun savePrefs() {
230228
updateFcmToken()
231229
}
232230

233231
private suspend fun updateFcmToken() {
234-
val pushToken = Firebase.messaging.token() ?: return
232+
val pushToken = pushTokenProvider.getToken() ?: return
235233
pushController.addToken(pushToken)
236234
.onSuccess {
237235
userManager.set(pushToken = pushToken)

apps/flipcash/shared/authentication/src/main/kotlin/com/flipcash/app/auth/internal/extensions/FirebaseMessaging.kt

Lines changed: 0 additions & 14 deletions
This file was deleted.

apps/flipcash/shared/authentication/src/test/kotlin/com/flipcash/app/auth/AuthManagerTest.kt

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
package com.flipcash.app.auth
22

3-
import android.app.Activity
43
import androidx.core.app.NotificationManagerCompat
54
import com.flipcash.app.appsettings.AppSettingsCoordinator
65
import com.flipcash.app.auth.internal.credentials.AccountMetadata
76
import com.flipcash.app.auth.internal.credentials.PassphraseCredentialManager
87
import com.flipcash.app.featureflags.FeatureFlagController
98
import com.flipcash.app.persistence.PersistenceProvider
9+
import com.flipcash.app.push.PushTokenProvider
1010
import com.flipcash.app.tokens.TokenCoordinator
1111
import com.flipcash.app.userflags.UserFlagsCoordinator
1212
import com.flipcash.services.controllers.AccountController
1313
import com.flipcash.services.controllers.PushController
1414
import com.flipcash.services.models.UserFlags
1515
import com.flipcash.services.user.UserManager
16-
import com.bugsnag.android.Bugsnag
17-
import com.google.android.gms.tasks.OnSuccessListener
18-
import com.google.android.gms.tasks.Task
19-
import com.google.firebase.messaging.FirebaseMessaging
2016
import io.mockk.coEvery
2117
import io.mockk.coVerify
2218
import io.mockk.every
@@ -51,6 +47,7 @@ class AuthManagerTest {
5147
private val notificationManager: NotificationManagerCompat = mockk(relaxed = true)
5248
private val accountController: AccountController = mockk(relaxed = true)
5349
private val pushController: PushController = mockk(relaxed = true)
50+
private val pushTokenProvider: PushTokenProvider = mockk(relaxed = true)
5451
private val tokenCoordinator: TokenCoordinator = mockk(relaxed = true)
5552
private val persistence: PersistenceProvider = mockk(relaxed = true)
5653
private val featureFlagController: FeatureFlagController = mockk(relaxed = true)
@@ -65,30 +62,7 @@ class AuthManagerTest {
6562
mockkStatic("com.getcode.utils.LoggingKt")
6663
every { com.getcode.utils.trace(any(), any(), any(), any(), any()) } returns Unit
6764

68-
mockkStatic(Bugsnag::class)
69-
every { Bugsnag.setUser(any(), any(), any()) } returns Unit
70-
71-
mockkStatic(FirebaseMessaging::class)
72-
val mockMessaging: FirebaseMessaging = mockk(relaxed = true)
73-
every { FirebaseMessaging.getInstance() } returns mockMessaging
74-
75-
// Make FirebaseMessaging.token Task invoke success listener immediately
76-
// so the token() suspendCancellableCoroutine resolves
77-
val mockTask = mockk<Task<String>>(relaxed = true)
78-
every { mockMessaging.token } returns mockTask
79-
every { mockTask.addOnCanceledListener(any()) } returns mockTask
80-
every { mockTask.addOnFailureListener(any()) } returns mockTask
81-
every { mockTask.addOnSuccessListener(any()) } answers {
82-
val listener = firstArg<OnSuccessListener<String>>()
83-
listener.onSuccess("fake-token")
84-
mockTask
85-
}
86-
// Also handle the Activity variant
87-
every { mockTask.addOnSuccessListener(any<Activity>(), any()) } answers {
88-
val listener = secondArg<OnSuccessListener<String>>()
89-
listener.onSuccess("fake-token")
90-
mockTask
91-
}
65+
coEvery { pushTokenProvider.getToken() } returns "fake-token"
9266

9367
// Default stubs for methods called during login/createAccount success paths
9468
coEvery { accountController.getUserFlags() } returns Result.success(UserFlags.Default)
@@ -102,6 +76,7 @@ class AuthManagerTest {
10276
notificationManager = notificationManager,
10377
accountController = accountController,
10478
pushController = pushController,
79+
pushTokenProvider = pushTokenProvider,
10580
tokenCoordinator = tokenCoordinator,
10681
persistence = persistence,
10782
featureFlagController = featureFlagController,
@@ -114,8 +89,6 @@ class AuthManagerTest {
11489
fun tearDown() {
11590
Dispatchers.resetMain()
11691
unmockkStatic("com.getcode.utils.LoggingKt")
117-
unmockkStatic(FirebaseMessaging::class)
118-
unmockkStatic(Bugsnag::class)
11992
}
12093

12194
@Test

apps/flipcash/shared/notifications/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ android {
88

99
dependencies {
1010
implementation(project(":apps:flipcash:shared:authentication"))
11+
implementation(project(":apps:flipcash:shared:push"))
1112
implementation(project(":apps:flipcash:shared:tokens"))
1213
implementation(project(":services:flipcash"))
1314

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.flipcash.app.notifications
2+
3+
import com.flipcash.app.push.PushTokenProvider
4+
import com.getcode.utils.ErrorUtils
5+
import com.google.firebase.messaging.FirebaseMessaging
6+
import kotlinx.coroutines.suspendCancellableCoroutine
7+
import javax.inject.Inject
8+
import javax.inject.Singleton
9+
import kotlin.coroutines.resume
10+
11+
@Singleton
12+
class FirebasePushTokenProvider @Inject constructor() : PushTokenProvider {
13+
override suspend fun getToken(): String? = suspendCancellableCoroutine { cont ->
14+
FirebaseMessaging.getInstance().token
15+
.addOnCanceledListener { cont.resume(null) }
16+
.addOnFailureListener {
17+
ErrorUtils.handleError(it)
18+
cont.resume(null)
19+
}
20+
.addOnSuccessListener { cont.resume(it) }
21+
}
22+
23+
override suspend fun deleteToken() {
24+
FirebaseMessaging.getInstance().deleteToken()
25+
}
26+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.flipcash.app.notifications.inject
2+
3+
import com.flipcash.app.notifications.FirebasePushTokenProvider
4+
import com.flipcash.app.push.PushTokenProvider
5+
import dagger.Binds
6+
import dagger.Module
7+
import dagger.hilt.InstallIn
8+
import dagger.hilt.components.SingletonComponent
9+
10+
@Module
11+
@InstallIn(SingletonComponent::class)
12+
abstract class PushModule {
13+
@Binds
14+
abstract fun bindPushTokenProvider(impl: FirebasePushTokenProvider): PushTokenProvider
15+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build/
2+
.gradle/
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
plugins {
2+
alias(libs.plugins.flipcash.android.library)
3+
}
4+
5+
android {
6+
namespace = "${Gradle.flipcashNamespace}.shared.push"
7+
}
8+
9+
dependencies {
10+
implementation(libs.javax.inject)
11+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.flipcash.app.push
2+
3+
interface PushTokenProvider {
4+
suspend fun getToken(): String?
5+
suspend fun deleteToken()
6+
}

0 commit comments

Comments
 (0)