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

Class Class, % Method, % Branch, % Line, % Instruction, %
UpdateSavingsGoalUseCaseKt 100% (1/1) 100% (1/1) 87.5% (7/8) 100% (4/4) 100% (16/16)


 package com.georgeci.moneysurfer.domain.usecase
 
 import arrow.core.Either
 import arrow.core.raise.either
 import com.georgeci.moneysurfer.domain.model.GoalStatus
 import com.georgeci.moneysurfer.domain.model.SavingsGoal
 import com.georgeci.moneysurfer.domain.repositories.GoalContributionRepository
 import com.georgeci.moneysurfer.domain.repositories.SavingsGoalRepository
 import com.georgeci.moneysurfer.domain.repositories.WorkspaceRepository
 import org.koin.core.annotation.Single
 
 /**
  * Edits a goal. Lowering the target below the saved amount completes the goal, and
  * raising it above re-opens it — the same rule [AddContributionUseCase] applies from
  * the other side, since either edge can be crossed by moving either number.
  */
 @Single
 class UpdateSavingsGoalUseCase(
     private val goalRepository: SavingsGoalRepository,
     private val contributionRepository: GoalContributionRepository,
     private val workspaceRepository: WorkspaceRepository,
 ) {
 
     suspend operator fun invoke(goal: SavingsGoal): Either<GoalActionError, SavingsGoal> = either {
         goalRepository.getById(goal.id) ?: raise(GoalActionError.GoalNotFound)
         ensureGoalIsValid(goal, workspaceRepository)
         val saved = contributionRepository.savedAmount(goal.id)
         val retargeted = goal.copy(status = goal.status.afterProgressChange(saved >= goal.target))
         Either.catch { goalRepository.update(retargeted) }
             .mapLeft { GoalActionError.LocalWriteFailed(it) }
             .bind()
         retargeted
     }
 }
 
 /**
  * The ACTIVE ↔ COMPLETED flip. Only those two statuses move: a PAUSED or ARCHIVED
  * goal keeps its status no matter what the numbers do.
  */
 internal fun GoalStatus.afterProgressChange(isReached: Boolean): GoalStatus = when {
     this == GoalStatus.ACTIVE && isReached -> GoalStatus.COMPLETED
     this == GoalStatus.COMPLETED && !isReached -> GoalStatus.ACTIVE
     else -> this
 }