Coverage Summary for Class: WorkspaceRefRegistrar (com.georgeci.moneysurfer.data.sync)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| WorkspaceRefRegistrar |
100%
(2/2)
|
100%
(2/2)
|
100%
(9/9)
|
100%
(72/72)
|
| WorkspaceRefRegistrar$Companion |
|
| WorkspaceRefRegistrar$register$1 |
|
| Total |
100%
(2/2)
|
100%
(2/2)
|
100%
(9/9)
|
100%
(72/72)
|
package com.georgeci.moneysurfer.data.sync
import co.touchlab.kermit.Logger
import com.georgeci.moneysurfer.domain.auth.SessionPointers
import com.georgeci.moneysurfer.domain.primitives.WorkspaceId
import com.georgeci.moneysurfer.domain.repositories.UserRemoteRepository
import kotlinx.coroutines.flow.first
import org.koin.core.annotation.Single
/**
* Keeps `users/{uid}.workspaceIds` in step with the workspace documents this device pushes.
*
* `CreateWorkspaceUseCase` registers the ref at creation time, but only when sync is enabled —
* otherwise it would write a ref to a `workspaces/{wid}` document the disabled syncer never
* creates (issue #342). Workspaces created while sync was dark therefore sit in Room and in the
* outbox with no remote ref, and nothing else ever adds one: `addWorkspaceRef` is called from
* exactly two use cases, both at creation/join time.
*
* The moment the flag is flipped, the outbox drains and those documents land on Firestore — so
* this runs on the push, where the ref belongs. `SyncScope.AllUserData` resolves workspaces from
* the remote user document, so without it a workspace is invisible on every other device and
* after any reinstall, permanently.
*
* Idempotent: `addWorkspaceRef` is an `arrayUnion`, so re-registering an already-listed workspace
* is a no-op. Members are registered too, not just owners — a member does belong to the
* workspace, and rules would have rejected the push otherwise.
*/
@Single
class WorkspaceRefRegistrar(
private val userRemoteRepository: UserRemoteRepository,
private val session: SessionPointers,
) {
private val log = Logger.withTag(TAG)
/**
* Fail-loud on purpose: the caller is an outbox push, so a throw marks the mutation failed and
* the next drain retries both the document and the ref. Swallowing here would leave exactly
* the doc-without-ref state this class exists to prevent, with nothing to notice it.
*/
suspend fun register(workspaceId: String) {
val uid = session.currentFirebaseUid.first()
if (uid == null) {
log.d { "[ref] skipped wid=$workspaceId — no Firebase session" }
return
}
userRemoteRepository.addWorkspaceRef(uid, WorkspaceId(workspaceId))
log.d { "[ref] users/{uid}.workspaceIds now covers wid=$workspaceId" }
}
private companion object {
const val TAG = "WorkspaceRefRegistrar"
}
}