diff --git a/src/main/java/com/study/realworld/domain/follow/api/FollowQueryApi.java b/src/main/java/com/study/realworld/domain/follow/api/FollowQueryApi.java index 8dac65e6..55e1f2f6 100644 --- a/src/main/java/com/study/realworld/domain/follow/api/FollowQueryApi.java +++ b/src/main/java/com/study/realworld/domain/follow/api/FollowQueryApi.java @@ -23,7 +23,7 @@ public ResponseEntity profile(@AuthenticationPrincipal final Lo return ResponseEntity.ok().body(profile); } - @GetMapping("/profiles2/{username}") + @GetMapping("/profiles/querydsl/{username}") public ResponseEntity profile2(@AuthenticationPrincipal final Long userId, @PathVariable final String username) { final ProfileResponse profile = followQueryService.profile2(userId, UserName.from(username)); diff --git a/src/main/java/com/study/realworld/domain/follow/domain/Follow.java b/src/main/java/com/study/realworld/domain/follow/domain/Follow.java index b8f07aba..a85382ef 100644 --- a/src/main/java/com/study/realworld/domain/follow/domain/Follow.java +++ b/src/main/java/com/study/realworld/domain/follow/domain/Follow.java @@ -15,7 +15,7 @@ public class Follow { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false, updatable = false) - private Long id; + private Long followId; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "followee", nullable = false, foreignKey = @ForeignKey(name = "fk_follow_to_followee")) @@ -31,8 +31,8 @@ public Follow(final User followee, final User follower) { this.follower = follower; } - public Long id() { - return id; + public Long followId() { + return followId; } public User followee() { @@ -48,11 +48,11 @@ public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final Follow follow = (Follow) o; - return Objects.equals(id(), follow.id()); + return Objects.equals(followId(), follow.followId()); } @Override public int hashCode() { - return Objects.hash(id()); + return Objects.hash(followId()); } } diff --git a/src/main/java/com/study/realworld/domain/follow/dto/FollowResponse.java b/src/main/java/com/study/realworld/domain/follow/dto/FollowResponse.java index 15df1c56..d12d02d7 100644 --- a/src/main/java/com/study/realworld/domain/follow/dto/FollowResponse.java +++ b/src/main/java/com/study/realworld/domain/follow/dto/FollowResponse.java @@ -15,7 +15,7 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) @JsonTypeName("profile") @JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) -public class FollowResponse { +public final class FollowResponse { @JsonProperty("username") private UserName userName; @@ -30,27 +30,27 @@ public class FollowResponse { private Boolean followable; - public static FollowResponse from(final User user) { + public static final FollowResponse from(final User user) { return of(user, true); } - private static FollowResponse of(final User user, final boolean followable) { + private static final FollowResponse of(final User user, final boolean followable) { return new FollowResponse(user.userName(), user.userBio(), user.userImage(), followable); } - public UserName userName() { + public final UserName userName() { return userName; } - public UserBio userBio() { + public final UserBio userBio() { return userBio; } - public UserImage userImage() { + public final UserImage userImage() { return userImage; } - public Boolean isFollowing() { + public final Boolean isFollowing() { return followable; } } diff --git a/src/main/java/com/study/realworld/domain/follow/dto/UnFollowResponse.java b/src/main/java/com/study/realworld/domain/follow/dto/UnFollowResponse.java index 0522a765..93e24cc1 100644 --- a/src/main/java/com/study/realworld/domain/follow/dto/UnFollowResponse.java +++ b/src/main/java/com/study/realworld/domain/follow/dto/UnFollowResponse.java @@ -15,7 +15,7 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) @JsonTypeName("profile") @JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) -public class UnFollowResponse { +public final class UnFollowResponse { @JsonProperty("username") private UserName userName; @@ -29,28 +29,27 @@ public class UnFollowResponse { @JsonProperty("following") private Boolean followable; - public static UnFollowResponse from(final User user) { + public static final UnFollowResponse from(final User user) { return of(user, false); } - private static UnFollowResponse of(final User user, final boolean followable) { + private static final UnFollowResponse of(final User user, final boolean followable) { return new UnFollowResponse(user.userName(), user.userBio(), user.userImage(), followable); } - public UserName userName() { + public final UserName userName() { return userName; } - public UserBio userBio() { + public final UserBio userBio() { return userBio; } - public UserImage userImage() { + public final UserImage userImage() { return userImage; } - public Boolean isFollowing() { + public final Boolean isFollowing() { return followable; } } - diff --git a/src/main/java/com/study/realworld/domain/follow/error/FollowErrorCode.java b/src/main/java/com/study/realworld/domain/follow/error/FollowErrorCode.java index 80b7d649..c511d147 100644 --- a/src/main/java/com/study/realworld/domain/follow/error/FollowErrorCode.java +++ b/src/main/java/com/study/realworld/domain/follow/error/FollowErrorCode.java @@ -10,8 +10,8 @@ public enum FollowErrorCode implements ErrorCode { - FOLLOW_NOT_FOUND(FollowNotFoundException.class, HttpStatus.BAD_REQUEST, "Follow is not exist"), - DUPLICATED_FOLLOW(DuplicatedFollowException.class, HttpStatus.BAD_REQUEST, "Follow is already exist"); + DUPLICATED_FOLLOW(DuplicatedFollowException.class, HttpStatus.BAD_REQUEST, "Follow is already exist"), + FOLLOW_NOT_FOUND(FollowNotFoundException.class, HttpStatus.BAD_REQUEST, "Follow is not exist"); private final Class exceptionClass; private final HttpStatus httpStatus; diff --git a/src/main/java/com/study/realworld/domain/user/domain/vo/UserPassword.java b/src/main/java/com/study/realworld/domain/user/domain/vo/UserPassword.java index 11fc37a2..1b007fac 100644 --- a/src/main/java/com/study/realworld/domain/user/domain/vo/UserPassword.java +++ b/src/main/java/com/study/realworld/domain/user/domain/vo/UserPassword.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.study.realworld.domain.user.error.exception.PasswordMissMatchException; +import com.study.realworld.domain.user.error.exception.PasswordNullOrEmptyException; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; @@ -27,8 +28,8 @@ public static UserPassword encode(final String rawPassword, final PasswordEncode } private static void validateNullOrBlank(final String rawPassword) { - if (rawPassword.isBlank()) { - throw new IllegalArgumentException(); + if (Objects.isNull(rawPassword) || rawPassword.isBlank()) { + throw new PasswordNullOrEmptyException(); } } diff --git a/src/main/java/com/study/realworld/domain/user/dto/UserInfo.java b/src/main/java/com/study/realworld/domain/user/dto/UserInfo.java index b9e28029..023e95cc 100644 --- a/src/main/java/com/study/realworld/domain/user/dto/UserInfo.java +++ b/src/main/java/com/study/realworld/domain/user/dto/UserInfo.java @@ -17,7 +17,7 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) @JsonTypeName("user") @JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME) -public class UserInfo { +public final class UserInfo { @JsonProperty("username") private UserName userName; @@ -42,23 +42,23 @@ public static final UserInfo fromUserWithToken(final User user, final AccessToke return new UserInfo(userName, userEmail, userBio, userImage, accessToken); } - public UserName userName() { + public final UserName userName() { return userName; } - public UserEmail userEmail() { + public final UserEmail userEmail() { return userEmail; } - public UserBio userBio() { + public final UserBio userBio() { return userBio; } - public UserImage userImage() { + public final UserImage userImage() { return userImage; } - public AccessToken accessToken() { + public final AccessToken accessToken() { return accessToken; } } diff --git a/src/main/java/com/study/realworld/domain/user/dto/UserJoin.java b/src/main/java/com/study/realworld/domain/user/dto/UserJoin.java index 77e1d484..7f77b253 100644 --- a/src/main/java/com/study/realworld/domain/user/dto/UserJoin.java +++ b/src/main/java/com/study/realworld/domain/user/dto/UserJoin.java @@ -73,23 +73,23 @@ public static final Response fromUserWithToken(final User user, final AccessToke return new Response(userName, userEmail, userBio, userImage, accessToken); } - public UserEmail userEmail() { + public final UserEmail userEmail() { return userEmail; } - public UserName userName() { + public final UserName userName() { return userName; } - public UserBio userBio() { + public final UserBio userBio() { return userBio; } - public UserImage userImage() { + public final UserImage userImage() { return userImage; } - public AccessToken accessToken() { + public final AccessToken accessToken() { return accessToken; } } diff --git a/src/main/java/com/study/realworld/domain/user/dto/UserUpdate.java b/src/main/java/com/study/realworld/domain/user/dto/UserUpdate.java index b4d12944..fb375aa3 100644 --- a/src/main/java/com/study/realworld/domain/user/dto/UserUpdate.java +++ b/src/main/java/com/study/realworld/domain/user/dto/UserUpdate.java @@ -31,6 +31,13 @@ public static final class Request { @JsonProperty("image") private UserImage userImage; + @Builder + public Request(final UserEmail userEmail, final UserBio userBio, final UserImage userImage) { + this.userEmail = userEmail; + this.userBio = userBio; + this.userImage = userImage; + } + public final UserEmail memberEmail() { return userEmail; } @@ -42,13 +49,6 @@ public final UserBio memberBio() { public final UserImage memberImage() { return userImage; } - - @Builder - public Request(final UserEmail userEmail, final UserBio userBio, final UserImage userImage) { - this.userEmail = userEmail; - this.userBio = userBio; - this.userImage = userImage; - } } @AllArgsConstructor(access = AccessLevel.PRIVATE) @@ -95,7 +95,6 @@ public UserBio userBio() { public UserImage userImage() { return userImage; } - } } diff --git a/src/main/java/com/study/realworld/domain/user/error/exception/PasswordNullOrEmptyException.java b/src/main/java/com/study/realworld/domain/user/error/exception/PasswordNullOrEmptyException.java new file mode 100644 index 00000000..f2efbd8b --- /dev/null +++ b/src/main/java/com/study/realworld/domain/user/error/exception/PasswordNullOrEmptyException.java @@ -0,0 +1,10 @@ +package com.study.realworld.domain.user.error.exception; + +public class PasswordNullOrEmptyException extends UserBusinessException { + + private static final String MESSAGE = "올바르지 않은 패스워드입니다."; + + public PasswordNullOrEmptyException() { + super(MESSAGE); + } +} diff --git a/src/test/java/com/study/realworld/acceptance/FollowAcceptanceTest.java b/src/test/java/com/study/realworld/acceptance/FollowAcceptanceTest.java index 7494f099..8b7fc63a 100644 --- a/src/test/java/com/study/realworld/acceptance/FollowAcceptanceTest.java +++ b/src/test/java/com/study/realworld/acceptance/FollowAcceptanceTest.java @@ -5,6 +5,7 @@ import com.study.realworld.domain.follow.dto.FollowResponse; import com.study.realworld.domain.follow.dto.ProfileResponse; import com.study.realworld.domain.follow.dto.UnFollowResponse; +import com.study.realworld.domain.follow.error.FollowErrorResponse; import com.study.realworld.domain.user.dto.UserJoin; import com.study.realworld.global.common.AccessToken; import io.restassured.RestAssured; @@ -53,6 +54,23 @@ public void setUp() { ); } + @Test + void 특정_사람의_프로필을_조회한다_QueryDsl() { + final Login.Response loginResponse = 로그인_되어있음(user1.userEmail().userEmail()); + final ExtractableResponse response = 프로필_조회_요청_QueryDsl( + loginResponse.accessToken(), + user2.userName().userName()); + final ProfileResponse profileResponse = response.as(ProfileResponse.class); + + assertAll( + () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()), + () -> assertThat(profileResponse.userName()).isEqualTo(user2.userName()), + () -> assertThat(profileResponse.userBio()).isEqualTo(user2.userBio()), + () -> assertThat(profileResponse.userImage()).isEqualTo(user2.userImage()), + () -> assertThat(profileResponse.isFollowing()).isFalse() + ); + } + @Test void 특정_사람을_팔로우한다() { final Login.Response loginResponse = 로그인_되어있음(user1.userEmail().userEmail()); @@ -71,6 +89,18 @@ public void setUp() { ); } + @Test + void 특정_사람을_팔로우했는데_한번_더_팔로우하면_예외를_발생시킨다() { + final Login.Response loginResponse = 로그인_되어있음(user1.userEmail().userEmail()); + 팔로우_요청(loginResponse.accessToken(), user2.userName().userName()); + final ExtractableResponse response = 팔로우_요청( + loginResponse.accessToken(), + user2.userName().userName()); + + final FollowErrorResponse followErrorResponse = response.as(FollowErrorResponse.class); + assertThat(followErrorResponse.body().get(0)).isEqualTo("Follow is already exist"); + } + @Test void 특정_사람을_언팔로우한다() { final Login.Response loginResponse = 로그인_되어있음(user1.userEmail().userEmail()); @@ -90,6 +120,18 @@ public void setUp() { ); } + @Test + void 특정_사람을_언팔로우했는데_한번_더_언팔로우하면_예외를_발생시킨다() { + final Login.Response loginResponse = 로그인_되어있음(user1.userEmail().userEmail()); + 언팔로우_요청(loginResponse.accessToken(), user2.userName().userName()); + final ExtractableResponse response = 언팔로우_요청( + loginResponse.accessToken(), + user2.userName().userName()); + + final FollowErrorResponse followErrorResponse = response.as(FollowErrorResponse.class); + assertThat(followErrorResponse.body().get(0)).isEqualTo("Follow is not exist"); + } + protected ExtractableResponse 프로필_조회_요청(final AccessToken accessToken, final String userName) { return RestAssured.given() .header(AUTHORIZATION, BEARER + accessToken.accessToken()) @@ -99,6 +141,15 @@ public void setUp() { .extract(); } + protected ExtractableResponse 프로필_조회_요청_QueryDsl(final AccessToken accessToken, final String userName) { + return RestAssured.given() + .header(AUTHORIZATION, BEARER + accessToken.accessToken()) + .when() + .get(String.format("/api/profiles/querydsl/%s", userName)) + .then() + .extract(); + } + protected ExtractableResponse 팔로우_요청(final AccessToken accessToken, final String userName) { return RestAssured.given() .header(AUTHORIZATION, BEARER + accessToken.accessToken()) diff --git a/src/test/java/com/study/realworld/domain/article/domain/persist/ArticleTest.java b/src/test/java/com/study/realworld/domain/article/domain/persist/ArticleTest.java index e4f4700b..97328720 100644 --- a/src/test/java/com/study/realworld/domain/article/domain/persist/ArticleTest.java +++ b/src/test/java/com/study/realworld/domain/article/domain/persist/ArticleTest.java @@ -6,6 +6,7 @@ import com.study.realworld.domain.article.domain.vo.ArticleTitle; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; import static com.study.realworld.domain.user.domain.persist.UserTest.testDefaultUser; import static org.assertj.core.api.Assertions.assertThat; @@ -24,6 +25,20 @@ public class ArticleTest { ); } + @Test + void 식별자가_값으면_동일한_객체이다() { + final Article article = testArticle(); + final Article other = testArticle(); + + ReflectionTestUtils.setField(article, "articleId", 1L); + ReflectionTestUtils.setField(other, "articleId", 1L); + + assertAll( + () -> assertThat(article).isEqualTo(other), + () -> assertThat(article).hasSameHashCodeAs(other) + ); + } + public static Article testArticle() { return Article.builder() .articleSlug(ArticleSlug.from("how-to-train-your-dragon")) diff --git a/src/test/java/com/study/realworld/domain/follow/domain/FollowQueryDSLRepositoryTest.java b/src/test/java/com/study/realworld/domain/follow/domain/FollowQueryDSLRepositoryTest.java new file mode 100644 index 00000000..bdc14f90 --- /dev/null +++ b/src/test/java/com/study/realworld/domain/follow/domain/FollowQueryDSLRepositoryTest.java @@ -0,0 +1,53 @@ +package com.study.realworld.domain.follow.domain; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.study.realworld.domain.follow.dto.ProfileResponse; +import com.study.realworld.domain.user.domain.persist.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import javax.persistence.EntityManager; + +import static com.study.realworld.domain.user.domain.persist.UserTest.testUser; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DataJpaTest +class FollowQueryDSLRepositoryTest { + + @Autowired + private EntityManager entityManager; + + private FollowQueryDSLRepository followQueryDSLRepository; + + @BeforeEach + void setUp() { + final JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); + followQueryDSLRepository = new FollowQueryDSLRepository(queryFactory); + } + + @Test + void userInfoAndFollowable() { + final User follower = testUser("user1@gmail.com", "user1", "password1", "bio1", "image1"); + final User followee = testUser("user2@gmail.com", "user2", "password2", "bio2", "image2"); + entityManager.persist(follower); + entityManager.persist(followee); + + final Follow follow = Follow.builder() + .follower(follower) + .followee(followee) + .build(); + entityManager.persist(follow); + + final ProfileResponse profileResponse = followQueryDSLRepository.userInfoAndFollowable(followee, follower); + assertAll( + () -> assertThat(profileResponse.userName()).isEqualTo(followee.userName()), + () -> assertThat(profileResponse.userBio()).isEqualTo(followee.userBio()), + () -> assertThat(profileResponse.userImage()).isEqualTo(followee.userImage()), + () -> assertThat(profileResponse.isFollowing()).isEqualTo(true) + ); + } + +} \ No newline at end of file diff --git a/src/test/java/com/study/realworld/domain/follow/domain/FollowTest.java b/src/test/java/com/study/realworld/domain/follow/domain/FollowTest.java index 6d27931b..3f67e08c 100644 --- a/src/test/java/com/study/realworld/domain/follow/domain/FollowTest.java +++ b/src/test/java/com/study/realworld/domain/follow/domain/FollowTest.java @@ -25,15 +25,31 @@ public class FollowTest { ); } + @Test + void 팔로우는_식별자를_기준으로_동등성_비교를한다() { + final User follower = testUser(USER_EMAIL, USER_NAME, USER_PASSWORD, USER_BIO, USER_IMAGE); + final User followee = testUser(OTHER_USER_EMAIL, OTHER_USER_NAME, OTHER_USER_PASSWORD, OTHER_USER_BIO, OTHER_USER_IMAGE); + final Follow follow = testFollow(followee, follower); + final Follow other = testFollow(followee, follower); + + ReflectionTestUtils.setField(follow, "followId", 1L); + ReflectionTestUtils.setField(other, "followId", 1L); + + assertAll( + () -> assertThat(follow).isEqualTo(other), + () -> assertThat(follow).hasSameHashCodeAs(other) + ); + } + @Test void 팔로위_팔로워_정보를_반환한다() { final User follower = testUser(USER_EMAIL, USER_NAME, USER_PASSWORD, USER_BIO, USER_IMAGE); final User followee = testUser(OTHER_USER_EMAIL, OTHER_USER_NAME, OTHER_USER_PASSWORD, OTHER_USER_BIO, OTHER_USER_IMAGE); final Follow follow = testFollow(followee, follower); - ReflectionTestUtils.setField(follow, "id", 1L); + ReflectionTestUtils.setField(follow, "followId", 1L); assertAll( - () -> assertThat(follow.id()).isEqualTo(1L), + () -> assertThat(follow.followId()).isEqualTo(1L), () -> assertThat(follow.followee()).isEqualTo(followee), () -> assertThat(follow.follower()).isEqualTo(follower) ); diff --git a/src/test/java/com/study/realworld/domain/user/domain/persist/UserTest.java b/src/test/java/com/study/realworld/domain/user/domain/persist/UserTest.java index c1750f42..b3c298a6 100644 --- a/src/test/java/com/study/realworld/domain/user/domain/persist/UserTest.java +++ b/src/test/java/com/study/realworld/domain/user/domain/persist/UserTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.util.ReflectionTestUtils; import static com.study.realworld.domain.user.domain.vo.util.UserVOFixture.*; import static org.assertj.core.api.Assertions.assertThat; @@ -29,6 +30,17 @@ public class UserTest { ); } + @Test + void 아이덴티티가_같다면_동일한_객체이다() { + final User user = new User(); + final User other = new User(); + + ReflectionTestUtils.setField(user, "userId", 1L); + ReflectionTestUtils.setField(other, "userId", 1L); + + assertThat(user).isEqualTo(other); + } + @Test void 패스워드를_인코딩_할_수_있다() { final PasswordEncoder passwordEncoder = TestPasswordEncoder.initialize(); diff --git a/src/test/java/com/study/realworld/domain/user/domain/vo/UserPasswordTest.java b/src/test/java/com/study/realworld/domain/user/domain/vo/UserPasswordTest.java index 3b53625a..106e8731 100644 --- a/src/test/java/com/study/realworld/domain/user/domain/vo/UserPasswordTest.java +++ b/src/test/java/com/study/realworld/domain/user/domain/vo/UserPasswordTest.java @@ -1,6 +1,7 @@ package com.study.realworld.domain.user.domain.vo; import com.study.realworld.domain.user.domain.vo.util.TestPasswordEncoder; +import com.study.realworld.domain.user.error.exception.PasswordNullOrEmptyException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; @@ -17,6 +18,7 @@ import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; @DisplayName("사용자 비밀번호(UserPassword)") @@ -68,6 +70,15 @@ public static void close() { ); } + @ParameterizedTest(name = "입력값 : {0}") + @NullAndEmptySource + void 널또는_빈값으로_객체를_생성할_수_없다(final String userPasswordString) { + final PasswordEncoder passwordEncoder = TestPasswordEncoder.initialize(); + assertThatThrownBy(() -> UserPassword.encode(userPasswordString, passwordEncoder)) + .isExactlyInstanceOf(PasswordNullOrEmptyException.class) + .hasMessage("올바르지 않은 패스워드입니다."); + } + @ParameterizedTest(name = "입력값 : {0}") @ValueSource(strings = {"photo", "willy", "jhon", "kathy"}) void 값을_반환할_수_있다(final String userPasswordString) {