diff --git a/server/build.gradle b/server/build.gradle index f25380f8..a9149661 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -2,8 +2,8 @@ plugins { id 'java' id 'org.springframework.boot' version '3.3.1' id 'io.spring.dependency-management' version '1.1.5' - id 'checkstyle' - id 'org.asciidoctor.jvm.convert' version '3.3.2' //1 +// id 'checkstyle' + id 'org.asciidoctor.jvm.convert' version '3.3.2' // REST Docs } group = 'kr.or.kosa' @@ -19,7 +19,7 @@ configurations { compileOnly { extendsFrom annotationProcessor } - asciidoctorExtensions //2 + asciidoctorExtensions // REST Docs } repositories { @@ -27,7 +27,7 @@ repositories { } ext { - set('snippetsDir', file("build/generated-snippets")) //3 + set('snippetsDir', file("build/generated-snippets")) // REST Docs } dependencies { @@ -44,35 +44,41 @@ dependencies { compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - //4 + // REST Docs testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' } test { useJUnitPlatform() - outputs.dir snippetsDir + outputs.dir snippetsDir // REST Docs } +// REST Docs asciidoctor { inputs.dir snippetsDir configurations 'asciidoctorExtensions' dependsOn test + // source를 지정하면 특정 adoc만 HTML로 만듬 sources { include("**/*.adoc") } + // 경로를 baseDir로 맞춰줌 baseDirFollowsSourceFile() } +// REST Docs task copyDocs(type: Copy) { dependsOn asciidoctor from "${asciidoctor.outputDir}" into "src/main/resources/static/docs" // src/main/resources/static/docs로 복사 } +// REST Docs build { dependsOn copyDocs } @@ -96,22 +102,22 @@ clean.doLast { // Lint -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' -} - -tasks.withType(Checkstyle).configureEach { - mustRunAfter 'compileJava' - reports { - xml.required = false - html.required = true - } -} - -checkstyle { - configFile = file("config/checkstyle/naver-checkstyle-rules.xml") - configProperties = ["suppressionFile": "config/checkstyle/naver-checkstyle-suppressions.xml"] - sourceSets = [sourceSets.main] // CompileQuerydsl 오류 해결 -} - -checkstyleMain.source = fileTree('src/main/java') +//tasks.withType(JavaCompile).configureEach { +// options.encoding = 'UTF-8' +//} +// +//tasks.withType(Checkstyle).configureEach { +// mustRunAfter 'compileJava' +// reports { +// xml.required = false +// html.required = true +// } +//} +// +//checkstyle { +// configFile = file("config/checkstyle/naver-checkstyle-rules.xml") +// configProperties = ["suppressionFile": "config/checkstyle/naver-checkstyle-suppressions.xml"] +// sourceSets = [sourceSets.main] // CompileQuerydsl 오류 해결 +//} +// +//checkstyleMain.source = fileTree('src/main/java') diff --git a/server/src/docs/asciidoc/index.adoc b/server/src/docs/asciidoc/index.adoc index eacbba4f..3a2b910b 100644 --- a/server/src/docs/asciidoc/index.adoc +++ b/server/src/docs/asciidoc/index.adoc @@ -10,7 +10,110 @@ made by Sanghun.Kim :toc: left :toclevels: 4 :sectlinks: -:operation-http-request-title: Request -:operation-http-response-title: Response +:operation-http-request-title: REQUEST +:operation-http-response-title: RESPONSE -include::{docdir}/products/products.adoc[] \ No newline at end of file +[[POST-API]] +== 1. 상품 + +=== 1.1. 상품 목록 조회 + +.Request Header Parameter +|=== +|파라미터 |타입 |필수여부 |설명 +|Authorization |String |필수 |ACCESS_TOKEN +|X-CSRF |String |필수 |CSRF_TOKEN +|=== + +.Request Query Parameter +|=== +|파라미터 |타입 |필수여부 |설명 +|page |Integer|필수 |페이지 번호 +|offset |Integer|필수 |페이지당 오프셋 +|sort |String|선택 |정렬 기준 +|name |String|선택 |상품명으로 검색 +|price |double|선택 |금액으로 검색 +|contractCount |Integer|선택 |계약수로 검색 +|createdAt |String|선택 |생성시기로 검색 +|memo |String|선택 |비고로 검색 +|=== + +operation::findAllProductByCondition[snippets='http-request,http-response'] + +=== 1.2. 상품 등록 + +.Request Header Parameter +|=== +|파라미터 |타입 |필수여부 |설명 +|Authorization |String |필수 |ACCESS_TOKEN +|X-CSRF |String |필수 |CSRF_TOKEN +|=== + +.Request Body Parameter +|=== +|파라미터 |타입 |필수여부 |설명 +|name |String|필수 |상품명 +|price |double|필수 |금액 +|memo |Stirng|필수 |비고 +|=== + +operation::saveProduct[snippets='http-request,http-response'] + +=== 1.3. 상품 상세 조회 + +.Request Header Parameter +|=== +|파라미터 |타입 |필수여부 |설명 +|Authorization |String |필수 |ACCESS_TOKEN +|X-CSRF |String |필수 |CSRF_TOKEN +|=== + +.Request Path Parameter +|=== +|파라미터 |타입 |필수여부 |설명 +|PRODUCT_ID |Long|필수 |상품번호 +|=== + +operation::findProductById[snippets='http-request,http-response'] + +=== 1.4. 상품 수정 + +.Request Header Parameter +|=== +|파라미터 |타입 |필수여부 |설명 +|Authorization |String |필수 |ACCESS_TOKEN +|X-CSRF |String |필수 |CSRF_TOKEN +|=== + +.Request Path Parameter +|=== +|파라미터 |타입 |필수여부 |설명 +|PRODUCT_ID |Long|필수 |상품번호 +|=== + +.Request Body Parameter +|=== +|파라미터 |타입 |필수여부 |설명 +|name |String|선택 |상품명 +|price |double|선택 |금액 +|notes |Stirng|선택 |비고 +|=== + +operation::updateProduct[snippets='http-request,http-response'] + +=== 1.5. 상품 삭제 + +.Request Header +|=== +|파라미터 |타입 |필수여부 |설명 +|Authorization |String |필수 |인증 키 +|=== + +.Request Path Parameter +|=== +|파라미터 |타입 |필수여부 |설명 +|PRODUCT_ID |Long|필수 |상품번호 +|=== + + +operation::deleteProduct[snippets='http-request,http-response'] \ No newline at end of file diff --git a/server/src/docs/asciidoc/products/products-get.adoc b/server/src/docs/asciidoc/products/products-get.adoc deleted file mode 100644 index 58528017..00000000 --- a/server/src/docs/asciidoc/products/products-get.adoc +++ /dev/null @@ -1,17 +0,0 @@ -[[GET-POST]] -=== 상품 조회 - -.Request Header -|=== -|파라미터 |타입 |필수여부 |설명 -|Authorization |String |필수 |인증 키 -|=== - -.Request Parameter -|=== -|파라미터 |타입 |필수여부 |설명 -|page |Integer|선택 |페이지 번호 -|size |Integer|선택 |한 페이지에 표시할 데이터 개수 -|=== - -operation::getProducts[snippets='http-request,http-response'] \ No newline at end of file diff --git a/server/src/docs/asciidoc/products/products.adoc b/server/src/docs/asciidoc/products/products.adoc deleted file mode 100644 index 4e7bfefb..00000000 --- a/server/src/docs/asciidoc/products/products.adoc +++ /dev/null @@ -1,4 +0,0 @@ -[[POST-API]] -== 상품 - -include::{docdir}/products/products-get.adoc[] \ No newline at end of file diff --git a/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/controller/TProductController.java b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/controller/TProductController.java new file mode 100644 index 00000000..cb257a75 --- /dev/null +++ b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/controller/TProductController.java @@ -0,0 +1,136 @@ +package kr.or.kosa.cmsplusmain.test.domain.product.controller; + +import kr.or.kosa.cmsplusmain.test.domain.product.dto.TProductDto; +import kr.or.kosa.cmsplusmain.test.domain.product.dto.TProductFindAllByConditionRes; +import kr.or.kosa.cmsplusmain.test.domain.product.dto.TProductSaveReq; +import kr.or.kosa.cmsplusmain.test.domain.product.dto.TProductUpdateReq; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +@RestController +@RequestMapping("/api/v1/vendor/product") +public class TProductController { + + // 초기화 + public TProductController() { + initData(); + } + + // 상품 리스트 + private final List productList = new ArrayList<>(); + + // 테스트용 더미 데이터 생성 (조회시 등록하지않으나 있다고 가정하고 하나 넣어야함 | 실제 DB 연동으로 대체해야 함) + public void initData() { + + // 테스트에서 중복 실행 방지 초기화 + productList.clear(); + List initData = List.of( + new TProductDto(1L, 1001L, "상품 조회용 데이터1", 10.0, 10, "2024-01-01 11:11:11", "Null", "Null", "비고 1", "STATUS1"), + new TProductDto(2L, 1002L, "상품 조회용 데이터2", 20.0, 22, "2024-01-02 11:11:11", "2024-01-01 11:11:11", "2024-01-01", "비고 2", "STATUS2") + ); + + // 더미데이터 세팅 + productList.addAll(initData); + + } + + // 상품 목록 조회 + @GetMapping + public TProductFindAllByConditionRes findAllProductByCondition(@RequestParam int page, @RequestParam int offset) { + + // /api/v1/vendor/products?page=1&offset=10 + // 페이지네이션 이외에 정렬, 필터, 검색 기능 염두해야 함 + // 페이지네이션과 정렬, 필터링 기능을 구현해야 하지만 여기서는 간단히 더미 데이터를 반환 + List data = new ArrayList<>(productList); + + // Pagination (실제 로직에 따라 계산 필요) + int totalPage = 100; + int totalCount = 1000; + + TProductFindAllByConditionRes tProductFindAllByConditionRes = new TProductFindAllByConditionRes(); + tProductFindAllByConditionRes.setPage(page); + tProductFindAllByConditionRes.setOffset(offset); + tProductFindAllByConditionRes.setTotalPage(totalPage); + tProductFindAllByConditionRes.setTotalCount(totalCount); + tProductFindAllByConditionRes.setData(data); + + return tProductFindAllByConditionRes; + + } + + // 상품 등록 + @PostMapping + public TProductDto saveProduct(@RequestBody TProductSaveReq tProductSaveReq) { + + // 새로 등록할 상품 + TProductDto newProduct = new TProductDto( + tProductSaveReq.getId(), + tProductSaveReq.getVendorId(), + tProductSaveReq.getName(), + tProductSaveReq.getPrice(), + 0, // 이건 DB에서 알아서 할듯 + tProductSaveReq.getCreatedDateTime(), + null, // 이건 DB에서 알아서 할듯 + null, // 이건 DB에서 알아서 할듯 + tProductSaveReq.getMemo(), + "STATUS1" // 이건 DB에서 알아서 할듯 + ); + + productList.add(newProduct); + + return newProduct; + } + + // 상품 상세 조회 + @GetMapping("/{PRODUCT_ID}") + public TProductDto findProductById(@PathVariable("PRODUCT_ID") Long productId) { + + // 람다식으로 해당 상품아이디 매핑 + // 실제 코드에선 DTO를 분리해서 해야하나 현재 기본 TProductDto에 데이터를 강제로 주입시키기 때문에 애로사항이 있음 + TProductDto product = productList.stream() + .filter(p -> p.getId().equals(productId)) + .findFirst() + .orElse(null); + + return product; + } + + // 상품 수정 + @PutMapping("/{PRODUCT_ID}") + public TProductDto updateProduct(@PathVariable("PRODUCT_ID") Long productId, @RequestBody TProductUpdateReq tProductUpdateReq) { + + // 람다식으로 해당 상품아이디 매핑 + TProductDto updatedProduct = productList.stream() + .filter(p -> p.getId().equals(productId)) + .findFirst() + .orElse(null); + + // 상품 수정할 정보 + updatedProduct.setName(tProductUpdateReq.getName()); + updatedProduct.setPrice(tProductUpdateReq.getPrice()); + updatedProduct.setMemo(tProductUpdateReq.getMemo()); + updatedProduct.setUpdatedDateTime("2024-01-01 11:11:11"); // 이건 DB에서 알아서 할듯 + + return updatedProduct; + } + + // 상품 삭제 + @DeleteMapping("/{PRODUCT_ID}") + public TProductDto deleteProduct(@PathVariable("PRODUCT_ID") Long productId) { + + // 람다식으로 해당 상품아이디 매핑 + TProductDto product = productList.stream() + .filter(p -> p.getId().equals(productId)) + .findFirst() + .orElse(null); + + // 실제 삭제 대신 상태 업데이트 + product.setStatus("DELETED"); + product.setDeletedDate("2024-01-01"); // 이건 DB에서 알아서 할듯 + + return product; + } + +} \ No newline at end of file diff --git a/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductDto.java b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductDto.java new file mode 100644 index 00000000..d959da23 --- /dev/null +++ b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductDto.java @@ -0,0 +1,23 @@ +package kr.or.kosa.cmsplusmain.test.domain.product.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class TProductDto { // 상품 정보(원래는 이거 엔티티로 하고 DTO 따로 운영해야함) + + private Long id; + private Long vendorId; + private String name; + private double price; + private int contractCount; + private String createdDateTime; + private String updatedDateTime; + private String deletedDate; + private String memo; + private String status; + +} diff --git a/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductFindAllByConditionRes.java b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductFindAllByConditionRes.java new file mode 100644 index 00000000..c3cfa519 --- /dev/null +++ b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductFindAllByConditionRes.java @@ -0,0 +1,18 @@ +package kr.or.kosa.cmsplusmain.test.domain.product.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class TProductFindAllByConditionRes { // 상품 목록 조회(조건에 따른) + + private int page; + private int offset; + private int totalPage; + private int totalCount; + private List data; // 페이지네이션 데이터 + +} diff --git a/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductSaveReq.java b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductSaveReq.java new file mode 100644 index 00000000..b563b2b0 --- /dev/null +++ b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductSaveReq.java @@ -0,0 +1,17 @@ +package kr.or.kosa.cmsplusmain.test.domain.product.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TProductSaveReq { // 상품 등록 + + private Long id; + private Long vendorId; + private String name; + private double price; + private String createdDateTime; + private String memo; + +} diff --git a/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductUpdateReq.java b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductUpdateReq.java new file mode 100644 index 00000000..2f3b5eb4 --- /dev/null +++ b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/product/dto/TProductUpdateReq.java @@ -0,0 +1,14 @@ +package kr.or.kosa.cmsplusmain.test.domain.product.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TProductUpdateReq { // 상품 수정 + + private String name; + private double price; + private String memo; + +} diff --git a/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/products/controller/TProductsController.java b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/products/controller/TProductsController.java deleted file mode 100644 index cb011f55..00000000 --- a/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/products/controller/TProductsController.java +++ /dev/null @@ -1,53 +0,0 @@ -package kr.or.kosa.cmsplusmain.test.domain.products.controller; - -import kr.or.kosa.cmsplusmain.test.domain.products.dto.TProductsResponseDto; -import kr.or.kosa.cmsplusmain.test.domain.products.entity.TProducts; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.util.*; -import java.util.stream.Collectors; - -@RestController -@RequestMapping("/api/v1/vendor/products") -public class TProductsController { - - private List TProductsList = Arrays.asList( - TProducts.builder().name("제목1").price(1000.0).contractCount(5).createdAt("2024-07-07").notes("내용1").build(), - TProducts.builder().name("제목2").price(2000.0).contractCount(10).createdAt("2024-07-08").notes("내용2").build(), - TProducts.builder().name("제목3").price(3000.0).contractCount(15).createdAt("2024-07-09").notes("내용3").build() - ); - - @GetMapping - public ResponseEntity> getProducts(@RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "2") int size) { - int start = page * size; - int end = Math.min((page + 1) * size, TProductsList.size()); - - if (start > TProductsList.size()) { - return ResponseEntity.ok(Map.of( - "page", page, - "size", size, - "totalPage", (TProductsList.size() + size - 1) / size, - "totalCount", TProductsList.size(), - "data", Collections.emptyList() - )); - } - - List productsResponseList = this.TProductsList.subList(start, end).stream() - .map(TProductsResponseDto::fromEntity) - .collect(Collectors.toList()); - - Map response = new LinkedHashMap<>(); - response.put("page", page); - response.put("size", size); - response.put("totalPage", (this.TProductsList.size() + size - 1) / size); - response.put("totalCount", this.TProductsList.size()); - response.put("data", productsResponseList); - - return ResponseEntity.ok(response); - } -} \ No newline at end of file diff --git a/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/products/dto/TProductsResponseDto.java b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/products/dto/TProductsResponseDto.java deleted file mode 100644 index ff0b71c9..00000000 --- a/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/products/dto/TProductsResponseDto.java +++ /dev/null @@ -1,28 +0,0 @@ -package kr.or.kosa.cmsplusmain.test.domain.products.dto; - -import kr.or.kosa.cmsplusmain.test.domain.products.entity.TProducts; -import lombok.*; - -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor(access = AccessLevel.PROTECTED) -public class TProductsResponseDto { - - private String name; - private double price; - private int contractCount; - private String createdAt; - private String notes; - - public static TProductsResponseDto fromEntity(TProducts tProducts) { - return TProductsResponseDto.builder() - .name(tProducts.getName()) - .contractCount(tProducts.getContractCount()) - .price(tProducts.getPrice()) - .createdAt(tProducts.getCreatedAt()) - .notes(tProducts.getNotes()) - .build(); - } - -} \ No newline at end of file diff --git a/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/products/entity/TProducts.java b/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/products/entity/TProducts.java deleted file mode 100644 index 70690969..00000000 --- a/server/src/main/java/kr/or/kosa/cmsplusmain/test/domain/products/entity/TProducts.java +++ /dev/null @@ -1,17 +0,0 @@ -package kr.or.kosa.cmsplusmain.test.domain.products.entity; - -import lombok.*; - -@Getter -@Builder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor(access = AccessLevel.PROTECTED) -public class TProducts { - - private String name; - private double price; - private int contractCount; - private String createdAt; - private String notes; - -} \ No newline at end of file diff --git a/server/src/main/resources/static/docs/index.html b/server/src/main/resources/static/docs/index.html deleted file mode 100644 index 2b3f3d31..00000000 --- a/server/src/main/resources/static/docs/index.html +++ /dev/null @@ -1,553 +0,0 @@ - - - - - - - - -CMS+ Application API Document - - - - - - - -
-
-

상품

-
-
-

상품 조회

- - ------ - - - - - - - - - - - - - - -
Table 1. Request Header

파라미터

타입

필수여부

설명

Authorization

String

필수

인증 키

- - ------ - - - - - - - - - - - - - - - - - - - - -
Table 2. Request Parameter

파라미터

타입

필수여부

설명

page

Integer

선택

페이지 번호

size

Integer

선택

한 페이지에 표시할 데이터 개수

-
-

Request

-
-

Snippet http-request not found for operation::getProducts

-
-
-
-

Response

-
-

Snippet http-response not found for operation::getProducts

-
-
-
-
-
-
- - - - - \ No newline at end of file diff --git a/server/src/test/java/kr/or/kosa/cmsplusmain/domain/product/controller/TestController.java b/server/src/test/java/kr/or/kosa/cmsplusmain/domain/product/controller/TestController.java new file mode 100644 index 00000000..b570d26e --- /dev/null +++ b/server/src/test/java/kr/or/kosa/cmsplusmain/domain/product/controller/TestController.java @@ -0,0 +1,239 @@ +package kr.or.kosa.cmsplusmain.domain.product.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.or.kosa.cmsplusmain.test.domain.product.controller.TProductController; +import kr.or.kosa.cmsplusmain.test.domain.product.dto.TProductSaveReq; +import kr.or.kosa.cmsplusmain.test.domain.product.dto.TProductUpdateReq; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.headers.HeaderDocumentation; +import org.springframework.restdocs.payload.PayloadDocumentation; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; + +@ExtendWith({RestDocumentationExtension.class}) +@AutoConfigureMockMvc +@SpringBootTest +class TestController { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @Autowired + TProductController tProductController; + + // 테스트 초기화 + @BeforeEach + void setUp(WebApplicationContext webApplicationContext, + RestDocumentationContextProvider restDocumentationContextProvider) { + this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) + .addFilter(new CharacterEncodingFilter("UTF-8", true)) + .apply(documentationConfiguration(restDocumentationContextProvider) + .operationPreprocessors() + .withRequestDefaults(modifyUris().host("localhost").removePort(), prettyPrint()) + .withResponseDefaults(modifyUris().host("localhost").removePort(), prettyPrint())) + .build(); + + tProductController.initData(); // 더미 데이터 초기화 + } + + // 상품 목록 조회 + @Test + void findAllProductByCondition() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/vendor/product") + .param("page", "1") + .param("offset", "10") + .header(HttpHeaders.AUTHORIZATION, "ACCESS_TOKEN") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andDo(document("findAllProductByCondition", + HeaderDocumentation.requestHeaders( //요청 헤더 + HeaderDocumentation.headerWithName(HttpHeaders.AUTHORIZATION).description("ACCESS_TOKEN") + ), + HeaderDocumentation.responseHeaders( // 응답 헤더 + HeaderDocumentation.headerWithName(HttpHeaders.CONTENT_TYPE).description("Content-Type 헤더") + ), + PayloadDocumentation.responseFields( // 응답 바디 + PayloadDocumentation.fieldWithPath("page").description("현재 페이지 번호"), + PayloadDocumentation.fieldWithPath("offset").description("페이지당 항목 수"), + PayloadDocumentation.fieldWithPath("totalPage").description("총 페이지 수"), + PayloadDocumentation.fieldWithPath("totalCount").description("총 제품 수"), + PayloadDocumentation.fieldWithPath("data[].id").description("상품번호"), + PayloadDocumentation.fieldWithPath("data[].vendorId").description("고객번호"), + PayloadDocumentation.fieldWithPath("data[].name").description("상품명"), + PayloadDocumentation.fieldWithPath("data[].price").description("금액"), + PayloadDocumentation.fieldWithPath("data[].contractCount").description("계약수"), + PayloadDocumentation.fieldWithPath("data[].createdDateTime").description("생성시기"), + PayloadDocumentation.fieldWithPath("data[].updatedDateTime").description("수정시기"), + PayloadDocumentation.fieldWithPath("data[].deletedDate").description("삭제시기"), + PayloadDocumentation.fieldWithPath("data[].memo").description("비고"), + PayloadDocumentation.fieldWithPath("data[].status").description("상태") + ) + )); + } + + // 상품 등록 + @Test + void saveProduct() throws Exception { + + // 등록할 상품 정보 주입 + TProductSaveReq newProduct = new TProductSaveReq(); + newProduct.setId(1L); + newProduct.setVendorId(1003L); + newProduct.setName("상품 등록용 데이터1"); + newProduct.setPrice(50.0); + newProduct.setCreatedDateTime("2024-02-01 11:11:11"); + newProduct.setMemo("비고1"); + + this.mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/vendor/product") + .header(HttpHeaders.AUTHORIZATION, "ACCESS_TOKEN") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(newProduct))) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andDo(document("saveProduct", + HeaderDocumentation.requestHeaders( + HeaderDocumentation.headerWithName(HttpHeaders.AUTHORIZATION).description("ACCESS_TOKEN") + ), + PayloadDocumentation.requestFields( + PayloadDocumentation.fieldWithPath("id").description("상품번호"), + PayloadDocumentation.fieldWithPath("vendorId").description("고객번호"), + PayloadDocumentation.fieldWithPath("name").description("상품명"), + PayloadDocumentation.fieldWithPath("price").description("금액"), + PayloadDocumentation.fieldWithPath("createdDateTime").description("생성시기"), + PayloadDocumentation.fieldWithPath("memo").description("비고") + ), + PayloadDocumentation.responseFields( + PayloadDocumentation.fieldWithPath("id").description("상품번호"), + PayloadDocumentation.fieldWithPath("vendorId").description("고객번호"), + PayloadDocumentation.fieldWithPath("name").description("상품명"), + PayloadDocumentation.fieldWithPath("price").description("금액"), + PayloadDocumentation.fieldWithPath("contractCount").description("계약수"), + PayloadDocumentation.fieldWithPath("createdDateTime").description("생성시기"), + PayloadDocumentation.fieldWithPath("updatedDateTime").description("수정시기"), + PayloadDocumentation.fieldWithPath("deletedDate").description("삭제시기"), + PayloadDocumentation.fieldWithPath("memo").description("비고"), + PayloadDocumentation.fieldWithPath("status").description("상태") + ) + )); + + } + + // 상품 상세 조회 + @Test + void findProductById() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/vendor/product/{PRODUCT_ID}", 1L) + .header(HttpHeaders.AUTHORIZATION, "ACCESS_TOKEN") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andDo(document("findProductById", + HeaderDocumentation.requestHeaders( + HeaderDocumentation.headerWithName(HttpHeaders.AUTHORIZATION).description("ACCESS_TOKEN") + ), + PayloadDocumentation.responseFields( + PayloadDocumentation.fieldWithPath("id").description("상품번호"), + PayloadDocumentation.fieldWithPath("vendorId").description("고객번호"), + PayloadDocumentation.fieldWithPath("name").description("상품명"), + PayloadDocumentation.fieldWithPath("price").description("금액"), + PayloadDocumentation.fieldWithPath("contractCount").description("계약수"), + PayloadDocumentation.fieldWithPath("createdDateTime").description("생성시기"), + PayloadDocumentation.fieldWithPath("updatedDateTime").description("수정시기"), + PayloadDocumentation.fieldWithPath("deletedDate").description("삭제시기"), + PayloadDocumentation.fieldWithPath("memo").description("비고"), + PayloadDocumentation.fieldWithPath("status").description("상태") + ) + )); + } + + // 상품 수정 + @Test + void updateProduct() throws Exception { + + // 수정할 상품 정보 주입 + TProductUpdateReq updateProduct = new TProductUpdateReq(); + updateProduct.setName("상품 조회용 데이터2"); + updateProduct.setPrice(20.0); + updateProduct.setMemo("비고 2"); + + this.mockMvc.perform(MockMvcRequestBuilders.put("/api/v1/vendor/product/{PRODUCT_ID}", 2L) + .header(HttpHeaders.AUTHORIZATION, "ACCESS_TOKEN") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(updateProduct))) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andDo(document("updateProduct", + HeaderDocumentation.requestHeaders( + HeaderDocumentation.headerWithName(HttpHeaders.AUTHORIZATION).description("ACCESS_TOKEN") + ), + PayloadDocumentation.requestFields( + PayloadDocumentation.fieldWithPath("name").description("상품명"), + PayloadDocumentation.fieldWithPath("price").description("금액"), + PayloadDocumentation.fieldWithPath("memo").description("비고") + ), + PayloadDocumentation.responseFields( + PayloadDocumentation.fieldWithPath("id").description("상품번호"), + PayloadDocumentation.fieldWithPath("vendorId").description("고객번호"), + PayloadDocumentation.fieldWithPath("name").description("상품명"), + PayloadDocumentation.fieldWithPath("price").description("금액"), + PayloadDocumentation.fieldWithPath("contractCount").description("계약수"), + PayloadDocumentation.fieldWithPath("createdDateTime").description("생성시기"), + PayloadDocumentation.fieldWithPath("updatedDateTime").description("수정시기"), + PayloadDocumentation.fieldWithPath("deletedDate").description("삭제시기"), + PayloadDocumentation.fieldWithPath("memo").description("비고"), + PayloadDocumentation.fieldWithPath("status").description("상태") + ) + )); + } + + @Test + void deleteProduct() throws Exception { + this.mockMvc.perform(MockMvcRequestBuilders.delete("/api/v1/vendor/product/{PRODUCT_ID}", 2L) + .header(HttpHeaders.AUTHORIZATION, "ACCESS_TOKEN") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo(MockMvcResultHandlers.print()) + .andDo(document("deleteProduct", + HeaderDocumentation.requestHeaders( + HeaderDocumentation.headerWithName(HttpHeaders.AUTHORIZATION).description("ACCESS_TOKEN") + ), + PayloadDocumentation.responseFields( + PayloadDocumentation.fieldWithPath("id").description("상품번호"), + PayloadDocumentation.fieldWithPath("vendorId").description("고객번호"), + PayloadDocumentation.fieldWithPath("name").description("상품명"), + PayloadDocumentation.fieldWithPath("price").description("금액"), + PayloadDocumentation.fieldWithPath("contractCount").description("계약수"), + PayloadDocumentation.fieldWithPath("createdDateTime").description("생성시기"), + PayloadDocumentation.fieldWithPath("updatedDateTime").description("수정시기"), + PayloadDocumentation.fieldWithPath("deletedDate").description("삭제시기"), + PayloadDocumentation.fieldWithPath("memo").description("비고"), + PayloadDocumentation.fieldWithPath("status").description("상태") + ) + )); + } + + + +} diff --git a/server/src/test/java/kr/or/kosa/cmsplusmain/domain/products/controller/ProductsTestController.java b/server/src/test/java/kr/or/kosa/cmsplusmain/domain/products/controller/ProductsTestController.java deleted file mode 100644 index 4ca48908..00000000 --- a/server/src/test/java/kr/or/kosa/cmsplusmain/domain/products/controller/ProductsTestController.java +++ /dev/null @@ -1,81 +0,0 @@ -package kr.or.kosa.cmsplusmain.domain.products.controller; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.restdocs.RestDocumentationContextProvider; -import org.springframework.restdocs.RestDocumentationExtension; -import org.springframework.restdocs.headers.HeaderDocumentation; -import org.springframework.restdocs.payload.PayloadDocumentation; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultHandlers; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.filter.CharacterEncodingFilter; - -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; - -@ExtendWith({RestDocumentationExtension.class}) -@AutoConfigureMockMvc -@SpringBootTest -class ProductsTestController { - - @Autowired - MockMvc mockMvc; - - @Autowired - ObjectMapper objectMapper; - - @BeforeEach - void setUp(WebApplicationContext webApplicationContext, - RestDocumentationContextProvider restDocumentationContextProvider) { - this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) - .addFilter(new CharacterEncodingFilter("UTF-8", true)) - .apply(documentationConfiguration(restDocumentationContextProvider) - .operationPreprocessors() - .withRequestDefaults(modifyUris().host("localhost").removePort(), prettyPrint()) - .withResponseDefaults(modifyUris().host("localhost").removePort(), prettyPrint())) - .build(); - } - - @Test - void getProducts() throws Exception { - this.mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/vendor/products") - .param("page", "1") - .param("size", "2") - .accept(MediaType.APPLICATION_JSON)) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andDo(MockMvcResultHandlers.print()) - .andDo(document("getProducts", - HeaderDocumentation.requestHeaders( - HeaderDocumentation.headerWithName(HttpHeaders.ACCEPT).description("accept header") - ), - HeaderDocumentation.responseHeaders( - HeaderDocumentation.headerWithName(HttpHeaders.CONTENT_TYPE).description("content type") - ), - PayloadDocumentation.responseFields( - PayloadDocumentation.fieldWithPath("page").description("current page number"), - PayloadDocumentation.fieldWithPath("size").description("number of items per page"), - PayloadDocumentation.fieldWithPath("totalPage").description("total number of pages"), - PayloadDocumentation.fieldWithPath("totalCount").description("total number of products"), - PayloadDocumentation.fieldWithPath("data[].name").description("name of product"), - PayloadDocumentation.fieldWithPath("data[].price").description("price of product"), - PayloadDocumentation.fieldWithPath("data[].contractCount").description("contract count of product"), - PayloadDocumentation.fieldWithPath("data[].createdAt").description("creation date of product"), - PayloadDocumentation.fieldWithPath("data[].notes").description("notes of product") - ) - )); - } - -} \ No newline at end of file