Coverage Summary for Class: OutboxEnqueuerImpl (com.georgeci.moneysurfer.sync.repository)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| OutboxEnqueuerImpl |
100%
(5/5)
|
100%
(12/12)
|
100%
(22/22)
|
100%
(177/177)
|
| OutboxEnqueuerImpl$enqueueDelete$1 |
|
| OutboxEnqueuerImpl$enqueueUpsert$1 |
|
| OutboxEnqueuerImpl$isEnabled$1 |
|
| Total |
100%
(5/5)
|
100%
(12/12)
|
100%
(22/22)
|
100%
(177/177)
|
package com.georgeci.moneysurfer.sync.repository
import com.georgeci.moneysurfer.domain.auth.SessionPointers
import com.georgeci.moneysurfer.domain.repositories.AppVersionGate
import kotlinx.coroutines.flow.first
import org.koin.core.annotation.Single
import kotlin.time.Clock
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
/**
* Convenience wrapper for repositories: guards against demo mode and
* packages the [PendingMutation] for the outbox.
*
* [entityType] is a string constant from [com.georgeci.moneysurfer.domain.sync.SyncEntityTypes].
* [scopeKey] carries the workspace id for subcollection entities, null for root-collection entities.
*
* The push worker reads the entity from Room at push time โ no payload serialization happens here.
* See sync.md ยง4.3.
*/
@Single(binds = [OutboxEnqueuer::class])
class OutboxEnqueuerImpl(
private val outbox: PendingMutationQueue,
private val session: SessionPointers,
private val appVersionGate: AppVersionGate,
) : OutboxEnqueuer {
override suspend fun enqueueUpsert(
entityType: String,
entityId: String,
scopeKey: String?,
operation: MutationOperation,
) {
require(operation != MutationOperation.DELETE) {
"DELETE has no payload โ call enqueueDelete instead"
}
if (!isEnabled()) return
outbox.enqueue(buildMutation(entityType, entityId, scopeKey, operation))
}
override suspend fun enqueueDelete(
entityType: String,
entityId: String,
scopeKey: String?,
) {
if (!isEnabled()) return
outbox.enqueue(buildMutation(entityType, entityId, scopeKey, MutationOperation.DELETE))
}
/**
* Outbox accepts mutations only when:
* 1. The user is signed in to a real Firebase account (demo sessions
* skip โ mutations stay purely local).
* 2. The remote app-version gate is not in `Unsupported` state.
* See block.md.
*/
override suspend fun isEnabled(): Boolean {
val uid = session.currentFirebaseUid.first()
if (uid.isNullOrEmpty()) return false
return appVersionGate.isSyncAllowed()
}
@OptIn(ExperimentalUuidApi::class)
private fun buildMutation(
entityType: String,
entityId: String,
scopeKey: String?,
operation: MutationOperation,
): PendingMutation = PendingMutation(
id = Uuid.random().toString(),
entityType = entityType,
entityId = entityId,
operation = operation,
scopeKey = scopeKey,
createdAt = Clock.System.now(),
attempts = 0,
lastError = null,
)
}