Skip to content

Commit

Permalink
Test Auth against emulator
Browse files Browse the repository at this point in the history
  • Loading branch information
fmasa committed Oct 9, 2024
1 parent 94f0d1d commit 0ebbaa4
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 36 deletions.
1 change: 1 addition & 0 deletions .firebaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions .github/workflows/build-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ jobs:
with:
distribution: 'zulu'
java-version: 17
- name: Set up Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- run: firebase emulators:start --project my-firebase-project --import=src/test/resources/firebase_data
- name: Build
uses: eskatos/gradle-command-action@v3
with:
Expand Down
11 changes: 11 additions & 0 deletions firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"emulators": {
"auth": {
"port": 9099
},
"ui": {
"enabled": true
},
"singleProjectMode": true
}
}
35 changes: 26 additions & 9 deletions src/main/java/com/google/firebase/auth/FirebaseAuth.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ import java.util.concurrent.TimeUnit

val jsonParser = Json { ignoreUnknownKeys = true }

class UrlFactory(
private val app: FirebaseApp,
private val emulatorUrl: String? = null
) {
fun buildUrl(uri: String): String {
return "${emulatorUrl ?: "https://"}$uri?key=${app.options.apiKey}"
}
}

@Serializable
class FirebaseUserImpl private constructor(
@Transient
Expand All @@ -52,17 +61,20 @@ class FirebaseUserImpl private constructor(
val idToken: String,
val refreshToken: String,
val expiresIn: Int,
val createdAt: Long
val createdAt: Long,
@Transient
private val urlFactory: UrlFactory = UrlFactory(app)
) : FirebaseUser() {

constructor(app: FirebaseApp, data: JsonObject, isAnonymous: Boolean = data["isAnonymous"]?.jsonPrimitive?.booleanOrNull ?: false) : this(
constructor(app: FirebaseApp, data: JsonObject, isAnonymous: Boolean = data["isAnonymous"]?.jsonPrimitive?.booleanOrNull ?: false, urlFactory: UrlFactory = UrlFactory(app)) : this(
app,
isAnonymous,
data["uid"]?.jsonPrimitive?.contentOrNull ?: data["user_id"]?.jsonPrimitive?.contentOrNull ?: data["localId"]?.jsonPrimitive?.contentOrNull ?: "",
data["idToken"]?.jsonPrimitive?.contentOrNull ?: data.getValue("id_token").jsonPrimitive.content,
data["refreshToken"]?.jsonPrimitive?.contentOrNull ?: data.getValue("refresh_token").jsonPrimitive.content,
data["expiresIn"]?.jsonPrimitive?.intOrNull ?: data.getValue("expires_in").jsonPrimitive.int,
data["createdAt"]?.jsonPrimitive?.longOrNull ?: System.currentTimeMillis()
data["createdAt"]?.jsonPrimitive?.longOrNull ?: System.currentTimeMillis(),
urlFactory
)

val claims: Map<String, Any?> by lazy {
Expand All @@ -85,7 +97,7 @@ class FirebaseUserImpl private constructor(
val source = TaskCompletionSource<Void>()
val body = RequestBody.create(FirebaseAuth.getInstance(app).json, JsonObject(mapOf("idToken" to JsonPrimitive(idToken))).toString())
val request = Request.Builder()
.url("https://www.googleapis.com/identitytoolkit/v3/relyingparty/deleteAccount?key=" + app.options.apiKey)
.url(urlFactory.buildUrl("www.googleapis.com/identitytoolkit/v3/relyingparty/deleteAccount"))
.post(body)
.build()
FirebaseAuth.getInstance(app).client.newCall(request).enqueue(object : Callback {
Expand Down Expand Up @@ -184,11 +196,13 @@ class FirebaseAuth constructor(val app: FirebaseApp) : InternalAuthProvider {
}
}

private var urlFactory = UrlFactory(app)

fun signInAnonymously(): Task<AuthResult> {
val source = TaskCompletionSource<AuthResult>()
val body = RequestBody.create(json, JsonObject(mapOf("returnSecureToken" to JsonPrimitive(true))).toString())
val request = Request.Builder()
.url("https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=" + app.options.apiKey)
.url(urlFactory.buildUrl("identitytoolkit.googleapis.com/v1/accounts:signUp"))
.post(body)
.build()
client.newCall(request).enqueue(object : Callback {
Expand Down Expand Up @@ -220,7 +234,7 @@ class FirebaseAuth constructor(val app: FirebaseApp) : InternalAuthProvider {
JsonObject(mapOf("token" to JsonPrimitive(customToken), "returnSecureToken" to JsonPrimitive(true))).toString()
)
val request = Request.Builder()
.url("https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=" + app.options.apiKey)
.url(urlFactory.buildUrl("www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken"))
.post(body)
.build()
client.newCall(request).enqueue(object : Callback {
Expand Down Expand Up @@ -252,7 +266,7 @@ class FirebaseAuth constructor(val app: FirebaseApp) : InternalAuthProvider {
JsonObject(mapOf("email" to JsonPrimitive(email), "password" to JsonPrimitive(password), "returnSecureToken" to JsonPrimitive(true))).toString()
)
val request = Request.Builder()
.url("https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=" + app.options.apiKey)
.url(urlFactory.buildUrl("www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword"))
.post(body)
.build()
client.newCall(request).enqueue(object : Callback {
Expand Down Expand Up @@ -336,7 +350,7 @@ class FirebaseAuth constructor(val app: FirebaseApp) : InternalAuthProvider {
).toString()
)
val request = Request.Builder()
.url("https://securetoken.googleapis.com/v1/token?key=" + app.options.apiKey)
.url(urlFactory.buildUrl("securetoken.googleapis.com/v1/token"))
.post(body)
.build()

Expand Down Expand Up @@ -439,5 +453,8 @@ class FirebaseAuth constructor(val app: FirebaseApp) : InternalAuthProvider {
fun signInWithEmailLink(email: String, link: String): Task<AuthResult> = TODO()

fun setLanguageCode(value: String): Nothing = TODO()
fun useEmulator(host: String, port: Int): Unit = TODO()

fun useEmulator(host: String, port: Int) {
urlFactory = UrlFactory(app, "http://$host:$port/")
}
}
47 changes: 47 additions & 0 deletions src/test/kotlin/AuthTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseAuthInvalidUserException
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Test

class AuthTest : FirebaseTest() {
private fun createAuth(): FirebaseAuth {
return FirebaseAuth(app).apply {
useEmulator("localhost", 9099)
}
}

@Test
fun `should authenticate via anonymous auth`() = runTest {
val auth = createAuth()

auth.signInAnonymously().await()

assertEquals(true, auth.currentUser?.isAnonymous)
}

@Test
fun `should authenticate via email and password`() = runTest {
val auth = createAuth()

auth.signInWithEmailAndPassword("email@example.com", "securepassword").await()

assertEquals(false, auth.currentUser?.isAnonymous)
}

@Test
fun `should throw exception on invalid password`() {
val auth = createAuth()

val exception = assertThrows(FirebaseAuthInvalidUserException::class.java) {
runBlocking {
auth.signInWithEmailAndPassword("email@example.com", "wrongpassword").await()
}
}

assertEquals("INVALID_PASSWORD", exception.errorCode)
}
}
24 changes: 24 additions & 0 deletions src/test/kotlin/FirebaseTest.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
import android.app.Application
import com.google.firebase.Firebase
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.FirebasePlatform
import com.google.firebase.initialize
import org.junit.Before
import java.io.File

abstract class FirebaseTest {
protected val app: FirebaseApp get() {
val options = FirebaseOptions.Builder()
.setProjectId("my-firebase-project")
.setApplicationId("1:27992087142:android:ce3b6448250083d1")
.setApiKey("AIzaSyADUe90ULnQDuGShD9W23RDP0xmeDc6Mvw")
.build()

return Firebase.initialize(Application(), options)
}

@Before
fun beforeEach() {
FirebasePlatform.initializeFirebasePlatform(object : FirebasePlatform() {
val storage = mutableMapOf<String, String>()
override fun store(key: String, value: String) = storage.set(key, value)
override fun retrieve(key: String) = storage[key]
override fun clear(key: String) { storage.remove(key) }
override fun log(msg: String) = println(msg)
override fun getDatabasePath(name: String) = File("./build/$name")
})
FirebaseApp.clearInstancesForTest()
}
}
31 changes: 4 additions & 27 deletions src/test/kotlin/FirestoreTest.kt
Original file line number Diff line number Diff line change
@@ -1,42 +1,19 @@
import android.app.Application
import com.google.firebase.Firebase
import com.google.firebase.FirebaseOptions
import com.google.firebase.FirebasePlatform
import com.google.firebase.firestore.firestore
import com.google.firebase.initialize
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import java.io.File

class FirestoreTest : FirebaseTest() {
@Before
fun initialize() {
FirebasePlatform.initializeFirebasePlatform(object : FirebasePlatform() {
val storage = mutableMapOf<String, String>()
override fun store(key: String, value: String) = storage.set(key, value)
override fun retrieve(key: String) = storage[key]
override fun clear(key: String) { storage.remove(key) }
override fun log(msg: String) = println(msg)
override fun getDatabasePath(name: String) = File("./build/$name")
})
val options = FirebaseOptions.Builder()
.setProjectId("my-firebase-project")
.setApplicationId("1:27992087142:android:ce3b6448250083d1")
.setApiKey("AIzaSyADUe90ULnQDuGShD9W23RDP0xmeDc6Mvw")
// setDatabaseURL(...)
// setStorageBucket(...)
.build()
Firebase.initialize(Application(), options)
Firebase.firestore.disableNetwork()
}

@Test
fun testFirestore(): Unit = runTest {
val firestore = Firebase.firestore(app)
firestore.disableNetwork().await()

val data = Data("jim")
val doc = Firebase.firestore.document("sally/jim")
val doc = firestore.document("sally/jim")
doc.set(data)
assertEquals(data, doc.get().await().toObject(Data::class.java))
}
Expand Down
29 changes: 29 additions & 0 deletions src/test/resources/firebase_data/auth_export/accounts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"kind": "identitytoolkit#DownloadAccountResponse",
"users": [
{
"localId": "Ijat10t0F1gvH1VrClkkSqEcId1p",
"lastLoginAt": "1728509249920",
"displayName": "",
"photoUrl": "",
"emailVerified": true,
"email": "email@example.com",
"salt": "fakeSaltHsRxYqy9iKVQRLwz8975",
"passwordHash": "fakeHash:salt=fakeSaltHsRxYqy9iKVQRLwz8975:password=securepassword",
"passwordUpdatedAt": 1728509249921,
"validSince": "1728509249",
"mfaInfo": [],
"createdAt": "1728509249920",
"providerUserInfo": [
{
"providerId": "password",
"email": "email@example.com",
"federatedId": "email@example.com",
"rawId": "email@example.com",
"displayName": "",
"photoUrl": ""
}
]
}
]
}
8 changes: 8 additions & 0 deletions src/test/resources/firebase_data/auth_export/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"signIn": {
"allowDuplicateEmails": false
},
"emailPrivacyConfig": {
"enableImprovedEmailPrivacy": false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"version": "13.3.1",
"auth": {
"version": "13.3.1",
"path": "auth_export"
}
}

0 comments on commit 0ebbaa4

Please sign in to comment.