Skip to content

Commit 2230a3d

Browse files
authored
Merge pull request #431 from TaskFlow-CLAP/CLAP-112
CLAP-112 인증 인가 기능에 대한 테스트 코드 작성
2 parents 13cbc27 + 84a237f commit 2230a3d

File tree

12 files changed

+327
-28
lines changed

12 files changed

+327
-28
lines changed

src/main/java/clap/server/adapter/inbound/security/filter/LoginAttemptFilter.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package clap.server.adapter.inbound.security.filter;
22

3+
import clap.server.application.port.inbound.auth.CheckAccountLockStatusUseCase;
34
import clap.server.application.service.auth.LoginAttemptService;
45
import clap.server.exception.AuthException;
56
import jakarta.servlet.FilterChain;
@@ -25,7 +26,7 @@
2526
@Slf4j
2627
public class LoginAttemptFilter extends OncePerRequestFilter {
2728

28-
private final LoginAttemptService loginAttemptService;
29+
private final CheckAccountLockStatusUseCase checkAccountLockStatusUseCase;
2930

3031
@Override
3132
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
@@ -34,7 +35,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
3435
if (request.getRequestURI().equals(LOGIN_ENDPOINT)) {
3536
String clientIp = getClientIp(request);
3637

37-
loginAttemptService.checkAccountIsLocked(clientIp);
38+
checkAccountLockStatusUseCase.checkAccountIsLocked(clientIp);
3839

3940
}
4041
} catch (AuthException e) {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package clap.server.application.port.inbound.auth;
2+
3+
public interface CheckAccountLockStatusUseCase {
4+
void checkAccountIsLocked(String clientIp);
5+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package clap.server.application.port.outbound.log;
2+
3+
import clap.server.adapter.outbound.persistense.entity.log.constant.LogStatus;
4+
import jakarta.servlet.http.HttpServletRequest;
5+
6+
public interface LoggingPort {
7+
void createAnonymousLog(HttpServletRequest request, int statusCode, String customCode, LogStatus logStatus, Object responseBody, String requestBody, String nickName);
8+
void createMemberLog(HttpServletRequest request, int statusCode, String customCode,LogStatus logStatus, Object responseBody, String requestBody, Long memberId);
9+
void createLoginFailedLog(HttpServletRequest request, int statusCode, String customCode, LogStatus logStatus, String requestBody, String nickName);
10+
}

src/main/java/clap/server/application/service/auth/LoginAttemptService.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
package clap.server.application.service.auth;
22

3+
import clap.server.application.port.inbound.auth.CheckAccountLockStatusUseCase;
34
import clap.server.application.port.outbound.auth.loginLog.CommandLoginLogPort;
45
import clap.server.application.port.outbound.auth.loginLog.LoadLoginLogPort;
56
import clap.server.domain.model.auth.LoginLog;
67
import clap.server.exception.AuthException;
78
import clap.server.exception.code.AuthErrorCode;
89
import lombok.RequiredArgsConstructor;
9-
import org.springframework.stereotype.Component;
10+
import org.springframework.stereotype.Service;
1011
import org.springframework.transaction.annotation.Transactional;
1112

1213
import java.time.LocalDateTime;
1314
import java.time.temporal.ChronoUnit;
1415

1516
@RequiredArgsConstructor
16-
@Component
17+
@Service
1718
@Transactional
18-
public class LoginAttemptService {
19+
public class LoginAttemptService implements CheckAccountLockStatusUseCase {
1920
private final LoadLoginLogPort loadLoginLogPort;
2021
private final CommandLoginLogPort commandLoginLogPort;
2122
private static final int MAX_FAILED_ATTEMPTS = 5;
@@ -36,6 +37,7 @@ public void recordFailedAttempt(String clientIp, String attemptNickname) {
3637
commandLoginLogPort.save(loginLog);
3738
}
3839

40+
@Override
3941
public void checkAccountIsLocked(String clientIp) {
4042
LoginLog loginLog = loadLoginLogPort.findByClientIp(clientIp).orElse(null);
4143
if (loginLog == null) {

src/main/java/clap/server/application/port/inbound/domain/LogService.java renamed to src/main/java/clap/server/application/service/log/LogService.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
package clap.server.application.port.inbound.domain;
1+
package clap.server.application.service.log;
22

33
import clap.server.adapter.outbound.persistense.entity.log.constant.LogStatus;
4+
import clap.server.application.port.inbound.domain.MemberService;
45
import clap.server.application.port.outbound.auth.loginLog.LoadLoginLogPort;
56
import clap.server.application.port.outbound.log.CommandLogPort;
7+
import clap.server.application.port.outbound.log.LoggingPort;
68
import clap.server.common.utils.ClientIpParseUtil;
79
import clap.server.domain.model.auth.LoginLog;
810
import clap.server.domain.model.log.AnonymousLog;
911
import clap.server.domain.model.log.MemberLog;
1012
import clap.server.domain.model.member.Member;
11-
import com.fasterxml.jackson.core.JsonProcessingException;
12-
import com.fasterxml.jackson.databind.ObjectMapper;
1313
import jakarta.servlet.http.HttpServletRequest;
1414
import lombok.RequiredArgsConstructor;
1515
import org.springframework.stereotype.Service;
@@ -18,11 +18,10 @@
1818
@Service
1919
@RequiredArgsConstructor
2020
@Transactional
21-
public class LogService {
21+
public class LogService implements LoggingPort {
2222
private final CommandLogPort commandLogPort;
2323
private final MemberService memberService;
2424
private final LoadLoginLogPort loadLoginLogPort;
25-
private final ObjectMapper objectMapper;
2625

2726
public void createAnonymousLog(HttpServletRequest request, int statusCode, String customCode, LogStatus logStatus, Object responseBody, String requestBody, String nickName) {
2827
AnonymousLog anonymousLog = AnonymousLog.createAnonymousLog(request, statusCode,customCode, logStatus, responseBody, requestBody, nickName);
@@ -35,7 +34,7 @@ public void createMemberLog(HttpServletRequest request, int statusCode, String c
3534
commandLogPort.saveMemberLog(memberLog);
3635
}
3736

38-
public void createLoginFailedLog(HttpServletRequest request, int statusCode, String customCode, LogStatus logStatus, String requestBody, String nickName) throws JsonProcessingException {
37+
public void createLoginFailedLog(HttpServletRequest request, int statusCode, String customCode, LogStatus logStatus, String requestBody, String nickName) {
3938
LoginLog loginLog = loadLoginLogPort.findByClientIp(ClientIpParseUtil.getClientIp(request)).orElse(null);
4039
String responseBody = loginLog != null ? loginLog.toSummaryString() : null;
4140
AnonymousLog anonymousLog = AnonymousLog.createAnonymousLog(request, statusCode,customCode, logStatus, responseBody, requestBody, nickName);

src/main/java/clap/server/config/aop/LoggingAspect.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import clap.server.adapter.inbound.security.service.SecurityUserDetails;
44

55
import clap.server.adapter.outbound.persistense.entity.log.constant.LogStatus;
6-
import clap.server.application.port.inbound.domain.LogService;
6+
import clap.server.application.port.outbound.log.LoggingPort;
77
import clap.server.common.annotation.log.LogType;
88
import clap.server.exception.BaseException;
99
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -34,7 +34,7 @@
3434
@RequiredArgsConstructor
3535
public class LoggingAspect {
3636
private final ObjectMapper objectMapper;
37-
private final LogService logService;
37+
private final LoggingPort loggingPort;
3838

3939
@Pointcut("execution(* clap.server.adapter.inbound.web..*Controller.*(..))")
4040
public void controllerMethods() {
@@ -77,7 +77,7 @@ public Object logApiRequests(ProceedingJoinPoint joinPoint) throws Throwable {
7777
} else {
7878
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
7979
if (principal instanceof SecurityUserDetails userDetails) {
80-
logService.createMemberLog(request, statusCode, customCode, logStatus, result, getRequestBody(request), userDetails.getUserId());
80+
loggingPort.createMemberLog(request, statusCode, customCode, logStatus, result, getRequestBody(request), userDetails.getUserId());
8181
}
8282
}
8383
}
@@ -88,9 +88,9 @@ public Object logApiRequests(ProceedingJoinPoint joinPoint) throws Throwable {
8888

8989
private void handleLoginLog(int statusCode, HttpServletRequest request, String customCode, LogStatus logStatus, Object result) throws JsonProcessingException {
9090
if (statusCode == HttpStatus.SC_OK) {
91-
logService.createAnonymousLog(request, statusCode, customCode, logStatus, result, getRequestBody(request), getNicknameFromRequestBody(request));
91+
loggingPort.createAnonymousLog(request, statusCode, customCode, logStatus, result, getRequestBody(request), getNicknameFromRequestBody(request));
9292
} else {
93-
logService.createLoginFailedLog(request, statusCode, customCode, logStatus, getRequestBody(request), getNicknameFromRequestBody(request));
93+
loggingPort.createLoginFailedLog(request, statusCode, customCode, logStatus, getRequestBody(request), getNicknameFromRequestBody(request));
9494
}
9595
}
9696

src/main/resources/application.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ spring:
1717
multipart:
1818
max-file-size: 5MB
1919

20-
21-
2220
server:
2321
port: ${APPLICATION_PORT:8080}
2422
tomcat:
@@ -29,6 +27,10 @@ server:
2927
domain:
3028
local: ${TASKFLOW_LOCAL_SERVER:127.0.0.1:8080}
3129
service: ${TASKFLOW_SERVICE_SERVER:127.0.0.1:8080}
30+
servlet.session.cookie:
31+
http-only: true
32+
path: /
33+
secure: true
3234

3335
web:
3436
domain:

src/test/java/clap/server/TestDataFactory.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static Member createAdmin() {
2828
.emailNotificationEnabled(true)
2929
.imageUrl(null)
3030
.status(MemberStatus.ACTIVE)
31-
.password("1111")
31+
.password("Password123!")
3232
.department(createDepartment())
3333
.build();
3434
}
@@ -43,7 +43,7 @@ public static Member createManagerWithReviewer() {
4343
.emailNotificationEnabled(true)
4444
.imageUrl(null)
4545
.status(MemberStatus.ACTIVE)
46-
.password("1111")
46+
.password("Password456!")
4747
.department(createDepartment())
4848
.build();
4949
}
@@ -58,7 +58,7 @@ public static Member createManager() {
5858
.emailNotificationEnabled(true)
5959
.imageUrl(null)
6060
.status(MemberStatus.ACTIVE)
61-
.password("1111")
61+
.password("Password789!")
6262
.department(createDepartment())
6363
.build();
6464
}
@@ -73,7 +73,7 @@ public static Member createUser() {
7373
.emailNotificationEnabled(true)
7474
.imageUrl(null)
7575
.status(MemberStatus.ACTIVE)
76-
.password("1111")
76+
.password("Password000!")
7777
.department(createDepartment())
7878
.build();
7979
}
@@ -90,6 +90,33 @@ public static MemberInfo createAdminInfo() {
9090
.build();
9191
}
9292

93+
public static Member createNotApprovedUser() {
94+
return Member.builder()
95+
.memberId(4L)
96+
.memberInfo(createNotApprovedUserInfo())
97+
.admin(createAdmin())
98+
.kakaoworkNotificationEnabled(true)
99+
.agitNotificationEnabled(true)
100+
.emailNotificationEnabled(true)
101+
.imageUrl(null)
102+
.status(MemberStatus.APPROVAL_REQUEST)
103+
.password("Password000!")
104+
.department(createDepartment())
105+
.build();
106+
}
107+
108+
public static MemberInfo createNotApprovedUserInfo() {
109+
return MemberInfo.builder()
110+
.name("홍길동(등록 대기중인 사용자)")
111+
.email("atom8426@naver.com")
112+
.nickname("atom.user")
113+
.isReviewer(false)
114+
.department(null)
115+
.role(MemberRole.ROLE_USER)
116+
.departmentRole("인프라")
117+
.build();
118+
}
119+
93120
public static MemberInfo createManagerWithReviewerInfo() {
94121
return MemberInfo.builder()
95122
.name("홍길동(리뷰어)")

src/test/java/clap/server/application/service/admin/RegisterMemberCsvServiceTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ class RegisterMemberCSVServiceTest {
4747
@BeforeEach
4848
void setUp() {
4949
MockitoAnnotations.openMocks(this);
50-
// 예시로 Department가 빌더를 제공한다고 가정
5150
dummyDepartment = Department.builder().departmentId(100L).build();
5251
}
5352

@@ -142,7 +141,6 @@ void testRegisterMembersFromCsv_duplicateThrowsException() throws Exception {
142141
Member adminMember = mock(Member.class);
143142
when(memberService.findActiveMember(adminId)).thenReturn(adminMember);
144143

145-
// 중복 체크: 닉네임 또는 email 중 하나라도 중복이 있으면 에러 발생
146144
when(loadMemberPort.existsByNicknamesOrEmails(Set.of(dummyMemberInfo1.getNickname()), Set.of(dummyMemberInfo1.getEmail())))
147145
.thenReturn(true);
148146

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package clap.server.application.service.auth;
2+
3+
import clap.server.TestDataFactory;
4+
import clap.server.adapter.inbound.web.dto.auth.response.LoginResponse;
5+
import clap.server.application.port.outbound.member.LoadMemberPort;
6+
import clap.server.domain.model.auth.CustomJwts;
7+
import clap.server.domain.model.member.Member;
8+
import clap.server.exception.AuthException;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.DisplayName;
11+
import org.junit.jupiter.api.Test;
12+
import org.junit.jupiter.api.extension.ExtendWith;
13+
import org.mockito.InjectMocks;
14+
import org.mockito.Mock;
15+
import org.mockito.junit.jupiter.MockitoExtension;
16+
import org.springframework.security.crypto.password.PasswordEncoder;
17+
18+
import java.util.Optional;
19+
20+
import static org.junit.jupiter.api.Assertions.*;
21+
import static org.mockito.Mockito.*;
22+
23+
@ExtendWith(MockitoExtension.class)
24+
class AuthServiceTest {
25+
26+
@InjectMocks
27+
private AuthService authService;
28+
29+
@Mock
30+
private LoadMemberPort loadMemberPort;
31+
@Mock
32+
private ManageTokenService manageTokenService;
33+
@Mock
34+
private PasswordEncoder passwordEncoder;
35+
@Mock
36+
private LoginAttemptService loginAttemptService;
37+
@Mock
38+
private RefreshTokenService refreshTokenService;
39+
40+
private Member user;
41+
private Member notApprovedUser;
42+
43+
@BeforeEach
44+
void setUp() {
45+
user = TestDataFactory.createUser();
46+
notApprovedUser = TestDataFactory.createNotApprovedUser();
47+
}
48+
49+
@Test
50+
@DisplayName("로그인 성공")
51+
void loginSuccess() {
52+
// Given
53+
String nickname = "atom.user";
54+
String inputPassword = "Password000!";
55+
String clientIp = "127.0.0.1";
56+
Member member = user;
57+
CustomJwts jwtTokens = new CustomJwts("accessToken", "refreshToken");
58+
59+
when(loadMemberPort.findByNickname(nickname)).thenReturn(Optional.of(member));
60+
when(passwordEncoder.matches(inputPassword, member.getPassword())).thenReturn(true);
61+
when(manageTokenService.issueTokens(member)).thenReturn(jwtTokens);
62+
63+
// When
64+
LoginResponse response = authService.login(nickname, inputPassword, clientIp);
65+
66+
// Then
67+
assertNotNull(response);
68+
assertEquals(jwtTokens.accessToken(), response.accessToken());
69+
assertEquals(jwtTokens.refreshToken(), response.refreshToken());
70+
verify(loginAttemptService).resetFailedAttempts(clientIp);
71+
verify(refreshTokenService).saveRefreshToken(any());
72+
}
73+
74+
@Test
75+
@DisplayName("잘못된 비밀번호를 입력하면 로그인 실패한다.")
76+
void loginFailureWrongPassword() {
77+
// Given
78+
String nickname = "atom.user";
79+
String inputPassword = "wrongPassword000!";
80+
String clientIp = "127.0.0.1";
81+
Member member = user;
82+
83+
when(loadMemberPort.findByNickname(nickname)).thenReturn(Optional.of(member));
84+
when(passwordEncoder.matches(inputPassword, member.getPassword())).thenReturn(false);
85+
86+
// When & Then
87+
assertThrows(AuthException.class, () -> authService.login(nickname, inputPassword, clientIp));
88+
verify(loginAttemptService).recordFailedAttempt(clientIp, nickname);
89+
}
90+
91+
92+
@Test
93+
@DisplayName("사용자가 초기 로그인 시 임시 토큰이 발급된다.")
94+
void loginWithApprovalRequestStatus() {
95+
// Given
96+
String nickname = "atom.user";
97+
String inputPassword = "Password000!";
98+
String clientIp = "127.0.0.1";
99+
100+
Member member = notApprovedUser;
101+
String temporaryToken = "temporaryToken";
102+
103+
when(loadMemberPort.findByNickname(nickname)).thenReturn(Optional.of(member));
104+
when(passwordEncoder.matches(inputPassword, member.getPassword())).thenReturn(true);
105+
when(manageTokenService.issueTemporaryToken(notApprovedUser.getMemberId())).thenReturn(temporaryToken);
106+
107+
// When
108+
LoginResponse response = authService.login(nickname, inputPassword, clientIp);
109+
110+
// Then
111+
assertNotNull(response);
112+
assertEquals(temporaryToken, response.accessToken());
113+
assertNull(response.refreshToken());
114+
verify(manageTokenService).issueTemporaryToken(notApprovedUser.getMemberId());
115+
verify(manageTokenService, never()).issueTokens(any());
116+
verify(refreshTokenService, never()).saveRefreshToken(any());
117+
verify(loginAttemptService, never()).resetFailedAttempts(any());
118+
}
119+
120+
}

0 commit comments

Comments
 (0)