Skip to content

Commit ba70cbb

Browse files
committed
Test: User API 테스트 보완 및 관련 코드 수정
1 parent d8df449 commit ba70cbb

File tree

4 files changed

+141
-105
lines changed

4 files changed

+141
-105
lines changed

src/main/java/com/back/global/security/JwtAuthenticationEntryPoint.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.back.global.security;
22

33
import com.back.global.common.dto.RsData;
4+
import com.back.global.exception.CustomException;
45
import com.back.global.exception.ErrorCode;
56
import com.fasterxml.jackson.databind.ObjectMapper;
67
import jakarta.servlet.http.HttpServletRequest;
@@ -22,17 +23,24 @@ public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
2223
private final ObjectMapper objectMapper = new ObjectMapper();
2324

2425
@Override
25-
public void commence(
26-
HttpServletRequest request,
27-
HttpServletResponse response,
28-
AuthenticationException authException
29-
) throws IOException {
26+
public void commence(HttpServletRequest request,
27+
HttpServletResponse response,
28+
AuthenticationException authException) throws IOException {
3029

3130
response.setContentType("application/json;charset=UTF-8");
32-
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
3331

34-
RsData<Void> body = RsData.fail(ErrorCode.UNAUTHORIZED);
32+
// request attribute에서 ErrorCode 꺼내기
33+
ErrorCode errorCode = (ErrorCode) request.getAttribute("errorCode");
34+
35+
// ErrorCode가 없으면 UNAUTHORIZED로 기본 설정
36+
if (errorCode == null) {
37+
errorCode = ErrorCode.UNAUTHORIZED;
38+
}
39+
40+
response.setStatus(errorCode.getStatus().value());
41+
RsData<Void> body = RsData.fail(errorCode);
3542

3643
response.getWriter().write(objectMapper.writeValueAsString(body));
3744
}
45+
3846
}

src/main/java/com/back/global/security/JwtAuthenticationFilter.java

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.back.global.security;
22

