From 80683060f2c840fae89955714774040f1a631d93 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo <48495111+demolaf@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:32:42 +0100 Subject: [PATCH 1/4] build(e2eTest): give :e2eTest friend-path access to :auth internals --- e2eTest/build.gradle.kts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/e2eTest/build.gradle.kts b/e2eTest/build.gradle.kts index b7b294807..08d02bb35 100644 --- a/e2eTest/build.gradle.kts +++ b/e2eTest/build.gradle.kts @@ -85,4 +85,24 @@ tasks.register("e2eTest") { classpath = debug.classpath doNotTrackState("Always run e2e emulator tests to mirror Android Studio") -} \ No newline at end of file +} + +// Give this module friend access to `:auth` internals, so the e2e tests can drive test +// seams that are `internal` rather than forcing those seams to stay public API. +// +// Two things here are deliberate and should not be simplified: +// +// 1. `libraries` is filtered instead of naming the jar it resolves to. That jar lives under +// AGP's `intermediates` tree, which is an implementation detail and moves on an AGP bump. +// 2. `rootProject.layout.projectDirectory` rather than `project(":auth").layout.buildDirectory`. +// The latter works today, but it is cross-project model access and breaks under project +// isolation. +// +// Appending `-Xfriend-paths` to `compilerOptions.freeCompilerArgs` does not work: KGP +// generates that flag itself from the typed `friendPaths` property, so a hand-appended copy +// is ignored. Pointing at `auth/build/tmp/kotlin-classes/debug` does not work either, because +// `:e2eTest` never sees that directory. +val authBuildDir = rootProject.layout.projectDirectory.dir("auth/build").asFile.absolutePath +tasks.withType().configureEach { + friendPaths.from(libraries.filter { it.absolutePath.startsWith(authBuildDir) }) +} From 4013162c875d908415f88a1120f331f484199511 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo <48495111+demolaf@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:36:01 +0100 Subject: [PATCH 2/4] refactor(auth): narrow the credential-manager test seams to internal --- .../com/firebase/ui/auth/FirebaseAuthUI.kt | 6 ++---- .../credentialmanager/PasswordCredential.kt | 2 +- .../PasswordCredentialHandler.kt | 18 +++++------------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt index e321ffcf2..2735bc836 100644 --- a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt @@ -95,11 +95,9 @@ class FirebaseAuthUI private constructor( /** How many composed [FirebaseAuthScreen]s can currently drive a reauthentication request. */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - var testCredentialManagerProvider: AuthProvider.Google.CredentialManagerProvider? = null + internal var testCredentialManagerProvider: AuthProvider.Google.CredentialManagerProvider? = null - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - var testLoginManagerProvider: AuthProvider.Facebook.LoginManagerProvider? = null + internal var testLoginManagerProvider: AuthProvider.Facebook.LoginManagerProvider? = null /** * Checks whether a user is currently signed in. diff --git a/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredential.kt b/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredential.kt index 535ace99d..a7b6ac66f 100644 --- a/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredential.kt +++ b/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredential.kt @@ -20,7 +20,7 @@ package com.firebase.ui.auth.credentialmanager * @property username The username/identifier associated with the credential * @property password The password associated with the credential */ -data class PasswordCredential( +internal data class PasswordCredential( val username: String, val password: String ) diff --git a/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt b/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt index c83f9a280..5c6a15203 100644 --- a/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt +++ b/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt @@ -31,14 +31,14 @@ import com.firebase.ui.auth.util.CredentialPersistenceManager * Provider interface for obtaining CredentialManager instances. * This allows test code to inject mock CredentialManager instances. */ -interface CredentialManagerProvider { +internal interface CredentialManagerProvider { fun getCredentialManager(context: Context): CredentialManager } /** * Default implementation that creates a real CredentialManager instance. */ -class DefaultCredentialManagerProvider : CredentialManagerProvider { +internal class DefaultCredentialManagerProvider : CredentialManagerProvider { override fun getCredentialManager(context: Context): CredentialManager { return CredentialManager.create(context) } @@ -53,21 +53,13 @@ class DefaultCredentialManagerProvider : CredentialManagerProvider { * @property context The Android context used for credential operations * @property provider Optional provider for testing purposes */ -class PasswordCredentialHandler( +internal class PasswordCredentialHandler( private val context: Context, provider: CredentialManagerProvider? = null ) { companion object { /** * Test-only provider for injecting mock CredentialManager instances. - * Set this in your test setup to override the default CredentialManager. - * - * Example: - * ``` - * PasswordCredentialHandler.testCredentialManagerProvider = object : CredentialManagerProvider { - * override fun getCredentialManager(context: Context) = mockCredentialManager - * } - * ``` */ @Volatile var testCredentialManagerProvider: CredentialManagerProvider? = null @@ -186,7 +178,7 @@ open class PasswordCredentialException( /** * Exception thrown when a password credential operation is cancelled by the user. */ -class PasswordCredentialCancelledException( +internal class PasswordCredentialCancelledException( message: String, cause: Throwable? = null ) : PasswordCredentialException(message, cause) @@ -194,7 +186,7 @@ class PasswordCredentialCancelledException( /** * Exception thrown when no password credentials are found. */ -class PasswordCredentialNotFoundException( +internal class PasswordCredentialNotFoundException( message: String, cause: Throwable? = null ) : PasswordCredentialException(message, cause) From d7eb81be7ffb6d3e19c7e3edbca17a8aef9354fd Mon Sep 17 00:00:00 2001 From: Ademola Fadumo <48495111+demolaf@users.noreply.github.com> Date: Tue, 22 Sep 2026 11:30:53 +0100 Subject: [PATCH 3/4] refactor(auth): pair @RestrictTo with internal and scope friend paths to tests --- .../com/firebase/ui/auth/FirebaseAuthUI.kt | 6 ++-- .../credentialmanager/PasswordCredential.kt | 3 ++ .../PasswordCredentialHandler.kt | 10 +++++- e2eTest/build.gradle.kts | 35 ++++++++++--------- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt index 2735bc836..24100f28c 100644 --- a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt @@ -93,10 +93,10 @@ class FirebaseAuthUI private constructor( */ internal val pendingReauth = MutableStateFlow(null) - /** How many composed [FirebaseAuthScreen]s can currently drive a reauthentication request. */ - + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal var testCredentialManagerProvider: AuthProvider.Google.CredentialManagerProvider? = null + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal var testLoginManagerProvider: AuthProvider.Facebook.LoginManagerProvider? = null /** @@ -721,7 +721,7 @@ class FirebaseAuthUI private constructor( */ @JvmStatic @RestrictTo(RestrictTo.Scope.TESTS) - fun clearInstanceCache() { + internal fun clearInstanceCache() { instanceCache.clear() } diff --git a/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredential.kt b/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredential.kt index a7b6ac66f..1c22736d7 100644 --- a/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredential.kt +++ b/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredential.kt @@ -14,12 +14,15 @@ package com.firebase.ui.auth.credentialmanager +import androidx.annotation.RestrictTo + /** * Represents a password credential retrieved from the system credential manager. * * @property username The username/identifier associated with the credential * @property password The password associated with the credential */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal data class PasswordCredential( val username: String, val password: String diff --git a/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt b/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt index 5c6a15203..f70ab8261 100644 --- a/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt +++ b/auth/src/main/java/com/firebase/ui/auth/credentialmanager/PasswordCredentialHandler.kt @@ -22,6 +22,7 @@ import androidx.credentials.GetPasswordOption import androidx.credentials.PasswordCredential as AndroidPasswordCredential import androidx.credentials.exceptions.CreateCredentialCancellationException import androidx.credentials.exceptions.CreateCredentialException +import androidx.annotation.RestrictTo import androidx.credentials.exceptions.GetCredentialCancellationException import androidx.credentials.exceptions.GetCredentialException import androidx.credentials.exceptions.NoCredentialException @@ -31,6 +32,7 @@ import com.firebase.ui.auth.util.CredentialPersistenceManager * Provider interface for obtaining CredentialManager instances. * This allows test code to inject mock CredentialManager instances. */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal interface CredentialManagerProvider { fun getCredentialManager(context: Context): CredentialManager } @@ -38,6 +40,7 @@ internal interface CredentialManagerProvider { /** * Default implementation that creates a real CredentialManager instance. */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal class DefaultCredentialManagerProvider : CredentialManagerProvider { override fun getCredentialManager(context: Context): CredentialManager { return CredentialManager.create(context) @@ -53,6 +56,7 @@ internal class DefaultCredentialManagerProvider : CredentialManagerProvider { * @property context The Android context used for credential operations * @property provider Optional provider for testing purposes */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal class PasswordCredentialHandler( private val context: Context, provider: CredentialManagerProvider? = null @@ -62,6 +66,7 @@ internal class PasswordCredentialHandler( * Test-only provider for injecting mock CredentialManager instances. */ @Volatile + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) var testCredentialManagerProvider: CredentialManagerProvider? = null /** @@ -170,7 +175,8 @@ internal class PasswordCredentialHandler( /** * Base exception for password credential operations. */ -open class PasswordCredentialException( +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +internal open class PasswordCredentialException( message: String, cause: Throwable? = null ) : Exception(message, cause) @@ -178,6 +184,7 @@ open class PasswordCredentialException( /** * Exception thrown when a password credential operation is cancelled by the user. */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal class PasswordCredentialCancelledException( message: String, cause: Throwable? = null @@ -186,6 +193,7 @@ internal class PasswordCredentialCancelledException( /** * Exception thrown when no password credentials are found. */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal class PasswordCredentialNotFoundException( message: String, cause: Throwable? = null diff --git a/e2eTest/build.gradle.kts b/e2eTest/build.gradle.kts index 08d02bb35..d8880b900 100644 --- a/e2eTest/build.gradle.kts +++ b/e2eTest/build.gradle.kts @@ -87,22 +87,23 @@ tasks.register("e2eTest") { doNotTrackState("Always run e2e emulator tests to mirror Android Studio") } -// Give this module friend access to `:auth` internals, so the e2e tests can drive test -// seams that are `internal` rather than forcing those seams to stay public API. +// Give this module's unit-test compilations friend access to `:auth` internals, so the e2e +// tests can drive test seams that are `internal` rather than forcing those seams to stay +// public API. Deliberately scoped to the unit-test tasks: the production source set has no +// reason to reach into `:auth`. // -// Two things here are deliberate and should not be simplified: +// `libraries` is filtered rather than naming the jar it resolves to, because that jar lives +// under AGP's `intermediates` tree, which is an implementation detail and moves on an AGP bump. +// `layout.settingsDirectory` addresses `:auth`'s build directory without going through +// `project(":auth")`, which would be cross-project model access. // -// 1. `libraries` is filtered instead of naming the jar it resolves to. That jar lives under -// AGP's `intermediates` tree, which is an implementation detail and moves on an AGP bump. -// 2. `rootProject.layout.projectDirectory` rather than `project(":auth").layout.buildDirectory`. -// The latter works today, but it is cross-project model access and breaks under project -// isolation. -// -// Appending `-Xfriend-paths` to `compilerOptions.freeCompilerArgs` does not work: KGP -// generates that flag itself from the typed `friendPaths` property, so a hand-appended copy -// is ignored. Pointing at `auth/build/tmp/kotlin-classes/debug` does not work either, because -// `:e2eTest` never sees that directory. -val authBuildDir = rootProject.layout.projectDirectory.dir("auth/build").asFile.absolutePath -tasks.withType().configureEach { - friendPaths.from(libraries.filter { it.absolutePath.startsWith(authBuildDir) }) -} +// Appending `-Xfriend-paths` to `compilerOptions.freeCompilerArgs` does not work: KGP generates +// that flag itself from the typed `friendPaths` property, so a hand-appended copy is ignored. +// Pointing at `auth/build/tmp/kotlin-classes/debug` does not work either, because `:e2eTest` +// never sees that directory. +val authBuildDir = layout.settingsDirectory.dir("auth/build").asFile.absolutePath + File.separator +tasks.withType() + .matching { it.name.contains("UnitTest") } + .configureEach { + friendPaths.from(libraries.filter { it.absolutePath.startsWith(authBuildDir) }) + } From 0e7a9744f435ac4380a5995c458ec090dadf7bc1 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo <48495111+demolaf@users.noreply.github.com> Date: Tue, 22 Sep 2026 12:10:18 +0100 Subject: [PATCH 4/4] refactor(auth): mark the FirebaseAuthUI test seams @Volatile --- auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt index 24100f28c..8514e2bde 100644 --- a/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/FirebaseAuthUI.kt @@ -93,9 +93,11 @@ class FirebaseAuthUI private constructor( */ internal val pendingReauth = MutableStateFlow(null) + @Volatile @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal var testCredentialManagerProvider: AuthProvider.Google.CredentialManagerProvider? = null + @Volatile @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) internal var testLoginManagerProvider: AuthProvider.Facebook.LoginManagerProvider? = null