Skip to content

Commit

Permalink
[#158] fix: 이미 카테고리 존재 시, 부모, 형제가 주어지지 않은 카테고리를 또 생성하는 경우 예외처리
Browse files Browse the repository at this point in the history
  • Loading branch information
shin-mallang committed Dec 12, 2023
1 parent 4412fbe commit 34fb101
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 100 deletions.
36 changes: 18 additions & 18 deletions src/main/java/com/mallang/category/TieredCategory.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ protected TieredCategory(String name, Member owner) {
this.owner = owner;
}

public void create(
@Nullable T parent,
@Nullable T prevSibling,
@Nullable T nextSibling,
TieredCategoryValidator validator
) {
if (isNulls(parent, prevSibling, nextSibling)) {
validator.validateNoCategories(owner);
return;
}
updateHierarchy(parent, prevSibling, nextSibling);
}

public abstract void validateOwner(Member member);

public void updateHierarchy(
Expand Down Expand Up @@ -107,6 +120,9 @@ private void validateParentAndChildRelation(
@Nullable T nextSibling
) {
if (isNulls(prevSibling, nextSibling)) {
if (parent == null) {
throw new CategoryHierarchyViolationException("카테고리 계층구조 변경 시 부모나 형제들 중 최소 하나와의 관계가 주어져야 합니다.");
}
validateNoChildrenInParent(parent);
}
validateWhenNonNullWithFailCond(
Expand All @@ -123,25 +139,9 @@ private void validateParentAndChildRelation(


private void validateNoChildrenInParent(T parent) {
if (parent == null) {
T root = getRoot();
if (equals(root) && root.getPreviousSibling() == null && root.getNextSibling() == null) {
return;
}
throw new CategoryHierarchyViolationException("존재하는 다른 최상위 카테고리와의 관계가 명시되지 않았습니다.");
} else {
if (!parent.getChildren().isEmpty()) {
throw new CategoryHierarchyViolationException("주어진 부모의 자식 카테고리와의 관계가 명시되지 않았습니다.");
}
}
}

private T getRoot() {
T root = self();
while (root.getParent() != null) {
root = root.getParent();
if (!parent.getChildren().isEmpty()) {
throw new CategoryHierarchyViolationException("주어진 부모의 자식 카테고리와의 관계가 명시되지 않았습니다.");
}
return root;
}

private void validateDuplicatedNameWhenParticipated(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.mallang.category;

import com.mallang.auth.domain.Member;

public interface TieredCategoryValidator {

void validateNoCategories(Member member);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.mallang.post.domain.Post;
import com.mallang.post.domain.PostCategory;
import com.mallang.post.domain.PostCategoryRepository;
import com.mallang.post.domain.PostCategoryValidator;
import com.mallang.post.domain.PostRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -25,26 +26,26 @@ public class PostCategoryService {
private final PostRepository postRepository;
private final MemberRepository memberRepository;
private final PostCategoryRepository postCategoryRepository;
private final PostCategoryValidator postCategoryValidator;

public Long create(CreatePostCategoryCommand command) {
Member member = memberRepository.getById(command.memberId());
Blog blog = blogRepository.getByName(command.blogName());
PostCategory postCategory = new PostCategory(command.name(), member, blog);
updateHierarchy(postCategory, command.parentId(), command.prevId(), command.nextId());
PostCategory parent = postCategoryRepository.getByIdIfIdNotNull(command.parentId());
PostCategory prev = postCategoryRepository.getByIdIfIdNotNull(command.prevId());
PostCategory next = postCategoryRepository.getByIdIfIdNotNull(command.nextId());
postCategory.create(parent, prev, next, postCategoryValidator);
return postCategoryRepository.save(postCategory).getId();
}

public void updateHierarchy(UpdatePostCategoryHierarchyCommand command) {
Member member = memberRepository.getById(command.memberId());
PostCategory target = postCategoryRepository.getById(command.categoryId());
target.validateOwner(member);
updateHierarchy(target, command.parentId(), command.prevId(), command.nextId());
}

private void updateHierarchy(PostCategory target, Long parentId, Long prevId, Long nextId) {
PostCategory parent = postCategoryRepository.getByIdIfIdNotNull(parentId);
PostCategory prev = postCategoryRepository.getByIdIfIdNotNull(prevId);
PostCategory next = postCategoryRepository.getByIdIfIdNotNull(nextId);
PostCategory parent = postCategoryRepository.getByIdIfIdNotNull(command.parentId());
PostCategory prev = postCategoryRepository.getByIdIfIdNotNull(command.prevId());
PostCategory next = postCategoryRepository.getByIdIfIdNotNull(command.nextId());
target.updateHierarchy(parent, prev, next);
}

Expand Down
17 changes: 9 additions & 8 deletions src/main/java/com/mallang/post/application/StarGroupService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.mallang.post.domain.star.PostStarRepository;
import com.mallang.post.domain.star.StarGroup;
import com.mallang.post.domain.star.StarGroupRepository;
import com.mallang.post.domain.star.StarGroupValidator;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -21,25 +22,25 @@ public class StarGroupService {
private final MemberRepository memberRepository;
private final PostStarRepository postStarRepository;
private final StarGroupRepository starGroupRepository;
private final StarGroupValidator starGroupValidator;

public Long create(CreateStarGroupCommand command) {
Member member = memberRepository.getById(command.memberId());
StarGroup group = new StarGroup(command.name(), member);
updateHierarchy(group, command.parentId(), command.prevId(), command.nextId());
StarGroup parent = starGroupRepository.getByIdIfIdNotNull(command.parentId());
StarGroup prev = starGroupRepository.getByIdIfIdNotNull(command.prevId());
StarGroup next = starGroupRepository.getByIdIfIdNotNull(command.nextId());
group.create(parent, prev, next, starGroupValidator);
return starGroupRepository.save(group).getId();
}

public void updateHierarchy(UpdateStarGroupHierarchyCommand command) {
Member member = memberRepository.getById(command.memberId());
StarGroup target = starGroupRepository.getById(command.groupId());
target.validateOwner(member);
updateHierarchy(target, command.parentId(), command.prevId(), command.nextId());
}

private void updateHierarchy(StarGroup target, Long parentId, Long prevId, Long nextId) {
StarGroup parent = starGroupRepository.getByIdIfIdNotNull(parentId);
StarGroup prev = starGroupRepository.getByIdIfIdNotNull(prevId);
StarGroup next = starGroupRepository.getByIdIfIdNotNull(nextId);
StarGroup parent = starGroupRepository.getByIdIfIdNotNull(command.parentId());
StarGroup prev = starGroupRepository.getByIdIfIdNotNull(command.prevId());
StarGroup next = starGroupRepository.getByIdIfIdNotNull(command.nextId());
target.updateHierarchy(parent, prev, next);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mallang.post.domain;

import com.mallang.auth.domain.Member;
import com.mallang.post.exception.NotFoundPostCategoryException;
import jakarta.annotation.Nullable;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -17,4 +18,6 @@ default PostCategory getByIdIfIdNotNull(@Nullable Long categoryId) {
}
return getById(categoryId);
}

boolean existsByOwner(Member member);
}
21 changes: 21 additions & 0 deletions src/main/java/com/mallang/post/domain/PostCategoryValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.mallang.post.domain;

import com.mallang.auth.domain.Member;
import com.mallang.category.CategoryHierarchyViolationException;
import com.mallang.category.TieredCategoryValidator;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class PostCategoryValidator implements TieredCategoryValidator {

private final PostCategoryRepository postCategoryRepository;

@Override
public void validateNoCategories(Member member) {
if (postCategoryRepository.existsByOwner(member)) {
throw new CategoryHierarchyViolationException("이미 존재하는 카테고리가 있습니다.");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.mallang.post.domain.star;

import com.mallang.auth.domain.Member;
import com.mallang.post.exception.NotFoundStarGroupException;
import jakarta.annotation.Nullable;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -17,4 +18,6 @@ default StarGroup getByIdIfIdNotNull(@Nullable Long id) {
}
return getById(id);
}

boolean existsByOwner(Member member);
}
21 changes: 21 additions & 0 deletions src/main/java/com/mallang/post/domain/star/StarGroupValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.mallang.post.domain.star;

import com.mallang.auth.domain.Member;
import com.mallang.category.CategoryHierarchyViolationException;
import com.mallang.category.TieredCategoryValidator;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class StarGroupValidator implements TieredCategoryValidator {

private final StarGroupRepository starGroupRepository;

@Override
public void validateNoCategories(Member member) {
if (starGroupRepository.existsByOwner(member)) {
throw new CategoryHierarchyViolationException("이미 존재하는 즐겨찾기 그룹이 있습니다.");
}
}
}
118 changes: 52 additions & 66 deletions src/test/java/com/mallang/category/TieredCategoryTestTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import com.mallang.auth.domain.Member;
import com.mallang.common.execption.MallangLogException;
import java.util.List;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand All @@ -15,6 +21,9 @@ public abstract class TieredCategoryTestTemplate<T extends TieredCategory<T>> {

protected final Member member = 깃허브_말랑(1L);
protected final Member otherMember = 깃허브_동훈(2L);
protected final TieredCategoryValidator validator = mock(TieredCategoryValidator.class);

protected abstract T spyCategory(String name, Member owner);

protected abstract T createRoot(String name, Member owner);

Expand All @@ -24,15 +33,54 @@ public abstract class TieredCategoryTestTemplate<T extends TieredCategory<T>> {

protected abstract Class<?> 권한_없음_예외();

protected abstract Class<? extends MallangLogException> 회원의_카테고리_없음_검증_실패_시_발생할_예외();

@Nested
protected class 생성_시 {

@Test
void 생성한다() {
void 최초의_루트_카테고리_생성() {
// given
T mock = spyCategory("root", member);

// when & then
assertDoesNotThrow(() -> {
createRoot("최상위", member);
mock.create(null, null, null, validator);
});
verify(mock, times(0))
.updateHierarchy(any(), any(), any());
}

@Test
void 이미_다른_카테고리가_존재하는_상황에서_부모와_형제가_모두_null_이면_예외() {
// given
T root = createRoot("root", member);
willThrow(회원의_카테고리_없음_검증_실패_시_발생할_예외())
.given(validator)
.validateNoCategories(any());

// when & then
assertThatThrownBy(() -> {
root.create(null, null, null, validator);
}).isInstanceOf(회원의_카테고리_없음_검증_실패_시_발생할_예외());
}

@Test
void 부모와_형제가_모두_null_이_아니면_계층_업데이트_메서드를_호출하여_계층구조를_설정한다() {
// given
T child = spyCategory("child", member);
T root = createRoot("root", member);

// when & then
child.create(root, null, null, validator);
verify(child, times(1))
.updateHierarchy(any(), any(), any());
child.create(null, root, null, validator);
verify(child, times(2))
.updateHierarchy(any(), any(), any());
child.create(null, null, root, validator);
verify(child, times(3))
.updateHierarchy(any(), any(), any());
}
}

Expand Down Expand Up @@ -420,76 +468,15 @@ class 직전_형제와_다음_형제가_주어졌을_때 {
class 형제들이_주어지지_않았을_때 {

@Test
void 부모가_주어지지_않았으며_루트의_형제가_하나라도_존재한다면_예외() {
void 부모가_주어지지_않으면_예외() {
// given
T target = createRoot("target", member);
T prev = createRoot("prev", member);
prev.updateHierarchy(null, null, target);

// when & then
assertThatThrownBy(() -> {
target.updateHierarchy(null, null, null);
}).isInstanceOf(CategoryHierarchyViolationException.class)
.hasMessage("존재하는 다른 최상위 카테고리와의 관계가 명시되지 않았습니다.");
assertThatThrownBy(() -> {
prev.updateHierarchy(null, null, null);
}).isInstanceOf(CategoryHierarchyViolationException.class)
.hasMessage("존재하는 다른 최상위 카테고리와의 관계가 명시되지 않았습니다.");
}

@Test
void 부모가_주어지지_않았으며_루트의_형제가_존재하지_않을_때_내가_루트라면_업데이트() {
// given
T target = createRoot("target", member);

// when & then
assertDoesNotThrow(() -> {
target.updateHierarchy(null, null, null);
});
}

@Test
void 부모가_주어지지_않았으며_루트의_형제가_존재하지_않을_때_내가_루트가_아니라면_예외() {
// given
T root = createRoot("root", member);
T child = createRoot("child", member);
child.updateHierarchy(root, null, null);

// when & then
assertThatThrownBy(() -> {
child.updateHierarchy(null, null, null);
}).isInstanceOf(CategoryHierarchyViolationException.class)
.hasMessage("존재하는 다른 최상위 카테고리와의 관계가 명시되지 않았습니다.");
}

@Test
void 부모가_주어지지_않았으며_내가_루트가_아닌_경우_예외() {
// given
T root = createRoot("root", member);
T child = createRoot("target", member);
child.updateHierarchy(root, null, null);
T descendant = createRoot("descendant", member);
descendant.updateHierarchy(child, null, null);

// when
assertThatThrownBy(() -> {
child.updateHierarchy(null, null, null);
}).isInstanceOf(CategoryHierarchyViolationException.class)
.hasMessage("존재하는 다른 최상위 카테고리와의 관계가 명시되지 않았습니다.");
}

@Test
void 부모가_주어지지_않았으며_내가_루트이나_내_형제가_존재하면_예외() {
// given
T root = createRoot("root", member);
T next = createRoot("next", member);
next.updateHierarchy(null, root, null);

// when
assertThatThrownBy(() -> {
root.updateHierarchy(null, null, null);
}).isInstanceOf(CategoryHierarchyViolationException.class)
.hasMessage("존재하는 다른 최상위 카테고리와의 관계가 명시되지 않았습니다.");
.hasMessage("카테고리 계층구조 변경 시 부모나 형제들 중 최소 하나와의 관계가 주어져야 합니다.");
}

@Test
Expand Down Expand Up @@ -606,7 +593,6 @@ class 계층_참여_시_중복_이름이_존재하게_되는_경우 {
// given
T prev = createRoot("prev", member);
T next = createRoot("next", member);
prev.updateHierarchy(null, null, null);
next.updateHierarchy(null, prev, null);

T target = createRoot("next", member);
Expand Down
Loading

0 comments on commit 34fb101

Please sign in to comment.