Add Update Account HTTP API

- Add Command API test
- Refactor Interceptor, SessionManager, ArgumentResolver
This commit is contained in:
Rebwon
2021-09-30 12:35:43 +09:00
committed by MaengSol
parent f91b823bac
commit fd5a1b53bc
8 changed files with 124 additions and 15 deletions

View File

@@ -11,6 +11,8 @@ import com.yam.app.account.presentation.AccountResponse;
import com.yam.app.account.presentation.ConfirmRegisterAccountCommand;
import com.yam.app.account.presentation.LoginAccountCommand;
import com.yam.app.account.presentation.RegisterAccountCommand;
import com.yam.app.account.presentation.UpdateAccountCommand;
import com.yam.app.common.Authentication;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -61,4 +63,9 @@ public class AccountFacade {
return translator.toResponse(accountReader.findByEmail(email)
.orElseThrow(() -> new AccountNotFoundException(email)));
}
@Transactional
public void update(UpdateAccountCommand command, Authentication authentication) {
}
}

View File

@@ -1,6 +1,7 @@
package com.yam.app.account.infrastructure;
import com.yam.app.common.AuthenticationPrincipal;
import com.yam.app.common.UnauthorizedRequestException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.core.MethodParameter;
@@ -24,6 +25,7 @@ public final class AuthenticationPrincipalArgumentResolver
.getSession(false);
var sessionManager = new SessionManager(session);
return sessionManager.fetchPrincipal();
return sessionManager.fetchPrincipal()
.orElseThrow(() -> new UnauthorizedRequestException("Failed fetch principal"));
}
}

View File

@@ -16,12 +16,6 @@ public final class SessionAuthInterceptor implements HandlerInterceptor {
throw new UnauthorizedRequestException("Unauthorized request");
}
var sessionManager = new SessionManager(session);
if (!sessionManager.isExistPrincipal()) {
throw new UnauthorizedRequestException("Failed fetch principal");
}
return true;
}
}

View File

@@ -1,5 +1,6 @@
package com.yam.app.account.infrastructure;
import java.util.Optional;
import javax.servlet.http.HttpSession;
public final class SessionManager {
@@ -16,12 +17,8 @@ public final class SessionManager {
this.httpSession.setAttribute(LOGIN_ACCOUNT, principal);
}
public AccountPrincipal fetchPrincipal() {
return (AccountPrincipal) httpSession.getAttribute(LOGIN_ACCOUNT);
}
public boolean isExistPrincipal() {
return httpSession.getAttribute(LOGIN_ACCOUNT) != null;
public Optional<AccountPrincipal> fetchPrincipal() {
return Optional.ofNullable((AccountPrincipal) httpSession.getAttribute(LOGIN_ACCOUNT));
}
public void removePrincipal() {

View File

@@ -2,6 +2,8 @@ package com.yam.app.account.presentation;
import com.yam.app.account.application.AccountFacade;
import com.yam.app.account.infrastructure.SessionManager;
import com.yam.app.common.Authentication;
import com.yam.app.common.AuthenticationPrincipal;
import java.net.URI;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
@@ -11,6 +13,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -65,4 +68,12 @@ public final class AccountCommandApi {
session.removePrincipal();
return ResponseEntity.ok().build();
}
@PatchMapping("/api/accounts/update")
public ResponseEntity<Void> update(
@RequestBody @Valid UpdateAccountCommand command,
@AuthenticationPrincipal Authentication authentication) {
accountFacade.update(command, authentication);
return ResponseEntity.ok().build();
}
}

View File

@@ -0,0 +1,21 @@
package com.yam.app.account.presentation;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Data;
@Data
public final class UpdateAccountCommand {
@NotBlank
@Pattern(regexp = "^[A-Za-z1-9~!@#$%^&*()+|=]{8,12}$",
message = "Please enter the password in English, numbers, "
+ "and special characters within 8-12 digits.")
private String password;
@NotBlank
private String nickname;
@NotBlank
private String image;
}

View File

@@ -7,8 +7,7 @@ public final class AccountApiUri {
public static final String LOGIN = "/api/accounts/login";
public static final String FIND_INFO = "/api/accounts/me";
public static final String LOGOUT = "/api/accounts/logout";
public static final String UNAUTHORIZED_REQUEST = "/api/accounts/error/UnauthorizedRequest";
public static final String UPDATE = "/api/accounts/update";
private AccountApiUri() {
}

View File

@@ -4,8 +4,10 @@ import static com.yam.app.account.presentation.AccountApiUri.EMAIL_CONFIRM;
import static com.yam.app.account.presentation.AccountApiUri.LOGIN;
import static com.yam.app.account.presentation.AccountApiUri.LOGOUT;
import static com.yam.app.account.presentation.AccountApiUri.REGISTER;
import static com.yam.app.account.presentation.AccountApiUri.UPDATE;
import static org.mockito.Mockito.doThrow;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@@ -24,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
@@ -46,6 +49,81 @@ final class AccountCommandApiTests {
.andExpect(jsonPath("$.message").value("Invalid argument"));
}
@Nested
@DisplayName("회원 정보 수정 HTTP API")
class UpdateAccountApi {
@Test
@DisplayName("인증되지 않은 사용자가 정보 변경을 요청하면 401 에러를 반환한다.")
void not_authentication_update_command() throws Exception {
var command = new UpdateAccountCommand();
command.setImage("temp.png");
command.setNickname("jiwon");
command.setPassword("password!2");
//Act
final var actions = mockMvc.perform(patch(UPDATE)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(command))
);
//Assert
actions
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.success").value(false))
.andExpect(jsonPath("$.data").doesNotExist())
.andExpect(jsonPath("$.message").value("Unauthorized request"));
}
@ParameterizedTest
@ValueSource(strings = {"1", "a", "1a234567890123456"})
@DisplayName("인증된 사용자의 요청 Body 비밀번호 형식이 맞지 않은 경우 400 에러를 반환한다.")
void http_json_password_is_invalid(String args) throws Exception {
// Arrange
var session = new MockHttpSession();
var command = new UpdateAccountCommand();
command.setImage("temp.png");
command.setNickname("jiwon");
command.setPassword(args);
// Act
final var actions = mockMvc.perform(patch(UPDATE)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(command))
.session(session)
);
// Assert
assertThatInvalidArgumentError(actions);
}
@ParameterizedTest
@NullAndEmptySource
@DisplayName("인증된 사용자의 요청 Body 정보가 null 혹은 empty인 경우 400 에러를 반환한다.")
void http_json_value_is_empty_or_null(String args) throws Exception {
//Arrange
var session = new MockHttpSession();
var command = new UpdateAccountCommand();
command.setNickname(args);
command.setPassword(args);
command.setImage(args);
//Act
final var actions = mockMvc.perform(patch(UPDATE)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(command))
.session(session)
);
//Assert
assertThatInvalidArgumentError(actions);
}
}
@Nested
@DisplayName("로그인 HTTP API")
class LoginApi {