generated from pricelees/issue-pr-template
add application code with E2E tests
This commit is contained in:
parent
55856659dd
commit
68a0724e16
48
build.gradle
48
build.gradle
@ -1,48 +0,0 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.9.25'
|
||||
id 'org.jetbrains.kotlin.plugin.spring' version '1.9.25'
|
||||
id 'org.springframework.boot' version '3.5.3'
|
||||
id 'io.spring.dependency-management' version '1.1.7'
|
||||
}
|
||||
|
||||
group = 'com.sangdol'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
compileOnly {
|
||||
extendsFrom annotationProcessor
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-reflect'
|
||||
implementation 'io.github.oshai:kotlin-logging-jvm:7.0.3'
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.addAll '-Xjsr305=strict'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
57
build.gradle.kts
Normal file
57
build.gradle.kts
Normal file
@ -0,0 +1,57 @@
|
||||
plugins {
|
||||
val springBootVersion = "3.5.3"
|
||||
val springDependencyVersion = "1.1.7"
|
||||
val kotlinVersion = "2.2.0"
|
||||
|
||||
id("org.springframework.boot") version springBootVersion
|
||||
id("io.spring.dependency-management") version springDependencyVersion
|
||||
|
||||
//kotlin plugins
|
||||
kotlin("jvm") version kotlinVersion
|
||||
kotlin("plugin.spring") version kotlinVersion
|
||||
}
|
||||
|
||||
group = "com.sangdol"
|
||||
version = "0.0.1-SNAPSHOT"
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.springframework.boot:spring-boot-starter-validation")
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
implementation("io.github.oshai:kotlin-logging-jvm:7.0.3")
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
|
||||
tasks {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-Xjsr305=strict")
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
||||
freeCompilerArgs.set(listOf("-Xannotation-default-target=param-property"))
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
||||
freeCompilerArgs.set(listOf("-Xannotation-default-target=param-property"))
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/main/kotlin/com/sangdol/validation/DemoController.kt
Normal file
40
src/main/kotlin/com/sangdol/validation/DemoController.kt
Normal file
@ -0,0 +1,40 @@
|
||||
package com.sangdol.validation
|
||||
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
class DemoController(
|
||||
private val demoService: DemoService
|
||||
) {
|
||||
|
||||
@PostMapping("/solution")
|
||||
fun solution(
|
||||
@RequestBody @Valid request: SolutionForPrimitive
|
||||
): ResponseEntity<Void> {
|
||||
demoService.doSomething(request)
|
||||
|
||||
return ResponseEntity.ok().build()
|
||||
}
|
||||
|
||||
@PostMapping("/wrapper")
|
||||
fun wrapper(
|
||||
@RequestBody @Valid request: WrapperRequest
|
||||
): ResponseEntity<Void> {
|
||||
demoService.doSomething(request)
|
||||
|
||||
return ResponseEntity.ok().build()
|
||||
}
|
||||
|
||||
@PostMapping("/primitive")
|
||||
fun primitive(
|
||||
@RequestBody @Valid request: PrimitiveRequest
|
||||
): ResponseEntity<Void> {
|
||||
demoService.doSomething(request)
|
||||
|
||||
return ResponseEntity.ok().build()
|
||||
}
|
||||
}
|
||||
28
src/main/kotlin/com/sangdol/validation/DemoDTO.kt
Normal file
28
src/main/kotlin/com/sangdol/validation/DemoDTO.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package com.sangdol.validation
|
||||
|
||||
import jakarta.validation.constraints.NotNull
|
||||
|
||||
|
||||
data class WrapperRequest(
|
||||
@NotNull
|
||||
val withAnnotation: String,
|
||||
|
||||
val withoutAnnotation: String
|
||||
)
|
||||
|
||||
data class PrimitiveRequest(
|
||||
@NotNull
|
||||
val withAnnotation: Int,
|
||||
|
||||
val withoutAnnotation: Int
|
||||
)
|
||||
|
||||
data class SolutionForPrimitive(
|
||||
@NotNull
|
||||
val value: Int?
|
||||
)
|
||||
|
||||
data class ErrorResponse(
|
||||
val type: String,
|
||||
val message: String
|
||||
)
|
||||
@ -0,0 +1,40 @@
|
||||
package com.sangdol.validation
|
||||
|
||||
import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||
|
||||
@RestControllerAdvice
|
||||
class DemoExceptionHandler(
|
||||
private val log: KLogger = KotlinLogging.logger {}
|
||||
) {
|
||||
|
||||
@ExceptionHandler(value = [HttpMessageNotReadableException::class, MethodArgumentNotValidException::class])
|
||||
fun handleHttpMessageNotReadable(e: Exception): ResponseEntity<ErrorResponse> {
|
||||
log.warn { "${e.javaClass} 발생!" }
|
||||
|
||||
return ResponseEntity.badRequest()
|
||||
.body(
|
||||
ErrorResponse(
|
||||
e.javaClass.toString(),
|
||||
e.message ?: "Empty Messages"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception::class)
|
||||
fun handleException(e: Exception): ResponseEntity<ErrorResponse> {
|
||||
log.error { "${e.javaClass} 발생!" }
|
||||
|
||||
return ResponseEntity.internalServerError().body(
|
||||
ErrorResponse(
|
||||
e.javaClass.toString(),
|
||||
"Internal Server Error"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
26
src/main/kotlin/com/sangdol/validation/DemoService.kt
Normal file
26
src/main/kotlin/com/sangdol/validation/DemoService.kt
Normal file
@ -0,0 +1,26 @@
|
||||
package com.sangdol.validation
|
||||
|
||||
import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class DemoService(
|
||||
private val log: KLogger = KotlinLogging.logger {},
|
||||
) {
|
||||
|
||||
fun doSomething(request: SolutionForPrimitive) {
|
||||
log.info { "solution for primitive requests: $request" }
|
||||
log.info { "value: ${request.value!!}" }
|
||||
}
|
||||
|
||||
fun doSomething(request: WrapperRequest) {
|
||||
log.info { "wrapper requests: $request." }
|
||||
log.info { "withAnnotation: ${request.withAnnotation}, withoutAnnotation: ${request.withoutAnnotation}" }
|
||||
}
|
||||
|
||||
fun doSomething(request: PrimitiveRequest) {
|
||||
log.info { "primitive requests: $request." }
|
||||
log.info { "withAnnotation: ${request.withAnnotation}, withoutAnnotation: ${request.withoutAnnotation}" }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package com.sangdol.validation
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
|
||||
@SpringBootApplication
|
||||
class ValidationApplication
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<ValidationApplication>(*args)
|
||||
}
|
||||
1
src/main/resources/application.properties
Normal file
1
src/main/resources/application.properties
Normal file
@ -0,0 +1 @@
|
||||
spring.application.name=validation
|
||||
@ -1,3 +0,0 @@
|
||||
GET http://localhost:8080/numbers
|
||||
Accept: application/json
|
||||
|
||||
100
src/test/kotlin/com/sangdol/validation/DemoControllerTest.kt
Normal file
100
src/test/kotlin/com/sangdol/validation/DemoControllerTest.kt
Normal file
@ -0,0 +1,100 @@
|
||||
package com.sangdol.validation
|
||||
|
||||
import org.hamcrest.CoreMatchers.containsString
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.CsvSource
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.ResultActions
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
|
||||
@WebMvcTest(controllers = [DemoController::class])
|
||||
@MockitoBean(types = [DemoService::class])
|
||||
class DemoControllerTest {
|
||||
|
||||
@Autowired
|
||||
private val mockMvc: MockMvc? = null
|
||||
|
||||
@DisplayName("Wrapper 타입인 경우 값이 입력되지 않으면 HttpMessageNotReadableException이 발생한다.")
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@CsvSource(
|
||||
value = [
|
||||
"@NotNull 필드의 값만 입력되는 경우 ; withAnnotation",
|
||||
"@NotNull이 아닌 필드의 값만 입력되는 경우 ; withoutAnnotation"
|
||||
],
|
||||
delimiter = ';'
|
||||
)
|
||||
fun except_wrapper_field(testName: String, field: String) {
|
||||
val endpoint = "/wrapper"
|
||||
val value = "message"
|
||||
val body = "{\"$field\": \"$value\"}"
|
||||
|
||||
runException(endpoint, body, HttpStatus.BAD_REQUEST.value(), "HttpMessageNotReadableException")
|
||||
}
|
||||
|
||||
@DisplayName("Primitive 타입인 경우 @NotNull 지정과 무관하게 값이 입력되지 않아도 기본값이 들어간다.")
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@CsvSource(
|
||||
value = [
|
||||
"@NotNull 필드의 값만 입력되는 경우 ; withAnnotation",
|
||||
"@NotNull이 아닌 필드의 값만 입력되는 경우 ; withoutAnnotation"
|
||||
],
|
||||
delimiter = ';'
|
||||
)
|
||||
fun except_primitive_field(testName: String, field: String) {
|
||||
val endpoint = "/primitive"
|
||||
val value = 10
|
||||
val body = "{\"$field\": $value}"
|
||||
|
||||
run(endpoint, body, HttpStatus.OK.value())
|
||||
}
|
||||
|
||||
@DisplayName("Primitive 타입인 경우 Bean Validation을 위해서는 @NotNull + nullable 필드 지정이 필요하다.")
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@CsvSource(
|
||||
value = [
|
||||
"""필드가 없는 경우 ; {"notExistField": 10} ; 400""",
|
||||
"""필드가 Null인 경우 ; {"value": null} ; 400""",
|
||||
"""필드의 값이 정상인 경우 ; {"value": 10} ; 200"""
|
||||
], delimiter = ';'
|
||||
)
|
||||
fun primitive_validation_with_nullable_field(testName: String, body: String, expectedStatus: Int) {
|
||||
val endpoint = "/solution"
|
||||
|
||||
if (expectedStatus == HttpStatus.OK.value()) {
|
||||
run(endpoint, body, expectedStatus)
|
||||
} else {
|
||||
runException(endpoint, body, expectedStatus, "MethodArgumentNotValidException")
|
||||
}
|
||||
}
|
||||
|
||||
private fun run(endpoint: String, body: String, expectedStatus: Int): ResultActions = mockMvc!!
|
||||
.perform(
|
||||
post(endpoint)
|
||||
.contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
.content(body)
|
||||
)
|
||||
.andExpect(status().`is`(expectedStatus))
|
||||
.andDo(print())
|
||||
|
||||
|
||||
private fun runException(
|
||||
endpoint: String,
|
||||
body: String,
|
||||
expectedStatus: Int,
|
||||
expectedExceptionType: String
|
||||
): ResultActions = run(endpoint, body, expectedStatus)
|
||||
.andExpect(
|
||||
jsonPath(
|
||||
"$.type", containsString(expectedExceptionType)
|
||||
)
|
||||
)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user