3+
import com.back.global.exception.CustomException;
34
import jakarta.servlet.FilterChain;
45
import jakarta.servlet.ServletException;
56
import jakarta.servlet.http.HttpServletRequest;
67
import jakarta.servlet.http.HttpServletResponse;
78
import lombok.RequiredArgsConstructor;
9+
import org.springframework.security.authentication.InsufficientAuthenticationException;
810
import org.springframework.security.core.Authentication;
911
import org.springframework.security.core.context.SecurityContextHolder;
1012
import org.springframework.stereotype.Component;
@@ -21,31 +23,44 @@
2123
@RequiredArgsConstructor
2224
public class JwtAuthenticationFilter extends OncePerRequestFilter {
2325
private final JwtTokenProvider jwtTokenProvider;
26+
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
2427

2528
@Override
2629
protected void doFilterInternal(
2730
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain
2831
) throws ServletException, IOException {
2932

30-
// Request Header에서 토큰 추출
31-
String token = resolveToken(request);
33+
try {
34+
// Request Header에서 토큰 추출
35+
String token = resolveToken(request);
3236

33-
// 토큰이 유효한 경우에만 Authentication 객체 생성 및 SecurityContext에 저장
34-
if (token != null) {
35-
jwtTokenProvider.validateAccessToken(token);
36-
Authentication authentication = jwtTokenProvider.getAuthentication(token);
37-
SecurityContextHolder.getContext().setAuthentication(authentication);
37+
// 토큰이 유효한 경우에만 Authentication 객체 생성 및 SecurityContext에 저장
38+
if (token != null) {
39+
jwtTokenProvider.validateAccessToken(token);
40+
Authentication authentication = jwtTokenProvider.getAuthentication(token);
41+
SecurityContextHolder.getContext().setAuthentication(authentication);
42+
}
43+
44+
// 다음 필터로 요청 전달
45+
filterChain.doFilter(request, response);
46+
} catch (CustomException ex) {
47+
// SecurityContext 초기화
48+
SecurityContextHolder.clearContext();
49+
50+
// request attribute에 ErrorCode 저장
51+
request.setAttribute("errorCode", ex.getErrorCode());
52+
53+
// 인증 실패 처리
54+
jwtAuthenticationEntryPoint.commence(
55+
request, response,
56+
new InsufficientAuthenticationException("Custom auth error")
57+
);
3858
}
3959

40-
// 다음 필터로 요청 전달
41-
filterChain.doFilter(request, response);
4260
}
4361

4462
/**
4563
* Request의 Authorization 헤더에서 JWT 토큰을 추출
46-
*
47-
* @param request HTTP 요청 객체
48-
* @return JWT 토큰 문자열 또는 null
4964
*/
5065
private String resolveToken(HttpServletRequest request) {
5166
String bearerToken = request.getHeader("Authorization");

src/test/java/com/back/domain/user/controller/UserControllerTest.java

Lines changed: 97 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private String generateAccessToken(User user) {
5151
return testJwtTokenProvider.createAccessToken(user.getId(), user.getUsername(), user.getRole().name());
5252
}
5353

54-
// ---------------------- getMyInfo ----------------------
54+
// ====================== 내 정보 조회 테스트 ======================
5555

5656
@Test
5757
@DisplayName("내 정보 조회 성공 → 200 OK")
@@ -89,11 +89,12 @@ void getMyInfo_deletedUser() throws Exception {
8989

9090
String accessToken = generateAccessToken(user);
9191

92-
// when & then
92+
// when & then: 410 Gone + USER_009
9393
mvc.perform(get("/api/users/me").header("Authorization", "Bearer " + accessToken))
9494
.andDo(print())
9595
.andExpect(status().isGone())
96-
.andExpect(jsonPath("$.code").value("USER_009"));
96+
.andExpect(jsonPath("$.code").value("USER_009"))
97+
.andExpect(jsonPath("$.message").value("탈퇴한 계정입니다."));
9798
}
9899

99100
@Test
@@ -107,53 +108,56 @@ void getMyInfo_suspendedUser() throws Exception {
107108

108109
String accessToken = generateAccessToken(user);
109110

110-
// when & then
111+
// when & then: 403 Forbidden + USER_008
111112
mvc.perform(get("/api/users/me").header("Authorization", "Bearer " + accessToken))
112113
.andDo(print())
113114
.andExpect(status().isForbidden())
114-
.andExpect(jsonPath("$.code").value("USER_008"));
115+
.andExpect(jsonPath("$.code").value("USER_008"))
116+
.andExpect(jsonPath("$.message").value("정지된 계정입니다. 관리자에게 문의하세요."));
115117
}
116118

117119
@Test
118120
@DisplayName("AccessToken 없음 → 401 Unauthorized")
119121
void getMyInfo_noAccessToken() throws Exception {
120-
// when & then
122+
// when & then: 401 Unauthorized + AUTH_001
121123
mvc.perform(get("/api/users/me"))
122124
.andDo(print())
123125
.andExpect(status().isUnauthorized())
124-
.andExpect(jsonPath("$.code").value("AUTH_401"));
126+
.andExpect(jsonPath("$.code").value("AUTH_001"))
127+
.andExpect(jsonPath("$.message").value("인증이 필요합니다."));
125128
}
126129

127-
// TODO: 인증 에러 처리 로직 수정 후 테스트 케이스 활성화
128-
// @Test
129-
// @DisplayName("잘못된 AccessToken → 401 Unauthorized")
130-
// void getMyInfo_invalidAccessToken() throws Exception {
131-
// // when & then
132-
// mvc.perform(get("/api/users/me").header("Authorization", "Bearer invalidToken"))
133-
// .andDo(print())
134-
// .andExpect(status().isUnauthorized())
135-
// .andExpect(jsonPath("$.code").value("AUTH_401"));
136-
// }
137-
//
138-
// @Test
139-
// @DisplayName("만료된 AccessToken → 401 Unauthorized")
140-
// void getMyInfo_expiredAccessToken() throws Exception {
141-
// // given: 만료된 토큰 발급
142-
// User user = User.createUser("expired", "[email protected]", passwordEncoder.encode("P@ssw0rd!"));
143-
// user.setUserProfile(new UserProfile(user, "닉네임", null, null, null, 0));
144-
// user.setUserStatus(UserStatus.ACTIVE);
145-
// userRepository.save(user);
146-
//
147-
// String expiredToken = testJwtTokenProvider.createExpiredAccessToken(user.getId(), user.getUsername(), user.getRole().name());
148-
//
149-
// // when & then
150-
// mvc.perform(get("/api/users/me").header("Authorization", "Bearer " + expiredToken))
151-
// .andDo(print())
152-
// .andExpect(status().isUnauthorized())
153-
// .andExpect(jsonPath("$.code").value("AUTH_401"));
154-
// }
155-
156-
// ---------------------- updateMyProfile ----------------------
130+
@Test
131+
@DisplayName("잘못된 AccessToken → 401 Unauthorized")
132+
void getMyInfo_invalidAccessToken() throws Exception {
133+
// when & then: 401 Unauthorized + AUTH_002
134+
mvc.perform(get("/api/users/me").header("Authorization", "Bearer invalidToken"))
135+
.andDo(print())
136+
.andExpect(status().isUnauthorized())
137+
.andExpect(jsonPath("$.code").value("AUTH_002"))
138+
.andExpect(jsonPath("$.message").value("유효하지 않은 액세스 토큰입니다."));
139+
}
140+
141+
@Test
142+
@DisplayName("만료된 AccessToken → 401 Unauthorized")
143+
void getMyInfo_expiredAccessToken() throws Exception {
144+
// given: 만료된 토큰 발급
145+
User user = User.createUser("expired", "[email protected]", passwordEncoder.encode("P@ssw0rd!"));
146+
user.setUserProfile(new UserProfile(user, "닉네임", null, null, null, 0));
147+
user.setUserStatus(UserStatus.ACTIVE);
148+
userRepository.save(user);
149+
150+
String expiredToken = testJwtTokenProvider.createExpiredAccessToken(user.getId(), user.getUsername(), user.getRole().name());
151+
152+
// when & then: 401 Unauthorized + AUTH_004
153+
mvc.perform(get("/api/users/me").header("Authorization", "Bearer " + expiredToken))
154+
.andDo(print())
155+
.andExpect(status().isUnauthorized())
156+
.andExpect(jsonPath("$.code").value("AUTH_004"))
157+
.andExpect(jsonPath("$.message").value("만료된 액세스 토큰입니다."));
158+
}
159+
160+
// ====================== 내 프로필 수정 테스트 ======================
157161

158162
@Test
159163
@DisplayName("내 프로필 수정 성공 → 200 OK")
@@ -173,17 +177,19 @@ void updateMyProfile_success() throws Exception {
173177
LocalDate.of(2000, 5, 10)
174178
);
175179

176-
// when
180+
// when: 정상 프로필 수정 요청
177181
ResultActions resultActions = mvc.perform(
178182
patch("/api/users/me")
179183
.header("Authorization", "Bearer " + accessToken)
180184
.contentType(MediaType.APPLICATION_JSON)
181185
.content(objectMapper.writeValueAsString(request))
182186
).andDo(print());
183187

184-
// then
188+
// then: 200 OK + 변경된 값 검증
185189
resultActions
186190
.andExpect(status().isOk())
191+
.andExpect(jsonPath("$.success").value(true))
192+
.andExpect(jsonPath("$.message").value("회원 정보를 수정했습니다."))
187193
.andExpect(jsonPath("$.data.profile.nickname").value("새닉네임"))
188194
.andExpect(jsonPath("$.data.profile.bio").value("저는 개발자입니다!"))
189195
.andExpect(jsonPath("$.data.profile.birthDate").value("2000-05-10"));
@@ -192,7 +198,7 @@ void updateMyProfile_success() throws Exception {
192198
@Test
193199
@DisplayName("중복 닉네임 수정 → 409 Conflict")
194200
void updateMyProfile_duplicateNickname() throws Exception {
195-
// given: user1, user2 저장
201+
// given: user1, user2 저장 (닉네임 중복 상황)
196202
User user1 = User.createUser("user1", "[email protected]", passwordEncoder.encode("P@ssw0rd!"));
197203
user1.setUserProfile(new UserProfile(user1, "닉1", null, null, null, 0));
198204
user1.setUserStatus(UserStatus.ACTIVE);
@@ -214,7 +220,8 @@ void updateMyProfile_duplicateNickname() throws Exception {
214220
.content(objectMapper.writeValueAsString(request)))
215221
.andDo(print())
216222
.andExpect(status().isConflict())
217-
.andExpect(jsonPath("$.code").value("USER_004"));
223+
.andExpect(jsonPath("$.code").value("USER_004"))
224+
.andExpect(jsonPath("$.message").value("이미 사용 중인 닉네임입니다."));
218225
}
219226

220227
@Test
@@ -237,7 +244,8 @@ void updateMyProfile_deletedUser() throws Exception {
237244
.content(objectMapper.writeValueAsString(request)))
238245
.andDo(print())
239246
.andExpect(status().isGone())
240-
.andExpect(jsonPath("$.code").value("USER_009"));
247+
.andExpect(jsonPath("$.code").value("USER_009"))
248+
.andExpect(jsonPath("$.message").value("탈퇴한 계정입니다."));
241249
}
242250

243251
@Test
@@ -260,61 +268,66 @@ void updateMyProfile_suspendedUser() throws Exception {
260268
.content(objectMapper.writeValueAsString(request)))
261269
.andDo(print())
262270
.andExpect(status().isForbidden())
263-
.andExpect(jsonPath("$.code").value("USER_008"));
271+
.andExpect(jsonPath("$.code").value("USER_008"))
272+
.andExpect(jsonPath("$.message").value("정지된 계정입니다. 관리자에게 문의하세요."));
264273
}
265274

266275
@Test
267-
@DisplayName("AccessToken 없음으로 프로필 수정 → 401 Unauthorized")
276+
@DisplayName("AccessToken 없음으로 프로필 수정 → 401 Unauthorized (AUTH_001)")
268277
void updateMyProfile_noAccessToken() throws Exception {
278+
// given: 요청 바디 준비
279+
UpdateUserProfileRequest request = new UpdateUserProfileRequest("새닉", null, null, null);
280+
281+
// when & then
282+
mvc.perform(patch("/api/users/me")
283+
.contentType(MediaType.APPLICATION_JSON)
284+
.content(objectMapper.writeValueAsString(request)))
285+
.andDo(print())
286+
.andExpect(status().isUnauthorized())
287+
.andExpect(jsonPath("$.code").value("AUTH_001"))
288+
.andExpect(jsonPath("$.message").value("인증이 필요합니다."));
289+
}
290+
291+
@Test
292+
@DisplayName("잘못된 AccessToken으로 프로필 수정 → 401 Unauthorized (AUTH_002)")
293+
void updateMyProfile_invalidAccessToken() throws Exception {
269294
// given
270295
UpdateUserProfileRequest request = new UpdateUserProfileRequest("새닉", null, null, null);
271296

272297
// when & then
273298
mvc.perform(patch("/api/users/me")
299+
.header("Authorization", "Bearer invalidToken")
274300
.contentType(MediaType.APPLICATION_JSON)
275301
.content(objectMapper.writeValueAsString(request)))
276302
.andDo(print())
277303
.andExpect(status().isUnauthorized())
278-
.andExpect(jsonPath("$.code").value("AUTH_401"));
304+
.andExpect(jsonPath("$.code").value("AUTH_002"))
305+
.andExpect(jsonPath("$.message").value("유효하지 않은 액세스 토큰입니다."));
279306
}
280307

281-
// TODO: 인증 에러 처리 로직 수정 후 테스트 케이스 활성화
282-
// @Test
283-
// @DisplayName("잘못된 AccessToken으로 프로필 수정 → 401 Unauthorized")
284-
// void updateMyProfile_invalidAccessToken() throws Exception {
285-
// // given
286-
// UpdateUserProfileRequest request = new UpdateUserProfileRequest("새닉", null, null, null);
287-
//
288-
// // when & then
289-
// mvc.perform(patch("/api/users/me")
290-
// .header("Authorization", "Bearer invalidToken")
291-
// .contentType(MediaType.APPLICATION_JSON)
292-
// .content(objectMapper.writeValueAsString(request)))
293-
// .andDo(print())
294-
// .andExpect(status().isUnauthorized())
295-
// .andExpect(jsonPath("$.code").value("AUTH_401"));
296-
// }
297-
//
298-
// @Test
299-
// @DisplayName("만료된 AccessToken으로 프로필 수정 → 401 Unauthorized")
300-
// void updateMyProfile_expiredAccessToken() throws Exception {
301-
// // given: 만료된 토큰 발급
302-
// User user = User.createUser("expired2", "[email protected]", passwordEncoder.encode("P@ssw0rd!"));
303-
// user.setUserProfile(new UserProfile(user, "닉네임", null, null, null, 0));
304-
// user.setUserStatus(UserStatus.ACTIVE);
305-
// userRepository.save(user);
306-
//
307-
// String expiredToken = testJwtTokenProvider.createExpiredAccessToken(user.getId(), user.getUsername(), user.getRole().name());
308-
//
309-
// UpdateUserProfileRequest request = new UpdateUserProfileRequest("새닉", null, null, null);
310-
//
311-
// // when & then
312-
// mvc.perform(patch("/api/users/me")
313-
// .header("Authorization", "Bearer " + expiredToken)
314-
// .contentType(MediaType.APPLICATION_JSON)
315-
// .content(objectMapper.writeValueAsString(request)))
316-
// .andDo(print())
317-
// .andExpect(status().isUnauthorized())
318-
// .andExpect(jsonPath("$.code").value("AUTH_401"));
319-
// }
308+
@Test
309+
@DisplayName("만료된 AccessToken으로 프로필 수정 → 401 Unauthorized (AUTH_004)")
310+
void updateMyProfile_expiredAccessToken() throws Exception {
311+
// given: 만료된 토큰 발급
312+
User user = User.createUser("expired2", "[email protected]", passwordEncoder.encode("P@ssw0rd!"));
313+
user.setUserProfile(new UserProfile(user, "닉네임", null, null, null, 0));
314+
user.setUserStatus(UserStatus.ACTIVE);
315+
userRepository.save(user);
316+
317+
String expiredToken = testJwtTokenProvider.createExpiredAccessToken(
318+
user.getId(), user.getUsername(), user.getRole().name()
319+
);
320+
321+
UpdateUserProfileRequest request = new UpdateUserProfileRequest("새닉", null, null, null);
322+
323+
// when & then
324+
mvc.perform(patch("/api/users/me")
325+
.header("Authorization", "Bearer " + expiredToken)
326+
.contentType(MediaType.APPLICATION_JSON)
327+
.content(objectMapper.writeValueAsString(request)))
328+
.andDo(print())
329+
.andExpect(status().isUnauthorized())
330+
.andExpect(jsonPath("$.code").value("AUTH_004"))
331+
.andExpect(jsonPath("$.message").value("만료된 액세스 토큰입니다."));
332+
}
320333
}

0 commit comments

Comments
 (0)