update TransactionService with withdrawal

This commit is contained in:
2025-09-12 15:11:57 +02:00
parent 0454f112e7
commit 83375cf9cc
2 changed files with 113 additions and 31 deletions

View File

@@ -2,7 +2,9 @@ package ltd.lulz.service
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import java.math.BigDecimal import java.math.BigDecimal
import java.math.BigDecimal.ZERO
import java.util.UUID import java.util.UUID
import ltd.lulz.exception.InsufficientFundsException
import ltd.lulz.model.AccountEntity import ltd.lulz.model.AccountEntity
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.annotation.Transactional
@@ -23,4 +25,15 @@ class TransactionService(
.map { it.copy(amount = it.amount + amount) } .map { it.copy(amount = it.amount + amount) }
.doOnNext { log.trace { "Deposited $amount to account ${it.id}" } } .doOnNext { log.trace { "Deposited $amount to account ${it.id}" } }
.flatMap { accountService.save(it) } .flatMap { accountService.save(it) }
@Transactional
fun withdrawal(
account: UUID,
amount: BigDecimal,
): Mono<AccountEntity> = accountService.getForUpdateById(account)
.map { it.copy(amount = it.amount - amount) }
.filter { it.amount >= ZERO }
.doOnNext { log.trace { "withdrawal $amount to account ${it.id}" } }
.switchIfEmpty(Mono.error(InsufficientFundsException()))
.flatMap { accountService.save(it) }
} }

View File

@@ -7,9 +7,11 @@ import io.mockk.verify
import java.math.BigDecimal import java.math.BigDecimal
import java.util.UUID import java.util.UUID
import ltd.lulz.exception.AccountNotFoundException import ltd.lulz.exception.AccountNotFoundException
import ltd.lulz.exception.InsufficientFundsException
import ltd.lulz.model.AccountEntity import ltd.lulz.model.AccountEntity
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.test.StepVerifier import reactor.test.StepVerifier
@@ -31,6 +33,9 @@ class TransactionServiceTest {
service = TransactionService(accountService) service = TransactionService(accountService)
} }
@Nested
inner class Deposit {
@Test @Test
fun `deposit to account - success`() { fun `deposit to account - success`() {
// given // given
@@ -71,4 +76,68 @@ class TransactionServiceTest {
verify(exactly = 1) { accountService.getForUpdateById(any(UUID::class)) } verify(exactly = 1) { accountService.getForUpdateById(any(UUID::class)) }
verify(exactly = 0) { accountService.save(any()) } verify(exactly = 0) { accountService.save(any()) }
} }
}
@Nested
inner class Withdrawal {
@Test
fun `withdrawal from account - success`() {
// given
val deposit = BigDecimal.valueOf(1.01)
val capture = slot<UUID>()
every { accountService.getForUpdateById(capture(capture)) }
.answers { Mono.just(AccountEntity(capture.captured, name, amount)) }
val entity = slot<AccountEntity>()
every { accountService.save(capture(entity)) }
.answers { Mono.just(entity.captured) }
// when stepped
StepVerifier.create(service.withdrawal(uuid, deposit))
.assertNext { result ->
assertThat(result.id).isEqualTo(uuid)
assertThat(result.name).isEqualTo(name)
assertThat(result.amount).isEqualTo(amount - deposit)
}
.verifyComplete()
verify(exactly = 1) { accountService.getForUpdateById(any(UUID::class)) }
verify(exactly = 1) { accountService.save(any()) }
}
@Test
fun `withdrawal from account - insufficient founds`() {
// given
val deposit = BigDecimal.valueOf(1.10)
val capture = slot<UUID>()
every { accountService.getForUpdateById(capture(capture)) }
.answers { Mono.just(AccountEntity(capture.captured, name, amount)) }
// when stepped
StepVerifier.create(service.withdrawal(uuid, deposit))
.expectError(InsufficientFundsException::class.java)
.verify()
verify(exactly = 1) { accountService.getForUpdateById(any(UUID::class)) }
verify(exactly = 0) { accountService.save(any()) }
}
@Test
fun `withdrawal from account - account not found`() {
// given
val deposit = BigDecimal.valueOf(1.10)
every { accountService.getForUpdateById(any()) } returns Mono.error(AccountNotFoundException())
// when stepped
StepVerifier.create(service.withdrawal(uuid, deposit))
.expectError(AccountNotFoundException::class.java)
.verify()
verify(exactly = 1) { accountService.getForUpdateById(any(UUID::class)) }
verify(exactly = 0) { accountService.save(any()) }
}
}
} }