From 000fca6e6094833830602c0b5e030275a536187b Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Thu, 4 Apr 2024 14:45:42 +0000 Subject: [PATCH 01/17] =?UTF-8?q?feat:=20Product=20CRUD=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Product 엔티티에 대한 기본적인 CRUD 기능의 틀을 구현하였습니다. - 현재까지 GET 요청에 대한 테스트를 완료했습니다. 이를 통해 상품의 목록 조회 및 단일 상품 조회 기능이 정상적으로 동작함을 확인하였습니다. - Create, Update, Delete 기능에 대해서는 추가적인 구현이 필요합니다. 다음 단계에서는 이러한 기능들을 완성하여 상품 관리 기능을 완전히 지원할 예정입니다. --- .../domain/product/api/ProductController.java | 72 ++++++++++ .../product/application/ProductService.java | 58 ++++++++ .../domain/product/dao/ProductRepository.java | 7 + .../domain/product/domain/Product.java | 135 ++++++++++++++++++ .../domain/product/dto/ProductBase.java | 40 ++++++ .../domain/product/dto/ProductInput.java | 52 +++++++ .../domain/product/dto/ProductListOutput.java | 20 +++ .../domain/product/dto/ProductOutput.java | 52 +++++++ .../application/ProductServiceTest.java | 116 +++++++++++++++ 9 files changed, 552 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dao/ProductRepository.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductListOutput.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java create mode 100644 spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java new file mode 100644 index 0000000..0c21104 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java @@ -0,0 +1,72 @@ +package com.ssafy.springbootapi.domain.product.api; + +import java.util.List; + +import com.ssafy.springbootapi.domain.product.application.ProductService; +import com.ssafy.springbootapi.domain.product.domain.Product; +import com.ssafy.springbootapi.domain.product.dto.ProductInput; +import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; +import com.ssafy.springbootapi.domain.product.dto.ProductOutput; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "Product", description = "Product 관련 API 입니다.") +@RestController +@RequiredArgsConstructor +@CrossOrigin +@RequestMapping("/api/v1/products") +public class ProductController { + private final ProductService productService; + + @Operation(summary = "모든 제품 조회") + @GetMapping("") + public ResponseEntity> getAllProducts() { + List products = productService.getAllProducts(); + return new ResponseEntity<>(products, HttpStatus.OK); + } + + @Operation(summary = "제품 ID로 조회") + @GetMapping("/{id}") + public ResponseEntity getProductById(@PathVariable Long id) { + ProductOutput product = productService.getProductById(id); + return new ResponseEntity<>(product, HttpStatus.OK); + } + + @Operation(summary = "새 제품 추가") + @PostMapping("") + public ResponseEntity createProduct(@RequestBody ProductInput productInput) { + // ProductInput을 Product 엔티티로 변환 + Product product = new Product(); + product.setName(productInput.getName()); + product.setImageUrl(productInput.getImageUrl()); + product.setPrice(productInput.getPrice()); + product.setDescription(productInput.getDescription()); + product.setCategory(productInput.getCategory()); + product.setStock(productInput.getStock()); + product.setUserId(productInput.getUser_id()); + + // 서비스 레이어를 통해 비즈니스 로직 처리 + Product newProduct = productService.insertProduct(product); + + // 생성된 Product 객체 반환 + return new ResponseEntity<>(newProduct, HttpStatus.CREATED); + } + + @Operation(summary = "제품 정보 업데이트") + @PutMapping("/{id}") + public ResponseEntity updateProduct(@PathVariable Long id, @RequestBody Product productDetails) { + Product updatedProduct = productService.updateProduct(productDetails); + return new ResponseEntity<>(updatedProduct, HttpStatus.OK); + } + + @Operation(summary = "제품 삭제") + @DeleteMapping("/{id}") + public ResponseEntity deleteProduct(@PathVariable Long id) { + productService.removeProduct(id); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} \ No newline at end of file diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java new file mode 100644 index 0000000..efbb79b --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java @@ -0,0 +1,58 @@ +package com.ssafy.springbootapi.domain.product.application; + +import java.util.List; +import java.util.stream.Collectors; + +import com.ssafy.springbootapi.domain.product.dao.ProductRepository; +import com.ssafy.springbootapi.domain.product.domain.Product; +import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; +import com.ssafy.springbootapi.domain.product.dto.ProductOutput; +import org.modelmapper.ModelMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class ProductService { + private final ProductRepository productRepository; + private final ModelMapper modelMapper; + + @Autowired + public ProductService(ProductRepository productRepository, ModelMapper modelMapper) { + this.productRepository = productRepository; + this.modelMapper = modelMapper; + } + + + public List getAllProducts() { + List products = productRepository.findAll(); + return products.stream() + .map(product -> modelMapper.map(product, ProductListOutput.class)) + .collect(Collectors.toList()); + } + + public ProductOutput getProductById(Long id) { + Product product = productRepository.getReferenceById(id); + return modelMapper.map(product, ProductOutput.class); + } + + public Product insertProduct(Product product) { + return productRepository.save(product); + } + + public Product removeProduct(Long id) { + Product toRemove = productRepository.getReferenceById(id); + productRepository.delete(toRemove); + return toRemove; + } + + public Product updateProduct(Product product) { + Product newProduct = productRepository.getReferenceById(product.getId()); + newProduct.setCategory(product.getCategory()); + newProduct.setStock(product.getStock()); + newProduct.setImageUrl(product.getImageUrl()); + + productRepository.save(newProduct); + + return newProduct; + } +} \ No newline at end of file diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dao/ProductRepository.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dao/ProductRepository.java new file mode 100644 index 0000000..b16247e --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dao/ProductRepository.java @@ -0,0 +1,7 @@ +package com.ssafy.springbootapi.domain.product.dao; + +import com.ssafy.springbootapi.domain.product.domain.Product; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProductRepository extends JpaRepository { +} \ No newline at end of file diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java new file mode 100644 index 0000000..1818086 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java @@ -0,0 +1,135 @@ +package com.ssafy.springbootapi.domain.product.domain; + + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; + +import java.time.LocalDateTime; + +@Entity +@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) +public class Product { + @Id + @GeneratedValue + @Column(name = "id", unique = true) + private Long id; + @Column(name = "image_url") + private String imageUrl; + @Column(name = "created_at") + private LocalDateTime createdAt; + @Column(name = "updated_at") + private LocalDateTime updatedAt; + private String name; + private int price; + + private String description; + private int category; + private int stock; + // @ManyToOne +// @JoinColumn(name = "user_id") +// private User user; + @Column(name = "user_id") + private int userId; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public int getPrice() { + return price; + } + + public void setPrice(int price) { + this.price = price; + } + + public Product() { + + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public Long getId() { + return id; + } + + public String getImageUrl() { + return imageUrl; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public int getCategory() { + return category; + } + + public int getStock() { + return stock; + } + +// public User getUser() { +// return user; +// } + + public void setId(Long id) { + this.id = id; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + public void setCategory(int category) { + this.category = category; + } + + public void setStock(int stock) { + this.stock = stock; + } + +// public void setUser(User user) { +// this.user = user; +// } + + public Product(String name, String imageUrl, String description, int category, int stock, int userId, int price) { + this.name = name; + this.imageUrl = imageUrl; + this.price = price; + this.description = description; + this.category = category; + this.stock = stock; + this.userId = userId; + } +} \ No newline at end of file diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java new file mode 100644 index 0000000..8a0b928 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java @@ -0,0 +1,40 @@ +package com.ssafy.springbootapi.domain.product.dto; + +public class ProductBase { + private String imageUrl; + private String name; + private int price; + + public ProductBase() { + } + + public ProductBase(String imageUrl, String name, int price) { + this.imageUrl = imageUrl; + this.name = name; + this.price = price; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getPrice() { + return price; + } + + public void setPrice(int price) { + this.price = price; + } +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java new file mode 100644 index 0000000..187db32 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java @@ -0,0 +1,52 @@ +package com.ssafy.springbootapi.domain.product.dto; + +public class ProductInput extends ProductBase { + private String description; + private int category; + private int stock; + private int user_id; + + public ProductInput() { + super(); + } + + public ProductInput(String imageUrl, String name, int price, String description, int category, int stock, int user_id) { + super(imageUrl, name, price); + this.description = description; + this.category = category; + this.stock = stock; + this.user_id = user_id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public int getCategory() { + return category; + } + + public void setCategory(int category) { + this.category = category; + } + + public int getStock() { + return stock; + } + + public void setStock(int stock) { + this.stock = stock; + } + + public int getUser_id() { + return user_id; + } + + public void setUser_id(int user_id) { + this.user_id = user_id; + } +} \ No newline at end of file diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductListOutput.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductListOutput.java new file mode 100644 index 0000000..49a6065 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductListOutput.java @@ -0,0 +1,20 @@ +package com.ssafy.springbootapi.domain.product.dto; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class ProductListOutput extends ProductBase{ + private Long id; + + public ProductListOutput() { + super(); + } + + public ProductListOutput(String imageUrl, String name, int price, Long id) { + super(imageUrl, name, price); + this.id = id; + } + +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java new file mode 100644 index 0000000..6e3a0a6 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java @@ -0,0 +1,52 @@ +package com.ssafy.springbootapi.domain.product.dto; + +public class ProductOutput extends ProductListOutput{ + private String description; + private int category; + private int stock; + private int user_id; + + public ProductOutput() { + super(); + } + + public ProductOutput(String imageUrl, String name, int price, Long id, String description, int category, int stock, int user_id) { + super(imageUrl, name, price, id); + this.description = description; + this.category = category; + this.stock = stock; + this.user_id = user_id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public int getCategory() { + return category; + } + + public void setCategory(int category) { + this.category = category; + } + + public int getStock() { + return stock; + } + + public void setStock(int stock) { + this.stock = stock; + } + + public int getUser_id() { + return user_id; + } + + public void setUser_id(int user_id) { + this.user_id = user_id; + } +} diff --git a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java new file mode 100644 index 0000000..78f4745 --- /dev/null +++ b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java @@ -0,0 +1,116 @@ +package com.ssafy.springbootapi.domain.product.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import com.ssafy.springbootapi.domain.product.dao.ProductRepository; +import com.ssafy.springbootapi.domain.product.domain.Product; +import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; +import com.ssafy.springbootapi.domain.product.dto.ProductOutput; +import org.assertj.core.api.Assertions; +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; + +@ExtendWith(MockitoExtension.class) +class ProductServiceTest { + @Mock + private ProductRepository productRepository; + + @InjectMocks + private ProductService productService; + + @Test + void getAllProducts() { + // given + Product product1 = new Product("name1","img1", "desc1", 1, 10, 1, 1); + Product product2 = new Product("name2","img2", "desc2", 1, 10, 1, 1); + Product product3 = new Product("name3","img3", "desc3", 1, 10, 1, 1); + when(productRepository.findAll()).thenReturn(Arrays.asList(product1, product2, product3)); + + // when + List result = productService.getAllProducts(); + + // then + assertThat(result.size()).isEqualTo(3); +// assertThat(result).contains(product1, product2, product3); + } + + @Test + void getProductById() { + // given + Product product1 = new Product("name1","img1", "desc1", 1, 10, 1, 1); + when(productRepository.getReferenceById(1L)).thenReturn(product1); + + // when + ProductOutput result = productService.getProductById(1L); + + // then + assertThat(result).isNotNull(); +// assertThat(result).isEqualTo(product1); + } + + @Test + void insertProduct() { + // given + Product product1 = new Product("name1","img1", "desc1", 1, 10, 1, 1); + when(productRepository.save(product1)).thenReturn(product1); + + // when + Product result = productService.insertProduct(product1); + + // then + assertThat(result).isNotNull(); + assertThat(result).isEqualTo(product1); + } + + @Test + void removeProduct() { + Long id = 1L; + // given + Product product1 = new Product("name1","img1", "desc1", 1, 10, 1,1 ); + when(productRepository.getReferenceById(id)).thenReturn(product1); + doNothing().when(productRepository).delete(product1); + + // when + Product removedProduct = productService.removeProduct(id); + + // then + verify(productRepository).getReferenceById(id); + verify(productRepository).delete(product1); + assertThat(removedProduct).isEqualTo(product1); + } + + @Test + void updateProduct() { + Long id = 1L; + // given + Product product1 = new Product("name1","img1", "desc1", 1, 10, 1,1); + product1.setId(id); + Product product2 = new Product("name2","img2", "desc2", 2, 20, 2,1); + product2.setId(id); + + when(productRepository.getReferenceById(id)).thenReturn(product1); + when(productRepository.save(any(Product.class))).thenAnswer(i -> i.getArguments()[0]); + + // when + Product newProduct = productService.updateProduct(product2); + + // then + verify(productRepository).getReferenceById(id); // getReferenceById 메소드가 호출되었는지 검증 + verify(productRepository).save(any(Product.class)); // save 메소드가 호출되었는지 검증 + assertThat(newProduct.getImageUrl()).isEqualTo("img2"); // 업데이트된 이미지 URL 검증 + assertThat(newProduct.getStock()).isEqualTo(20); // 업데이트된 재고 수량 검증 + assertThat(newProduct.getCategory()).isEqualTo(2); // 업데이트된 카테고리 ID 검증 + } +} \ No newline at end of file From e556ae5a04b730f5bd3b5d4894b3a3e545645565 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Sun, 7 Apr 2024 16:07:07 +0000 Subject: [PATCH 02/17] =?UTF-8?q?Refactor=20:=20Controller=EC=99=80=20Serv?= =?UTF-8?q?ice=20=EB=A0=88=EC=9D=B4=EC=96=B4=EA=B0=84=20=EA=B4=80=EC=8B=AC?= =?UTF-8?q?=EC=82=AC=20=EB=B6=84=EB=A6=AC=20-=20DTO=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=EB=8A=94=20Service=20=EB=8B=A8=EC=97=90=EC=84=9C=20-=20Control?= =?UTF-8?q?ler=EB=8A=94=20=EC=9A=94=EC=B2=AD=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EB=A7=8C=20=EC=88=98=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/product/api/ProductController.java | 29 +++++--------- .../product/application/ProductService.java | 40 ++++++++++++------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java index 0c21104..8170057 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java @@ -3,7 +3,6 @@ import java.util.List; import com.ssafy.springbootapi.domain.product.application.ProductService; -import com.ssafy.springbootapi.domain.product.domain.Product; import com.ssafy.springbootapi.domain.product.dto.ProductInput; import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; import com.ssafy.springbootapi.domain.product.dto.ProductOutput; @@ -38,19 +37,9 @@ public ResponseEntity getProductById(@PathVariable Long id) { @Operation(summary = "새 제품 추가") @PostMapping("") - public ResponseEntity createProduct(@RequestBody ProductInput productInput) { - // ProductInput을 Product 엔티티로 변환 - Product product = new Product(); - product.setName(productInput.getName()); - product.setImageUrl(productInput.getImageUrl()); - product.setPrice(productInput.getPrice()); - product.setDescription(productInput.getDescription()); - product.setCategory(productInput.getCategory()); - product.setStock(productInput.getStock()); - product.setUserId(productInput.getUser_id()); - + public ResponseEntity createProduct(@RequestBody ProductInput productInput) { // 서비스 레이어를 통해 비즈니스 로직 처리 - Product newProduct = productService.insertProduct(product); + ProductOutput newProduct = productService.insertProduct(productInput); // 생성된 Product 객체 반환 return new ResponseEntity<>(newProduct, HttpStatus.CREATED); @@ -58,15 +47,17 @@ public ResponseEntity createProduct(@RequestBody ProductInput productIn @Operation(summary = "제품 정보 업데이트") @PutMapping("/{id}") - public ResponseEntity updateProduct(@PathVariable Long id, @RequestBody Product productDetails) { - Product updatedProduct = productService.updateProduct(productDetails); + public ResponseEntity updateProduct(@PathVariable Long id, @RequestBody ProductInput productDetails) { + // 서비스 레이어를 통해 비즈니스 로직 처리 + ProductOutput updatedProduct = productService.updateProduct(id, productDetails); + + // 업데이트된 Product 객체 반환 return new ResponseEntity<>(updatedProduct, HttpStatus.OK); } - @Operation(summary = "제품 삭제") @DeleteMapping("/{id}") - public ResponseEntity deleteProduct(@PathVariable Long id) { - productService.removeProduct(id); - return new ResponseEntity<>(HttpStatus.NO_CONTENT); + public ResponseEntity deleteProduct(@PathVariable Long id) { + ProductOutput product = productService.removeProduct(id); + return new ResponseEntity<>(product, HttpStatus.NO_CONTENT); } } \ No newline at end of file diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java index efbb79b..633c118 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java @@ -5,8 +5,10 @@ import com.ssafy.springbootapi.domain.product.dao.ProductRepository; import com.ssafy.springbootapi.domain.product.domain.Product; +import com.ssafy.springbootapi.domain.product.dto.ProductInput; import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; import com.ssafy.springbootapi.domain.product.dto.ProductOutput; +import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -22,7 +24,6 @@ public ProductService(ProductRepository productRepository, ModelMapper modelMapp this.modelMapper = modelMapper; } - public List getAllProducts() { List products = productRepository.findAll(); return products.stream() @@ -31,28 +32,39 @@ public List getAllProducts() { } public ProductOutput getProductById(Long id) { - Product product = productRepository.getReferenceById(id); + Product product = findProductByIdOrThrow(id); return modelMapper.map(product, ProductOutput.class); } - public Product insertProduct(Product product) { - return productRepository.save(product); + public ProductOutput insertProduct(ProductInput productInput) { + Product product = modelMapper.map(productInput, Product.class); + return modelMapper.map(productRepository.save(product), ProductOutput.class); } - public Product removeProduct(Long id) { - Product toRemove = productRepository.getReferenceById(id); + public ProductOutput removeProduct(Long id) { + Product toRemove = findProductByIdOrThrow(id); productRepository.delete(toRemove); - return toRemove; + return modelMapper.map(toRemove, ProductOutput.class); } - public Product updateProduct(Product product) { - Product newProduct = productRepository.getReferenceById(product.getId()); - newProduct.setCategory(product.getCategory()); - newProduct.setStock(product.getStock()); - newProduct.setImageUrl(product.getImageUrl()); - productRepository.save(newProduct); + public ProductOutput updateProduct(Long id, ProductInput productInput) { + Product productToUpdate = findProductByIdOrThrow(id); + + // 업데이트 로직 + productToUpdate.setCategory(productInput.getCategory()); + productToUpdate.setStock(productInput.getStock()); + productToUpdate.setImageUrl(productInput.getImageUrl()); + + productRepository.save(productToUpdate); - return newProduct; + return modelMapper.map(productToUpdate, ProductOutput.class); } + + // ProductService 클래스 내부에 private 메소드 추가 + private Product findProductByIdOrThrow(Long id) { + return productRepository.findById(id) + .orElseThrow(() -> new NotFoundProductException("Product not found with id: " + id)); + } + } \ No newline at end of file From ec32a2d8ed88afa66ec1dafdff81b46aabb70555 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Sun, 7 Apr 2024 16:10:27 +0000 Subject: [PATCH 03/17] =?UTF-8?q?Test=20:=20ProductService=20Test=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ProductServiceTest.java | 176 +++++++++++------- 1 file changed, 105 insertions(+), 71 deletions(-) diff --git a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java index 78f4745..01344d0 100644 --- a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java +++ b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java @@ -1,116 +1,150 @@ package com.ssafy.springbootapi.domain.product.application; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - import com.ssafy.springbootapi.domain.product.dao.ProductRepository; import com.ssafy.springbootapi.domain.product.domain.Product; +import com.ssafy.springbootapi.domain.product.dto.ProductInput; import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; import com.ssafy.springbootapi.domain.product.dto.ProductOutput; -import org.assertj.core.api.Assertions; +import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.modelmapper.ModelMapper; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Arrays; +import java.util.List; @ExtendWith(MockitoExtension.class) class ProductServiceTest { + @Mock private ProductRepository productRepository; + @Mock + private ModelMapper modelMapper; + @InjectMocks private ProductService productService; - + @DisplayName("Get All Products") @Test void getAllProducts() { - // given - Product product1 = new Product("name1","img1", "desc1", 1, 10, 1, 1); - Product product2 = new Product("name2","img2", "desc2", 1, 10, 1, 1); - Product product3 = new Product("name3","img3", "desc3", 1, 10, 1, 1); - when(productRepository.findAll()).thenReturn(Arrays.asList(product1, product2, product3)); + Product product1 = new Product(); + Product product2 = new Product(); + ProductListOutput productListOutput1 = new ProductListOutput(); + ProductListOutput productListOutput2 = new ProductListOutput(); + + when(productRepository.findAll()).thenReturn(Arrays.asList(product1, product2)); + when(modelMapper.map(product1, ProductListOutput.class)).thenReturn(productListOutput1); + when(modelMapper.map(product2, ProductListOutput.class)).thenReturn(productListOutput2); - // when List result = productService.getAllProducts(); - // then - assertThat(result.size()).isEqualTo(3); -// assertThat(result).contains(product1, product2, product3); + assertThat(result).containsExactly(productListOutput1, productListOutput2); } - + @DisplayName("Get One Product") @Test void getProductById() { - // given - Product product1 = new Product("name1","img1", "desc1", 1, 10, 1, 1); - when(productRepository.getReferenceById(1L)).thenReturn(product1); + Product product = new Product(); + ProductOutput productOutput = new ProductOutput(); + + when(productRepository.findById(1L)).thenReturn(java.util.Optional.of(product)); + when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); - // when ProductOutput result = productService.getProductById(1L); - // then - assertThat(result).isNotNull(); -// assertThat(result).isEqualTo(product1); + assertThat(result).isEqualTo(productOutput); } + @DisplayName("Product Not Found") + @Test + void getProductByIdNotFound() { + when(productRepository.findById(1L)).thenReturn(java.util.Optional.empty()); + assertThatThrownBy(() -> productService.getProductById(1L)) + .isInstanceOf(NotFoundProductException.class) + .hasMessageContaining("Product not found with id: 1"); + } + @DisplayName("Insert Product") @Test void insertProduct() { - // given - Product product1 = new Product("name1","img1", "desc1", 1, 10, 1, 1); - when(productRepository.save(product1)).thenReturn(product1); + ProductInput productInput = new ProductInput(); + Product product = new Product(); + ProductOutput productOutput = new ProductOutput(); - // when - Product result = productService.insertProduct(product1); + when(modelMapper.map(productInput, Product.class)).thenReturn(product); + when(productRepository.save(product)).thenReturn(product); + when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); - // then - assertThat(result).isNotNull(); - assertThat(result).isEqualTo(product1); + ProductOutput result = productService.insertProduct(productInput); + + assertThat(result).isEqualTo(productOutput); } + @DisplayName("Remove Product") @Test void removeProduct() { - Long id = 1L; - // given - Product product1 = new Product("name1","img1", "desc1", 1, 10, 1,1 ); - when(productRepository.getReferenceById(id)).thenReturn(product1); - doNothing().when(productRepository).delete(product1); - - // when - Product removedProduct = productService.removeProduct(id); - - // then - verify(productRepository).getReferenceById(id); - verify(productRepository).delete(product1); - assertThat(removedProduct).isEqualTo(product1); + Product product = new Product(); + ProductOutput productOutput = new ProductOutput(); + + when(productRepository.findById(1L)).thenReturn(java.util.Optional.of(product)); + doNothing().when(productRepository).delete(product); + when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); + + ProductOutput result = productService.removeProduct(1L); + + assertThat(result).isEqualTo(productOutput); } + @DisplayName("Remove Product Failed - Not found") + @Test + void removeProductNotFound() { + Long invalidProductId = 1L; + when(productRepository.findById(invalidProductId)).thenReturn(java.util.Optional.empty()); + assertThatThrownBy(() -> productService.removeProduct(invalidProductId)) + .isInstanceOf(NotFoundProductException.class) + .hasMessageContaining("Product not found with id: " + invalidProductId); + } + @DisplayName("Update Product") @Test void updateProduct() { - Long id = 1L; - // given - Product product1 = new Product("name1","img1", "desc1", 1, 10, 1,1); - product1.setId(id); - Product product2 = new Product("name2","img2", "desc2", 2, 20, 2,1); - product2.setId(id); - - when(productRepository.getReferenceById(id)).thenReturn(product1); - when(productRepository.save(any(Product.class))).thenAnswer(i -> i.getArguments()[0]); - - // when - Product newProduct = productService.updateProduct(product2); - - // then - verify(productRepository).getReferenceById(id); // getReferenceById 메소드가 호출되었는지 검증 - verify(productRepository).save(any(Product.class)); // save 메소드가 호출되었는지 검증 - assertThat(newProduct.getImageUrl()).isEqualTo("img2"); // 업데이트된 이미지 URL 검증 - assertThat(newProduct.getStock()).isEqualTo(20); // 업데이트된 재고 수량 검증 - assertThat(newProduct.getCategory()).isEqualTo(2); // 업데이트된 카테고리 ID 검증 + ProductInput productInput = new ProductInput(); + productInput.setCategory(2); + productInput.setStock(20); + productInput.setImageUrl("newImageUrl"); + + Product product = new Product(); + product.setId(1L); + + ProductOutput productOutput = new ProductOutput(); + + when(productRepository.findById(1L)).thenReturn(java.util.Optional.of(product)); + when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); + + ProductOutput result = productService.updateProduct(1L, productInput); + + assertThat(result).isEqualTo(productOutput); + assertThat(product.getCategory()).isEqualTo(2); + assertThat(product.getStock()).isEqualTo(20); + assertThat(product.getImageUrl()).isEqualTo("newImageUrl"); + } + @DisplayName("Update Product Failed - Not found") + @Test + void updateProductNotFound() { + Long invalidProductId = 1L; + ProductInput productInput = new ProductInput(); + productInput.setCategory(1); + productInput.setStock(10); + productInput.setImageUrl("testUrl"); + + when(productRepository.findById(invalidProductId)).thenReturn(java.util.Optional.empty()); + + assertThatThrownBy(() -> productService.updateProduct(invalidProductId, productInput)) + .isInstanceOf(NotFoundProductException.class) + .hasMessageContaining("Product not found with id: " + invalidProductId); } -} \ No newline at end of file +} From 3575602e28b48d70bd72fb96d1b9639ffebf0114 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Sun, 7 Apr 2024 16:11:00 +0000 Subject: [PATCH 04/17] =?UTF-8?q?Feat=20:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC(Pr?= =?UTF-8?q?oduct=20Not=20found=20exception)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/product/exception/NotFoundProductException.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/exception/NotFoundProductException.java diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/exception/NotFoundProductException.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/exception/NotFoundProductException.java new file mode 100644 index 0000000..d47b370 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/exception/NotFoundProductException.java @@ -0,0 +1,7 @@ +package com.ssafy.springbootapi.domain.product.exception; + +public class NotFoundProductException extends RuntimeException{ + public NotFoundProductException(String message) { + super(message); + } +} From ab0dc78ee98c030ce88088a84723848dd4b1f8f4 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Wed, 10 Apr 2024 14:07:26 +0000 Subject: [PATCH 05/17] =?UTF-8?q?=F0=9F=A9=B9=20Fix=20:=20=EC=BB=A8?= =?UTF-8?q?=EB=B2=A4=EC=85=98=20=EA=B7=9C=EC=B9=99=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EB=B0=8F=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/ProductService.java | 8 +- .../domain/product/domain/Product.java | 114 ++---------------- .../domain/product/dto/ProductBase.java | 41 ++----- .../domain/product/dto/ProductInput.java | 52 ++------ .../domain/product/dto/ProductListOutput.java | 18 +-- .../domain/product/dto/ProductOutput.java | 54 ++------- .../global/error/GlobalExceptionHandler.java | 10 +- .../application/ProductServiceTest.java | 70 +++++++---- 8 files changed, 96 insertions(+), 271 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java index 633c118..7b953af 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java @@ -9,21 +9,17 @@ import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; import com.ssafy.springbootapi.domain.product.dto.ProductOutput; import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException; +import lombok.RequiredArgsConstructor; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service +@RequiredArgsConstructor public class ProductService { private final ProductRepository productRepository; private final ModelMapper modelMapper; - @Autowired - public ProductService(ProductRepository productRepository, ModelMapper modelMapper) { - this.productRepository = productRepository; - this.modelMapper = modelMapper; - } - public List getAllProducts() { List products = productRepository.findAll(); return products.stream() diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java index 1818086..65eed26 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java @@ -3,11 +3,18 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; +import lombok.*; import java.time.LocalDateTime; @Entity @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) +@Table(name = "product") +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Builder public class Product { @Id @GeneratedValue @@ -19,117 +26,16 @@ public class Product { private LocalDateTime createdAt; @Column(name = "updated_at") private LocalDateTime updatedAt; + @Column(name = "name", nullable = false) private String name; + @Column(name = "price", nullable = false) private int price; - private String description; private int category; private int stock; - // @ManyToOne +// @ManyToOne // @JoinColumn(name = "user_id") // private User user; @Column(name = "user_id") private int userId; - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public int getPrice() { - return price; - } - - public void setPrice(int price) { - this.price = price; - } - - public Product() { - - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getUserId() { - return userId; - } - - public void setUserId(int userId) { - this.userId = userId; - } - - public Long getId() { - return id; - } - - public String getImageUrl() { - return imageUrl; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public int getCategory() { - return category; - } - - public int getStock() { - return stock; - } - -// public User getUser() { -// return user; -// } - - public void setId(Long id) { - this.id = id; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } - - public void setCategory(int category) { - this.category = category; - } - - public void setStock(int stock) { - this.stock = stock; - } - -// public void setUser(User user) { -// this.user = user; -// } - - public Product(String name, String imageUrl, String description, int category, int stock, int userId, int price) { - this.name = name; - this.imageUrl = imageUrl; - this.price = price; - this.description = description; - this.category = category; - this.stock = stock; - this.userId = userId; - } } \ No newline at end of file diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java index 8a0b928..fb22a54 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java @@ -1,40 +1,15 @@ package com.ssafy.springbootapi.domain.product.dto; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@NoArgsConstructor +@AllArgsConstructor +@Setter +@Getter +@SuperBuilder public class ProductBase { private String imageUrl; private String name; private int price; - - public ProductBase() { - } - - public ProductBase(String imageUrl, String name, int price) { - this.imageUrl = imageUrl; - this.name = name; - this.price = price; - } - - public String getImageUrl() { - return imageUrl; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getPrice() { - return price; - } - - public void setPrice(int price) { - this.price = price; - } } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java index 187db32..5999f14 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java @@ -1,52 +1,16 @@ package com.ssafy.springbootapi.domain.product.dto; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@SuperBuilder public class ProductInput extends ProductBase { private String description; private int category; private int stock; private int user_id; - - public ProductInput() { - super(); - } - - public ProductInput(String imageUrl, String name, int price, String description, int category, int stock, int user_id) { - super(imageUrl, name, price); - this.description = description; - this.category = category; - this.stock = stock; - this.user_id = user_id; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public int getCategory() { - return category; - } - - public void setCategory(int category) { - this.category = category; - } - - public int getStock() { - return stock; - } - - public void setStock(int stock) { - this.stock = stock; - } - - public int getUser_id() { - return user_id; - } - - public void setUser_id(int user_id) { - this.user_id = user_id; - } } \ No newline at end of file diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductListOutput.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductListOutput.java index 49a6065..c562fdf 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductListOutput.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductListOutput.java @@ -1,20 +1,12 @@ package com.ssafy.springbootapi.domain.product.dto; -import lombok.Getter; -import lombok.Setter; +import lombok.*; +import lombok.experimental.SuperBuilder; -@Setter +@NoArgsConstructor +@AllArgsConstructor @Getter +@SuperBuilder public class ProductListOutput extends ProductBase{ private Long id; - - public ProductListOutput() { - super(); - } - - public ProductListOutput(String imageUrl, String name, int price, Long id) { - super(imageUrl, name, price); - this.id = id; - } - } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java index 6e3a0a6..6d9e14c 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java @@ -1,52 +1,18 @@ package com.ssafy.springbootapi.domain.product.dto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@SuperBuilder public class ProductOutput extends ProductListOutput{ private String description; private int category; private int stock; private int user_id; - - public ProductOutput() { - super(); - } - - public ProductOutput(String imageUrl, String name, int price, Long id, String description, int category, int stock, int user_id) { - super(imageUrl, name, price, id); - this.description = description; - this.category = category; - this.stock = stock; - this.user_id = user_id; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public int getCategory() { - return category; - } - - public void setCategory(int category) { - this.category = category; - } - - public int getStock() { - return stock; - } - - public void setStock(int stock) { - this.stock = stock; - } - - public int getUser_id() { - return user_id; - } - - public void setUser_id(int user_id) { - this.user_id = user_id; - } } diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java index a5b5de7..07f6342 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java @@ -1,6 +1,6 @@ package com.ssafy.springbootapi.global.error; -// import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException; + import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -9,8 +9,8 @@ @ControllerAdvice public class GlobalExceptionHandler { // Domain별 Exception 핸들러 - // @ExceptionHandler(NotFoundProductException.class) - // public ResponseEntity handleNotFoundProductException(NotFoundProductException ex) { - // return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); - // } + @ExceptionHandler(NotFoundProductException.class) + public ResponseEntity handleNotFoundProductException(NotFoundProductException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); + } } diff --git a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java index 01344d0..5233cc2 100644 --- a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java +++ b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java @@ -31,9 +31,11 @@ class ProductServiceTest { @InjectMocks private ProductService productService; - @DisplayName("Get All Products") + + @DisplayName("상품 전체 조회 성공 테스트") @Test - void getAllProducts() { + void 상품전체조회성공테스트() { + // given Product product1 = new Product(); Product product2 = new Product(); ProductListOutput productListOutput1 = new ProductListOutput(); @@ -43,35 +45,43 @@ void getAllProducts() { when(modelMapper.map(product1, ProductListOutput.class)).thenReturn(productListOutput1); when(modelMapper.map(product2, ProductListOutput.class)).thenReturn(productListOutput2); + // when List result = productService.getAllProducts(); + // then assertThat(result).containsExactly(productListOutput1, productListOutput2); } - @DisplayName("Get One Product") + @DisplayName("상품 아이디 조회 성공 테스트") @Test - void getProductById() { + void 상품아이디조회성공테스트() { + // given Product product = new Product(); ProductOutput productOutput = new ProductOutput(); when(productRepository.findById(1L)).thenReturn(java.util.Optional.of(product)); when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); + // when ProductOutput result = productService.getProductById(1L); + // then assertThat(result).isEqualTo(productOutput); } - @DisplayName("Product Not Found") + @DisplayName("상품 아이디 조회 실패 : 존재하지 않는 아이디 테스트") @Test - void getProductByIdNotFound() { + void 상품아이디조회실패존재하지않는아이디테스트() { + // given when(productRepository.findById(1L)).thenReturn(java.util.Optional.empty()); + // when then assertThatThrownBy(() -> productService.getProductById(1L)) .isInstanceOf(NotFoundProductException.class) .hasMessageContaining("Product not found with id: 1"); } - @DisplayName("Insert Product") + @DisplayName("상품 삽입 성공 테스트") @Test - void insertProduct() { + void 상품삽입성공테스트() { + // given ProductInput productInput = new ProductInput(); Product product = new Product(); ProductOutput productOutput = new ProductOutput(); @@ -80,14 +90,17 @@ void insertProduct() { when(productRepository.save(product)).thenReturn(product); when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); + // when ProductOutput result = productService.insertProduct(productInput); + // then assertThat(result).isEqualTo(productOutput); } - @DisplayName("Remove Product") + @DisplayName("상품 삭제 성공 테스트") @Test - void removeProduct() { + void 상품삭제성공테스트() { + // given Product product = new Product(); ProductOutput productOutput = new ProductOutput(); @@ -95,27 +108,36 @@ void removeProduct() { doNothing().when(productRepository).delete(product); when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); + // when ProductOutput result = productService.removeProduct(1L); + // then assertThat(result).isEqualTo(productOutput); } - @DisplayName("Remove Product Failed - Not found") + @DisplayName("상품 삭제 실패 : 존재하지 않는 아이디 테스트") @Test - void removeProductNotFound() { + void 상품삭제실패존재하지않는아이디테스트() { + // given Long invalidProductId = 1L; when(productRepository.findById(invalidProductId)).thenReturn(java.util.Optional.empty()); + // when then assertThatThrownBy(() -> productService.removeProduct(invalidProductId)) .isInstanceOf(NotFoundProductException.class) .hasMessageContaining("Product not found with id: " + invalidProductId); } - @DisplayName("Update Product") + @DisplayName("상품 수정 성공 테스트") @Test - void updateProduct() { + void 상품수정성공테스트() { + // given + int newCategory = 2; + int newStock = 20; + String newImageUrl = "newImageUrl"; + ProductInput productInput = new ProductInput(); - productInput.setCategory(2); - productInput.setStock(20); - productInput.setImageUrl("newImageUrl"); + productInput.setCategory(newCategory); + productInput.setStock(newStock); + productInput.setImageUrl(newImageUrl); Product product = new Product(); product.setId(1L); @@ -125,16 +147,19 @@ void updateProduct() { when(productRepository.findById(1L)).thenReturn(java.util.Optional.of(product)); when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); + // when ProductOutput result = productService.updateProduct(1L, productInput); + // then assertThat(result).isEqualTo(productOutput); - assertThat(product.getCategory()).isEqualTo(2); - assertThat(product.getStock()).isEqualTo(20); - assertThat(product.getImageUrl()).isEqualTo("newImageUrl"); + assertThat(product.getCategory()).isEqualTo(newCategory); + assertThat(product.getStock()).isEqualTo(newStock); + assertThat(product.getImageUrl()).isEqualTo(newImageUrl); } - @DisplayName("Update Product Failed - Not found") + @DisplayName("상품 수정 실패 : 존재하지 않는 아이디 테스트") @Test - void updateProductNotFound() { + void 상품수정실패존재하지않는아이디테스트() { + // given Long invalidProductId = 1L; ProductInput productInput = new ProductInput(); productInput.setCategory(1); @@ -143,6 +168,7 @@ void updateProductNotFound() { when(productRepository.findById(invalidProductId)).thenReturn(java.util.Optional.empty()); + // when then assertThatThrownBy(() -> productService.updateProduct(invalidProductId, productInput)) .isInstanceOf(NotFoundProductException.class) .hasMessageContaining("Product not found with id: " + invalidProductId); From 7b2ecc39413535cc765cdd2fd88b7dd31fcfbe8b Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Sun, 14 Apr 2024 14:38:46 +0000 Subject: [PATCH 06/17] =?UTF-8?q?:zap:=20[Refactor]=20=EC=84=B1=EB=8A=A5?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=EC=9D=84=20=EC=9C=84=ED=95=B4=20ModelMapper?= =?UTF-8?q?=20->=20MapStruct=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20-=20=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EC=A0=81=EC=9D=B8=20Product=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD=20-=20Service=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A3=BC=EC=84=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20ProductMapper=20=EC=9D=B8=ED=84=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80=20-=20ProductUpdate=20D?= =?UTF-8?q?TO=20=EC=B6=94=EA=B0=80=20-=20build.gradle=20=EC=97=90=EC=84=9C?= =?UTF-8?q?=20ModelMapper=20=EB=94=94=ED=8E=9C=EB=8D=98=EC=8B=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20-=20ModelMapperConfig.java=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring-boot-api/build.gradle | 3 - .../domain/product/api/ProductController.java | 7 +- .../product/application/ProductService.java | 64 ++++++++++++------- .../domain/product/domain/Product.java | 8 ++- .../domain/product/domain/ProductMapper.java | 22 +++++++ .../domain/product/dto/ProductInput.java | 2 + .../domain/product/dto/ProductUpdate.java | 18 ++++++ .../global/config/ModelMapperConfig.java | 14 ---- .../application/ProductServiceTest.java | 46 +++++++------ 9 files changed, 121 insertions(+), 63 deletions(-) create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/ProductMapper.java create mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductUpdate.java delete mode 100644 spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/ModelMapperConfig.java diff --git a/spring-boot-api/build.gradle b/spring-boot-api/build.gradle index a0bd52a..bd560e5 100755 --- a/spring-boot-api/build.gradle +++ b/spring-boot-api/build.gradle @@ -32,9 +32,6 @@ dependencies { implementation 'org.mapstruct:mapstruct:1.4.2.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final' - // for domain -> dto auto change - implementation 'org.modelmapper:modelmapper:2.3.8' - } tasks.named('test') { diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java index 8170057..774bab4 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java @@ -6,6 +6,7 @@ import com.ssafy.springbootapi.domain.product.dto.ProductInput; import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; import com.ssafy.springbootapi.domain.product.dto.ProductOutput; +import com.ssafy.springbootapi.domain.product.dto.ProductUpdate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -16,7 +17,7 @@ @Tag(name = "Product", description = "Product 관련 API 입니다.") @RestController @RequiredArgsConstructor -@CrossOrigin +//@CrossOrigin // spring security 디펜던시 삭제로 인한 주석처리 @RequestMapping("/api/v1/products") public class ProductController { private final ProductService productService; @@ -46,8 +47,8 @@ public ResponseEntity createProduct(@RequestBody ProductInput pro } @Operation(summary = "제품 정보 업데이트") - @PutMapping("/{id}") - public ResponseEntity updateProduct(@PathVariable Long id, @RequestBody ProductInput productDetails) { + @PatchMapping("/{id}") + public ResponseEntity updateProduct(@PathVariable Long id, @RequestBody ProductUpdate productDetails) { // 서비스 레이어를 통해 비즈니스 로직 처리 ProductOutput updatedProduct = productService.updateProduct(id, productDetails); diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java index 7b953af..4ac6cf9 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java @@ -5,62 +5,82 @@ import com.ssafy.springbootapi.domain.product.dao.ProductRepository; import com.ssafy.springbootapi.domain.product.domain.Product; +import com.ssafy.springbootapi.domain.product.domain.ProductMapper; import com.ssafy.springbootapi.domain.product.dto.ProductInput; import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; import com.ssafy.springbootapi.domain.product.dto.ProductOutput; +import com.ssafy.springbootapi.domain.product.dto.ProductUpdate; import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException; import lombok.RequiredArgsConstructor; -import org.modelmapper.ModelMapper; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class ProductService { private final ProductRepository productRepository; - private final ModelMapper modelMapper; + private final ProductMapper productMapper; + /** + * Product 전체 조회 + * @return List(ProductListOutput) + */ public List getAllProducts() { List products = productRepository.findAll(); return products.stream() - .map(product -> modelMapper.map(product, ProductListOutput.class)) + .map(productMapper::toProductListOutput) .collect(Collectors.toList()); } + /** + * Product ID로 조회 + * @param id : 조회할 상품 id + * @return ProductOutput(DTO) + */ public ProductOutput getProductById(Long id) { Product product = findProductByIdOrThrow(id); - return modelMapper.map(product, ProductOutput.class); + return productMapper.toProductOutput(product); } + /** + * Product 삽입 메서드 + * @param productInput : Product 생성 데이터 + * @return ProductOutput(DTO) + */ public ProductOutput insertProduct(ProductInput productInput) { - Product product = modelMapper.map(productInput, Product.class); - return modelMapper.map(productRepository.save(product), ProductOutput.class); + Product product = productMapper.toEntity(productInput); + return productMapper.toProductOutput(productRepository.save(product)); } + /** + * Product 삭제 메서드 + * @param id : 삭제할 product id + * @return ProductOutput(DTO) + */ public ProductOutput removeProduct(Long id) { Product toRemove = findProductByIdOrThrow(id); productRepository.delete(toRemove); - return modelMapper.map(toRemove, ProductOutput.class); + return productMapper.toProductOutput(toRemove); } - - public ProductOutput updateProduct(Long id, ProductInput productInput) { + /** + * Product 수정 메서드 + * @param id : 수정할 product id + * @param productUpdate : 수정할 product 데이터 + * @return ProductOutput(DTO) + */ + public ProductOutput updateProduct(Long id, ProductUpdate productUpdate) { Product productToUpdate = findProductByIdOrThrow(id); - - // 업데이트 로직 - productToUpdate.setCategory(productInput.getCategory()); - productToUpdate.setStock(productInput.getStock()); - productToUpdate.setImageUrl(productInput.getImageUrl()); - - productRepository.save(productToUpdate); - - return modelMapper.map(productToUpdate, ProductOutput.class); + productMapper.updateProduct(productUpdate, productToUpdate); + return productMapper.toProductOutput(productRepository.save(productToUpdate)); } - // ProductService 클래스 내부에 private 메소드 추가 + /** + * id로 product 찾기 또는 없을 경우 Exception 발생 + * @param id : product id + * @return Product(Entity) + */ private Product findProductByIdOrThrow(Long id) { return productRepository.findById(id) .orElseThrow(() -> new NotFoundProductException("Product not found with id: " + id)); } - -} \ No newline at end of file +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java index 65eed26..30a6fb1 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java @@ -13,7 +13,7 @@ @AllArgsConstructor @NoArgsConstructor @Getter -@Setter +//@Setter entity에 setter두는건 안 좋다고 해서 삭제 @Builder public class Product { @Id @@ -38,4 +38,10 @@ public class Product { // private User user; @Column(name = "user_id") private int userId; + // test code를 위해 추가 + public void updateInfo(int category, int stock, String imageUrl) { + this.category = category; + this.stock = stock; + this.imageUrl = imageUrl; + } } \ No newline at end of file diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/ProductMapper.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/ProductMapper.java new file mode 100644 index 0000000..ba5f1d7 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/ProductMapper.java @@ -0,0 +1,22 @@ +package com.ssafy.springbootapi.domain.product.domain; + +import com.ssafy.springbootapi.domain.product.dto.ProductInput; +import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; +import com.ssafy.springbootapi.domain.product.dto.ProductOutput; +import com.ssafy.springbootapi.domain.product.dto.ProductUpdate; +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; + +@Mapper(componentModel = "spring") +public interface ProductMapper { + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void updateProduct(ProductUpdate dto, @MappingTarget Product entity); + + Product toEntity(ProductUpdate dto); + Product toEntity(ProductInput dto); + + ProductOutput toProductOutput(Product entity); + ProductListOutput toProductListOutput(Product entity); +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java index 5999f14..f5808e9 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java @@ -1,5 +1,6 @@ package com.ssafy.springbootapi.domain.product.dto; +import jakarta.validation.constraints.NotBlank; import lombok.*; import lombok.experimental.SuperBuilder; @@ -12,5 +13,6 @@ public class ProductInput extends ProductBase { private String description; private int category; private int stock; + @NotBlank(message = "user id is required") private int user_id; } \ No newline at end of file diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductUpdate.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductUpdate.java new file mode 100644 index 0000000..0b3b948 --- /dev/null +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductUpdate.java @@ -0,0 +1,18 @@ +package com.ssafy.springbootapi.domain.product.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@SuperBuilder +public class ProductUpdate extends ProductBase { + private String description; + private int category; + private int stock; +} diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/ModelMapperConfig.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/ModelMapperConfig.java deleted file mode 100644 index 22c791d..0000000 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/config/ModelMapperConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ssafy.springbootapi.global.config; - -import org.modelmapper.ModelMapper; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class ModelMapperConfig { // Entity -> DTO - - @Bean - public ModelMapper modelMapper() { - return new ModelMapper(); - } -} diff --git a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java index 5233cc2..29784fc 100644 --- a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java +++ b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java @@ -2,14 +2,15 @@ import com.ssafy.springbootapi.domain.product.dao.ProductRepository; import com.ssafy.springbootapi.domain.product.domain.Product; +import com.ssafy.springbootapi.domain.product.domain.ProductMapper; import com.ssafy.springbootapi.domain.product.dto.ProductInput; import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; import com.ssafy.springbootapi.domain.product.dto.ProductOutput; +import com.ssafy.springbootapi.domain.product.dto.ProductUpdate; import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.modelmapper.ModelMapper; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -27,7 +28,7 @@ class ProductServiceTest { private ProductRepository productRepository; @Mock - private ModelMapper modelMapper; + private ProductMapper productMapper; @InjectMocks private ProductService productService; @@ -42,8 +43,8 @@ class ProductServiceTest { ProductListOutput productListOutput2 = new ProductListOutput(); when(productRepository.findAll()).thenReturn(Arrays.asList(product1, product2)); - when(modelMapper.map(product1, ProductListOutput.class)).thenReturn(productListOutput1); - when(modelMapper.map(product2, ProductListOutput.class)).thenReturn(productListOutput2); + when(productMapper.toProductListOutput(product1)).thenReturn(productListOutput1); + when(productMapper.toProductListOutput(product2)).thenReturn(productListOutput2); // when List result = productService.getAllProducts(); @@ -51,6 +52,7 @@ class ProductServiceTest { // then assertThat(result).containsExactly(productListOutput1, productListOutput2); } + @DisplayName("상품 아이디 조회 성공 테스트") @Test void 상품아이디조회성공테스트() { @@ -59,7 +61,7 @@ class ProductServiceTest { ProductOutput productOutput = new ProductOutput(); when(productRepository.findById(1L)).thenReturn(java.util.Optional.of(product)); - when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); + when(productMapper.toProductOutput(product)).thenReturn(productOutput); // when ProductOutput result = productService.getProductById(1L); @@ -67,6 +69,7 @@ class ProductServiceTest { // then assertThat(result).isEqualTo(productOutput); } + @DisplayName("상품 아이디 조회 실패 : 존재하지 않는 아이디 테스트") @Test void 상품아이디조회실패존재하지않는아이디테스트() { @@ -78,6 +81,7 @@ class ProductServiceTest { .isInstanceOf(NotFoundProductException.class) .hasMessageContaining("Product not found with id: 1"); } + @DisplayName("상품 삽입 성공 테스트") @Test void 상품삽입성공테스트() { @@ -86,9 +90,9 @@ class ProductServiceTest { Product product = new Product(); ProductOutput productOutput = new ProductOutput(); - when(modelMapper.map(productInput, Product.class)).thenReturn(product); + when(productMapper.toEntity(productInput)).thenReturn(product); when(productRepository.save(product)).thenReturn(product); - when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); + when(productMapper.toProductOutput(product)).thenReturn(productOutput); // when ProductOutput result = productService.insertProduct(productInput); @@ -106,7 +110,7 @@ class ProductServiceTest { when(productRepository.findById(1L)).thenReturn(java.util.Optional.of(product)); doNothing().when(productRepository).delete(product); - when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); + when(productMapper.toProductOutput(product)).thenReturn(productOutput); // when ProductOutput result = productService.removeProduct(1L); @@ -134,21 +138,24 @@ class ProductServiceTest { int newStock = 20; String newImageUrl = "newImageUrl"; - ProductInput productInput = new ProductInput(); - productInput.setCategory(newCategory); - productInput.setStock(newStock); - productInput.setImageUrl(newImageUrl); + ProductUpdate productUpdate = new ProductUpdate(); + productUpdate.setCategory(newCategory); + productUpdate.setStock(newStock); + productUpdate.setImageUrl(newImageUrl); Product product = new Product(); - product.setId(1L); + product.updateInfo(newCategory, newStock, newImageUrl); ProductOutput productOutput = new ProductOutput(); when(productRepository.findById(1L)).thenReturn(java.util.Optional.of(product)); - when(modelMapper.map(product, ProductOutput.class)).thenReturn(productOutput); + // modelMapper 대신 productMapper 사용 + productMapper.updateProduct(productUpdate, product); + when(productRepository.save(product)).thenReturn(product); + when(productMapper.toProductOutput(product)).thenReturn(productOutput); // when - ProductOutput result = productService.updateProduct(1L, productInput); + ProductOutput result = productService.updateProduct(1L, productUpdate); // then assertThat(result).isEqualTo(productOutput); @@ -156,21 +163,20 @@ class ProductServiceTest { assertThat(product.getStock()).isEqualTo(newStock); assertThat(product.getImageUrl()).isEqualTo(newImageUrl); } + @DisplayName("상품 수정 실패 : 존재하지 않는 아이디 테스트") @Test void 상품수정실패존재하지않는아이디테스트() { // given Long invalidProductId = 1L; - ProductInput productInput = new ProductInput(); - productInput.setCategory(1); - productInput.setStock(10); - productInput.setImageUrl("testUrl"); + ProductUpdate productUpdate = new ProductUpdate(); when(productRepository.findById(invalidProductId)).thenReturn(java.util.Optional.empty()); // when then - assertThatThrownBy(() -> productService.updateProduct(invalidProductId, productInput)) + assertThatThrownBy(() -> productService.updateProduct(invalidProductId, productUpdate)) .isInstanceOf(NotFoundProductException.class) .hasMessageContaining("Product not found with id: " + invalidProductId); } + } From 9a47e92c8851359ab167c30a5f204569d17bd26e Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Tue, 23 Apr 2024 02:21:44 +0000 Subject: [PATCH 07/17] =?UTF-8?q?:sparkles:=20ddl-auto=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring-boot-api/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-api/src/main/resources/application.yml b/spring-boot-api/src/main/resources/application.yml index 48f4a90..408e372 100644 --- a/spring-boot-api/src/main/resources/application.yml +++ b/spring-boot-api/src/main/resources/application.yml @@ -10,7 +10,7 @@ spring: database: mysql database-platform: org.hibernate.dialect.MySQL8Dialect hibernate: - ddl-auto: none + ddl-auto: create generate-ddl: false show-sql: true properties: From fd09bfaf0884f21243c5cff5acbd58504899eb71 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Tue, 23 Apr 2024 02:23:29 +0000 Subject: [PATCH 08/17] =?UTF-8?q?:sparkles:=20=EB=82=A0=EC=A7=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=95=84=EB=93=9C=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EC=B1=84=EC=9A=B0=EA=B8=B0=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=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 --- .../java/com/ssafy/springbootapi/SpringBootApiApplication.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/SpringBootApiApplication.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/SpringBootApiApplication.java index b0177f5..cc77925 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/SpringBootApiApplication.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/SpringBootApiApplication.java @@ -2,7 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +// 날짜 관련 속성 자동 채우기를 위한 어노테이션 +@EnableJpaAuditing @SpringBootApplication public class SpringBootApiApplication { From 138e9f7bb60984d73d42fbc0322d98cc542fb279 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Tue, 23 Apr 2024 02:24:13 +0000 Subject: [PATCH 09/17] =?UTF-8?q?:sparkles:=20=EB=82=A0=EC=A7=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=95=84=EB=93=9C=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EC=B1=84=EC=9A=B0=EA=B8=B0=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20product=EC=99=80=20user=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/product/domain/Product.java | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java index 30a6fb1..38020aa 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/domain/Product.java @@ -2,42 +2,61 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.ssafy.springbootapi.domain.user.domain.User; import jakarta.persistence.*; import lombok.*; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; @Entity +@EntityListeners(AuditingEntityListener.class) @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) @Table(name = "product") @AllArgsConstructor @NoArgsConstructor @Getter -//@Setter entity에 setter두는건 안 좋다고 해서 삭제 +@Setter @Builder public class Product { @Id @GeneratedValue @Column(name = "id", unique = true) private Long id; + @Column(name = "image_url") private String imageUrl; + + @CreatedDate @Column(name = "created_at") private LocalDateTime createdAt; + + @LastModifiedDate @Column(name = "updated_at") private LocalDateTime updatedAt; + @Column(name = "name", nullable = false) private String name; + @Column(name = "price", nullable = false) private int price; + + @Column(name = "description", nullable = false) private String description; + + @Column(nullable = false) private int category; + + @Column(nullable = false) private int stock; -// @ManyToOne -// @JoinColumn(name = "user_id") -// private User user; - @Column(name = "user_id") - private int userId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; +// @Column(name = "user_id") +// private int userId; // test code를 위해 추가 public void updateInfo(int category, int stock, String imageUrl) { this.category = category; From b9c540c290295a86b4e66b0cc4f4c0e7b90840e8 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Tue, 23 Apr 2024 02:24:44 +0000 Subject: [PATCH 10/17] =?UTF-8?q?:art:=20=EC=95=88=20=EC=93=B0=EB=8A=94=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ssafy/springbootapi/domain/product/dto/ProductBase.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java index fb22a54..961ce63 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductBase.java @@ -5,7 +5,6 @@ @NoArgsConstructor @AllArgsConstructor -@Setter @Getter @SuperBuilder public class ProductBase { From c5b546b20e82a320710aca4e49058e551bc854f4 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Tue, 23 Apr 2024 02:25:15 +0000 Subject: [PATCH 11/17] =?UTF-8?q?:art:=20controller=20=EB=A6=AC=ED=84=B4?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=20=ED=8C=80=EC=9B=90=EA=B3=BC=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/product/api/ProductController.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java index 774bab4..358c196 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/api/ProductController.java @@ -7,6 +7,7 @@ import com.ssafy.springbootapi.domain.product.dto.ProductListOutput; import com.ssafy.springbootapi.domain.product.dto.ProductOutput; import com.ssafy.springbootapi.domain.product.dto.ProductUpdate; +import com.ssafy.springbootapi.global.aop.annotation.ToException; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -26,14 +27,14 @@ public class ProductController { @GetMapping("") public ResponseEntity> getAllProducts() { List products = productService.getAllProducts(); - return new ResponseEntity<>(products, HttpStatus.OK); + return ResponseEntity.status(HttpStatus.OK).body(products); } @Operation(summary = "제품 ID로 조회") @GetMapping("/{id}") public ResponseEntity getProductById(@PathVariable Long id) { ProductOutput product = productService.getProductById(id); - return new ResponseEntity<>(product, HttpStatus.OK); + return ResponseEntity.status(HttpStatus.OK).body(product); } @Operation(summary = "새 제품 추가") @@ -43,7 +44,7 @@ public ResponseEntity createProduct(@RequestBody ProductInput pro ProductOutput newProduct = productService.insertProduct(productInput); // 생성된 Product 객체 반환 - return new ResponseEntity<>(newProduct, HttpStatus.CREATED); + return ResponseEntity.status(HttpStatus.CREATED).body(newProduct); } @Operation(summary = "제품 정보 업데이트") @@ -53,12 +54,12 @@ public ResponseEntity updateProduct(@PathVariable Long id, @Reque ProductOutput updatedProduct = productService.updateProduct(id, productDetails); // 업데이트된 Product 객체 반환 - return new ResponseEntity<>(updatedProduct, HttpStatus.OK); + return ResponseEntity.ok().body(updatedProduct); } @Operation(summary = "제품 삭제") @DeleteMapping("/{id}") public ResponseEntity deleteProduct(@PathVariable Long id) { - ProductOutput product = productService.removeProduct(id); - return new ResponseEntity<>(product, HttpStatus.NO_CONTENT); + ProductOutput removedProduct = productService.removeProduct(id); + return ResponseEntity.ok().body(removedProduct); } } \ No newline at end of file From 6e92bf0be53354aa775eff07d1f704fb559f4d56 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Tue, 23 Apr 2024 02:25:55 +0000 Subject: [PATCH 12/17] =?UTF-8?q?:art:=20int=20->=20Long=20=ED=98=95=20?= =?UTF-8?q?=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ssafy/springbootapi/domain/product/dto/ProductInput.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java index f5808e9..986d395 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductInput.java @@ -14,5 +14,5 @@ public class ProductInput extends ProductBase { private int category; private int stock; @NotBlank(message = "user id is required") - private int user_id; + private Long user_id; } \ No newline at end of file From fc165be733f37b178a553401081dd01f8aa49beb Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Tue, 23 Apr 2024 02:26:12 +0000 Subject: [PATCH 13/17] =?UTF-8?q?:art:=20=EC=95=88=20=EC=93=B0=EB=8A=94=20?= =?UTF-8?q?=EC=86=8D=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ssafy/springbootapi/domain/product/dto/ProductOutput.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java index 6d9e14c..ba3dc8b 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductOutput.java @@ -14,5 +14,5 @@ public class ProductOutput extends ProductListOutput{ private String description; private int category; private int stock; - private int user_id; +// private int user_id; } From da6af3f79872e28f2376b9d07814ae5e6cf378e6 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Tue, 23 Apr 2024 02:27:08 +0000 Subject: [PATCH 14/17] =?UTF-8?q?:sparkles:=20GlobalHandler=EC=97=90=20Use?= =?UTF-8?q?rNotFound=20Exception=20=EC=B6=94=EA=B0=80=20:=20=EC=83=81?= =?UTF-8?q?=ED=92=88=EC=97=90=EC=84=9C=EB=8F=84=20=ED=95=B4=EB=8B=B9=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EB=95=8C=EB=AC=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/error/GlobalExceptionHandler.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java index 07f6342..26f8a35 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/global/error/GlobalExceptionHandler.java @@ -1,7 +1,8 @@ package com.ssafy.springbootapi.global.error; import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException; -import org.springframework.http.HttpStatus; + import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -11,6 +12,13 @@ public class GlobalExceptionHandler { // Domain별 Exception 핸들러 @ExceptionHandler(NotFoundProductException.class) public ResponseEntity handleNotFoundProductException(NotFoundProductException ex) { - return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()); } + + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity handleUserNotFoundException(UserNotFoundException ex) { + return ResponseEntity + .status(HttpStatus.NOT_FOUND) + .body(ex.getMessage()); + } } From 585a84548082cfff4d47cbbf73586ee8f2cc4323 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Tue, 23 Apr 2024 02:27:35 +0000 Subject: [PATCH 15/17] =?UTF-8?q?:sparkles:=20insert,=20update=20=EC=95=88?= =?UTF-8?q?=20=EB=90=98=EB=8A=94=20=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/application/ProductService.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java index 4ac6cf9..f04e705 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/application/ProductService.java @@ -1,6 +1,7 @@ package com.ssafy.springbootapi.domain.product.application; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import com.ssafy.springbootapi.domain.product.dao.ProductRepository; @@ -11,15 +12,23 @@ import com.ssafy.springbootapi.domain.product.dto.ProductOutput; import com.ssafy.springbootapi.domain.product.dto.ProductUpdate; import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException; +import com.ssafy.springbootapi.domain.user.dao.UserRepository; +import com.ssafy.springbootapi.domain.user.domain.User; +import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; @Service @RequiredArgsConstructor public class ProductService { private final ProductRepository productRepository; + private final UserRepository userRepository; private final ProductMapper productMapper; + /** * Product 전체 조회 * @return List(ProductListOutput) @@ -47,10 +56,23 @@ public ProductOutput getProductById(Long id) { * @return ProductOutput(DTO) */ public ProductOutput insertProduct(ProductInput productInput) { + // user_id로 User 객체 찾기 + Optional userOptional = userRepository.findById(productInput.getUser_id()); + User user = userOptional.orElseThrow(() -> + new UserNotFoundException("User not found with id: " + productInput.getUser_id()) + ); + + // ProductInput에서 Product 엔티티로 변환 Product product = productMapper.toEntity(productInput); + + // User 객체를 Product 엔티티에 설정 + product.setUser(user); + + // Product 엔티티를 DB에 저장하고, 저장된 엔티티를 ProductOutput DTO로 변환하여 반환 return productMapper.toProductOutput(productRepository.save(product)); } + /** * Product 삭제 메서드 * @param id : 삭제할 product id @@ -68,6 +90,7 @@ public ProductOutput removeProduct(Long id) { * @param productUpdate : 수정할 product 데이터 * @return ProductOutput(DTO) */ + @Transactional public ProductOutput updateProduct(Long id, ProductUpdate productUpdate) { Product productToUpdate = findProductByIdOrThrow(id); productMapper.updateProduct(productUpdate, productToUpdate); From 312d0b9ae109f22b5b740f2639bb18c46c3247eb Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Tue, 23 Apr 2024 02:27:47 +0000 Subject: [PATCH 16/17] =?UTF-8?q?:sparkles:=20insert,=20update=20=EC=95=88?= =?UTF-8?q?=20=EB=90=98=EB=8A=94=20=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ProductServiceTest.java | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java index 29784fc..e21ab2a 100644 --- a/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java +++ b/spring-boot-api/src/test/java/com/ssafy/springbootapi/domain/product/application/ProductServiceTest.java @@ -8,6 +8,9 @@ import com.ssafy.springbootapi.domain.product.dto.ProductOutput; import com.ssafy.springbootapi.domain.product.dto.ProductUpdate; import com.ssafy.springbootapi.domain.product.exception.NotFoundProductException; +import com.ssafy.springbootapi.domain.user.dao.UserRepository; +import com.ssafy.springbootapi.domain.user.domain.User; +import com.ssafy.springbootapi.domain.user.exception.UserNotFoundException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -23,6 +26,8 @@ @ExtendWith(MockitoExtension.class) class ProductServiceTest { + @Mock + private UserRepository userRepository; @Mock private ProductRepository productRepository; @@ -82,15 +87,22 @@ class ProductServiceTest { .hasMessageContaining("Product not found with id: 1"); } + // 상품 삽입 성공 테스트에 User 관련 로직 추가 @DisplayName("상품 삽입 성공 테스트") @Test void 상품삽입성공테스트() { // given + Long userId = 1L; // 테스트용 user_id 값 ProductInput productInput = new ProductInput(); + productInput.setUser_id(userId); // ProductInput에 user_id 설정 Product product = new Product(); + User user = new User(); // 새 User 객체 생성 + user.setId(userId); // User 객체에 id 설정 ProductOutput productOutput = new ProductOutput(); + when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); // userRepository.findById 호출 시 user 반환 when(productMapper.toEntity(productInput)).thenReturn(product); + product.setUser(user); // Product 객체에 User 설정 when(productRepository.save(product)).thenReturn(product); when(productMapper.toProductOutput(product)).thenReturn(productOutput); @@ -101,6 +113,23 @@ class ProductServiceTest { assertThat(result).isEqualTo(productOutput); } + @DisplayName("상품 삽입 실패 : 존재하지 않는 사용자 테스트") + @Test + void 상품삽입실패존재하지않는사용자테스트() { + // given + ProductInput productInput = new ProductInput(); + productInput.setUser_id(1L); // 존재하지 않는 사용자 ID 설정 + + when(userRepository.findById(1L)).thenReturn(java.util.Optional.empty()); + + // when then + assertThatThrownBy(() -> productService.insertProduct(productInput)) + .isInstanceOf(UserNotFoundException.class) + .hasMessageContaining("User not found with id: 1"); + } + + + @DisplayName("상품 삭제 성공 테스트") @Test void 상품삭제성공테스트() { @@ -139,9 +168,11 @@ class ProductServiceTest { String newImageUrl = "newImageUrl"; ProductUpdate productUpdate = new ProductUpdate(); - productUpdate.setCategory(newCategory); - productUpdate.setStock(newStock); - productUpdate.setImageUrl(newImageUrl); + productUpdate.builder() + .category(newCategory) + .stock(newStock) + .imageUrl(newImageUrl).build(); + Product product = new Product(); product.updateInfo(newCategory, newStock, newImageUrl); From 8d6a8e2b6f599f7cfd0cfe819d288bc50f156dc9 Mon Sep 17 00:00:00 2001 From: wintiger98 Date: Tue, 23 Apr 2024 02:27:59 +0000 Subject: [PATCH 17/17] =?UTF-8?q?:art:=20=EC=95=88=20=EC=93=B0=EB=8A=94=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ssafy/springbootapi/domain/product/dto/ProductUpdate.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductUpdate.java b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductUpdate.java index 0b3b948..473a6b1 100644 --- a/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductUpdate.java +++ b/spring-boot-api/src/main/java/com/ssafy/springbootapi/domain/product/dto/ProductUpdate.java @@ -9,7 +9,6 @@ @AllArgsConstructor @NoArgsConstructor @Getter -@Setter @SuperBuilder public class ProductUpdate extends ProductBase { private String description;