Coverage Summary for Class: PendingMutationQueueImplKt (com.georgeci.moneysurfer.sync.internal.repository)

Class Class, % Method, % Branch, % Line, % Instruction, %
PendingMutationQueueImplKt 100% (1/1) 100% (2/2) 100% (19/19) 100% (44/44)


 package com.georgeci.moneysurfer.sync.internal.repository
 
 import com.georgeci.moneysurfer.sync.api.SyncScope
 import com.georgeci.moneysurfer.sync.db.dao.PendingMutationDao
 import com.georgeci.moneysurfer.sync.db.entity.PendingMutationEntity
 import com.georgeci.moneysurfer.sync.repository.MutationOperation
 import com.georgeci.moneysurfer.sync.repository.PendingMutation
 import com.georgeci.moneysurfer.sync.repository.PendingMutationQueue
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
 import org.koin.core.annotation.Single
 import kotlin.time.Instant
 
 @Single(binds = [PendingMutationQueue::class])
 class PendingMutationQueueImpl(
     private val dao: PendingMutationDao,
 ) : PendingMutationQueue {
 
     /** Insert-if-absent — see [PendingMutationDao.insertIfAbsent] for why the dedup lives in SQL. */
     override suspend fun enqueue(mutation: PendingMutation) {
         val entity = mutation.toEntity(status = PendingMutationEntity.STATUS_PENDING)
         dao.insertIfAbsent(
             id = entity.id,
             entityType = entity.entityType,
             entityId = entity.entityId,
             operation = entity.operation,
             workspaceId = entity.workspaceId,
             createdAt = entity.createdAt,
             attempts = entity.attempts,
             status = entity.status,
             lastError = entity.lastError,
         )
     }
 
     override suspend fun pending(scope: SyncScope, limit: Int): List<PendingMutation> =
         dao.pending(workspaceId = workspaceFilterFor(scope), limit = limit)
             .map { it.toDomain() }
 
     override suspend fun markInFlight(ids: List<String>) {
         if (ids.isNotEmpty()) dao.markInFlight(ids)
     }
 
     override suspend fun markCompleted(ids: List<String>) {
         if (ids.isNotEmpty()) dao.deleteByIds(ids)
     }
 
     override suspend fun markFailed(id: String, error: String) {
         dao.markFailed(id, error)
     }
 
     override val pendingCount: Flow<Int> = dao.pendingCount()
 
     override fun observeOutbox(limit: Int): Flow<List<PendingMutation>> =
         dao.observeAll(limit).map { rows -> rows.map { it.toDomain() } }
 
     private fun workspaceFilterFor(scope: SyncScope): String? = when (scope) {
         SyncScope.UploadOnly,
         SyncScope.ActiveWorkspace,
         SyncScope.AllUserData,
         SyncScope.ChangedSinceLastSync,
         -> null
     }
 }
 
 private fun PendingMutation.toEntity(status: String) = PendingMutationEntity(
     id = id,
     entityType = entityType,
     entityId = entityId,
     operation = operation.name,
     workspaceId = scopeKey,
     createdAt = createdAt.toEpochMilliseconds(),
     attempts = attempts,
     status = status,
     lastError = lastError,
 )
 
 private fun PendingMutationEntity.toDomain() = PendingMutation(
     id = id,
     entityType = entityType,
     entityId = entityId,
     operation = MutationOperation.valueOf(operation),
     scopeKey = workspaceId,
     createdAt = Instant.fromEpochMilliseconds(createdAt),
     attempts = attempts,
     lastError = lastError,
 )