diff --git a/.gitea/ISSUE_TEMPLATE/0feature.yml b/.gitea/ISSUE_TEMPLATE/0feature.yml index 8ed56579..1dd31388 100644 --- a/.gitea/ISSUE_TEMPLATE/0feature.yml +++ b/.gitea/ISSUE_TEMPLATE/0feature.yml @@ -12,16 +12,12 @@ body: label: 배경 description: 이 기능을 추가하게 된 배경을 작성해주세요. placeholder: 기능을 추가하기 전 상황은 간단하게, 추가하게 된 이유는 가급적 자세하게 작성해주세요! - validations: - required: true - type: textarea id: changes attributes: label: 작업 내용 description: 필요한 작업 내용을 작성해주세요. placeholder: 작업과 그 작업의 예상 결과를 작성해주세요! - validations: - required: true - type: textarea id: notes attributes: diff --git a/.gitea/ISSUE_TEMPLATE/1bug.yml b/.gitea/ISSUE_TEMPLATE/1bug.yml index c74f46d0..4c603a7c 100644 --- a/.gitea/ISSUE_TEMPLATE/1bug.yml +++ b/.gitea/ISSUE_TEMPLATE/1bug.yml @@ -12,8 +12,6 @@ body: label: 배경 description: 발생한 버그가 어떤 상황에서 발생했는지 작성해주세요. placeholder: 버그 발생 시나리오, 재현 방법, 로그 등을 자세히 작성해주세요! - validations: - required: true - type: textarea id: changes attributes: diff --git a/.gitea/ISSUE_TEMPLATE/2refactor.yml b/.gitea/ISSUE_TEMPLATE/2refactor.yml index 2c5295a9..ad279a9f 100644 --- a/.gitea/ISSUE_TEMPLATE/2refactor.yml +++ b/.gitea/ISSUE_TEMPLATE/2refactor.yml @@ -12,16 +12,12 @@ body: label: 배경 description: 리팩터링을 결정하게 된 배경을 작성해주세요. placeholder: 현재 코드의 문제점, 리팩토링을 통해 얻을 수 있는 이점 등을 상세히 작성해 주세요! - validations: - required: true - type: textarea id: changes attributes: label: 작업 내용 description: 필요한 작업 내용을 작성해주세요. placeholder: 작업과 그 작업의 예상 결과를 작성해주세요! - validations: - required: true - type: textarea id: notes attributes: diff --git a/.gitea/ISSUE_TEMPLATE/3Infra.yml b/.gitea/ISSUE_TEMPLATE/3Infra.yml index c369514b..7e48cdd9 100644 --- a/.gitea/ISSUE_TEMPLATE/3Infra.yml +++ b/.gitea/ISSUE_TEMPLATE/3Infra.yml @@ -12,8 +12,6 @@ body: label: 배경 description: 작업의 배경과 상황을 작성해주세요. placeholder: 구체적으로 작성할 수록 좋아요! - validations: - required: true - type: textarea id: changes attributes: diff --git a/src/main/java/roomescape/system/auth/infrastructure/jwt/JwtHandler.kt b/src/main/java/roomescape/auth/infrastructure/jwt/JwtHandler.kt similarity index 75% rename from src/main/java/roomescape/system/auth/infrastructure/jwt/JwtHandler.kt rename to src/main/java/roomescape/auth/infrastructure/jwt/JwtHandler.kt index 4afd8e3e..649fe125 100644 --- a/src/main/java/roomescape/system/auth/infrastructure/jwt/JwtHandler.kt +++ b/src/main/java/roomescape/auth/infrastructure/jwt/JwtHandler.kt @@ -1,11 +1,11 @@ -package roomescape.system.auth.infrastructure.jwt +package roomescape.auth.infrastructure.jwt import io.jsonwebtoken.* import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpStatus import org.springframework.stereotype.Component -import roomescape.system.exception.ErrorType -import roomescape.system.exception.RoomEscapeException +import roomescape.common.exception.ErrorType +import roomescape.common.exception.RoomescapeException import java.util.* @Component @@ -38,12 +38,12 @@ class JwtHandler( .toLong() } catch (e: Exception) { when (e) { - is ExpiredJwtException -> throw RoomEscapeException(ErrorType.EXPIRED_TOKEN, HttpStatus.UNAUTHORIZED) - is UnsupportedJwtException -> throw RoomEscapeException(ErrorType.UNSUPPORTED_TOKEN, HttpStatus.UNAUTHORIZED) - is MalformedJwtException -> throw RoomEscapeException(ErrorType.MALFORMED_TOKEN, HttpStatus.UNAUTHORIZED) - is SignatureException -> throw RoomEscapeException(ErrorType.INVALID_SIGNATURE_TOKEN, HttpStatus.UNAUTHORIZED) - is IllegalArgumentException -> throw RoomEscapeException(ErrorType.INVALID_TOKEN, HttpStatus.UNAUTHORIZED) - else -> throw RoomEscapeException(ErrorType.UNEXPECTED_ERROR, HttpStatus.INTERNAL_SERVER_ERROR) + is ExpiredJwtException -> throw RoomescapeException(ErrorType.EXPIRED_TOKEN, HttpStatus.UNAUTHORIZED) + is UnsupportedJwtException -> throw RoomescapeException(ErrorType.UNSUPPORTED_TOKEN, HttpStatus.UNAUTHORIZED) + is MalformedJwtException -> throw RoomescapeException(ErrorType.MALFORMED_TOKEN, HttpStatus.UNAUTHORIZED) + is SignatureException -> throw RoomescapeException(ErrorType.INVALID_SIGNATURE_TOKEN, HttpStatus.UNAUTHORIZED) + is IllegalArgumentException -> throw RoomescapeException(ErrorType.INVALID_TOKEN, HttpStatus.UNAUTHORIZED) + else -> throw RoomescapeException(ErrorType.UNEXPECTED_ERROR, HttpStatus.INTERNAL_SERVER_ERROR) } } } diff --git a/src/main/java/roomescape/system/auth/service/AuthService.kt b/src/main/java/roomescape/auth/service/AuthService.kt similarity index 75% rename from src/main/java/roomescape/system/auth/service/AuthService.kt rename to src/main/java/roomescape/auth/service/AuthService.kt index b6394130..a5f50636 100644 --- a/src/main/java/roomescape/system/auth/service/AuthService.kt +++ b/src/main/java/roomescape/auth/service/AuthService.kt @@ -1,12 +1,12 @@ -package roomescape.system.auth.service +package roomescape.auth.service import org.springframework.stereotype.Service import roomescape.member.business.MemberService import roomescape.member.infrastructure.persistence.Member -import roomescape.system.auth.infrastructure.jwt.JwtHandler -import roomescape.system.auth.web.LoginCheckResponse -import roomescape.system.auth.web.LoginRequest -import roomescape.system.auth.web.TokenResponse +import roomescape.auth.infrastructure.jwt.JwtHandler +import roomescape.auth.web.LoginCheckResponse +import roomescape.auth.web.LoginRequest +import roomescape.auth.web.TokenResponse @Service class AuthService( diff --git a/src/main/java/roomescape/system/auth/web/AuthAPI.kt b/src/main/java/roomescape/auth/web/AuthAPI.kt similarity index 91% rename from src/main/java/roomescape/system/auth/web/AuthAPI.kt rename to src/main/java/roomescape/auth/web/AuthAPI.kt index 27dd2ecd..12abbce9 100644 --- a/src/main/java/roomescape/system/auth/web/AuthAPI.kt +++ b/src/main/java/roomescape/auth/web/AuthAPI.kt @@ -1,4 +1,4 @@ -package roomescape.system.auth.web +package roomescape.auth.web import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter @@ -13,10 +13,10 @@ import jakarta.validation.Valid import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.ResponseStatus -import roomescape.system.auth.web.support.LoginRequired -import roomescape.system.auth.web.support.MemberId -import roomescape.system.dto.response.ErrorResponse -import roomescape.system.dto.response.RoomEscapeApiResponse +import roomescape.auth.web.support.LoginRequired +import roomescape.auth.web.support.MemberId +import roomescape.common.dto.response.ErrorResponse +import roomescape.common.dto.response.RoomEscapeApiResponse @Tag(name = "1. 인증 / 인가 API", description = "로그인, 로그아웃 및 로그인 상태를 확인합니다") interface AuthAPI { diff --git a/src/main/java/roomescape/system/auth/web/AuthController.kt b/src/main/java/roomescape/auth/web/AuthController.kt similarity index 81% rename from src/main/java/roomescape/system/auth/web/AuthController.kt rename to src/main/java/roomescape/auth/web/AuthController.kt index 2008a124..d3dfbf9d 100644 --- a/src/main/java/roomescape/system/auth/web/AuthController.kt +++ b/src/main/java/roomescape/auth/web/AuthController.kt @@ -1,4 +1,4 @@ -package roomescape.system.auth.web +package roomescape.auth.web import io.swagger.v3.oas.annotations.Parameter import jakarta.servlet.http.Cookie @@ -9,9 +9,13 @@ 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 -import roomescape.system.auth.service.AuthService -import roomescape.system.auth.web.support.* -import roomescape.system.dto.response.RoomEscapeApiResponse +import roomescape.auth.service.AuthService +import roomescape.auth.web.support.MemberId +import roomescape.auth.web.support.accessTokenCookie +import roomescape.auth.web.support.addAccessTokenCookie +import roomescape.auth.web.support.expire +import roomescape.auth.web.support.toCookie +import roomescape.common.dto.response.RoomEscapeApiResponse @RestController class AuthController( diff --git a/src/main/java/roomescape/system/auth/web/AuthDTO.kt b/src/main/java/roomescape/auth/web/AuthDTO.kt similarity index 96% rename from src/main/java/roomescape/system/auth/web/AuthDTO.kt rename to src/main/java/roomescape/auth/web/AuthDTO.kt index 05ef0b32..15a87d24 100644 --- a/src/main/java/roomescape/system/auth/web/AuthDTO.kt +++ b/src/main/java/roomescape/auth/web/AuthDTO.kt @@ -1,4 +1,4 @@ -package roomescape.system.auth.web +package roomescape.auth.web import io.swagger.v3.oas.annotations.media.Schema import jakarta.validation.constraints.Email diff --git a/src/main/java/roomescape/system/auth/web/support/AuthAnnotations.kt b/src/main/java/roomescape/auth/web/support/AuthAnnotations.kt similarity index 87% rename from src/main/java/roomescape/system/auth/web/support/AuthAnnotations.kt rename to src/main/java/roomescape/auth/web/support/AuthAnnotations.kt index 77bb3090..3f98cdf1 100644 --- a/src/main/java/roomescape/system/auth/web/support/AuthAnnotations.kt +++ b/src/main/java/roomescape/auth/web/support/AuthAnnotations.kt @@ -1,4 +1,4 @@ -package roomescape.system.auth.web.support +package roomescape.auth.web.support @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) diff --git a/src/main/java/roomescape/system/auth/web/support/AuthInterceptors.kt b/src/main/java/roomescape/auth/web/support/AuthInterceptors.kt similarity index 86% rename from src/main/java/roomescape/system/auth/web/support/AuthInterceptors.kt rename to src/main/java/roomescape/auth/web/support/AuthInterceptors.kt index 7ef82712..869f734e 100644 --- a/src/main/java/roomescape/system/auth/web/support/AuthInterceptors.kt +++ b/src/main/java/roomescape/auth/web/support/AuthInterceptors.kt @@ -1,4 +1,4 @@ -package roomescape.system.auth.web.support +package roomescape.auth.web.support import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse @@ -8,9 +8,9 @@ import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.HandlerInterceptor import roomescape.member.business.MemberService import roomescape.member.infrastructure.persistence.Member -import roomescape.system.auth.infrastructure.jwt.JwtHandler -import roomescape.system.exception.ErrorType -import roomescape.system.exception.RoomEscapeException +import roomescape.auth.infrastructure.jwt.JwtHandler +import roomescape.common.exception.ErrorType +import roomescape.common.exception.RoomescapeException private fun Any.isIrrelevantWith(annotationType: Class): Boolean { if (this !is HandlerMethod) { @@ -40,9 +40,9 @@ class LoginInterceptor( val memberId: Long = jwtHandler.getMemberIdFromToken(token) return memberService.existsById(memberId) - } catch (e: RoomEscapeException) { + } catch (e: RoomescapeException) { response.sendRedirect("/login") - throw RoomEscapeException(ErrorType.LOGIN_REQUIRED, HttpStatus.FORBIDDEN) + throw RoomescapeException(ErrorType.LOGIN_REQUIRED, HttpStatus.FORBIDDEN) } } } @@ -69,7 +69,7 @@ class AdminInterceptor( val token: String? = request.accessTokenCookie().value val memberId: Long = jwtHandler.getMemberIdFromToken(token) member = memberService.findById(memberId) - } catch (e: RoomEscapeException) { + } catch (e: RoomescapeException) { response.sendRedirect("/login") throw e } @@ -80,7 +80,7 @@ class AdminInterceptor( } response.sendRedirect("/login") - throw RoomEscapeException( + throw RoomescapeException( ErrorType.PERMISSION_DOES_NOT_EXIST, String.format("[memberId: %d, Role: %s]", this.id, this.role), HttpStatus.FORBIDDEN diff --git a/src/main/java/roomescape/system/auth/web/support/CookieUtils.kt b/src/main/java/roomescape/auth/web/support/CookieUtils.kt similarity index 88% rename from src/main/java/roomescape/system/auth/web/support/CookieUtils.kt rename to src/main/java/roomescape/auth/web/support/CookieUtils.kt index 77d33b44..bedcaf11 100644 --- a/src/main/java/roomescape/system/auth/web/support/CookieUtils.kt +++ b/src/main/java/roomescape/auth/web/support/CookieUtils.kt @@ -1,9 +1,9 @@ -package roomescape.system.auth.web.support +package roomescape.auth.web.support import jakarta.servlet.http.Cookie import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse -import roomescape.system.auth.web.TokenResponse +import roomescape.auth.web.TokenResponse const val ACCESS_TOKEN_COOKIE_NAME = "accessToken" diff --git a/src/main/java/roomescape/system/auth/web/support/MemberIdResolver.kt b/src/main/java/roomescape/auth/web/support/MemberIdResolver.kt similarity index 91% rename from src/main/java/roomescape/system/auth/web/support/MemberIdResolver.kt rename to src/main/java/roomescape/auth/web/support/MemberIdResolver.kt index a22d5fc5..dfd07804 100644 --- a/src/main/java/roomescape/system/auth/web/support/MemberIdResolver.kt +++ b/src/main/java/roomescape/auth/web/support/MemberIdResolver.kt @@ -1,4 +1,4 @@ -package roomescape.system.auth.web.support +package roomescape.auth.web.support import jakarta.servlet.http.HttpServletRequest import org.springframework.core.MethodParameter @@ -7,7 +7,7 @@ import org.springframework.web.bind.support.WebDataBinderFactory import org.springframework.web.context.request.NativeWebRequest import org.springframework.web.method.support.HandlerMethodArgumentResolver import org.springframework.web.method.support.ModelAndViewContainer -import roomescape.system.auth.infrastructure.jwt.JwtHandler +import roomescape.auth.infrastructure.jwt.JwtHandler @Component class MemberIdResolver( diff --git a/src/main/java/roomescape/common/config/JacksonConfig.kt b/src/main/java/roomescape/common/config/JacksonConfig.kt new file mode 100644 index 00000000..418ddbb9 --- /dev/null +++ b/src/main/java/roomescape/common/config/JacksonConfig.kt @@ -0,0 +1,41 @@ +package roomescape.common.config + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer +import com.fasterxml.jackson.module.kotlin.kotlinModule +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.time.LocalDate +import java.time.LocalTime +import java.time.format.DateTimeFormatter + +@Configuration +class JacksonConfig { + + @Bean + fun objectMapper(): ObjectMapper = ObjectMapper() + .registerModule(javaTimeModule()) + .registerModule(kotlinModule()) + + private fun javaTimeModule(): JavaTimeModule = JavaTimeModule() + .addSerializer( + LocalDate::class.java, + LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE) + ) + .addDeserializer( + LocalDate::class.java, + LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE) + ) + .addSerializer( + LocalTime::class.java, + LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm")) + ) + .addDeserializer( + LocalTime::class.java, + LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm")) + ) as JavaTimeModule +} diff --git a/src/main/java/roomescape/system/config/SwaggerConfig.java b/src/main/java/roomescape/common/config/SwaggerConfig.kt similarity index 71% rename from src/main/java/roomescape/system/config/SwaggerConfig.java rename to src/main/java/roomescape/common/config/SwaggerConfig.kt index 0045f4fb..fc251e03 100644 --- a/src/main/java/roomescape/system/config/SwaggerConfig.java +++ b/src/main/java/roomescape/common/config/SwaggerConfig.kt @@ -1,23 +1,22 @@ -package roomescape.system.config; +package roomescape.common.config -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration @Configuration -public class SwaggerConfig { +class SwaggerConfig { - @Bean - public OpenAPI openAPI() { - return new OpenAPI().info(apiInfo()); - } + @Bean + fun openAPI(): OpenAPI { + return OpenAPI().info(apiInfo()) + } - private Info apiInfo() { - return new Info() - .title("방탈출 예약 API 문서") - .description(""" + private fun apiInfo(): Info { + return Info() + .title("방탈출 예약 API 문서") + .description(""" ## API 테스트는 '1. 인증 / 인가 API' 의 '/login' 을 통해 로그인 후 사용해주세요. ### 테스트시 로그인 가능한 계정 정보 @@ -70,7 +69,8 @@ public class SwaggerConfig { - 7: 예약은 승인되었으나, 결제 대기 상태 - 8 ~ 10: 예약 대기 상태 - """) - .version("1.0.0"); - } + + """.trimIndent()) + .version("1.0.0") + } } diff --git a/src/main/java/roomescape/common/config/WebMvcConfig.kt b/src/main/java/roomescape/common/config/WebMvcConfig.kt new file mode 100644 index 00000000..2e1c2286 --- /dev/null +++ b/src/main/java/roomescape/common/config/WebMvcConfig.kt @@ -0,0 +1,26 @@ +package roomescape.common.config + +import org.springframework.context.annotation.Configuration +import org.springframework.web.method.support.HandlerMethodArgumentResolver +import org.springframework.web.servlet.config.annotation.InterceptorRegistry +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer +import roomescape.auth.web.support.AdminInterceptor +import roomescape.auth.web.support.LoginInterceptor +import roomescape.auth.web.support.MemberIdResolver + +@Configuration +class WebMvcConfig( + private val memberIdResolver: MemberIdResolver, + private val adminInterceptor: AdminInterceptor, + private val loginInterceptor: LoginInterceptor +) : WebMvcConfigurer { + + override fun addArgumentResolvers(resolvers: MutableList) { + resolvers.add(memberIdResolver) + } + + override fun addInterceptors(registry: InterceptorRegistry) { + registry.addInterceptor(adminInterceptor) + registry.addInterceptor(loginInterceptor) + } +} diff --git a/src/main/java/roomescape/common/dto/response/ErrorResponse.kt b/src/main/java/roomescape/common/dto/response/ErrorResponse.kt new file mode 100644 index 00000000..3d04e3c0 --- /dev/null +++ b/src/main/java/roomescape/common/dto/response/ErrorResponse.kt @@ -0,0 +1,21 @@ +package roomescape.common.dto.response + +import io.swagger.v3.oas.annotations.media.Schema +import roomescape.common.exception.ErrorType + +@Schema(name = "예외 응답", description = "예외 발생 시 응답에 사용됩니다.") +@JvmRecord +data class ErrorResponse( + @field:Schema(description = "발생한 예외의 종류", example = "INVALID_REQUEST_DATA") + val errorType: ErrorType, + + @field:Schema(description = "예외 메시지", example = "요청 데이터 값이 올바르지 않습니다.") + val message: String +) { + companion object { + @JvmStatic + fun of(errorType: ErrorType, message: String): ErrorResponse { + return ErrorResponse(errorType, message) + } + } +} diff --git a/src/main/java/roomescape/common/dto/response/RoomEscapeApiResponse.kt b/src/main/java/roomescape/common/dto/response/RoomEscapeApiResponse.kt new file mode 100644 index 00000000..15124474 --- /dev/null +++ b/src/main/java/roomescape/common/dto/response/RoomEscapeApiResponse.kt @@ -0,0 +1,27 @@ +package roomescape.common.dto.response + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(description = "API 응답 시에 사용합니다.") +@JvmRecord +data class RoomEscapeApiResponse( + @field:Schema(description = "응답 메시지", defaultValue = SUCCESS_MESSAGE) + val message: String, + + @field:Schema(description = "응답 바디") + val data: T? = null +) { + companion object { + private const val SUCCESS_MESSAGE = "요청이 성공적으로 수행되었습니다." + + @JvmStatic + fun success(data: T): RoomEscapeApiResponse { + return RoomEscapeApiResponse(SUCCESS_MESSAGE, data) + } + + @JvmStatic + fun success(): RoomEscapeApiResponse { + return RoomEscapeApiResponse(SUCCESS_MESSAGE, null) + } + } +} diff --git a/src/main/java/roomescape/common/dto/response/RoomescapeApiResponseKT.kt b/src/main/java/roomescape/common/dto/response/RoomescapeApiResponseKT.kt new file mode 100644 index 00000000..e3e60f74 --- /dev/null +++ b/src/main/java/roomescape/common/dto/response/RoomescapeApiResponseKT.kt @@ -0,0 +1,33 @@ +package roomescape.common.dto.response + +import com.fasterxml.jackson.annotation.JsonInclude +import roomescape.common.exception.ErrorType + + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class RoomescapeApiResponseKT( + val success: Boolean, + val data: T? = null, + val errorType: ErrorType? = null, + val message: String? = null, +) { + companion object { + + @JvmStatic + fun success(data: T? = null): RoomescapeApiResponseKT { + return RoomescapeApiResponseKT( + success = true, + data = data, + ) + } + + @JvmStatic + fun fail(errorType: ErrorType, message: String? = null): RoomescapeApiResponseKT { + return RoomescapeApiResponseKT( + success = false, + errorType = errorType, + message = message ?: errorType.description + ) + } + } +} diff --git a/src/main/java/roomescape/common/exception/ErrorType.kt b/src/main/java/roomescape/common/exception/ErrorType.kt new file mode 100644 index 00000000..abc8e03d --- /dev/null +++ b/src/main/java/roomescape/common/exception/ErrorType.kt @@ -0,0 +1,70 @@ +package roomescape.common.exception + +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import org.springframework.http.HttpStatus + +enum class ErrorType( + @JvmField val description: String +) { + // 400 Bad Request + REQUEST_DATA_BLANK("요청 데이터에 유효하지 않은 값(null OR 공백)이 포함되어있습니다."), + INVALID_REQUEST_DATA_TYPE("요청 데이터 형식이 올바르지 않습니다."), + INVALID_REQUEST_DATA("요청 데이터 값이 올바르지 않습니다."), + INVALID_DATE_RANGE("종료 날짜는 시작 날짜 이전일 수 없습니다."), + HAS_RESERVATION_OR_WAITING("같은 테마에 대한 예약(대기)는 한 번만 가능합니다."), + + // 401 Unauthorized + EXPIRED_TOKEN("토큰이 만료되었습니다. 다시 로그인 해주세요."), + UNSUPPORTED_TOKEN("지원하지 않는 JWT 토큰입니다."), + MALFORMED_TOKEN("형식이 맞지 않는 JWT 토큰입니다."), + INVALID_SIGNATURE_TOKEN("잘못된 JWT 토큰 Signature 입니다."), + ILLEGAL_TOKEN("JWT 토큰의 Claim 이 비어있습니다."), + INVALID_TOKEN("JWT 토큰이 존재하지 않거나 유효하지 않습니다."), + NOT_EXIST_COOKIE("쿠키가 존재하지 않습니다. 로그인이 필요한 서비스입니다."), + + // 403 Forbidden + LOGIN_REQUIRED("로그인이 필요한 서비스입니다."), + PERMISSION_DOES_NOT_EXIST("접근 권한이 존재하지 않습니다."), + + // 404 Not Found + MEMBER_NOT_FOUND("회원(Member) 정보가 존재하지 않습니다."), + RESERVATION_NOT_FOUND("예약(Reservation) 정보가 존재하지 않습니다."), + RESERVATION_TIME_NOT_FOUND("예약 시간(ReservationTime) 정보가 존재하지 않습니다."), + THEME_NOT_FOUND("테마(Theme) 정보가 존재하지 않습니다."), + PAYMENT_NOT_POUND("결제(Payment) 정보가 존재하지 않습니다."), + + // 405 Method Not Allowed + METHOD_NOT_ALLOWED("지원하지 않는 HTTP Method 입니다."), + + // 409 Conflict + TIME_IS_USED_CONFLICT("삭제할 수 없는 시간대입니다. 예약이 존재하는지 확인해주세요."), + THEME_IS_USED_CONFLICT("삭제할 수 없는 테마입니다. 예약이 존재하는지 확인해주세요."), + TIME_DUPLICATED("이미 해당 시간이 존재합니다."), + THEME_DUPLICATED("같은 이름의 테마가 존재합니다."), + RESERVATION_DUPLICATED("해당 시간에 이미 예약이 존재합니다."), + RESERVATION_PERIOD_IN_PAST("이미 지난 시간대는 예약할 수 없습니다."), + CANCELED_BEFORE_PAYMENT("취소 시간이 결제 시간 이전일 수 없습니다."), + + // 500 Internal Server Error, + INTERNAL_SERVER_ERROR("서버 내부에서 에러가 발생하였습니다."), + UNEXPECTED_ERROR("예상치 못한 에러가 발생하였습니다. 잠시 후 다시 시도해주세요."), + + // Payment Error + PAYMENT_ERROR("결제(취소)에 실패했습니다. 결제(취소) 정보를 확인해주세요."), + PAYMENT_SERVER_ERROR("결제 서버에서 에러가 발생하였습니다. 잠시 후 다시 시도해주세요."); + + companion object { + @JvmStatic + @JsonCreator + fun from(@JsonProperty("errorType") errorType: String): ErrorType { + return entries.toTypedArray() + .firstOrNull { it.name == errorType } + ?: throw RoomescapeException( + INVALID_REQUEST_DATA, + "[ErrorType: ${errorType}]", + HttpStatus.BAD_REQUEST + ) + } + } +} diff --git a/src/main/java/roomescape/common/exception/ExceptionControllerAdvice.kt b/src/main/java/roomescape/common/exception/ExceptionControllerAdvice.kt new file mode 100644 index 00000000..8080467c --- /dev/null +++ b/src/main/java/roomescape/common/exception/ExceptionControllerAdvice.kt @@ -0,0 +1,75 @@ +package roomescape.common.exception + +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.servlet.http.HttpServletResponse +import org.springframework.http.HttpStatus +import org.springframework.http.converter.HttpMessageNotReadableException +import org.springframework.web.HttpRequestMethodNotSupportedException +import org.springframework.web.bind.MethodArgumentNotValidException +import org.springframework.web.bind.annotation.ExceptionHandler +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestControllerAdvice +import org.springframework.web.client.ResourceAccessException +import roomescape.common.dto.response.ErrorResponse + +@RestControllerAdvice +class ExceptionControllerAdvice( + private val logger: KLogger = KotlinLogging.logger {} +) { + + @ExceptionHandler(value = [RoomescapeException::class]) + fun handleRoomEscapeException( + e: RoomescapeException, + response: HttpServletResponse + ): ErrorResponse { + logger.error(e) { "message: ${e.message}, invalidValue: ${e.invalidValue}" } + response.status = e.httpStatus.value() + + return ErrorResponse.of(e.errorType, e.message!!) + } + + @ExceptionHandler(ResourceAccessException::class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + fun handleResourceAccessException(e: ResourceAccessException): ErrorResponse { + logger.error(e) { "message: ${e.message}" } + + return ErrorResponse.of(ErrorType.PAYMENT_SERVER_ERROR, ErrorType.PAYMENT_SERVER_ERROR.description) + } + + @ExceptionHandler(value = [HttpMessageNotReadableException::class]) + @ResponseStatus(HttpStatus.BAD_REQUEST) + fun handleHttpMessageNotReadableException(e: HttpMessageNotReadableException): ErrorResponse { + logger.error(e) { "message: ${e.message}" } + + return ErrorResponse.of(ErrorType.INVALID_REQUEST_DATA_TYPE, + ErrorType.INVALID_REQUEST_DATA_TYPE.description) + } + + @ExceptionHandler(value = [MethodArgumentNotValidException::class]) + @ResponseStatus(HttpStatus.BAD_REQUEST) + fun handleMethodArgumentNotValidException(e: MethodArgumentNotValidException): ErrorResponse { + val messages: String = e.bindingResult.allErrors + .mapNotNull { it.defaultMessage } + .joinToString(", ") + logger.error(e) { "message: $messages" } + + return ErrorResponse.of(ErrorType.INVALID_REQUEST_DATA, messages) + } + + @ExceptionHandler(value = [HttpRequestMethodNotSupportedException::class]) + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + fun handleHttpRequestMethodNotSupportedException(e: HttpRequestMethodNotSupportedException): ErrorResponse { + logger.error(e) { "message: ${e.message}" } + + return ErrorResponse.of(ErrorType.METHOD_NOT_ALLOWED, ErrorType.METHOD_NOT_ALLOWED.description) + } + + @ExceptionHandler(value = [Exception::class]) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + fun handleException(e: Exception): ErrorResponse { + logger.error(e) { "message: ${e.message}" } + + return ErrorResponse.of(ErrorType.UNEXPECTED_ERROR, ErrorType.UNEXPECTED_ERROR.description) + } +} diff --git a/src/main/java/roomescape/common/exception/RoomescapeException.kt b/src/main/java/roomescape/common/exception/RoomescapeException.kt new file mode 100644 index 00000000..d6a8cb07 --- /dev/null +++ b/src/main/java/roomescape/common/exception/RoomescapeException.kt @@ -0,0 +1,11 @@ +package roomescape.common.exception + +import org.springframework.http.HttpStatusCode + +class RoomescapeException( + val errorType: ErrorType, + val invalidValue: String? = "", + val httpStatus: HttpStatusCode, +) : RuntimeException(errorType.description) { + constructor(errorType: ErrorType, httpStatus: HttpStatusCode) : this(errorType, null, httpStatus) +} diff --git a/src/main/java/roomescape/member/business/MemberService.kt b/src/main/java/roomescape/member/business/MemberService.kt index f52269f4..6ac50940 100644 --- a/src/main/java/roomescape/member/business/MemberService.kt +++ b/src/main/java/roomescape/member/business/MemberService.kt @@ -8,8 +8,8 @@ import roomescape.member.infrastructure.persistence.Member import roomescape.member.infrastructure.persistence.MemberRepository import roomescape.member.web.MembersResponse import roomescape.member.web.toResponse -import roomescape.system.exception.ErrorType -import roomescape.system.exception.RoomEscapeException +import roomescape.common.exception.ErrorType +import roomescape.common.exception.RoomescapeException @Service @Transactional(readOnly = true) @@ -23,7 +23,7 @@ class MemberService( ) fun findById(memberId: Long): Member = memberRepository.findByIdOrNull(memberId) - ?: throw RoomEscapeException( + ?: throw RoomescapeException( ErrorType.MEMBER_NOT_FOUND, String.format("[memberId: %d]", memberId), HttpStatus.BAD_REQUEST @@ -31,7 +31,7 @@ class MemberService( fun findMemberByEmailAndPassword(email: String, password: String): Member = memberRepository.findByEmailAndPassword(email, password) - ?: throw RoomEscapeException( + ?: throw RoomescapeException( ErrorType.MEMBER_NOT_FOUND, String.format("[email: %s, password: %s]", email, password), HttpStatus.BAD_REQUEST diff --git a/src/main/java/roomescape/member/web/MemberAPI.kt b/src/main/java/roomescape/member/web/MemberAPI.kt index 92aa4bbd..020b1ec2 100644 --- a/src/main/java/roomescape/member/web/MemberAPI.kt +++ b/src/main/java/roomescape/member/web/MemberAPI.kt @@ -6,8 +6,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.HttpStatus import org.springframework.web.bind.annotation.ResponseStatus -import roomescape.system.auth.web.support.Admin -import roomescape.system.dto.response.RoomEscapeApiResponse +import roomescape.auth.web.support.Admin +import roomescape.common.dto.response.RoomEscapeApiResponse @Tag(name = "2. 회원 API", description = "회원 정보를 관리할 때 사용합니다.") interface MemberAPI { diff --git a/src/main/java/roomescape/member/web/MemberController.kt b/src/main/java/roomescape/member/web/MemberController.kt index 1889ebb3..bb514406 100644 --- a/src/main/java/roomescape/member/web/MemberController.kt +++ b/src/main/java/roomescape/member/web/MemberController.kt @@ -5,7 +5,7 @@ import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController import roomescape.member.business.MemberService import roomescape.member.infrastructure.persistence.Member -import roomescape.system.dto.response.RoomEscapeApiResponse +import roomescape.common.dto.response.RoomEscapeApiResponse @RestController class MemberController( diff --git a/src/main/java/roomescape/payment/client/TossPaymentClient.java b/src/main/java/roomescape/payment/client/TossPaymentClient.java index 49c9397a..8245760f 100644 --- a/src/main/java/roomescape/payment/client/TossPaymentClient.java +++ b/src/main/java/roomescape/payment/client/TossPaymentClient.java @@ -19,8 +19,8 @@ import roomescape.payment.dto.request.PaymentRequest; import roomescape.payment.dto.response.PaymentCancelResponse; import roomescape.payment.dto.response.PaymentResponse; import roomescape.payment.dto.response.TossPaymentErrorResponse; -import roomescape.system.exception.ErrorType; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.ErrorType; +import roomescape.common.exception.RoomescapeException; @Component public class TossPaymentClient { @@ -76,7 +76,7 @@ public class TossPaymentClient { ErrorType errorType = getErrorTypeByStatusCode(statusCode); TossPaymentErrorResponse errorResponse = getErrorResponse(res); - throw new RoomEscapeException(errorType, + throw new RoomescapeException(errorType, String.format("[ErrorCode = %s, ErrorMessage = %s]", errorResponse.code(), errorResponse.message()), statusCode); } diff --git a/src/main/java/roomescape/payment/domain/CanceledPayment.java b/src/main/java/roomescape/payment/domain/CanceledPayment.java index dc2056ec..5d4750f9 100644 --- a/src/main/java/roomescape/payment/domain/CanceledPayment.java +++ b/src/main/java/roomescape/payment/domain/CanceledPayment.java @@ -8,8 +8,8 @@ import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import roomescape.system.exception.ErrorType; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.ErrorType; +import roomescape.common.exception.RoomescapeException; @Entity public class CanceledPayment { @@ -39,7 +39,7 @@ public class CanceledPayment { private void validateDate(OffsetDateTime approvedAt, OffsetDateTime canceledAt) { if (canceledAt.isBefore(approvedAt)) { - throw new RoomEscapeException(ErrorType.CANCELED_BEFORE_PAYMENT, + throw new RoomescapeException(ErrorType.CANCELED_BEFORE_PAYMENT, String.format("[approvedAt: %s, canceledAt: %s]", approvedAt, canceledAt), HttpStatus.CONFLICT); } diff --git a/src/main/java/roomescape/payment/domain/Payment.java b/src/main/java/roomescape/payment/domain/Payment.java index 92c1eac1..1ead5b25 100644 --- a/src/main/java/roomescape/payment/domain/Payment.java +++ b/src/main/java/roomescape/payment/domain/Payment.java @@ -13,8 +13,8 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; import roomescape.reservation.domain.Reservation; -import roomescape.system.exception.ErrorType; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.ErrorType; +import roomescape.common.exception.RoomescapeException; @Entity public class Payment { @@ -63,21 +63,21 @@ public class Payment { private void validateIsNullOrBlank(String input, String fieldName) { if (input == null || input.isBlank()) { - throw new RoomEscapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[value : %s]", fieldName), + throw new RoomescapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[value : %s]", fieldName), HttpStatus.BAD_REQUEST); } } private void validateIsInvalidAmount(Long totalAmount) { if (totalAmount == null || totalAmount < 0) { - throw new RoomEscapeException(ErrorType.INVALID_REQUEST_DATA, + throw new RoomescapeException(ErrorType.INVALID_REQUEST_DATA, String.format("[totalAmount : %d]", totalAmount), HttpStatus.BAD_REQUEST); } } private void validateIsNull(T value, String fieldName) { if (value == null) { - throw new RoomEscapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[value : %s]", fieldName), + throw new RoomescapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[value : %s]", fieldName), HttpStatus.BAD_REQUEST); } } diff --git a/src/main/java/roomescape/payment/service/PaymentService.java b/src/main/java/roomescape/payment/service/PaymentService.java index e3c6332e..9bf28730 100644 --- a/src/main/java/roomescape/payment/service/PaymentService.java +++ b/src/main/java/roomescape/payment/service/PaymentService.java @@ -16,8 +16,8 @@ import roomescape.payment.dto.response.PaymentCancelResponse; import roomescape.payment.dto.response.PaymentResponse; import roomescape.payment.dto.response.ReservationPaymentResponse; import roomescape.reservation.domain.Reservation; -import roomescape.system.exception.ErrorType; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.ErrorType; +import roomescape.common.exception.RoomescapeException; @Service @Transactional @@ -50,7 +50,7 @@ public class PaymentService { public PaymentCancelRequest cancelPaymentByAdmin(Long reservationId) { String paymentKey = findPaymentByReservationId(reservationId) - .orElseThrow(() -> new RoomEscapeException(ErrorType.PAYMENT_NOT_POUND, + .orElseThrow(() -> new RoomescapeException(ErrorType.PAYMENT_NOT_POUND, String.format("[reservationId: %d]", reservationId), HttpStatus.NOT_FOUND)) .getPaymentKey(); // 취소 시간은 현재 시간으로 일단 생성한 뒤, 결제 취소 완료 후 해당 시간으로 변경합니다. @@ -74,8 +74,8 @@ public class PaymentService { canceledPayment.setCanceledAt(canceledAt); } - private RoomEscapeException throwPaymentNotFoundByPaymentKey(String paymentKey) { - return new RoomEscapeException( + private RoomescapeException throwPaymentNotFoundByPaymentKey(String paymentKey) { + return new RoomescapeException( ErrorType.PAYMENT_NOT_POUND, String.format("[paymentKey: %s]", paymentKey), HttpStatus.NOT_FOUND); } diff --git a/src/main/java/roomescape/reservation/controller/ReservationController.java b/src/main/java/roomescape/reservation/controller/ReservationController.java index 0b2c2705..c483f2a8 100644 --- a/src/main/java/roomescape/reservation/controller/ReservationController.java +++ b/src/main/java/roomescape/reservation/controller/ReservationController.java @@ -37,12 +37,12 @@ import roomescape.reservation.dto.response.ReservationResponse; import roomescape.reservation.dto.response.ReservationsResponse; import roomescape.reservation.service.ReservationService; import roomescape.reservation.service.ReservationWithPaymentService; -import roomescape.system.auth.web.support.Admin; -import roomescape.system.auth.web.support.LoginRequired; -import roomescape.system.auth.web.support.MemberId; -import roomescape.system.dto.response.ErrorResponse; -import roomescape.system.dto.response.RoomEscapeApiResponse; -import roomescape.system.exception.RoomEscapeException; +import roomescape.auth.web.support.Admin; +import roomescape.auth.web.support.LoginRequired; +import roomescape.auth.web.support.MemberId; +import roomescape.common.dto.response.ErrorResponse; +import roomescape.common.dto.response.RoomEscapeApiResponse; +import roomescape.common.exception.RoomescapeException; @RestController @Tag(name = "3. 예약 API", description = "예약 및 대기 정보를 추가 / 조회 / 삭제할 때 사용합니다.") @@ -151,7 +151,7 @@ public class ReservationController { ReservationResponse reservationResponse = reservationWithPaymentService.addReservationWithPayment( reservationRequest, paymentResponse, memberId); return getCreatedReservationResponse(reservationResponse, response); - } catch (RoomEscapeException e) { + } catch (RoomescapeException e) { PaymentCancelRequest cancelRequest = new PaymentCancelRequest(paymentRequest.paymentKey(), paymentRequest.amount(), e.getMessage()); diff --git a/src/main/java/roomescape/reservation/controller/ReservationTimeController.java b/src/main/java/roomescape/reservation/controller/ReservationTimeController.java index 6d804b1a..b8b6dae4 100644 --- a/src/main/java/roomescape/reservation/controller/ReservationTimeController.java +++ b/src/main/java/roomescape/reservation/controller/ReservationTimeController.java @@ -28,10 +28,10 @@ import roomescape.reservation.dto.response.ReservationTimeInfosResponse; import roomescape.reservation.dto.response.ReservationTimeResponse; import roomescape.reservation.dto.response.ReservationTimesResponse; import roomescape.reservation.service.ReservationTimeService; -import roomescape.system.auth.web.support.Admin; -import roomescape.system.auth.web.support.LoginRequired; -import roomescape.system.dto.response.ErrorResponse; -import roomescape.system.dto.response.RoomEscapeApiResponse; +import roomescape.auth.web.support.Admin; +import roomescape.auth.web.support.LoginRequired; +import roomescape.common.dto.response.ErrorResponse; +import roomescape.common.dto.response.RoomEscapeApiResponse; @RestController @Tag(name = "4. 예약 시간 API", description = "예약 시간을 조회 / 추가 / 삭제할 때 사용합니다.") diff --git a/src/main/java/roomescape/reservation/domain/Reservation.java b/src/main/java/roomescape/reservation/domain/Reservation.java index 7d300176..2c393643 100644 --- a/src/main/java/roomescape/reservation/domain/Reservation.java +++ b/src/main/java/roomescape/reservation/domain/Reservation.java @@ -16,8 +16,8 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import roomescape.member.infrastructure.persistence.Member; -import roomescape.system.exception.ErrorType; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.ErrorType; +import roomescape.common.exception.RoomescapeException; import roomescape.theme.domain.Theme; @Entity @@ -77,7 +77,7 @@ public class Reservation { private void validateIsNull(LocalDate date, ReservationTime reservationTime, Theme theme, Member member, ReservationStatus reservationStatus) { if (date == null || reservationTime == null || theme == null || member == null || reservationStatus == null) { - throw new RoomEscapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[values: %s]", this), + throw new RoomescapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[values: %s]", this), HttpStatus.BAD_REQUEST); } } diff --git a/src/main/java/roomescape/reservation/domain/ReservationTime.java b/src/main/java/roomescape/reservation/domain/ReservationTime.java index 5d3f2705..e54a39b0 100644 --- a/src/main/java/roomescape/reservation/domain/ReservationTime.java +++ b/src/main/java/roomescape/reservation/domain/ReservationTime.java @@ -8,8 +8,8 @@ import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import roomescape.system.exception.ErrorType; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.ErrorType; +import roomescape.common.exception.RoomescapeException; @Entity public class ReservationTime { @@ -36,7 +36,7 @@ public class ReservationTime { private void validateNull() { if (startAt == null) { - throw new RoomEscapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[values: %s]", this), + throw new RoomescapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[values: %s]", this), HttpStatus.BAD_REQUEST); } } diff --git a/src/main/java/roomescape/reservation/dto/request/ReservationTimeRequest.java b/src/main/java/roomescape/reservation/dto/request/ReservationTimeRequest.java index 00db2360..a01433ed 100644 --- a/src/main/java/roomescape/reservation/dto/request/ReservationTimeRequest.java +++ b/src/main/java/roomescape/reservation/dto/request/ReservationTimeRequest.java @@ -8,8 +8,8 @@ import io.micrometer.common.util.StringUtils; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import roomescape.reservation.domain.ReservationTime; -import roomescape.system.exception.ErrorType; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.ErrorType; +import roomescape.common.exception.RoomescapeException; @Schema(name = "예약 시간 저장 요청", description = "예약 시간 저장 요청시 사용됩니다.") public record ReservationTimeRequest( @@ -20,7 +20,7 @@ public record ReservationTimeRequest( public ReservationTimeRequest { if (StringUtils.isBlank(startAt.toString())) { - throw new RoomEscapeException(ErrorType.REQUEST_DATA_BLANK, + throw new RoomescapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[values: %s]", this), HttpStatus.BAD_REQUEST); } } diff --git a/src/main/java/roomescape/reservation/service/ReservationService.java b/src/main/java/roomescape/reservation/service/ReservationService.java index 20a856c6..9f0301ba 100644 --- a/src/main/java/roomescape/reservation/service/ReservationService.java +++ b/src/main/java/roomescape/reservation/service/ReservationService.java @@ -22,8 +22,8 @@ import roomescape.reservation.dto.request.WaitingRequest; import roomescape.reservation.dto.response.MyReservationsResponse; import roomescape.reservation.dto.response.ReservationResponse; import roomescape.reservation.dto.response.ReservationsResponse; -import roomescape.system.exception.ErrorType; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.ErrorType; +import roomescape.common.exception.RoomescapeException; import roomescape.theme.domain.Theme; import roomescape.theme.service.ThemeService; @@ -111,7 +111,7 @@ public class ReservationService { .build(); if (reservationRepository.exists(spec)) { - throw new RoomEscapeException(ErrorType.HAS_RESERVATION_OR_WAITING, HttpStatus.BAD_REQUEST); + throw new RoomescapeException(ErrorType.HAS_RESERVATION_OR_WAITING, HttpStatus.BAD_REQUEST); } } @@ -124,7 +124,7 @@ public class ReservationService { .build(); if (reservationRepository.exists(spec)) { - throw new RoomEscapeException(ErrorType.RESERVATION_DUPLICATED, HttpStatus.CONFLICT); + throw new RoomescapeException(ErrorType.RESERVATION_DUPLICATED, HttpStatus.CONFLICT); } } @@ -135,7 +135,7 @@ public class ReservationService { LocalDateTime now = LocalDateTime.now(); LocalDateTime request = LocalDateTime.of(requestDate, requestReservationTime.getStartAt()); if (request.isBefore(now)) { - throw new RoomEscapeException(ErrorType.RESERVATION_PERIOD_IN_PAST, + throw new RoomescapeException(ErrorType.RESERVATION_PERIOD_IN_PAST, String.format("[now: %s %s | request: %s %s]", now.toLocalDate(), now.toLocalTime(), requestDate, requestReservationTime.getStartAt()), HttpStatus.BAD_REQUEST @@ -178,7 +178,7 @@ public class ReservationService { return; } if (startFrom.isAfter(endAt)) { - throw new RoomEscapeException(ErrorType.INVALID_DATE_RANGE, + throw new RoomescapeException(ErrorType.INVALID_DATE_RANGE, String.format("[startFrom: %s, endAt: %s", startFrom, endAt), HttpStatus.BAD_REQUEST); } } @@ -191,7 +191,7 @@ public class ReservationService { public void approveWaiting(Long reservationId, Long memberId) { validateIsMemberAdmin(memberId); if (reservationRepository.isExistConfirmedReservation(reservationId)) { - throw new RoomEscapeException(ErrorType.RESERVATION_DUPLICATED, HttpStatus.CONFLICT); + throw new RoomescapeException(ErrorType.RESERVATION_DUPLICATED, HttpStatus.CONFLICT); } reservationRepository.updateStatusByReservationId(reservationId, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED); } @@ -217,11 +217,11 @@ public class ReservationService { if (member.isAdmin()) { return; } - throw new RoomEscapeException(ErrorType.PERMISSION_DOES_NOT_EXIST, HttpStatus.FORBIDDEN); + throw new RoomescapeException(ErrorType.PERMISSION_DOES_NOT_EXIST, HttpStatus.FORBIDDEN); } - private RoomEscapeException throwReservationNotFound(Long reservationId) { - return new RoomEscapeException(ErrorType.RESERVATION_NOT_FOUND, + private RoomescapeException throwReservationNotFound(Long reservationId) { + return new RoomescapeException(ErrorType.RESERVATION_NOT_FOUND, String.format("[reservationId: %d]", reservationId), HttpStatus.NOT_FOUND); } } diff --git a/src/main/java/roomescape/reservation/service/ReservationTimeService.java b/src/main/java/roomescape/reservation/service/ReservationTimeService.java index 3dd3342e..5ea06332 100644 --- a/src/main/java/roomescape/reservation/service/ReservationTimeService.java +++ b/src/main/java/roomescape/reservation/service/ReservationTimeService.java @@ -16,8 +16,8 @@ import roomescape.reservation.dto.response.ReservationTimeInfoResponse; import roomescape.reservation.dto.response.ReservationTimeInfosResponse; import roomescape.reservation.dto.response.ReservationTimeResponse; import roomescape.reservation.dto.response.ReservationTimesResponse; -import roomescape.system.exception.ErrorType; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.ErrorType; +import roomescape.common.exception.RoomescapeException; @Service @Transactional @@ -37,7 +37,7 @@ public class ReservationTimeService { @Transactional(readOnly = true) public ReservationTime findTimeById(Long id) { return reservationTimeRepository.findById(id) - .orElseThrow(() -> new RoomEscapeException(ErrorType.RESERVATION_TIME_NOT_FOUND, + .orElseThrow(() -> new RoomescapeException(ErrorType.RESERVATION_TIME_NOT_FOUND, String.format("[reservationTimeId: %d]", id), HttpStatus.BAD_REQUEST)); } @@ -63,7 +63,7 @@ public class ReservationTimeService { reservationTimeRequest.startAt()); if (!duplicateReservationTimes.isEmpty()) { - throw new RoomEscapeException(ErrorType.TIME_DUPLICATED, + throw new RoomescapeException(ErrorType.TIME_DUPLICATED, String.format("[startAt: %s]", reservationTimeRequest.startAt()), HttpStatus.CONFLICT); } } @@ -73,7 +73,7 @@ public class ReservationTimeService { List usingTimeReservations = reservationRepository.findByReservationTime(reservationTime); if (!usingTimeReservations.isEmpty()) { - throw new RoomEscapeException(ErrorType.TIME_IS_USED_CONFLICT, String.format("[timeId: %d]", id), + throw new RoomescapeException(ErrorType.TIME_IS_USED_CONFLICT, String.format("[timeId: %d]", id), HttpStatus.CONFLICT); } diff --git a/src/main/java/roomescape/system/config/JacksonConfig.java b/src/main/java/roomescape/system/config/JacksonConfig.java deleted file mode 100644 index 6753e4e2..00000000 --- a/src/main/java/roomescape/system/config/JacksonConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -package roomescape.system.config; - -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; - -@Configuration -public class JacksonConfig { - - @Bean - public ObjectMapper objectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModule(javaTimeModule()); - return objectMapper; - } - - @Bean - public JavaTimeModule javaTimeModule() { - JavaTimeModule javaTimeModule = new JavaTimeModule(); - - javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE)); - javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE)); - - javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm"))); - javaTimeModule.addDeserializer(LocalTime.class, - new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm"))); - - return javaTimeModule; - } -} diff --git a/src/main/java/roomescape/system/config/WebMvcConfig.java b/src/main/java/roomescape/system/config/WebMvcConfig.java deleted file mode 100644 index 9c286cab..00000000 --- a/src/main/java/roomescape/system/config/WebMvcConfig.java +++ /dev/null @@ -1,38 +0,0 @@ -package roomescape.system.config; - -import java.util.List; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -import roomescape.system.auth.web.support.AdminInterceptor; -import roomescape.system.auth.web.support.LoginInterceptor; -import roomescape.system.auth.web.support.MemberIdResolver; - -@Configuration -public class WebMvcConfig implements WebMvcConfigurer { - - private final MemberIdResolver memberIdResolver; - private final AdminInterceptor adminInterceptor; - private final LoginInterceptor loginInterceptor; - - public WebMvcConfig(MemberIdResolver memberIdResolver, AdminInterceptor adminInterceptor, - LoginInterceptor loginInterceptor) { - this.memberIdResolver = memberIdResolver; - this.adminInterceptor = adminInterceptor; - this.loginInterceptor = loginInterceptor; - } - - @Override - public void addArgumentResolvers(List resolvers) { - resolvers.add(memberIdResolver); - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(adminInterceptor); - registry.addInterceptor(loginInterceptor); - } -} diff --git a/src/main/java/roomescape/system/dto/response/ErrorResponse.java b/src/main/java/roomescape/system/dto/response/ErrorResponse.java deleted file mode 100644 index a5c4a638..00000000 --- a/src/main/java/roomescape/system/dto/response/ErrorResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package roomescape.system.dto.response; - -import io.swagger.v3.oas.annotations.media.Schema; -import roomescape.system.exception.ErrorType; - -@Schema(name = "예외 응답", description = "예외 발생 시 응답에 사용됩니다.") -public record ErrorResponse( - @Schema(description = "발생한 예외의 종류", example = "INVALID_REQUEST_DATA") ErrorType errorType, - @Schema(description = "예외 메시지", example = "요청 데이터 값이 올바르지 않습니다.") String message -) { - - public static ErrorResponse of(ErrorType errorType, String message) { - return new ErrorResponse(errorType, message); - } -} diff --git a/src/main/java/roomescape/system/dto/response/RoomEscapeApiResponse.java b/src/main/java/roomescape/system/dto/response/RoomEscapeApiResponse.java deleted file mode 100644 index 31628de3..00000000 --- a/src/main/java/roomescape/system/dto/response/RoomEscapeApiResponse.java +++ /dev/null @@ -1,20 +0,0 @@ -package roomescape.system.dto.response; - -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "API 응답 시에 사용합니다.") -public record RoomEscapeApiResponse( - @Schema(description = "응답 메시지", defaultValue = SUCCESS_MESSAGE) String message, - @Schema(description = "응답 바디") T data -) { - - private static final String SUCCESS_MESSAGE = "요청이 성공적으로 수행되었습니다."; - - public static RoomEscapeApiResponse success(T data) { - return new RoomEscapeApiResponse<>(SUCCESS_MESSAGE, data); - } - - public static RoomEscapeApiResponse success() { - return new RoomEscapeApiResponse<>(SUCCESS_MESSAGE, null); - } -} diff --git a/src/main/java/roomescape/system/exception/ErrorType.java b/src/main/java/roomescape/system/exception/ErrorType.java deleted file mode 100644 index b99be8d1..00000000 --- a/src/main/java/roomescape/system/exception/ErrorType.java +++ /dev/null @@ -1,61 +0,0 @@ -package roomescape.system.exception; - -public enum ErrorType { - - // 400 Bad Request - REQUEST_DATA_BLANK("요청 데이터에 유효하지 않은 값(null OR 공백)이 포함되어있습니다."), - INVALID_REQUEST_DATA_TYPE("요청 데이터 형식이 올바르지 않습니다."), - INVALID_REQUEST_DATA("요청 데이터 값이 올바르지 않습니다."), - INVALID_DATE_RANGE("종료 날짜는 시작 날짜 이전일 수 없습니다."), - HAS_RESERVATION_OR_WAITING("같은 테마에 대한 예약(대기)는 한 번만 가능합니다."), - - // 401 Unauthorized - EXPIRED_TOKEN("토큰이 만료되었습니다. 다시 로그인 해주세요."), - UNSUPPORTED_TOKEN("지원하지 않는 JWT 토큰입니다."), - MALFORMED_TOKEN("형식이 맞지 않는 JWT 토큰입니다."), - INVALID_SIGNATURE_TOKEN("잘못된 JWT 토큰 Signature 입니다."), - ILLEGAL_TOKEN("JWT 토큰의 Claim 이 비어있습니다."), - INVALID_TOKEN("JWT 토큰이 존재하지 않거나 유효하지 않습니다."), - NOT_EXIST_COOKIE("쿠키가 존재하지 않습니다. 로그인이 필요한 서비스입니다."), - - // 403 Forbidden - LOGIN_REQUIRED("로그인이 필요한 서비스입니다."), - PERMISSION_DOES_NOT_EXIST("접근 권한이 존재하지 않습니다."), - - // 404 Not Found - MEMBER_NOT_FOUND("회원(Member) 정보가 존재하지 않습니다."), - RESERVATION_NOT_FOUND("예약(Reservation) 정보가 존재하지 않습니다."), - RESERVATION_TIME_NOT_FOUND("예약 시간(ReservationTime) 정보가 존재하지 않습니다."), - THEME_NOT_FOUND("테마(Theme) 정보가 존재하지 않습니다."), - PAYMENT_NOT_POUND("결제(Payment) 정보가 존재하지 않습니다."), - - // 405 Method Not Allowed - METHOD_NOT_ALLOWED("지원하지 않는 HTTP Method 입니다."), - - // 409 Conflict - TIME_IS_USED_CONFLICT("삭제할 수 없는 시간대입니다. 예약이 존재하는지 확인해주세요."), - THEME_IS_USED_CONFLICT("삭제할 수 없는 테마입니다. 예약이 존재하는지 확인해주세요."), - TIME_DUPLICATED("이미 해당 시간이 존재합니다."), - THEME_DUPLICATED("같은 이름의 테마가 존재합니다."), - RESERVATION_DUPLICATED("해당 시간에 이미 예약이 존재합니다."), - RESERVATION_PERIOD_IN_PAST("이미 지난 시간대는 예약할 수 없습니다."), - CANCELED_BEFORE_PAYMENT("취소 시간이 결제 시간 이전일 수 없습니다."), - - // 500 Internal Server Error, - INTERNAL_SERVER_ERROR("서버 내부에서 에러가 발생하였습니다."), - UNEXPECTED_ERROR("예상치 못한 에러가 발생하였습니다. 잠시 후 다시 시도해주세요."), - - // Payment Error - PAYMENT_ERROR("결제(취소)에 실패했습니다. 결제(취소) 정보를 확인해주세요."), - PAYMENT_SERVER_ERROR("결제 서버에서 에러가 발생하였습니다. 잠시 후 다시 시도해주세요."); - - private final String description; - - ErrorType(String description) { - this.description = description; - } - - public String getDescription() { - return description; - } -} diff --git a/src/main/java/roomescape/system/exception/ExceptionControllerAdvice.java b/src/main/java/roomescape/system/exception/ExceptionControllerAdvice.java deleted file mode 100644 index d6a2c375..00000000 --- a/src/main/java/roomescape/system/exception/ExceptionControllerAdvice.java +++ /dev/null @@ -1,71 +0,0 @@ -package roomescape.system.exception; - -import java.util.stream.Collectors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.support.DefaultMessageSourceResolvable; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.web.HttpRequestMethodNotSupportedException; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.client.ResourceAccessException; - -import jakarta.servlet.http.HttpServletResponse; -import roomescape.system.dto.response.ErrorResponse; - -@RestControllerAdvice -public class ExceptionControllerAdvice { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - @ExceptionHandler(value = {RoomEscapeException.class}) - public ErrorResponse handleRoomEscapeException(RoomEscapeException e, HttpServletResponse response) { - logger.error("{}{}", e.getMessage(), e.getInvalidValue().orElse(""), e); - response.setStatus(e.getHttpStatus().value()); - return ErrorResponse.of(e.getErrorType(), e.getMessage()); - } - - @ExceptionHandler(ResourceAccessException.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ErrorResponse handleResourceAccessException(ResourceAccessException e) { - logger.error(e.getMessage(), e); - return ErrorResponse.of(ErrorType.PAYMENT_SERVER_ERROR, ErrorType.PAYMENT_SERVER_ERROR.getDescription()); - } - - @ExceptionHandler(value = HttpMessageNotReadableException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { - logger.error(e.getMessage(), e); - return ErrorResponse.of(ErrorType.INVALID_REQUEST_DATA_TYPE, - ErrorType.INVALID_REQUEST_DATA_TYPE.getDescription()); - } - - @ExceptionHandler(value = MethodArgumentNotValidException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - String messages = e.getBindingResult().getAllErrors().stream() - .map(DefaultMessageSourceResolvable::getDefaultMessage) - .collect(Collectors.joining(", ")); - - logger.error(messages, e); - return ErrorResponse.of(ErrorType.INVALID_REQUEST_DATA, messages); - } - - @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class) - @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) - public ErrorResponse handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { - logger.error(e.getMessage(), e); - return ErrorResponse.of(ErrorType.METHOD_NOT_ALLOWED, ErrorType.METHOD_NOT_ALLOWED.getDescription()); - } - - @ExceptionHandler(value = Exception.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ErrorResponse handleException(Exception e) { - logger.error(e.getMessage(), e); - return ErrorResponse.of(ErrorType.INTERNAL_SERVER_ERROR, ErrorType.INTERNAL_SERVER_ERROR.getDescription()); - } -} diff --git a/src/main/java/roomescape/system/exception/RoomEscapeException.java b/src/main/java/roomescape/system/exception/RoomEscapeException.java deleted file mode 100644 index 2f00a8e5..00000000 --- a/src/main/java/roomescape/system/exception/RoomEscapeException.java +++ /dev/null @@ -1,41 +0,0 @@ -package roomescape.system.exception; - -import java.util.Optional; - -import org.springframework.http.HttpStatusCode; - -public class RoomEscapeException extends RuntimeException { - - private final ErrorType errorType; - private final String message; - private final String invalidValue; - private final HttpStatusCode httpStatus; - - public RoomEscapeException(ErrorType errorType, HttpStatusCode httpStatus) { - this(errorType, null, httpStatus); - } - - public RoomEscapeException(ErrorType errorType, String invalidValue, HttpStatusCode httpStatus) { - this.errorType = errorType; - this.message = errorType.getDescription(); - this.invalidValue = invalidValue; - this.httpStatus = httpStatus; - } - - public ErrorType getErrorType() { - return errorType; - } - - public HttpStatusCode getHttpStatus() { - return httpStatus; - } - - public Optional getInvalidValue() { - return Optional.ofNullable(invalidValue); - } - - @Override - public String getMessage() { - return message; - } -} diff --git a/src/main/java/roomescape/theme/controller/ThemeController.java b/src/main/java/roomescape/theme/controller/ThemeController.java index 0c44bf4c..3a6507b6 100644 --- a/src/main/java/roomescape/theme/controller/ThemeController.java +++ b/src/main/java/roomescape/theme/controller/ThemeController.java @@ -21,10 +21,10 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; -import roomescape.system.auth.web.support.Admin; -import roomescape.system.auth.web.support.LoginRequired; -import roomescape.system.dto.response.ErrorResponse; -import roomescape.system.dto.response.RoomEscapeApiResponse; +import roomescape.auth.web.support.Admin; +import roomescape.auth.web.support.LoginRequired; +import roomescape.common.dto.response.ErrorResponse; +import roomescape.common.dto.response.RoomEscapeApiResponse; import roomescape.theme.dto.ThemeRequest; import roomescape.theme.dto.ThemeResponse; import roomescape.theme.dto.ThemesResponse; diff --git a/src/main/java/roomescape/theme/service/ThemeService.java b/src/main/java/roomescape/theme/service/ThemeService.java index d15bf703..c9874dfd 100644 --- a/src/main/java/roomescape/theme/service/ThemeService.java +++ b/src/main/java/roomescape/theme/service/ThemeService.java @@ -8,8 +8,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import roomescape.reservation.domain.repository.ReservationRepository; -import roomescape.system.exception.ErrorType; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.ErrorType; +import roomescape.common.exception.RoomescapeException; import roomescape.theme.domain.Theme; import roomescape.theme.domain.repository.ThemeRepository; import roomescape.theme.dto.ThemeRequest; @@ -31,7 +31,7 @@ public class ThemeService { @Transactional(readOnly = true) public Theme findThemeById(Long id) { return themeRepository.findById(id) - .orElseThrow(() -> new RoomEscapeException(ErrorType.THEME_NOT_FOUND, + .orElseThrow(() -> new RoomescapeException(ErrorType.THEME_NOT_FOUND, String.format("[themeId: %d]", id), HttpStatus.BAD_REQUEST)); } @@ -69,14 +69,14 @@ public class ThemeService { private void validateIsSameThemeNameExist(String name) { if (themeRepository.existsByName(name)) { - throw new RoomEscapeException(ErrorType.THEME_DUPLICATED, + throw new RoomescapeException(ErrorType.THEME_DUPLICATED, String.format("[name: %s]", name), HttpStatus.CONFLICT); } } public void removeThemeById(Long id) { if (themeRepository.isReservedTheme(id)) { - throw new RoomEscapeException(ErrorType.THEME_IS_USED_CONFLICT, + throw new RoomescapeException(ErrorType.THEME_IS_USED_CONFLICT, String.format("[themeId: %d]", id), HttpStatus.CONFLICT); } themeRepository.deleteById(id); diff --git a/src/main/java/roomescape/view/controller/PageController.kt b/src/main/java/roomescape/view/PageController.kt similarity index 88% rename from src/main/java/roomescape/view/controller/PageController.kt rename to src/main/java/roomescape/view/PageController.kt index 62504e7e..10425b88 100644 --- a/src/main/java/roomescape/view/controller/PageController.kt +++ b/src/main/java/roomescape/view/PageController.kt @@ -1,11 +1,11 @@ -package roomescape.view.controller +package roomescape.view import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping -import roomescape.system.auth.web.support.Admin -import roomescape.system.auth.web.support.LoginRequired +import roomescape.auth.web.support.Admin +import roomescape.auth.web.support.LoginRequired @Controller class AuthPageController { diff --git a/src/test/java/roomescape/system/auth/business/AuthServiceTest.kt b/src/test/java/roomescape/auth/business/AuthServiceTest.kt similarity index 85% rename from src/test/java/roomescape/system/auth/business/AuthServiceTest.kt rename to src/test/java/roomescape/auth/business/AuthServiceTest.kt index 8e415266..31cdef51 100644 --- a/src/test/java/roomescape/system/auth/business/AuthServiceTest.kt +++ b/src/test/java/roomescape/auth/business/AuthServiceTest.kt @@ -1,4 +1,4 @@ -package roomescape.system.auth.business +package roomescape.auth.business import io.kotest.assertions.assertSoftly import io.kotest.assertions.throwables.shouldThrow @@ -7,15 +7,15 @@ import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk import org.springframework.data.repository.findByIdOrNull -import roomescape.common.JwtFixture -import roomescape.common.MemberFixture +import roomescape.util.JwtFixture +import roomescape.util.MemberFixture import roomescape.member.business.MemberService import roomescape.member.infrastructure.persistence.Member import roomescape.member.infrastructure.persistence.MemberRepository -import roomescape.system.auth.infrastructure.jwt.JwtHandler -import roomescape.system.auth.service.AuthService -import roomescape.system.exception.ErrorType -import roomescape.system.exception.RoomEscapeException +import roomescape.auth.infrastructure.jwt.JwtHandler +import roomescape.auth.service.AuthService +import roomescape.common.exception.ErrorType +import roomescape.common.exception.RoomescapeException class AuthServiceTest : BehaviorSpec({ @@ -46,7 +46,7 @@ class AuthServiceTest : BehaviorSpec({ memberRepository.findByEmailAndPassword(request.email, request.password) } returns null - val exception = shouldThrow { + val exception = shouldThrow { authService.login(request) } @@ -72,7 +72,7 @@ class AuthServiceTest : BehaviorSpec({ Then("회원이 없다면 예외를 던진다.") { every { memberRepository.findByIdOrNull(userId) } returns null - val exception = shouldThrow { + val exception = shouldThrow { authService.checkLogin(userId) } diff --git a/src/test/java/roomescape/system/auth/infrastructure/jwt/JwtHandlerTest.kt b/src/test/java/roomescape/auth/infrastructure/jwt/JwtHandlerTest.kt similarity index 86% rename from src/test/java/roomescape/system/auth/infrastructure/jwt/JwtHandlerTest.kt rename to src/test/java/roomescape/auth/infrastructure/jwt/JwtHandlerTest.kt index b7565255..4626bab1 100644 --- a/src/test/java/roomescape/system/auth/infrastructure/jwt/JwtHandlerTest.kt +++ b/src/test/java/roomescape/auth/infrastructure/jwt/JwtHandlerTest.kt @@ -1,13 +1,13 @@ -package roomescape.system.auth.infrastructure.jwt +package roomescape.auth.infrastructure.jwt import io.jsonwebtoken.Jwts import io.jsonwebtoken.SignatureAlgorithm import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe -import roomescape.common.JwtFixture -import roomescape.system.exception.ErrorType -import roomescape.system.exception.RoomEscapeException +import roomescape.util.JwtFixture +import roomescape.common.exception.ErrorType +import roomescape.common.exception.RoomescapeException import java.util.* import kotlin.random.Random @@ -33,13 +33,13 @@ class JwtHandlerTest : FunSpec({ Thread.sleep(expirationTime) // 만료 시간 이후로 대기 // when & then - shouldThrow { + shouldThrow { shortExpirationTimeJwtHandler.getMemberIdFromToken(token) }.errorType shouldBe ErrorType.EXPIRED_TOKEN } test("토큰이 빈 값이면 예외를 던진다.") { - shouldThrow { + shouldThrow { jwtHandler.getMemberIdFromToken("") }.errorType shouldBe ErrorType.INVALID_TOKEN } @@ -53,7 +53,7 @@ class JwtHandlerTest : FunSpec({ .signWith(SignatureAlgorithm.HS256, JwtFixture.SECRET_KEY.substring(1).toByteArray()) .compact() - shouldThrow { + shouldThrow { jwtHandler.getMemberIdFromToken(invalidSignatureToken) }.errorType shouldBe ErrorType.INVALID_SIGNATURE_TOKEN } diff --git a/src/test/java/roomescape/system/auth/web/AuthControllerTest.kt b/src/test/java/roomescape/auth/web/AuthControllerTest.kt similarity index 96% rename from src/test/java/roomescape/system/auth/web/AuthControllerTest.kt rename to src/test/java/roomescape/auth/web/AuthControllerTest.kt index 7e6303c9..e0e8722c 100644 --- a/src/test/java/roomescape/system/auth/web/AuthControllerTest.kt +++ b/src/test/java/roomescape/auth/web/AuthControllerTest.kt @@ -1,12 +1,13 @@ -package roomescape.system.auth.web +package roomescape.auth.web import io.mockk.every import org.hamcrest.Matchers.containsString import org.hamcrest.Matchers.`is` import org.springframework.data.repository.findByIdOrNull -import roomescape.common.MemberFixture -import roomescape.common.RoomescapeApiTest -import roomescape.system.exception.ErrorType +import roomescape.auth.web.LoginRequest +import roomescape.util.MemberFixture +import roomescape.util.RoomescapeApiTest +import roomescape.common.exception.ErrorType class AuthControllerTest : RoomescapeApiTest() { diff --git a/src/test/java/roomescape/common/config/JacksonConfigTest.kt b/src/test/java/roomescape/common/config/JacksonConfigTest.kt new file mode 100644 index 00000000..5033c0d6 --- /dev/null +++ b/src/test/java/roomescape/common/config/JacksonConfigTest.kt @@ -0,0 +1,55 @@ +package roomescape.common.config + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.exc.InvalidFormatException +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import java.time.LocalDate +import java.time.LocalTime + +class JacksonConfigTest( + private val objectMapper: ObjectMapper = JacksonConfig().objectMapper() +) : FunSpec({ + + context("날짜는 yyyy-mm-dd 형식이다.") { + val date = "2025-07-14" + val serialized: String = objectMapper.writeValueAsString(LocalDate.parse(date)) + val deserialized: LocalDate = objectMapper.readValue(serialized, LocalDate::class.java) + + test("LocalDate 직렬화") { + serialized shouldBe "\"$date\"" + } + + test("LocalDate 역직렬화") { + deserialized shouldBe LocalDate.parse(date) + } + + test("형식이 잘못되면 InvalidFormatException을 던진다.") { + shouldThrow { + objectMapper.readValue("\"2025/07/14\"", LocalDate::class.java) + }.message shouldContain "Text '2025/07/14' could not be parsed" + } + } + + context("시간은 HH:mm 형식이다.") { + val (hour, minute, sec) = Triple(12, 30, 45) + val serialized: String = objectMapper.writeValueAsString(LocalTime.of(hour, minute, sec)) + val deserialized: LocalTime = objectMapper.readValue(serialized, LocalTime::class.java) + + test("LocalTime 직렬화") { + serialized shouldBe "\"$hour:$minute\"" + } + + test("LocalTime 역직렬화") { + deserialized shouldBe LocalTime.of(hour, minute) + } + + test("형식이 잘못되면 InvalidFormatException을 던진다.") { + shouldThrow { + objectMapper.readValue("\"$hour:$minute:$sec\"", LocalTime::class.java) + }.message shouldContain "Text '$hour:$minute:$sec' could not be parsed" + } + } +}) diff --git a/src/test/java/roomescape/common/dto/response/RoomescapeApiResponseKTTest.kt b/src/test/java/roomescape/common/dto/response/RoomescapeApiResponseKTTest.kt new file mode 100644 index 00000000..5ca55169 --- /dev/null +++ b/src/test/java/roomescape/common/dto/response/RoomescapeApiResponseKTTest.kt @@ -0,0 +1,158 @@ +package roomescape.common.dto.response + +import com.fasterxml.jackson.databind.ObjectMapper +import com.ninjasquad.springmockk.MockkBean +import com.ninjasquad.springmockk.SpykBean +import io.kotest.core.spec.style.BehaviorSpec +import org.hamcrest.CoreMatchers.equalTo +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.get +import org.springframework.test.web.servlet.post +import org.springframework.web.bind.annotation.* +import roomescape.auth.infrastructure.jwt.JwtHandler +import roomescape.auth.web.support.AdminInterceptor +import roomescape.auth.web.support.LoginInterceptor +import roomescape.auth.web.support.MemberIdResolver +import roomescape.common.exception.ErrorType +import roomescape.member.business.MemberService +import roomescape.member.infrastructure.persistence.MemberRepository + +@WebMvcTest(ApiResponseTestController::class) +class RoomescapeApiResponseKTTest( + @Autowired private val mockMvc: MockMvc +) : BehaviorSpec() { + @Autowired + private lateinit var AdminInterceptor: AdminInterceptor + + @Autowired + private lateinit var loginInterceptor: LoginInterceptor + + @Autowired + private lateinit var memberIdResolver: MemberIdResolver + + @SpykBean + private lateinit var memberService: MemberService + + @MockkBean + private lateinit var memberRepository: MemberRepository + + @MockkBean + private lateinit var jwtHandler: JwtHandler + + init { + Given("성공 응답에") { + val endpoint = "/success" + When("객체 데이터를 담으면") { + val id: Long = 1L + val name = "name" + Then("success=true, data={객체} 형태로 응답한다.") { + mockMvc.post("$endpoint/$id/$name") { + contentType = MediaType.APPLICATION_JSON + }.andDo { + print() + }.andExpect { + status { isOk() } + jsonPath("$.success", equalTo(true)) + jsonPath("$.data.id", equalTo(id.toInt())) + jsonPath("$.data.name", equalTo(name)) + } + } + } + + When("문자열 데이터를 담으면") { + val message: String = "Hello, World!" + + Then("success=true, data={문자열} 형태로 응답한다.") { + mockMvc.get("/success/$message") { + contentType = MediaType.APPLICATION_JSON + }.andDo { + print() + }.andExpect { + status { isOk() } + jsonPath("$.success", equalTo(true)) + jsonPath("$.data", equalTo(message)) + } + } + } + } + + Given("실패 응답에") { + val endpoint = "/fail" + val objectMapper = ObjectMapper() + + When("errorType만 담으면") { + Then("success=false, errorType={errorType}, message={errorType.description} 형태로 응답한다.") { + mockMvc.post(endpoint) { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(FailRequest(errorType = ErrorType.INTERNAL_SERVER_ERROR)) + }.andDo { + print() + }.andExpect { + status { isOk() } + jsonPath("$.success", equalTo(false)) + jsonPath("$.errorType", equalTo(ErrorType.INTERNAL_SERVER_ERROR.name)) + jsonPath("$.message", equalTo(ErrorType.INTERNAL_SERVER_ERROR.description)) + } + } + } + + When("errorType과 message를 담으면") { + val message: String = "An error occurred" + + Then("success=false, errorType={errorType}, message={message} 형태로 응답한다.") { + mockMvc.post(endpoint) { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(FailRequest(errorType = ErrorType.INTERNAL_SERVER_ERROR, message = message)) + }.andDo { + print() + }.andExpect { + status { isOk() } + jsonPath("$.success", equalTo(false)) + jsonPath("$.errorType", equalTo(ErrorType.INTERNAL_SERVER_ERROR.name)) + jsonPath("$.message", equalTo(message)) + } + } + } + } + } +} + +data class SuccessResponse( + val id: Long, + val name: String +) + +data class FailRequest( + val errorType: ErrorType, + val message: String? = null +) + +@RestController +class ApiResponseTestController { + + @GetMapping("/success/{message}") + fun succeedToGet( + @PathVariable message: String, + ): RoomescapeApiResponseKT = + RoomescapeApiResponseKT.success(message) + + + @PostMapping("/success/{id}/{name}") + fun succeedToPost( + @PathVariable id: Long, + @PathVariable name: String, + ): RoomescapeApiResponseKT = + RoomescapeApiResponseKT.success(SuccessResponse(id, name)) + + + @PostMapping("/fail") + fun fail( + @RequestBody request: FailRequest + ): RoomescapeApiResponseKT = + request.message?.let { + RoomescapeApiResponseKT.fail(request.errorType, it) + } ?: RoomescapeApiResponseKT.fail(request.errorType) +} diff --git a/src/test/java/roomescape/member/controller/MemberControllerTest.kt b/src/test/java/roomescape/member/controller/MemberControllerTest.kt index f268c327..59e5b3a2 100644 --- a/src/test/java/roomescape/member/controller/MemberControllerTest.kt +++ b/src/test/java/roomescape/member/controller/MemberControllerTest.kt @@ -7,8 +7,8 @@ import io.kotest.matchers.shouldBe import io.mockk.every import io.restassured.module.kotlin.extensions.Extract import org.hamcrest.Matchers.containsString -import roomescape.common.MemberFixture -import roomescape.common.RoomescapeApiTest +import roomescape.util.MemberFixture +import roomescape.util.RoomescapeApiTest import roomescape.member.web.MembersResponse class MemberControllerTest : RoomescapeApiTest() { diff --git a/src/test/java/roomescape/payment/client/TossPaymentClientTest.java b/src/test/java/roomescape/payment/client/TossPaymentClientTest.java index 2e37c791..98418893 100644 --- a/src/test/java/roomescape/payment/client/TossPaymentClientTest.java +++ b/src/test/java/roomescape/payment/client/TossPaymentClientTest.java @@ -1,14 +1,10 @@ package roomescape.payment.client; -import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.*; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; import static org.springframework.test.web.client.response.MockRestResponseCreators.*; -import java.util.Optional; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -22,8 +18,8 @@ import roomescape.payment.dto.request.PaymentCancelRequest; import roomescape.payment.dto.request.PaymentRequest; import roomescape.payment.dto.response.PaymentCancelResponse; import roomescape.payment.dto.response.PaymentResponse; -import roomescape.system.exception.ErrorType; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.ErrorType; +import roomescape.common.exception.RoomescapeException; @RestClientTest(TossPaymentClient.class) class TossPaymentClientTest { @@ -90,10 +86,10 @@ class TossPaymentClientTest { // when & then assertThatThrownBy(() -> tossPaymentClient.confirmPayment(SampleTossPaymentConst.paymentRequest)) - .isInstanceOf(RoomEscapeException.class) + .isInstanceOf(RoomescapeException.class) .hasFieldOrPropertyWithValue("errorType", ErrorType.PAYMENT_ERROR) .hasFieldOrPropertyWithValue("invalidValue", - Optional.of("[ErrorCode = ERROR_CODE, ErrorMessage = Error message]")) + "[ErrorCode = ERROR_CODE, ErrorMessage = Error message]") .hasFieldOrPropertyWithValue("httpStatus", HttpStatus.BAD_REQUEST); } @@ -111,10 +107,10 @@ class TossPaymentClientTest { // when & then assertThatThrownBy(() -> tossPaymentClient.cancelPayment(SampleTossPaymentConst.cancelRequest)) - .isInstanceOf(RoomEscapeException.class) + .isInstanceOf(RoomescapeException.class) .hasFieldOrPropertyWithValue("errorType", ErrorType.PAYMENT_SERVER_ERROR) .hasFieldOrPropertyWithValue("invalidValue", - Optional.of("[ErrorCode = ERROR_CODE, ErrorMessage = Error message]")) + "[ErrorCode = ERROR_CODE, ErrorMessage = Error message]") .hasFieldOrPropertyWithValue("httpStatus", HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/src/test/java/roomescape/payment/domain/CanceledPaymentTest.java b/src/test/java/roomescape/payment/domain/CanceledPaymentTest.java index 5ec19814..019c4da3 100644 --- a/src/test/java/roomescape/payment/domain/CanceledPaymentTest.java +++ b/src/test/java/roomescape/payment/domain/CanceledPaymentTest.java @@ -7,7 +7,7 @@ import java.time.OffsetDateTime; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.RoomescapeException; class CanceledPaymentTest { @@ -17,6 +17,6 @@ class CanceledPaymentTest { OffsetDateTime approvedAt = OffsetDateTime.now(); OffsetDateTime canceledAt = approvedAt.minusMinutes(1L); assertThatThrownBy(() -> new CanceledPayment("payment-key", "reason", 10000L, approvedAt, canceledAt)) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } } \ No newline at end of file diff --git a/src/test/java/roomescape/payment/domain/PaymentTest.java b/src/test/java/roomescape/payment/domain/PaymentTest.java index 1544cbb0..7b2030d7 100644 --- a/src/test/java/roomescape/payment/domain/PaymentTest.java +++ b/src/test/java/roomescape/payment/domain/PaymentTest.java @@ -18,7 +18,7 @@ import roomescape.member.infrastructure.persistence.Role; import roomescape.reservation.domain.Reservation; import roomescape.reservation.domain.ReservationStatus; import roomescape.reservation.domain.ReservationTime; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.RoomescapeException; import roomescape.theme.domain.Theme; class PaymentTest { @@ -40,7 +40,7 @@ class PaymentTest { @NullAndEmptySource void invalidPaymentKey(String paymentKey) { assertThatThrownBy(() -> new Payment("order-id", paymentKey, 10000L, reservation, OffsetDateTime.now())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @ParameterizedTest @@ -48,7 +48,7 @@ class PaymentTest { @NullAndEmptySource void invalidOrderId(String orderId) { assertThatThrownBy(() -> new Payment(orderId, "payment-key", 10000L, reservation, OffsetDateTime.now())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @ParameterizedTest @@ -57,7 +57,7 @@ class PaymentTest { void invalidOrderId(Long totalAmount) { assertThatThrownBy( () -> new Payment("orderId", "payment-key", totalAmount, reservation, OffsetDateTime.now())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @ParameterizedTest @@ -65,7 +65,7 @@ class PaymentTest { @NullSource void invalidReservation(Reservation reservation) { assertThatThrownBy(() -> new Payment("orderId", "payment-key", 10000L, reservation, OffsetDateTime.now())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @ParameterizedTest @@ -73,6 +73,6 @@ class PaymentTest { @NullSource void invalidApprovedAt(OffsetDateTime approvedAt) { assertThatThrownBy(() -> new Payment("orderId", "payment-key", 10000L, reservation, approvedAt)) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } } diff --git a/src/test/java/roomescape/payment/service/PaymentServiceTest.java b/src/test/java/roomescape/payment/service/PaymentServiceTest.java index 66719b95..19f28cde 100644 --- a/src/test/java/roomescape/payment/service/PaymentServiceTest.java +++ b/src/test/java/roomescape/payment/service/PaymentServiceTest.java @@ -25,7 +25,7 @@ import roomescape.reservation.domain.ReservationStatus; import roomescape.reservation.domain.ReservationTime; import roomescape.reservation.domain.repository.ReservationRepository; import roomescape.reservation.domain.repository.ReservationTimeRepository; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.RoomescapeException; import roomescape.theme.domain.Theme; import roomescape.theme.domain.repository.ThemeRepository; @@ -100,7 +100,7 @@ class PaymentServiceTest { // when assertThatThrownBy(() -> paymentService.cancelPaymentByAdmin(nonExistentReservationId)) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -136,6 +136,6 @@ class PaymentServiceTest { // when assertThatThrownBy(() -> paymentService.updateCanceledTime("non-existent-payment-key", canceledAt)) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } } diff --git a/src/test/java/roomescape/reservation/domain/ReservationTest.java b/src/test/java/roomescape/reservation/domain/ReservationTest.java index 757b27a5..ba1bc491 100644 --- a/src/test/java/roomescape/reservation/domain/ReservationTest.java +++ b/src/test/java/roomescape/reservation/domain/ReservationTest.java @@ -12,7 +12,7 @@ import org.junit.jupiter.params.provider.MethodSource; import roomescape.member.infrastructure.persistence.Member; import roomescape.member.infrastructure.persistence.Role; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.RoomescapeException; import roomescape.theme.domain.Theme; public class ReservationTest { @@ -26,7 +26,7 @@ public class ReservationTest { // when & then Assertions.assertThatThrownBy( () -> new Reservation(date, reservationTime, theme, member, ReservationStatus.CONFIRMED)) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } static Stream validateConstructorParameterBlankSource() { diff --git a/src/test/java/roomescape/reservation/domain/ReservationTimeTest.java b/src/test/java/roomescape/reservation/domain/ReservationTimeTest.java index 4cdd9dd4..17e9dfaa 100644 --- a/src/test/java/roomescape/reservation/domain/ReservationTimeTest.java +++ b/src/test/java/roomescape/reservation/domain/ReservationTimeTest.java @@ -4,7 +4,7 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.RoomescapeException; class ReservationTimeTest { @@ -14,6 +14,6 @@ class ReservationTimeTest { // when & then Assertions.assertThatThrownBy(() -> new ReservationTime(null)) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } } diff --git a/src/test/java/roomescape/reservation/service/ReservationServiceTest.java b/src/test/java/roomescape/reservation/service/ReservationServiceTest.java index 17cacf6d..cd3cb65b 100644 --- a/src/test/java/roomescape/reservation/service/ReservationServiceTest.java +++ b/src/test/java/roomescape/reservation/service/ReservationServiceTest.java @@ -26,7 +26,7 @@ import roomescape.reservation.domain.repository.ReservationTimeRepository; import roomescape.reservation.dto.request.ReservationRequest; import roomescape.reservation.dto.request.WaitingRequest; import roomescape.reservation.dto.response.ReservationResponse; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.RoomescapeException; import roomescape.theme.domain.Theme; import roomescape.theme.domain.repository.ThemeRepository; import roomescape.theme.service.ThemeService; @@ -66,7 +66,7 @@ class ReservationServiceTest { assertThatThrownBy(() -> reservationService.addReservation( new ReservationRequest(date, reservationTime.getId(), theme.getId(), "paymentKey", "orderId", 1000L, "paymentType"), member1.getId())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -86,7 +86,7 @@ class ReservationServiceTest { // then assertThatThrownBy(() -> reservationService.addWaiting( new WaitingRequest(date, reservationTime.getId(), theme.getId()), member.getId())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -110,7 +110,7 @@ class ReservationServiceTest { // then assertThatThrownBy(() -> reservationService.addWaiting( new WaitingRequest(date, reservationTime.getId(), theme.getId()), member1.getId())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -126,7 +126,7 @@ class ReservationServiceTest { assertThatThrownBy(() -> reservationService.addReservation( new ReservationRequest(beforeDate, reservationTime.getId(), theme.getId(), "paymentKey", "orderId", 1000L, "paymentType"), member.getId())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -142,7 +142,7 @@ class ReservationServiceTest { assertThatThrownBy(() -> reservationService.addReservation( new ReservationRequest(beforeTime.toLocalDate(), reservationTime.getId(), theme.getId(), "paymentKey", "orderId", 1000L, "paymentType"), member.getId())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -159,7 +159,7 @@ class ReservationServiceTest { new ReservationRequest(beforeTime.toLocalDate(), reservationTime.getId(), theme.getId(), "paymentKey", "orderId", 1000L, "paymentType"), NotExistMemberId)) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -171,7 +171,7 @@ class ReservationServiceTest { // when & then assertThatThrownBy(() -> reservationService.findFilteredReservations(null, null, dateFrom, dateTo)) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -194,7 +194,7 @@ class ReservationServiceTest { // when & then assertThatThrownBy(() -> reservationService.approveWaiting(waiting.id(), admin.getId())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test diff --git a/src/test/java/roomescape/reservation/service/ReservationTimeServiceTest.java b/src/test/java/roomescape/reservation/service/ReservationTimeServiceTest.java index 73682f55..76d4e97a 100644 --- a/src/test/java/roomescape/reservation/service/ReservationTimeServiceTest.java +++ b/src/test/java/roomescape/reservation/service/ReservationTimeServiceTest.java @@ -22,7 +22,7 @@ import roomescape.reservation.domain.ReservationTime; import roomescape.reservation.domain.repository.ReservationRepository; import roomescape.reservation.domain.repository.ReservationTimeRepository; import roomescape.reservation.dto.request.ReservationTimeRequest; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.RoomescapeException; import roomescape.theme.domain.Theme; import roomescape.theme.domain.repository.ThemeRepository; @@ -50,7 +50,7 @@ class ReservationTimeServiceTest { // when & then assertThatThrownBy(() -> reservationTimeService.addTime(new ReservationTimeRequest(LocalTime.of(12, 30)))) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -64,7 +64,7 @@ class ReservationTimeServiceTest { // when & then assertThatThrownBy(() -> reservationTimeService.findTimeById(invalidTimeId)) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -83,6 +83,6 @@ class ReservationTimeServiceTest { // then assertThatThrownBy(() -> reservationTimeService.removeTimeById(reservationTime.getId())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } } diff --git a/src/test/java/roomescape/system/config/JacksonConfigTest.java b/src/test/java/roomescape/system/config/JacksonConfigTest.java deleted file mode 100644 index 2d638aa3..00000000 --- a/src/test/java/roomescape/system/config/JacksonConfigTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package roomescape.system.config; - -import static org.assertj.core.api.Assertions.*; - -import java.time.LocalDate; -import java.time.LocalTime; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -class JacksonConfigTest { - - private ObjectMapper objectMapper; - - @BeforeEach - void setUp() { - JacksonConfig jacksonConfig = new JacksonConfig(); - objectMapper = jacksonConfig.objectMapper(); - } - - @DisplayName("날짜는 yyyy-MM-dd 형식으로 직렬화 된다.") - @Test - void dateSerialize() throws JsonProcessingException { - // given - LocalDate date = LocalDate.parse("2021-07-01"); - - // when - String json = objectMapper.writeValueAsString(date); - LocalDate actual = objectMapper.readValue(json, LocalDate.class); - - // then - assertThat(actual.toString()).isEqualTo("2021-07-01"); - } - - @DisplayName("시간은 HH:mm 형식으로 직렬화된다.") - @Test - void timeSerialize() throws JsonProcessingException { - // given - LocalTime time = LocalTime.parse("12:30:00"); - - // when - String json = objectMapper.writeValueAsString(time); - LocalTime actual = objectMapper.readValue(json, LocalTime.class); - - // then - assertThat(actual.toString()).isEqualTo("12:30"); - } - - @DisplayName("yyyy-MM-dd 형식의 문자열은 LocalDate로 역직렬화된다.") - @Test - void dateDeserialize() throws JsonProcessingException { - // given - String json = "\"2021-07-01\""; - - // when - LocalDate actual = objectMapper.readValue(json, LocalDate.class); - - // then - assertThat(actual).isEqualTo(LocalDate.of(2021, 7, 1)); - } - - @DisplayName("HH:mm 형식의 문자열은 LocalTime으로 역직렬화된다.") - @Test - void timeDeserialize() throws JsonProcessingException { - // given - String json = "\"12:30\""; - - // when - LocalTime actual = objectMapper.readValue(json, LocalTime.class); - - // then - assertThat(actual).isEqualTo(LocalTime.of(12, 30)); - } -} diff --git a/src/test/java/roomescape/theme/service/ThemeServiceTest.java b/src/test/java/roomescape/theme/service/ThemeServiceTest.java index 3ef8b8b7..6a440389 100644 --- a/src/test/java/roomescape/theme/service/ThemeServiceTest.java +++ b/src/test/java/roomescape/theme/service/ThemeServiceTest.java @@ -22,7 +22,7 @@ import roomescape.reservation.dto.request.ReservationTimeRequest; import roomescape.reservation.dto.response.ReservationTimeResponse; import roomescape.reservation.service.ReservationService; import roomescape.reservation.service.ReservationTimeService; -import roomescape.system.exception.RoomEscapeException; +import roomescape.common.exception.RoomescapeException; import roomescape.theme.domain.Theme; import roomescape.theme.domain.repository.ThemeRepository; import roomescape.theme.dto.ThemeRequest; @@ -73,7 +73,7 @@ class ThemeServiceTest { // then assertThatThrownBy(() -> themeService.findThemeById(notExistId)) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -128,7 +128,7 @@ class ThemeServiceTest { // then assertThatThrownBy(() -> themeService.addTheme(invalidRequest)) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } @Test @@ -159,6 +159,6 @@ class ThemeServiceTest { // when & then assertThatThrownBy(() -> themeService.removeThemeById(theme.getId())) - .isInstanceOf(RoomEscapeException.class); + .isInstanceOf(RoomescapeException.class); } } diff --git a/src/test/java/roomescape/common/Fixtures.kt b/src/test/java/roomescape/util/Fixtures.kt similarity index 90% rename from src/test/java/roomescape/common/Fixtures.kt rename to src/test/java/roomescape/util/Fixtures.kt index b53e7f3e..d03b6fe5 100644 --- a/src/test/java/roomescape/common/Fixtures.kt +++ b/src/test/java/roomescape/util/Fixtures.kt @@ -1,9 +1,9 @@ -package roomescape.common +package roomescape.util import roomescape.member.infrastructure.persistence.Member import roomescape.member.infrastructure.persistence.Role -import roomescape.system.auth.infrastructure.jwt.JwtHandler -import roomescape.system.auth.web.LoginRequest +import roomescape.auth.infrastructure.jwt.JwtHandler +import roomescape.auth.web.LoginRequest import java.util.concurrent.atomic.AtomicLong object MemberFixture { diff --git a/src/test/java/roomescape/common/KotestConfig.kt b/src/test/java/roomescape/util/KotestConfig.kt similarity index 91% rename from src/test/java/roomescape/common/KotestConfig.kt rename to src/test/java/roomescape/util/KotestConfig.kt index 39f66cb0..782520cf 100644 --- a/src/test/java/roomescape/common/KotestConfig.kt +++ b/src/test/java/roomescape/util/KotestConfig.kt @@ -1,4 +1,4 @@ -package roomescape.common +package roomescape.util import io.kotest.core.config.AbstractProjectConfig import io.kotest.extensions.spring.SpringExtension diff --git a/src/test/java/roomescape/common/RoomescapeApiTest.kt b/src/test/java/roomescape/util/RoomescapeApiTest.kt similarity index 92% rename from src/test/java/roomescape/common/RoomescapeApiTest.kt rename to src/test/java/roomescape/util/RoomescapeApiTest.kt index 6c4701ce..bd02bba7 100644 --- a/src/test/java/roomescape/common/RoomescapeApiTest.kt +++ b/src/test/java/roomescape/util/RoomescapeApiTest.kt @@ -1,4 +1,4 @@ -package roomescape.common +package roomescape.util import com.ninjasquad.springmockk.MockkBean import io.kotest.core.spec.style.BehaviorSpec @@ -14,9 +14,9 @@ import org.springframework.data.repository.findByIdOrNull import org.springframework.http.HttpStatus import roomescape.member.infrastructure.persistence.Member import roomescape.member.infrastructure.persistence.MemberRepository -import roomescape.system.auth.infrastructure.jwt.JwtHandler -import roomescape.system.exception.ErrorType -import roomescape.system.exception.RoomEscapeException +import roomescape.auth.infrastructure.jwt.JwtHandler +import roomescape.common.exception.ErrorType +import roomescape.common.exception.RoomescapeException const val NOT_LOGGED_IN_USERID: Long = 0; @@ -84,7 +84,7 @@ class RoomescapeApiTest( jwtHandler.getMemberIdFromToken(any()) } returns NOT_LOGGED_IN_USERID - every { memberRepository.existsById(NOT_LOGGED_IN_USERID) } throws RoomEscapeException( + every { memberRepository.existsById(NOT_LOGGED_IN_USERID) } throws RoomescapeException( ErrorType.LOGIN_REQUIRED, HttpStatus.FORBIDDEN ) diff --git a/src/test/java/roomescape/common/TestAnnotations.kt b/src/test/java/roomescape/util/TestAnnotations.kt similarity index 91% rename from src/test/java/roomescape/common/TestAnnotations.kt rename to src/test/java/roomescape/util/TestAnnotations.kt index 0f1dfe19..0d04e884 100644 --- a/src/test/java/roomescape/common/TestAnnotations.kt +++ b/src/test/java/roomescape/util/TestAnnotations.kt @@ -1,4 +1,4 @@ -package roomescape.common +package roomescape.util import org.springframework.test.context.TestPropertySource diff --git a/src/test/java/roomescape/view/controller/PageControllerTest.kt b/src/test/java/roomescape/view/PageControllerTest.kt similarity index 90% rename from src/test/java/roomescape/view/controller/PageControllerTest.kt rename to src/test/java/roomescape/view/PageControllerTest.kt index 825467b5..273d9235 100644 --- a/src/test/java/roomescape/view/controller/PageControllerTest.kt +++ b/src/test/java/roomescape/view/PageControllerTest.kt @@ -1,7 +1,7 @@ -package roomescape.view.controller +package roomescape.view -import org.hamcrest.Matchers.containsString -import roomescape.common.RoomescapeApiTest +import org.hamcrest.Matchers +import roomescape.util.RoomescapeApiTest class PageControllerTest() : RoomescapeApiTest() { @@ -54,7 +54,7 @@ class PageControllerTest() : RoomescapeApiTest() { then("로그인 페이지로 이동한다.") { runGetTest(it) { statusCode(200) - body(containsString("Login")) + body(Matchers.containsString("Login")) } } } @@ -86,7 +86,7 @@ class PageControllerTest() : RoomescapeApiTest() { runGetTest(it) { statusCode(200) - body(containsString("Login")) + body(Matchers.containsString("Login")) } } }