diff --git a/.coderabbit.yaml b/.coderabbit.yaml deleted file mode 100644 index 0827258e..00000000 --- a/.coderabbit.yaml +++ /dev/null @@ -1,14 +0,0 @@ -language: "ko-KR" -early_access: false -reviews: - profile: "chill" - request_changes_workflow: false - high_level_summary: true - poem: false - review_status: true - collapse_walkthrough: false - auto_review: - enabled: true - drafts: false -chat: - auto_reply: true \ No newline at end of file diff --git a/aics-api/build.gradle b/aics-api/build.gradle index 1978588a..2f90817f 100644 --- a/aics-api/build.gradle +++ b/aics-api/build.gradle @@ -3,7 +3,7 @@ bootJar { } jar { - enabled = false + enabled = true } dependencies { @@ -11,4 +11,12 @@ dependencies { implementation project(':aics-common') implementation project(':aics-infra') implementation project(':aics-global-utils') + + testFixturesImplementation project(':aics-domain') + testFixturesImplementation project(':aics-common') + testFixturesImplementation project(':aics-infra') + testFixturesImplementation project(':aics-global-utils') + + testFixturesImplementation testFixtures(project(':aics-domain')) + testFixturesImplementation testFixtures(project(':aics-global-utils')) } \ No newline at end of file diff --git a/aics-api/src/main/java/kgu/developers/api/auth/application/AuthService.java b/aics-api/src/main/java/kgu/developers/api/auth/application/AuthService.java index 118aaf26..6e837ec4 100644 --- a/aics-api/src/main/java/kgu/developers/api/auth/application/AuthService.java +++ b/aics-api/src/main/java/kgu/developers/api/auth/application/AuthService.java @@ -12,9 +12,11 @@ import kgu.developers.api.user.application.UserService; import kgu.developers.common.auth.jwt.TokenProvider; import kgu.developers.domain.user.domain.User; +import lombok.Builder; import lombok.RequiredArgsConstructor; @Service +@Builder @RequiredArgsConstructor public class AuthService { private final UserService userService; diff --git a/aics-api/src/main/java/kgu/developers/api/auth/presentation/AuthController.java b/aics-api/src/main/java/kgu/developers/api/auth/presentation/AuthController.java index 71d75d04..bc1f62fa 100644 --- a/aics-api/src/main/java/kgu/developers/api/auth/presentation/AuthController.java +++ b/aics-api/src/main/java/kgu/developers/api/auth/presentation/AuthController.java @@ -9,6 +9,7 @@ import kgu.developers.api.auth.application.AuthService; import kgu.developers.api.auth.presentation.request.LoginRequest; import kgu.developers.api.auth.presentation.response.TokenResponse; +import lombok.Builder; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Builder @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/auth") diff --git a/aics-api/src/main/java/kgu/developers/api/auth/presentation/request/LoginRequest.java b/aics-api/src/main/java/kgu/developers/api/auth/presentation/request/LoginRequest.java index 796b0059..6ed5c0df 100644 --- a/aics-api/src/main/java/kgu/developers/api/auth/presentation/request/LoginRequest.java +++ b/aics-api/src/main/java/kgu/developers/api/auth/presentation/request/LoginRequest.java @@ -4,7 +4,9 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import lombok.Builder; +@Builder public record LoginRequest( @Schema(description = "학번", example = "202412345", requiredMode = REQUIRED) @NotNull diff --git a/aics-api/src/main/java/kgu/developers/api/user/application/UserService.java b/aics-api/src/main/java/kgu/developers/api/user/application/UserService.java index 79fb3389..2eb36b8f 100644 --- a/aics-api/src/main/java/kgu/developers/api/user/application/UserService.java +++ b/aics-api/src/main/java/kgu/developers/api/user/application/UserService.java @@ -18,9 +18,11 @@ import kgu.developers.domain.user.domain.User; import kgu.developers.domain.user.domain.UserRepository; import kgu.developers.domain.user.exception.UserNotFoundException; +import lombok.Builder; import lombok.RequiredArgsConstructor; @Service +@Builder @RequiredArgsConstructor public class UserService { private final BCryptPasswordEncoder bCryptPasswordEncoder; diff --git a/aics-api/src/main/java/kgu/developers/api/user/presentation/request/UserCreateRequest.java b/aics-api/src/main/java/kgu/developers/api/user/presentation/request/UserCreateRequest.java index c4ec53e8..a5a20dd7 100644 --- a/aics-api/src/main/java/kgu/developers/api/user/presentation/request/UserCreateRequest.java +++ b/aics-api/src/main/java/kgu/developers/api/user/presentation/request/UserCreateRequest.java @@ -6,7 +6,9 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import kgu.developers.domain.user.domain.Major; +import lombok.Builder; +@Builder public record UserCreateRequest( @Schema(description = "학번", example = "202412345", requiredMode = REQUIRED) diff --git a/aics-api/src/main/java/kgu/developers/api/user/presentation/request/UserUpdateRequest.java b/aics-api/src/main/java/kgu/developers/api/user/presentation/request/UserUpdateRequest.java index 75ac529a..5e65425c 100644 --- a/aics-api/src/main/java/kgu/developers/api/user/presentation/request/UserUpdateRequest.java +++ b/aics-api/src/main/java/kgu/developers/api/user/presentation/request/UserUpdateRequest.java @@ -4,7 +4,9 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Pattern; +import lombok.Builder; +@Builder public record UserUpdateRequest( @Schema(description = "전화번호", example = "010-1234-5678", requiredMode = REQUIRED) @Pattern(regexp = "^\\d{2,4}-\\d{3,4}-\\d{4}$", message = "유효한 전화번호 형식이 아닙니다.") diff --git a/aics-api/src/testFixtures/java/auth/application/AuthServiceTest.java b/aics-api/src/testFixtures/java/auth/application/AuthServiceTest.java new file mode 100644 index 00000000..dfbd484f --- /dev/null +++ b/aics-api/src/testFixtures/java/auth/application/AuthServiceTest.java @@ -0,0 +1,89 @@ +package auth.application; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import kgu.developers.api.auth.application.AuthService; +import kgu.developers.api.auth.presentation.exception.InvalidPasswordException; +import kgu.developers.api.auth.presentation.request.LoginRequest; +import kgu.developers.api.user.application.UserService; +import kgu.developers.common.auth.jwt.JwtProperties; +import kgu.developers.common.auth.jwt.TokenProvider; +import kgu.developers.domain.user.domain.Major; +import kgu.developers.domain.user.domain.User; +import mock.FakeUserRepository; + +public class AuthServiceTest { + private AuthService authService; + + @BeforeEach + public void init() { + FakeUserRepository fakeUserRepository = new FakeUserRepository(); + BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); + + this.authService = AuthService.builder() + .userService( + UserService.builder() + .userRepository(fakeUserRepository) + .bCryptPasswordEncoder(bCryptPasswordEncoder) + .build() + ) + .passwordEncoder(bCryptPasswordEncoder) + .tokenProvider( + TokenProvider.builder() + .jwtProperties(new JwtProperties("testIssuer", "testSecretKey")) + .build() + ) + .build(); + + fakeUserRepository.save(User.builder() + .id("202411345") + .password(bCryptPasswordEncoder.encode("password1234")) + .name("홍길동") + .email("test@kyonggi.ac.kr") + .phone("010-1234-5678") + .major(Major.CSE) + .build()); + } + + @Test + @DisplayName("login은 토큰을 발급할 수 있다") + public void login_Success() { + // given + String userId = "202411345"; + String password = "password1234"; + + // when + // then + assertThatCode(() -> { + authService.login(LoginRequest.builder() + .userId(userId) + .password(password) + .build() + ); + }).doesNotThrowAnyException(); + } + + @Test + @DisplayName("login은 비밀번호가 틀리면 InvalidPasswordException을 발생시킨다") + public void login_InvalidPassword_ThrowsException() { + // given + String userId = "202411345"; + String password = "wrongPassword"; + + // when + // then + assertThatThrownBy(() -> { + authService.login(LoginRequest.builder() + .userId(userId) + .password(password) + .build() + ); + }).isInstanceOf(InvalidPasswordException.class); + } +} diff --git a/aics-api/src/testFixtures/java/auth/presentation/AuthControllerTest.java b/aics-api/src/testFixtures/java/auth/presentation/AuthControllerTest.java new file mode 100644 index 00000000..9706fcbb --- /dev/null +++ b/aics-api/src/testFixtures/java/auth/presentation/AuthControllerTest.java @@ -0,0 +1,48 @@ +package auth.presentation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Objects; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; + +import kgu.developers.api.auth.presentation.request.LoginRequest; +import kgu.developers.api.auth.presentation.response.TokenResponse; +import kgu.developers.api.user.presentation.request.UserCreateRequest; +import kgu.developers.domain.user.domain.Major; +import mock.TestContainer; +/* + * 추후 Controller 테스트는 medium test로 전환할 예정입니다. + * medium test는 Controller / Service / Repository 계층을 함께 테스트합니다. + */ +public class AuthControllerTest { + + @Test + @DisplayName("로그인 성공 후 200 상태 코드와 토큰을 정상적으로 발급받는다") + public void login_Success() { + // given + TestContainer testContainer = new TestContainer(); + testContainer.userService.createUser(UserCreateRequest.builder() + .userId("202411345") + .password("password0000") + .name("김철수") + .email("kim@kyonggi.ac.kr") + .phone("010-0000-0000") + .major(Major.CSE) + .build()); + + // when + ResponseEntity result = testContainer.authController.login(LoginRequest.builder() + .userId("202411345") + .password("password0000") + .build()); + + // then + assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200)); + assertThat(Objects.requireNonNull(result.getBody()).accessToken()).isNotNull(); + assertThat(Objects.requireNonNull(result.getBody()).refreshToken()).isNotNull(); + } +} diff --git a/aics-api/src/testFixtures/java/mock/TestContainer.java b/aics-api/src/testFixtures/java/mock/TestContainer.java new file mode 100644 index 00000000..9123a9e6 --- /dev/null +++ b/aics-api/src/testFixtures/java/mock/TestContainer.java @@ -0,0 +1,37 @@ +package mock; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import kgu.developers.api.auth.application.AuthService; +import kgu.developers.api.auth.presentation.AuthController; +import kgu.developers.api.user.application.UserService; +import kgu.developers.common.auth.jwt.JwtProperties; +import kgu.developers.common.auth.jwt.TokenProvider; +import kgu.developers.domain.user.domain.UserRepository; + +public class TestContainer { + public final UserRepository userRepository; + public final UserService userService; + public final AuthService authService; + public final AuthController authController; + + public TestContainer() { + BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); + this.userRepository = new FakeUserRepository(); + this.userService = UserService.builder() + .userRepository(this.userRepository) + .bCryptPasswordEncoder(bCryptPasswordEncoder) + .build(); + this.authService = AuthService.builder() + .userService(this.userService) + .passwordEncoder(bCryptPasswordEncoder) + .tokenProvider(TokenProvider.builder() + .jwtProperties(new JwtProperties("testIssuer", "testSecretKey")) + .build() + ) + .build(); + this.authController = AuthController.builder() + .authService(this.authService) + .build(); + } +} \ No newline at end of file diff --git a/aics-api/src/testFixtures/java/user/application/UserServiceTest.java b/aics-api/src/testFixtures/java/user/application/UserServiceTest.java new file mode 100644 index 00000000..eed79561 --- /dev/null +++ b/aics-api/src/testFixtures/java/user/application/UserServiceTest.java @@ -0,0 +1,117 @@ +package user.application; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import kgu.developers.api.user.application.UserService; +import kgu.developers.api.user.presentation.exception.UserIdDuplicateException; +import kgu.developers.api.user.presentation.request.UserCreateRequest; +import kgu.developers.api.user.presentation.response.UserPersistResponse; +import kgu.developers.domain.user.domain.Major; +import kgu.developers.domain.user.domain.User; +import kgu.developers.domain.user.exception.UserNotFoundException; +import mock.FakeUserRepository; + +public class UserServiceTest { + private UserService userService; + + @BeforeEach + public void init() { + BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); + FakeUserRepository fakeUserRepository = new FakeUserRepository(); + + this.userService = UserService.builder() + .userRepository(fakeUserRepository) + .bCryptPasswordEncoder(bCryptPasswordEncoder) + .build(); + + fakeUserRepository.save(User.builder() + .id("202411345") + .password(bCryptPasswordEncoder.encode("password1234")) + .name("홍길동") + .email("test@kyonggi.ac.kr") + .phone("010-1234-5678") + .major(Major.CSE) + .build()); + + fakeUserRepository.save(User.builder() + .id("202411346") + .password(bCryptPasswordEncoder.encode("password5678")) + .name("신짱구") + .email("shin@kyonggi.ac.kr") + .phone("010-5678-1234") + .major(Major.AIT) + .build()); + } + + @Test + @DisplayName("createUser는 유저를 생성할 수 있다") + public void createUser_Success() { + // given + UserCreateRequest request = UserCreateRequest.builder() + .userId("202411347") + .password("password0000") + .name("김철수") + .email("kim@kyonggi.ac.kr") + .phone("010-0000-0000") + .major(Major.CSE) + .build(); + + // when + UserPersistResponse result = userService.createUser(request); + + // then + assertEquals("202411347", result.id()); + } + + @Test + @DisplayName("createUser는 중복된 아이디로 유저를 생성할 경우 UserIdDuplicateException을 발생시킨다") + public void createUser_DuplicateId_ThrowsException() { + // given + UserCreateRequest request = UserCreateRequest.builder() + .userId("202411345") + .password("password0000") + .name("김철수") + .email("kim@kyonggi.ac.kr") + .phone("010-0000-0000") + .major(Major.CSE) + .build(); + + // when + // then + assertThatThrownBy(() -> { + userService.createUser(request); + }).isInstanceOf(UserIdDuplicateException.class); + } + + @Test + @DisplayName("getUserById는 유저를 찾아올 수 있다") + public void getUserById_Success() { + // given + String id = "202411345"; + + // when + User result = userService.getUserById(id); + + // then + assertEquals("홍길동", result.getName()); + } + + @Test + @DisplayName("getUserById는 존재하지 않는 유저를 찾아올 경우 UserNotFoundException을 발생시킨다") + public void getUserById_NotFound_ThrowsException() { + // given + String id = "202411348"; + + // when + // then + assertThatThrownBy(() -> { + userService.getUserById(id); + }).isInstanceOf(UserNotFoundException.class); + } +} diff --git a/aics-api/src/testFixtures/java/user/presentation/UserControllerTest.java b/aics-api/src/testFixtures/java/user/presentation/UserControllerTest.java new file mode 100644 index 00000000..4acf3488 --- /dev/null +++ b/aics-api/src/testFixtures/java/user/presentation/UserControllerTest.java @@ -0,0 +1,8 @@ +package user.presentation; + +/* + * 추후 Controller 테스트는 medium test로 전환할 예정입니다. + * medium test는 Controller / Service / Repository 계층을 함께 테스트합니다. + */ +public class UserControllerTest { +} diff --git a/aics-common/src/main/java/kgu/developers/common/auth/jwt/JwtProperties.java b/aics-common/src/main/java/kgu/developers/common/auth/jwt/JwtProperties.java index 1223bd75..989a9efa 100644 --- a/aics-common/src/main/java/kgu/developers/common/auth/jwt/JwtProperties.java +++ b/aics-common/src/main/java/kgu/developers/common/auth/jwt/JwtProperties.java @@ -3,12 +3,16 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; -@Setter @Getter @Component +@NoArgsConstructor +@AllArgsConstructor @ConfigurationProperties(prefix = "jwt") public class JwtProperties { private String issuer; diff --git a/aics-common/src/main/java/kgu/developers/common/auth/jwt/TokenProvider.java b/aics-common/src/main/java/kgu/developers/common/auth/jwt/TokenProvider.java index ed75a45c..50237caa 100644 --- a/aics-common/src/main/java/kgu/developers/common/auth/jwt/TokenProvider.java +++ b/aics-common/src/main/java/kgu/developers/common/auth/jwt/TokenProvider.java @@ -16,9 +16,11 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; +import lombok.Builder; import lombok.RequiredArgsConstructor; @Service +@Builder @RequiredArgsConstructor public class TokenProvider { private final JwtProperties jwtProperties; diff --git a/aics-domain/build.gradle b/aics-domain/build.gradle index baf16e5f..ed75add7 100644 --- a/aics-domain/build.gradle +++ b/aics-domain/build.gradle @@ -10,4 +10,8 @@ dependencies { implementation project(':aics-common') implementation project(':aics-infra') implementation project(':aics-global-utils') + + testFixturesImplementation project(':aics-common') + testFixturesImplementation project(':aics-infra') + testFixturesImplementation project(':aics-global-utils') } \ No newline at end of file diff --git a/aics-domain/src/main/java/kgu/developers/domain/user/domain/DepartmentCodeCondition.java b/aics-domain/src/main/java/kgu/developers/domain/user/domain/DeptCode.java similarity index 75% rename from aics-domain/src/main/java/kgu/developers/domain/user/domain/DepartmentCodeCondition.java rename to aics-domain/src/main/java/kgu/developers/domain/user/domain/DeptCode.java index cac26d66..4524d423 100644 --- a/aics-domain/src/main/java/kgu/developers/domain/user/domain/DepartmentCodeCondition.java +++ b/aics-domain/src/main/java/kgu/developers/domain/user/domain/DeptCode.java @@ -7,7 +7,7 @@ @Getter @AllArgsConstructor -public enum DepartmentCodeCondition { +public enum DeptCode { CONDITION_18("18", List.of("10", "11", "12")), CONDITION_19("19", List.of("12")), CONDITION_20("20", List.of("14")), @@ -20,8 +20,8 @@ public enum DepartmentCodeCondition { private final String year; private final List validCode; - public static DepartmentCodeCondition from(String code) { - for (DepartmentCodeCondition condition : values()) { + public static DeptCode from(String code) { + for (DeptCode condition : values()) { if (condition.year.equals(code)) { return condition; } @@ -29,7 +29,7 @@ public static DepartmentCodeCondition from(String code) { return null; } - public static boolean isValidDepartmentCode(String id) { + public static boolean isValidDeptCode(String id) { if (id == null || !id.matches("\\d{9}")) { return false; } @@ -37,7 +37,7 @@ public static boolean isValidDepartmentCode(String id) { String year = id.substring(2, 4); String code = id.substring(4, 6); - DepartmentCodeCondition condition = DepartmentCodeCondition.from(year); + DeptCode condition = DeptCode.from(year); return condition != null && condition.isValidCode(code); } diff --git a/aics-domain/src/main/java/kgu/developers/domain/user/domain/User.java b/aics-domain/src/main/java/kgu/developers/domain/user/domain/User.java index d561845f..88ed45c7 100644 --- a/aics-domain/src/main/java/kgu/developers/domain/user/domain/User.java +++ b/aics-domain/src/main/java/kgu/developers/domain/user/domain/User.java @@ -3,7 +3,7 @@ import static jakarta.persistence.CascadeType.ALL; import static jakarta.persistence.EnumType.STRING; import static jakarta.persistence.FetchType.LAZY; -import static kgu.developers.domain.user.domain.DepartmentCodeCondition.isValidDepartmentCode; +import static kgu.developers.domain.user.domain.DeptCode.isValidDeptCode; import static lombok.AccessLevel.PROTECTED; import jakarta.persistence.Column; @@ -14,12 +14,13 @@ import jakarta.persistence.Table; import kgu.developers.common.domain.BaseTimeEntity; import kgu.developers.domain.post.domain.Post; -import kgu.developers.domain.user.exception.DepartmentCodeNotValidException; +import kgu.developers.domain.user.exception.DeptCodeNotValidException; import kgu.developers.domain.user.exception.EmailDomainNotValidException; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; + import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -33,8 +34,8 @@ @Getter @Builder @Table(name = "\"user\"") -@AllArgsConstructor @NoArgsConstructor(access = PROTECTED) +@AllArgsConstructor(access = PROTECTED) public class User extends BaseTimeEntity implements UserDetails { @Id @@ -66,9 +67,9 @@ public class User extends BaseTimeEntity implements UserDetails { List posts = new ArrayList<>(); public static User create(String id, String password, - String name, String email, - String phone, Major major) { - validateDepartment(id, email); + String name, String email, + String phone, Major major) { + validateDept(id, email); return User.builder() .id(id) .password(password) @@ -81,9 +82,8 @@ public static User create(String id, String password, } public void updateEmail(String email) { - if (!isValidEmailDomain(email)) { + if (!isValidEmailDomain(email)) throw new EmailDomainNotValidException(); - } this.email = email; } @@ -93,7 +93,7 @@ public void updatePhone(String phone) { @Override public Collection getAuthorities() { - return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); + return Collections.singletonList(new SimpleGrantedAuthority(Role.USER.name())); } @Override @@ -106,16 +106,16 @@ public String getPassword() { return password; } - private static void validateDepartment(String id, String email) { - if (!isValidEmailDomain(email)) { + private static void validateDept(String id, String email) { + if (!isValidEmailDomain(email)) throw new EmailDomainNotValidException(); - } - if (!isValidDepartmentCode(id)) { - throw new DepartmentCodeNotValidException(); - } + if (!isValidDeptCode(id)) + throw new DeptCodeNotValidException(); } + private static final String ACCESSIBLE_EMAIL_DOMAIN = "@kyonggi.ac.kr"; + private static boolean isValidEmailDomain(String email) { - return email != null && email.endsWith("@kyonggi.ac.kr"); + return email != null && email.endsWith(ACCESSIBLE_EMAIL_DOMAIN); } } diff --git a/aics-domain/src/main/java/kgu/developers/domain/user/exception/DepartmentCodeNotValidException.java b/aics-domain/src/main/java/kgu/developers/domain/user/exception/DepartmentCodeNotValidException.java deleted file mode 100644 index 0bda093d..00000000 --- a/aics-domain/src/main/java/kgu/developers/domain/user/exception/DepartmentCodeNotValidException.java +++ /dev/null @@ -1,11 +0,0 @@ -package kgu.developers.domain.user.exception; - -import static kgu.developers.domain.user.exception.UserDomainExceptionCode.DEPARTMENT_CODE_NOT_VALID; - -import kgu.developers.common.exception.CustomException; - -public class DepartmentCodeNotValidException extends CustomException { - public DepartmentCodeNotValidException() { - super(DEPARTMENT_CODE_NOT_VALID); - } -} diff --git a/aics-domain/src/main/java/kgu/developers/domain/user/exception/DeptCodeNotValidException.java b/aics-domain/src/main/java/kgu/developers/domain/user/exception/DeptCodeNotValidException.java new file mode 100644 index 00000000..febbbcf5 --- /dev/null +++ b/aics-domain/src/main/java/kgu/developers/domain/user/exception/DeptCodeNotValidException.java @@ -0,0 +1,11 @@ +package kgu.developers.domain.user.exception; + +import static kgu.developers.domain.user.exception.UserDomainExceptionCode.DEPT_CODE_NOT_VALID; + +import kgu.developers.common.exception.CustomException; + +public class DeptCodeNotValidException extends CustomException { + public DeptCodeNotValidException() { + super(DEPT_CODE_NOT_VALID); + } +} diff --git a/aics-domain/src/main/java/kgu/developers/domain/user/exception/UserDomainExceptionCode.java b/aics-domain/src/main/java/kgu/developers/domain/user/exception/UserDomainExceptionCode.java index 5cb7a51c..6c45efab 100644 --- a/aics-domain/src/main/java/kgu/developers/domain/user/exception/UserDomainExceptionCode.java +++ b/aics-domain/src/main/java/kgu/developers/domain/user/exception/UserDomainExceptionCode.java @@ -13,7 +13,7 @@ public enum UserDomainExceptionCode implements ExceptionCode { USER_NOT_FOUND(NOT_FOUND, "해당 회원을 찾을 수 없습니다."), EMAIL_NOT_VALID(BAD_REQUEST, "학교 이메일 형식이 아닙니다."), - DEPARTMENT_CODE_NOT_VALID(BAD_REQUEST, "학번별 학과 코드가 일치하지 않습니다.") + DEPT_CODE_NOT_VALID(BAD_REQUEST, "학번별 학과 코드가 일치하지 않습니다.") ; private final HttpStatus status; diff --git a/aics-domain/src/testFixtures/java/mock/FakeUserRepository.java b/aics-domain/src/testFixtures/java/mock/FakeUserRepository.java new file mode 100644 index 00000000..2558bfb3 --- /dev/null +++ b/aics-domain/src/testFixtures/java/mock/FakeUserRepository.java @@ -0,0 +1,66 @@ +package mock; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.data.domain.Pageable; + +import kgu.developers.common.response.PageableResponse; +import kgu.developers.common.response.PaginatedListResponse; +import kgu.developers.domain.user.domain.User; +import kgu.developers.domain.user.domain.UserRepository; + +public class FakeUserRepository implements UserRepository { + + // private final AtomicLong autoGeneratedId = new AtomicLong(0); + private final List data = Collections.synchronizedList(new ArrayList<>()); + + @Override + public User save(User user) { + User newUser = User.builder() + .id(user.getId()) + .password(user.getPassword()) + .name(user.getName()) + .email(user.getEmail()) + .phone(user.getPhone()) + .role(user.getRole()) + .major(user.getMajor()) + .build(); + data.add(newUser); + return newUser; + } + + @Override + public boolean existsById(String userId) { + return data.stream().anyMatch(item -> item.getId().equals(userId)); + } + + @Override + public Optional findById(String userId) { + return data.stream().filter(item -> item.getId().equals(userId)).findAny(); + } + + @Override + public PaginatedListResponse findAllOrderByIdDesc(Pageable pageable) { + List sortedUsers = data.stream() + .sorted(Comparator.comparing(User::getId).reversed()) + .collect(Collectors.toList()); + + int start = (int) pageable.getOffset(); + int end = Math.min((start + pageable.getPageSize()), sortedUsers.size()); + + List paginatedUsers = start > sortedUsers.size() ? + Collections.emptyList() : + sortedUsers.subList(start, end); + + List userIds = sortedUsers.stream() + .map(User::getId) + .collect(Collectors.toList()); + + return PaginatedListResponse.of(paginatedUsers, PageableResponse.of(pageable, userIds)); + } +} diff --git a/aics-domain/src/testFixtures/java/user/application/UserServiceTest.java b/aics-domain/src/testFixtures/java/user/application/UserServiceTest.java new file mode 100644 index 00000000..48dee65b --- /dev/null +++ b/aics-domain/src/testFixtures/java/user/application/UserServiceTest.java @@ -0,0 +1,9 @@ +package user.application; + +/* + * 추후 Admin 모듈과 분리하게 되면 공통 로직을 분리하기 위해 Facade Pattern을 적용할 예정입니다. + * API & ADMIN 모듈에는 Controller와 Facade가, DOMAIN 모듈에는 Service와 Repository가 위치하게 됩니다. + * 따라서 UserServiceTest 역시 DOMAIN 모듈에 위치하게 됩니다. + */ +public class UserServiceTest { +} diff --git a/aics-domain/src/testFixtures/java/user/domain/UserDomainTest.java b/aics-domain/src/testFixtures/java/user/domain/UserDomainTest.java new file mode 100644 index 00000000..aab4d186 --- /dev/null +++ b/aics-domain/src/testFixtures/java/user/domain/UserDomainTest.java @@ -0,0 +1,78 @@ +package user.domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import kgu.developers.domain.user.domain.Major; +import kgu.developers.domain.user.domain.Role; +import kgu.developers.domain.user.domain.User; +import kgu.developers.domain.user.exception.DeptCodeNotValidException; +import kgu.developers.domain.user.exception.EmailDomainNotValidException; + +public class UserDomainTest { + + @Test + @DisplayName("USER 객체를 생성할 수 있다") + public void createUser_Success() { + // given + String id = "202411345"; + String password = "password"; + String name = "홍길동"; + String email = "valid@kyonggi.ac.kr"; + String phone = "010-1234-5678"; + Major major = Major.CSE; + + // when + User user = User.create(id, password, name, email, phone, major); + + // then + assertNotNull(user); + assertEquals(id, user.getId()); + assertEquals(password, user.getPassword()); + assertEquals(name, user.getName()); + assertEquals(email, user.getEmail()); + assertEquals(phone, user.getPhone()); + assertEquals(Role.USER, user.getRole()); + assertEquals(major, user.getMajor()); + } + + @Test + @DisplayName("잘못된 이메일 도메인으로 USER 생성 시 EmailDomainNotValidException이 발생 한다") + public void createUser_InvalidEmailDomain_ThrowsException() { + // given + String id = "202411345"; + String password = "password"; + String name = "홍길동"; + String email = "valid@gmail.com"; // 잘못된 이메일 도메인 + String phone = "010-1234-5678"; + Major major = Major.CSE; + + // when + // then + assertThatThrownBy(() -> { + User.create(id, password, name, email, phone, major); + }).isInstanceOf(EmailDomainNotValidException.class); + } + + @Test + @DisplayName("잘못된 학과 코드로 USER 생성 시 DeptCodeNotValidException이 발생 한다") + public void createUser_InvalidDeptCode_ThrowsException() { + // given + String id = "202410345"; // 잘못된 학과 코드 + String password = "password"; + String name = "홍길동"; + String email = "valid@kyonggi.ac.kr"; + String phone = "010-1234-5678"; + Major major = Major.CSE; + + // when + // then + assertThatThrownBy(() -> { + User.create(id, password, name, email, phone, major); + }).isInstanceOf(DeptCodeNotValidException.class); + } +} diff --git a/build.gradle b/build.gradle index e03ee087..4bb114d7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id 'java-test-fixtures' id 'org.springframework.boot' version '3.3.5' id 'io.spring.dependency-management' version '1.1.6' } @@ -19,6 +20,7 @@ subprojects { targetCompatibility = JavaVersion.VERSION_17 apply plugin: 'java' + apply plugin: "java-test-fixtures" apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' @@ -47,13 +49,17 @@ subprojects { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' implementation 'io.jsonwebtoken:jjwt:0.9.1' implementation 'javax.xml.bind:jaxb-api:2.3.1' + + testFixturesImplementation 'org.springframework.boot:spring-boot-starter-test' + testFixturesRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testFixturesImplementation 'org.springframework.boot:spring-boot-starter-security' + testFixturesImplementation 'org.springframework.boot:spring-boot-starter-data-jpa' + testFixturesImplementation 'org.projectlombok:lombok' + testFixturesImplementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0' } test {