Coverage Summary for Class: DeclineInviteUseCase (com.georgeci.moneysurfer.domain.usecase)

Class Method, % Branch, % Line, % Instruction, %
DeclineInviteUseCase 0% (0/2) 0% (0/20) 0% (0/30) 0% (0/674)
DeclineInviteUseCase$Companion
DeclineInviteUseCase$invoke$1
Total 0% (0/2) 0% (0/20) 0% (0/30) 0% (0/674)


 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.InviteStatus
 import com.georgeci.moneysurfer.domain.primitives.WorkspaceInviteId
 import com.georgeci.moneysurfer.domain.repositories.UserRemoteRepository
 import com.georgeci.moneysurfer.domain.repositories.UserRepository
 import com.georgeci.moneysurfer.domain.repositories.WorkspaceInviteRepository
 import kotlinx.coroutines.flow.first
 import org.koin.core.annotation.Single
 
 /**
  * Recipient declines a pending invitation. No member row created; invite transitions to DECLINED.
  */
 @Single
 class DeclineInviteUseCase(
     private val inviteRepository: WorkspaceInviteRepository,
     private val userRepository: UserRepository,
     private val userRemoteRepository: UserRemoteRepository,
     private val session: SessionPointers,
 ) {
     private val log = Logger.withTag(TAG)
 
     suspend operator fun invoke(id: WorkspaceInviteId): Either<InviteError, Unit> = either {
         val callerId = session.currentUserId.first()
             ?: raise(InviteError.NoCurrentUser)
         val caller = userRepository.getById(callerId)
             ?: raise(InviteError.NoCurrentUser)
 
         val invite = inviteRepository.getById(id)
             ?: raise(InviteError.InviteNotFound)
         ensure(invite.status == InviteStatus.PENDING) { InviteError.InviteNotPending }
 
         val emailMatches = caller.email?.lowercase() == invite.email.lowercase()
         val uidMatches = invite.targetUserId == callerId
         ensure(emailMatches || uidMatches) { InviteError.InviteNotFound }
 
         Either.catch { inviteRepository.markDeclined(id) }
             .onLeft { log.e(it) { "[local] markDeclined failed id=${id.value}" } }
             .mapLeft { InviteError.LocalWriteFailed(it) }
             .bind()
 
         val firebaseUid = session.currentFirebaseUid.first()
         if (!firebaseUid.isNullOrBlank()) {
             Either.catch {
                 userRemoteRepository.removeInvitedWorkspaceRef(firebaseUid, invite.workspaceId)
             }.onLeft {
                 log.w(it) { "[remote] removeInvitedWorkspaceRef failed — non-fatal" }
             }
         }
 
         log.i { "[done] uid=${callerId.value.redactUid()} id=${id.value}" }
     }
 
     private companion object {
         const val TAG = "DeclineInvite"
     }
 }