diff --git a/README.md b/README.md index cafde8a2c..1991ea826 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# spring-gift-product \ No newline at end of file +# spring-gift-product + +## 기능 목록 +- 상품 추가 기능 +- 상품 조회 기능 +- 상품 수정 기능 +- 상품 삭제 기능 +- 상품 테스트 코드 작성 +- 리팩토링 \ No newline at end of file diff --git a/build.gradle b/build.gradle index df7db9334..9742d9d3d 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,9 @@ dependencies { runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + implementation 'org.springframework.boot:spring-boot-starter-validation' + } tasks.named('test') { diff --git a/src/main/java/gift/Application.java b/src/main/java/gift/Application.java index 61603cca0..6084d34ae 100644 --- a/src/main/java/gift/Application.java +++ b/src/main/java/gift/Application.java @@ -8,4 +8,6 @@ public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + + } diff --git a/src/main/java/gift/controller/BasicController.java b/src/main/java/gift/controller/BasicController.java new file mode 100644 index 000000000..516b5070c --- /dev/null +++ b/src/main/java/gift/controller/BasicController.java @@ -0,0 +1,36 @@ +package gift.controller; + +import gift.service.ProductService; +import gift.model.Product; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping( "/api") +public class BasicController { + + private final ProductService productService; + + @Autowired + public BasicController(ProductService productService) { + this.productService = productService; + } + + /** + * 홈 화면 렌더링 (thymeleaf) + * + * @param model + * @return 홈 화면 html 명 + */ + @GetMapping + String homePage(Model model) { + // 현재 상품 목록 조회 + List products = productService.getProducts(); + model.addAttribute("products", products); + return "index"; + } +} diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java new file mode 100644 index 000000000..9c349f8f3 --- /dev/null +++ b/src/main/java/gift/controller/ProductController.java @@ -0,0 +1,101 @@ +package gift.controller; + +import gift.global.response.ResponseMaker; +import gift.global.response.ResultResponseDto; +import gift.global.response.SimpleResultResponseDto; +import gift.service.ProductService; +import gift.dto.ProductDTO; +import gift.model.Product; +import jakarta.validation.Valid; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/products") +public class ProductController { + + private final ProductService productService; + + @Autowired + public ProductController(ProductService productService) { + this.productService = productService; + } + + /** + * 상품 추가 + * + * @param productDTO + * @return 결과 메시지 + */ + @PostMapping + public ResponseEntity createProduct( + @Valid @ModelAttribute ProductDTO productDTO) { + productService.createProduct(productDTO); + return ResponseMaker.createSimpleResponse(HttpStatus.CREATED, "상품이 추가되었습니다."); + } + + /** + * 전체 상품 목록 조회 + * + * @return 결과 메시지, products (상품 목록) + */ + @GetMapping + public ResponseEntity>> getProducts() { + List products = productService.getProducts(); + // 성공 시 + return ResponseMaker.createResponse(HttpStatus.OK, "전체 목록 상품을 조회했습니다.", products); + } + + + /** + * 상품 수정 + * + * @param id + * @param productDTO + * @return 결과 메시지 + */ + @PutMapping("/{id}") + public ResponseEntity updateProduct(@PathVariable("id") Long id, + @Valid @RequestBody ProductDTO productDTO) { + productService.updateProduct(id, productDTO); + return ResponseMaker.createSimpleResponse(HttpStatus.OK, "상품을 수정했습니다."); + } + + + /** + * 해당 ID 리스트에 속한 상품 삭제 + * + * @param productIds + * @return 결과 메시지 + */ + @DeleteMapping + public ResponseEntity deleteSelectedProducts(@RequestBody List productIds) { + productService.deleteProductsByIds(productIds); + return ResponseMaker.createSimpleResponse(HttpStatus.OK, "선택된 상품들을 삭제했습니다."); + } + + /** + * 해당 ID 를 가진 상품 삭제 + * + * @param id + * @return 결과 메시지 + */ + @DeleteMapping("/{id}") + public ResponseEntity deleteProduct(@PathVariable("id") Long id) { + productService.deleteProduct(id); + return ResponseMaker.createSimpleResponse(HttpStatus.OK, "상품이 삭제되었습니다."); + } + + +} diff --git a/src/main/java/gift/dto/ProductDTO.java b/src/main/java/gift/dto/ProductDTO.java new file mode 100644 index 000000000..f6ec7695d --- /dev/null +++ b/src/main/java/gift/dto/ProductDTO.java @@ -0,0 +1,50 @@ +package gift.dto; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public class ProductDTO { + + @NotBlank + private String name; + + @Min(0) + private int price; + + @NotBlank + private String imageUrl; + + public ProductDTO() { + } + + public ProductDTO(String name, int price, String imageUrl) { + this.name = name; + this.price = price; + 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; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } +} diff --git a/src/main/java/gift/global/exception/BusinessException.java b/src/main/java/gift/global/exception/BusinessException.java new file mode 100644 index 000000000..4a2dd1134 --- /dev/null +++ b/src/main/java/gift/global/exception/BusinessException.java @@ -0,0 +1,24 @@ +package gift.global.exception; + +import org.springframework.http.HttpStatus; + +/** + * RuntimeException 을 상속받는 커스텀 에러 + * 개발자가 직접 날리는 에러 + */ +public class BusinessException extends RuntimeException{ + HttpStatus status; + + public BusinessException(HttpStatus status, String message) { + super(message); + this.status = status; + } + + public HttpStatus getStatus() { + return status; + } + + public void setStatus(HttpStatus status) { + this.status = status; + } +} diff --git a/src/main/java/gift/global/handler/GlobalExceptionHandler.java b/src/main/java/gift/global/handler/GlobalExceptionHandler.java new file mode 100644 index 000000000..d8a3ff71d --- /dev/null +++ b/src/main/java/gift/global/handler/GlobalExceptionHandler.java @@ -0,0 +1,38 @@ +package gift.global.handler; + +import gift.global.exception.BusinessException; +import gift.global.response.ErrorResponseDto; +import gift.global.response.ResponseMaker; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * RuntimeException 을 상속받는 커스텀 에러 핸들러 + * 개발자가 직접 날리는 에러 + * @param e + * @return + */ + @ExceptionHandler(BusinessException.class) + public ResponseEntity handleBusinessException(BusinessException e) { + return ResponseMaker.createErrorResponse(e.getStatus(), e.getMessage()); + } + + + /** + * MethodArgumentNotValidException 에러 핸들러 + * 매개변수 인자가 유효하지 않은 경우 발생 + * @param e + * @return + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity MethodArgumentNotValidException( + MethodArgumentNotValidException e) { + return ResponseMaker.createErrorResponse(HttpStatus.BAD_REQUEST, "입력값이 유효하지 않습니다."); + } +} diff --git a/src/main/java/gift/global/response/ErrorResponseDto.java b/src/main/java/gift/global/response/ErrorResponseDto.java new file mode 100644 index 000000000..199b609b3 --- /dev/null +++ b/src/main/java/gift/global/response/ErrorResponseDto.java @@ -0,0 +1,5 @@ +package gift.global.response; + +public record ErrorResponseDto(String message) { + +} \ No newline at end of file diff --git a/src/main/java/gift/global/response/ResponseMaker.java b/src/main/java/gift/global/response/ResponseMaker.java new file mode 100644 index 000000000..7c2e416ab --- /dev/null +++ b/src/main/java/gift/global/response/ResponseMaker.java @@ -0,0 +1,47 @@ +package gift.global.response; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +/** + * 응답 객체를 생성하는 클래스 + */ +public class ResponseMaker { + + /** + * BODY 에 성공 메시지와 데이터를 보냄 + * @param message + * @param data + * @return + * @param + */ + public static ResponseEntity> createResponse(HttpStatus status, String message, T data) { + ResultResponseDto resultResponseDto = new ResultResponseDto<>(message, data); + + return ResponseEntity.status(status).body(resultResponseDto); + } + + /** + * BODY 에 성공 메시지만 보냄 (데이터 X) + * @param message + * @return + */ + public static ResponseEntity createSimpleResponse(HttpStatus status, String message) { + System.out.println("message = " + message); + SimpleResultResponseDto simpleResultResponseDto = new SimpleResultResponseDto(message); + System.out.println("simpleResultResponseDto = " + simpleResultResponseDto); + return ResponseEntity.status(status).body(simpleResultResponseDto); + } + + /** + * BODY 에 에러 메시지만 보냄 (데이터 X) + * @param message + * @return + */ + public static ResponseEntity createErrorResponse(HttpStatus status, String message) { + ErrorResponseDto errorResponseDto = new ErrorResponseDto(message); + return ResponseEntity.status(status) + .body(errorResponseDto); + } + +} diff --git a/src/main/java/gift/global/response/ResultResponseDto.java b/src/main/java/gift/global/response/ResultResponseDto.java new file mode 100644 index 000000000..aa783ba76 --- /dev/null +++ b/src/main/java/gift/global/response/ResultResponseDto.java @@ -0,0 +1,12 @@ +package gift.global.response; + +/** + * 메시지, "데이터" 전달 + * + * @param message + * @param data + * @param + */ +public record ResultResponseDto(String message, T data) { + +} diff --git a/src/main/java/gift/global/response/SimpleResultResponseDto.java b/src/main/java/gift/global/response/SimpleResultResponseDto.java new file mode 100644 index 000000000..444fc255e --- /dev/null +++ b/src/main/java/gift/global/response/SimpleResultResponseDto.java @@ -0,0 +1,10 @@ +package gift.global.response; + +/** + * 메시지 전달 (데이터 X) + * + * @param message + */ +public record SimpleResultResponseDto(String message) { + +} \ No newline at end of file diff --git a/src/main/java/gift/global/validation/validator.java b/src/main/java/gift/global/validation/validator.java new file mode 100644 index 000000000..ba87bd16a --- /dev/null +++ b/src/main/java/gift/global/validation/validator.java @@ -0,0 +1,21 @@ +package gift.global.validation; + +import gift.dto.ProductDTO; +import gift.global.exception.BusinessException; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * 유효성 검증 클래스 + */ +@Component +public class validator { + + public void validateProduct(ProductDTO productDTO) { + if (productDTO.getName() == "" + || productDTO.getPrice() == 0 + || productDTO.getImageUrl() == "") { + throw new BusinessException(HttpStatus.BAD_REQUEST, "입력값이 유효하지 않습니다."); + } + } +} diff --git a/src/main/java/gift/model/Product.java b/src/main/java/gift/model/Product.java new file mode 100644 index 000000000..1317c34e4 --- /dev/null +++ b/src/main/java/gift/model/Product.java @@ -0,0 +1,62 @@ +package gift.model; + +public class Product { + + private Long id; + private String name; + private int price; + private String imageUrl; + + public Product() { + + } + + public Product(String name, int price, String imageUrl) { + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getId() { + return this.id; + } + + + 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; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + @Override + public String toString() { + return "Product{" + + "id=" + id + + ", name='" + name + '\'' + + ", price=" + price + + ", imageUrl='" + imageUrl + '\'' + + '}'; + } +} diff --git a/src/main/java/gift/service/ProductService.java b/src/main/java/gift/service/ProductService.java new file mode 100644 index 000000000..1aa79c698 --- /dev/null +++ b/src/main/java/gift/service/ProductService.java @@ -0,0 +1,105 @@ +package gift.service; + +import gift.dto.ProductDTO; +import gift.global.exception.BusinessException; +import gift.model.Product; +import gift.global.validation.validator; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +@Service +public class ProductService { + + private final JdbcTemplate jdbcTemplate; // h2 DB 사용한 메모리 저장 방식 + private final validator validator; // 유효성 검증 + + @Autowired + public ProductService(JdbcTemplate jdbcTemplate, validator validator) { + this.jdbcTemplate = jdbcTemplate; + this.validator = validator; + } + + /** + * 상품 추가 + * + * @param productDTO + */ + public void createProduct(ProductDTO productDTO) { + System.out.println("여기까지 올 수 있나?"); + + String sql = "INSERT INTO product (name, price, image_url) VALUES (?, ?, ?)"; + + int rowNum = jdbcTemplate.update(sql, productDTO.getName(), productDTO.getPrice(), + productDTO.getImageUrl()); + + if (rowNum == 0) { + throw new BusinessException(HttpStatus.BAD_REQUEST, "상품 추가에 실패했습니다."); + } + } + + /** + * 전체 싱픔 목록 조회 + * + * @return 전체 상품 목록 + */ + public List getProducts() { + String sql = "SELECT * FROM product ORDER BY ID ASC"; + + List products = jdbcTemplate.query(sql, + BeanPropertyRowMapper.newInstance(Product.class)); + + return products; + } + + /** + * 상품 수정 + * + * @param id + * @param productDTO + */ + public void updateProduct(Long id, ProductDTO productDTO) { + String sql = "UPDATE product SET name = ?, price = ?, image_url = ? WHERE id = ?"; + + int rowNum = jdbcTemplate.update(sql, productDTO.getName(), productDTO.getPrice(), + productDTO.getImageUrl(), id); + if (rowNum == 0) { + throw new BusinessException(HttpStatus.NOT_FOUND, "상품 수정에 실패했습니다."); + } + } + + + /** + * 상품 삭제 + * + * @param id + */ + public void deleteProduct(Long id) { + String sql = "DELETE FROM product WHERE id = ?"; + int rowNum = jdbcTemplate.update(sql, id); + if (rowNum == 0) { + throw new BusinessException(HttpStatus.NOT_FOUND, "상품 삭제에 실패했습니다."); + } + } + + /** + * 해당 ID 리스트에 속한 상품들 삭제 + * + * @param productIds + */ + public void deleteProductsByIds(List productIds) { + int rowNum = 0; + String sql = "DELETE FROM product WHERE id = ?"; + for (Long productId : productIds) { + int update = jdbcTemplate.update(sql, productId); + rowNum += update; + } + // 모두 삭제가 이루어지지 않은 경우 + if (rowNum != productIds.size()) { + throw new BusinessException(HttpStatus.NOT_FOUND, "선택된 상품들 삭제에 실패했습니다."); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3d16b65f4..946d2834c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,15 @@ spring.application.name=spring-gift + +# h2-console +spring.h2.console.enabled=true +# db url +spring.datasource.url=jdbc:h2:mem:test + +# Enable logging for JdbcTemplate +logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG +logging.level.org.springframework.jdbc.core.StatementCreatorUtils=TRACE + +# H2 ??????? ?? ??? +spring.sql.init.mode=always +spring.sql.init.schema-locations=classpath:schema.sql +spring.sql.init.data-locations=classpath:data.sql \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 000000000..aa9159193 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,3 @@ +INSERT INTO product (name, price, image_url) VALUES ('아이스 아메리카노 T', 4500, 'https://example.com/image.jpg'); +INSERT INTO product (name, price, image_url) VALUES ('아이스 카푸치노 M', 4700, 'https://example.com/image.jpg'); +INSERT INTO product (name, price, image_url) VALUES ('핫 말차라떼 L', 6800, 'https://example.com/image.jpg'); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 000000000..a471aad4a --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,11 @@ +DROP TABLE IF EXISTS product; + +CREATE TABLE product +( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + price DECIMAL(10, 2) NOT NULL, + image_url VARCHAR(255) +); + +ALTER TABLE product ALTER COLUMN id RESTART WITH 1; \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 000000000..a815acc0d --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,279 @@ + + + + + Product Management System + + + + +
+

