-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
Merge pull request #24 from taco-official/KL-87/카테고리-소분류-목록조회-api-구현
feat(Kl-87) : add Subcategory Api
- KL-163/프론트-요구사항-처리
- (#24)
- KL-184/서버-배포
- (#24)
- develop
- (#72, #24)
Showing
13 changed files
with
446 additions
and
1 deletion.
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
src/main/java/taco/klkl/domain/subcategory/controller/SubcategoryController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package taco.klkl.domain.subcategory.controller; | ||
|
||
import java.util.List; | ||
|
||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import taco.klkl.domain.subcategory.dto.response.SubcategoryResponseDto; | ||
import taco.klkl.domain.subcategory.sevice.SubcategoryService; | ||
|
||
@Slf4j | ||
@Tag(name = "7. 서브카테고리", description = "서브카테고리 관련 API") | ||
@RestController | ||
@RequestMapping("/v1/categories/{id}/subcategories") | ||
@RequiredArgsConstructor | ||
public class SubcategoryController { | ||
private final SubcategoryService subcategoryService; | ||
|
||
@GetMapping | ||
public ResponseEntity<List<SubcategoryResponseDto>> getSubcategory(@PathVariable Long id) { | ||
List<SubcategoryResponseDto> subcategories = subcategoryService.getSubcategories(id); | ||
return ResponseEntity.ok(subcategories); | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
src/main/java/taco/klkl/domain/subcategory/convert/SubcategoryNameConverter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package taco.klkl.domain.subcategory.convert; | ||
|
||
import jakarta.persistence.AttributeConverter; | ||
import jakarta.persistence.Converter; | ||
import taco.klkl.domain.subcategory.domain.SubcategoryName; | ||
|
||
@Converter(autoApply = true) | ||
public class SubcategoryNameConverter implements AttributeConverter<SubcategoryName, String> { | ||
|
||
@Override | ||
public String convertToDatabaseColumn(SubcategoryName subcategoryName) { | ||
return subcategoryName.getName(); | ||
} | ||
|
||
@Override | ||
public SubcategoryName convertToEntityAttribute(String name) { | ||
return SubcategoryName.getByName(name); | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/main/java/taco/klkl/domain/subcategory/dao/SubcategoryRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package taco.klkl.domain.subcategory.dao; | ||
|
||
import java.util.List; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
import taco.klkl.domain.subcategory.domain.Subcategory; | ||
|
||
@Repository | ||
public interface SubcategoryRepository extends JpaRepository<Subcategory, Long> { | ||
List<Subcategory> findAllByCategoryId(Long id); | ||
} |
39 changes: 39 additions & 0 deletions
39
src/main/java/taco/klkl/domain/subcategory/domain/Subcategory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package taco.klkl.domain.subcategory.domain; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import taco.klkl.domain.category.domain.Category; | ||
|
||
@Getter | ||
@Entity | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class Subcategory { | ||
@Id | ||
@Column(name = "subcategory_id") | ||
@GeneratedValue(strategy = GenerationType.AUTO) | ||
private Long id; | ||
|
||
@ManyToOne | ||
@JoinColumn(name = "category_id") | ||
private Category category; | ||
|
||
@Column(name = "name") | ||
private SubcategoryName name; | ||
|
||
private Subcategory(Category category, SubcategoryName name) { | ||
this.category = category; | ||
this.name = name; | ||
} | ||
|
||
public static Subcategory of(Category category, SubcategoryName name) { | ||
return new Subcategory(category, name); | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
src/main/java/taco/klkl/domain/subcategory/domain/SubcategoryName.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package taco.klkl.domain.subcategory.domain; | ||
|
||
import java.util.Arrays; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
@AllArgsConstructor | ||
public enum SubcategoryName { | ||
//식품 | ||
INSTANT_FOOD("라면 및 즉석식품"), | ||
SNACK("스낵 및 과자"), | ||
SAUCE("조미료 및 소스"), | ||
HEALTH_FOOD("보충제 및 건강식품"), | ||
BEVERAGE("음료 및 차"), | ||
DRINKS("주류"), | ||
//의류 | ||
TOP("상의"), | ||
BOTTOM("하의"), | ||
OUTER("아우터"), | ||
DRESS("원피스"), | ||
SHOES("신발"), | ||
ACCESSORY("액세사리"), | ||
JEWELRY("쥬얼리"), | ||
//잡화 | ||
DRUG("일반의약품"), | ||
KITCHEN_SUPPLIES("주방잡화"), | ||
BATHROOM_SUPPLIES("욕실잡화"), | ||
STATIONERY("문구 및 완구"), | ||
//화장품 | ||
SKIN_CARE("스킨케어"), | ||
MAKEUP("메이크업"), | ||
HAIR_CARE("헤어케어"), | ||
BODY_CARE("바디케어"), | ||
HYGIENE_PRODUCT("위생용품"), | ||
//None | ||
NONE(""), | ||
; | ||
|
||
private final String name; | ||
|
||
public static SubcategoryName getByName(String name) { | ||
return Arrays.stream(values()) | ||
.filter(subcategoryName -> subcategoryName.getName().equals(name)) | ||
.findFirst() | ||
.orElse(NONE); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/main/java/taco/klkl/domain/subcategory/dto/response/SubcategoryResponseDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package taco.klkl.domain.subcategory.dto.response; | ||
|
||
import taco.klkl.domain.subcategory.domain.SubcategoryName; | ||
|
||
public record SubcategoryResponseDto( | ||
Long id, | ||
String name | ||
) { | ||
public static SubcategoryResponseDto of(Long id, SubcategoryName subcategoryName) { | ||
return new SubcategoryResponseDto(id, subcategoryName.getName()); | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
src/main/java/taco/klkl/domain/subcategory/exception/CategoryNotFoundException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package taco.klkl.domain.subcategory.exception; | ||
|
||
import taco.klkl.global.error.exception.CustomException; | ||
import taco.klkl.global.error.exception.ErrorCode; | ||
|
||
public class CategoryNotFoundException extends CustomException { | ||
public CategoryNotFoundException() { | ||
super(ErrorCode.CATEGORY_ID_NOT_FOUND); | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/main/java/taco/klkl/domain/subcategory/sevice/SubcategoryService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package taco.klkl.domain.subcategory.sevice; | ||
|
||
import java.util.List; | ||
|
||
import org.springframework.stereotype.Service; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import taco.klkl.domain.subcategory.dao.SubcategoryRepository; | ||
import taco.klkl.domain.subcategory.domain.Subcategory; | ||
import taco.klkl.domain.subcategory.dto.response.SubcategoryResponseDto; | ||
import taco.klkl.domain.subcategory.exception.CategoryNotFoundException; | ||
|
||
@Slf4j | ||
@Service | ||
@RequiredArgsConstructor | ||
public class SubcategoryService { | ||
private final SubcategoryRepository subcategoryRepository; | ||
|
||
public List<SubcategoryResponseDto> getSubcategories(Long id) { | ||
List<Subcategory> subcategories = subcategoryRepository.findAllByCategoryId(id); | ||
if (subcategories.isEmpty()) { | ||
throw new CategoryNotFoundException(); | ||
} | ||
return subcategories.stream() | ||
.map(subcategory -> SubcategoryResponseDto.of(subcategory.getId(), subcategory.getName())) | ||
.toList(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
src/test/java/taco/klkl/domain/subcategory/controller/SubcategoryControllerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package taco.klkl.domain.subcategory.controller; | ||
|
||
import static org.hamcrest.Matchers.*; | ||
import static org.mockito.Mockito.*; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||
import org.springframework.boot.test.mock.mockito.MockBean; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.test.web.servlet.MockMvc; | ||
|
||
import taco.klkl.domain.subcategory.dto.response.SubcategoryResponseDto; | ||
import taco.klkl.domain.subcategory.exception.CategoryNotFoundException; | ||
import taco.klkl.domain.subcategory.sevice.SubcategoryService; | ||
import taco.klkl.global.error.exception.ErrorCode; | ||
|
||
@WebMvcTest(SubcategoryController.class) | ||
public class SubcategoryControllerTest { | ||
@Autowired | ||
private MockMvc mockMvc; | ||
|
||
@MockBean | ||
private SubcategoryService subcategoryService; | ||
|
||
@Test | ||
@DisplayName("valid id가 들어올 경우 GlobalResponse로 Wrapping되어 나오는지 Test") | ||
public void testGetSubcategoryWithValidId() throws Exception { | ||
// given | ||
List<SubcategoryResponseDto> subcategoryResponseDto = Arrays.asList( | ||
new SubcategoryResponseDto(1L, "즉석식품"), | ||
new SubcategoryResponseDto(2L, "스낵") | ||
); | ||
|
||
// when | ||
when(subcategoryService.getSubcategories(anyLong())).thenReturn(subcategoryResponseDto); | ||
|
||
// then | ||
mockMvc.perform(get("/v1/categories/1/subcategories") | ||
.contentType(MediaType.APPLICATION_JSON)) | ||
.andExpect(status().isOk()) | ||
.andExpect(jsonPath("$.isSuccess", is(true))) | ||
.andExpect(jsonPath("$.code", is("C000"))) | ||
.andExpect(jsonPath("$.data", hasSize(2))) | ||
.andExpect(jsonPath("$.data[0].id", is(1))) | ||
.andExpect(jsonPath("$.data[0].name", is("즉석식품"))) | ||
.andExpect(jsonPath("$.data[1].id", is(2))) | ||
.andExpect(jsonPath("$.data[1].name", is("스낵"))) | ||
.andExpect(jsonPath("$.timestamp", notNullValue())); | ||
|
||
verify(subcategoryService, times(1)).getSubcategories(anyLong()); | ||
} | ||
|
||
@Test | ||
@DisplayName("invalid한 id가 들어올 경우 GlobalException으로 Wrapping되어 나오는지 Test") | ||
public void testGetSubcategoryWithInvalidId() throws Exception { | ||
// given | ||
when(subcategoryService.getSubcategories(anyLong())).thenThrow(new CategoryNotFoundException()); | ||
|
||
// when & then | ||
mockMvc.perform(get("/v1/categories/999/subcategories") | ||
.contentType(MediaType.APPLICATION_JSON)) | ||
.andExpect(status().isNotFound()) | ||
.andExpect(jsonPath("$.isSuccess", is(false))) | ||
.andExpect(jsonPath("$.code", is(ErrorCode.CATEGORY_ID_NOT_FOUND.getCode()))) | ||
.andExpect(jsonPath("$.data.message", is(ErrorCode.CATEGORY_ID_NOT_FOUND.getMessage()))) | ||
.andExpect(jsonPath("$.timestamp", notNullValue())); | ||
|
||
verify(subcategoryService, times(1)).getSubcategories(anyLong()); | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
src/test/java/taco/klkl/domain/subcategory/integration/SubcategoryIntegrationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package taco.klkl.domain.subcategory.integration; | ||
|
||
import static org.hamcrest.Matchers.*; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; | ||
|
||
import java.util.List; | ||
|
||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.test.web.servlet.MockMvc; | ||
|
||
import jakarta.transaction.Transactional; | ||
import taco.klkl.domain.subcategory.dto.response.SubcategoryResponseDto; | ||
import taco.klkl.domain.subcategory.sevice.SubcategoryService; | ||
import taco.klkl.global.error.exception.ErrorCode; | ||
|
||
@SpringBootTest | ||
@AutoConfigureMockMvc | ||
@Transactional | ||
public class SubcategoryIntegrationTest { | ||
|
||
@Autowired | ||
private MockMvc mockMvc; | ||
|
||
@Autowired | ||
private SubcategoryService subcategoryService; | ||
|
||
@Test | ||
@DisplayName("valid한 id값이 들어왔을 때 반환값이 제대로 전달되는지 테스트") | ||
public void testGetCategoriesWithValidId() throws Exception { | ||
//given | ||
List<SubcategoryResponseDto> subcategoryResponseDto = subcategoryService.getSubcategories(300L); | ||
|
||
//when, then | ||
mockMvc.perform(get("/v1/categories/300/subcategories") | ||
.contentType(MediaType.APPLICATION_JSON)) | ||
.andExpect(status().isOk()) | ||
.andExpect(jsonPath("$.data", hasSize(subcategoryResponseDto.size()))) | ||
.andExpect(jsonPath("$.isSuccess", is(true))) | ||
.andExpect(jsonPath("$.code", is("C000"))); | ||
} | ||
|
||
@Test | ||
@DisplayName("invalid한 id값이 들어왔을 때 오류가 전달되는지 테스트") | ||
public void testGetCategoriesWithInvalidId() throws Exception { | ||
//given | ||
|
||
//when, then | ||
mockMvc.perform(get("/v1/categories/999/subcategories") | ||
.contentType(MediaType.APPLICATION_JSON)) | ||
.andExpect(status().isNotFound()) | ||
.andExpect(jsonPath("$.isSuccess", is(false))) | ||
.andExpect(jsonPath("$.code", is(ErrorCode.CATEGORY_ID_NOT_FOUND.getCode()))) | ||
.andExpect(jsonPath("$.data.message", is(ErrorCode.CATEGORY_ID_NOT_FOUND.getMessage()))); | ||
} | ||
} |
75 changes: 75 additions & 0 deletions
75
src/test/java/taco/klkl/domain/subcategory/service/SubcategoryServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package taco.klkl.domain.subcategory.service; | ||
|
||
import static org.junit.jupiter.api.Assertions.*; | ||
import static org.mockito.Mockito.*; | ||
|
||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
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 jakarta.transaction.Transactional; | ||
import taco.klkl.domain.category.domain.Category; | ||
import taco.klkl.domain.subcategory.dao.SubcategoryRepository; | ||
import taco.klkl.domain.subcategory.domain.Subcategory; | ||
import taco.klkl.domain.subcategory.domain.SubcategoryName; | ||
import taco.klkl.domain.subcategory.dto.response.SubcategoryResponseDto; | ||
import taco.klkl.domain.subcategory.exception.CategoryNotFoundException; | ||
import taco.klkl.domain.subcategory.sevice.SubcategoryService; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
@Transactional | ||
public class SubcategoryServiceTest { | ||
|
||
@Mock | ||
private SubcategoryRepository subcategoryRepository; | ||
|
||
@InjectMocks | ||
private SubcategoryService subcategoryService; | ||
|
||
@Test | ||
@DisplayName("Valid한 카테고리ID 입력시 해당하는 서브카테고리를 반환하는지 테스트") | ||
void testGetSubcategoriesWithValidCategoryId() { | ||
//given | ||
Category category = Category.of("Category1"); | ||
Subcategory subcategory1 = Subcategory.of(category, SubcategoryName.DRESS); | ||
Subcategory subcategory2 = Subcategory.of(category, SubcategoryName.HAIR_CARE); | ||
List<Subcategory> subcategories = Arrays.asList(subcategory1, subcategory2); | ||
|
||
//when | ||
when(subcategoryRepository.findAllByCategoryId(category.getId())).thenReturn(subcategories); | ||
List<SubcategoryResponseDto> response = subcategoryService.getSubcategories(category.getId()); | ||
|
||
//then | ||
assertNotNull(response); | ||
assertEquals(2, response.size()); | ||
assertEquals(SubcategoryName.DRESS.getName(), response.get(0).name()); | ||
assertEquals(SubcategoryName.HAIR_CARE.getName(), response.get(1).name()); | ||
|
||
verify(subcategoryRepository, times(1)).findAllByCategoryId(category.getId()); | ||
} | ||
|
||
@Test | ||
@DisplayName("Invalid한 카테고리 ID 입력시 CategoryNotFoundException을 반환하는지 테스트") | ||
void testGetSubcategoriesWithInvalidCategoryId() { | ||
//given | ||
Long categoryId = 1L; | ||
|
||
//when | ||
when(subcategoryRepository.findAllByCategoryId(categoryId)).thenReturn(Collections.emptyList()); | ||
|
||
//then | ||
assertThrows(CategoryNotFoundException.class, () -> { | ||
subcategoryService.getSubcategories(categoryId); | ||
}); | ||
|
||
verify(subcategoryRepository, times(1)).findAllByCategoryId(categoryId); | ||
} | ||
} | ||
|