diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java index 85e4938..ec8643b 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/controller/CategoryController.java @@ -1,17 +1,25 @@ package com.starchive.springapp.category.controller; +import com.starchive.springapp.category.dto.CategoryCreateRequest; import com.starchive.springapp.category.dto.CategoryDto; +import com.starchive.springapp.category.dto.CategoryUpdateRequest; +import com.starchive.springapp.category.dto.CategoryUpdateResponse; import com.starchive.springapp.category.service.CategoryService; import com.starchive.springapp.global.dto.ResponseDto; import com.starchive.springapp.hashtag.dto.HashTagDto; import com.starchive.springapp.hashtag.service.HashTagService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Null; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RestController; @RestController @@ -36,4 +44,28 @@ public ResponseEntity>> showHashTags(@PathVariable( ResponseDto> listResponseDto = new ResponseDto<>(categories); return ResponseEntity.ok(listResponseDto); } + + @PostMapping("/categories") + @Operation(summary = "카테고리 생성") + public ResponseEntity createCategory(@Valid CategoryCreateRequest categoryCreateRequest) { + categoryService.create(categoryCreateRequest); + + return ResponseEntity.noContent().build(); + } + + @PutMapping("/categories") + @Operation(summary = "카테고리 수정") + public ResponseEntity> updateCategory( + @Valid CategoryUpdateRequest categoryUpdateRequest) { + CategoryUpdateResponse updateResponse = categoryService.update(categoryUpdateRequest); + + return ResponseEntity.ok(new ResponseDto<>(updateResponse)); + } + + @DeleteMapping("/categories/{categoryId}") + @Operation(summary = "카테고리 삭제") + public ResponseEntity deleteCategory(@PathVariable("categoryId") Long categoryId) { + categoryService.delete(categoryId); + return ResponseEntity.noContent().build(); + } } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java index b4c63e8..4a74d6d 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/domain/Category.java @@ -2,6 +2,7 @@ import static jakarta.persistence.FetchType.LAZY; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -33,7 +34,7 @@ public class Category { @Column(length = 100) String name; - @OneToMany(mappedBy = "parent", fetch = LAZY) + @OneToMany(mappedBy = "parent", fetch = LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List children = new ArrayList<>(); public Category(String name, Category parent) { @@ -44,9 +45,15 @@ public Category(String name, Category parent) { } } - private void changeParent(Category parent) { + public void changeName(String name) { + this.name = name; + } + + public void changeParent(Category parent) { this.parent = parent; - parent.getChildren().add(this); + if (parent != null) { + parent.getChildren().add(this); + } } } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryCreateRequest.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryCreateRequest.java new file mode 100644 index 0000000..2821eed --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryCreateRequest.java @@ -0,0 +1,20 @@ +package com.starchive.springapp.category.dto; + +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CategoryCreateRequest { + @Size(max = 100) + @NotNull + private String name; + + @Nullable + private Long parentId; +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateRequest.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateRequest.java new file mode 100644 index 0000000..1eab455 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateRequest.java @@ -0,0 +1,23 @@ +package com.starchive.springapp.category.dto; + +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CategoryUpdateRequest { + @NotNull + private Long categoryId; + + @Size(max = 100) + @NotNull + private String name; + + @Nullable + private Long parentId; +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateResponse.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateResponse.java new file mode 100644 index 0000000..2227807 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/dto/CategoryUpdateResponse.java @@ -0,0 +1,29 @@ +package com.starchive.springapp.category.dto; + +import com.starchive.springapp.category.domain.Category; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CategoryUpdateResponse { + private Long categoryId; + + private String name; + + private Long parentId; + + public static CategoryUpdateResponse from(Category category) { + CategoryUpdateResponse categoryUpdateResponse = new CategoryUpdateResponse(); + categoryUpdateResponse.categoryId = category.getId(); + categoryUpdateResponse.name = category.getName(); + if (category.getParent() == null) { + categoryUpdateResponse.parentId = null; + } else { + categoryUpdateResponse.parentId = category.getParent().getId(); + } + return categoryUpdateResponse; + } +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/exception/CategoryAlreadyExistsException.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/exception/CategoryAlreadyExistsException.java new file mode 100644 index 0000000..afb2c70 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/exception/CategoryAlreadyExistsException.java @@ -0,0 +1,26 @@ +package com.starchive.springapp.category.exception; + +import static com.starchive.springapp.global.ErrorMessage.ALREADY_EXISTS_CATEGORY; + +public class CategoryAlreadyExistsException extends RuntimeException { + public CategoryAlreadyExistsException() { + super(ALREADY_EXISTS_CATEGORY); + } + + public CategoryAlreadyExistsException(String message) { + super(message); + } + + public CategoryAlreadyExistsException(String message, Throwable cause) { + super(message, cause); + } + + public CategoryAlreadyExistsException(Throwable cause) { + super(cause); + } + + protected CategoryAlreadyExistsException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/repository/CategoryRepository.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/repository/CategoryRepository.java index 37d0bc6..3cd4ddb 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/category/repository/CategoryRepository.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/repository/CategoryRepository.java @@ -9,8 +9,14 @@ public interface CategoryRepository extends JpaRepository { @Query("SELECT c FROM Category c LEFT JOIN FETCH c.children left join fetch c.parent WHERE c.id = :id") - Optional findByIdWithChildren(@Param("id") Long id); + Optional findByIdWithParentAndChildren(@Param("id") Long id); @Query("SELECT DISTINCT c FROM Category c LEFT JOIN FETCH c.children WHERE c.parent IS NULL") List findRootCategoriesWithChildren(); + + Optional findByName(@Param("name") String name); + + @Query("select c from Category c left join fetch c.parent where c.id = :id") + Optional findByIdWithParent(@Param("id") Long id); + } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/category/service/CategoryService.java b/BACK/spring-app/src/main/java/com/starchive/springapp/category/service/CategoryService.java index 77ed129..1703657 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/category/service/CategoryService.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/category/service/CategoryService.java @@ -1,17 +1,26 @@ package com.starchive.springapp.category.service; import com.starchive.springapp.category.domain.Category; +import com.starchive.springapp.category.dto.CategoryCreateRequest; import com.starchive.springapp.category.dto.CategoryDto; +import com.starchive.springapp.category.dto.CategoryUpdateRequest; +import com.starchive.springapp.category.dto.CategoryUpdateResponse; +import com.starchive.springapp.category.exception.CategoryAlreadyExistsException; import com.starchive.springapp.category.exception.CategoryNotFoundException; import com.starchive.springapp.category.repository.CategoryRepository; +import com.starchive.springapp.post.repository.PostRepository; +import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service +@Transactional @RequiredArgsConstructor public class CategoryService { private final CategoryRepository categoryRepository; + private final PostRepository postRepository; public List findAll() { List rootCateGories = categoryRepository.findRootCategoriesWithChildren(); @@ -19,8 +28,99 @@ public List findAll() { } public Category findOne(Long id) { - return categoryRepository.findByIdWithChildren(id).orElseThrow(CategoryNotFoundException::new); + return categoryRepository.findByIdWithParentAndChildren(id).orElseThrow(CategoryNotFoundException::new); } + public void create(CategoryCreateRequest categoryCreateRequest) { + categoryRepository.findByName(categoryCreateRequest.getName()).ifPresent(category -> { + throw new CategoryAlreadyExistsException(); + }); + + if (categoryCreateRequest.getParentId() == null) { + Category category = new Category(categoryCreateRequest.getName(), null); + categoryRepository.save(category); + return; + } + + Category parentCategory = categoryRepository.findById(categoryCreateRequest.getParentId()) + .orElseThrow(() -> new CategoryNotFoundException("존재하지 않는 부모 카테고리입니다.")); + + Category category = new Category(categoryCreateRequest.getName(), parentCategory); + categoryRepository.save(category); + + } + + public CategoryUpdateResponse update(CategoryUpdateRequest categoryUpdateRequest) { + Category category = categoryRepository.findByIdWithParent(categoryUpdateRequest.getCategoryId()) + .orElseThrow(CategoryNotFoundException::new); + + if (!category.getName().equals(categoryUpdateRequest.getName())) { + categoryRepository.findByName(categoryUpdateRequest.getName()).ifPresent(findOne -> { + throw new CategoryAlreadyExistsException(categoryUpdateRequest.getName() + " 은 이미 존재하는 카테고리 이름입니다."); + }); + + category.changeName(categoryUpdateRequest.getName()); + } + + updateParentCategory(categoryUpdateRequest, category); + + return CategoryUpdateResponse.from(category); + } + + public void delete(Long id) { + if (id == 0) { + throw new RuntimeException("삭제할 수 없는 카테고리입니다."); + } + Category category = categoryRepository.findByIdWithParentAndChildren(id) + .orElseThrow(CategoryNotFoundException::new); + + List categoryIds = new ArrayList<>(); + categoryIds.add(category.getId()); + + if (!category.getChildren().isEmpty()) { + for (Category child : category.getChildren()) { + categoryIds.add(child.getId()); + } + } + + postRepository.bulkUpdateToNoneCategory(categoryIds); + + categoryRepository.deleteById(category.getId()); + } + + private Category updateParentCategory(CategoryUpdateRequest categoryUpdateRequest, Category category) { + Long newParentId = categoryUpdateRequest.getParentId(); + if (category.getParent() != null && newParentId == null) { + category.changeParent(null); + return category; + } + + if (category.getParent() != null && newParentId != null) { + + Category parentCategory = findParentCategoryById(newParentId); + + if (category.getParent().getId() != newParentId) { + category.changeParent(parentCategory); + } + + return category; + } + + if (category.getParent() == null && newParentId != null) { + + Category parentCategory = findParentCategoryById(newParentId); + + category.changeParent(parentCategory); + + return category; + } + + return category; + } + + private Category findParentCategoryById(Long parentId) { + return categoryRepository.findById(parentId) + .orElseThrow(() -> new CategoryNotFoundException("존재하지 않는 부모 카테고리입니다.")); + } } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java b/BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java index a96681d..24bd0d5 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/global/ErrorMessage.java @@ -7,4 +7,5 @@ public class ErrorMessage { final public static String CATEGORY_NOT_FOUND = "카테고리가 존재하지 않습니다."; final public static String HASHTAG_NOT_FOUND = "해쉬태그가 존재하지 않습니다."; final public static String POST_NOT_FOUND = "게시글이 존재하지 않습니다."; + final public static String ALREADY_EXISTS_CATEGORY = "이미 존재하는 카테고리 이름입니다."; } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/global/dto/ErrorResult.java b/BACK/spring-app/src/main/java/com/starchive/springapp/global/dto/ErrorResult.java new file mode 100644 index 0000000..edd7604 --- /dev/null +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/global/dto/ErrorResult.java @@ -0,0 +1,12 @@ +package com.starchive.springapp.global.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.springframework.http.HttpStatus; + +@Data +@AllArgsConstructor +public class ErrorResult { + private HttpStatus code; + private String message; +} diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java b/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java index abba3eb..c6c6142 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/global/exception/GlobalExceptionHandler.java @@ -1,8 +1,8 @@ package com.starchive.springapp.global.exception; -import static com.starchive.springapp.global.ErrorMessage.INVALID_FILE_SIZE; - +import com.starchive.springapp.category.exception.CategoryAlreadyExistsException; import com.starchive.springapp.category.exception.CategoryNotFoundException; +import com.starchive.springapp.global.dto.ErrorResult; import com.starchive.springapp.hashtag.exception.HashTagNotFoundException; import com.starchive.springapp.post.exception.PostNotFoundException; import java.util.HashMap; @@ -28,21 +28,23 @@ public ResponseEntity> handleValidationExceptions(MethodArgu return ResponseEntity.badRequest().body(errors); } - @ExceptionHandler(CategoryNotFoundException.class) - public ResponseEntity handleMaxSizeException(CategoryNotFoundException ex) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(ex.getMessage()); + @ExceptionHandler({CategoryNotFoundException.class, PostNotFoundException.class, HashTagNotFoundException.class}) + public ResponseEntity handleNotFoundException(RuntimeException ex) { + ErrorResult errorResult = new ErrorResult(HttpStatus.BAD_REQUEST, ex.getMessage()); + return ResponseEntity.badRequest() + .body(errorResult); } - @ExceptionHandler(PostNotFoundException.class) - public ResponseEntity handlePostNotFoundException(PostNotFoundException ex) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(ex.getMessage()); + @ExceptionHandler({CategoryAlreadyExistsException.class}) + public ResponseEntity handleAlreadyExistsException(RuntimeException ex) { + ErrorResult errorResult = new ErrorResult(HttpStatus.BAD_REQUEST, ex.getMessage()); + return ResponseEntity.badRequest() + .body(errorResult); } - @ExceptionHandler(HashTagNotFoundException.class) - public ResponseEntity handleHashTagNotFoundException(HashTagNotFoundException ex) { - return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE) - .body(INVALID_FILE_SIZE); + @ExceptionHandler(RuntimeException.class) + public ResponseEntity handleRuntimeException(RuntimeException ex) { + ErrorResult errorResult = new ErrorResult(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage()); + return ResponseEntity.internalServerError().body(errorResult); } } diff --git a/BACK/spring-app/src/main/java/com/starchive/springapp/post/repository/PostRepository.java b/BACK/spring-app/src/main/java/com/starchive/springapp/post/repository/PostRepository.java index 6622a6e..173a4ae 100644 --- a/BACK/spring-app/src/main/java/com/starchive/springapp/post/repository/PostRepository.java +++ b/BACK/spring-app/src/main/java/com/starchive/springapp/post/repository/PostRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -16,4 +17,7 @@ Page findManyByCategoryIds(@Param("categoryIds") List categoryIds, @Param("postIds") List postIds, Pageable pageable); + @Modifying + @Query("update Post p set p.category.id = 0 where p.category.id in :categoryIds") + int bulkUpdateToNoneCategory(@Param("categoryIds") List categoryIds); } diff --git a/BACK/spring-app/src/main/resources/data.sql b/BACK/spring-app/src/main/resources/data.sql new file mode 100644 index 0000000..3f8b778 --- /dev/null +++ b/BACK/spring-app/src/main/resources/data.sql @@ -0,0 +1 @@ +INSERT IGNORE INTO categories (categoryId, name) VALUES (0, '카테고리 없음'); \ No newline at end of file diff --git a/BACK/spring-app/src/test/java/com/starchive/springapp/category/dto/CategoryDtoTest.java b/BACK/spring-app/src/test/java/com/starchive/springapp/category/dto/CategoryDtoTest.java index c733d82..3ec2d1f 100644 --- a/BACK/spring-app/src/test/java/com/starchive/springapp/category/dto/CategoryDtoTest.java +++ b/BACK/spring-app/src/test/java/com/starchive/springapp/category/dto/CategoryDtoTest.java @@ -35,7 +35,7 @@ class CategoryDtoTest { entityManager.flush(); entityManager.clear(); System.out.println("---------------------쿼리3"); - Category findOne = categoryRepository.findByIdWithChildren(parent.getId()).get(); + Category findOne = categoryRepository.findByIdWithParentAndChildren(parent.getId()).get(); System.out.println("---------------------쿼리4"); //when diff --git a/BACK/spring-app/src/test/java/com/starchive/springapp/category/service/CategoryServiceTest.java b/BACK/spring-app/src/test/java/com/starchive/springapp/category/service/CategoryServiceTest.java index 492c85c..18ad713 100644 --- a/BACK/spring-app/src/test/java/com/starchive/springapp/category/service/CategoryServiceTest.java +++ b/BACK/spring-app/src/test/java/com/starchive/springapp/category/service/CategoryServiceTest.java @@ -1,15 +1,25 @@ package com.starchive.springapp.category.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.starchive.springapp.category.domain.Category; +import com.starchive.springapp.category.dto.CategoryCreateRequest; import com.starchive.springapp.category.dto.CategoryDto; +import com.starchive.springapp.category.dto.CategoryUpdateRequest; +import com.starchive.springapp.category.dto.CategoryUpdateResponse; +import com.starchive.springapp.category.exception.CategoryAlreadyExistsException; +import com.starchive.springapp.category.exception.CategoryNotFoundException; import com.starchive.springapp.category.repository.CategoryRepository; +import com.starchive.springapp.post.domain.Post; +import com.starchive.springapp.post.repository.PostRepository; +import jakarta.persistence.EntityManager; +import java.time.LocalDateTime; import java.util.List; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; import org.springframework.transaction.annotation.Transactional; @SpringBootTest @@ -19,9 +29,13 @@ class CategoryServiceTest { CategoryRepository categoryRepository; @Autowired CategoryService categoryService; + @Autowired + PostRepository postRepository; + @Autowired + EntityManager em; - @BeforeEach - void init() { + @Test + void 전체_목록_조회_테스트() { //given Category parent1 = new Category("알고리즘", null); Category child1 = new Category("자료구조", parent1); @@ -35,11 +49,6 @@ void init() { categoryRepository.save(parent2); categoryRepository.save(child3); categoryRepository.save(parent3); - } - - - @Test - void 전체_목록_조회_테스트() { // when List response = categoryService.findAll(); @@ -53,4 +62,199 @@ void init() { assertThat(response.get(1).getChildren().get(0).getName()).isEqualTo("요구사항"); } + @Test + public void 루트_카테고리_생성_테스트() throws Exception { + //given + CategoryCreateRequest request = new CategoryCreateRequest("알고리즘", null); + + //when + categoryService.create(request); + List categories = categoryRepository.findAll(); + Category category = categories.get(0); + + //then + assertThat(categoryRepository.findAll().size()).isEqualTo(1); + assertThat(category.getName()).isEqualTo("알고리즘"); + } + + @Test + public void 하위_카테고리_생성_테스트() throws Exception { + //given + CategoryCreateRequest request1 = new CategoryCreateRequest("알고리즘", null); + categoryService.create(request1); + + Category category = categoryRepository.findByName("알고리즘").get(); + + CategoryCreateRequest request2 = new CategoryCreateRequest("DP", category.getId()); + categoryService.create(request2); + //when + Category findOne = categoryRepository.findByName("DP").get(); + //then + assertThat(categoryRepository.findAll().size()).isEqualTo(2); + assertThat(findOne.getName()).isEqualTo("DP"); + assertThat(findOne.getParent().getName()).isEqualTo("알고리즘"); + + } + + @Test + public void 이미_존재하는_카테고리_이름_생성_테스트() throws Exception { + //given + CategoryCreateRequest request1 = new CategoryCreateRequest("알고리즘", null); + categoryService.create(request1); + CategoryCreateRequest request2 = new CategoryCreateRequest("알고리즘", null); + //when + //then + + assertThatThrownBy(() -> categoryService.create(request2)).isInstanceOf(CategoryAlreadyExistsException.class) + .hasMessage("이미 존재하는 카테고리 이름입니다."); + } + + + @Test + public void 카테고리_이름_수정_테스트() throws Exception { + //given + Category category = new Category("알고리즘", null); + categoryRepository.save(category); + + CategoryUpdateRequest updateRequest = new CategoryUpdateRequest(category.getId(), "프로젝트", null); + + //when + CategoryUpdateResponse updateResponse = categoryService.update(updateRequest); + + Category findOne = categoryRepository.findById(category.getId()).get(); + //then + assertThat(findOne.getName()).isEqualTo("프로젝트"); + } + + @Test + public void 카테고리_다른_부모로_수정_테스트() throws Exception { + //given + Category parent1 = new Category("프로젝트", null); + categoryRepository.save(parent1); + Category parent2 = new Category("알고리즘", null); + categoryRepository.save(parent2); + Category child = new Category("DP", parent1); + categoryRepository.save(child); + + CategoryUpdateRequest updateRequest = new CategoryUpdateRequest(child.getId(), "DP", parent2.getId()); + + //when + CategoryUpdateResponse updateResponse = categoryService.update(updateRequest); + + Category findOne = categoryRepository.findById(child.getId()).get(); + //then + assertThat(findOne.getName()).isEqualTo("DP"); + assertThat(findOne.getParent().getName()).isEqualTo("알고리즘"); + } + + @Test + public void 카테고리_루트카테고리로_수정_테스트() throws Exception { + //given + Category parent1 = new Category("알고리즘", null); + categoryRepository.save(parent1); + Category child = new Category("DP", parent1); + categoryRepository.save(child); + + CategoryUpdateRequest updateRequest = new CategoryUpdateRequest(child.getId(), "DP", null); + + //when + CategoryUpdateResponse updateResponse = categoryService.update(updateRequest); + + Category findOne = categoryRepository.findById(child.getId()).get(); + //then + assertThat(findOne.getName()).isEqualTo("DP"); + assertThat(findOne.getParent()).isNull(); + } + + @Test + public void 카테고리_수정_이미_존재하는_이름예외_테스트() throws Exception { + //given + Category category = new Category("알고리즘", null); + categoryRepository.save(category); + Category category1 = new Category("프로젝트", null); + categoryRepository.save(category1); + + CategoryUpdateRequest updateRequest = new CategoryUpdateRequest(category.getId(), "프로젝트", null); + + //when + assertThatThrownBy(() -> categoryService.update(updateRequest)).isInstanceOf( + CategoryAlreadyExistsException.class); + } + + @Test + public void 카테고리_수정_존재하지않는_부모카테고리로_수정_테스트() throws Exception { + //given + Category category = new Category("알고리즘", null); + categoryRepository.save(category); + + CategoryUpdateRequest updateRequest = new CategoryUpdateRequest(category.getId(), "프로젝트", -1L); + + //when + assertThatThrownBy(() -> categoryService.update(updateRequest)).isInstanceOf(CategoryNotFoundException.class); + } + + @Test + @Sql(statements = "INSERT INTO Categories (categoryId, name) VALUES (0, 'Default Category')") + public void 자식_카테고리_삭제_테스트() throws Exception { + //given + Category parent1 = new Category("알고리즘", null); + categoryRepository.save(parent1); + Category child = new Category("DP", parent1); + categoryRepository.save(child); + Category child2 = new Category("greedy", parent1); + categoryRepository.save(child2); + Post post1 = new Post(null, "title1", "content", "author1", "123", LocalDateTime.now(), child); + postRepository.save(post1); + + //when + em.flush(); + em.clear(); + categoryService.delete(child.getId()); + em.flush(); + em.clear(); + post1 = postRepository.findById(post1.getId()).orElseThrow(); + List categories = categoryRepository.findAll(); + for (Category category : categories) { + System.out.println(category.getName()); + } + + //then + assertThat(categories.size()).isEqualTo(3); + assertThat(post1.getCategory().getId()).isEqualTo(0); + } + + @Test + @Sql(statements = "INSERT INTO Categories (categoryId, name) VALUES (0, 'Default Category')") + public void 부모_카테고리_삭제_테스트() throws Exception { + //given + Category parent1 = new Category("알고리즘", null); + categoryRepository.save(parent1); + Category child = new Category("DP", parent1); + categoryRepository.save(child); + Category child2 = new Category("greedy", parent1); + categoryRepository.save(child2); + Post post1 = new Post(null, "title1", "content", "author1", "123", LocalDateTime.now(), parent1); + Post post2 = new Post(null, "title1", "content", "author1", "123", LocalDateTime.now(), child); + Post post3 = new Post(null, "title1", "content", "author1", "123", LocalDateTime.now(), child2); + postRepository.save(post1); + postRepository.save(post2); + postRepository.save(post3); + + //when + categoryService.delete(parent1.getId()); + + em.flush(); + em.clear(); + post1 = postRepository.findById(post1.getId()).orElseThrow(); + post2 = postRepository.findById(post2.getId()).orElseThrow(); + post3 = postRepository.findById(post3.getId()).orElseThrow(); + + //then + assertThat(categoryRepository.findAll().size()).isEqualTo(1); + assertThat(post1.getCategory().getId()).isEqualTo(0); + assertThat(post2.getCategory().getId()).isEqualTo(0); + assertThat(post3.getCategory().getId()).isEqualTo(0); + } + + } \ No newline at end of file