Coverage Summary for Class: WorkspaceMemberSyncPlugin (com.georgeci.moneysurfer.data.sync.plugin)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| WorkspaceMemberSyncPlugin |
75%
(3/4)
|
62.5%
(5/8)
|
97.1%
(33/34)
|
96.7%
(295/305)
|
| WorkspaceMemberSyncPlugin$applyDoc$1 |
|
| WorkspaceMemberSyncPlugin$push$2 |
100%
(1/1)
|
50%
(2/4)
|
100%
(10/10)
|
98%
(100/102)
|
| Total |
80%
(4/5)
|
58.3%
(7/12)
|
97.7%
(43/44)
|
97.1%
(395/407)
|
package com.georgeci.moneysurfer.data.sync.plugin
import com.georgeci.moneysurfer.data.db.dao.UserDao
import com.georgeci.moneysurfer.data.db.dao.WorkspaceInviteDao
import com.georgeci.moneysurfer.data.db.dao.WorkspaceMemberDao
import com.georgeci.moneysurfer.data.db.entity.UserEntity
import com.georgeci.moneysurfer.data.remote.WorkspaceMemberDoc
import com.georgeci.moneysurfer.data.sync.WorkspaceDocumentWriter
import com.georgeci.moneysurfer.data.sync.toDoc
import com.georgeci.moneysurfer.data.sync.toEntity
import com.georgeci.moneysurfer.domain.AppInfo
import com.georgeci.moneysurfer.domain.sync.SyncEntityTypes
import com.georgeci.moneysurfer.sync.api.SyncCollection
import com.georgeci.moneysurfer.sync.plugin.EntityApplyResult
import com.georgeci.moneysurfer.sync.plugin.RemoteDocument
import com.georgeci.moneysurfer.sync.plugin.SyncEntityPlugin
import com.georgeci.moneysurfer.sync.repository.ConflictMetadata
import com.georgeci.moneysurfer.sync.repository.ConflictResolver
import com.georgeci.moneysurfer.sync.repository.PendingMutation
import org.koin.core.annotation.Single
import kotlin.time.Instant
@Single(binds = [SyncEntityPlugin::class])
class WorkspaceMemberSyncPlugin(
private val writer: WorkspaceDocumentWriter,
private val appInfo: AppInfo,
private val conflictResolver: ConflictResolver,
private val workspaceMemberDao: WorkspaceMemberDao,
private val workspaceInviteDao: WorkspaceInviteDao,
private val userDao: UserDao,
) : SyncEntityPlugin {
override val entityType: String = SyncEntityTypes.WORKSPACE_MEMBER
override val firestoreCollectionName: String = SyncCollection.WORKSPACE_MEMBERS
override val pullPriority: Int = SyncPullPriorities.MEMBERS
override suspend fun push(mutation: PendingMutation) = writer.pushToCollection(
mutation = mutation,
collectionName = SyncCollection.WORKSPACE_MEMBERS,
clientVersionCode = appInfo.versionCode,
strategy = WorkspaceMemberDoc.serializer(),
) {
val scopeKey = mutation.scopeKey!!
val entity = workspaceMemberDao.getById(
userId = mutation.entityId,
workspaceId = scopeKey,
)
entity?.toDoc()?.copy(
clientVersionCode = appInfo.versionCode,
// Stamp the admitting invite so the rules can authorize the accept-invite
// self-create (issue #152). Usually null for owner-created rows and not
// required for owner writes (the rules' owner branch never consults it);
// it may still be non-null if a joinable invite happens to target the
// owner, which is harmless.
inviteId = workspaceInviteDao.findJoinableInviteId(
workspaceId = scopeKey,
userId = entity.userId,
email = entity.email,
),
)
}
override suspend fun applyDoc(doc: RemoteDocument, scopeKey: String): EntityApplyResult {
val dto = doc.decodeOrNull(WorkspaceMemberDoc.serializer()) ?: return SKIPPED_APPLY_RESULT
if (dto.deletedAt != null) {
workspaceMemberDao.delete(userId = doc.id, workspaceId = scopeKey)
return EntityApplyResult(applied = true, wasConflict = false)
}
userDao.insertIgnore(
UserEntity(
id = doc.id,
displayName = dto.displayName.ifEmpty { null },
isAnon = false,
),
)
val local = workspaceMemberDao.getById(userId = doc.id, workspaceId = scopeKey)
val resolution = conflictResolver.resolve(
local = local,
remote = dto.toEntity(workspaceId = scopeKey),
metadata = ConflictMetadata(
entityType = SyncEntityTypes.WORKSPACE_MEMBER,
entityId = doc.id,
localUpdatedAt = local?.updatedAt?.let(Instant::fromEpochMilliseconds),
remoteUpdatedAt = Instant.fromEpochMilliseconds(dto.updatedAt),
),
)
return applyResolution(resolution) { workspaceMemberDao.upsert(it) }
}
}