Products List

+ + + + + + + + + + + + + + + + + + + + + + +
SelectIDNamePriceImage URL
+ + + + + + +
+
+ + + + + + + + + + + +
+ +
+ + + + + + + + + + + diff --git a/src/test/java/gift/ProductTest.java b/src/test/java/gift/ProductTest.java new file mode 100644 index 000000000..052cd71a9 --- /dev/null +++ b/src/test/java/gift/ProductTest.java @@ -0,0 +1,159 @@ +package gift; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import gift.dto.ProductDTO; +import gift.model.Product; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class ProductTest { + + @LocalServerPort + private int port; + @Autowired + private TestRestTemplate restTemplate; + @Autowired + private ObjectMapper objectMapper; + + private Map products; + + @BeforeEach + void setUp() { + products = new HashMap<>(); + } + + /** + * 싱품 추가 테스트 + */ + @Test + void postProductTest() { + //given + String name = "아이스 아메리카노 T"; + int price = 4500; + String imageUrl = "testImageUrl.com"; + + ProductDTO productDTO = new ProductDTO(name, price, imageUrl); + String url = "http://localhost:" + port + "/api/products"; + + //when + ResponseEntity responseEntity = restTemplate.postForEntity(url, productDTO, + Object.class); + + //then + assertEquals(responseEntity.getStatusCode(), HttpStatus.OK); + Product product = objectMapper.convertValue(responseEntity.getBody(), Product.class); + + assertThat(product.getName()).isEqualTo("아이스 아메리카노 T"); + assertThat(product.getPrice()).isEqualTo(4500); + assertThat(product.getImageUrl()).isEqualTo("testImageUrl.com"); + } + + /** + * 상품 조회 테스트 + */ + @Test + void getProductTest() { + //given + String name = "아이스 아메리카노 T"; + int price = 4500; + String imageUrl = "testImageUrl.com"; + + ProductDTO productDTO = new ProductDTO(name, price, imageUrl); + String url = "http://localhost:" + port + "/api/products"; + ResponseEntity postResponseEntity = restTemplate.postForEntity(url, productDTO, + Object.class); + Product postProduct = objectMapper.convertValue(postResponseEntity.getBody(), + Product.class); + Long postId = postProduct.getId(); + System.out.println("postProduct = " + postProduct); + + //when + url = "http://localhost:" + port + "/api/products/" + postId; + ResponseEntity getResponseEntity = restTemplate.getForEntity(url, Object.class); + Product getProduct = objectMapper.convertValue(getResponseEntity.getBody(), Product.class); + Long getId = getProduct.getId(); + System.out.println("getProduct = " + getProduct); + + //then + assertThat(getResponseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(getProduct.getId()).isEqualTo(postId); + assertThat(getProduct.getName()).isEqualTo(name); + assertThat(getProduct.getPrice()).isEqualTo(price); + assertThat(getProduct.getImageUrl()).isEqualTo(imageUrl); + } + + /** + * 상품 수정 테스트 + */ + @Test + void updateProductTest() { + //given + String name = "아이스 아메리카노 T"; + int price = 4500; + String imageUrl = "testImageUrl.com"; + + ProductDTO productDTO = new ProductDTO(name, price, imageUrl); + String url = "http://localhost:" + port + "/api/products"; + ResponseEntity postResponseEntity = restTemplate.postForEntity(url, productDTO, + Object.class); + Product postProduct = objectMapper.convertValue(postResponseEntity.getBody(), + Product.class); + Long postId = postProduct.getId(); + + //when + ProductDTO updateProductDTO = new ProductDTO(postProduct.getName(), 4700, + postProduct.getImageUrl()); + String updateUrl = "http://localhost:" + port + "/api/products/" + postId; + restTemplate.put(updateUrl, updateProductDTO, Object.class); + + String getUrl = updateUrl; + ResponseEntity getResponseEntity = restTemplate.getForEntity(getUrl, Object.class); + Product getProduct = objectMapper.convertValue(getResponseEntity.getBody(), Product.class); + + //then + assertThat(getProduct.getId()).isEqualTo(postId); + assertThat(getProduct.getName()).isEqualTo(name); + assertThat(getProduct.getPrice()).isEqualTo(4700); + assertThat(getProduct.getImageUrl()).isEqualTo(imageUrl); + } + + /** + * 상품 삭제 테스트 + */ + @Test + void deleteProduct() { + //given + String name = "아이스 아메리카노 T"; + int price = 4500; + String imageUrl = "testImageUrl.com"; + + ProductDTO productDTO = new ProductDTO(name, price, imageUrl); + String url = "http://localhost:" + port + "/api/products"; + ResponseEntity postResponseEntity = restTemplate.postForEntity(url, productDTO, + Object.class); + Product product = objectMapper.convertValue(postResponseEntity.getBody(), Product.class); + Long id = product.getId(); + + //when + String deleteUrl = "http://localhost:" + port + "/api/products/" + id; + restTemplate.delete(deleteUrl); + + //then + String getUrl = "http://localhost:" + port + "/api/products/" + id; + ResponseEntity getResponseEntity = restTemplate.getForEntity(getUrl, String.class); + assertThat(getResponseEntity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(getResponseEntity.getBody()).isEqualTo("해당 ID의 상품이 존재하지 않습니다."); + } +}