diff --git a/src/main/java/com/backend/server/api/common/dto/ApiResponse.java b/src/main/java/com/backend/server/api/common/dto/ApiResponse.java index 51375ee4..8cd3b0ee 100644 --- a/src/main/java/com/backend/server/api/common/dto/ApiResponse.java +++ b/src/main/java/com/backend/server/api/common/dto/ApiResponse.java @@ -2,13 +2,15 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @AllArgsConstructor +@NoArgsConstructor public class ApiResponse { - private final String status; - private final String message; - private final T data; + private String status; + private String message; + private T data; public static ApiResponse success(String message, T data) { return new ApiResponse<>("success", message, data); diff --git a/src/main/java/com/backend/server/api/common/dto/CommonResponse.java b/src/main/java/com/backend/server/api/common/dto/CommonResponse.java index e3ee3752..a1536503 100644 --- a/src/main/java/com/backend/server/api/common/dto/CommonResponse.java +++ b/src/main/java/com/backend/server/api/common/dto/CommonResponse.java @@ -2,13 +2,15 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @AllArgsConstructor +@NoArgsConstructor public class CommonResponse { - private final String status; - private final String message; - private final T data; + private String status; + private String message; + private T data; public static CommonResponse success(String message, T data) { return new CommonResponse<>("success", message, data); diff --git a/src/main/java/com/backend/server/api/user/mypage/dto/MyInfoResponse.java b/src/main/java/com/backend/server/api/user/mypage/dto/MyInfoResponse.java index f0c5d654..3bd8ee69 100644 --- a/src/main/java/com/backend/server/api/user/mypage/dto/MyInfoResponse.java +++ b/src/main/java/com/backend/server/api/user/mypage/dto/MyInfoResponse.java @@ -3,8 +3,10 @@ import com.backend.server.api.common.dto.LoginUser; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@NoArgsConstructor public class MyInfoResponse { @Schema(description = "사용자 이름", example = "홍길동") diff --git a/src/test/java/com/backend/server/api/common/auth/controller/CommonAuthControllerTest.java b/src/test/java/com/backend/server/api/common/auth/controller/CommonAuthControllerTest.java new file mode 100644 index 00000000..18475893 --- /dev/null +++ b/src/test/java/com/backend/server/api/common/auth/controller/CommonAuthControllerTest.java @@ -0,0 +1,93 @@ +package com.backend.server.api.common.auth.controller; + +import static com.backend.server.fixture.UserFixture.MOCK_MVC_테스트시_로그인_계정; +import static com.backend.server.util.MockMvcUtil.convertToJson; +import static com.backend.server.util.MockMvcUtil.toDto; +import static com.backend.server.util.MockMvcUtil.toJsonPathDocument; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.backend.server.api.common.auth.dto.CommonSignInRequest; +import com.backend.server.api.common.dto.ApiResponse; +import com.backend.server.api.common.dto.LoginUser; +import com.backend.server.api.user.mypage.dto.MyInfoResponse; +import com.backend.server.config.MockMvcConfig; +import com.backend.server.model.entity.User; +import com.backend.server.model.repository.user.UserRepository; +import com.fasterxml.jackson.core.type.TypeReference; +import jakarta.servlet.http.Cookie; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@Import(MockMvcConfig.class) +@DisplayName("CommonAuthController") +class CommonAuthControllerTest { + + @Autowired private MockMvc mockMvc; + @Autowired private PasswordEncoder passwordEncoder; + @Autowired private UserRepository userRepository; + + @Nested + class 로그인_API_는 { + + @Test + void 사용가능한_엑세스_토큰을_응답한다() throws Exception { + /* 1. 로그인 */ + //given + final User user = userRepository.save(MOCK_MVC_테스트시_로그인_계정.엔티티_생성(passwordEncoder, null)); + userRepository.flush(); + final CommonSignInRequest signInRequest = MOCK_MVC_테스트시_로그인_계정.로그인_요청_생성(); + + //when + final ResultActions resultActions = mockMvc.perform(post("/api/auth/sign-in") + .contentType(MediaType.APPLICATION_JSON) + .content(convertToJson(signInRequest))); + + //then + resultActions.andExpect(status().isOk()); + + final Cookie refreshToken = resultActions.andReturn().getResponse().getCookie("refreshToken"); + if (refreshToken == null) + throw new Exception("No refresh token found"); + System.out.printf("refresh token : %s\n", refreshToken.getValue()); + + final String accessToken = toJsonPathDocument(resultActions).read("$.data.accessToken"); + System.out.printf("access token : %s\n", accessToken); + /* 1. 로그인 */ + + /* 2. 발급 받은 엑세스 토큰으로 내 정보 조회하기 */ + //given + final MyInfoResponse expectedMyInfoResponse = new MyInfoResponse(new LoginUser(user)); + + //when + final ResultActions resultActions2 = mockMvc.perform(get("/api/mypage") + .header("Referer", "http://localhost:3000") + .header("Authorization", "Bearer " + accessToken)); + + //then + resultActions2.andExpect(status().isOk()); + + final ApiResponse myInfoResponse = toDto(resultActions2, new TypeReference<>() {}); + assertThat(myInfoResponse.getData()) + .usingRecursiveComparison() + .isEqualTo(expectedMyInfoResponse) + .as("내 정보 조회 api 가 예상과 다른 값을 반환합니다."); + /* 2. 발급 받은 엑세스 토큰으로 내 정보 조회하기 */ + } + } +} \ No newline at end of file diff --git a/src/test/java/com/backend/server/api/common/s3/controller/CommonPresignedUrlControllerTest.java b/src/test/java/com/backend/server/api/common/s3/controller/CommonPresignedUrlControllerTest.java index 6d72aa02..bd0861ce 100644 --- a/src/test/java/com/backend/server/api/common/s3/controller/CommonPresignedUrlControllerTest.java +++ b/src/test/java/com/backend/server/api/common/s3/controller/CommonPresignedUrlControllerTest.java @@ -1,12 +1,12 @@ package com.backend.server.api.common.s3.controller; +import static com.backend.server.util.MockMvcUtil.toJsonPathDocument; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.backend.server.config.ControllerTest; import com.backend.server.util.S3ApiUtil; -import com.jayway.jsonpath.JsonPath; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -44,8 +44,7 @@ class Presigned_URL_API_는 { //then result.andExpect(status().isOk()); - String responseJson = result.andReturn().getResponse().getContentAsString(); - String presignedUrl = JsonPath.parse(responseJson).read("$.data", String.class); + String presignedUrl = toJsonPathDocument(result).read("$.data", String.class); /* presigned url 을 발급받습니다. */ /* presigned url 로 파일이 잘 업로드 되는지 테스트 */ diff --git a/src/test/java/com/backend/server/config/ControllerTest.java b/src/test/java/com/backend/server/config/ControllerTest.java index a009ebbc..d2c2746b 100644 --- a/src/test/java/com/backend/server/config/ControllerTest.java +++ b/src/test/java/com/backend/server/config/ControllerTest.java @@ -12,6 +12,11 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestExecutionListeners; +/** + * 로그인을 자동으로 해주어, 컨트롤러 레이어의 테스트를 쉽게 할수 있게 해주는 어노테이션입니다.
+ * dev 서버용 인증필터를 사용합니다.
+ * (엑세스 토큰이 없으면, 알아서 액세스 토큰을 만들어서 인증시켜줌) + */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Inherited @@ -19,7 +24,7 @@ @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("dev") -@Import(MockMvcUTF8Config.class) +@Import(MockMvcConfig.class) @TestExecutionListeners( value = UserSaveListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) diff --git a/src/test/java/com/backend/server/config/MockMvcUTF8Config.java b/src/test/java/com/backend/server/config/MockMvcConfig.java similarity index 80% rename from src/test/java/com/backend/server/config/MockMvcUTF8Config.java rename to src/test/java/com/backend/server/config/MockMvcConfig.java index 0291124b..79293440 100644 --- a/src/test/java/com/backend/server/config/MockMvcUTF8Config.java +++ b/src/test/java/com/backend/server/config/MockMvcConfig.java @@ -1,5 +1,7 @@ package com.backend.server.config; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; + import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -9,12 +11,13 @@ import org.springframework.web.filter.CharacterEncodingFilter; @TestConfiguration -public class MockMvcUTF8Config { +public class MockMvcConfig { @Bean @Primary public MockMvc customMockMvc(WebApplicationContext context) { return MockMvcBuilders.webAppContextSetup(context) .addFilter(new CharacterEncodingFilter("UTF-8", true)) + .apply(springSecurity()) .build(); } } diff --git a/src/test/java/com/backend/server/fixture/UserFixture.java b/src/test/java/com/backend/server/fixture/UserFixture.java index 29e703f1..1a931968 100644 --- a/src/test/java/com/backend/server/fixture/UserFixture.java +++ b/src/test/java/com/backend/server/fixture/UserFixture.java @@ -1,6 +1,7 @@ package com.backend.server.fixture; import com.backend.server.api.admin.user.dto.AdminUserRequest; +import com.backend.server.api.common.auth.dto.CommonSignInRequest; import com.backend.server.model.entity.Professor; import com.backend.server.model.entity.User; import com.backend.server.model.entity.enums.Gender; @@ -130,4 +131,11 @@ public enum UserFixture { .birthday(birthDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))) .build(); } + + public CommonSignInRequest 로그인_요청_생성() { + final CommonSignInRequest commonSignInRequest = new CommonSignInRequest(); + commonSignInRequest.setPassword(password); + commonSignInRequest.setStudentNumber(studentNumber); + return commonSignInRequest; + } } diff --git a/src/test/java/com/backend/server/util/MockMvcUtil.java b/src/test/java/com/backend/server/util/MockMvcUtil.java index 6dbc83c7..96adc633 100644 --- a/src/test/java/com/backend/server/util/MockMvcUtil.java +++ b/src/test/java/com/backend/server/util/MockMvcUtil.java @@ -6,12 +6,16 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; +import java.io.UnsupportedEncodingException; import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.util.LinkedMultiValueMap; @@ -24,8 +28,8 @@ public class MockMvcUtil { .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); /** - * 하나의 API 응답에 대해 여러번 이 함수를 호출할 일이 있다면, JsonPath 라이브러리로 대체하는걸 추천합니다. - * 이 메소드는 API 응답을 json 으로 파싱하므로, 한번의 API 응답에 대해 여러번 호출하기 부적절합니다. + * 이 메소드는 API 응답을 json 으로 파싱하므로, 한번의 API 응답에 대해 여러번 호출하기 부적절합니다.
+ * 여러번 호출할거라면, toJsonPathDocument() / toDto 를 사용하세요. */ public static ResultMatcher jsonPathEquals(String jsonPath, Object expected) { return result -> { @@ -66,6 +70,16 @@ public static MultiValueMap convertToParams(Object dto) { return params; } + public static DocumentContext toJsonPathDocument(ResultActions result) throws UnsupportedEncodingException { + String responseBody = result.andReturn().getResponse().getContentAsString(); + return JsonPath.parse(responseBody); + } + + public static T toDto(ResultActions result, TypeReference typeReference) throws Exception { + String responseBody = result.andReturn().getResponse().getContentAsString(); + return mapper.readValue(responseBody, typeReference); + } + public static MockHttpServletRequestBuilder postJson(String url, Object body) throws JsonProcessingException { return post(url) .contentType(MediaType.APPLICATION_JSON)