diff --git a/src/main/kotlin/com/doki/global/security/JwtProvider.kt b/src/main/kotlin/com/doki/global/security/JwtProvider.kt index dc3d632..cb4302a 100644 --- a/src/main/kotlin/com/doki/global/security/JwtProvider.kt +++ b/src/main/kotlin/com/doki/global/security/JwtProvider.kt @@ -1,7 +1,7 @@ package com.doki.global.security import com.doki.user.auth.infrastructure.dto.response.TokenResponse -import com.doki.user.domain.vo.UserRole +import com.doki.user.user.domain.vo.UserRole import io.jsonwebtoken.* import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.security.Keys diff --git a/src/main/kotlin/com/doki/mypage/ui/MyPageApi.kt b/src/main/kotlin/com/doki/mypage/ui/MyPageApi.kt index 00947be..cc9e9b9 100644 --- a/src/main/kotlin/com/doki/mypage/ui/MyPageApi.kt +++ b/src/main/kotlin/com/doki/mypage/ui/MyPageApi.kt @@ -2,7 +2,7 @@ package com.doki.mypage.ui import com.doki.productpackage.reservation.application.dto.ReservationSummaryResponse import com.doki.review.application.dto.PackageReviewResponse -import com.doki.user.application.dto.GetUserLoginInfoResponse +import com.doki.user.user.application.dto.GetUserLoginInfoResponse import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.media.Content diff --git a/src/main/kotlin/com/doki/mypage/ui/MyPageController.kt b/src/main/kotlin/com/doki/mypage/ui/MyPageController.kt index 2846ec8..bfab1ea 100644 --- a/src/main/kotlin/com/doki/mypage/ui/MyPageController.kt +++ b/src/main/kotlin/com/doki/mypage/ui/MyPageController.kt @@ -5,8 +5,8 @@ import com.doki.productpackage.reservation.application.ReservationQueryService import com.doki.productpackage.reservation.application.dto.ReservationSummaryResponse import com.doki.review.application.PackageReviewQueryService import com.doki.review.application.dto.PackageReviewResponse -import com.doki.user.application.UserService -import com.doki.user.application.dto.GetUserLoginInfoResponse +import com.doki.user.user.application.UserService +import com.doki.user.user.application.dto.GetUserLoginInfoResponse import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/src/main/kotlin/com/doki/user/auth/application/AuthService.kt b/src/main/kotlin/com/doki/user/auth/application/AuthService.kt index 380d097..30add4b 100644 --- a/src/main/kotlin/com/doki/user/auth/application/AuthService.kt +++ b/src/main/kotlin/com/doki/user/auth/application/AuthService.kt @@ -7,10 +7,10 @@ import com.doki.user.auth.domain.vo.AuthProvider import com.doki.global.exceptions.CustomException import com.doki.global.exceptions.auth.AuthExceptionType import com.doki.global.security.JwtProvider -import com.doki.user.domain.User +import com.doki.user.user.domain.User import com.doki.user.auth.domain.UserOAuth -import com.doki.user.infrastructure.UserOAuthRepository -import com.doki.user.infrastructure.UserRepository +import com.doki.user.user.domain.UserRepository +import com.doki.user.user.infrastructure.UserOAuthRepository import jakarta.servlet.http.HttpServletResponse import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -46,10 +46,10 @@ class AuthService( } val user = userRepository.findById(userId) - .orElseThrow { CustomException(AuthExceptionType.USER_NOT_FOUND) } + ?: throw CustomException(AuthExceptionType.USER_NOT_FOUND) // 마지막 로그인 시각 업데이트 - user.updateLasLoginTime() + user.updateLastLoginTime() return jwtProvider.generateToken(user.id, user.role) } @@ -67,7 +67,7 @@ class AuthService( val userId = jwtProvider.getSubjectAsUserId(refreshToken) val user = userRepository.findById(userId) - .orElseThrow { CustomException(AuthExceptionType.USER_NOT_FOUND) } + ?: throw CustomException(AuthExceptionType.USER_NOT_FOUND) // accessToken, refreshToken 재발급 val tokenResponse = jwtProvider.generateToken(user.id, user.role) diff --git a/src/main/kotlin/com/doki/user/dto/GetUserLoginInfoResponse.kt b/src/main/kotlin/com/doki/user/dto/GetUserLoginInfoResponse.kt deleted file mode 100644 index 8a7d7b1..0000000 --- a/src/main/kotlin/com/doki/user/dto/GetUserLoginInfoResponse.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.doki.user.dto - -import com.doki.user.domain.User -import java.time.LocalDateTime - -data class GetUserLoginInfoResponse( - val id: Long, - val email: String, - val nickname: String, - val lastLoginAt: LocalDateTime? -) { - companion object { - fun from(user: User): GetUserLoginInfoResponse = - GetUserLoginInfoResponse( - id = user.id, - email = user.email, - nickname = user.nickname.value, - lastLoginAt = user.lastLoginAt - ) - } -} diff --git a/src/main/kotlin/com/doki/user/infrastructure/UserRepository.kt b/src/main/kotlin/com/doki/user/infrastructure/UserRepository.kt deleted file mode 100644 index 9d241ac..0000000 --- a/src/main/kotlin/com/doki/user/infrastructure/UserRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.doki.user.infrastructure - -import com.doki.user.domain.User -import org.springframework.data.jpa.repository.JpaRepository - -interface UserRepository : JpaRepository { - - fun findByEmail(email: String): User? -} diff --git a/src/main/kotlin/com/doki/user/application/UserService.kt b/src/main/kotlin/com/doki/user/user/application/UserService.kt similarity index 75% rename from src/main/kotlin/com/doki/user/application/UserService.kt rename to src/main/kotlin/com/doki/user/user/application/UserService.kt index 1e3c024..6f1318c 100644 --- a/src/main/kotlin/com/doki/user/application/UserService.kt +++ b/src/main/kotlin/com/doki/user/user/application/UserService.kt @@ -1,10 +1,10 @@ -package com.doki.user.application +package com.doki.user.user.application import com.doki.global.exceptions.CustomException import com.doki.global.exceptions.auth.AuthExceptionType -import com.doki.user.application.dto.GetUserLoginInfoResponse -import com.doki.user.domain.User -import com.doki.user.infrastructure.UserRepository +import com.doki.user.user.application.dto.GetUserLoginInfoResponse +import com.doki.user.user.domain.User +import com.doki.user.user.domain.UserRepository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -20,7 +20,7 @@ class UserService( private fun findUser(userId: Long): User = userRepository.findById(userId) - .orElseThrow { CustomException(AuthExceptionType.USER_NOT_FOUND) } + ?: throw CustomException(AuthExceptionType.USER_NOT_FOUND) @Transactional fun updateNickname(userId: Long, nickname: String): GetUserLoginInfoResponse { diff --git a/src/main/kotlin/com/doki/user/user/application/UserSurveyCommandService.kt b/src/main/kotlin/com/doki/user/user/application/UserSurveyCommandService.kt new file mode 100644 index 0000000..cffcc45 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/application/UserSurveyCommandService.kt @@ -0,0 +1,39 @@ +package com.doki.user.user.application + +import com.doki.user.user.application.dto.CreateUserSurveyCommand +import com.doki.user.user.domain.UserRepository +import com.doki.user.user.domain.UserSurvey +import com.doki.user.user.domain.dto.UserSurveyCreatedResponse +import com.doki.user.user.domain.dto.UserSurveyResponse +import com.doki.user.user.domain.UserSurveyRepository +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Transactional +@Service +class UserSurveyCommandService( + private val userRepository: UserRepository, + private val userSurveyRepository: UserSurveyRepository, +) { + + fun saveUserSurvey(userId: Long, command: CreateUserSurveyCommand): UserSurveyCreatedResponse { + val user = userRepository.findById(userId) + ?: throw IllegalArgumentException("User with id $userId does not exist.") + + if (userSurveyRepository.existsByUserId(userId)) { + throw IllegalArgumentException("UserSurvey already exists.") + } + + val survey = UserSurvey.of( + user = user, + concept = command.concept, + idolName = command.idolName, + visitStartDate = command.visitStartDate, + visitEndDate = command.visitEndDate, + places = command.places + ) + + val saved = userSurveyRepository.save(survey) + return UserSurveyCreatedResponse(saved.id) + } +} diff --git a/src/main/kotlin/com/doki/user/user/application/UserSurveyQueryService.kt b/src/main/kotlin/com/doki/user/user/application/UserSurveyQueryService.kt new file mode 100644 index 0000000..a22d061 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/application/UserSurveyQueryService.kt @@ -0,0 +1,20 @@ +package com.doki.user.user.application + +import com.doki.user.user.domain.UserSurveyRepository +import com.doki.user.user.domain.dto.UserSurveyResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Transactional(readOnly = true) +@Service +class UserSurveyQueryService( + private val userSurveyRepository: UserSurveyRepository +) { + + fun findUserSurvey(userId: Long): UserSurveyResponse { + val survey = userSurveyRepository.findByUserId(userId) + ?: throw IllegalArgumentException("UserSurvey does not exist.") + + return UserSurveyResponse.from(survey) + } +} diff --git a/src/main/kotlin/com/doki/user/user/application/dto/CreateUserSurveyCommand.kt b/src/main/kotlin/com/doki/user/user/application/dto/CreateUserSurveyCommand.kt new file mode 100644 index 0000000..c42b7de --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/application/dto/CreateUserSurveyCommand.kt @@ -0,0 +1,12 @@ +package com.doki.user.user.application.dto + +import com.doki.user.user.domain.vo.ConceptType +import java.time.LocalDate + +data class CreateUserSurveyCommand ( + val concept: ConceptType, + val idolName: String, + val visitStartDate: LocalDate, + val visitEndDate: LocalDate, + val places: List +) diff --git a/src/main/kotlin/com/doki/user/application/dto/GetUserLoginInfoResponse.kt b/src/main/kotlin/com/doki/user/user/application/dto/GetUserLoginInfoResponse.kt similarity index 90% rename from src/main/kotlin/com/doki/user/application/dto/GetUserLoginInfoResponse.kt rename to src/main/kotlin/com/doki/user/user/application/dto/GetUserLoginInfoResponse.kt index 3f60e22..0e2d60d 100644 --- a/src/main/kotlin/com/doki/user/application/dto/GetUserLoginInfoResponse.kt +++ b/src/main/kotlin/com/doki/user/user/application/dto/GetUserLoginInfoResponse.kt @@ -1,6 +1,6 @@ -package com.doki.user.application.dto +package com.doki.user.user.application.dto -import com.doki.user.domain.User +import com.doki.user.user.domain.User import io.swagger.v3.oas.annotations.media.Schema import java.time.LocalDateTime diff --git a/src/main/kotlin/com/doki/user/domain/User.kt b/src/main/kotlin/com/doki/user/user/domain/User.kt similarity index 86% rename from src/main/kotlin/com/doki/user/domain/User.kt rename to src/main/kotlin/com/doki/user/user/domain/User.kt index 4e969e4..b9bd40e 100644 --- a/src/main/kotlin/com/doki/user/domain/User.kt +++ b/src/main/kotlin/com/doki/user/user/domain/User.kt @@ -1,7 +1,7 @@ -package com.doki.user.domain +package com.doki.user.user.domain -import com.doki.user.domain.vo.Nickname -import com.doki.user.domain.vo.UserRole +import com.doki.user.user.domain.vo.Nickname +import com.doki.user.user.domain.vo.UserRole import jakarta.persistence.Column import jakarta.persistence.Embedded import jakarta.persistence.Entity @@ -34,7 +34,7 @@ class User( var lastLoginAt: LocalDateTime? = null ) { // 마지막 로그인 시점 업데이트 - fun updateLasLoginTime(now: LocalDateTime = LocalDateTime.now()) { + fun updateLastLoginTime(now : LocalDateTime = LocalDateTime.now()) { this.lastLoginAt = now } diff --git a/src/main/kotlin/com/doki/user/user/domain/UserRepository.kt b/src/main/kotlin/com/doki/user/user/domain/UserRepository.kt new file mode 100644 index 0000000..dccae71 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/domain/UserRepository.kt @@ -0,0 +1,10 @@ +package com.doki.user.user.domain + +interface UserRepository { + + fun findById(userId: Long): User? + + fun findByEmail(email: String): User? + + fun save(user: User): User +} diff --git a/src/main/kotlin/com/doki/user/user/domain/UserSurvey.kt b/src/main/kotlin/com/doki/user/user/domain/UserSurvey.kt new file mode 100644 index 0000000..3337a09 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/domain/UserSurvey.kt @@ -0,0 +1,70 @@ +package com.doki.user.user.domain + +import com.doki.global.BaseEntity +import com.doki.user.user.domain.vo.ConceptType +import jakarta.persistence.* +import java.time.LocalDate + +@Entity +@Table(name = "user_survey") +class UserSurvey( + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long = 0L, + + @OneToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "user_id", nullable = false, unique = true) + val user: User, + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 30) + val concept: ConceptType, + + @Column(name = "idol_name", nullable = false) + val idolName: String, + + @Column(name = "visit_start_date", nullable = false) + val visitStartDate: LocalDate, + + @Column(name = "visit_end_date", nullable = false) + val visitEndDate: LocalDate, + + @ElementCollection + @CollectionTable( + name = "user_survey_places", + joinColumns = [JoinColumn(name = "user_survey_id")] + ) + @Column(name = "place", nullable = false, length = 100) + val places: List +) : BaseEntity() { + + init { + require(places.isNotEmpty()) { "place must not be empty" } + require(!visitEndDate.isBefore(visitStartDate)) { "visitEndDate must be on or after visitStartDate" } + } + + companion object { + private fun normalizePlaces(places: List): List = + places.map { it.trim() } + .filter { it.isNotBlank() } + .distinct() + + fun of( + user: User, + concept: ConceptType, + idolName: String, + visitStartDate: LocalDate, + visitEndDate: LocalDate, + places: List + ): UserSurvey { + return UserSurvey( + user = user, + concept = concept, + idolName = idolName.trim(), + visitStartDate = visitStartDate, + visitEndDate = visitEndDate, + places = normalizePlaces(places) + ) + } + } +} diff --git a/src/main/kotlin/com/doki/user/user/domain/UserSurveyRepository.kt b/src/main/kotlin/com/doki/user/user/domain/UserSurveyRepository.kt new file mode 100644 index 0000000..221bf8a --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/domain/UserSurveyRepository.kt @@ -0,0 +1,10 @@ +package com.doki.user.user.domain + +interface UserSurveyRepository { + + fun findByUserId(userId: Long): UserSurvey? + + fun existsByUserId(userId: Long): Boolean + + fun save(userSurvey: UserSurvey): UserSurvey +} diff --git a/src/main/kotlin/com/doki/user/user/domain/dto/UserSurveyCreatedResponse.kt b/src/main/kotlin/com/doki/user/user/domain/dto/UserSurveyCreatedResponse.kt new file mode 100644 index 0000000..86cb395 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/domain/dto/UserSurveyCreatedResponse.kt @@ -0,0 +1,8 @@ +package com.doki.user.user.domain.dto + +import io.swagger.v3.oas.annotations.media.Schema + +data class UserSurveyCreatedResponse( + @field:Schema(description = "생성된 설문조사 ID", example = "1") + val id: Long +) diff --git a/src/main/kotlin/com/doki/user/user/domain/dto/UserSurveyResponse.kt b/src/main/kotlin/com/doki/user/user/domain/dto/UserSurveyResponse.kt new file mode 100644 index 0000000..3eff228 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/domain/dto/UserSurveyResponse.kt @@ -0,0 +1,39 @@ +package com.doki.user.user.domain.dto + +import com.doki.user.user.domain.UserSurvey +import com.doki.user.user.domain.vo.ConceptType +import io.swagger.v3.oas.annotations.media.Schema +import java.time.LocalDate + +data class UserSurveyResponse( + @field:Schema(description = "설문조사 ID", example = "1") + val id: Long, + + @field:Schema(description = "선호 컨셉", example = "GIRL_CRUSH") + val concept: ConceptType, + + @field:Schema(description = "선호 아이돌 또는 그룹", example = "에스파") + val idolName: String, + + @field:Schema(description = "여행 시작일", example = "2025-07-20") + val visitStartDate: LocalDate, + + @field:Schema(description = "여행 종료일", example = "2025-07-22") + val visitEndDate: LocalDate, + + @field:Schema(description = "여행 장소", example = "['용산구', '마포구']") + val places: List +) { + companion object { + fun from(userSurvey: UserSurvey): UserSurveyResponse { + return UserSurveyResponse( + id = userSurvey.id, + concept = userSurvey.concept, + idolName = userSurvey.idolName, + visitStartDate = userSurvey.visitStartDate, + visitEndDate = userSurvey.visitEndDate, + places = userSurvey.places + ) + } + } +} diff --git a/src/main/kotlin/com/doki/user/domain/exception/UserExceptionType.kt b/src/main/kotlin/com/doki/user/user/domain/exception/UserExceptionType.kt similarity index 89% rename from src/main/kotlin/com/doki/user/domain/exception/UserExceptionType.kt rename to src/main/kotlin/com/doki/user/user/domain/exception/UserExceptionType.kt index 97b30b4..f6cf0a9 100644 --- a/src/main/kotlin/com/doki/user/domain/exception/UserExceptionType.kt +++ b/src/main/kotlin/com/doki/user/user/domain/exception/UserExceptionType.kt @@ -1,4 +1,4 @@ -package com.doki.user.domain.exception +package com.doki.user.user.domain.exception import com.doki.global.exceptions.CustomExceptionType diff --git a/src/main/kotlin/com/doki/user/user/domain/vo/ConceptType.kt b/src/main/kotlin/com/doki/user/user/domain/vo/ConceptType.kt new file mode 100644 index 0000000..3e27791 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/domain/vo/ConceptType.kt @@ -0,0 +1,10 @@ +package com.doki.user.user.domain.vo + +enum class ConceptType { + GIRL_CRUSH, + LOVELY_FRESH, + ELEGANT_GLAM, + DREAMY, + HIGHTEEN, + ETC +} diff --git a/src/main/kotlin/com/doki/user/domain/vo/Nickname.kt b/src/main/kotlin/com/doki/user/user/domain/vo/Nickname.kt similarity index 90% rename from src/main/kotlin/com/doki/user/domain/vo/Nickname.kt rename to src/main/kotlin/com/doki/user/user/domain/vo/Nickname.kt index 421be5a..b1c472e 100644 --- a/src/main/kotlin/com/doki/user/domain/vo/Nickname.kt +++ b/src/main/kotlin/com/doki/user/user/domain/vo/Nickname.kt @@ -1,7 +1,7 @@ -package com.doki.user.domain.vo +package com.doki.user.user.domain.vo import com.doki.global.exceptions.CustomException -import com.doki.user.domain.exception.UserExceptionType +import com.doki.user.user.domain.exception.UserExceptionType import jakarta.persistence.Column import jakarta.persistence.Embeddable diff --git a/src/main/kotlin/com/doki/user/domain/vo/UserRole.kt b/src/main/kotlin/com/doki/user/user/domain/vo/UserRole.kt similarity index 74% rename from src/main/kotlin/com/doki/user/domain/vo/UserRole.kt rename to src/main/kotlin/com/doki/user/user/domain/vo/UserRole.kt index 8acb8ed..387d6b5 100644 --- a/src/main/kotlin/com/doki/user/domain/vo/UserRole.kt +++ b/src/main/kotlin/com/doki/user/user/domain/vo/UserRole.kt @@ -1,4 +1,4 @@ -package com.doki.user.domain.vo +package com.doki.user.user.domain.vo enum class UserRole { USER // 회원 diff --git a/src/main/kotlin/com/doki/user/user/infrastructure/UserJpaRepository.kt b/src/main/kotlin/com/doki/user/user/infrastructure/UserJpaRepository.kt new file mode 100644 index 0000000..0c61b42 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/infrastructure/UserJpaRepository.kt @@ -0,0 +1,8 @@ +package com.doki.user.user.infrastructure + +import com.doki.user.user.domain.User +import org.springframework.data.jpa.repository.JpaRepository + +interface UserJpaRepository : JpaRepository { + fun findByEmail(email: String): User? +} diff --git a/src/main/kotlin/com/doki/user/infrastructure/UserOAuthRepository.kt b/src/main/kotlin/com/doki/user/user/infrastructure/UserOAuthRepository.kt similarity index 89% rename from src/main/kotlin/com/doki/user/infrastructure/UserOAuthRepository.kt rename to src/main/kotlin/com/doki/user/user/infrastructure/UserOAuthRepository.kt index ea8cc90..eb8c457 100644 --- a/src/main/kotlin/com/doki/user/infrastructure/UserOAuthRepository.kt +++ b/src/main/kotlin/com/doki/user/user/infrastructure/UserOAuthRepository.kt @@ -1,4 +1,4 @@ -package com.doki.user.infrastructure +package com.doki.user.user.infrastructure import com.doki.user.auth.domain.vo.AuthProvider import com.doki.user.auth.domain.UserOAuth diff --git a/src/main/kotlin/com/doki/user/user/infrastructure/UserRepositoryImpl.kt b/src/main/kotlin/com/doki/user/user/infrastructure/UserRepositoryImpl.kt new file mode 100644 index 0000000..8824d4b --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/infrastructure/UserRepositoryImpl.kt @@ -0,0 +1,21 @@ +package com.doki.user.user.infrastructure + +import com.doki.user.user.domain.User +import com.doki.user.user.domain.UserRepository +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Repository + +@Repository +class UserRepositoryImpl( + private val userJpaRepository: UserJpaRepository +): UserRepository { + + override fun findById(userId: Long): User? = + userJpaRepository.findByIdOrNull(userId) + + override fun findByEmail(email: String): User? = + userJpaRepository.findByEmail(email) + + override fun save(user: User): User = + userJpaRepository.save(user) +} diff --git a/src/main/kotlin/com/doki/user/user/infrastructure/UserSurveyJpaRepository.kt b/src/main/kotlin/com/doki/user/user/infrastructure/UserSurveyJpaRepository.kt new file mode 100644 index 0000000..3e36521 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/infrastructure/UserSurveyJpaRepository.kt @@ -0,0 +1,9 @@ +package com.doki.user.user.infrastructure + +import com.doki.user.user.domain.UserSurvey +import org.springframework.data.jpa.repository.JpaRepository + +interface UserSurveyJpaRepository: JpaRepository { + fun findByUserId(userId: Long): UserSurvey? + fun existsByUserId(userId: Long): Boolean +} diff --git a/src/main/kotlin/com/doki/user/user/infrastructure/UserSurveyRepositoryImpl.kt b/src/main/kotlin/com/doki/user/user/infrastructure/UserSurveyRepositoryImpl.kt new file mode 100644 index 0000000..ec81770 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/infrastructure/UserSurveyRepositoryImpl.kt @@ -0,0 +1,20 @@ +package com.doki.user.user.infrastructure + +import com.doki.user.user.domain.UserSurvey +import com.doki.user.user.domain.UserSurveyRepository +import org.springframework.stereotype.Repository + +@Repository +class UserSurveyRepositoryImpl( + private val userSurveyJpaRepository: UserSurveyJpaRepository +): UserSurveyRepository { + + override fun findByUserId(userId: Long): UserSurvey? = + userSurveyJpaRepository.findByUserId(userId) + + override fun existsByUserId(userId: Long): Boolean = + userSurveyJpaRepository.existsByUserId(userId) + + override fun save(userSurvey: UserSurvey): UserSurvey = + userSurveyJpaRepository.save(userSurvey) +} diff --git a/src/main/kotlin/com/doki/user/ui/UserApi.kt b/src/main/kotlin/com/doki/user/user/ui/UserApi.kt similarity index 88% rename from src/main/kotlin/com/doki/user/ui/UserApi.kt rename to src/main/kotlin/com/doki/user/user/ui/UserApi.kt index 39ef079..fe3fa19 100644 --- a/src/main/kotlin/com/doki/user/ui/UserApi.kt +++ b/src/main/kotlin/com/doki/user/user/ui/UserApi.kt @@ -1,7 +1,7 @@ -package com.doki.user.ui +package com.doki.user.user.ui -import com.doki.user.application.dto.GetUserLoginInfoResponse -import com.doki.user.ui.request.UpdateNicknameRequest +import com.doki.user.user.application.dto.GetUserLoginInfoResponse +import com.doki.user.user.ui.request.UpdateNicknameRequest import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.media.Content diff --git a/src/main/kotlin/com/doki/user/ui/UserController.kt b/src/main/kotlin/com/doki/user/user/ui/UserController.kt similarity index 78% rename from src/main/kotlin/com/doki/user/ui/UserController.kt rename to src/main/kotlin/com/doki/user/user/ui/UserController.kt index 7f646f5..7c02ab6 100644 --- a/src/main/kotlin/com/doki/user/ui/UserController.kt +++ b/src/main/kotlin/com/doki/user/user/ui/UserController.kt @@ -1,9 +1,9 @@ -package com.doki.user.ui +package com.doki.user.user.ui import com.doki.global.resolver.LoginUser -import com.doki.user.application.dto.GetUserLoginInfoResponse -import com.doki.user.application.UserService -import com.doki.user.ui.request.UpdateNicknameRequest +import com.doki.user.user.application.UserService +import com.doki.user.user.application.dto.GetUserLoginInfoResponse +import com.doki.user.user.ui.request.UpdateNicknameRequest import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PatchMapping diff --git a/src/main/kotlin/com/doki/user/user/ui/UserSurveyApi.kt b/src/main/kotlin/com/doki/user/user/ui/UserSurveyApi.kt new file mode 100644 index 0000000..a82ebb0 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/ui/UserSurveyApi.kt @@ -0,0 +1,47 @@ +package com.doki.user.user.ui + +import com.doki.user.user.domain.dto.UserSurveyCreatedResponse +import com.doki.user.user.domain.dto.UserSurveyResponse +import com.doki.user.user.ui.request.CreateUserSurveyRequest +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.validation.Valid +import org.springframework.web.bind.annotation.PathVariable + +@Tag(name = "UserSurvey", description = "회원 설문 관련 API") +interface UserSurveyApi { + + @Operation( + summary = "설문 조회", + description = "설문 내용을 조회합니다." + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "조회 성공"), + ApiResponse(responseCode = "404", description = "설문을 찾을 수 없음") + ] + ) + fun findUserSurvey( + // TODO: @LoginUser + @PathVariable userId: Long + ): UserSurveyResponse + + @Operation( + summary = "설문 저장", + description = "설문 내용을 저장합니다." + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "201", description = "생성 성공"), + ApiResponse(responseCode = "400", description = "잘못된 요청"), + ApiResponse(responseCode = "409", description = "이미 설문이 존재함") + ] + ) + fun saveUserSurvey( + // TODO: @LoginUser + @PathVariable userId: Long, + @Valid request: CreateUserSurveyRequest + ): UserSurveyCreatedResponse +} diff --git a/src/main/kotlin/com/doki/user/user/ui/UserSurveyController.kt b/src/main/kotlin/com/doki/user/user/ui/UserSurveyController.kt new file mode 100644 index 0000000..d5db8e6 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/ui/UserSurveyController.kt @@ -0,0 +1,32 @@ +package com.doki.user.user.ui + +import com.doki.user.user.application.UserSurveyCommandService +import com.doki.user.user.application.UserSurveyQueryService +import com.doki.user.user.domain.dto.UserSurveyCreatedResponse +import com.doki.user.user.domain.dto.UserSurveyResponse +import com.doki.user.user.ui.request.CreateUserSurveyRequest +import com.doki.user.user.ui.request.toCommand +import jakarta.validation.Valid +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.* + +@RequestMapping("/surveys") +@RestController +class UserSurveyController( + private val userSurveyCommandService: UserSurveyCommandService, + private val userSurveyQueryService: UserSurveyQueryService, +) : UserSurveyApi { + + @GetMapping("/{userId}") + override fun findUserSurvey(@PathVariable userId: Long): UserSurveyResponse = + userSurveyQueryService.findUserSurvey(userId) + + @PostMapping("/{userId}") + @ResponseStatus(HttpStatus.CREATED) + override fun saveUserSurvey( + @PathVariable userId: Long, + @Valid @RequestBody request: CreateUserSurveyRequest + ): UserSurveyCreatedResponse { + return userSurveyCommandService.saveUserSurvey(userId, request.toCommand()) + } +} diff --git a/src/main/kotlin/com/doki/user/user/ui/request/CreateUserSurveyRequest.kt b/src/main/kotlin/com/doki/user/user/ui/request/CreateUserSurveyRequest.kt new file mode 100644 index 0000000..de531d4 --- /dev/null +++ b/src/main/kotlin/com/doki/user/user/ui/request/CreateUserSurveyRequest.kt @@ -0,0 +1,41 @@ +package com.doki.user.user.ui.request + +import com.doki.user.user.application.dto.CreateUserSurveyCommand +import com.doki.user.user.domain.vo.ConceptType +import io.swagger.v3.oas.annotations.media.Schema +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull +import java.time.LocalDate + +data class CreateUserSurveyRequest( + + @field:Schema(description = "선호 컨셉", example = "GIRL_CRUSH") + @field:NotNull + val concept: ConceptType, + + @field:Schema(description = "선호 아이돌 또는 그룹", example = "에스파") + @field:NotBlank + val idolName: String, + + @field:Schema(description = "여행 시작일", example = "2025-07-20") + @field:NotNull + val visitStartDate: LocalDate, + + @field:Schema(description = "여행 종료일", example = "2025-07-22") + @field:NotNull + val visitEndDate: LocalDate, + + @field:Schema(description = "여행 장소", example = "['용산구', '마포구']") + @field:NotEmpty + val places: List<@NotBlank String> +) + +fun CreateUserSurveyRequest.toCommand(): CreateUserSurveyCommand = + CreateUserSurveyCommand( + concept = concept, + idolName = idolName, + visitStartDate = visitStartDate, + visitEndDate = visitEndDate, + places = places + ) diff --git a/src/main/kotlin/com/doki/user/ui/request/UpdateNicknameRequest.kt b/src/main/kotlin/com/doki/user/user/ui/request/UpdateNicknameRequest.kt similarity index 86% rename from src/main/kotlin/com/doki/user/ui/request/UpdateNicknameRequest.kt rename to src/main/kotlin/com/doki/user/user/ui/request/UpdateNicknameRequest.kt index 49e940d..b27ad30 100644 --- a/src/main/kotlin/com/doki/user/ui/request/UpdateNicknameRequest.kt +++ b/src/main/kotlin/com/doki/user/user/ui/request/UpdateNicknameRequest.kt @@ -1,4 +1,4 @@ -package com.doki.user.ui.request +package com.doki.user.user.ui.request import io.swagger.v3.oas.annotations.media.Schema import jakarta.validation.constraints.NotBlank diff --git a/src/test/kotlin/com/doki/mypage/ui/MyPageControllerTest.kt b/src/test/kotlin/com/doki/mypage/ui/MyPageControllerTest.kt index 0530d34..ac86886 100644 --- a/src/test/kotlin/com/doki/mypage/ui/MyPageControllerTest.kt +++ b/src/test/kotlin/com/doki/mypage/ui/MyPageControllerTest.kt @@ -12,10 +12,10 @@ import com.doki.productpackage.reservation.ui.request.CreateReservationRequest import com.doki.review.application.dto.PackageReviewResponse import com.doki.review.ui.request.CreatePackageReviewRequest import com.doki.testhelper.IntegrationTest -import com.doki.user.application.dto.GetUserLoginInfoResponse -import com.doki.user.domain.User -import com.doki.user.domain.vo.UserRole -import com.doki.user.infrastructure.UserRepository +import com.doki.user.user.application.dto.GetUserLoginInfoResponse +import com.doki.user.user.domain.User +import com.doki.user.user.domain.UserRepository +import com.doki.user.user.domain.vo.UserRole import com.fasterxml.jackson.databind.ObjectMapper import io.kotest.matchers.shouldBe import io.restassured.RestAssured.given diff --git a/src/test/kotlin/com/doki/productpackage/pkg/ui/PackageControllerTest.kt b/src/test/kotlin/com/doki/productpackage/pkg/ui/PackageControllerTest.kt index 1caf291..c05dead 100644 --- a/src/test/kotlin/com/doki/productpackage/pkg/ui/PackageControllerTest.kt +++ b/src/test/kotlin/com/doki/productpackage/pkg/ui/PackageControllerTest.kt @@ -9,7 +9,7 @@ import com.doki.productpackage.pkg.ui.request.CreatePackageRequest import com.doki.productpackage.product.application.dto.ProductResponse import com.doki.productpackage.product.ui.request.CreateProductRequest import com.doki.testhelper.IntegrationTest -import com.doki.user.domain.vo.UserRole +import com.doki.user.user.domain.vo.UserRole import io.kotest.matchers.shouldBe import io.kotest.matchers.longs.shouldBeGreaterThan import io.restassured.RestAssured.given diff --git a/src/test/kotlin/com/doki/productpackage/product/ui/ProductControllerTest.kt b/src/test/kotlin/com/doki/productpackage/product/ui/ProductControllerTest.kt index 714b3bb..f2f9ec8 100644 --- a/src/test/kotlin/com/doki/productpackage/product/ui/ProductControllerTest.kt +++ b/src/test/kotlin/com/doki/productpackage/product/ui/ProductControllerTest.kt @@ -7,7 +7,7 @@ import com.doki.global.security.JwtProvider import com.doki.productpackage.product.application.dto.ProductResponse import com.doki.productpackage.product.ui.request.CreateProductRequest import com.doki.testhelper.IntegrationTest -import com.doki.user.domain.vo.UserRole +import com.doki.user.user.domain.vo.UserRole import io.kotest.matchers.shouldBe import io.kotest.matchers.longs.shouldBeGreaterThan import io.restassured.RestAssured.given diff --git a/src/test/kotlin/com/doki/productpackage/reservation/ui/ReservationControllerTest.kt b/src/test/kotlin/com/doki/productpackage/reservation/ui/ReservationControllerTest.kt index 669d0af..a608d5e 100644 --- a/src/test/kotlin/com/doki/productpackage/reservation/ui/ReservationControllerTest.kt +++ b/src/test/kotlin/com/doki/productpackage/reservation/ui/ReservationControllerTest.kt @@ -8,9 +8,9 @@ import com.doki.productpackage.product.infrastructure.ProductJpaRepository import com.doki.productpackage.reservation.application.dto.ReservationCreatedResponse import com.doki.productpackage.reservation.ui.request.CreateReservationRequest import com.doki.testhelper.IntegrationTest -import com.doki.user.domain.User -import com.doki.user.domain.vo.UserRole -import com.doki.user.infrastructure.UserRepository +import com.doki.user.user.domain.User +import com.doki.user.user.domain.UserRepository +import com.doki.user.user.domain.vo.UserRole import com.fasterxml.jackson.databind.ObjectMapper import io.kotest.matchers.longs.shouldBeGreaterThan import io.restassured.RestAssured.given diff --git a/src/test/kotlin/com/doki/review/ui/PackageReviewControllerTest.kt b/src/test/kotlin/com/doki/review/ui/PackageReviewControllerTest.kt index fffbf55..00c121f 100644 --- a/src/test/kotlin/com/doki/review/ui/PackageReviewControllerTest.kt +++ b/src/test/kotlin/com/doki/review/ui/PackageReviewControllerTest.kt @@ -1,7 +1,7 @@ package com.doki.review.ui import com.fasterxml.jackson.databind.ObjectMapper -import com.doki.user.domain.vo.UserRole +import com.doki.user.user.domain.vo.UserRole import com.doki.global.security.JwtProvider import com.doki.productpackage.pkg.domain.Package import com.doki.productpackage.pkg.infrastructure.PackageCommandRepository diff --git a/src/test/kotlin/com/doki/auth/AuthControllerReissueTest.kt b/src/test/kotlin/com/doki/user/auth/AuthControllerReissueTest.kt similarity index 98% rename from src/test/kotlin/com/doki/auth/AuthControllerReissueTest.kt rename to src/test/kotlin/com/doki/user/auth/AuthControllerReissueTest.kt index 265e4ac..7c5da1b 100644 --- a/src/test/kotlin/com/doki/auth/AuthControllerReissueTest.kt +++ b/src/test/kotlin/com/doki/user/auth/AuthControllerReissueTest.kt @@ -1,4 +1,4 @@ -package com.doki.auth +package com.doki.user.auth import com.doki.user.auth.application.AuthService import com.doki.user.auth.infrastructure.dto.response.AccessTokenResponse diff --git a/src/test/kotlin/com/doki/auth/AuthServiceTest.kt b/src/test/kotlin/com/doki/user/auth/AuthServiceTest.kt similarity index 94% rename from src/test/kotlin/com/doki/auth/AuthServiceTest.kt rename to src/test/kotlin/com/doki/user/auth/AuthServiceTest.kt index 6fae198..c40f8ed 100644 --- a/src/test/kotlin/com/doki/auth/AuthServiceTest.kt +++ b/src/test/kotlin/com/doki/user/auth/AuthServiceTest.kt @@ -1,4 +1,4 @@ -package com.doki.auth +package com.doki.user.auth import com.doki.user.auth.application.AuthService @@ -6,9 +6,9 @@ import com.doki.user.auth.infrastructure.dto.response.AccessTokenResponse import com.doki.user.auth.infrastructure.dto.response.TokenResponse import com.doki.user.auth.infrastructure.support.RefreshTokenCookieSupporter import com.doki.global.security.JwtProvider -import com.doki.user.domain.User -import com.doki.user.infrastructure.UserOAuthRepository -import com.doki.user.infrastructure.UserRepository +import com.doki.user.user.domain.User +import com.doki.user.user.domain.UserRepository +import com.doki.user.user.infrastructure.UserOAuthRepository import jakarta.servlet.http.HttpServletResponse import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach @@ -22,7 +22,6 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.times import org.mockito.junit.jupiter.MockitoExtension import org.springframework.mock.web.MockHttpServletResponse -import java.util.Optional @ExtendWith(MockitoExtension::class) class AuthServiceTest { @@ -59,7 +58,7 @@ class AuthServiceTest { // userId로 User를 조회 val user = User.of(email = "test@example.com").apply { id = userId } - whenever(userRepository.findById(anyLong())).thenReturn(Optional.of(user)) + whenever(userRepository.findById(anyLong())).thenReturn(user) // jwtProvider가 새 토큰 발급 val newTokenResponse = TokenResponse( diff --git a/src/test/kotlin/com/doki/auth/RefreshTokenCookieSupporterTest.kt b/src/test/kotlin/com/doki/user/auth/RefreshTokenCookieSupporterTest.kt similarity index 98% rename from src/test/kotlin/com/doki/auth/RefreshTokenCookieSupporterTest.kt rename to src/test/kotlin/com/doki/user/auth/RefreshTokenCookieSupporterTest.kt index 27ba279..8609fbd 100644 --- a/src/test/kotlin/com/doki/auth/RefreshTokenCookieSupporterTest.kt +++ b/src/test/kotlin/com/doki/user/auth/RefreshTokenCookieSupporterTest.kt @@ -1,4 +1,4 @@ -package com.doki.auth +package com.doki.user.auth import com.doki.user.auth.infrastructure.support.RefreshTokenCookieSupporter import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/com/doki/user/user/application/UserSurveyCommandServiceTest.kt b/src/test/kotlin/com/doki/user/user/application/UserSurveyCommandServiceTest.kt new file mode 100644 index 0000000..358ec2c --- /dev/null +++ b/src/test/kotlin/com/doki/user/user/application/UserSurveyCommandServiceTest.kt @@ -0,0 +1,111 @@ +package com.doki.user.user.application + +import com.doki.user.user.application.dto.CreateUserSurveyCommand +import com.doki.user.user.domain.User +import com.doki.user.user.domain.UserRepository +import com.doki.user.user.domain.UserSurvey +import com.doki.user.user.domain.UserSurveyRepository +import com.doki.user.user.domain.vo.ConceptType +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class UserSurveyCommandServiceTest { + + private val userRepository: UserRepository = mockk() + private val userSurveyRepository: UserSurveyRepository = mockk() + + private val service = UserSurveyCommandService( + userRepository = userRepository, + userSurveyRepository = userSurveyRepository + ) + + private val queryService = UserSurveyQueryService( + userSurveyRepository = userSurveyRepository + ) + + @Test + fun `설문을 저장하면 저장된 설문 ID를 반환한다`() { + // given + val userId = 1L + val user = User.of(email = "test@google.com").apply { id = userId } + + val command = CreateUserSurveyCommand( + concept = ConceptType.GIRL_CRUSH, + idolName = "에스파", + visitStartDate = LocalDate.of(2025, 7, 20), + visitEndDate = LocalDate.of(2025, 7, 22), + places = listOf(" 용산구 ", "마포구", "용산구", " ") + ) + + every { userRepository.findById(userId) } returns user + every { userSurveyRepository.existsByUserId(userId) } returns false + every { userSurveyRepository.save(any()) } answers { call -> + val survey = call.invocation.args.first() as UserSurvey + UserSurvey( + id = 10L, + user = survey.user, + concept = survey.concept, + idolName = survey.idolName, + visitStartDate = survey.visitStartDate, + visitEndDate = survey.visitEndDate, + places = survey.places + ) + } + + // when + val created = service.saveUserSurvey(userId, command) + + // then + created.id shouldBe 10L + + verify(exactly = 1){ + userSurveyRepository.save( + match { + it.user.id == userId && + it.concept == ConceptType.GIRL_CRUSH && + it.idolName == "에스파" && + it.places == listOf("용산구", "마포구") + } + ) + } + } + + @Test + fun `조회한 회원이 없으면 예외가 발생한다`() { + // given + val userId = 1L + val command = CreateUserSurveyCommand( + concept = ConceptType.GIRL_CRUSH, + idolName = "에스파", + visitStartDate = LocalDate.of(2025, 7, 20), + visitEndDate = LocalDate.of(2025, 7, 22), + places = listOf("용산구") + ) + + every { userRepository.findById(userId) } returns null + + // when // then + shouldThrow { + service.saveUserSurvey(userId, command) + } + + verify(exactly = 0) { userSurveyRepository.save(any()) } + } + + @Test + fun `조회한 설문이 없으면 예외가 발생한다`() { + // given + val userId = 1L + every { userSurveyRepository.findByUserId(userId) } returns null + + // when // then + shouldThrow { + queryService.findUserSurvey(userId) + } + } +} diff --git a/src/test/kotlin/com/doki/user/user/domain/UserSurveyTest.kt b/src/test/kotlin/com/doki/user/user/domain/UserSurveyTest.kt new file mode 100644 index 0000000..c06d0ae --- /dev/null +++ b/src/test/kotlin/com/doki/user/user/domain/UserSurveyTest.kt @@ -0,0 +1,77 @@ +package com.doki.user.user.domain + +import com.doki.user.user.domain.vo.ConceptType +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.time.LocalDate +import kotlin.test.assertEquals + +class UserSurveyTest { + + private fun user(id: Long) = + User.of(email = "test@google.com").apply { this.id = id } + + @Nested + inner class 생성 { + + @Test + fun `places는 trim되며 중복과 공백은 제거된다`() { + // given + val user = user(1L) + + // when + val survey = UserSurvey.of( + user = user, + concept = ConceptType.GIRL_CRUSH, + idolName = "에스파", + visitStartDate = LocalDate.of(2025, 7, 20), + visitEndDate = LocalDate.of(2025, 7, 22), + places = listOf("용산구", "마포구 ", "용산구 ") + ) + + // then + assertEquals(listOf("용산구", "마포구"), survey.places) + } + } + + @Nested + inner class 예외 { + + @Test + fun `places가 모두 공백이면 예외가 발생한다`() { + // given + val user = user(1L) + + // when // then + assertThrows { + UserSurvey.of( + user = user, + concept = ConceptType.GIRL_CRUSH, + idolName = "에스파", + visitStartDate = LocalDate.of(2025, 7, 20), + visitEndDate = LocalDate.of(2025, 7, 22), + places = listOf(" ", " ") + ) + } + } + + @Test + fun `종료일이 시작일보다 이전이면 예외가 발생한다`() { + // given + val user = user(1L) + + // when // then + assertThrows { + UserSurvey.of( + user = user, + concept = ConceptType.GIRL_CRUSH, + idolName = "에스파", + visitStartDate = LocalDate.of(2025, 7, 22), + visitEndDate = LocalDate.of(2025, 7, 20), + places = listOf("용산구", "마포구 ", "용산구 ") + ) + } + } + } +} diff --git a/src/test/kotlin/com/doki/user/domain/vo/NicknameTest.kt b/src/test/kotlin/com/doki/user/user/domain/vo/NicknameTest.kt similarity index 90% rename from src/test/kotlin/com/doki/user/domain/vo/NicknameTest.kt rename to src/test/kotlin/com/doki/user/user/domain/vo/NicknameTest.kt index 01ad142..aeda5da 100644 --- a/src/test/kotlin/com/doki/user/domain/vo/NicknameTest.kt +++ b/src/test/kotlin/com/doki/user/user/domain/vo/NicknameTest.kt @@ -1,7 +1,7 @@ -package com.doki.user.domain.vo +package com.doki.user.user.domain.vo import com.doki.global.exceptions.CustomException -import com.doki.user.domain.exception.UserExceptionType +import com.doki.user.user.domain.exception.UserExceptionType import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/com/doki/user/ui/UserControllerTest.kt b/src/test/kotlin/com/doki/user/user/ui/UserControllerTest.kt similarity index 85% rename from src/test/kotlin/com/doki/user/ui/UserControllerTest.kt rename to src/test/kotlin/com/doki/user/user/ui/UserControllerTest.kt index 0cab782..9f7d611 100644 --- a/src/test/kotlin/com/doki/user/ui/UserControllerTest.kt +++ b/src/test/kotlin/com/doki/user/user/ui/UserControllerTest.kt @@ -1,12 +1,12 @@ -package com.doki.user.ui +package com.doki.user.user.ui import com.doki.global.security.JwtProvider import com.doki.testhelper.IntegrationTest -import com.doki.user.application.dto.GetUserLoginInfoResponse -import com.doki.user.domain.User -import com.doki.user.domain.vo.UserRole -import com.doki.user.infrastructure.UserRepository -import com.doki.user.ui.request.UpdateNicknameRequest +import com.doki.user.user.application.dto.GetUserLoginInfoResponse +import com.doki.user.user.domain.User +import com.doki.user.user.domain.UserRepository +import com.doki.user.user.domain.vo.UserRole +import com.doki.user.user.ui.request.UpdateNicknameRequest import com.fasterxml.jackson.databind.ObjectMapper import io.kotest.matchers.shouldBe import io.restassured.RestAssured.given diff --git a/src/test/kotlin/com/doki/user/user/ui/UserSurveyControllerTest.kt b/src/test/kotlin/com/doki/user/user/ui/UserSurveyControllerTest.kt new file mode 100644 index 0000000..6327bfc --- /dev/null +++ b/src/test/kotlin/com/doki/user/user/ui/UserSurveyControllerTest.kt @@ -0,0 +1,92 @@ +package com.doki.user.user.ui + +import com.fasterxml.jackson.databind.ObjectMapper +import com.doki.global.security.JwtProvider +import com.doki.testhelper.IntegrationTest +import com.doki.user.user.domain.User +import com.doki.user.user.domain.UserRepository +import com.doki.user.user.domain.dto.UserSurveyCreatedResponse +import com.doki.user.user.domain.dto.UserSurveyResponse +import com.doki.user.user.domain.vo.ConceptType +import com.doki.user.user.domain.vo.UserRole +import com.doki.user.user.ui.request.CreateUserSurveyRequest +import io.kotest.matchers.shouldBe +import io.restassured.RestAssured.given +import io.restassured.http.ContentType +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import java.time.LocalDate + +@IntegrationTest +class UserSurveyControllerTest { + + @Autowired + private lateinit var userRepository: UserRepository + + @Autowired + private lateinit var jwtProvider: JwtProvider + + @Autowired + private lateinit var objectMapper: ObjectMapper + + @Test + fun `회원 설문을 E2E로 생성하고 조회한다`() { + // given + val userId = saveUser() + val token = jwtProvider.generateToken(userId, UserRole.USER).accessToken + + val request = CreateUserSurveyRequest( + concept = ConceptType.GIRL_CRUSH, + idolName = "에스파", + visitStartDate = LocalDate.of(2025, 7, 20), + visitEndDate = LocalDate.of(2025, 7, 22), + places = listOf("용산구", "마포구") + ) + + // when + val created = + given() + .header(HttpHeaders.AUTHORIZATION, bearer(token)) + .contentType(ContentType.JSON) + .body(objectMapper.writeValueAsString(request)) + .`when`() + .post("/surveys/$userId") + .then() + .statusCode(HttpStatus.CREATED.value()) + .extract() + .`as`(UserSurveyCreatedResponse::class.java) + + // then + created.id shouldBe created.id + + // when + val found = + given() + .header(HttpHeaders.AUTHORIZATION, bearer(token)) + .`when`() + .get("/surveys/$userId") + .then() + .statusCode(HttpStatus.OK.value()) + .extract() + .`as`(UserSurveyResponse::class.java) + + // then + found.id shouldBe created.id + found.concept shouldBe ConceptType.GIRL_CRUSH + found.idolName shouldBe "에스파" + found.visitStartDate shouldBe LocalDate.of(2025, 7, 20) + found.visitEndDate shouldBe LocalDate.of(2025, 7, 22) + found.places shouldBe listOf("용산구", "마포구") + } + + private fun saveUser(): Long { + val saved = userRepository.save( + User.of(email = "test@google.com") + ) + return saved.id + } + + private fun bearer(token: String) = "Bearer $token" +}