diff --git a/build.gradle.kts b/build.gradle.kts index 439b04b..5d553cd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { implementation(aa.kotlinx.coroutines) implementation(aa.springboot.starter.actuator) implementation(aa.springboot.starter.r2dbc) + implementation(aa.springboot.starter.validation) implementation(aa.springboot.starter.webflux) runtimeOnly(aa.postgresql) diff --git a/http/account.http b/http/account.http new file mode 100644 index 0000000..5754455 --- /dev/null +++ b/http/account.http @@ -0,0 +1,8 @@ +### Create Account +POST {{url}}/account +Content-Type: application/json + +{ + "name": "account name", + "amount": -1.11 +} diff --git a/src/main/kotlin/ltd/lulz/controller/AccountController.kt b/src/main/kotlin/ltd/lulz/controller/AccountController.kt new file mode 100644 index 0000000..f34e060 --- /dev/null +++ b/src/main/kotlin/ltd/lulz/controller/AccountController.kt @@ -0,0 +1,28 @@ +package ltd.lulz.controller + +import jakarta.validation.Valid +import ltd.lulz.model.Account +import ltd.lulz.service.AccountService +import ltd.lulz.util.toEntity +import ltd.lulz.util.toResponse +import org.springframework.http.HttpStatus.CREATED +import org.springframework.validation.annotation.Validated +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController +import reactor.core.publisher.Mono + +@RestController +@Validated +class AccountController( + private val accountService: AccountService, +) { + + @PostMapping("/account") + @ResponseStatus(CREATED) + fun create( + @Valid @RequestBody request: Account.Request, + ): Mono = accountService.create(request.toEntity()) + .map { it.toResponse() } +} diff --git a/src/main/kotlin/ltd/lulz/model/Account.kt b/src/main/kotlin/ltd/lulz/model/Account.kt index 9265aad..e3799ac 100644 --- a/src/main/kotlin/ltd/lulz/model/Account.kt +++ b/src/main/kotlin/ltd/lulz/model/Account.kt @@ -1,11 +1,16 @@ package ltd.lulz.model +import jakarta.validation.constraints.DecimalMin +import jakarta.validation.constraints.NotEmpty import java.math.BigDecimal import java.util.UUID object Account { + data class Request( + @field:NotEmpty val name: String, + @field:DecimalMin(value = "0.01") val amount: BigDecimal, ) diff --git a/src/test/kotlin/ltd/lulz/controller/AccountControllerTest.kt b/src/test/kotlin/ltd/lulz/controller/AccountControllerTest.kt new file mode 100644 index 0000000..3f06e0a --- /dev/null +++ b/src/test/kotlin/ltd/lulz/controller/AccountControllerTest.kt @@ -0,0 +1,88 @@ +package ltd.lulz.controller + +import io.mockk.every +import io.mockk.mockk +import java.math.BigDecimal +import java.util.UUID +import ltd.lulz.model.Account +import ltd.lulz.model.AccountEntity +import ltd.lulz.service.AccountService +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.http.MediaType.APPLICATION_JSON +import org.springframework.test.web.reactive.server.WebTestClient +import reactor.core.publisher.Mono + +@Suppress("MayBeConstant") +class AccountControllerTest { + + companion object { + val name: String = "some name" + val amount: BigDecimal = BigDecimal.valueOf(0.01) + val uuid: UUID = UUID.fromString("00000000-0000-0000-0000-000000000000") + } + + private val accountService: AccountService = mockk() + private lateinit var webTestClient: WebTestClient + + @BeforeEach + fun setUp() { + webTestClient = WebTestClient.bindToController(AccountController(accountService)).build() + } + + @Test + fun `create account success`() { + // given + val request = Account.Request(name, amount) + + every { accountService.create(any()) } returns Mono.just( + AccountEntity(id = uuid, name = name, amount = amount), + ) + + // when + val result = webTestClient.post() + .uri("/account") + .contentType(APPLICATION_JSON) + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isCreated + .expectBody() + .jsonPath("$.id").isEqualTo(uuid.toString()) + .jsonPath("$.name").isEqualTo(name) + .jsonPath("$.amount").isEqualTo(amount.toString()) + } + + @Test + fun `create account fail no name`() { + // given + val request = Account.Request("", amount) + + // when + val result = webTestClient.post() + .uri("/account") + .contentType(APPLICATION_JSON) + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isBadRequest + } + + @Test + fun `create account fail zero or less amount`() { + // given + val request = Account.Request("name", BigDecimal.valueOf(0)) + + // when + val result = webTestClient.post() + .uri("/account") + .contentType(APPLICATION_JSON) + .bodyValue(request) + .exchange() + + // then + result.expectStatus().isBadRequest + } +}