From 94bf3e492bb13fc1fd857b5d7c103ae80d54aa1c Mon Sep 17 00:00:00 2001 From: gru Date: Sat, 8 Feb 2025 01:51:45 +0900 Subject: [PATCH 01/27] =?UTF-8?q?[WIP]=20FollowServiceTest=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/entity/common/ParseUtil.java | 2 + .../flab/stargram/entity/dto/FollowPair.java | 2 + .../repository/FollowGroupRepository.java | 6 +- .../stargram/service/FollowGroupService.java | 4 +- .../stargram/service/FollowServiceTest.java | 61 +++++++++++++++++++ 5 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 src/test/java/com/flab/stargram/service/FollowServiceTest.java diff --git a/src/main/java/com/flab/stargram/entity/common/ParseUtil.java b/src/main/java/com/flab/stargram/entity/common/ParseUtil.java index 113c573..4b8d49a 100644 --- a/src/main/java/com/flab/stargram/entity/common/ParseUtil.java +++ b/src/main/java/com/flab/stargram/entity/common/ParseUtil.java @@ -6,6 +6,8 @@ public class ParseUtil { private static final String parseLongRegex = "^[0-9]+$"; public static Long parseToLong(String value) { + assert value != null : "Input value is null"; + if (value.matches(parseLongRegex)) { return Long.parseLong(value); } else { diff --git a/src/main/java/com/flab/stargram/entity/dto/FollowPair.java b/src/main/java/com/flab/stargram/entity/dto/FollowPair.java index b85aaf1..f8dd194 100644 --- a/src/main/java/com/flab/stargram/entity/dto/FollowPair.java +++ b/src/main/java/com/flab/stargram/entity/dto/FollowPair.java @@ -2,8 +2,10 @@ import com.flab.stargram.entity.common.ParseUtil; +import lombok.Builder; import lombok.Getter; +@Builder @Getter public class FollowPair { private Long followerId; diff --git a/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java b/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java index f1372db..c6997e4 100644 --- a/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java +++ b/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java @@ -6,7 +6,7 @@ public interface FollowGroupRepository extends JpaRepository { //SELECT * FROM follow_group WHERE user_id = ? - FollowGroup findByUserId(Long userId); - //SELECT EXISTS (SELECT 1 FROM follow_group WHERE user_id= ?); - boolean existsByUserId(Long userId); + FollowGroup findByFollowingId(Long followingId); + //SELECT EXISTS (SELECT 1 FROM follow_group WHERE following_id= ?); + boolean existsByFollowingId(Long followingId); } diff --git a/src/main/java/com/flab/stargram/service/FollowGroupService.java b/src/main/java/com/flab/stargram/service/FollowGroupService.java index dcfb228..07ab0c2 100644 --- a/src/main/java/com/flab/stargram/service/FollowGroupService.java +++ b/src/main/java/com/flab/stargram/service/FollowGroupService.java @@ -13,11 +13,11 @@ public class FollowGroupService { private final FollowGroupRepository followGroupRepository; public boolean hasFollow(Long userId) { - return followGroupRepository.existsByUserId(userId); + return followGroupRepository.existsByFollowingId(userId); } public FollowGroup getOrCreateFollowGroup(long followingId) { - FollowGroup followGroup = followGroupRepository.findByUserId(followingId); + FollowGroup followGroup = followGroupRepository.findByFollowingId(followingId); if (followGroup == null) { followGroup = createFollowGroup(followingId); } diff --git a/src/test/java/com/flab/stargram/service/FollowServiceTest.java b/src/test/java/com/flab/stargram/service/FollowServiceTest.java new file mode 100644 index 0000000..8fe5bb6 --- /dev/null +++ b/src/test/java/com/flab/stargram/service/FollowServiceTest.java @@ -0,0 +1,61 @@ +package com.flab.stargram.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.flab.stargram.entity.dto.FollowPair; +import com.flab.stargram.entity.model.Follow; +import com.flab.stargram.entity.model.FollowGroup; +import com.flab.stargram.repository.FollowGroupRepository; +import com.flab.stargram.repository.FollowRepository; + +class FollowServiceTest { + @InjectMocks + private FollowService followService; + + @Mock + private UserService userService; + @Mock + private FollowGroupService followGroupService; + @Mock + private FollowRepository followRepository; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void followUser() { + //given + FollowPair followPair = FollowPair.builder() + .followingId(1L) + .followerId(2L) + .build(); + + //when + when(userService.hasUserId(followPair.getFollowerId())).thenReturn(true); + when(userService.hasUserId(followPair.getFollowingId())).thenReturn(true); + when(followGroupService.getOrCreateFollowGroup(followPair.getFollowingId())).thenReturn(mock(FollowGroup.class)); + when(followRepository.existsByFollowerIdAndFollowingId(followPair.getFollowerId(), followPair.getFollowingId())).thenReturn(false); + + followService.followUser(followPair); + + //then + verify(followRepository, times(1)).save(any(Follow.class)); + } + + @Test + void unfollowUser() { + } + + @Test + void getFollowers() { + } +} \ No newline at end of file From cfb488ec800917172cf27873aec763388c805ccd Mon Sep 17 00:00:00 2001 From: gru Date: Sat, 8 Feb 2025 01:52:30 +0900 Subject: [PATCH 02/27] =?UTF-8?q?FollowController=20Validate=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../exception/GlobalApiExceptionHandler.java | 22 +++++++++++++++++++ .../stargram/controller/FollowController.java | 9 ++++---- .../stargram/entity/common/ApiResult.java | 5 +++++ .../stargram/entity/dto/FollowRequestDto.java | 20 +++++------------ 5 files changed, 39 insertions(+), 18 deletions(-) diff --git a/build.gradle b/build.gradle index abe8044..9698651 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'com.mysql:mysql-connector-j' implementation 'io.jsonwebtoken:jjwt-api:0.12.6' + implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/com/flab/stargram/config/exception/GlobalApiExceptionHandler.java b/src/main/java/com/flab/stargram/config/exception/GlobalApiExceptionHandler.java index 20d8e45..d11bc9d 100644 --- a/src/main/java/com/flab/stargram/config/exception/GlobalApiExceptionHandler.java +++ b/src/main/java/com/flab/stargram/config/exception/GlobalApiExceptionHandler.java @@ -1,11 +1,16 @@ package com.flab.stargram.config.exception; +import java.util.Map; + +import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.flab.stargram.entity.common.ApiResponseEnum; import com.flab.stargram.entity.common.ApiResult; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; @RestControllerAdvice public class GlobalApiExceptionHandler { @@ -19,4 +24,21 @@ public ResponseEntity handleBaseApiException(BaseApiException e) { public ResponseEntity handleGeneralException(Exception e) { return ApiResult.error(ApiResponseEnum.FAILURE, e.getMessage()); } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) { + Throwable cause = ex.getCause(); + + if (cause instanceof InvalidFormatException invalidFormatException) { + String fieldName = invalidFormatException.getPath().get(0).getFieldName(); + String invalidValue = invalidFormatException.getValue().toString(); + return ApiResult.error( + ApiResponseEnum.INVALID_INPUT, + ApiResponseEnum.INVALID_INPUT.getMessage(), + Map.of(fieldName, invalidValue) + ); + } + + return ApiResult.error(ApiResponseEnum.INVALID_INPUT, ApiResponseEnum.INVALID_INPUT.getMessage()); + } } \ No newline at end of file diff --git a/src/main/java/com/flab/stargram/controller/FollowController.java b/src/main/java/com/flab/stargram/controller/FollowController.java index c03fd13..ad64886 100644 --- a/src/main/java/com/flab/stargram/controller/FollowController.java +++ b/src/main/java/com/flab/stargram/controller/FollowController.java @@ -1,6 +1,7 @@ package com.flab.stargram.controller; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -14,17 +15,18 @@ import com.flab.stargram.entity.dto.FollowRequestDto; import com.flab.stargram.service.FollowService; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RestController @RequestMapping("/users/{inputUserId}") @RequiredArgsConstructor +@Validated public class FollowController { private final FollowService followService; @PostMapping("/follow") - public ResponseEntity followUser(@RequestBody FollowRequestDto dto, @PathVariable String inputUserId) { - dto.validateEmpty().ifInvalidThrow(); + public ResponseEntity followUser(@Valid @RequestBody FollowRequestDto dto, @PathVariable String inputUserId) { FollowPair followPair = FollowPair.createFollowPairOf(inputUserId, dto); followService.followUser(followPair); @@ -32,8 +34,7 @@ public ResponseEntity followUser(@RequestBody FollowRequestDto dto, @ } @PostMapping("/unfollow") - public ResponseEntity unfollowUser(@RequestBody FollowRequestDto dto, @PathVariable String inputUserId) { - dto.validateEmpty().ifInvalidThrow(); + public ResponseEntity unfollowUser(@Valid @RequestBody FollowRequestDto dto, @PathVariable String inputUserId) { FollowPair followPair = FollowPair.createFollowPairOf(inputUserId, dto); followService.unfollowUser(followPair); diff --git a/src/main/java/com/flab/stargram/entity/common/ApiResult.java b/src/main/java/com/flab/stargram/entity/common/ApiResult.java index 60d3608..0a85ecb 100644 --- a/src/main/java/com/flab/stargram/entity/common/ApiResult.java +++ b/src/main/java/com/flab/stargram/entity/common/ApiResult.java @@ -24,4 +24,9 @@ public static ResponseEntity error(ApiResponseEnum apiResponseEnum, S ApiResult result = new ApiResult(apiResponseEnum, message, null); return ResponseEntity.status(apiResponseEnum.getHttpStatus()).body(result); } + + public static ResponseEntity error(ApiResponseEnum apiResponseEnum, String message, Object detail) { + ApiResult result = new ApiResult(apiResponseEnum, message, detail); + return ResponseEntity.status(apiResponseEnum.getHttpStatus()).body(result); + } } diff --git a/src/main/java/com/flab/stargram/entity/dto/FollowRequestDto.java b/src/main/java/com/flab/stargram/entity/dto/FollowRequestDto.java index 78ec280..6ac0651 100644 --- a/src/main/java/com/flab/stargram/entity/dto/FollowRequestDto.java +++ b/src/main/java/com/flab/stargram/entity/dto/FollowRequestDto.java @@ -1,21 +1,13 @@ package com.flab.stargram.entity.dto; -import com.flab.stargram.entity.common.ApiResponseEnum; -import com.flab.stargram.entity.common.BaseDto; -import com.flab.stargram.entity.common.ValidationResult; - +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Positive; import lombok.Getter; @Getter -public class FollowRequestDto extends BaseDto { +public class FollowRequestDto { + @NotNull(message = "팔로우할 사용자 ID는 필수 입니다.") + @Positive(message = "사용자 ID는 양수로 입력해야 합니다.") private Long followingId; - - @Override - public ValidationResult validateEmpty() { - if (isFieldEmpty(followingId)) { - validationResult.addError(ApiResponseEnum.EMPTY_FOLLOWING_ID); - } - - return validationResult; - } } From 07328144e0e33e80729cf846dfff45dec8a3b5c8 Mon Sep 17 00:00:00 2001 From: gru Date: Mon, 10 Feb 2025 17:03:01 +0900 Subject: [PATCH 03/27] =?UTF-8?q?=EC=83=81=EC=88=98->=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/flab/stargram/config/KeyPairConfig.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/flab/stargram/config/KeyPairConfig.java b/src/main/java/com/flab/stargram/config/KeyPairConfig.java index 268f8ea..39270f9 100644 --- a/src/main/java/com/flab/stargram/config/KeyPairConfig.java +++ b/src/main/java/com/flab/stargram/config/KeyPairConfig.java @@ -8,11 +8,13 @@ @Configuration public class KeyPairConfig { + private static final String KEY_ALGORITHM = "RSA"; + private static final int RSA_KEY_SIZE = 2048; @Bean public KeyPair keyPair() throws Exception { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(2048); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM); + keyPairGenerator.initialize(RSA_KEY_SIZE); return keyPairGenerator.generateKeyPair(); } } \ No newline at end of file From 47a9a0f39e2b65b613e58707ba5e2ff2e361b810 Mon Sep 17 00:00:00 2001 From: gru Date: Mon, 10 Feb 2025 17:03:25 +0900 Subject: [PATCH 04/27] =?UTF-8?q?FollowGroup=20Transactional=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/flab/stargram/service/FollowGroupService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/flab/stargram/service/FollowGroupService.java b/src/main/java/com/flab/stargram/service/FollowGroupService.java index 07ab0c2..df0cddc 100644 --- a/src/main/java/com/flab/stargram/service/FollowGroupService.java +++ b/src/main/java/com/flab/stargram/service/FollowGroupService.java @@ -1,6 +1,7 @@ package com.flab.stargram.service; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.flab.stargram.entity.model.FollowGroup; import com.flab.stargram.repository.FollowGroupRepository; @@ -16,6 +17,7 @@ public boolean hasFollow(Long userId) { return followGroupRepository.existsByFollowingId(userId); } + @Transactional public FollowGroup getOrCreateFollowGroup(long followingId) { FollowGroup followGroup = followGroupRepository.findByFollowingId(followingId); if (followGroup == null) { From c4ca026ddc7755188b5146a2175f7572a13e5e8b Mon Sep 17 00:00:00 2001 From: gru Date: Mon, 10 Feb 2025 17:03:47 +0900 Subject: [PATCH 05/27] =?UTF-8?q?[AUTH]=20token=20=EB=B3=80=EC=A1=B0=20?= =?UTF-8?q?=EC=8B=9C=20Exception=20=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/domain/auth/service/AuthInterceptorService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/flab/stargram/domain/auth/service/AuthInterceptorService.java b/src/main/java/com/flab/stargram/domain/auth/service/AuthInterceptorService.java index 77de1cd..c6100ae 100644 --- a/src/main/java/com/flab/stargram/domain/auth/service/AuthInterceptorService.java +++ b/src/main/java/com/flab/stargram/domain/auth/service/AuthInterceptorService.java @@ -5,7 +5,9 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.SignatureException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -28,7 +30,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons try { request.setAttribute("userId", authService.validateToken(token).getSubject()); - } catch (ExpiredJwtException e) { + } catch (SignatureException | ExpiredJwtException e) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } From 3f4a4639a41fe67a42f96295e3a5ae385fc5ed05 Mon Sep 17 00:00:00 2001 From: gru Date: Mon, 10 Feb 2025 17:04:18 +0900 Subject: [PATCH 06/27] =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EB=B3=80=EC=88=98=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/flab/stargram/controller/UserController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/flab/stargram/controller/UserController.java b/src/main/java/com/flab/stargram/controller/UserController.java index 6aa2a6d..ecf5dcb 100644 --- a/src/main/java/com/flab/stargram/controller/UserController.java +++ b/src/main/java/com/flab/stargram/controller/UserController.java @@ -25,7 +25,7 @@ public class UserController { private final AuthCookieService authCookieService; @PostMapping("/signup") - public ResponseEntity signUp(@RequestBody SignUpRequestDto dto, HttpServletResponse response) { + public ResponseEntity signUp(@RequestBody SignUpRequestDto dto) { dto.validateEmpty().ifInvalidThrow(); return ApiResult.success(userService.signUp(dto)); From 455111243367174fb73363ca375e469b5ac70603 Mon Sep 17 00:00:00 2001 From: gru Date: Mon, 10 Feb 2025 17:04:36 +0900 Subject: [PATCH 07/27] =?UTF-8?q?[UTIL]=20ParseUtil=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/entity/common/ParseUtil.java | 6 +++-- .../stargram/entity/common/ParseUtilTest.java | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/flab/stargram/entity/common/ParseUtil.java b/src/main/java/com/flab/stargram/entity/common/ParseUtil.java index 4b8d49a..4812961 100644 --- a/src/main/java/com/flab/stargram/entity/common/ParseUtil.java +++ b/src/main/java/com/flab/stargram/entity/common/ParseUtil.java @@ -1,14 +1,16 @@ package com.flab.stargram.entity.common; +import java.util.regex.Pattern; + import com.flab.stargram.config.exception.InvalidInputException; public class ParseUtil { - private static final String parseLongRegex = "^[0-9]+$"; + private static final Pattern parseLongPattern = Pattern.compile("^[0-9]+$"); public static Long parseToLong(String value) { assert value != null : "Input value is null"; - if (value.matches(parseLongRegex)) { + if (parseLongPattern.matcher(value).matches()) { return Long.parseLong(value); } else { throw new InvalidInputException(ApiResponseEnum.INVALID_INPUT); diff --git a/src/test/java/com/flab/stargram/entity/common/ParseUtilTest.java b/src/test/java/com/flab/stargram/entity/common/ParseUtilTest.java index 61258ca..927f390 100644 --- a/src/test/java/com/flab/stargram/entity/common/ParseUtilTest.java +++ b/src/test/java/com/flab/stargram/entity/common/ParseUtilTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import com.flab.stargram.config.exception.InvalidInputException; @@ -15,6 +16,7 @@ void parseLong() { assertEquals(expectLong, ParseUtil.parseToLong(input)); } + @DisplayName("문자열이 들어왔을경우 에러 반환 테스트") @Test void parseString() { String input = "abc"; @@ -25,6 +27,19 @@ void parseString() { } + @DisplayName("숫자와 L이 함께 들어왔을경우 에러 반환 테스트") + @Test + void parseLongString() { + String input = "365L"; + + assertThrows(InvalidInputException.class, () -> { + ParseUtil.parseToLong(input); + }); + + } + + + @DisplayName("문자열과 숫자가 섞여 들어왔을경우 에러 반환 테스트") @Test void parseStringWithLong() { String input = "a2b1c4"; @@ -33,4 +48,14 @@ void parseStringWithLong() { ParseUtil.parseToLong(input); }); } + + @DisplayName("빈 문자열이 들어왔을경우 에러 반환 테스트") + @Test + void parseEmptyString() { + String input = ""; + + assertThrows(InvalidInputException.class, () -> { + ParseUtil.parseToLong(input); + }); + } } \ No newline at end of file From 5173c13914c07e39051dff9d9d055873d5a6fc3b Mon Sep 17 00:00:00 2001 From: gru Date: Mon, 10 Feb 2025 18:13:56 +0900 Subject: [PATCH 08/27] =?UTF-8?q?FollowPair=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=8F=20FollowDto=20=EB=A1=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/controller/FollowController.java | 19 +++----- .../flab/stargram/entity/dto/FollowDto.java | 18 ++++++++ .../flab/stargram/entity/dto/FollowPair.java | 22 ---------- .../stargram/entity/dto/FollowRequestDto.java | 13 ------ .../flab/stargram/entity/model/Follow.java | 6 +-- .../stargram/service/FollowGroupService.java | 11 ++--- .../flab/stargram/service/FollowService.java | 44 +++++++++---------- .../stargram/service/FollowServiceTest.java | 16 +++---- 8 files changed, 63 insertions(+), 86 deletions(-) create mode 100644 src/main/java/com/flab/stargram/entity/dto/FollowDto.java delete mode 100644 src/main/java/com/flab/stargram/entity/dto/FollowPair.java delete mode 100644 src/main/java/com/flab/stargram/entity/dto/FollowRequestDto.java diff --git a/src/main/java/com/flab/stargram/controller/FollowController.java b/src/main/java/com/flab/stargram/controller/FollowController.java index ad64886..284185e 100644 --- a/src/main/java/com/flab/stargram/controller/FollowController.java +++ b/src/main/java/com/flab/stargram/controller/FollowController.java @@ -11,37 +11,32 @@ import com.flab.stargram.entity.common.ApiResult; import com.flab.stargram.entity.common.ParseUtil; -import com.flab.stargram.entity.dto.FollowPair; -import com.flab.stargram.entity.dto.FollowRequestDto; +import com.flab.stargram.entity.dto.FollowDto; import com.flab.stargram.service.FollowService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RestController -@RequestMapping("/users/{inputUserId}") +@RequestMapping("/users") @RequiredArgsConstructor @Validated public class FollowController { private final FollowService followService; @PostMapping("/follow") - public ResponseEntity followUser(@Valid @RequestBody FollowRequestDto dto, @PathVariable String inputUserId) { - FollowPair followPair = FollowPair.createFollowPairOf(inputUserId, dto); - - followService.followUser(followPair); + public ResponseEntity followUser(@Valid @RequestBody FollowDto dto) { + followService.followUser(dto); return ApiResult.success(null); } @PostMapping("/unfollow") - public ResponseEntity unfollowUser(@Valid @RequestBody FollowRequestDto dto, @PathVariable String inputUserId) { - FollowPair followPair = FollowPair.createFollowPairOf(inputUserId, dto); - - followService.unfollowUser(followPair); + public ResponseEntity unfollowUser(@Valid @RequestBody FollowDto dto) { + followService.unfollowUser(dto); return ApiResult.success(null); } - @GetMapping("/followers") + @GetMapping("/{inputUserId}/followers") public ResponseEntity followUsers(@PathVariable String inputUserId) { Long userId = ParseUtil.parseToLong(inputUserId); diff --git a/src/main/java/com/flab/stargram/entity/dto/FollowDto.java b/src/main/java/com/flab/stargram/entity/dto/FollowDto.java new file mode 100644 index 0000000..5ca1642 --- /dev/null +++ b/src/main/java/com/flab/stargram/entity/dto/FollowDto.java @@ -0,0 +1,18 @@ +package com.flab.stargram.entity.dto; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class FollowDto { + @NotNull(message = "following ID는 필수 입니다.") + @Positive(message = "following ID는 양수로 입력해야 합니다.") + private Long followingId; + + @NotNull(message = "follower ID는 필수 입니다.") + @Positive(message = "follower ID는 양수로 입력해야 합니다.") + private Long followerId; +} diff --git a/src/main/java/com/flab/stargram/entity/dto/FollowPair.java b/src/main/java/com/flab/stargram/entity/dto/FollowPair.java deleted file mode 100644 index f8dd194..0000000 --- a/src/main/java/com/flab/stargram/entity/dto/FollowPair.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.flab.stargram.entity.dto; - -import com.flab.stargram.entity.common.ParseUtil; - -import lombok.Builder; -import lombok.Getter; - -@Builder -@Getter -public class FollowPair { - private Long followerId; - private Long followingId; - - private FollowPair(Long followerId, Long followingId) { - this.followerId = followerId; - this.followingId = followingId; - } - - public static FollowPair createFollowPairOf(String followerId, FollowRequestDto followRequestDto) { - return new FollowPair(ParseUtil.parseToLong(followerId), followRequestDto.getFollowingId()); - } -} diff --git a/src/main/java/com/flab/stargram/entity/dto/FollowRequestDto.java b/src/main/java/com/flab/stargram/entity/dto/FollowRequestDto.java deleted file mode 100644 index 6ac0651..0000000 --- a/src/main/java/com/flab/stargram/entity/dto/FollowRequestDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.flab.stargram.entity.dto; - -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Positive; -import lombok.Getter; - -@Getter -public class FollowRequestDto { - @NotNull(message = "팔로우할 사용자 ID는 필수 입니다.") - @Positive(message = "사용자 ID는 양수로 입력해야 합니다.") - private Long followingId; -} diff --git a/src/main/java/com/flab/stargram/entity/model/Follow.java b/src/main/java/com/flab/stargram/entity/model/Follow.java index 7c10a52..7d91708 100644 --- a/src/main/java/com/flab/stargram/entity/model/Follow.java +++ b/src/main/java/com/flab/stargram/entity/model/Follow.java @@ -1,7 +1,7 @@ package com.flab.stargram.entity.model; import com.flab.stargram.entity.common.BaseEntity; -import com.flab.stargram.entity.dto.FollowPair; +import com.flab.stargram.entity.dto.FollowDto; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -36,7 +36,7 @@ private Follow(FollowGroup followGroup, Long followerId, Long followingId) { this.followingId = followingId; } - public static Follow createFollowOf(FollowGroup followGroup, FollowPair followPair) { - return new Follow(followGroup, followPair.getFollowerId(), followPair.getFollowingId()); + public static Follow createFollowOf(FollowGroup followGroup, FollowDto followDto) { + return new Follow(followGroup, followDto.getFollowerId(), followDto.getFollowingId()); } } diff --git a/src/main/java/com/flab/stargram/service/FollowGroupService.java b/src/main/java/com/flab/stargram/service/FollowGroupService.java index df0cddc..e33ebb8 100644 --- a/src/main/java/com/flab/stargram/service/FollowGroupService.java +++ b/src/main/java/com/flab/stargram/service/FollowGroupService.java @@ -3,6 +3,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.flab.stargram.entity.dto.FollowDto; import com.flab.stargram.entity.model.FollowGroup; import com.flab.stargram.repository.FollowGroupRepository; @@ -18,16 +19,16 @@ public boolean hasFollow(Long userId) { } @Transactional - public FollowGroup getOrCreateFollowGroup(long followingId) { - FollowGroup followGroup = followGroupRepository.findByFollowingId(followingId); + public FollowGroup getOrCreateFollowGroup(FollowDto followDto) { + FollowGroup followGroup = followGroupRepository.findByFollowingId(followDto.getFollowingId()); if (followGroup == null) { - followGroup = createFollowGroup(followingId); + followGroup = createFollowGroup(followDto); } return followGroup; } - private FollowGroup createFollowGroup(long followingId) { - FollowGroup followGroup = FollowGroup.create(followingId); + private FollowGroup createFollowGroup(FollowDto followDto) { + FollowGroup followGroup = FollowGroup.create(followDto.getFollowingId()); return followGroupRepository.save(followGroup); } } diff --git a/src/main/java/com/flab/stargram/service/FollowService.java b/src/main/java/com/flab/stargram/service/FollowService.java index 1440314..6c687c8 100644 --- a/src/main/java/com/flab/stargram/service/FollowService.java +++ b/src/main/java/com/flab/stargram/service/FollowService.java @@ -8,8 +8,8 @@ import com.flab.stargram.config.exception.DataNotFoundException; import com.flab.stargram.config.exception.DuplicateException; import com.flab.stargram.entity.common.ApiResponseEnum; +import com.flab.stargram.entity.dto.FollowDto; import com.flab.stargram.entity.model.Follow; -import com.flab.stargram.entity.dto.FollowPair; import com.flab.stargram.repository.FollowRepository; import lombok.RequiredArgsConstructor; @@ -22,19 +22,19 @@ public class FollowService { private final FollowGroupService followGroupService; @Transactional - public void followUser(FollowPair follow) { - validateFollowPair(follow); + public void followUser(FollowDto follow) { + validateFollowDto(follow); checkDuplicateFollow(follow); - followRepository.save(Follow.createFollowOf(followGroupService.getOrCreateFollowGroup(follow.getFollowingId()), follow)); + followRepository.save(Follow.createFollowOf(followGroupService.getOrCreateFollowGroup(follow), follow)); } @Transactional - public void unfollowUser(FollowPair followPair) { - validateFollowPair(followPair); - validateFollowing(followPair); + public void unfollowUser(FollowDto followDto) { + validateFollowDto(followDto); + validateFollowing(followDto); - deleteFollow(followPair); + deleteFollow(followDto); } @Transactional @@ -43,40 +43,40 @@ public List getFollowers(Long userId) { throw new DataNotFoundException(ApiResponseEnum.USER_NOT_FOUND); } - if(!followGroupService.hasFollow(userId)) { + if (!followGroupService.hasFollow(userId)) { throw new DataNotFoundException(ApiResponseEnum.FOLLOW_NOT_FOUND); } return followRepository.findByFollowerId(userId); } - private void validateFollowPair(FollowPair followPair) { - userService.hasUserId(followPair.getFollowerId()); - userService.hasUserId(followPair.getFollowingId()); + private void validateFollowDto(FollowDto followDto) { + userService.hasUserId(followDto.getFollowerId()); + userService.hasUserId(followDto.getFollowingId()); } - private void checkDuplicateFollow(FollowPair followPair) { - if (hasFollow(followPair)) { + private void checkDuplicateFollow(FollowDto followDto) { + if (hasFollow(followDto)) { throw new DuplicateException(ApiResponseEnum.ALREADY_FOLLOWING); } - if(followPair.getFollowerId().equals(followPair.getFollowingId())) { + if (followDto.getFollowerId().equals(followDto.getFollowingId())) { throw new DuplicateException(ApiResponseEnum.ALREADY_FOLLOWING); } } - private boolean hasFollow(FollowPair followPair) { - return followRepository.existsByFollowerIdAndFollowingId(followPair.getFollowerId(), - followPair.getFollowingId()); + private boolean hasFollow(FollowDto followDto) { + return followRepository.existsByFollowerIdAndFollowingId(followDto.getFollowerId(), + followDto.getFollowingId()); } - private void validateFollowing(FollowPair followPair) { - if (!hasFollow(followPair)) { + private void validateFollowing(FollowDto followDto) { + if (!hasFollow(followDto)) { throw new DataNotFoundException(ApiResponseEnum.FOLLOW_NOT_FOUND); } } - private void deleteFollow(FollowPair followPair) { - followRepository.deleteByFollowerIdAndFollowingId(followPair.getFollowerId(), followPair.getFollowingId()); + private void deleteFollow(FollowDto followDto) { + followRepository.deleteByFollowerIdAndFollowingId(followDto.getFollowerId(), followDto.getFollowingId()); } } \ No newline at end of file diff --git a/src/test/java/com/flab/stargram/service/FollowServiceTest.java b/src/test/java/com/flab/stargram/service/FollowServiceTest.java index 8fe5bb6..2c42122 100644 --- a/src/test/java/com/flab/stargram/service/FollowServiceTest.java +++ b/src/test/java/com/flab/stargram/service/FollowServiceTest.java @@ -1,6 +1,5 @@ package com.flab.stargram.service; -import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import org.junit.jupiter.api.BeforeEach; @@ -9,10 +8,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import com.flab.stargram.entity.dto.FollowPair; +import com.flab.stargram.entity.dto.FollowDto; import com.flab.stargram.entity.model.Follow; import com.flab.stargram.entity.model.FollowGroup; -import com.flab.stargram.repository.FollowGroupRepository; import com.flab.stargram.repository.FollowRepository; class FollowServiceTest { @@ -34,18 +32,18 @@ void setUp() { @Test void followUser() { //given - FollowPair followPair = FollowPair.builder() + FollowDto followDto = FollowDto.builder() .followingId(1L) .followerId(2L) .build(); //when - when(userService.hasUserId(followPair.getFollowerId())).thenReturn(true); - when(userService.hasUserId(followPair.getFollowingId())).thenReturn(true); - when(followGroupService.getOrCreateFollowGroup(followPair.getFollowingId())).thenReturn(mock(FollowGroup.class)); - when(followRepository.existsByFollowerIdAndFollowingId(followPair.getFollowerId(), followPair.getFollowingId())).thenReturn(false); + when(userService.hasUserId(followDto.getFollowerId())).thenReturn(true); + when(userService.hasUserId(followDto.getFollowingId())).thenReturn(true); + when(followGroupService.getOrCreateFollowGroup(followDto)).thenReturn(mock(FollowGroup.class)); + when(followRepository.existsByFollowerIdAndFollowingId(followDto.getFollowerId(), followDto.getFollowingId())).thenReturn(false); - followService.followUser(followPair); + followService.followUser(followDto); //then verify(followRepository, times(1)).save(any(Follow.class)); From 27fa715f52e0f8ad28b4d3a9382713fa18b15f83 Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 12:06:25 +0900 Subject: [PATCH 09/27] =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=EC=99=80=20=EB=8F=99=EC=9D=BC=20=EA=B5=AC=EC=A1=B0=EB=A1=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20dto=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/entity/dto/FollowDtoTest.java | 96 +++++++++++++++++++ .../entity/{model => dto}/LoginDtoTest.java | 3 +- .../{model => dto}/SignUpRequestDtoTest.java | 3 +- .../stargram/entity/dto/ValidatorTest.java | 17 ++++ 4 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java rename src/test/java/com/flab/stargram/entity/{model => dto}/LoginDtoTest.java (91%) rename src/test/java/com/flab/stargram/entity/{model => dto}/SignUpRequestDtoTest.java (91%) create mode 100644 src/test/java/com/flab/stargram/entity/dto/ValidatorTest.java diff --git a/src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java b/src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java new file mode 100644 index 0000000..3ffef0b --- /dev/null +++ b/src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java @@ -0,0 +1,96 @@ +package com.flab.stargram.entity.dto; +import static org.assertj.core.api.Assertions.*; + +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import jakarta.validation.ConstraintViolation; + +class FollowDtoTest extends ValidatorTest{ + + @DisplayName("정상 케이스") + @Test + public void testValidFollowDto() { + FollowDto dto = FollowDto.builder() + .followingId(1L) + .followerId(2L) + .build(); + + Set> violations = validator.validate(dto); + assertThat(violations).isEmpty(); + } + + @DisplayName("followingID가 null인 경우 검증 오류 발생") + @Test + public void testInvalidFollowDto_FollowingIdIsNull() { + FollowDto dto = FollowDto.builder() + .followingId(null) + .followerId(2L) + .build(); + + Set> violations = validator.validate(dto); + + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("following ID는 필수 입니다."); + } + + @DisplayName("followingId와 followerId가 모두 null인 경우 두 개의 검증 오류 발생") + @Test + void bothFollowingIdAndFollowerIdAreNull() { + FollowDto dto = FollowDto.builder() + .followingId(null) + .followerId(null) + .build(); + + Set> violations = validator.validate(dto); + + assertThat(violations).hasSize(2); + assertThat(violations.stream().map(ConstraintViolation::getMessage)) + .contains("following ID는 필수 입니다.", "follower ID는 필수 입니다."); + } + + @DisplayName("followingId가 음수인 경우 검증 오류 발생") + @Test + void followingIdIsNegative() { + FollowDto dto = FollowDto.builder() + .followingId(-1L) + .followerId(2L) + .build(); + + Set> violations = validator.validate(dto); + + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("following ID는 양수로 입력해야 합니다."); + } + + @DisplayName("followerId가 null인 경우 검증 오류 발생") + @Test + void followerIdIsNull() { + FollowDto dto = FollowDto.builder() + .followingId(1L) + .followerId(null) + .build(); + + Set> violations = validator.validate(dto); + + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("follower ID는 필수 입니다."); + } + + @DisplayName("followerId가 음수인 경우 검증 오류 발생") + @Test + void followerIdIsNegative() { + FollowDto dto = FollowDto.builder() + .followingId(1L) + .followerId(-2L) + .build(); + + Set> violations = validator.validate(dto); + + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("follower ID는 양수로 입력해야 합니다."); + } + +} \ No newline at end of file diff --git a/src/test/java/com/flab/stargram/entity/model/LoginDtoTest.java b/src/test/java/com/flab/stargram/entity/dto/LoginDtoTest.java similarity index 91% rename from src/test/java/com/flab/stargram/entity/model/LoginDtoTest.java rename to src/test/java/com/flab/stargram/entity/dto/LoginDtoTest.java index d666528..883a656 100644 --- a/src/test/java/com/flab/stargram/entity/model/LoginDtoTest.java +++ b/src/test/java/com/flab/stargram/entity/dto/LoginDtoTest.java @@ -1,4 +1,4 @@ -package com.flab.stargram.entity.model; +package com.flab.stargram.entity.dto; import static org.assertj.core.api.Assertions.*; @@ -6,7 +6,6 @@ import com.flab.stargram.entity.common.ApiResponseEnum; import com.flab.stargram.entity.common.ValidationResult; -import com.flab.stargram.entity.dto.LoginDto; class LoginDtoTest { diff --git a/src/test/java/com/flab/stargram/entity/model/SignUpRequestDtoTest.java b/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java similarity index 91% rename from src/test/java/com/flab/stargram/entity/model/SignUpRequestDtoTest.java rename to src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java index 97c9aa0..dffcbc9 100644 --- a/src/test/java/com/flab/stargram/entity/model/SignUpRequestDtoTest.java +++ b/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java @@ -1,4 +1,4 @@ -package com.flab.stargram.entity.model; +package com.flab.stargram.entity.dto; import static org.assertj.core.api.Assertions.*; @@ -6,7 +6,6 @@ import com.flab.stargram.entity.common.ApiResponseEnum; import com.flab.stargram.entity.common.ValidationResult; -import com.flab.stargram.entity.dto.SignUpRequestDto; class SignUpRequestDtoTest { diff --git a/src/test/java/com/flab/stargram/entity/dto/ValidatorTest.java b/src/test/java/com/flab/stargram/entity/dto/ValidatorTest.java new file mode 100644 index 0000000..49c030d --- /dev/null +++ b/src/test/java/com/flab/stargram/entity/dto/ValidatorTest.java @@ -0,0 +1,17 @@ +package com.flab.stargram.entity.dto; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.junit.jupiter.api.BeforeEach; + + +abstract class ValidatorTest { + + protected Validator validator; + + @BeforeEach + public void setUpValidator() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } +} From bc3b0cbddb641859df0e99d1dd8c49177bba8f8a Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 12:06:48 +0900 Subject: [PATCH 10/27] =?UTF-8?q?[TEST]=20followServiceTest=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/service/FollowServiceTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/test/java/com/flab/stargram/service/FollowServiceTest.java b/src/test/java/com/flab/stargram/service/FollowServiceTest.java index 2c42122..a9849d4 100644 --- a/src/test/java/com/flab/stargram/service/FollowServiceTest.java +++ b/src/test/java/com/flab/stargram/service/FollowServiceTest.java @@ -2,7 +2,10 @@ import static org.mockito.Mockito.*; +import java.util.Collections; + import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -29,6 +32,7 @@ void setUp() { MockitoAnnotations.openMocks(this); } + @DisplayName("follow API 통합테스트") @Test void followUser() { //given @@ -49,11 +53,44 @@ void followUser() { verify(followRepository, times(1)).save(any(Follow.class)); } + + @DisplayName("unfollow API 통합테스트") @Test void unfollowUser() { + //given + FollowDto followDto = FollowDto.builder() + .followingId(1L) + .followerId(2L) + .build(); + + //when + when(userService.hasUserId(followDto.getFollowerId())).thenReturn(true); + when(userService.hasUserId(followDto.getFollowingId())).thenReturn(true); + when(followRepository.existsByFollowerIdAndFollowingId(followDto.getFollowerId(), + followDto.getFollowingId())).thenReturn(true); + doNothing().when(followRepository).deleteByFollowerIdAndFollowingId(followDto.getFollowerId(), followDto.getFollowingId()); + + followService.unfollowUser(followDto); + + //then + verify(followRepository, times(1)).deleteByFollowerIdAndFollowingId(followDto.getFollowerId(), followDto.getFollowingId()); } + + @DisplayName("특정 유저의 팔로워 들을 불러오는 API 통합테스트") @Test void getFollowers() { + //given + Long userId = 1L; + + //then + when(userService.hasUserId(userId)).thenReturn(true); + when(followGroupService.hasFollow(userId)).thenReturn(true); + when(followRepository.findByFollowerId(userId)).thenReturn(Collections.emptyList()); + + followService.getFollowers(userId); + + //then + verify(followRepository, times(1)).findByFollowerId(userId); } } \ No newline at end of file From bf263b7b8402f5011dfe70d21cf0d638c6adf4f8 Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 13:42:06 +0900 Subject: [PATCH 11/27] =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20Re?= =?UTF-8?q?sponseEntity=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/flab/stargram/controller/CommentController.java | 2 +- .../com/flab/stargram/controller/FollowController.java | 8 +++----- .../java/com/flab/stargram/controller/PostController.java | 2 +- .../java/com/flab/stargram/controller/UserController.java | 6 +++--- .../java/com/flab/stargram/entity/common/ApiResult.java | 5 ++--- .../com/flab/stargram/entity/dto/SignUpRequestDto.java | 1 + src/main/java/com/flab/stargram/entity/model/User.java | 2 ++ 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/flab/stargram/controller/CommentController.java b/src/main/java/com/flab/stargram/controller/CommentController.java index 79b62d7..a9c6a95 100644 --- a/src/main/java/com/flab/stargram/controller/CommentController.java +++ b/src/main/java/com/flab/stargram/controller/CommentController.java @@ -22,7 +22,7 @@ public class CommentController { private final CommentService commentService; @PostMapping("/{postIdInput}/comment") - public ResponseEntity createComment(@RequestBody CommentRequestDto dto, @PathVariable String postIdInput) { + public ApiResult createComment(@RequestBody CommentRequestDto dto, @PathVariable String postIdInput) { dto.validateEmpty().ifInvalidThrow(); Long postId = ParseUtil.parseToLong(postIdInput); diff --git a/src/main/java/com/flab/stargram/controller/FollowController.java b/src/main/java/com/flab/stargram/controller/FollowController.java index 284185e..e8cc808 100644 --- a/src/main/java/com/flab/stargram/controller/FollowController.java +++ b/src/main/java/com/flab/stargram/controller/FollowController.java @@ -1,6 +1,5 @@ package com.flab.stargram.controller; -import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -25,21 +24,20 @@ public class FollowController { private final FollowService followService; @PostMapping("/follow") - public ResponseEntity followUser(@Valid @RequestBody FollowDto dto) { + public ApiResult followUser(@Valid @RequestBody FollowDto dto) { followService.followUser(dto); return ApiResult.success(null); } @PostMapping("/unfollow") - public ResponseEntity unfollowUser(@Valid @RequestBody FollowDto dto) { + public ApiResult unfollowUser(@Valid @RequestBody FollowDto dto) { followService.unfollowUser(dto); return ApiResult.success(null); } @GetMapping("/{inputUserId}/followers") - public ResponseEntity followUsers(@PathVariable String inputUserId) { + public ApiResult followUsers(@PathVariable String inputUserId) { Long userId = ParseUtil.parseToLong(inputUserId); - return ApiResult.success(followService.getFollowers(userId)); } } diff --git a/src/main/java/com/flab/stargram/controller/PostController.java b/src/main/java/com/flab/stargram/controller/PostController.java index 5d35a95..407a4ff 100644 --- a/src/main/java/com/flab/stargram/controller/PostController.java +++ b/src/main/java/com/flab/stargram/controller/PostController.java @@ -21,7 +21,7 @@ public class PostController { private final PostService postService; @PostMapping - public ResponseEntity createPost(@RequestBody PostRequestDto dto) { + public ApiResult createPost(@RequestBody PostRequestDto dto) { dto.validateEmpty().ifInvalidThrow(); return ApiResult.success(postService.postFeed(dto)); diff --git a/src/main/java/com/flab/stargram/controller/UserController.java b/src/main/java/com/flab/stargram/controller/UserController.java index ecf5dcb..b9a2ee5 100644 --- a/src/main/java/com/flab/stargram/controller/UserController.java +++ b/src/main/java/com/flab/stargram/controller/UserController.java @@ -25,14 +25,14 @@ public class UserController { private final AuthCookieService authCookieService; @PostMapping("/signup") - public ResponseEntity signUp(@RequestBody SignUpRequestDto dto) { + public ApiResult signUp(@RequestBody SignUpRequestDto dto) { dto.validateEmpty().ifInvalidThrow(); return ApiResult.success(userService.signUp(dto)); } @PostMapping("/login") - public ResponseEntity login(@RequestBody LoginDto dto, HttpServletResponse response) { + public ApiResult login(@RequestBody LoginDto dto, HttpServletResponse response) { dto.validateEmpty().ifInvalidThrow(); User user = userService.login(dto); @@ -43,7 +43,7 @@ public ResponseEntity login(@RequestBody LoginDto dto, HttpServletRes } @PostMapping("/users/logout") - public ResponseEntity logout(HttpServletResponse response) { + public ApiResult logout(HttpServletResponse response) { authCookieService.removeAuthCookie(response); return ApiResult.success(null); } diff --git a/src/main/java/com/flab/stargram/entity/common/ApiResult.java b/src/main/java/com/flab/stargram/entity/common/ApiResult.java index 0a85ecb..38f74fb 100644 --- a/src/main/java/com/flab/stargram/entity/common/ApiResult.java +++ b/src/main/java/com/flab/stargram/entity/common/ApiResult.java @@ -15,9 +15,8 @@ public class ApiResult { private final String message; private final Object data; - public static ResponseEntity success(Object data) { - ApiResult result = new ApiResult(ApiResponseEnum.SUCCESS, null, data); - return ResponseEntity.ok(result); + public static ApiResult success(Object data) { + return new ApiResult(ApiResponseEnum.SUCCESS, null, data); } public static ResponseEntity error(ApiResponseEnum apiResponseEnum, String message) { diff --git a/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java b/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java index 494c6f3..aaf237d 100644 --- a/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java +++ b/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java @@ -4,6 +4,7 @@ import com.flab.stargram.entity.common.BaseDto; import com.flab.stargram.entity.common.ValidationResult; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/flab/stargram/entity/model/User.java b/src/main/java/com/flab/stargram/entity/model/User.java index 5aa4cd0..5150f79 100644 --- a/src/main/java/com/flab/stargram/entity/model/User.java +++ b/src/main/java/com/flab/stargram/entity/model/User.java @@ -7,6 +7,7 @@ import com.flab.stargram.entity.dto.SignUpRequestDto; import jakarta.persistence.*; +import jakarta.validation.constraints.Email; import lombok.*; @Entity @@ -20,6 +21,7 @@ public class User extends BaseEntity { @Column(unique = true, nullable = false) private String userName; + @Email @Column(unique = true, nullable = false) private String email; From f80aec2e6b3d123628fc3882f756020eb1007d13 Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 13:43:03 +0900 Subject: [PATCH 12/27] =?UTF-8?q?[BUG]=20GetFollower=20=ED=8C=94=EB=A1=9C?= =?UTF-8?q?=EC=9B=8C=20=EB=B6=88=EB=9F=AC=EC=98=A4=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flab/stargram/controller/FollowController.java | 3 +-- .../java/com/flab/stargram/entity/dto/FollowDto.java | 4 ++++ .../flab/stargram/repository/FollowRepository.java | 2 +- .../com/flab/stargram/service/FollowService.java | 12 +++++++++--- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/flab/stargram/controller/FollowController.java b/src/main/java/com/flab/stargram/controller/FollowController.java index e8cc808..bb43e9f 100644 --- a/src/main/java/com/flab/stargram/controller/FollowController.java +++ b/src/main/java/com/flab/stargram/controller/FollowController.java @@ -37,7 +37,6 @@ public ApiResult unfollowUser(@Valid @RequestBody FollowDto dto) { @GetMapping("/{inputUserId}/followers") public ApiResult followUsers(@PathVariable String inputUserId) { - Long userId = ParseUtil.parseToLong(inputUserId); - return ApiResult.success(followService.getFollowers(userId)); + return ApiResult.success(followService.getFollowers(ParseUtil.parseToLong(inputUserId))); } } diff --git a/src/main/java/com/flab/stargram/entity/dto/FollowDto.java b/src/main/java/com/flab/stargram/entity/dto/FollowDto.java index 5ca1642..2507d38 100644 --- a/src/main/java/com/flab/stargram/entity/dto/FollowDto.java +++ b/src/main/java/com/flab/stargram/entity/dto/FollowDto.java @@ -15,4 +15,8 @@ public class FollowDto { @NotNull(message = "follower ID는 필수 입니다.") @Positive(message = "follower ID는 양수로 입력해야 합니다.") private Long followerId; + + public boolean isSameUser(){ + return followingId.equals(followerId); + } } diff --git a/src/main/java/com/flab/stargram/repository/FollowRepository.java b/src/main/java/com/flab/stargram/repository/FollowRepository.java index 9276c5b..b66ba7e 100644 --- a/src/main/java/com/flab/stargram/repository/FollowRepository.java +++ b/src/main/java/com/flab/stargram/repository/FollowRepository.java @@ -20,7 +20,7 @@ public interface FollowRepository extends JpaRepository { //SELECT COUNT(*) FROM follow WHERE follower_id = ?; long countByFollowerId(Long followerId); //SELECT * FROM follow WHERE follower_id = ?; - List findByFollowerId(Long followerId); + List findByFollowingId(Long followingId); //SELECT * FROM follow WHERE follower_id = ? ORDER BY id ASC LIMIT ? OffSET ?; Page findByFollowerId(Long followingId, Pageable pageable); diff --git a/src/main/java/com/flab/stargram/service/FollowService.java b/src/main/java/com/flab/stargram/service/FollowService.java index 6c687c8..4cc4524 100644 --- a/src/main/java/com/flab/stargram/service/FollowService.java +++ b/src/main/java/com/flab/stargram/service/FollowService.java @@ -38,7 +38,7 @@ public void unfollowUser(FollowDto followDto) { } @Transactional - public List getFollowers(Long userId) { + public List getFollowers(Long userId) { if (!userService.hasUserId(userId)) { throw new DataNotFoundException(ApiResponseEnum.USER_NOT_FOUND); } @@ -47,7 +47,7 @@ public List getFollowers(Long userId) { throw new DataNotFoundException(ApiResponseEnum.FOLLOW_NOT_FOUND); } - return followRepository.findByFollowerId(userId); + return getFollowerIds(followRepository.findByFollowingId(userId)); } private void validateFollowDto(FollowDto followDto) { @@ -60,7 +60,7 @@ private void checkDuplicateFollow(FollowDto followDto) { throw new DuplicateException(ApiResponseEnum.ALREADY_FOLLOWING); } - if (followDto.getFollowerId().equals(followDto.getFollowingId())) { + if (followDto.isSameUser()) { throw new DuplicateException(ApiResponseEnum.ALREADY_FOLLOWING); } } @@ -79,4 +79,10 @@ private void validateFollowing(FollowDto followDto) { private void deleteFollow(FollowDto followDto) { followRepository.deleteByFollowerIdAndFollowingId(followDto.getFollowerId(), followDto.getFollowingId()); } + + private List getFollowerIds(List follows) { + return follows.stream() + .map(Follow::getFollowerId) + .toList(); + } } \ No newline at end of file From b8249780ab025d7fa4e61509d6790081fef51b7b Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 14:16:43 +0900 Subject: [PATCH 13/27] =?UTF-8?q?=EC=9E=98=EB=AA=BB=EB=90=9C=20query=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/repository/FollowGroupRepository.java | 2 +- .../flab/stargram/repository/FollowRepository.java | 5 ++--- .../com/flab/stargram/repository/PostRepository.java | 2 +- .../com/flab/stargram/repository/UserRepository.java | 12 ++++++------ .../java/com/flab/stargram/service/PostService.java | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java b/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java index c6997e4..6ad0bd6 100644 --- a/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java +++ b/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java @@ -7,6 +7,6 @@ public interface FollowGroupRepository extends JpaRepository { //SELECT * FROM follow_group WHERE user_id = ? FollowGroup findByFollowingId(Long followingId); - //SELECT EXISTS (SELECT 1 FROM follow_group WHERE following_id= ?); + //SELECT COUNT(*) > 0 FROM follow_group WHERE following_id = ? boolean existsByFollowingId(Long followingId); } diff --git a/src/main/java/com/flab/stargram/repository/FollowRepository.java b/src/main/java/com/flab/stargram/repository/FollowRepository.java index b66ba7e..8e39d0b 100644 --- a/src/main/java/com/flab/stargram/repository/FollowRepository.java +++ b/src/main/java/com/flab/stargram/repository/FollowRepository.java @@ -11,7 +11,7 @@ @Repository public interface FollowRepository extends JpaRepository { - //SELECT EXISTS (SELECT 1 FROM follow WHERE follower_id = ? AND following_id = ?) + //SELECT COUNT(*) > 0 FROM follow WHERE follower_id = ? AND following_id = ? boolean existsByFollowerIdAndFollowingId(Long followerId, Long followingId); //DELETE FROM follow WHERE follower_id = ? AND following_id = ? void deleteByFollowerIdAndFollowingId(Long followerId, Long followingId); @@ -22,6 +22,5 @@ public interface FollowRepository extends JpaRepository { //SELECT * FROM follow WHERE follower_id = ?; List findByFollowingId(Long followingId); //SELECT * FROM follow WHERE follower_id = ? ORDER BY id ASC LIMIT ? OffSET ?; - Page findByFollowerId(Long followingId, Pageable pageable); - + List findByFollowerId(Long followerId); } diff --git a/src/main/java/com/flab/stargram/repository/PostRepository.java b/src/main/java/com/flab/stargram/repository/PostRepository.java index 1823b23..430477b 100644 --- a/src/main/java/com/flab/stargram/repository/PostRepository.java +++ b/src/main/java/com/flab/stargram/repository/PostRepository.java @@ -7,6 +7,6 @@ @Repository public interface PostRepository extends JpaRepository { - //SELECT EXISTS (SELECT 1 FROM post WHERE post_id = ?); + //SELECT COUNT(*) > 0 FROM post WHERE post_id = ? boolean existsByPostId(Long postId); } diff --git a/src/main/java/com/flab/stargram/repository/UserRepository.java b/src/main/java/com/flab/stargram/repository/UserRepository.java index ff568c2..34d2298 100644 --- a/src/main/java/com/flab/stargram/repository/UserRepository.java +++ b/src/main/java/com/flab/stargram/repository/UserRepository.java @@ -8,15 +8,15 @@ @Repository public interface UserRepository extends JpaRepository { - //SELECT * FROM user WHERE user_name = ?; + //SELECT * FROM user WHERE user_name = ? User findByUserName(String userName); - //SELECT * FROM user WHERE user_id = ?; - Optional findById(Long userId); - //SELECT EXISTS (SELECT 1 FROM user WHERE email = ?); + //SELECT * FROM user where id = ? + User findById(long id); + //SELECT COUNT(*) > 0 FROM user WHERE email = ? boolean existsByEmail(String email); - //SELECT EXISTS (SELECT 1 FROM user WHERE user_name= ?); + //SELECT COUNT(*) > 0 FROM user WHERE user_name = ? boolean existsByUserName(String userName); - //SELECT EXISTS (SELECT 1 FROM user WHERE user_id= ?); + //SELECT COUNT(*) > 0 FROM user WHERE user_id = ? boolean existsById(Long userId); //SELECT * FROM user WHERE user_name = ? OR email = ? User findByUserNameOrEmail(String userName, String email); diff --git a/src/main/java/com/flab/stargram/service/PostService.java b/src/main/java/com/flab/stargram/service/PostService.java index 1e78f10..b448100 100644 --- a/src/main/java/com/flab/stargram/service/PostService.java +++ b/src/main/java/com/flab/stargram/service/PostService.java @@ -30,7 +30,7 @@ public Post postFeed(PostRequestDto dto) { } public boolean hasPostId(Long postId) { - return postRepository.existsById(postId); + return postRepository.existsByPostId(postId); } private boolean findByUserId(PostRequestDto dto) { From fba540bec78747b8ca0299a1f4ab9e31f1ee294b Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 15:07:54 +0900 Subject: [PATCH 14/27] =?UTF-8?q?[TEST]=20LoginDto=20test=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flab/stargram/entity/dto/LoginDto.java | 3 +- .../controller/UserControllerTest.java | 54 ------------------- .../stargram/entity/dto/LoginDtoTest.java | 51 +++++++++++++++--- 3 files changed, 47 insertions(+), 61 deletions(-) delete mode 100644 src/test/java/com/flab/stargram/controller/UserControllerTest.java diff --git a/src/main/java/com/flab/stargram/entity/dto/LoginDto.java b/src/main/java/com/flab/stargram/entity/dto/LoginDto.java index 1f2803d..dfa5510 100644 --- a/src/main/java/com/flab/stargram/entity/dto/LoginDto.java +++ b/src/main/java/com/flab/stargram/entity/dto/LoginDto.java @@ -4,11 +4,12 @@ import com.flab.stargram.entity.common.BaseDto; import com.flab.stargram.entity.common.ValidationResult; +import lombok.Builder; import lombok.Getter; import lombok.Setter; +@Builder @Getter -@Setter public class LoginDto extends BaseDto { private String userName; private String password; diff --git a/src/test/java/com/flab/stargram/controller/UserControllerTest.java b/src/test/java/com/flab/stargram/controller/UserControllerTest.java deleted file mode 100644 index f31c140..0000000 --- a/src/test/java/com/flab/stargram/controller/UserControllerTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.flab.stargram.controller; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -import com.flab.stargram.entity.dto.LoginDto; -import com.flab.stargram.entity.dto.SignUpRequestDto; -import com.flab.stargram.entity.model.User; -import com.flab.stargram.service.UserService; - -@ExtendWith(MockitoExtension.class) -class UserControllerTest { - - @Mock - private UserService userService; - - private SignUpRequestDto signUpRequestDto; - private LoginDto loginDto; - private User mockUser; - - @BeforeEach - void setUp() { - signUpRequestDto = new SignUpRequestDto(); - signUpRequestDto.setUserName("gru"); - signUpRequestDto.setPassword("asdf"); - signUpRequestDto.setEmail("gru@f-lab.com"); - - loginDto = new LoginDto(); - loginDto.setUserName("gru"); - loginDto.setPassword("asdf"); - - mockUser = User.createUserOf(signUpRequestDto); - } - - @Test - void login() { - // Given - when(userService.login(any(LoginDto.class))).thenReturn(mockUser); - - // When - User loggedInUser = userService.login(loginDto); - - // Then - assertNotNull(loggedInUser); - assertEquals("gru", loggedInUser.getUserName()); - verify(userService, times(1)).login(loginDto); - } -} \ No newline at end of file diff --git a/src/test/java/com/flab/stargram/entity/dto/LoginDtoTest.java b/src/test/java/com/flab/stargram/entity/dto/LoginDtoTest.java index 883a656..62cae2e 100644 --- a/src/test/java/com/flab/stargram/entity/dto/LoginDtoTest.java +++ b/src/test/java/com/flab/stargram/entity/dto/LoginDtoTest.java @@ -2,19 +2,56 @@ import static org.assertj.core.api.Assertions.*; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import com.flab.stargram.entity.common.ApiResponseEnum; import com.flab.stargram.entity.common.ValidationResult; class LoginDtoTest { + @DisplayName("userName에 빈 데이터 입력한 경우 에러 반환") + @Test + void validateDtoEmpty_errorEmptyuserName() { + // Given + LoginDto dto = LoginDto.builder() + .userName("") + .password("1234") + .build(); + + // When + ValidationResult result = dto.validateEmpty(); + + // Then + assertThat(result.isValid()).isFalse(); + assertThat(result.getError()).isEqualTo(ApiResponseEnum.EMPTY_USERNAME); + } + + @DisplayName("userName에 null 입력한 경우 에러 반환") + @Test + void validateDtoEmpty_errorNulluserName() { + // Given + LoginDto dto = LoginDto.builder() + .userName(null) + .password("1234") + .build(); + + // When + ValidationResult result = dto.validateEmpty(); + + // Then + assertThat(result.isValid()).isFalse(); + assertThat(result.getError()).isEqualTo(ApiResponseEnum.EMPTY_USERNAME); + } + + @DisplayName("비밀번호 비어있는 데이터 입력한 경우 에러 반환") @Test void validateDtoEmpty_error() { // Given - LoginDto dto = new LoginDto(); - dto.setUserName("testUser"); - dto.setPassword(""); + LoginDto dto = LoginDto.builder() + .userName("testUser") + .password("") + .build(); // When ValidationResult result = dto.validateEmpty(); @@ -24,12 +61,14 @@ void validateDtoEmpty_error() { assertThat(result.getError()).isEqualTo(ApiResponseEnum.EMPTY_PASSWORD); } + @DisplayName("정상 케이스") @Test void validateDtoEmpty_success() { // Given - LoginDto dto = new LoginDto(); - dto.setUserName("testUser"); - dto.setPassword("123123"); + LoginDto dto = LoginDto.builder() + .userName("testUser") + .password("123123") + .build(); // When ValidationResult result = dto.validateEmpty(); From f0a478d23c16a1aab96357915babeecb124660cc Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 15:08:08 +0900 Subject: [PATCH 15/27] =?UTF-8?q?[TEST]=20SignupRequestDto=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/entity/dto/SignUpRequestDto.java | 6 ++- .../entity/dto/SignUpRequestDtoTest.java | 43 +++++++++++++++---- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java b/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java index aaf237d..352d36c 100644 --- a/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java +++ b/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java @@ -4,14 +4,18 @@ import com.flab.stargram.entity.common.BaseDto; import com.flab.stargram.entity.common.ValidationResult; +import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotNull; +import lombok.Builder; import lombok.Getter; import lombok.Setter; +@Builder @Getter -@Setter public class SignUpRequestDto extends BaseDto { private String userName; + + @Email(message = "올바른 형식의 email을 입력해주세요") private String email; private String password; diff --git a/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java b/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java index dffcbc9..130011c 100644 --- a/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java +++ b/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java @@ -2,19 +2,26 @@ import static org.assertj.core.api.Assertions.*; +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import com.flab.stargram.entity.common.ApiResponseEnum; import com.flab.stargram.entity.common.ValidationResult; -class SignUpRequestDtoTest { +import jakarta.validation.ConstraintViolation; + +class SignUpRequestDtoTest extends ValidatorTest{ + @DisplayName("빈 데이터 입력 시 에러반환 테스트") @Test void validateDtoEmpty_error() { // Given - SignUpRequestDto dto = new SignUpRequestDto(); - dto.setUserName(null); - dto.setPassword(""); + SignUpRequestDto dto = SignUpRequestDto.builder() + .userName(null) + .password("") + .build(); // When ValidationResult result = dto.validateEmpty(); @@ -24,13 +31,15 @@ void validateDtoEmpty_error() { assertThat(result.getError()).isEqualTo(ApiResponseEnum.EMPTY_USERNAME); } + @DisplayName("정상 입력 케이스") @Test void validateDtoEmpty_success() { // Given - SignUpRequestDto dto = new SignUpRequestDto(); - dto.setUserName("testUser"); - dto.setEmail("test@f-lab.com"); - dto.setPassword("123123"); + SignUpRequestDto dto = SignUpRequestDto.builder() + .userName("testUser") + .email("test@f-lab.com") + .password("123123") + .build(); // When ValidationResult result = dto.validateEmpty(); @@ -38,4 +47,22 @@ void validateDtoEmpty_success() { // Then assertThat(result.isValid()).isTrue(); } + + @DisplayName("잘못된 email 형식 에러 반환") + @Test + void validateDtoEmail_failure(){ + // Given + SignUpRequestDto dto = SignUpRequestDto.builder() + .userName("testUser") + .email("test.com") + .password("123123") + .build(); + + // when + Set> violations = validator.validate(dto); + + // then + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("올바른 형식의 email을 입력해주세요"); + } } \ No newline at end of file From 2208aed14a7544bcd2c5235002b1c21b5bf7c404 Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 15:08:21 +0900 Subject: [PATCH 16/27] =?UTF-8?q?[Test]=20UserServiceTest=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/service/UserServiceTest.java | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/test/java/com/flab/stargram/service/UserServiceTest.java diff --git a/src/test/java/com/flab/stargram/service/UserServiceTest.java b/src/test/java/com/flab/stargram/service/UserServiceTest.java new file mode 100644 index 0000000..2ebcd40 --- /dev/null +++ b/src/test/java/com/flab/stargram/service/UserServiceTest.java @@ -0,0 +1,101 @@ +package com.flab.stargram.service; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.flab.stargram.config.exception.BaseApiException; +import com.flab.stargram.config.exception.DataNotFoundException; +import com.flab.stargram.config.exception.InvalidPasswordException; +import com.flab.stargram.entity.common.ApiResponseEnum; +import com.flab.stargram.entity.dto.LoginDto; +import com.flab.stargram.entity.dto.SignUpRequestDto; +import com.flab.stargram.entity.model.User; +import com.flab.stargram.repository.UserRepository; + +@ExtendWith(MockitoExtension.class) +class UserServiceTest { + + @InjectMocks + private UserService userService; + + @Mock + private UserRepository userRepository; + + private SignUpRequestDto signUpRequestDto; + private User mockUser; + + @BeforeEach + void setUp() { + signUpRequestDto = SignUpRequestDto.builder() + .userName("gru") + .password("asdf") + .email("gru@f-lab.com") + .build(); + + mockUser = User.createUserOf(signUpRequestDto); + } + + + @DisplayName("정상 로그인 테스트") + @Test + void login() { + LoginDto loginDto = LoginDto.builder() + .userName("gru") + .password("asdf") + .build(); + // Given + when(userRepository.findByUserName(loginDto.getUserName())).thenReturn(mockUser); + + // When + User result = userService.login(loginDto); + + // Then + assertNotNull(result); + assertThat(result.getUserName()).isEqualTo("gru"); + verify(userRepository, times(1)).findByUserName(loginDto.getUserName()); + } + + @DisplayName("존재하지 않는 사용자 ID로 로그인 시 에러반환 테스트") + @Test + void login_error_NotExistId(){ + // Given + LoginDto dto = LoginDto.builder() + .userName("josh") + .password("123asd") + .build(); + + //when + when(userRepository.findByUserName(dto.getUserName())).thenReturn(null); + + + //then + DataNotFoundException exception = assertThrows(DataNotFoundException.class, () -> userService.login(dto)); + assertThat(exception.getResponseEnum()).isEqualTo(ApiResponseEnum.USER_NOT_FOUND); + } + + @DisplayName("로그인중 비밀번호 오류 시 에러 반환 테스트") + @Test + void login_error_incorrectPassword(){ + // Given + LoginDto dto = LoginDto.builder() + .userName("gru") + .password("123asd") + .build(); + + //when + when(userRepository.findByUserName(dto.getUserName())).thenReturn(mockUser); + + //then + InvalidPasswordException exception = assertThrows(InvalidPasswordException.class, () -> userService.login(dto)); + assertThat(exception.getResponseEnum()).isEqualTo(ApiResponseEnum.INVALID_PASSWORD); + } +} \ No newline at end of file From a434685dc6241692b7237c039610255e5535b4ae Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 15:43:46 +0900 Subject: [PATCH 17/27] =?UTF-8?q?[TEST]=20FollowService=20Test=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/service/FollowServiceTest.java | 71 +++++++++++++++++-- .../stargram/service/UserServiceTest.java | 1 - 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/flab/stargram/service/FollowServiceTest.java b/src/test/java/com/flab/stargram/service/FollowServiceTest.java index a9849d4..f9fd727 100644 --- a/src/test/java/com/flab/stargram/service/FollowServiceTest.java +++ b/src/test/java/com/flab/stargram/service/FollowServiceTest.java @@ -1,8 +1,11 @@ package com.flab.stargram.service; +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -11,10 +14,14 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import com.flab.stargram.config.exception.DataNotFoundException; +import com.flab.stargram.config.exception.DuplicateException; +import com.flab.stargram.entity.common.ApiResponseEnum; import com.flab.stargram.entity.dto.FollowDto; import com.flab.stargram.entity.model.Follow; import com.flab.stargram.entity.model.FollowGroup; import com.flab.stargram.repository.FollowRepository; +import com.flab.stargram.repository.UserRepository; class FollowServiceTest { @InjectMocks @@ -26,6 +33,8 @@ class FollowServiceTest { private FollowGroupService followGroupService; @Mock private FollowRepository followRepository; + @Mock + private UserRepository userRepository; @BeforeEach void setUp() { @@ -53,6 +62,43 @@ void followUser() { verify(followRepository, times(1)).save(any(Follow.class)); } + @DisplayName("follow API 중복 Follow 에러 반환 테스트") + @Test + void followUser_error_duplicate() { + //given + FollowDto followDto = FollowDto.builder() + .followingId(1L) + .followerId(2L) + .build(); + + //when + when(userRepository.existsById(followDto.getFollowerId())).thenReturn(true); + when(followRepository.existsByFollowerIdAndFollowingId(followDto.getFollowerId(), + followDto.getFollowingId())).thenReturn(true); + //then + DuplicateException exception = assertThrows(DuplicateException.class, () -> followService.followUser(followDto)); + assertThat(exception.getResponseEnum()).isEqualTo(ApiResponseEnum.ALREADY_FOLLOWING); + } + + @DisplayName("follow API followingID와 followID 동일한 경우 에러 반환 테스트") + @Test + void followUser_error_InvalidFollow() { + //given + FollowDto followDto = FollowDto.builder() + .followingId(1L) + .followerId(1L) + .build(); + + //when + when(userRepository.existsById(followDto.getFollowerId())).thenReturn(true); + when(followRepository.existsByFollowerIdAndFollowingId(followDto.getFollowerId(), + followDto.getFollowingId())).thenReturn(false); + + //then + DuplicateException exception = assertThrows(DuplicateException.class, () -> followService.followUser(followDto)); + assertThat(exception.getResponseEnum()).isEqualTo(ApiResponseEnum.ALREADY_FOLLOWING); + } + @DisplayName("unfollow API 통합테스트") @Test @@ -82,15 +128,32 @@ void unfollowUser() { void getFollowers() { //given Long userId = 1L; + List mockFollowers = Collections.singletonList(mock(Follow.class)); - //then + //when when(userService.hasUserId(userId)).thenReturn(true); when(followGroupService.hasFollow(userId)).thenReturn(true); - when(followRepository.findByFollowerId(userId)).thenReturn(Collections.emptyList()); + when(followRepository.findByFollowerId(userId)).thenReturn(mockFollowers); + + List followerIds = followService.getFollowers(userId); - followService.getFollowers(userId); + //then + assertNotNull(followerIds); + verify(followRepository, times(1)).findByFollowingId(userId); + } + + @DisplayName("특정 유저의 팔로워 들을 불러오는 API 팔로워가 없는경우 에러 반환 테스트") + @Test + void getFollowers_error_followNotExists() { + //given + Long userId = 1L; + + //when + when(userService.hasUserId(userId)).thenReturn(true); + when(followGroupService.hasFollow(userId)).thenReturn(false); //then - verify(followRepository, times(1)).findByFollowerId(userId); + DataNotFoundException exception = assertThrows(DataNotFoundException.class, () -> followService.getFollowers(userId)); + assertThat(exception.getResponseEnum()).isEqualTo(ApiResponseEnum.FOLLOW_NOT_FOUND); } } \ No newline at end of file diff --git a/src/test/java/com/flab/stargram/service/UserServiceTest.java b/src/test/java/com/flab/stargram/service/UserServiceTest.java index 2ebcd40..8cfffc1 100644 --- a/src/test/java/com/flab/stargram/service/UserServiceTest.java +++ b/src/test/java/com/flab/stargram/service/UserServiceTest.java @@ -12,7 +12,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import com.flab.stargram.config.exception.BaseApiException; import com.flab.stargram.config.exception.DataNotFoundException; import com.flab.stargram.config.exception.InvalidPasswordException; import com.flab.stargram.entity.common.ApiResponseEnum; From 21df28980e4c26bfdaa2b56db259a05d1983d1b3 Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 16:17:45 +0900 Subject: [PATCH 18/27] =?UTF-8?q?[TEST]=20FollowGroupService=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/FollowGroupServiceTest.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/test/java/com/flab/stargram/service/FollowGroupServiceTest.java diff --git a/src/test/java/com/flab/stargram/service/FollowGroupServiceTest.java b/src/test/java/com/flab/stargram/service/FollowGroupServiceTest.java new file mode 100644 index 0000000..e5204dd --- /dev/null +++ b/src/test/java/com/flab/stargram/service/FollowGroupServiceTest.java @@ -0,0 +1,74 @@ +package com.flab.stargram.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.flab.stargram.entity.dto.FollowDto; +import com.flab.stargram.entity.model.FollowGroup; +import com.flab.stargram.repository.FollowGroupRepository; + +@ExtendWith(MockitoExtension.class) +class FollowGroupServiceTest { + + @InjectMocks + private FollowGroupService followGroupService; + + @Mock + private FollowGroupRepository followGroupRepository; + + private FollowDto followDto; + private FollowGroup mockFollowGroup; + + @BeforeEach + void setUp() { + followDto = FollowDto.builder() + .followingId(1L) + .followerId(2L) + .build(); + + mockFollowGroup = FollowGroup.create(1L); + } + + @DisplayName("기존 FollowGroup이 있는 경우 정상 반환 테스트") + @Test + void getOrCreateFollowGroup_existingGroup() { + // Given + when(followGroupRepository.findByFollowingId(followDto.getFollowingId())).thenReturn(mockFollowGroup); + + // When + FollowGroup result = followGroupService.getOrCreateFollowGroup(followDto); + + // Then + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(mockFollowGroup); + verify(followGroupRepository, times(1)).findByFollowingId(followDto.getFollowingId()); + verify(followGroupRepository, never()).save(any(FollowGroup.class)); + } + + @DisplayName("기존 FollowGroup이 없으면 새로 생성 후 저장 테스트") + @Test + void getOrCreateFollowGroup_createNewGroup() { + // Given + when(followGroupRepository.findByFollowingId(followDto.getFollowingId())).thenReturn(null); + when(followGroupRepository.save(any(FollowGroup.class))).thenReturn(mockFollowGroup); + + // When + FollowGroup result = followGroupService.getOrCreateFollowGroup(followDto); + + // Then + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(mockFollowGroup); + verify(followGroupRepository, times(1)).findByFollowingId(followDto.getFollowingId()); + verify(followGroupRepository, times(1)).save(any(FollowGroup.class)); + } + + +} \ No newline at end of file From 9a18155c16c66db0d0c83833777399608a0ce8f5 Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 20:02:41 +0900 Subject: [PATCH 19/27] =?UTF-8?q?[DOMAIN]=20@Valid=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/controller/PostController.java | 9 +++---- .../stargram/controller/UserController.java | 14 +++++----- .../flab/stargram/entity/dto/LoginDto.java | 21 +++++---------- .../stargram/entity/dto/PostRequestDto.java | 5 ++++ .../stargram/entity/dto/SignUpRequestDto.java | 27 ++++++------------- .../flab/stargram/service/UserService.java | 4 +-- 6 files changed, 31 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/flab/stargram/controller/PostController.java b/src/main/java/com/flab/stargram/controller/PostController.java index 407a4ff..f711792 100644 --- a/src/main/java/com/flab/stargram/controller/PostController.java +++ b/src/main/java/com/flab/stargram/controller/PostController.java @@ -1,6 +1,6 @@ package com.flab.stargram.controller; -import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -10,20 +10,19 @@ import com.flab.stargram.entity.dto.PostRequestDto; import com.flab.stargram.entity.common.ApiResult; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RestController @RequestMapping("/posts") @RequiredArgsConstructor - +@Validated public class PostController { private final PostService postService; @PostMapping - public ApiResult createPost(@RequestBody PostRequestDto dto) { - dto.validateEmpty().ifInvalidThrow(); - + public ApiResult createPost(@Valid @RequestBody PostRequestDto dto) { return ApiResult.success(postService.postFeed(dto)); } } diff --git a/src/main/java/com/flab/stargram/controller/UserController.java b/src/main/java/com/flab/stargram/controller/UserController.java index b9a2ee5..d4402e7 100644 --- a/src/main/java/com/flab/stargram/controller/UserController.java +++ b/src/main/java/com/flab/stargram/controller/UserController.java @@ -1,22 +1,24 @@ package com.flab.stargram.controller; -import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import com.flab.stargram.domain.auth.service.AuthCookieService; import com.flab.stargram.domain.auth.service.AuthService; +import com.flab.stargram.entity.common.ApiResult; import com.flab.stargram.entity.dto.LoginDto; import com.flab.stargram.entity.dto.SignUpRequestDto; -import com.flab.stargram.entity.common.ApiResult; import com.flab.stargram.entity.model.User; import com.flab.stargram.service.UserService; import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RestController +@Validated @RequiredArgsConstructor public class UserController { @@ -25,16 +27,12 @@ public class UserController { private final AuthCookieService authCookieService; @PostMapping("/signup") - public ApiResult signUp(@RequestBody SignUpRequestDto dto) { - dto.validateEmpty().ifInvalidThrow(); - + public ApiResult signUp(@Valid @RequestBody SignUpRequestDto dto) { return ApiResult.success(userService.signUp(dto)); } @PostMapping("/login") - public ApiResult login(@RequestBody LoginDto dto, HttpServletResponse response) { - dto.validateEmpty().ifInvalidThrow(); - + public ApiResult login(@Valid @RequestBody LoginDto dto, HttpServletResponse response) { User user = userService.login(dto); String token = authService.generateToken(user); authCookieService.addAuthCookie(response, token); diff --git a/src/main/java/com/flab/stargram/entity/dto/LoginDto.java b/src/main/java/com/flab/stargram/entity/dto/LoginDto.java index dfa5510..eda78e1 100644 --- a/src/main/java/com/flab/stargram/entity/dto/LoginDto.java +++ b/src/main/java/com/flab/stargram/entity/dto/LoginDto.java @@ -4,26 +4,19 @@ import com.flab.stargram.entity.common.BaseDto; import com.flab.stargram.entity.common.ValidationResult; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; import lombok.Setter; @Builder @Getter -public class LoginDto extends BaseDto { +public class LoginDto { + @NotBlank(message = "userName은 비어 있을 수 없습니다.") private String userName; - private String password; - - @Override - public ValidationResult validateEmpty() { - if (isFieldEmpty(userName)) { - validationResult.addError(ApiResponseEnum.EMPTY_USERNAME); - } - if (isFieldEmpty(password)) { - validationResult.addError(ApiResponseEnum.EMPTY_PASSWORD); - } - - return validationResult; - } + @NotBlank(message = "password는 비어 있을 수 없습니다.") + private String password; } diff --git a/src/main/java/com/flab/stargram/entity/dto/PostRequestDto.java b/src/main/java/com/flab/stargram/entity/dto/PostRequestDto.java index e7953c0..0b376a9 100644 --- a/src/main/java/com/flab/stargram/entity/dto/PostRequestDto.java +++ b/src/main/java/com/flab/stargram/entity/dto/PostRequestDto.java @@ -4,11 +4,16 @@ import com.flab.stargram.entity.common.BaseDto; import com.flab.stargram.entity.common.ValidationResult; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.Getter; @Getter public class PostRequestDto extends BaseDto { + @NotNull(message = "userID는 필수 입니다.") private Long userId; + + @NotEmpty(message = "content는 필수 입니다.") private String content; @Override diff --git a/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java b/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java index 352d36c..58e0d76 100644 --- a/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java +++ b/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java @@ -5,6 +5,8 @@ import com.flab.stargram.entity.common.ValidationResult; import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; @@ -12,27 +14,14 @@ @Builder @Getter -public class SignUpRequestDto extends BaseDto { +public class SignUpRequestDto { + @NotBlank(message = "userName은 비어 있을 수 없습니다.") private String userName; - @Email(message = "올바른 형식의 email을 입력해주세요") - private String email; + @NotBlank(message = "password는 비어 있을 수 없습니다.") private String password; - @Override - public ValidationResult validateEmpty() { - if (isFieldEmpty(userName)) { - validationResult.addError(ApiResponseEnum.EMPTY_USERNAME); - } - - if (isFieldEmpty(email)) { - validationResult.addError(ApiResponseEnum.EMPTY_EMAIL); - } - - if (isFieldEmpty(password)) { - validationResult.addError(ApiResponseEnum.EMPTY_PASSWORD); - } - - return validationResult; - } + @Email(message = "올바른 형식의 email을 입력해주세요.") + @NotBlank(message = "email은 비어 있을 수 없습니다.") + private String email; } diff --git a/src/main/java/com/flab/stargram/service/UserService.java b/src/main/java/com/flab/stargram/service/UserService.java index 5bc8312..b559dc8 100644 --- a/src/main/java/com/flab/stargram/service/UserService.java +++ b/src/main/java/com/flab/stargram/service/UserService.java @@ -24,9 +24,7 @@ public class UserService { public User signUp(SignUpRequestDto dto) { checkUserDuplication(dto); - User user = User.createUserOf(dto); - userRepository.save(user); - return user; + return userRepository.save(User.createUserOf(dto)); } @Transactional From 1320403f4a04186f426010bded71667ad15e9f5d Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 20:03:55 +0900 Subject: [PATCH 20/27] =?UTF-8?q?Valid=20=EC=97=90=EB=9F=AC=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=20=EC=8B=9C=20=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=ED=8C=8C=EC=8B=B1=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalApiExceptionHandler.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/com/flab/stargram/config/exception/GlobalApiExceptionHandler.java b/src/main/java/com/flab/stargram/config/exception/GlobalApiExceptionHandler.java index d11bc9d..71754e6 100644 --- a/src/main/java/com/flab/stargram/config/exception/GlobalApiExceptionHandler.java +++ b/src/main/java/com/flab/stargram/config/exception/GlobalApiExceptionHandler.java @@ -1,6 +1,9 @@ package com.flab.stargram.config.exception; import java.util.Map; +import java.util.Optional; +import java.util.stream.Collector; +import java.util.stream.Collectors; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.flab.stargram.entity.common.ApiResponseEnum; @@ -8,6 +11,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; @@ -41,4 +46,21 @@ public ResponseEntity handleHttpMessageNotReadableException(HttpMessa return ApiResult.error(ApiResponseEnum.INVALID_INPUT, ApiResponseEnum.INVALID_INPUT.getMessage()); } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { + Map errors = ex.getBindingResult().getFieldErrors().stream() + .collect( + Collectors.toMap( + FieldError::getField, + fieldError -> Optional.ofNullable(fieldError.getDefaultMessage()).orElse("Invalid value") + ) + ); + + return ApiResult.error( + ApiResponseEnum.INVALID_INPUT, + ApiResponseEnum.INVALID_INPUT.getMessage(), + errors + ); + } } \ No newline at end of file From c5da0fda89fda02f00beb2aea40c7c4baad9ae24 Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 20:04:29 +0900 Subject: [PATCH 21/27] =?UTF-8?q?[TEST]=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=EB=90=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/entity/dto/FollowDtoTest.java | 3 +- .../stargram/entity/dto/LoginDtoTest.java | 30 ++++----- .../entity/dto/SignUpRequestDtoTest.java | 24 +++---- .../stargram/service/UserServiceTest.java | 66 ++++++++++++++++++- 4 files changed, 94 insertions(+), 29 deletions(-) diff --git a/src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java b/src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java index 3ffef0b..5a7f0a1 100644 --- a/src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java +++ b/src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java @@ -1,4 +1,5 @@ package com.flab.stargram.entity.dto; + import static org.assertj.core.api.Assertions.*; import java.util.Set; @@ -8,7 +9,7 @@ import jakarta.validation.ConstraintViolation; -class FollowDtoTest extends ValidatorTest{ +class FollowDtoTest extends ValidatorTest { @DisplayName("정상 케이스") @Test diff --git a/src/test/java/com/flab/stargram/entity/dto/LoginDtoTest.java b/src/test/java/com/flab/stargram/entity/dto/LoginDtoTest.java index 62cae2e..ef35f36 100644 --- a/src/test/java/com/flab/stargram/entity/dto/LoginDtoTest.java +++ b/src/test/java/com/flab/stargram/entity/dto/LoginDtoTest.java @@ -2,13 +2,14 @@ import static org.assertj.core.api.Assertions.*; +import java.util.Set; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import com.flab.stargram.entity.common.ApiResponseEnum; -import com.flab.stargram.entity.common.ValidationResult; +import jakarta.validation.ConstraintViolation; -class LoginDtoTest { +class LoginDtoTest extends ValidatorTest { @DisplayName("userName에 빈 데이터 입력한 경우 에러 반환") @Test void validateDtoEmpty_errorEmptyuserName() { @@ -19,11 +20,11 @@ void validateDtoEmpty_errorEmptyuserName() { .build(); // When - ValidationResult result = dto.validateEmpty(); + Set> violations = validator.validate(dto); // Then - assertThat(result.isValid()).isFalse(); - assertThat(result.getError()).isEqualTo(ApiResponseEnum.EMPTY_USERNAME); + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("userName은 비어 있을 수 없습니다."); } @DisplayName("userName에 null 입력한 경우 에러 반환") @@ -36,14 +37,13 @@ void validateDtoEmpty_errorNulluserName() { .build(); // When - ValidationResult result = dto.validateEmpty(); + Set> violations = validator.validate(dto); // Then - assertThat(result.isValid()).isFalse(); - assertThat(result.getError()).isEqualTo(ApiResponseEnum.EMPTY_USERNAME); + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("userName은 비어 있을 수 없습니다."); } - @DisplayName("비밀번호 비어있는 데이터 입력한 경우 에러 반환") @Test void validateDtoEmpty_error() { @@ -54,11 +54,11 @@ void validateDtoEmpty_error() { .build(); // When - ValidationResult result = dto.validateEmpty(); + Set> violations = validator.validate(dto); // Then - assertThat(result.isValid()).isFalse(); - assertThat(result.getError()).isEqualTo(ApiResponseEnum.EMPTY_PASSWORD); + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("password는 비어 있을 수 없습니다."); } @DisplayName("정상 케이스") @@ -71,9 +71,9 @@ void validateDtoEmpty_success() { .build(); // When - ValidationResult result = dto.validateEmpty(); + Set> violations = validator.validate(dto); // Then - assertThat(result.isValid()).isTrue(); + assertThat(violations).isEmpty(); } } \ No newline at end of file diff --git a/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java b/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java index 130011c..73df208 100644 --- a/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java +++ b/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java @@ -7,12 +7,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import com.flab.stargram.entity.common.ApiResponseEnum; -import com.flab.stargram.entity.common.ValidationResult; - import jakarta.validation.ConstraintViolation; -class SignUpRequestDtoTest extends ValidatorTest{ +class SignUpRequestDtoTest extends ValidatorTest { @DisplayName("빈 데이터 입력 시 에러반환 테스트") @Test @@ -24,11 +21,13 @@ void validateDtoEmpty_error() { .build(); // When - ValidationResult result = dto.validateEmpty(); + Set> violations = validator.validate(dto); // Then - assertThat(result.isValid()).isFalse(); - assertThat(result.getError()).isEqualTo(ApiResponseEnum.EMPTY_USERNAME); + assertThat(violations).isNotEmpty(); + assertThat(violations).hasSize(3); + assertThat(violations.stream().map(ConstraintViolation::getMessage)) + .contains("userName은 비어 있을 수 없습니다.", "email은 비어 있을 수 없습니다.", "password는 비어 있을 수 없습니다."); } @DisplayName("정상 입력 케이스") @@ -42,19 +41,19 @@ void validateDtoEmpty_success() { .build(); // When - ValidationResult result = dto.validateEmpty(); + Set> violations = validator.validate(dto); // Then - assertThat(result.isValid()).isTrue(); + assertThat(violations).isEmpty(); } @DisplayName("잘못된 email 형식 에러 반환") @Test - void validateDtoEmail_failure(){ + void validateDtoEmail_failure() { // Given SignUpRequestDto dto = SignUpRequestDto.builder() .userName("testUser") - .email("test.com") + .email("josh.com") .password("123123") .build(); @@ -63,6 +62,7 @@ void validateDtoEmail_failure(){ // then assertThat(violations).isNotEmpty(); - assertThat(violations.iterator().next().getMessage()).isEqualTo("올바른 형식의 email을 입력해주세요"); + assertThat(violations).hasSize(1); + assertThat(violations.stream().map(ConstraintViolation::getMessage)).contains("올바른 형식의 email을 입력해주세요."); } } \ No newline at end of file diff --git a/src/test/java/com/flab/stargram/service/UserServiceTest.java b/src/test/java/com/flab/stargram/service/UserServiceTest.java index 8cfffc1..84d0e6f 100644 --- a/src/test/java/com/flab/stargram/service/UserServiceTest.java +++ b/src/test/java/com/flab/stargram/service/UserServiceTest.java @@ -13,6 +13,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.flab.stargram.config.exception.DataNotFoundException; +import com.flab.stargram.config.exception.DuplicateException; import com.flab.stargram.config.exception.InvalidPasswordException; import com.flab.stargram.entity.common.ApiResponseEnum; import com.flab.stargram.entity.dto.LoginDto; @@ -43,8 +44,71 @@ void setUp() { mockUser = User.createUserOf(signUpRequestDto); } + @DisplayName("정상 회원가입") + @Test + void signup(){ + // Given + SignUpRequestDto dto = SignUpRequestDto.builder() + .userName("gru") + .password("asdf") + .email("gru@f-lab.com") + .build(); + + //when + when(userRepository.findByUserNameOrEmail(dto.getUserName(), dto.getEmail())).thenReturn(null); + User mockUser = User.createUserOf(signUpRequestDto); + when(userRepository.save(any(User.class))).thenReturn(mockUser); + + User result = userService.signUp(dto); + + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(mockUser); + } + + @DisplayName("중복된 사용자명 회원가입 시 에러 반환 테스트") + @Test + void signUp_error_duplicateUserName() { + // Given + SignUpRequestDto dto = SignUpRequestDto.builder() + .userName(mockUser.getUserName()) + .password("asdf") + .email("gru@f-lab.com") + .build(); + + // When + when(userRepository.findByUserNameOrEmail(dto.getUserName(), dto.getEmail())).thenReturn(mockUser); + + // Then + DuplicateException exception = assertThrows(DuplicateException.class, () -> userService.signUp(dto)); + assertThat(exception.getResponseEnum()).isEqualTo(ApiResponseEnum.DUPLICATE_USERNAME); + + verify(userRepository, times(1)).findByUserNameOrEmail(dto.getUserName(), dto.getEmail()); + verify(userRepository, never()).save(any(User.class)); + } + + @DisplayName("중복된 email 회원가입 시 에러 반환 테스트") + @Test + void signup_error_invalidEmail() { + // Given + SignUpRequestDto dto = SignUpRequestDto.builder() + .userName("gru2") + .password("asdf") + .email(mockUser.getEmail()) + .build(); + + //when + when(userRepository.findByUserNameOrEmail(dto.getUserName(), dto.getEmail())).thenReturn(mockUser); + + //then + DuplicateException exception = assertThrows(DuplicateException.class, () -> userService.signUp(dto)); + assertThat(exception.getResponseEnum()).isEqualTo(ApiResponseEnum.DUPLICATE_EMAIL); + + verify(userRepository, times(1)).findByUserNameOrEmail(dto.getUserName(), dto.getEmail()); + verify(userRepository, never()).save(any(User.class)); + } + - @DisplayName("정상 로그인 테스트") + @DisplayName("정상 로그인") @Test void login() { LoginDto loginDto = LoginDto.builder() From a38e1cef3716fc8ecd4d03fa25277bd5d2d44cf4 Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 11 Feb 2025 21:28:16 +0900 Subject: [PATCH 22/27] =?UTF-8?q?[domain]=20=ED=8C=94=EB=A1=9C=EC=9B=8C/?= =?UTF-8?q?=ED=8C=94=EB=A1=9C=EC=9E=89=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/controller/FollowController.java | 2 +- .../repository/FollowGroupRepository.java | 2 ++ .../stargram/repository/FollowRepository.java | 2 +- .../stargram/service/FollowGroupService.java | 6 ++++- .../flab/stargram/service/FollowService.java | 22 +++++++++++++++---- .../stargram/StargramApplicationTests.java | 13 ----------- .../stargram/service/FollowServiceTest.java | 22 +++++++++---------- 7 files changed, 38 insertions(+), 31 deletions(-) delete mode 100644 src/test/java/com/flab/stargram/StargramApplicationTests.java diff --git a/src/main/java/com/flab/stargram/controller/FollowController.java b/src/main/java/com/flab/stargram/controller/FollowController.java index bb43e9f..e48d3be 100644 --- a/src/main/java/com/flab/stargram/controller/FollowController.java +++ b/src/main/java/com/flab/stargram/controller/FollowController.java @@ -37,6 +37,6 @@ public ApiResult unfollowUser(@Valid @RequestBody FollowDto dto) { @GetMapping("/{inputUserId}/followers") public ApiResult followUsers(@PathVariable String inputUserId) { - return ApiResult.success(followService.getFollowers(ParseUtil.parseToLong(inputUserId))); + return ApiResult.success(followService.getFollowerIds(ParseUtil.parseToLong(inputUserId))); } } diff --git a/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java b/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java index 6ad0bd6..243efc9 100644 --- a/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java +++ b/src/main/java/com/flab/stargram/repository/FollowGroupRepository.java @@ -9,4 +9,6 @@ public interface FollowGroupRepository extends JpaRepository FollowGroup findByFollowingId(Long followingId); //SELECT COUNT(*) > 0 FROM follow_group WHERE following_id = ? boolean existsByFollowingId(Long followingId); + //SELECT COUNT(*) > 0 FROM follow_group WHERE follower_id = ? + boolean existsByFollowerId(Long followerId); } diff --git a/src/main/java/com/flab/stargram/repository/FollowRepository.java b/src/main/java/com/flab/stargram/repository/FollowRepository.java index 8e39d0b..cc922e9 100644 --- a/src/main/java/com/flab/stargram/repository/FollowRepository.java +++ b/src/main/java/com/flab/stargram/repository/FollowRepository.java @@ -19,7 +19,7 @@ public interface FollowRepository extends JpaRepository { long countByFollowingId(Long followingId); //SELECT COUNT(*) FROM follow WHERE follower_id = ?; long countByFollowerId(Long followerId); - //SELECT * FROM follow WHERE follower_id = ?; + //SELECT * FROM follow WHERE following_id = ?; List findByFollowingId(Long followingId); //SELECT * FROM follow WHERE follower_id = ? ORDER BY id ASC LIMIT ? OffSET ?; List findByFollowerId(Long followerId); diff --git a/src/main/java/com/flab/stargram/service/FollowGroupService.java b/src/main/java/com/flab/stargram/service/FollowGroupService.java index e33ebb8..efa5191 100644 --- a/src/main/java/com/flab/stargram/service/FollowGroupService.java +++ b/src/main/java/com/flab/stargram/service/FollowGroupService.java @@ -14,10 +14,14 @@ public class FollowGroupService { private final FollowGroupRepository followGroupRepository; - public boolean hasFollow(Long userId) { + public boolean hasFollowing(Long userId) { return followGroupRepository.existsByFollowingId(userId); } + public boolean hasFollower(Long userId) { + return followGroupRepository.existsByFollowerId(userId); + } + @Transactional public FollowGroup getOrCreateFollowGroup(FollowDto followDto) { FollowGroup followGroup = followGroupRepository.findByFollowingId(followDto.getFollowingId()); diff --git a/src/main/java/com/flab/stargram/service/FollowService.java b/src/main/java/com/flab/stargram/service/FollowService.java index 4cc4524..2f8cbba 100644 --- a/src/main/java/com/flab/stargram/service/FollowService.java +++ b/src/main/java/com/flab/stargram/service/FollowService.java @@ -1,5 +1,6 @@ package com.flab.stargram.service; +import java.util.Collections; import java.util.List; import org.springframework.stereotype.Service; @@ -38,16 +39,23 @@ public void unfollowUser(FollowDto followDto) { } @Transactional - public List getFollowers(Long userId) { + public List getFollowerIds(Long userId) { if (!userService.hasUserId(userId)) { throw new DataNotFoundException(ApiResponseEnum.USER_NOT_FOUND); } - if (!followGroupService.hasFollow(userId)) { - throw new DataNotFoundException(ApiResponseEnum.FOLLOW_NOT_FOUND); + List followerIds = followRepository.findByFollowerId(userId); + return followerIds.isEmpty() ? Collections.emptyList() : getFollowerIds(followerIds); + } + + @Transactional + public List getFollowingIds(Long userId) { + if (!userService.hasUserId(userId)) { + throw new DataNotFoundException(ApiResponseEnum.USER_NOT_FOUND); } - return getFollowerIds(followRepository.findByFollowingId(userId)); + List followingIds = followRepository.findByFollowingId(userId); + return followingIds.isEmpty() ? Collections.emptyList() : getFollowingIds(followingIds); } private void validateFollowDto(FollowDto followDto) { @@ -85,4 +93,10 @@ private List getFollowerIds(List follows) { .map(Follow::getFollowerId) .toList(); } + + public List getFollowingIds(List follows) { + return follows.stream() + .map(Follow::getFollowingId) + .toList(); + } } \ No newline at end of file diff --git a/src/test/java/com/flab/stargram/StargramApplicationTests.java b/src/test/java/com/flab/stargram/StargramApplicationTests.java deleted file mode 100644 index afdee09..0000000 --- a/src/test/java/com/flab/stargram/StargramApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.flab.stargram; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class StargramApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/com/flab/stargram/service/FollowServiceTest.java b/src/test/java/com/flab/stargram/service/FollowServiceTest.java index f9fd727..4eeac0a 100644 --- a/src/test/java/com/flab/stargram/service/FollowServiceTest.java +++ b/src/test/java/com/flab/stargram/service/FollowServiceTest.java @@ -125,35 +125,35 @@ void unfollowUser() { @DisplayName("특정 유저의 팔로워 들을 불러오는 API 통합테스트") @Test - void getFollowers() { + void getFollowerIds() { //given Long userId = 1L; List mockFollowers = Collections.singletonList(mock(Follow.class)); //when when(userService.hasUserId(userId)).thenReturn(true); - when(followGroupService.hasFollow(userId)).thenReturn(true); when(followRepository.findByFollowerId(userId)).thenReturn(mockFollowers); - - List followerIds = followService.getFollowers(userId); + List follow = followService.getFollowerIds(userId); //then - assertNotNull(followerIds); - verify(followRepository, times(1)).findByFollowingId(userId); + assertNotNull(follow); + verify(followRepository, times(1)).findByFollowerId(userId); } - @DisplayName("특정 유저의 팔로워 들을 불러오는 API 팔로워가 없는경우 에러 반환 테스트") + @DisplayName("특정 유저의 팔로잉들을 불러오는 API 통합테스트") @Test - void getFollowers_error_followNotExists() { + void getFollowings() { //given Long userId = 1L; + List mockFollowings = Collections.singletonList(mock(Follow.class)); //when when(userService.hasUserId(userId)).thenReturn(true); - when(followGroupService.hasFollow(userId)).thenReturn(false); + when(followRepository.findByFollowingId(userId)).thenReturn(mockFollowings); + List follow = followService.getFollowingIds(userId); //then - DataNotFoundException exception = assertThrows(DataNotFoundException.class, () -> followService.getFollowers(userId)); - assertThat(exception.getResponseEnum()).isEqualTo(ApiResponseEnum.FOLLOW_NOT_FOUND); + assertNotNull(follow); + verify(followRepository, times(1)).findByFollowingId(userId); } } \ No newline at end of file From 2de0fb9ffd78c1dbe5c954ae503c3651fa0ef6fd Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 25 Feb 2025 02:55:10 +0900 Subject: [PATCH 23/27] =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B3=B4=EC=99=84=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/flab/stargram/entity/model/Follow.java | 8 ++++---- .../flab/stargram/entity/dto/FollowDtoTest.java | 15 ++++++++++++++- .../flab/stargram/service/FollowServiceTest.java | 13 +++++++------ .../flab/stargram/service/UserServiceTest.java | 11 ++++++----- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/flab/stargram/entity/model/Follow.java b/src/main/java/com/flab/stargram/entity/model/Follow.java index 7d91708..668f47c 100644 --- a/src/main/java/com/flab/stargram/entity/model/Follow.java +++ b/src/main/java/com/flab/stargram/entity/model/Follow.java @@ -30,13 +30,13 @@ public class Follow extends BaseEntity { @Column(nullable = false) private Long followingId; - private Follow(FollowGroup followGroup, Long followerId, Long followingId) { + private Follow(FollowGroup followGroup, FollowDto followDto) { this.followGroup = followGroup; - this.followerId = followerId; - this.followingId = followingId; + this.followerId = followDto.getFollowerId(); + this.followingId = followDto.getFollowingId(); } public static Follow createFollowOf(FollowGroup followGroup, FollowDto followDto) { - return new Follow(followGroup, followDto.getFollowerId(), followDto.getFollowingId()); + return new Follow(followGroup, followDto); } } diff --git a/src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java b/src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java index 5a7f0a1..2afdf71 100644 --- a/src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java +++ b/src/test/java/com/flab/stargram/entity/dto/FollowDtoTest.java @@ -80,7 +80,7 @@ void followerIdIsNull() { assertThat(violations.iterator().next().getMessage()).isEqualTo("follower ID는 필수 입니다."); } - @DisplayName("followerId가 음수인 경우 검증 오류 발생") + @DisplayName("followerId가 음수인 경우 검증 오류 발생시킨다") @Test void followerIdIsNegative() { FollowDto dto = FollowDto.builder() @@ -94,4 +94,17 @@ void followerIdIsNegative() { assertThat(violations.iterator().next().getMessage()).isEqualTo("follower ID는 양수로 입력해야 합니다."); } + @DisplayName("follow dto followingID와 FollowID가 동일하면 true를 반환한다") + @Test + void followDtoSameUser(){ + FollowDto dto = FollowDto.builder() + .followingId(1L) + .followerId(1L) + .build(); + + assertThat(dto.isSameUser()).isTrue(); + } + + + } \ No newline at end of file diff --git a/src/test/java/com/flab/stargram/service/FollowServiceTest.java b/src/test/java/com/flab/stargram/service/FollowServiceTest.java index 4eeac0a..3d96b79 100644 --- a/src/test/java/com/flab/stargram/service/FollowServiceTest.java +++ b/src/test/java/com/flab/stargram/service/FollowServiceTest.java @@ -14,7 +14,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import com.flab.stargram.config.exception.DataNotFoundException; import com.flab.stargram.config.exception.DuplicateException; import com.flab.stargram.entity.common.ApiResponseEnum; import com.flab.stargram.entity.dto.FollowDto; @@ -62,7 +61,7 @@ void followUser() { verify(followRepository, times(1)).save(any(Follow.class)); } - @DisplayName("follow API 중복 Follow 에러 반환 테스트") + @DisplayName("Follow API Follow 에러 반환 테스트") @Test void followUser_error_duplicate() { //given @@ -76,8 +75,9 @@ void followUser_error_duplicate() { when(followRepository.existsByFollowerIdAndFollowingId(followDto.getFollowerId(), followDto.getFollowingId())).thenReturn(true); //then - DuplicateException exception = assertThrows(DuplicateException.class, () -> followService.followUser(followDto)); - assertThat(exception.getResponseEnum()).isEqualTo(ApiResponseEnum.ALREADY_FOLLOWING); + assertThatThrownBy(() -> followService.followUser(followDto)) + .usingRecursiveComparison() + .isEqualTo(new DuplicateException(ApiResponseEnum.ALREADY_FOLLOWING)); } @DisplayName("follow API followingID와 followID 동일한 경우 에러 반환 테스트") @@ -95,8 +95,9 @@ void followUser_error_InvalidFollow() { followDto.getFollowingId())).thenReturn(false); //then - DuplicateException exception = assertThrows(DuplicateException.class, () -> followService.followUser(followDto)); - assertThat(exception.getResponseEnum()).isEqualTo(ApiResponseEnum.ALREADY_FOLLOWING); + assertThatThrownBy(() -> followService.followUser(followDto)) + .usingRecursiveComparison() + .isEqualTo(new DuplicateException(ApiResponseEnum.ALREADY_FOLLOWING)); } diff --git a/src/test/java/com/flab/stargram/service/UserServiceTest.java b/src/test/java/com/flab/stargram/service/UserServiceTest.java index 84d0e6f..fd751c0 100644 --- a/src/test/java/com/flab/stargram/service/UserServiceTest.java +++ b/src/test/java/com/flab/stargram/service/UserServiceTest.java @@ -65,7 +65,7 @@ void signup(){ assertThat(result).isEqualTo(mockUser); } - @DisplayName("중복된 사용자명 회원가입 시 에러 반환 테스트") + @DisplayName("Signup AIP 입력한 userName이 이미 존재하는 경우 DuplicateException을 반환한다") @Test void signUp_error_duplicateUserName() { // Given @@ -86,7 +86,7 @@ void signUp_error_duplicateUserName() { verify(userRepository, never()).save(any(User.class)); } - @DisplayName("중복된 email 회원가입 시 에러 반환 테스트") + @DisplayName("Signup AIP 입력한 email이 이미 존재하는 경우 DuplicateException을 반환한다") @Test void signup_error_invalidEmail() { // Given @@ -127,7 +127,7 @@ void login() { verify(userRepository, times(1)).findByUserName(loginDto.getUserName()); } - @DisplayName("존재하지 않는 사용자 ID로 로그인 시 에러반환 테스트") + @DisplayName("LogIn API userName이 DB에 없는경우 DataNotFoundException를 반환한다") @Test void login_error_NotExistId(){ // Given @@ -139,13 +139,14 @@ void login_error_NotExistId(){ //when when(userRepository.findByUserName(dto.getUserName())).thenReturn(null); - //then DataNotFoundException exception = assertThrows(DataNotFoundException.class, () -> userService.login(dto)); assertThat(exception.getResponseEnum()).isEqualTo(ApiResponseEnum.USER_NOT_FOUND); + + } - @DisplayName("로그인중 비밀번호 오류 시 에러 반환 테스트") + @DisplayName("LogIn API password가 DB에 저장된 값과 다른경우 InvalidPasswordException를 반환한다") @Test void login_error_incorrectPassword(){ // Given From 048223b01cac0ac47864ac19d4675aaa6b973aee Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 25 Feb 2025 02:55:50 +0900 Subject: [PATCH 24/27] =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EA=B0=92=20=EC=97=86=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=98=A4=EB=B2=84=EB=A1=9C=EB=94=A9=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/flab/stargram/controller/FollowController.java | 4 ++-- src/main/java/com/flab/stargram/entity/common/ApiResult.java | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/flab/stargram/controller/FollowController.java b/src/main/java/com/flab/stargram/controller/FollowController.java index e48d3be..74c8364 100644 --- a/src/main/java/com/flab/stargram/controller/FollowController.java +++ b/src/main/java/com/flab/stargram/controller/FollowController.java @@ -26,13 +26,13 @@ public class FollowController { @PostMapping("/follow") public ApiResult followUser(@Valid @RequestBody FollowDto dto) { followService.followUser(dto); - return ApiResult.success(null); + return ApiResult.success(); } @PostMapping("/unfollow") public ApiResult unfollowUser(@Valid @RequestBody FollowDto dto) { followService.unfollowUser(dto); - return ApiResult.success(null); + return ApiResult.success(); } @GetMapping("/{inputUserId}/followers") diff --git a/src/main/java/com/flab/stargram/entity/common/ApiResult.java b/src/main/java/com/flab/stargram/entity/common/ApiResult.java index 38f74fb..5cdc00c 100644 --- a/src/main/java/com/flab/stargram/entity/common/ApiResult.java +++ b/src/main/java/com/flab/stargram/entity/common/ApiResult.java @@ -19,6 +19,10 @@ public static ApiResult success(Object data) { return new ApiResult(ApiResponseEnum.SUCCESS, null, data); } + public static ApiResult success() { + return new ApiResult(ApiResponseEnum.SUCCESS, null, null); + } + public static ResponseEntity error(ApiResponseEnum apiResponseEnum, String message) { ApiResult result = new ApiResult(apiResponseEnum, message, null); return ResponseEntity.status(apiResponseEnum.getHttpStatus()).body(result); From 1deb6a748eb19e61017991fe62b2011c787fa4e9 Mon Sep 17 00:00:00 2001 From: gru <68883983+gru3530@users.noreply.github.com> Date: Tue, 25 Feb 2025 01:29:24 +0900 Subject: [PATCH 25/27] Update README.md # Conflicts: # README.md --- README.md | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6d370b1..a6ac38b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,85 @@ -# Stargram -인스타그램을 모방한 SNS서비스 +# 🌟 Stargram - Instagram Clone Project + +> **Stargram은 인스타그램의 핵심 기능을 구현한 SNS 플랫폼입니다.** +> **사용자는 사진/동영상과 텍스트를 공유하며, 댓글과 좋아요를 통해 소통할 수 있습니다.** + + + +--- + +## 📷 **프로젝트 개요** +### 🔹 Stargram이란? +Stargram은 **Instagram의 핵심 기능을 모방한 SNS 플랫폼**으로, 사용자가 **사진과 동영상을 공유하고, 댓글과 좋아요로 소통하며, 팔로우 시스템을 통해 개인화된 피드를 받을 수 있도록 설계**되었습니다. + +### 🔹 주요 목표 및 특징 +✅ **객체 지향 원칙(SOLID) 적용** +✅ **Spring의 트랜잭션 관리(@Transactional) 활용** +✅ **테스트 코드(JUnit) 기반 안정성 확보** +✅ **GitHub PR & Code Review 방식으로 코드 품질 개선** +✅ **GitHub Projects를 활용한 애자일 개발 진행** +✅ **RESTful API 설계 및 문서화** + + +📌 **[Stargram API LIST](https://docs.google.com/spreadsheets/d/1piFu00NngGIWQvQfoORgb7A-OuY4zVjEH804PHcYPtA/edit?gid=0#gid=0)** +📌 **[Stargram 애자일 프로젝트](https://github.com/orgs/f-lab-edu/projects/331)** +📌 **[코드 규칙 (Naver Convention)](https://naver.github.io/hackday-conventions-java/#_intellij)** + +--- + +## 🛠 **기술 스택** +| 기술 분야 | 사용한 기술 | +|-----------|------------| +| **Language** | Java 17 | +| **Framework** | Spring Boot 3.4.0 | +| **Database** | MySQL | +| **Version Control** | Git & GitHub | +| **Testing** | JUnit, Mockito | +| **Development Tools** | IntelliJ IDEA, Gradle | +| **Infrastructure** | Redis (세션 관리) | + +--- + +## ⚙️ **주요 기능** +### 📌 **1. 사용자 인증 및 관리** +- ✅ JWT 기반 로그인 +- ✅ 쿠키를 활용한 사용자 상태 검증 +- ✅ 사용자 프로필 조회 및 수정 (구현예정) + +### 📌 **2. 피드 시스템** +- ✅ 이미지/동영상 업로드 (구현예정) +- ✅ 댓글 작성 및 삭제 +- ✅ 좋아요 기능 + +### 📌 **3. 팔로우/팔로잉 시스템** +- ✅ 사용자가 다른 유저를 팔로우/언팔로우 +- ✅ 팔로워 기반 개인화 피드 제공 (구현예정) + +### 📌 **4. 트랜잭션 관리** +- ✅ **Spring @Transactional 적용** → DB 무결성 보장 +- ✅ 여러 DB 연산을 하나의 트랜잭션으로 처리 + +### 📌 **5. 테스트 코드 작성** +- ✅ JUnit 기반의 단위 테스트(Unit Test) 및 통합 테스트(Integration Test) 작성 +- ✅ Mocking을 활용한 서비스 테스트 수행 + +--- + +## 🏗 **프로젝트 구조** +``` +Stargram +│── src +│ ├── main +│ │ ├── java.com.flab.stargram +│ │ │ ├── config # 프로젝트 설정 (예외 처리, 보안, 키 관리 등) +│ │ │ ├── controller # REST API 컨트롤러 +│ │ │ ├── domain # 비즈니스 로직 +│ │ │ ├── entity # DB 엔티티 및 DTO +│ │ │ ├── repository # 데이터 접근 계층 +│ │ │ ├── service # 서비스 계층 (비즈니스 로직 처리) +│ │ ├── resources +│ │ │ ├── application.properties # 환경 설정 파일 +│ ├── test # JUnit 기반 테스트 코드 +│── build.gradle # 프로젝트 의존성 관리 +``` + +--- From cd409bf8ae6dcd6b89e16681814f206b6959fbda Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 25 Feb 2025 13:55:54 +0900 Subject: [PATCH 26/27] =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20?= =?UTF-8?q?=EC=8B=9C=20=EA=B8=80=EC=9E=90=EC=88=98=20=EC=A0=9C=ED=95=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stargram/entity/dto/SignUpRequestDto.java | 10 +-- .../entity/dto/SignUpRequestDtoTest.java | 84 +++++++++++++++++-- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java b/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java index 58e0d76..a848114 100644 --- a/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java +++ b/src/main/java/com/flab/stargram/entity/dto/SignUpRequestDto.java @@ -1,23 +1,19 @@ package com.flab.stargram.entity.dto; -import com.flab.stargram.entity.common.ApiResponseEnum; -import com.flab.stargram.entity.common.BaseDto; -import com.flab.stargram.entity.common.ValidationResult; - import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.Builder; import lombok.Getter; -import lombok.Setter; @Builder @Getter public class SignUpRequestDto { + @Size(min = 2, max = 20, message = "userName은 2~20자로 입력해야 합니다.") @NotBlank(message = "userName은 비어 있을 수 없습니다.") private String userName; + @Size(min = 8, max = 50, message = "비밀번호는 8~50자로 입력해야 합니다.") @NotBlank(message = "password는 비어 있을 수 없습니다.") private String password; diff --git a/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java b/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java index 73df208..b5462e8 100644 --- a/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java +++ b/src/test/java/com/flab/stargram/entity/dto/SignUpRequestDtoTest.java @@ -11,7 +11,7 @@ class SignUpRequestDtoTest extends ValidatorTest { - @DisplayName("빈 데이터 입력 시 에러반환 테스트") + @DisplayName("SignupDto 빈 데이터 입력 시 validate 에러반환") @Test void validateDtoEmpty_error() { // Given @@ -25,9 +25,9 @@ void validateDtoEmpty_error() { // Then assertThat(violations).isNotEmpty(); - assertThat(violations).hasSize(3); + assertThat(violations).hasSize(4); assertThat(violations.stream().map(ConstraintViolation::getMessage)) - .contains("userName은 비어 있을 수 없습니다.", "email은 비어 있을 수 없습니다.", "password는 비어 있을 수 없습니다."); + .contains("userName은 비어 있을 수 없습니다.", "email은 비어 있을 수 없습니다.", "password는 비어 있을 수 없습니다.", "비밀번호는 8~50자로 입력해야 합니다."); } @DisplayName("정상 입력 케이스") @@ -37,7 +37,7 @@ void validateDtoEmpty_success() { SignUpRequestDto dto = SignUpRequestDto.builder() .userName("testUser") .email("test@f-lab.com") - .password("123123") + .password("12345678") .build(); // When @@ -47,14 +47,14 @@ void validateDtoEmpty_success() { assertThat(violations).isEmpty(); } - @DisplayName("잘못된 email 형식 에러 반환") + @DisplayName("SignUpDto email 잘못된 형식 입력 시 validate 에러 반환") @Test void validateDtoEmail_failure() { // Given SignUpRequestDto dto = SignUpRequestDto.builder() .userName("testUser") .email("josh.com") - .password("123123") + .password("12345678") .build(); // when @@ -65,4 +65,76 @@ void validateDtoEmail_failure() { assertThat(violations).hasSize(1); assertThat(violations.stream().map(ConstraintViolation::getMessage)).contains("올바른 형식의 email을 입력해주세요."); } + + @DisplayName("SignUpdto userName이 2자 미만일 경우 validate 에러 반환") + @Test + void validateUserNameTooShort() { + // Given + SignUpRequestDto dto = SignUpRequestDto.builder() + .userName("g") + .email("gru@f-lab.com") + .password("12345678") + .build(); + + //when + Set> violations = validator.validate(dto); + + //then + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("userName은 2~20자로 입력해야 합니다."); + } + + @DisplayName("SignUpdto userName이 20자 초과일 경우 validate 에러 반환") + @Test + void validateUserNameTooLong() { + // Given + SignUpRequestDto dto = SignUpRequestDto.builder() + .userName("abcdefghijklmnopqrstu") + .email("gru@f-lab.com") + .password("12345678") + .build(); + + //when + Set> violations = validator.validate(dto); + + //then + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("userName은 2~20자로 입력해야 합니다."); + } + + @DisplayName("SignUpdto password가 8자 미만일 경우 validate 에러 반환") + @Test + void validatePasswordTooShort() { + // Given + SignUpRequestDto dto = SignUpRequestDto.builder() + .userName("gru") + .email("gru@f-lab.com") + .password("1234567") + .build(); + + // when + Set> violations = validator.validate(dto); + + // Then + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("비밀번호는 8~50자로 입력해야 합니다."); + } + + @DisplayName("SignUpdto password가 50자 초과일 경우 validate 에러 반환") + @Test + void validatePasswordTooLong() { + // Given + SignUpRequestDto dto = SignUpRequestDto.builder() + .userName("gru") + .email("gru@f-lab.com") + .password("123456789012345678901234567890123456789012345678901") + .build(); + + // when + Set> violations = validator.validate(dto); + + // Then + assertThat(violations).isNotEmpty(); + assertThat(violations.iterator().next().getMessage()).isEqualTo("비밀번호는 8~50자로 입력해야 합니다."); + } } \ No newline at end of file From 1eeb1c48e027edb0b9fbf79aa49ae0b9f7b31423 Mon Sep 17 00:00:00 2001 From: gru Date: Tue, 25 Feb 2025 16:12:23 +0900 Subject: [PATCH 27/27] =?UTF-8?q?Github=20Action=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b8b2b7f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI Pipeline + +on: + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: 코드 체크아웃 + uses: actions/checkout@v3 + + - name: JDK 설정 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Gradle 캐시 설정 + uses: actions/cache@v3 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle + + - name: Gradle Wrapper 실행 권한 부여 + run: chmod +x ./gradlew + + - name: 종속성 설치 + run: ./gradlew build -x test + + - name: 테스트 실행 + run: ./gradlew test \ No newline at end of file