Skip to content

Commit

Permalink
merge develop into feature/email-history
Browse files Browse the repository at this point in the history
  • Loading branch information
NewWisdom authored and knae11 committed Oct 10, 2021
1 parent 0a347b5 commit 566c309
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ class UserAuthenticationService(
private val authenticationCodeRepository: AuthenticationCodeRepository,
private val jwtTokenProvider: JwtTokenProvider
) {
fun generateToken(request: RegisterUserRequest): String {
// todo: 회원 가입 시, email 이 authenticated 인지 확인하는 로직 추가
val user = userRepository.findByEmail(request.email)
?.also { it.authenticate(request.toEntity()) }
?: userRepository.save(request.toEntity())
fun generateTokenByRegister(request: RegisterUserRequest): String {
check(!userRepository.existsByEmail(request.email)) { "이미 가입된 이메일입니다." }
authenticationCodeRepository.getLastByEmail(request.email).validate(request.authenticationCode)
val user = userRepository.save(request.toEntity())
return jwtTokenProvider.createToken(user.email)
}

Expand Down
5 changes: 4 additions & 1 deletion src/main/kotlin/apply/application/UserDtos.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ data class RegisterUserRequest(
val birthday: LocalDate,

@field:NotNull
val password: Password
val password: Password,

@field:NotBlank
val authenticationCode: String
) {
fun toEntity(): User {
return User(name, email, phoneNumber, gender, birthday, password)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ class AuthenticationCode(
authenticated = true
}

fun validate(code: String) {
check(this.code == code) { "인증 코드가 일치하지 않습니다." }
check(authenticated) { "인증되지 않았습니다." }
}

companion object {
private val EXPIRY_MINUTE_TIME: Duration = Duration.ofMinutes(10L)
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/apply/ui/api/UserRestController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class UserRestController(
) {
@PostMapping("/register")
fun generateToken(@RequestBody @Valid request: RegisterUserRequest): ResponseEntity<ApiResponse<String>> {
val token = userAuthenticationService.generateToken(request)
val token = userAuthenticationService.generateTokenByRegister(request)
return ResponseEntity.ok().body(ApiResponse.success(token))
}

Expand Down
18 changes: 18 additions & 0 deletions src/test/kotlin/apply/AuthenticationCodeFixture.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package apply

import apply.domain.authenticationcode.AuthenticationCode
import java.time.LocalDateTime

const val VALID_CODE: String = "VALID"
const val INVALID_CODE: String = "INVALID"
private const val AUTHENTICATION_EMAIL: String = "[email protected]"
private val CREATE_DATE_TIME: LocalDateTime = LocalDateTime.now()

fun createAuthenticationCode(
email: String = AUTHENTICATION_EMAIL,
code: String = VALID_CODE,
authenticated: Boolean = false,
createdDateTime: LocalDateTime = CREATE_DATE_TIME
): AuthenticationCode {
return AuthenticationCode(email, code, authenticated, createdDateTime)
}
File renamed without changes.
59 changes: 37 additions & 22 deletions src/test/kotlin/apply/application/UserAuthenticationServiceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ package apply.application
import apply.BIRTHDAY
import apply.EMAIL
import apply.GENDER
import apply.INVALID_CODE
import apply.NAME
import apply.PASSWORD
import apply.PHONE_NUMBER
import apply.VALID_CODE
import apply.VALID_TOKEN
import apply.WRONG_PASSWORD
import apply.createAuthenticationCode
import apply.createUser
import apply.domain.authenticationcode.AuthenticationCode
import apply.domain.authenticationcode.AuthenticationCodeRepository
import apply.domain.authenticationcode.getLastByEmail
import apply.domain.user.User
import apply.domain.user.UserAuthenticationException
import apply.domain.user.UserRepository
import apply.domain.user.existsByEmail
Expand Down Expand Up @@ -49,34 +50,48 @@ internal class UserAuthenticationServiceTest {
)
}

@DisplayName("토큰 생성은")
@DisplayName("(회원 가입) 토큰 생성은")
@Nested
inner class GenerateToken {
inner class GenerateTokenByRegister {
private lateinit var request: RegisterUserRequest

fun subject(): String {
return userAuthenticationService.generateToken(request)
return userAuthenticationService.generateTokenByRegister(request)
}

@Test
fun `회원이 존재하고 인증에 성공하면 유효한 토큰을 반환한다`() {
every { userRepository.findByEmail(any()) } answers { createUser() }
request = RegisterUserRequest(NAME, EMAIL, PHONE_NUMBER, GENDER, BIRTHDAY, PASSWORD)
assertThat(subject()).isEqualTo(VALID_TOKEN)
fun `가입된 이메일이라면 예외가 발생한다`() {
every { userRepository.existsByEmail(any()) } returns true
request = RegisterUserRequest(NAME, EMAIL, PHONE_NUMBER, GENDER, BIRTHDAY, PASSWORD, VALID_CODE)
assertThrows<IllegalStateException> { subject() }
}

@Test
fun `회원이 존재하지만 인증에 실패하면 예외가 발생한다`() {
every { userRepository.findByEmail(any()) } answers { createUser() }
request = RegisterUserRequest("가짜 이름", EMAIL, PHONE_NUMBER, GENDER, BIRTHDAY, PASSWORD)
assertThrows<UserAuthenticationException> { subject() }
fun `인증된 인증 코드와 일치하지 않는다면 예외가 발생한다`() {
every { userRepository.existsByEmail(any()) } returns false
every { authenticationCodeRepository.getLastByEmail(any()) } returns
createAuthenticationCode(EMAIL, INVALID_CODE)
request = RegisterUserRequest(NAME, EMAIL, PHONE_NUMBER, GENDER, BIRTHDAY, PASSWORD, VALID_CODE)
assertThrows<IllegalStateException> { subject() }
}

@Test
fun `회원이 존재하지 않다면 지원자를 저장한 뒤, 유효한 토큰을 반환한다`() {
every { userRepository.findByEmail(any()) } answers { null }
every { userRepository.save(any<User>()) } returns createUser()
request = RegisterUserRequest(NAME, EMAIL, PHONE_NUMBER, GENDER, BIRTHDAY, PASSWORD)
fun `인증된 이메일이 아니라면 예외가 발생한다`() {
every { userRepository.existsByEmail(any()) } returns false
every { authenticationCodeRepository.getLastByEmail(any()) } returns
createAuthenticationCode(EMAIL, INVALID_CODE)
request = RegisterUserRequest(NAME, "[email protected]", PHONE_NUMBER, GENDER, BIRTHDAY, PASSWORD, VALID_CODE)
assertThrows<IllegalStateException> { subject() }
}

@Test
fun `가입되지 않고 인증된 이메일이라면 회원을 저장하고 토큰을 반환한다`() {
every { userRepository.existsByEmail(any()) } returns false
every { authenticationCodeRepository.getLastByEmail(any()) } returns
createAuthenticationCode(EMAIL, VALID_CODE, true)

every { userRepository.save(any()) } returns createUser()
request = RegisterUserRequest(NAME, EMAIL, PHONE_NUMBER, GENDER, BIRTHDAY, PASSWORD, VALID_CODE)
assertThat(subject()).isEqualTo(VALID_TOKEN)
}
}
Expand All @@ -92,21 +107,21 @@ internal class UserAuthenticationServiceTest {

@Test
fun `회원이 존재하고 인증에 성공하면 유효한 토큰을 반환한다`() {
every { userRepository.findByEmail(any()) } answers { createUser() }
every { userRepository.findByEmail(any()) } returns createUser()
request = AuthenticateUserRequest(EMAIL, PASSWORD)
assertThat(subject()).isEqualTo(VALID_TOKEN)
}

@Test
fun `회원이 존재하지만 인증에 실패하면 예외가 발생한다`() {
every { userRepository.findByEmail(any()) } answers { createUser() }
every { userRepository.findByEmail(any()) } returns createUser()
request = AuthenticateUserRequest(EMAIL, WRONG_PASSWORD)
assertThrows<UserAuthenticationException> { subject() }
}

@Test
fun `회원이 존재하지 않다면 예외가 발생한다`() {
every { userRepository.findByEmail(any()) } answers { null }
every { userRepository.findByEmail(any()) } returns null
request = AuthenticateUserRequest(EMAIL, PASSWORD)
assertThrows<UserAuthenticationException> { subject() }
}
Expand All @@ -115,7 +130,7 @@ internal class UserAuthenticationServiceTest {
@DisplayName("이메일 사용자 인증 시")
@Nested
inner class AuthenticateEmail {
private val authenticationCode = AuthenticationCode("[email protected]")
private val authenticationCode = createAuthenticationCode()

@Test
fun `인증 코드가 일치한다면 인증된 회원으로 변경한다`() {
Expand All @@ -128,7 +143,7 @@ internal class UserAuthenticationServiceTest {
fun `인증 코드가 일치하지 않는다면 예외가 발생한다`() {
every { authenticationCodeRepository.getLastByEmail(any()) } returns authenticationCode
assertThrows<IllegalArgumentException> {
userAuthenticationService.authenticateEmail(authenticationCode.email, "INVALID")
userAuthenticationService.authenticateEmail(authenticationCode.email, INVALID_CODE)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package apply.domain.authenticationcode

import apply.EMAIL
import apply.createAuthenticationCode
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import support.test.RepositoryTest
import java.time.LocalDateTime

private const val EMAIL: String = "[email protected]"

@RepositoryTest
class AuthenticationCodeRepositoryTest(
private val authenticationCodeRepository: AuthenticationCodeRepository
Expand All @@ -16,9 +16,9 @@ class AuthenticationCodeRepositoryTest(
val now = LocalDateTime.now()
val authenticationCodes = authenticationCodeRepository.saveAll(
listOf(
AuthenticationCode(EMAIL, createdDateTime = now),
AuthenticationCode(EMAIL, createdDateTime = now.plusSeconds(1L)),
AuthenticationCode(EMAIL, createdDateTime = now.plusSeconds(2L))
createAuthenticationCode(EMAIL),
createAuthenticationCode(EMAIL, createdDateTime = now.plusSeconds(1L)),
createAuthenticationCode(EMAIL, createdDateTime = now.plusSeconds(2L))
)
)
val actual = authenticationCodeRepository.findFirstByEmailOrderByCreatedDateTimeDesc(EMAIL)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package apply.domain.authenticationcode

import apply.EMAIL
import apply.INVALID_CODE
import apply.VALID_CODE
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
import org.junit.jupiter.api.assertThrows
import java.time.LocalDateTime

private const val EMAIL: String = "[email protected]"
private const val VALID_CODE: String = "VALID"
private const val INVALID_CODE: String = "INVALID"

internal class AuthenticationCodeTest {
@Test
fun `인증 코드를 생성한다`() {
Expand Down Expand Up @@ -46,4 +45,16 @@ internal class AuthenticationCodeTest {
val authenticationCode = AuthenticationCode(EMAIL, VALID_CODE, createdDateTime = now.minusMinutes(11L))
assertThrows<IllegalStateException> { authenticationCode.authenticate(VALID_CODE) }
}

@Test
fun `일치하지 않은 코드로 검증한다`() {
val authenticationCode = AuthenticationCode(EMAIL, VALID_CODE, true)
assertThrows<IllegalStateException> { authenticationCode.validate(INVALID_CODE) }
}

@Test
fun `인증 여부를 검증한다`() {
val authenticationCode = AuthenticationCode(EMAIL, VALID_CODE, false)
assertThrows<IllegalStateException> { authenticationCode.validate(VALID_CODE) }
}
}
30 changes: 11 additions & 19 deletions src/test/kotlin/apply/ui/api/UserRestControllerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ private fun RegisterUserRequest.withPlainPassword(password: String): Map<String,
"phoneNumber" to phoneNumber,
"gender" to gender,
"birthday" to birthday,
"password" to password
"password" to password,
"authenticationCode" to authenticationCode
)
}

Expand Down Expand Up @@ -74,7 +75,8 @@ internal class UserRestControllerTest : RestControllerTest() {
phoneNumber = "010-0000-0000",
gender = Gender.MALE,
birthday = createLocalDate(1995, 2, 2),
password = Password(PASSWORD)
password = Password(PASSWORD),
authenticationCode = "3ea9fa6c"
)

private val userLoginRequest = AuthenticateUserRequest(
Expand Down Expand Up @@ -111,7 +113,7 @@ internal class UserRestControllerTest : RestControllerTest() {

@Test
fun `유효한 회원 생성 및 검증 요청에 대하여 응답으로 토큰이 반환된다`() {
every { userAuthenticationService.generateToken(userRequest) } returns VALID_TOKEN
every { userAuthenticationService.generateTokenByRegister(userRequest) } returns VALID_TOKEN
every { mailSenderService.sendAuthenticationCodeMail(any(), any()) } just Runs
every { userService.getByEmail(userRequest.email) } returns userRequest.toEntity()

Expand All @@ -124,21 +126,6 @@ internal class UserRestControllerTest : RestControllerTest() {
}
}

@Test
fun `기존 회원 정보와 일치하지 않는 회원 생성 및 검증 요청에 응답으로 Unauthorized를 반환한다`() {
every {
userAuthenticationService.generateToken(invalidUserRequest)
} throws UserAuthenticationException()

mockMvc.post("/api/users/register") {
content = objectMapper.writeValueAsBytes(invalidUserRequest.withPlainPassword(INVALID_PASSWORD))
contentType = MediaType.APPLICATION_JSON
}.andExpect {
status { isUnauthorized }
content { json(objectMapper.writeValueAsString(ApiResponse.error("요청 정보가 기존 회원 정보와 일치하지 않습니다"))) }
}
}

@Test
fun `올바른 회원 로그인 요청에 응답으로 Token을 반환한다`() {
every {
Expand Down Expand Up @@ -241,7 +228,12 @@ internal class UserRestControllerTest : RestControllerTest() {
fun `이메일 인증 코드 요청에 응답으로 NoContent를 반환한다`() {
val authenticationCode = AuthenticationCode("[email protected]")
every { userAuthenticationService.generateAuthenticationCode(any()) } returns authenticationCode.code
every { mailSenderService.sendAuthenticationCodeMail(authenticationCode.email, authenticationCode.code) } just Runs
every {
mailSenderService.sendAuthenticationCodeMail(
authenticationCode.email,
authenticationCode.code
)
} just Runs

mockMvc.post("/api/users/authentication-code") {
param("email", authenticationCode.email)
Expand Down

0 comments on commit 566c309

Please sign in to comment.