Coverage Summary for Class: ChangeMemberRoleUseCase (com.georgeci.moneysurfer.domain.usecase)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| ChangeMemberRoleUseCase |
100%
(2/2)
|
75%
(9/12)
|
100%
(25/25)
|
99.3%
(295/297)
|
| ChangeMemberRoleUseCase$Companion |
|
| ChangeMemberRoleUseCase$invoke$1 |
|
| ChangeMemberRoleUseCase$Params |
100%
(1/1)
|
|
100%
(4/4)
|
100%
(17/17)
|
| Total |
100%
(3/3)
|
75%
(9/12)
|
100%
(29/29)
|
99.4%
(312/314)
|
package com.georgeci.moneysurfer.domain.usecase
import arrow.core.Either
import arrow.core.raise.either
import arrow.core.raise.ensure
import co.touchlab.kermit.Logger
import com.georgeci.moneysurfer.domain.auth.SessionPointers
import com.georgeci.moneysurfer.domain.logging.redactUid
import com.georgeci.moneysurfer.domain.model.WorkspaceMemberStatus
import com.georgeci.moneysurfer.domain.model.WorkspaceRole
import com.georgeci.moneysurfer.domain.primitives.UserId
import com.georgeci.moneysurfer.domain.primitives.WorkspaceId
import com.georgeci.moneysurfer.domain.repositories.WorkspaceMemberRepository
import org.koin.core.annotation.Single
/**
* Owner changes a member's role. Refuses to demote the last OWNER.
*/
@Single
class ChangeMemberRoleUseCase(
private val memberRepository: WorkspaceMemberRepository,
private val session: SessionPointers,
) {
private val log = Logger.withTag(TAG)
data class Params(
val workspaceId: WorkspaceId,
val targetUserId: UserId,
val newRole: WorkspaceRole,
)
suspend operator fun invoke(params: Params): Either<InviteError, Unit> = either {
val (callerId, members, target) = resolveOwnerActionTarget(
session = session,
memberRepository = memberRepository,
workspaceId = params.workspaceId,
targetUserId = params.targetUserId,
)
if (target.role == WorkspaceRole.OWNER && params.newRole != WorkspaceRole.OWNER) {
val remainingOwners = members.count {
it.role == WorkspaceRole.OWNER &&
it.status == WorkspaceMemberStatus.ACTIVE &&
it.userId != params.targetUserId
}
ensure(remainingOwners >= 1) { InviteError.CannotDemoteLastOwner }
}
// No-op fast path: avoid writing if nothing actually changes.
if (target.role == params.newRole) return@either
Either.catch { memberRepository.update(target.copy(role = params.newRole)) }
.onLeft { log.e(it) { "[local] update failed uid=${params.targetUserId.value.redactUid()}" } }
.mapLeft { InviteError.LocalWriteFailed(it) }
.bind()
log.i {
"[done] uid=${params.targetUserId.value.redactUid()} ${target.role} -> ${params.newRole} " +
"by=${callerId.value.redactUid()}"
}
}
private companion object {
const val TAG = "ChangeMemberRole"
}
}