Skip to content

Commit

Permalink
feat: Add pretotype and basic testing
Browse files Browse the repository at this point in the history
  • Loading branch information
lukeqwaszx committed Dec 25, 2024
1 parent fa987fa commit 38dde39
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 0 deletions.
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")

developmentOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testImplementation("com.h2database:h2:2.2.220")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

implementation("org.hibernate.validator:hibernate-validator:8.0.0.Final")
Expand Down
17 changes: 17 additions & 0 deletions src/main/kotlin/com/waffletoy/team1server/DomainException.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.waffletoy.team1server

import org.springframework.http.HttpStatus
import org.springframework.http.HttpStatusCode

open class DomainException(
// client 와 약속된 Application Error 에 대한 코드 필요 시 Enum 으로 관리하자.
val errorCode: Int,
// HTTP Status Code, 비어있다면 500 이다.
val httpErrorCode: HttpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR,
val msg: String,
cause: Throwable? = null,
) : RuntimeException(msg, cause) {
override fun toString(): String {
return "com.waffletoy.team1server.DomainException(msg='$msg', errorCode=$errorCode, httpErrorCode=$httpErrorCode)"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.waffletoy.team1server

import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler

@ControllerAdvice
class GlobalControllerExceptionHandler {
@ExceptionHandler(DomainException::class)
fun handle(exception: DomainException): ResponseEntity<Map<String, Any>> {
return ResponseEntity
.status(exception.httpErrorCode)
.body(mapOf("error" to exception.msg, "errorCode" to exception.errorCode))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.waffletoy.team1server.pretotype

import com.waffletoy.team1server.DomainException
import org.springframework.http.HttpStatus

class PretotypeEmailConflictException: DomainException(0, HttpStatus.CONFLICT, "Email already exists")
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.waffletoy.team1server.pretotype.controller

import com.waffletoy.team1server.pretotype.persistence.PretotypeEntity
import java.time.Instant

class Pretotype (
val email: String,
val isSubscribed: Boolean,
val createdAt: Instant,
) {
companion object {
fun fromEntity(entity: PretotypeEntity): Pretotype {
return Pretotype(
email = entity.email,
isSubscribed = entity.isSubscribed,
createdAt = entity.createdAt
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.waffletoy.team1server.pretotype.controller

import com.waffletoy.team1server.pretotype.service.PretotypeService
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController

@RestController
class TestResponseController (
private val pretotypeService: PretotypeService,
){
@PostMapping("/api/pretotype")
fun createPretotype(
@RequestBody request: PretotypeRequest
): ResponseEntity<Pretotype> {
val pretotype = pretotypeService.createPretotype(request.email, request.isSubscribed)
return ResponseEntity.ok(pretotype)
}

@GetMapping("/api/pretotype/list")
fun listPretotypes(): ResponseEntity<List<Pretotype>> {
val pretotypes = pretotypeService.listPretotypes()
return ResponseEntity.ok(pretotypes)
}
}

data class PretotypeRequest(
val email: String,
val isSubscribed: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.waffletoy.team1server.pretotype.persistence

import jakarta.persistence.*
import java.time.Instant

@Entity
@Table(name = "pretotypes")
class PretotypeEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
@Column(unique = true)
var email: String = "",
@Column(name = "is_subscribed")
var isSubscribed: Boolean = false,
@Column(name = "created_at")
var createdAt: Instant = Instant.now()
) {
// No-args constructor for Hibernate
constructor() : this(null, "", false, Instant.now())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.waffletoy.team1server.pretotype.persistence

import org.springframework.data.jpa.repository.JpaRepository

interface PretotypeRepository: JpaRepository<PretotypeEntity, String> {
fun findByEmail(email: String): PretotypeEntity?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.waffletoy.team1server.pretotype.service

import com.waffletoy.team1server.pretotype.PretotypeEmailConflictException
import com.waffletoy.team1server.pretotype.controller.Pretotype
import com.waffletoy.team1server.pretotype.persistence.PretotypeEntity
import com.waffletoy.team1server.pretotype.persistence.PretotypeRepository
import jakarta.transaction.Transactional
import org.springframework.stereotype.Service
import java.time.Instant

@Service
class PretotypeService (
private val pretotypeRepository: PretotypeRepository
){
@Transactional
fun createPretotype(
email: String,
isSubscribed: Boolean,
): Pretotype {
pretotypeRepository.findByEmail(email) ?.let {
throw PretotypeEmailConflictException()
} ?: run {
val pretotypeEntity = PretotypeEntity(
email = email,
isSubscribed = isSubscribed,
createdAt = Instant.now()
)
pretotypeRepository.save(pretotypeEntity)
return Pretotype.fromEntity(pretotypeEntity)
}
}

fun listPretotypes(): List<Pretotype> {
return pretotypeRepository.findAll().map {
Pretotype.fromEntity(it)
}
}
}
13 changes: 13 additions & 0 deletions src/test/kotlin/com/waffletoy/team1server/ApplicationTests.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
package com.waffletoy.team1server

import com.waffletoy.team1server.pretotype.service.PretotypeService
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.hasSize
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest

@SpringBootTest
class ApplicationTests {
@Autowired
private val pretotypeService: PretotypeService? = null

@Test
fun contextLoads() {
}

@Test
fun whenPretotypeAdded_thenOneItemInList() {
pretotypeService!!.createPretotype("[email protected]", isSubscribed = false)
assertThat(pretotypeService.listPretotypes(), hasSize(1))
}
}

0 comments on commit 38dde39

Please sign in to comment.