diff --git a/src/main/kotlin/ltd/lulz/service/TransactionService.kt b/src/main/kotlin/ltd/lulz/service/TransactionService.kt new file mode 100644 index 0000000..ca56cbd --- /dev/null +++ b/src/main/kotlin/ltd/lulz/service/TransactionService.kt @@ -0,0 +1,26 @@ +package ltd.lulz.service + +import io.github.oshai.kotlinlogging.KotlinLogging +import java.math.BigDecimal +import java.util.UUID +import ltd.lulz.model.AccountEntity +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import reactor.core.publisher.Mono + +private val log = KotlinLogging.logger {} + +@Service +class TransactionService( + private val accountService: AccountService, +) { + + @Transactional + fun deposit( + id: UUID, + amount: BigDecimal, + ): Mono = accountService.getForUpdateById(id) + .map { it.copy(amount = it.amount + amount) } + .doOnNext { log.trace { "Deposited $amount to account ${it.id}" } } + .flatMap { accountService.save(it) } +} diff --git a/src/test/kotlin/ltd/lulz/service/TransactionServiceTest.kt b/src/test/kotlin/ltd/lulz/service/TransactionServiceTest.kt new file mode 100644 index 0000000..de42337 --- /dev/null +++ b/src/test/kotlin/ltd/lulz/service/TransactionServiceTest.kt @@ -0,0 +1,74 @@ +package ltd.lulz.service + +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import io.mockk.verify +import java.math.BigDecimal +import java.util.UUID +import ltd.lulz.exception.AccountNotFoundException +import ltd.lulz.model.AccountEntity +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import reactor.core.publisher.Mono +import reactor.test.StepVerifier + +@Suppress("MayBeConstant", "ReactiveStreamsUnusedPublisher") +class TransactionServiceTest { + + companion object { + val name: String = "some name" + val amount: BigDecimal = BigDecimal.valueOf(1.01) + val uuid: UUID = UUID.fromString("00000000-0000-0000-0000-000000000000") + } + + private val accountService: AccountService = mockk() + private lateinit var service: TransactionService + + @BeforeEach + fun setup() { + service = TransactionService(accountService) + } + + @Test + fun `deposit to account - success`() { + // given + val deposit = BigDecimal.valueOf(1.10) + + val capture = slot() + every { accountService.getForUpdateById(capture(capture)) } + .answers { Mono.just(AccountEntity(capture.captured, name, amount)) } + val entity = slot() + every { accountService.save(capture(entity)) } + .answers { Mono.just(entity.captured) } + + // when stepped + StepVerifier.create(service.deposit(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 `deposit to account - account not found`() { + // given + val deposit = BigDecimal.valueOf(1.10) + + every { accountService.getForUpdateById(any()) } returns Mono.error(AccountNotFoundException()) + + // when stepped + StepVerifier.create(service.deposit(uuid, deposit)) + .expectError(AccountNotFoundException::class.java) + .verify() + + verify(exactly = 1) { accountService.getForUpdateById(any(UUID::class)) } + verify(exactly = 0) { accountService.save(any()) } + } +}