Coverage Summary for Class: FirebaseUserRemoteSource (com.georgeci.moneysurfer.data.remote)

Class Method, % Branch, % Line, % Instruction, %
FirebaseUserRemoteSource 0% (0/9) 0% (0/4) 0% (0/16) 0% (0/192)
FirebaseUserRemoteSource$Companion
FirebaseUserRemoteSource$createUser$1
FirebaseUserRemoteSource$fetchEmailMapping$1
FirebaseUserRemoteSource$fetchUser$1
FirebaseUserRemoteSource$upsertEmailMapping$1
Total 0% (0/9) 0% (0/4) 0% (0/16) 0% (0/192)


 package com.georgeci.moneysurfer.data.remote
 
 import dev.gitlive.firebase.firestore.FieldValue
 import dev.gitlive.firebase.firestore.FirebaseFirestore
 import org.koin.core.annotation.Single
 
 /**
  * The `users/{uid}` and `userEmails/{emailKey}` documents, expressed without any Firebase type.
  *
  * Same seam as [AccountDeletionRemoteSource] and [AppConfigRemoteSource], for the same reason: the
  * parts of [UserRemoteRepositoryImpl][com.georgeci.moneysurfer.data.repository.UserRemoteRepositoryImpl]
  * worth testing are the email normalization, the blank-input guards and the doc→domain mapping,
  * and gitlive's client cannot be constructed off Android.
  *
  * The workspace-ref writers are field-level array mutations rather than whole-document writes: two
  * devices joining a workspace at once must not overwrite each other's entry.
  */
 interface UserRemoteSource {
 
     /** `users/{uid}`, or null when the document does not exist. */
     suspend fun fetchUser(uid: String): UserDoc?
 
     suspend fun createUser(uid: String, doc: UserDoc)
 
     suspend fun addWorkspaceRef(uid: String, workspaceId: String)
 
     suspend fun setDefaultWorkspace(uid: String, workspaceId: String)
 
     suspend fun addInvitedWorkspaceRef(uid: String, workspaceId: String)
 
     suspend fun removeInvitedWorkspaceRef(uid: String, workspaceId: String)
 
     /** `userEmails/{emailKey}`, or null when there is no mapping. */
     suspend fun fetchEmailMapping(emailKey: String): UserEmailDoc?
 
     suspend fun upsertEmailMapping(emailKey: String, doc: UserEmailDoc)
 }
 
 @Single(binds = [UserRemoteSource::class])
 class FirebaseUserRemoteSource(
     private val firestore: FirebaseFirestore,
 ) : UserRemoteSource {
 
     override suspend fun fetchUser(uid: String): UserDoc? {
         val snapshot = firestore.collection(USERS).document(uid).get()
         return if (snapshot.exists) snapshot.data(UserDoc.serializer()) else null
     }
 
     override suspend fun createUser(uid: String, doc: UserDoc) {
         firestore.collection(USERS).document(uid).set(UserDoc.serializer(), doc)
     }
 
     override suspend fun addWorkspaceRef(uid: String, workspaceId: String) {
         firestore.collection(USERS).document(uid).update(
             "workspaceIds" to FieldValue.arrayUnion(workspaceId),
         )
     }
 
     override suspend fun setDefaultWorkspace(uid: String, workspaceId: String) {
         firestore.collection(USERS).document(uid).update(
             "defaultWorkspaceId" to workspaceId,
         )
     }
 
     override suspend fun addInvitedWorkspaceRef(uid: String, workspaceId: String) {
         firestore.collection(USERS).document(uid).update(
             "invitedWorkspaceIds" to FieldValue.arrayUnion(workspaceId),
         )
     }
 
     override suspend fun removeInvitedWorkspaceRef(uid: String, workspaceId: String) {
         firestore.collection(USERS).document(uid).update(
             "invitedWorkspaceIds" to FieldValue.arrayRemove(workspaceId),
         )
     }
 
     override suspend fun fetchEmailMapping(emailKey: String): UserEmailDoc? {
         val snapshot = firestore.collection(USER_EMAILS).document(emailKey).get()
         return if (snapshot.exists) snapshot.data(UserEmailDoc.serializer()) else null
     }
 
     override suspend fun upsertEmailMapping(emailKey: String, doc: UserEmailDoc) {
         firestore.collection(USER_EMAILS).document(emailKey).set(UserEmailDoc.serializer(), doc)
     }
 
     private companion object {
         const val USERS = "users"
         const val USER_EMAILS = "userEmails"
     }
 }