From cec9434e703a07d67d3a46bf87f1f08f77a8bc6e Mon Sep 17 00:00:00 2001 From: Bob Sin Date: Thu, 30 May 2024 21:42:39 +0900 Subject: [PATCH] =?UTF-8?q?[KAN-109]=20=EC=9C=A0=EC=A0=80=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20+=20=EC=9C=A0=EB=8B=9B=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20(#80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/domain/service/SignUpUserService.kt | 19 +- .../user/domain/service/UpdateUserService.kt | 3 +- .../controller/UpdateUserController.kt | 4 +- .../be/user/presentation/dto/SignUpUserDto.kt | 8 +- .../presentation/dto/UpdatePasswordDto.kt | 4 - .../com/restaurant/be/common/util/UserUtil.kt | 8 +- .../controller/CheckNicknameControllerTest.kt | 98 +++++++++++ .../controller/DeleteUserControllerTest.kt | 93 ++++++++++ .../controller/GetUserControllerTest.kt | 97 +++++++++++ .../controller/SignInUserControllerTest.kt | 149 ++++++++++++++++ .../controller/SignUpUserControllerTest.kt | 144 +++++++++++++++ .../UpdatePasswordControllerTest.kt | 164 ++++++++++++++++++ .../controller/UpdateUserControllerTest.kt | 123 +++++++++++++ .../controller/ValidateEmailControllerTest.kt | 159 +++++++++++++++++ .../presentation/dto/SignUpUserDtoTest.kt | 25 +++ .../be/user/repository/EmailRepositoryTest.kt | 49 ++++++ 16 files changed, 1124 insertions(+), 23 deletions(-) create mode 100644 src/test/kotlin/com/restaurant/be/user/presentation/controller/CheckNicknameControllerTest.kt create mode 100644 src/test/kotlin/com/restaurant/be/user/presentation/controller/DeleteUserControllerTest.kt create mode 100644 src/test/kotlin/com/restaurant/be/user/presentation/controller/GetUserControllerTest.kt create mode 100644 src/test/kotlin/com/restaurant/be/user/presentation/controller/SignInUserControllerTest.kt create mode 100644 src/test/kotlin/com/restaurant/be/user/presentation/controller/SignUpUserControllerTest.kt create mode 100644 src/test/kotlin/com/restaurant/be/user/presentation/controller/UpdatePasswordControllerTest.kt create mode 100644 src/test/kotlin/com/restaurant/be/user/presentation/controller/UpdateUserControllerTest.kt create mode 100644 src/test/kotlin/com/restaurant/be/user/presentation/controller/ValidateEmailControllerTest.kt create mode 100644 src/test/kotlin/com/restaurant/be/user/presentation/dto/SignUpUserDtoTest.kt create mode 100644 src/test/kotlin/com/restaurant/be/user/repository/EmailRepositoryTest.kt diff --git a/src/main/kotlin/com/restaurant/be/user/domain/service/SignUpUserService.kt b/src/main/kotlin/com/restaurant/be/user/domain/service/SignUpUserService.kt index 5f3b0da..103007f 100644 --- a/src/main/kotlin/com/restaurant/be/user/domain/service/SignUpUserService.kt +++ b/src/main/kotlin/com/restaurant/be/user/domain/service/SignUpUserService.kt @@ -21,19 +21,18 @@ class SignUpUserService( @Transactional fun signUpUser(request: SignUpUserRequest): SignUpUserResponse { with(request) { - userRepository.findByNicknameOrEmail(nickname, email)?.let { - if (it.nickname == nickname) { - throw DuplicateUserNicknameException() - } + val user = userRepository.findByNicknameOrEmail(nickname, email) + if (user?.nickname == nickname) { + throw DuplicateUserNicknameException() + } - if (it.email == email) { - throw DuplicateUserEmailException() - } + if (user?.email == email) { + throw DuplicateUserEmailException() } - val user = userRepository.save(toEntity()) + val newUser = userRepository.save(toEntity()) - val token = tokenProvider.createTokens(email, user.roles) + val token = tokenProvider.createTokens(email, newUser.roles) redisRepository.setValue( redisRepository.REFRESH_PREFIX + email, @@ -41,7 +40,7 @@ class SignUpUserService( tokenProvider.refreshTokenValidityInMilliseconds, TimeUnit.MILLISECONDS ) - return SignUpUserResponse(user = user, token = token) + return SignUpUserResponse(user = newUser, token = token) } } } diff --git a/src/main/kotlin/com/restaurant/be/user/domain/service/UpdateUserService.kt b/src/main/kotlin/com/restaurant/be/user/domain/service/UpdateUserService.kt index 55636c1..fcacaa2 100644 --- a/src/main/kotlin/com/restaurant/be/user/domain/service/UpdateUserService.kt +++ b/src/main/kotlin/com/restaurant/be/user/domain/service/UpdateUserService.kt @@ -21,8 +21,7 @@ class UpdateUserService( @Transactional fun updatePassword(request: UpdatePasswordRequest) { - val user = userRepository.findByEmail(request.email) - ?: throw NotFoundUserEmailException() + val user = userRepository.findByEmail(request.email) ?: throw NotFoundUserEmailException() val token = redisRepository .getValue( "user:${request.email}:${EmailSendType diff --git a/src/main/kotlin/com/restaurant/be/user/presentation/controller/UpdateUserController.kt b/src/main/kotlin/com/restaurant/be/user/presentation/controller/UpdateUserController.kt index d111ad8..0115fa2 100644 --- a/src/main/kotlin/com/restaurant/be/user/presentation/controller/UpdateUserController.kt +++ b/src/main/kotlin/com/restaurant/be/user/presentation/controller/UpdateUserController.kt @@ -37,7 +37,7 @@ class UpdateUserController( @Valid @RequestBody request: UpdateUserRequest ): CommonResponse { - updateUserService.updateUser(request, principal.name) - return CommonResponse.success() + val response = updateUserService.updateUser(request, principal.name) + return CommonResponse.success(response) } } diff --git a/src/main/kotlin/com/restaurant/be/user/presentation/dto/SignUpUserDto.kt b/src/main/kotlin/com/restaurant/be/user/presentation/dto/SignUpUserDto.kt index 14b86ff..e62f49a 100644 --- a/src/main/kotlin/com/restaurant/be/user/presentation/dto/SignUpUserDto.kt +++ b/src/main/kotlin/com/restaurant/be/user/presentation/dto/SignUpUserDto.kt @@ -15,23 +15,23 @@ data class SignUpUserRequest( @field:Email(message = "이메일 형식이 아닙니다.") @field:NotBlank(message = "아이디를 입력해 주세요.") @ApiModelProperty(value = "이메일 아이디", example = "test@gmail.com", required = true) - val email: String = "", + val email: String, @field:Size(min = 8, max = 20, message = "8~20자 이내로 입력해 주세요.") @field:NotBlank(message = "비밀번호를 입력해 주세요.") @ApiModelProperty(value = "비밀번호", example = "test12!@", required = true) - val password: String = "", + val password: String, @field:NotBlank(message = "닉네임을 입력해 주세요.") @ApiModelProperty(value = "닉네임", example = "닉네임", required = true) - val nickname: String = "", + val nickname: String, @ApiModelProperty( value = "프로필 이미지 URL", example = "https://test.com/test.jpg", required = true ) - val profileImageUrl: String = "" + val profileImageUrl: String ) { fun toEntity() = User( diff --git a/src/main/kotlin/com/restaurant/be/user/presentation/dto/UpdatePasswordDto.kt b/src/main/kotlin/com/restaurant/be/user/presentation/dto/UpdatePasswordDto.kt index a6183df..d4f94c5 100644 --- a/src/main/kotlin/com/restaurant/be/user/presentation/dto/UpdatePasswordDto.kt +++ b/src/main/kotlin/com/restaurant/be/user/presentation/dto/UpdatePasswordDto.kt @@ -13,10 +13,6 @@ data class UpdatePasswordRequest( @ApiModelProperty(value = "이메일", example = "test@test.com", required = true) val email: String, @field:NotEmpty(message = "비밀번호는 필수 값 입니다.") - @field:Pattern( - regexp = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[\\W_])[A-Za-z\\d\\W_]{8,16}\$", - message = "영소문자, 영대문자, 숫자, 특수문자가 포함된 8~16자 비밀번호" - ) @ApiModelProperty(value = "비밀번호", example = "Test1234!", required = true) val password: String, @field:NotEmpty(message = "변경 토큰은 필수 값 입니다.") diff --git a/src/test/kotlin/com/restaurant/be/common/util/UserUtil.kt b/src/test/kotlin/com/restaurant/be/common/util/UserUtil.kt index 30ae488..357c921 100644 --- a/src/test/kotlin/com/restaurant/be/common/util/UserUtil.kt +++ b/src/test/kotlin/com/restaurant/be/common/util/UserUtil.kt @@ -1,5 +1,6 @@ package com.restaurant.be.common.util +import com.restaurant.be.common.password.PasswordService import com.restaurant.be.user.domain.entity.User import com.restaurant.be.user.repository.UserRepository import org.springframework.security.core.authority.SimpleGrantedAuthority @@ -8,7 +9,12 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA import java.security.Principal fun setUpUser(email: String, userRepository: UserRepository): User { - val user = User(email = email, profileImageUrl = "") + val user = User( + email = email, + profileImageUrl = "", + nickname = "nickname", + password = "password".run(PasswordService::hashPassword) + ) userRepository.save(user) SecurityContextHolder.getContext().authentication = diff --git a/src/test/kotlin/com/restaurant/be/user/presentation/controller/CheckNicknameControllerTest.kt b/src/test/kotlin/com/restaurant/be/user/presentation/controller/CheckNicknameControllerTest.kt new file mode 100644 index 0000000..080bc0d --- /dev/null +++ b/src/test/kotlin/com/restaurant/be/user/presentation/controller/CheckNicknameControllerTest.kt @@ -0,0 +1,98 @@ +package com.restaurant.be.user.presentation.controller + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.restaurant.be.common.CustomDescribeSpec +import com.restaurant.be.common.IntegrationTest +import com.restaurant.be.common.PageDeserializer +import com.restaurant.be.common.response.CommonResponse +import com.restaurant.be.common.util.setUpUser +import com.restaurant.be.restaurant.presentation.controller.dto.common.RestaurantDto +import com.restaurant.be.user.presentation.dto.CheckNicknameResponse +import com.restaurant.be.user.repository.UserRepository +import io.kotest.matchers.shouldBe +import org.springframework.data.domain.Page +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.transaction.annotation.Transactional +import java.nio.charset.Charset + +@IntegrationTest +@Transactional +class CheckNicknameControllerTest( + private val mockMvc: MockMvc, + private val userRepository: UserRepository +) : CustomDescribeSpec() { + private val baseUrl = "/v1/users" + private val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()).apply { + val module = SimpleModule() + module.addDeserializer(Page::class.java, PageDeserializer(RestaurantDto::class.java)) + this.registerModule(module) + } + + init { + beforeEach { + setUpUser("test@gmail.com", userRepository) + } + + describe("#checkNickname basic test") { + it("when nickname is not used should return isDuplicate false") { + // given + val nickname = "test" + + // when + val result = mockMvc.perform( + get("$baseUrl/check-nickname") + .param("nickname", nickname) + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isOk) + .andExpect(jsonPath("$.result").value("SUCCESS")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.data!!.isDuplicate shouldBe false + } + + it("when nickname is used should throw DuplicateUserNicknameException") { + // given + val existedUserNickname = userRepository.findByEmail("test@gmail.com")?.nickname ?: "" + + // when + val result = mockMvc.perform( + get("$baseUrl/check-nickname") + .param("nickname", existedUserNickname) + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.result").value("FAIL")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.message shouldBe "이미 존재 하는 닉네임 입니다." + } + } + } +} diff --git a/src/test/kotlin/com/restaurant/be/user/presentation/controller/DeleteUserControllerTest.kt b/src/test/kotlin/com/restaurant/be/user/presentation/controller/DeleteUserControllerTest.kt new file mode 100644 index 0000000..11c2d25 --- /dev/null +++ b/src/test/kotlin/com/restaurant/be/user/presentation/controller/DeleteUserControllerTest.kt @@ -0,0 +1,93 @@ +package com.restaurant.be.user.presentation.controller + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.restaurant.be.common.CustomDescribeSpec +import com.restaurant.be.common.IntegrationTest +import com.restaurant.be.common.PageDeserializer +import com.restaurant.be.common.response.CommonResponse +import com.restaurant.be.common.util.setUpUser +import com.restaurant.be.restaurant.presentation.controller.dto.common.RestaurantDto +import com.restaurant.be.user.repository.UserRepository +import io.kotest.matchers.shouldBe +import org.springframework.data.domain.Page +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.transaction.annotation.Transactional +import java.nio.charset.Charset + +@IntegrationTest +@Transactional +class DeleteUserControllerTest( + private val mockMvc: MockMvc, + private val userRepository: UserRepository +) : CustomDescribeSpec() { + private val baseUrl = "/v1/users" + private val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()).apply { + val module = SimpleModule() + module.addDeserializer(Page::class.java, PageDeserializer(RestaurantDto::class.java)) + this.registerModule(module) + } + + init { + beforeEach { + setUpUser("test@gmail.com", userRepository) + } + + describe("#deleteUser basic test") { + it("when user delete should return success") { + // given + // when + val result = mockMvc.perform( + delete("$baseUrl") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isOk) + .andExpect(jsonPath("$.result").value("SUCCESS")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.result shouldBe CommonResponse.Result.SUCCESS + } + + it("when not exists user delete should return fail") { + // given + userRepository.deleteAll() + + // when + val result = mockMvc.perform( + delete("$baseUrl") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.result").value("FAIL")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.message shouldBe "존재 하지 않는 유저 입니다." + } + } + } +} diff --git a/src/test/kotlin/com/restaurant/be/user/presentation/controller/GetUserControllerTest.kt b/src/test/kotlin/com/restaurant/be/user/presentation/controller/GetUserControllerTest.kt new file mode 100644 index 0000000..e5096ce --- /dev/null +++ b/src/test/kotlin/com/restaurant/be/user/presentation/controller/GetUserControllerTest.kt @@ -0,0 +1,97 @@ +package com.restaurant.be.user.presentation.controller + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.restaurant.be.common.CustomDescribeSpec +import com.restaurant.be.common.IntegrationTest +import com.restaurant.be.common.PageDeserializer +import com.restaurant.be.common.response.CommonResponse +import com.restaurant.be.common.util.setUpUser +import com.restaurant.be.restaurant.presentation.controller.dto.common.RestaurantDto +import com.restaurant.be.user.presentation.dto.GetUserResponse +import com.restaurant.be.user.repository.UserRepository +import io.kotest.matchers.shouldBe +import org.springframework.data.domain.Page +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.transaction.annotation.Transactional +import java.nio.charset.Charset + +@IntegrationTest +@Transactional +class GetUserControllerTest( + private val mockMvc: MockMvc, + private val userRepository: UserRepository +) : CustomDescribeSpec() { + private val baseUrl = "/v1/users" + private val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()).apply { + val module = SimpleModule() + module.addDeserializer(Page::class.java, PageDeserializer(RestaurantDto::class.java)) + this.registerModule(module) + } + + init { + beforeEach { + setUpUser("test@gmail.com", userRepository) + } + + describe("#getUser basic test") { + it("when existed user should return user info") { + // given + val user = userRepository.findByEmail("test@gmail.com") ?: throw Exception("User not found") + + // when + val result = mockMvc.perform( + get("$baseUrl/${user.id}") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isOk) + .andExpect(jsonPath("$.result").value("SUCCESS")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.data!!.userDto.id shouldBe user.id + actualResult.data!!.userDto.email shouldBe user.email + actualResult.data!!.userDto.nickname shouldBe user.nickname + actualResult.data!!.userDto.profileImageUrl shouldBe user.profileImageUrl + } + + it("when not existed user should fail") { + // given + // when + val result = mockMvc.perform( + get("$baseUrl/12345") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.result").value("FAIL")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.message shouldBe "존재 하지 않는 유저 입니다." + } + } + } +} diff --git a/src/test/kotlin/com/restaurant/be/user/presentation/controller/SignInUserControllerTest.kt b/src/test/kotlin/com/restaurant/be/user/presentation/controller/SignInUserControllerTest.kt new file mode 100644 index 0000000..d5ad405 --- /dev/null +++ b/src/test/kotlin/com/restaurant/be/user/presentation/controller/SignInUserControllerTest.kt @@ -0,0 +1,149 @@ +package com.restaurant.be.user.presentation.controller + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.restaurant.be.common.CustomDescribeSpec +import com.restaurant.be.common.IntegrationTest +import com.restaurant.be.common.PageDeserializer +import com.restaurant.be.common.response.CommonResponse +import com.restaurant.be.common.util.setUpUser +import com.restaurant.be.restaurant.presentation.controller.dto.common.RestaurantDto +import com.restaurant.be.user.presentation.dto.SignInUserResponse +import com.restaurant.be.user.repository.UserRepository +import io.kotest.matchers.shouldBe +import org.springframework.data.domain.Page +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.transaction.annotation.Transactional +import java.nio.charset.Charset + +@IntegrationTest +@Transactional +class SignInUserControllerTest( + private val mockMvc: MockMvc, + private val userRepository: UserRepository +) : CustomDescribeSpec() { + private val baseUrl = "/v1/users/email" + private val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()).apply { + val module = SimpleModule() + module.addDeserializer(Page::class.java, PageDeserializer(RestaurantDto::class.java)) + this.registerModule(module) + } + + init { + beforeEach { + setUpUser("test@gmail.com", userRepository) + } + + describe("#signIn basic test") { + it("when email and password is correct should return token") { + // given + val user = userRepository.findByEmail("test@gmail.com")!! + + // when + val result = mockMvc.perform( + post("$baseUrl/sign-in") + .content( + objectMapper.writeValueAsString( + mapOf( + "email" to user.email, + "password" to "password" + ) + ) + ) + .contentType("application/json") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isOk) + .andExpect(jsonPath("$.result").value("SUCCESS")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.data!!.userDto.email shouldBe user.email + } + + it("when email is not exist should throw NotFoundUserEmailException") { + // given + val notFoundEmail = "notfound@gmail.com" + + // when + val result = mockMvc.perform( + post("$baseUrl/sign-in") + .content( + objectMapper.writeValueAsString( + mapOf( + "email" to notFoundEmail, + "password" to "test1234" + ) + ) + ) + .contentType("application/json") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.result").value("FAIL")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.message shouldBe "존재 하지 않는 유저 이메일 입니다." + } + + it("when password is incorrect should throw InvalidPasswordException") { + // given + val user = userRepository.findByEmail("test@gmail.com") + + // when + val result = mockMvc.perform( + post("$baseUrl/sign-in") + .content( + objectMapper.writeValueAsString( + mapOf( + "email" to user?.email, + "password" to "test1234" + ) + ) + ) + .contentType("application/json") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isUnauthorized) + .andExpect(jsonPath("$.result").value("FAIL")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.message shouldBe "패스워드가 일치 하지 않습니다." + } + } + } +} diff --git a/src/test/kotlin/com/restaurant/be/user/presentation/controller/SignUpUserControllerTest.kt b/src/test/kotlin/com/restaurant/be/user/presentation/controller/SignUpUserControllerTest.kt new file mode 100644 index 0000000..e38e00b --- /dev/null +++ b/src/test/kotlin/com/restaurant/be/user/presentation/controller/SignUpUserControllerTest.kt @@ -0,0 +1,144 @@ +package com.restaurant.be.user.presentation.controller + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.restaurant.be.common.CustomDescribeSpec +import com.restaurant.be.common.IntegrationTest +import com.restaurant.be.common.PageDeserializer +import com.restaurant.be.common.response.CommonResponse +import com.restaurant.be.common.util.setUpUser +import com.restaurant.be.restaurant.presentation.controller.dto.common.RestaurantDto +import com.restaurant.be.user.presentation.dto.SignUpUserResponse +import com.restaurant.be.user.repository.UserRepository +import io.kotest.matchers.shouldBe +import org.springframework.data.domain.Page +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.transaction.annotation.Transactional +import java.nio.charset.Charset + +@IntegrationTest +@Transactional +class SignUpUserControllerTest( + private val mockMvc: MockMvc, + private val userRepository: UserRepository +) : CustomDescribeSpec() { + private val baseUrl = "/v1/users" + private val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()).apply { + val module = SimpleModule() + module.addDeserializer(Page::class.java, PageDeserializer(RestaurantDto::class.java)) + this.registerModule(module) + } + + init { + beforeEach { + setUpUser("test@gmail.com", userRepository) + } + + describe("#signUpUser basic test") { + it("when email and password is correct should return token") { + // given + val email = "newUser@gmail.com" + val password = "password" + val nickname = "newUser" + val profileImageUrl = "profileImageUrl" + + // when + val result = mockMvc.perform( + post("$baseUrl/email/sign-up").content( + objectMapper.writeValueAsString( + mapOf( + "email" to email, + "password" to password, + "nickname" to nickname, + "profileImageUrl" to profileImageUrl + ) + ) + ).contentType("application/json") + ).also { + println(it.andReturn().response.contentAsString) + }.andExpect(status().isOk).andExpect(jsonPath("$.result").value("SUCCESS")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.data!!.userDto.email shouldBe email + } + + it("when existed nickname should throw DuplicateUserNicknameException") { + // given + val existedUserNickname = + userRepository.findByEmail("test@gmail.com")?.nickname ?: "" + + // when + val result = mockMvc.perform( + post("$baseUrl/email/sign-up").content( + objectMapper.writeValueAsString( + mapOf( + "email" to "test@test.com", + "password" to "123456789", + "nickname" to existedUserNickname, + "profileImageUrl" to "" + ) + ) + ).contentType("application/json") + ).also { + println(it.andReturn().response.contentAsString) + }.andExpect(status().isBadRequest).andExpect(jsonPath("$.result").value("FAIL")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.message shouldBe "이미 존재 하는 닉네임 입니다." + } + + it("when existed email should throw DuplicateUserEmailException") { + // given + val existedUserEmail = "test@gmail.com" + + // when + val result = mockMvc.perform( + post("$baseUrl/email/sign-up").content( + objectMapper.writeValueAsString( + mapOf( + "email" to existedUserEmail, + "password" to "123456789", + "nickname" to "test", + "profileImageUrl" to "" + ) + ) + ).contentType("application/json") + ).also { + println(it.andReturn().response.contentAsString) + }.andExpect(status().isBadRequest).andExpect(jsonPath("$.result").value("FAIL")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = object : TypeReference>() {} + val actualResult: CommonResponse = objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.message shouldBe "이미 존재 하는 이메일 입니다." + } + } + } +} diff --git a/src/test/kotlin/com/restaurant/be/user/presentation/controller/UpdatePasswordControllerTest.kt b/src/test/kotlin/com/restaurant/be/user/presentation/controller/UpdatePasswordControllerTest.kt new file mode 100644 index 0000000..69808f6 --- /dev/null +++ b/src/test/kotlin/com/restaurant/be/user/presentation/controller/UpdatePasswordControllerTest.kt @@ -0,0 +1,164 @@ +package com.restaurant.be.user.presentation.controller + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.restaurant.be.common.CustomDescribeSpec +import com.restaurant.be.common.IntegrationTest +import com.restaurant.be.common.PageDeserializer +import com.restaurant.be.common.response.CommonResponse +import com.restaurant.be.common.util.setUpUser +import com.restaurant.be.restaurant.presentation.controller.dto.common.RestaurantDto +import com.restaurant.be.user.repository.UserRepository +import io.kotest.matchers.shouldBe +import org.springframework.data.domain.Page +import org.springframework.data.redis.core.RedisTemplate +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.transaction.annotation.Transactional +import java.nio.charset.Charset + +@IntegrationTest +@Transactional +class UpdatePasswordControllerTest( + private val mockMvc: MockMvc, + private val userRepository: UserRepository, + private val redisTemplate: RedisTemplate +) : CustomDescribeSpec() { + private val baseUrl = "/v1/users/password" + private val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()).apply { + val module = SimpleModule() + module.addDeserializer(Page::class.java, PageDeserializer(RestaurantDto::class.java)) + this.registerModule(module) + } + + init { + beforeEach { + setUpUser("test@gmail.com", userRepository) + } + + afterEach { + redisTemplate.keys("*").forEach { redisTemplate.delete(it) } + } + + describe("#passwordUpdate") { + it("when token don't saved should return 400") { + // given + val email = userRepository.findByEmail("test@gmail.com")?.email ?: "" + + // when + val result = mockMvc.perform( + patch(baseUrl) + .content( + objectMapper.writeValueAsString( + mapOf( + "email" to email, + "password" to "!!!QQQqqq111", + "token" to "token" + ) + ) + ) + .contentType("application/json") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.result").value("FAIL")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = + objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.result shouldBe CommonResponse.Result.FAIL + actualResult.message shouldBe "유저가 비밀번호 초기화 상태가 아닙니다." + } + + it("when token don't match should return 400") { + // given + val email = userRepository.findByEmail("test@gmail.com")?.email ?: "" + redisTemplate.opsForValue().set("user:$email:reset_password_token", "tokenTest") + + // when + val result = mockMvc.perform( + patch(baseUrl) + .content( + objectMapper.writeValueAsString( + mapOf( + "email" to email, + "password" to "!!!QQQqqq111", + "token" to "token" + ) + ) + ) + .contentType("application/json") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.result").value("FAIL")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = + objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.result shouldBe CommonResponse.Result.FAIL + actualResult.message shouldBe "토큰이 일치 하지 않습니다." + } + + it("when token saved should return 200") { + // given + val email = userRepository.findByEmail("test@gmail.com")?.email ?: "" + redisTemplate.opsForValue().set("user:$email:reset_password_token", "tokenTest") + + // when + val result = mockMvc.perform( + patch(baseUrl) + .content( + objectMapper.writeValueAsString( + mapOf( + "email" to email, + "password" to "!!!QQQqqq111", + "token" to "tokenTest" + ) + ) + ) + .contentType("application/json") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isOk) + .andExpect(jsonPath("$.result").value("SUCCESS")) + .andReturn() + + val responseContent = result.response.getContentAsString(Charset.forName("UTF-8")) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = + objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.result shouldBe CommonResponse.Result.SUCCESS + } + } + } +} diff --git a/src/test/kotlin/com/restaurant/be/user/presentation/controller/UpdateUserControllerTest.kt b/src/test/kotlin/com/restaurant/be/user/presentation/controller/UpdateUserControllerTest.kt new file mode 100644 index 0000000..3f5e02e --- /dev/null +++ b/src/test/kotlin/com/restaurant/be/user/presentation/controller/UpdateUserControllerTest.kt @@ -0,0 +1,123 @@ +package com.restaurant.be.user.presentation.controller + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.restaurant.be.common.CustomDescribeSpec +import com.restaurant.be.common.IntegrationTest +import com.restaurant.be.common.PageDeserializer +import com.restaurant.be.common.response.CommonResponse +import com.restaurant.be.common.util.setUpUser +import com.restaurant.be.restaurant.presentation.controller.dto.common.RestaurantDto +import com.restaurant.be.user.presentation.dto.UpdateUserResponse +import com.restaurant.be.user.repository.UserRepository +import io.kotest.matchers.shouldBe +import org.springframework.data.domain.Page +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.transaction.annotation.Transactional +import java.nio.charset.Charset + +@IntegrationTest +@Transactional +class UpdateUserControllerTest( + private val mockMvc: MockMvc, + private val userRepository: UserRepository +) : CustomDescribeSpec() { + private val baseUrl = "/v1/users" + private val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()).apply { + val module = SimpleModule() + module.addDeserializer(Page::class.java, PageDeserializer(RestaurantDto::class.java)) + this.registerModule(module) + } + + init { + beforeEach { + setUpUser("test@gmail.com", userRepository) + } + + describe("#updateUser") { + it("when existed user update should return updated user") { + // given + // when + val result = mockMvc.perform( + patch(baseUrl) + .content( + objectMapper.writeValueAsString( + mapOf( + "nickname" to "newNickname", + "profileImageUrl" to "newProfileImageUrl" + ) + ) + ) + .contentType("application/json") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isOk) + .andExpect(jsonPath("$.result").value("SUCCESS")) + .andReturn() + + val responseContent = result.response.getContentAsString( + Charset.forName( + "UTF-8" + ) + ) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = + objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.data!!.userDto.nickname shouldBe "newNickname" + actualResult.data!!.userDto.profileImageUrl shouldBe "newProfileImageUrl" + } + + it("when not existed user update should return 404") { + // given + userRepository.deleteAll() + // when + val result = mockMvc.perform( + patch(baseUrl) + .content( + objectMapper.writeValueAsString( + mapOf( + "nickname" to "newNickname", + "profileImageUrl" to "newProfileImageUrl" + ) + ) + ) + .contentType("application/json") + ).also { + println(it.andReturn().response.contentAsString) + } + .andExpect(status().isBadRequest) + .andExpect(jsonPath("$.result").value("FAIL")) + .andReturn() + + val responseContent = result.response.getContentAsString( + Charset.forName( + "UTF-8" + ) + ) + val responseType = + object : TypeReference>() {} + val actualResult: CommonResponse = + objectMapper.readValue( + responseContent, + responseType + ) + + // then + actualResult.result shouldBe CommonResponse.Result.FAIL + actualResult.message shouldBe "존재 하지 않는 유저 이메일 입니다." + } + } + } +} diff --git a/src/test/kotlin/com/restaurant/be/user/presentation/controller/ValidateEmailControllerTest.kt b/src/test/kotlin/com/restaurant/be/user/presentation/controller/ValidateEmailControllerTest.kt new file mode 100644 index 0000000..adedc74 --- /dev/null +++ b/src/test/kotlin/com/restaurant/be/user/presentation/controller/ValidateEmailControllerTest.kt @@ -0,0 +1,159 @@ +package com.restaurant.be.user.presentation.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.restaurant.be.common.CustomDescribeSpec +import com.restaurant.be.common.IntegrationTest +import com.restaurant.be.common.PageDeserializer +import com.restaurant.be.common.exception.InvalidEmailCodeException +import com.restaurant.be.common.exception.SendEmailException +import com.restaurant.be.common.exception.SkkuEmailException +import com.restaurant.be.common.redis.RedisRepository +import com.restaurant.be.common.response.CommonResponse +import com.restaurant.be.common.util.setUpUser +import com.restaurant.be.restaurant.presentation.controller.dto.common.RestaurantDto +import com.restaurant.be.user.domain.service.ValidateEmailService +import com.restaurant.be.user.presentation.dto.SendEmailRequest +import com.restaurant.be.user.presentation.dto.ValidateEmailRequest +import com.restaurant.be.user.presentation.dto.common.EmailSendType +import com.restaurant.be.user.repository.EmailRepository +import com.restaurant.be.user.repository.UserRepository +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import org.springframework.data.domain.Page +import org.springframework.data.redis.core.RedisTemplate +import org.springframework.transaction.annotation.Transactional + +@IntegrationTest +@Transactional +class ValidateEmailControllerTest( + private val userRepository: UserRepository, + private val redisTemplate: RedisTemplate, + private val redisRepository: RedisRepository +) : CustomDescribeSpec() { + private val emailRepository: EmailRepository = mockk() + private val validateEmailService = ValidateEmailService(emailRepository, "", redisRepository) + private val validateEmailController = ValidateEmailController(validateEmailService) + + private val baseUrl = "/v1/users/email" + private val objectMapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule()).apply { + val module = SimpleModule() + module.addDeserializer(Page::class.java, PageDeserializer(RestaurantDto::class.java)) + this.registerModule(module) + } + + init { + beforeEach { + setUpUser("test@gmail.com", userRepository) + } + + afterEach { + redisTemplate.keys("*").forEach { redisTemplate.delete(it) } + } + + describe("#sendEmail") { + it("when sign up email success should return 200") { + // given + val request = + SendEmailRequest(email = "test@g.skku.edu", sendType = EmailSendType.SIGN_UP) + + every { emailRepository.generateRandomCode() } returns "123456" + every { emailRepository.sendEmail(any()) } returns Unit + + // when + val result = validateEmailController.sendEmail(request) + + // then + result.result shouldBe CommonResponse.Result.SUCCESS + } + + it("when reset password email success should return 200") { + // given + val request = + SendEmailRequest( + email = "test@g.skku.edu", + sendType = EmailSendType.RESET_PASSWORD + ) + + every { emailRepository.generateRandomCode() } returns "123456" + every { emailRepository.sendEmail(any()) } returns Unit + + // when + val result = validateEmailController.sendEmail(request) + + // then + result.result shouldBe CommonResponse.Result.SUCCESS + } + + it("when invalid email should return 400") { + // given + val request = + SendEmailRequest(email = "test@gmail.com", sendType = EmailSendType.SIGN_UP) + + // then + shouldThrow { + // when + validateEmailController.sendEmail(request) + } + } + + it("when email sender fail should return 400") { + // given + val request = + SendEmailRequest(email = "test@g.skku.edu", sendType = EmailSendType.SIGN_UP) + + every { emailRepository.generateRandomCode() } returns "123456" + every { emailRepository.sendEmail(any()) } throws Exception() + + // then + shouldThrow { + // when + validateEmailController.sendEmail(request) + } + } + } + + describe("#validateEmail") { + it("when valid code success should return 200") { + // given + val request = ValidateEmailRequest( + email = "test@test.com", + code = "123456", + sendType = EmailSendType.SIGN_UP + ) + redisTemplate.opsForValue().set( + "user:${request.email}:${request.sendType.name.lowercase()}_code", + "123456" + ) + + // when + val result = validateEmailController.validateEmail(request) + + // then + result.result shouldBe CommonResponse.Result.SUCCESS + } + + it("when invalid code should return InvalidEmailCodeException") { + // given + val request = ValidateEmailRequest( + email = "test@gmail.com", + code = "123456", + sendType = EmailSendType.SIGN_UP + ) + redisTemplate.opsForValue().set( + "user:${request.email}:${request.sendType.name.lowercase()}_code", + "1234567" + ) + + // then + shouldThrow { + // when + validateEmailController.validateEmail(request) + } + } + } + } +} diff --git a/src/test/kotlin/com/restaurant/be/user/presentation/dto/SignUpUserDtoTest.kt b/src/test/kotlin/com/restaurant/be/user/presentation/dto/SignUpUserDtoTest.kt new file mode 100644 index 0000000..22151b1 --- /dev/null +++ b/src/test/kotlin/com/restaurant/be/user/presentation/dto/SignUpUserDtoTest.kt @@ -0,0 +1,25 @@ +package com.restaurant.be.user.presentation.dto + +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldNotBeEmpty + +class SignUpUserDtoTest : DescribeSpec({ + describe("SignUpUserRequest") { + context("when valid parameters are provided") { + it("should create a valid User entity") { + val user = SignUpUserRequest( + email = "test@gmail.com", + password = "test12!@", + nickname = "닉네임", + profileImageUrl = "https://test.com/test.jpg" + ) + + user.email shouldBe "test@gmail.com" + user.password.shouldNotBeEmpty() + user.nickname shouldBe "닉네임" + user.profileImageUrl shouldBe "https://test.com/test.jpg" + } + } + } +}) diff --git a/src/test/kotlin/com/restaurant/be/user/repository/EmailRepositoryTest.kt b/src/test/kotlin/com/restaurant/be/user/repository/EmailRepositoryTest.kt new file mode 100644 index 0000000..b8ab3b5 --- /dev/null +++ b/src/test/kotlin/com/restaurant/be/user/repository/EmailRepositoryTest.kt @@ -0,0 +1,49 @@ +package com.restaurant.be.user.repository + +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.shouldBe +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import software.amazon.awssdk.services.ses.SesClient +import software.amazon.awssdk.services.ses.model.SendEmailRequest +import java.time.LocalDateTime +import java.time.ZoneOffset +import kotlin.random.Random + +class EmailRepositoryTest : DescribeSpec({ + + val sesClient = mockk() + val emailRepository = EmailRepository(sesClient) + + describe("EmailRepository") { + + context("sendEmail") { + it("should call sesClient.sendEmail with the correct request") { + val sendEmailRequest = SendEmailRequest.builder() + .destination { it.toAddresses("test@example.com") } + .message { + it.subject { it.data("Test Email") } + it.body { it.text { it.data("This is a test email.") } } + } + .source("noreply@example.com") + .build() + + every { sesClient.sendEmail(any()) } returns mockk() + + emailRepository.sendEmail(sendEmailRequest) + + coVerify { sesClient.sendEmail(sendEmailRequest) } + } + } + + context("generateRandomCode") { + it("should generate a 6 digit random code") { + val fixedSeed = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) + val rnd = Random(fixedSeed) + + emailRepository.generateRandomCode().length shouldBe 6 + } + } + } +})