From b0fe8213fb4111bf880df71f45ffe7037deeb03e Mon Sep 17 00:00:00 2001 From: JiwonDev Date: Sat, 4 Sep 2021 17:17:49 +0900 Subject: [PATCH 1/3] =?UTF-8?q?ADD=20AccountRepository=20update=20&=20Toke?= =?UTF-8?q?nVerifier=20HTTP=20API=20-=20MybatisAccountRepository=20update?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20-=20RegisterAccountApi=20->=20AccountCo?= =?UTF-8?q?mmandApi=20=EB=B3=80=EA=B2=BD=20-=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20TestAccountRepositoryStub=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../account/application/AccountFacade.java | 4 + .../app/account/domain/AccountRepository.java | 3 + .../MybatisAccountRepository.java | 10 + .../presentation/AccountCommandApi.java | 53 +++++ .../presentation/RegisterAccountApi.java | 30 --- .../mapper/xml/AccountCommandMapper.xml | 12 ++ .../account/domain/FakeAccountRepository.java | 45 ++++ .../domain/RegisterAccountProcessorTest.java | 32 +-- .../MybatisAccountRepositoryTest.java | 12 ++ .../presentation/AccountCommandApiTests.java | 200 ++++++++++++++++++ .../presentation/RegisterAccountApiTests.java | 125 ----------- 11 files changed, 340 insertions(+), 186 deletions(-) create mode 100644 src/main/java/com/yam/app/account/presentation/AccountCommandApi.java delete mode 100644 src/main/java/com/yam/app/account/presentation/RegisterAccountApi.java create mode 100644 src/test/java/com/yam/app/account/domain/FakeAccountRepository.java create mode 100644 src/test/java/com/yam/app/account/presentation/AccountCommandApiTests.java delete mode 100644 src/test/java/com/yam/app/account/presentation/RegisterAccountApiTests.java diff --git a/src/main/java/com/yam/app/account/application/AccountFacade.java b/src/main/java/com/yam/app/account/application/AccountFacade.java index 2dd2ab8..d0347f7 100644 --- a/src/main/java/com/yam/app/account/application/AccountFacade.java +++ b/src/main/java/com/yam/app/account/application/AccountFacade.java @@ -29,4 +29,8 @@ public class AccountFacade { publisher.publishEvent(new RegisterAccountEvent(entity)); return translator.toResponse(entity); } + + public boolean verify(String token, String email) { + return false; + } } diff --git a/src/main/java/com/yam/app/account/domain/AccountRepository.java b/src/main/java/com/yam/app/account/domain/AccountRepository.java index 2b24467..30a48b3 100644 --- a/src/main/java/com/yam/app/account/domain/AccountRepository.java +++ b/src/main/java/com/yam/app/account/domain/AccountRepository.java @@ -7,4 +7,7 @@ public interface AccountRepository { boolean existsByNickname(String nickname); Account save(Account entity); + + Account update(Account entity); + } diff --git a/src/main/java/com/yam/app/account/infrastructure/MybatisAccountRepository.java b/src/main/java/com/yam/app/account/infrastructure/MybatisAccountRepository.java index 4b7d09f..8382348 100644 --- a/src/main/java/com/yam/app/account/infrastructure/MybatisAccountRepository.java +++ b/src/main/java/com/yam/app/account/infrastructure/MybatisAccountRepository.java @@ -22,6 +22,16 @@ public final class MybatisAccountRepository implements AccountRepository, Accoun return result != 0; } + @Override + public Account update(Account entity) { + int result = template.update(COMMAND_NAMESPACE + "update", entity); + if (result != 1) { + throw new RuntimeException( + String.format("There was a problem updating the object : %s", entity)); + } + return findByEmail(entity.getEmail()); + } + @Override public boolean existsByNickname(String nickname) { int result = template.selectOne(COMMAND_NAMESPACE + "existsByNickname", nickname); diff --git a/src/main/java/com/yam/app/account/presentation/AccountCommandApi.java b/src/main/java/com/yam/app/account/presentation/AccountCommandApi.java new file mode 100644 index 0000000..93e6a37 --- /dev/null +++ b/src/main/java/com/yam/app/account/presentation/AccountCommandApi.java @@ -0,0 +1,53 @@ +package com.yam.app.account.presentation; + +import com.yam.app.account.application.AccountFacade; +import java.net.URI; +import javax.validation.Valid; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping( + produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE +) +public final class AccountCommandApi { + + private final AccountFacade accountFacade; + + public AccountCommandApi(AccountFacade accountFacade) { + this.accountFacade = accountFacade; + } + + @PostMapping("/api/accounts") + public ResponseEntity register( + @RequestBody @Valid RegisterAccountRequest request) { + return ResponseEntity.ok(accountFacade.register(request)); + } + + /** + * 회원가입 이메일 검증 컨트롤러 + * 임시로 "http://localhost:3000/login"로 리다이렉트 되도록 설정. + */ + @GetMapping("/api/accounts/authorize") + public ResponseEntity verify( + @RequestParam String token, + @RequestParam String email) throws Exception { + var result = accountFacade.verify(token, email); + if (!result) { + return ResponseEntity.badRequest().build(); + } + var uri = new URI("http://localhost:3000/login"); + var header = new HttpHeaders(); + header.setLocation(uri); + return new ResponseEntity<>(header, HttpStatus.SEE_OTHER); + } +} diff --git a/src/main/java/com/yam/app/account/presentation/RegisterAccountApi.java b/src/main/java/com/yam/app/account/presentation/RegisterAccountApi.java deleted file mode 100644 index 2a5468a..0000000 --- a/src/main/java/com/yam/app/account/presentation/RegisterAccountApi.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.yam.app.account.presentation; - -import com.yam.app.account.application.AccountFacade; -import javax.validation.Valid; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping( - produces = MediaType.APPLICATION_JSON_VALUE, - consumes = MediaType.APPLICATION_JSON_VALUE -) -public final class RegisterAccountApi { - - private final AccountFacade accountFacade; - - public RegisterAccountApi(AccountFacade accountFacade) { - this.accountFacade = accountFacade; - } - - @PostMapping("/api/accounts") - public ResponseEntity register( - @RequestBody @Valid RegisterAccountRequest request) { - return ResponseEntity.ok(accountFacade.register(request)); - } -} diff --git a/src/main/resources/mapper/xml/AccountCommandMapper.xml b/src/main/resources/mapper/xml/AccountCommandMapper.xml index 6a683f5..a6bb04c 100644 --- a/src/main/resources/mapper/xml/AccountCommandMapper.xml +++ b/src/main/resources/mapper/xml/AccountCommandMapper.xml @@ -2,6 +2,18 @@ + + UPDATE ACCOUNT + SET email = #{email}, + email_check_token = #{emailCheckToken}, + email_check_token_generated_at = #{emailCheckTokenGeneratedAt}, + email_verified = #{emailVerified}, + nickname = #{nickname}, + password = #{password}, + withdraw = #{withdraw} + WHERE id = #{id} + LIMIT 1 + diff --git a/src/test/java/com/yam/app/account/domain/ConfirmRegisterAccountProcessorTest.java b/src/test/java/com/yam/app/account/domain/ConfirmRegisterAccountProcessorTest.java new file mode 100644 index 0000000..03617d0 --- /dev/null +++ b/src/test/java/com/yam/app/account/domain/ConfirmRegisterAccountProcessorTest.java @@ -0,0 +1,72 @@ +package com.yam.app.account.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.yam.app.account.application.ConfirmRegisterAccountCommand; +import java.util.Arrays; +import java.util.Collection; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; + +@DisplayName("회원 검증 도메인 서비스") +class ConfirmRegisterAccountProcessorTest { + + @TestFactory + @DisplayName("이메일 검증 시나리오") + Collection verify_account_scenarios() { + var accountRepository = new FakeAccountRepository(); + var accountReader = accountRepository; + var tokenVerifier = new TokenVerifier(accountReader); + var confirmRegisterAccountProcessor = new ConfirmRegisterAccountProcessor(accountReader, + accountRepository, tokenVerifier); + + var account = accountRepository.save( + Account.of("jiwonDev@gmail.com", "jiwon", "password!")); + + return Arrays.asList( + DynamicTest.dynamicTest("회원 이메일 토큰검증에 성공한다.", () -> { + //Arrange + var command = new ConfirmRegisterAccountCommand(account.getEmailCheckToken(), + account.getEmail()); + + // Act + confirmRegisterAccountProcessor.registerConfirm(command); + Account updatedAccount = accountRepository.findByEmail(account.getEmail()); + + // Assert + assertThat(updatedAccount.isEmailVerified()).isTrue(); + }), + DynamicTest.dynamicTest("이메일이나 토큰의 값이 null인 경우 예외를 리턴한다.", + () -> { + //Arrange + var command = new ConfirmRegisterAccountCommand(null, null); + + // Act & Assert + assertThatExceptionOfType(NullPointerException.class) + .isThrownBy(() -> confirmRegisterAccountProcessor.registerConfirm(command)); + }), + DynamicTest.dynamicTest("이메일 검증에 실패하여 예외를 리턴한다.", + () -> { + //Arrange + var command = new ConfirmRegisterAccountCommand(account.getEmailCheckToken(), + "HiIamNotExistEmail@naver.com"); + + // Act & Assert + assertThatExceptionOfType(IllegalStateException.class) + .isThrownBy(() -> confirmRegisterAccountProcessor.registerConfirm(command)); + }), + DynamicTest.dynamicTest("토큰 검증에 실패하여 예외를 리턴한다.", + () -> { + //Arrange + var command = new ConfirmRegisterAccountCommand("안녕난가짜토큰이라고해", + account.getEmail()); + + // Act & Assert + assertThatExceptionOfType(IllegalStateException.class) + .isThrownBy(() -> confirmRegisterAccountProcessor.registerConfirm(command)); + }) + ); + } +} diff --git a/src/test/java/com/yam/app/account/domain/FakeAccountRepository.java b/src/test/java/com/yam/app/account/domain/FakeAccountRepository.java index df3a9e7..80c8bb6 100644 --- a/src/test/java/com/yam/app/account/domain/FakeAccountRepository.java +++ b/src/test/java/com/yam/app/account/domain/FakeAccountRepository.java @@ -3,9 +3,7 @@ package com.yam.app.account.domain; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; -import org.springframework.stereotype.Component; -@Component public final class FakeAccountRepository implements AccountRepository, AccountReader { private final Map data = new ConcurrentHashMap<>(); diff --git a/src/test/java/com/yam/app/account/domain/TokenVerifierTest.java b/src/test/java/com/yam/app/account/domain/TokenVerifierTest.java index cebe347..021e21b 100644 --- a/src/test/java/com/yam/app/account/domain/TokenVerifierTest.java +++ b/src/test/java/com/yam/app/account/domain/TokenVerifierTest.java @@ -1,77 +1,21 @@ package com.yam.app.account.domain; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; -@DisplayName("검증 도메인 서비스") +@DisplayName("토큰 검증 도메인 테스트") class TokenVerifierTest { @Test - @DisplayName("이메일과 토큰의 값을 입력받고 검증에 성공하여 회원의 이메일 검증 상태를 갱신하고, true를 반환한다.") - void verify_success() { - + @DisplayName("입력된 이메일과 토큰값을 예외를 발생시키지 않고 올바르게 검증하는지 테스트한다.") + void verify_email_and_token_correctly() throws Exception { //Arrange var accountRepository = new FakeAccountRepository(); - var accountReader = accountRepository; - var tokenVerifier = new TokenVerifier(accountReader, accountRepository); - + var tokenVerifier = new TokenVerifier(accountRepository); var account = accountRepository.save( - Account.of("jiwonDev@gmail.com", "jiwon", "password!")); + Account.of("jijiwon@gmail.com", "jiwon", "password!")); - //Act - boolean result = tokenVerifier.verify(account.getEmailCheckToken(), - account.getEmail()); - var updatedAccount = accountReader.findByEmail(account.getEmail()); - - //Assert - assertThat(result).isTrue(); - assertThat(updatedAccount.isEmailVerified()).isTrue(); - } - - @ParameterizedTest - @NullAndEmptySource - @DisplayName("이메일과 토큰의 값이 비어있거나 null인 경우 IllegalArgumentException이 발생한다.") - void param_is_empty_or_null(String arg) { - // Arrange - var accountRepository = new FakeAccountRepository(); - var accountReader = new FakeAccountRepository(); - var tokenVerifier = new TokenVerifier(accountReader, accountRepository); - - // Act & Assert - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> tokenVerifier.verify(arg, arg)); - } - - @Test - @DisplayName("유효하지 않은 이메일의 경우 IllegalStateException이 발생한다.") - void email_is_not_valid() { - // Arrange - var accountRepository = new FakeAccountRepository(); - var accountReader = new FakeAccountRepository(); - var tokenVerifier = new TokenVerifier(accountReader, accountRepository); - - // Act & Assert - assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy(() -> tokenVerifier.verify("abcdefgh", "abcdefgh@naver.com")); - } - - @Test - @DisplayName("유효하지 않은 토큰인 경우 IllegalStateException이 발생한다.") - void token_is_not_valid() { - // Arrange - var accountRepository = new FakeAccountRepository(); - var accountReader = new FakeAccountRepository(); - var tokenVerifier = new TokenVerifier(accountReader, accountRepository); - - accountRepository.save(Account.of("jiwonDev@gmail.com", "jiwon", "password!")); - - // Act & Assert - assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy(() -> tokenVerifier.verify("asdhji", "jiwondev@naver.com")); + //Act & Assert + tokenVerifier.verify(account.getEmailCheckToken(), account.getEmail()); } } diff --git a/src/test/java/com/yam/app/account/infrastructure/MybatisAccountRepositoryTest.java b/src/test/java/com/yam/app/account/infrastructure/MybatisAccountRepositoryTest.java index c25bc3c..a5b4cc1 100644 --- a/src/test/java/com/yam/app/account/infrastructure/MybatisAccountRepositoryTest.java +++ b/src/test/java/com/yam/app/account/infrastructure/MybatisAccountRepositoryTest.java @@ -60,7 +60,7 @@ class MybatisAccountRepositoryTest { Account account = accountRepository.save( Account.of("jiwon22@gmail.com", "jiwon2", "password!")); - account.completeRegister(); // boolean emailVerified = true + account.completeRegister(); Account updatedAccount = accountRepository.update(account); assertThat(updatedAccount.isEmailVerified()).isTrue(); diff --git a/src/test/java/com/yam/app/account/presentation/AccountCommandApiTests.java b/src/test/java/com/yam/app/account/presentation/AccountCommandApiTests.java index 7f62356..a0df535 100644 --- a/src/test/java/com/yam/app/account/presentation/AccountCommandApiTests.java +++ b/src/test/java/com/yam/app/account/presentation/AccountCommandApiTests.java @@ -1,6 +1,6 @@ package com.yam.app.account.presentation; -import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -35,21 +35,23 @@ class AccountCommandApiTests { @Nested @DisplayName("이메일 검증 HTTP API") - class EmailVerifiedApi { + class RegisterConfirmApi { @ParameterizedTest @NullAndEmptySource - @DisplayName("비어있거나 null인 토큰과 이메일 정보로 검증 요청을 보낸 경우 400 HTTP Code를 리턴한다.") + @DisplayName("HTTP 파라메타가 비었거나 null인 검증요청을 보낸 경우 400 HTTP Code 리턴한다.") void http_param_is_empty_or_null(String args) throws Exception { - // Arrange // Act - when(accountFacade.verify(args, args)).thenReturn(false); + var request = new ConfirmRegisterAccountRequest(); + request.setToken(args); + request.setEmail(args); + doThrow(IllegalStateException.class).when(accountFacade).registerConfirm(request); final var actions = mockMvc.perform(get("/api/accounts/authorize") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) - .param("token", "emailTOken") - .param("email", "jiwonDev@gmail.com") + .param("token", args) + .param("email", args) ); // Assert @@ -59,17 +61,19 @@ class AccountCommandApiTests { } @Test - @DisplayName("유효하지 않은 토큰과 이메일 정보로 검증 요청을 보낸 경우 400 HTTP Code를 리턴한다.") + @DisplayName("HTTP 파라메타가 유효하지 않은 값으로 검증요청을 보낸 경우 400 HTTP Code 리턴한다.") void http_param_is_not_valid() throws Exception { - // Arrange // Act - when(accountFacade.verify(anyString(), anyString())).thenReturn(false); + var request = new ConfirmRegisterAccountRequest(); + request.setToken("QWEIUHQWDU"); + request.setEmail("QWEIOWQJE@naver.com"); + doThrow(IllegalStateException.class).when(accountFacade).registerConfirm(request); final var actions = mockMvc.perform(get("/api/accounts/authorize") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) - .param("token", "emailTOken") - .param("email", "jiwonDev@gmail.com") + .param("token", "QWEIUHQWDU") + .param("email", "QWEIOWQJE@naver.com") ); // Assert @@ -79,11 +83,12 @@ class AccountCommandApiTests { } @Test - @DisplayName("토큰과 이메일 정보로 검증 요청을 보낸 경우 303 HTTP Code를 리턴한다.") + @DisplayName("토큰과 이메일 정보로 검증요청을 보낸 경우 303 HTTP Code 리턴한다.") void valid_success() throws Exception { - // Arrange // Act - when(accountFacade.verify(anyString(), anyString())).thenReturn(true); + var request = new ConfirmRegisterAccountRequest(); + request.setToken("emailTOken"); + request.setEmail("jiwonDev@gmail.com"); final var actions = mockMvc.perform(get("/api/accounts/authorize") .accept(MediaType.APPLICATION_JSON)