diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c0aa37f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM eclipse-temurin:17 + +WORKDIR /app + +COPY build/libs/*.jar app.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-Dspring.profiles.active=${SPRING_PROFILES_ACTIVE}", "-jar", "app.jar"] diff --git a/docs/CONVENTIONS.md b/docs/CONVENTIONS.md new file mode 100644 index 0000000..f0f2eaf --- /dev/null +++ b/docs/CONVENTIONS.md @@ -0,0 +1,160 @@ +# CrewWiki Backend 컨벤션 + +## 1. 패키지 구조 +``` +├── post +│ ├── controller +│ ├── dto +│ ├── repository +│ └── service +└── user +│ ├── controller +│ ├── dto +│ ├── repository +│ └── service +├── common +│ ├── config +│ ├── entity +│ ├── exception +│ ├── infrastructure +│ ├── log +│ └── utils +``` + +## 2. 테스트 코드 +### 2.1 네이밍 +- `@Nested`는 프로덕션 **메서드명**을 기준으로 작성한다. +- `@Nested` 클래스명은 **UpperCamel**로 표기한다. +- 테스트 메서드명 형식: + - `프로덕션메서드_success_조건` + - `프로덕션메서드_fail_조건` + +예시 +```java +@Nested +@DisplayName("로그인 기능") +class Login { + @Test + @DisplayName("유효한 정보로 로그인 성공") + void login_success_byValidCredentials() { } + + @Test + @DisplayName("유효하지 않은 정보로 로그인 실패") + void login_fail_byInvalidCredentials() { } +} +``` + +### 2.2 BDD 패턴 +```java +// given (불필요하면 생략) +// when +// then +``` + +### 2.3 Fixture +- 정적 팩토리 메서드 사용 + +예시 +```java +CrewDocument doc = DocumentFixture.createDefaultCrewDocument(); +``` + +### 2.4 테스트 환경 및 전략 +- 인증/인가 필요 시 컨트롤러 테스트에서 `RestAssured` 사용 +- 서비스 테스트는 통합 테스트로 작성 (`@SpringBootTest(WebEnvironment.MOCK/NONE)`)하여 톰캣을 띄우지 않음 +- 필요 시 `assertSoftly`로 감싸기 +- 결과 검증은 **상태 검증 우선** +- 서비스 테스트는 필수, 컨트롤러 테스트는 필요 시 + +## 3. 네이밍 규칙 +- 메서드명: 동사로 시작, 엔티티명 미사용 + - 예: `memberService.findById(Long id)` +- Path Variable 명확화 + - 예: `/api/{documentId}` +- DTO: `Request`/`Response` 접미사 사용 + - 등록/수정은 `Register`/`Update` 가능 + - 예: `DocumentSearchResponse`, `DocumentUpdateRequest` + +## 4. 객체지향 생활체조 (최대한 지킴) +1. 한 메서드 한 indent +2. `else` 금지 +3. 원시값·문자열 포장 +4. 한 줄에 점 하나 +5. 축약 금지 +6. 작은 엔티티 +7. 인스턴스 변수 ≤ 2 +8. 일급 컬렉션 +9. Getter/Setter는 필요할 때만 사용 + +## 5. 예외 처리 +- 에러 코드를 포함해 반환 + +## 6. 어노테이션 순서 (권장) +1. 로깅 +2. 롬복 +3. 스프링 메타 +4. 스프링 컴포넌트 (가장 아래) + +예시 +```java +@Slf4j +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +@EntityListeners(AuditingEntityListener.class) +@Entity +@Table(name = "...") +public class SampleEntity { + ... +} +``` + +## 7. 도메인 외부 노출 범위 +- 서비스 외부에는 DTO만 노출 + +예시 +```java +public ApiResponse> getByUuid(...) { } +``` + +## 8. API ↔ DTO +- 1:1 매핑 +- 어드민/일반 API가 다르면 DTO도 분리 + +예시 +- 어드민 문서 검색 → `AdminDocumentSearchResponse` +- 일반 문서 검색 → `DocumentSearchResponse` + +## 9. 코드 스타일 +- 클래스 선언 전 공백 한 줄 +- 메서드 파라미터 2개 이상이면 개행 +- 메서드 라인 수 제한 없음 (개행 규칙 우선) + +## 10. 검증 위치 +- 행동 검증: 서비스 +- 상태 검증: 도메인 +- 애매한 경우 개발 시 협의 + +## 11. import +- 와일드카드 금지 +- IntelliJ 컨벤션 적용 +- static import 허용 + +## 12. 기타 +- `final`은 필드/상수에 사용 가능 +- 정적 팩토리 메서드 사용 가능 +- VO 미사용 (필요 시 도입) +- 상수는 `UPPER_SNAKE_CASE` +- 접근자/메서드 정렬: `public` → `protected` → `private` + - 예외: getter는 맨 아래 + +## 13. DTO 정책 (업데이트) +- DTO는 `record`를 최대한 유지 +- 불가피한 경우에만 `class` 사용 (예: `@ModelAttribute` 바인딩 등) + +현재 적용된 공용 네이밍 +- `PagedResponse` +- `PagingRequest` +- `TokenInfoResponse` +- `AuthTokensResponse` diff --git a/src/main/java/com/wooteco/wiki/WikiApplication.java b/src/main/java/com/wooteco/wiki/WikiApplication.java index 29c2808..ab35129 100644 --- a/src/main/java/com/wooteco/wiki/WikiApplication.java +++ b/src/main/java/com/wooteco/wiki/WikiApplication.java @@ -9,5 +9,4 @@ public class WikiApplication { public static void main(String[] args) { SpringApplication.run(WikiApplication.class, args); } - } diff --git a/src/main/java/com/wooteco/wiki/admin/controller/AdminController.java b/src/main/java/com/wooteco/wiki/admin/controller/AdminController.java index a6226b5..07ce2be 100644 --- a/src/main/java/com/wooteco/wiki/admin/controller/AdminController.java +++ b/src/main/java/com/wooteco/wiki/admin/controller/AdminController.java @@ -4,6 +4,7 @@ import com.wooteco.wiki.global.common.ApiResponse; import com.wooteco.wiki.global.common.ApiResponseGenerator; import io.swagger.v3.oas.annotations.Operation; +import java.util.UUID; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -20,10 +21,10 @@ public AdminController(AdminService adminService) { this.adminService = adminService; } - @Operation(summary = "문서 삭제", description = "문서 ID로 문서를 삭제합니다.") - @DeleteMapping("/documents/{documentId}") - public ApiResponse> deleteDocumentByDocumentId(@PathVariable Long documentId) { - adminService.deleteDocumentByDocumentId(documentId); + @Operation(summary = "문서 삭제", description = "문서 Uuid로 문서를 삭제합니다.") + @DeleteMapping("/documents/{documentUuid}") + public ApiResponse> deleteDocumentByDocumentId(@PathVariable UUID documentUuid) { + adminService.deleteDocumentByDocumentUuid(documentUuid); return ApiResponseGenerator.success(HttpStatus.NO_CONTENT); } } diff --git a/src/main/java/com/wooteco/wiki/admin/service/AdminService.java b/src/main/java/com/wooteco/wiki/admin/service/AdminService.java index e947a3a..58b779b 100644 --- a/src/main/java/com/wooteco/wiki/admin/service/AdminService.java +++ b/src/main/java/com/wooteco/wiki/admin/service/AdminService.java @@ -1,20 +1,16 @@ package com.wooteco.wiki.admin.service; -import com.wooteco.wiki.document.service.DocumentService; -import com.wooteco.wiki.global.exception.ErrorCode; -import com.wooteco.wiki.global.exception.WikiException; +import java.util.UUID; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +@RequiredArgsConstructor @Service public class AdminService { - private final DocumentService documentService; + private final CrewDocumentService crewDocumentService; - public AdminService(DocumentService documentService) { - this.documentService = documentService; - } - - public void deleteDocumentByDocumentId(Long documentId) { - documentService.deleteById(documentId); + public void deleteDocumentByDocumentUuid(UUID documentUuid) { + crewDocumentService.deleteByUuid(documentUuid); } } diff --git a/src/main/java/com/wooteco/wiki/admin/service/CrewDocumentService.java b/src/main/java/com/wooteco/wiki/admin/service/CrewDocumentService.java new file mode 100644 index 0000000..1eea9eb --- /dev/null +++ b/src/main/java/com/wooteco/wiki/admin/service/CrewDocumentService.java @@ -0,0 +1,106 @@ +package com.wooteco.wiki.admin.service; + +import com.wooteco.wiki.document.domain.CrewDocument; +import com.wooteco.wiki.document.domain.Document; +import com.wooteco.wiki.document.domain.dto.CrewDocumentCreateRequest; +import com.wooteco.wiki.document.domain.dto.DocumentResponse; +import com.wooteco.wiki.document.domain.dto.DocumentUpdateRequest; +import com.wooteco.wiki.document.repository.CrewDocumentRepository; +import com.wooteco.wiki.document.repository.DocumentRepository; +import com.wooteco.wiki.global.exception.ErrorCode; +import com.wooteco.wiki.global.exception.WikiException; +import com.wooteco.wiki.history.service.HistoryService; +import com.wooteco.wiki.organizationdocument.dto.response.OrganizationDocumentResponse; +import com.wooteco.wiki.organizationdocument.service.DocumentOrganizationLinkService; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class CrewDocumentService { + + private final DocumentOrganizationLinkService documentOrganizationLinkService; + private final CrewDocumentRepository crewDocumentRepository; + private final DocumentRepository documentRepository; + private final HistoryService historyService; + private final Random random; + + @Transactional + public void deleteByUuid(UUID documentUuid) { + CrewDocument crewDocument = crewDocumentRepository.findByUuid(documentUuid) + .orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND)); + + documentOrganizationLinkService.unlinkAll(crewDocument); + + documentRepository.deleteByUuid(documentUuid); + } + + @Transactional + public DocumentResponse create(CrewDocumentCreateRequest request) { + String title = request.title(); + if (documentRepository.existsByTitle(title)) { + throw new WikiException(ErrorCode.DOCUMENT_DUPLICATE); + } + + CrewDocument crewDocument = request.toCrewDocument(); + CrewDocument savedDocument = crewDocumentRepository.save(crewDocument); + historyService.save(savedDocument); + return mapToResponse(savedDocument); + } + + public DocumentResponse getByUuid(UUID uuid) { + CrewDocument crewDocument = crewDocumentRepository.findByUuid(uuid) + .orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND)); + return mapToResponse(crewDocument); + } + + public DocumentResponse getByTitle(String title) { + Document document = documentRepository.findByTitle(title) + .orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND)); + + if (!(document instanceof CrewDocument crewDocument)) { + throw new WikiException(ErrorCode.DOCUMENT_NOT_FOUND); + } + + return mapToResponse(crewDocument); + } + + public DocumentResponse getRandom() { + List documents = crewDocumentRepository.findAll(); + if (documents.isEmpty()) { + throw new WikiException(ErrorCode.DOCUMENT_NOT_FOUND); + } + CrewDocument document = documents.get(random.nextInt(documents.size())); + return mapToResponse(document); + } + + @Transactional + public DocumentResponse update(UUID uuid, DocumentUpdateRequest request) { + CrewDocument crewDocument = crewDocumentRepository.findByUuid(uuid) + .orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND)); + + Document updateData = crewDocument.update( + request.title(), + request.contents(), + request.writer(), + request.documentBytes(), + LocalDateTime.now() + ); + historyService.save(updateData); + return mapToResponse(crewDocument); + } + + private DocumentResponse mapToResponse(CrewDocument crewDocument) { + long latestVersion = historyService.findLatestVersionByDocument(crewDocument); + List organizationDocumentResponses = + documentOrganizationLinkService.findOrganizationDocumentResponsesByDocument(crewDocument); + + return DocumentResponse.toDocumentResponse(crewDocument, latestVersion, organizationDocumentResponses); + } +} diff --git a/src/main/java/com/wooteco/wiki/document/controller/DocumentController.java b/src/main/java/com/wooteco/wiki/document/controller/DocumentController.java new file mode 100644 index 0000000..b0fdf3f --- /dev/null +++ b/src/main/java/com/wooteco/wiki/document/controller/DocumentController.java @@ -0,0 +1,151 @@ +package com.wooteco.wiki.document.controller; + +import com.wooteco.wiki.admin.service.CrewDocumentService; +import com.wooteco.wiki.document.domain.Document; +import com.wooteco.wiki.document.domain.dto.*; +import com.wooteco.wiki.document.service.DocumentSearchService; +import com.wooteco.wiki.document.service.DocumentService; +import com.wooteco.wiki.document.service.DocumentServiceJava; +import com.wooteco.wiki.global.common.ApiResponse; +import com.wooteco.wiki.global.common.ApiResponse.SuccessBody; +import com.wooteco.wiki.global.common.ApiResponseGenerator; +import com.wooteco.wiki.global.common.PagingRequest; +import com.wooteco.wiki.global.common.PagedResponse; +import com.wooteco.wiki.history.domain.dto.HistoryDetailResponse; +import com.wooteco.wiki.history.domain.dto.HistoryResponse; +import com.wooteco.wiki.history.service.HistoryService; +import com.wooteco.wiki.organizationdocument.dto.OrganizationDocumentSearchResponse; +import io.swagger.v3.oas.annotations.Operation; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/document") +@RequiredArgsConstructor +public class DocumentController { + + private final CrewDocumentService crewDocumentService; + private final DocumentService documentService; + private final HistoryService historyService; + private final DocumentSearchService documentSearchService; + private final DocumentServiceJava documentServiceJava; + + @Operation(summary = "위키 글 작성", description = "위키 글을 작성합니다.") + @PostMapping + public ApiResponse> post(@RequestBody CrewDocumentCreateRequest crewDocumentCreateRequest) { + DocumentResponse response = crewDocumentService.create(crewDocumentCreateRequest); + return ApiResponseGenerator.success(response); + } + + @Operation(summary = "랜덤 위키 글 조회", description = "랜덤으로 위키 글을 조회합니다.") + @GetMapping("/random") + public ApiResponse> getRandom() { + DocumentResponse response = crewDocumentService.getRandom(); + return ApiResponseGenerator.success(response); + } + + @Operation(summary = "위키 글 전체 조회", description = "페이지네이션을 통해 모든 위키 글을 조회합니다.") + @GetMapping("") + public ApiResponse>>> findAll(@ModelAttribute PagingRequest pageRequestDto) { + Page pageResponses = documentService.findAll(pageRequestDto); + Page responses = pageResponses.map(DocumentListResponse::from); + return ApiResponseGenerator.success(convertToResponse(responses)); + } + + @Operation(summary = "제목으로 위키 글 조회", description = "제목을 통해 위키 글을 조회합니다.") + @GetMapping("title/{title}") + public ApiResponse> get(@PathVariable String title) { + DocumentResponse response = crewDocumentService.getByTitle(title); + return ApiResponseGenerator.success(response); + } + + @Operation(summary = "제목으로 UUID 조회", description = "제목을 통해 위키 글의 UUID를 조회합니다.") + @GetMapping("title/{title}/uuid") + public ApiResponse> getUuidByTitle(@PathVariable String title) { + Object response = documentService.getUuidByTitle(title); + return ApiResponseGenerator.success(response); + } + + @Operation(summary = "UUID로 위키 글 조회", description = "UUID를 통해 위키 글을 조회합니다.") + @GetMapping("uuid/{uuidText}") + public ApiResponse> getByUuid(@PathVariable String uuidText) { + UUID uuid = UUID.fromString(uuidText); + DocumentResponse response = crewDocumentService.getByUuid(uuid); + return ApiResponseGenerator.success(response); + } + + @Operation(summary = "문서 로그 목록 조회", description = "문서 UUID로 해당 문서의 로그 목록을 페이지네이션을 통해 조회합니다.") + @GetMapping("uuid/{uuidText}/log") + public ApiResponse>>> getLogs( + @PathVariable String uuidText, + @ModelAttribute PagingRequest pageRequestDto + ) { + UUID uuid = UUID.fromString(uuidText); + Page pageResponses = historyService.findAllByDocumentUuid(uuid, pageRequestDto); + return ApiResponseGenerator.success(convertToResponse(pageResponses)); + } + + @Operation(summary = "로그 상세 조회", description = "로그 ID로 로그 상세 정보를 조회합니다.") + @GetMapping("/log/{logId}") + public ApiResponse> getDocumentLogs(@PathVariable Long logId) { + HistoryDetailResponse logDetail = historyService.getLogDetail(logId); + return ApiResponseGenerator.success(logDetail); + } + + @Operation(summary = "위키 글 수정", description = "위키 글을 수정합니다.") + @PutMapping + public ApiResponse> put( + @RequestBody DocumentUpdateRequest documentUpdateRequest + ) { + DocumentResponse response = crewDocumentService.update(documentUpdateRequest.uuid(), documentUpdateRequest); + return ApiResponseGenerator.success(response); + } + + @Operation(summary = "키워드로 위키 글 검색", description = "키워드로 위키 글을 검색합니다.") + @GetMapping("/search") + public ApiResponse>> search(@RequestParam String keyWord) { + return ApiResponseGenerator.success(documentSearchService.search(keyWord)); + } + + @Operation(summary = "누적 조회수 수신 API", description = "프론트에서 누적된 조회수를 전달받아 DB에 반영합니다.") + @PostMapping("/views/flush") + public ApiResponse> flushViews( + @RequestBody ViewFlushRequest request + ) { + documentService.flushViews(request.views()); + return ApiResponseGenerator.success("조회수 누적 완료"); + } + + @Operation(summary = "특정 문서에 대한 조직 문서 조회 API", description = "특정 문서에 대한 조직 문서들을 조회합니다.") + @GetMapping("/{uuidText}/organization-documents") + public ApiResponse>> readOrganizationDocument( + @PathVariable String uuidText + ) { + UUID uuid = UUID.fromString(uuidText); + return ApiResponseGenerator.success(documentServiceJava.searchOrganizationDocument(uuid)); + } + + @Operation(summary = "특정 문서에 대한 조직 문서 삭제 API", description = "특정 문서에 대한 조직 문서를 제거합니다.") + @DeleteMapping("/{uuidText}/organization-documents/{organizationDocumentUuidText}") + public ApiResponse> deleteOrganizationDocument( + @PathVariable String uuidText, + @PathVariable String organizationDocumentUuidText + ) { + UUID documentUuid = UUID.fromString(uuidText); + UUID organizationDocumentUuid = UUID.fromString(organizationDocumentUuidText); + documentServiceJava.deleteOrganizationDocument(documentUuid, organizationDocumentUuid); + return ApiResponseGenerator.success(HttpStatus.NO_CONTENT); + } + + private PagedResponse> convertToResponse(Page pageResponses) { + return PagedResponse.of( + pageResponses.getNumber(), + pageResponses.getTotalPages(), + pageResponses.getContent() + ); + } +} diff --git a/src/main/java/com/wooteco/wiki/document/controller/DocumentController.kt b/src/main/java/com/wooteco/wiki/document/controller/DocumentController.kt deleted file mode 100644 index b153d33..0000000 --- a/src/main/java/com/wooteco/wiki/document/controller/DocumentController.kt +++ /dev/null @@ -1,132 +0,0 @@ -package com.wooteco.wiki.document.controller - -import com.wooteco.wiki.document.domain.Document -import com.wooteco.wiki.document.domain.dto.* -import com.wooteco.wiki.document.service.DocumentSearchService -import com.wooteco.wiki.document.service.DocumentService -import com.wooteco.wiki.document.service.DocumentServiceJava -import com.wooteco.wiki.global.common.ApiResponse -import com.wooteco.wiki.global.common.ApiResponse.SuccessBody -import com.wooteco.wiki.global.common.ApiResponseGenerator -import com.wooteco.wiki.global.common.PageRequestDto -import com.wooteco.wiki.global.common.ResponseDto -import com.wooteco.wiki.history.domain.dto.HistoryDetailResponse -import com.wooteco.wiki.history.domain.dto.HistoryResponse -import com.wooteco.wiki.history.service.HistoryService -import com.wooteco.wiki.organizationdocument.dto.OrganizationDocumentSearchResponse -import io.swagger.v3.oas.annotations.Operation -import org.springframework.data.domain.Page -import org.springframework.web.bind.annotation.* -import java.util.* - -@RestController -@RequestMapping("/document") -class DocumentController( - private val documentService: DocumentService, - private val historyService: HistoryService, - private val documentSearchService: DocumentSearchService, - private val documentServiceJava: DocumentServiceJava -) { - - @Operation(summary = "위키 글 작성", description = "위키 글을 작성합니다.") - @PostMapping - fun post(@RequestBody crewDocumentCreateRequest: CrewDocumentCreateRequest): ApiResponse> { - val response = documentService.postCrewDocument(crewDocumentCreateRequest) - return ApiResponseGenerator.success(response) - } - - @Operation(summary = "랜덤 위키 글 조회", description = "랜덤으로 위키 글을 조회합니다.") - @GetMapping("/random") - fun getRandom(): ApiResponse> { - val response = documentService.getRandom() - return ApiResponseGenerator.success(response) - } - - @Operation(summary = "위키 글 전체 조회", description = "페이지네이션을 통해 모든 위키 글을 조회합니다.") - @GetMapping("") - fun findAll(@ModelAttribute pageRequestDto: PageRequestDto): ApiResponse>>> { - val pageResponses = documentService.findAll(pageRequestDto) - return ApiResponseGenerator.success(convertToResponse(pageResponses)) - } - - @Operation(summary = "제목으로 위키 글 조회", description = "제목을 통해 위키 글을 조회합니다.") - @GetMapping("title/{title}") - fun get(@PathVariable title: String): ApiResponse> { - val response = documentService.get(title) - return ApiResponseGenerator.success(response) - } - - @Operation(summary = "제목으로 UUID 조회", description = "제목을 통해 위키 글의 UUID를 조회합니다.") - @GetMapping("title/{title}/uuid") - fun getUuidByTitle(@PathVariable title: String): ApiResponse> { - val response = documentService.getUuidByTitle(title) - return ApiResponseGenerator.success(response) - } - - @Operation(summary = "UUID로 위키 글 조회", description = "UUID를 통해 위키 글을 조회합니다.") - @GetMapping("uuid/{uuidText}") - fun getByUuid(@PathVariable uuidText: String): ApiResponse> { - val uuid = UUID.fromString(uuidText) - val response = documentService.getByUuid(uuid) - return ApiResponseGenerator.success(response) - } - - @Operation(summary = "문서 로그 목록 조회", description = "문서 UUID로 해당 문서의 로그 목록을 페이지네이션을 통해 조회합니다.") - @GetMapping("uuid/{uuidText}/log") - fun getLogs( - @PathVariable uuidText: String, - @ModelAttribute pageRequestDto: PageRequestDto - ): ApiResponse>>> { - val uuid = UUID.fromString(uuidText) - val pageResponses = historyService.findAllByDocumentUuid(uuid, pageRequestDto) - return ApiResponseGenerator.success(convertToResponse(pageResponses)) - } - - @Operation(summary = "로그 상세 조회", description = "로그 ID로 로그 상세 정보를 조회합니다.") - @GetMapping("/log/{logId}") - fun getDocumentLogs(@PathVariable logId: Long): ApiResponse> { - val logDetail = historyService.getLogDetail(logId) - return ApiResponseGenerator.success(logDetail) - } - - @Operation(summary = "위키 글 수정", description = "위키 글을 수정합니다.") - @PutMapping - fun put( - @RequestBody documentUpdateRequest: DocumentUpdateRequest - ): ApiResponse> { - val response = documentService.put(documentUpdateRequest.uuid, documentUpdateRequest) - return ApiResponseGenerator.success(response) - } - - @Operation(summary = "키워드로 위키 글 검색", description = "키워드로 위키 글을 검색합니다.") - @GetMapping("/search") - fun search(@RequestParam keyWord: String): ApiResponse>> { - return ApiResponseGenerator.success(documentSearchService.search(keyWord)) - } - - @Operation(summary = "누적 조회수 수신 API", description = "프론트에서 누적된 조회수를 전달받아 DB에 반영합니다.") - @PostMapping("/views/flush") - fun flushViews( - @RequestBody request: ViewFlushRequest - ): ApiResponse> { - documentService.flushViews(request.views) - return ApiResponseGenerator.success("조회수 누적 완료") - } - - @Operation(summary = "특정 문서에 대한 조직 문서 조회 API", description = "특정 문서에 대한 조직 문서들을 조회합니다.") - @GetMapping("/{uuidText}/organization-documents") - fun readOrganizationDocument( - @PathVariable uuidText: String - ): ApiResponse>> { - val uuid = UUID.fromString(uuidText) - return ApiResponseGenerator.success(documentServiceJava.searchOrganizationDocument(uuid)) - } - - private fun convertToResponse(pageResponses: Page): ResponseDto> { - return ResponseDto.of( - pageResponses.number, - pageResponses.totalPages, - pageResponses.content - ) - } -} diff --git a/src/main/java/com/wooteco/wiki/document/domain/Document.java b/src/main/java/com/wooteco/wiki/document/domain/Document.java index 178dccf..3220ccc 100644 --- a/src/main/java/com/wooteco/wiki/document/domain/Document.java +++ b/src/main/java/com/wooteco/wiki/document/domain/Document.java @@ -87,6 +87,10 @@ public void changeViewCount(int viewCount) { this.viewCount = viewCount; } + public DocumentType getDocumentType() { + return type(); + } + public abstract DocumentType type(); @Override diff --git a/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentListResponse.java b/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentListResponse.java new file mode 100644 index 0000000..bc64652 --- /dev/null +++ b/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentListResponse.java @@ -0,0 +1,32 @@ +package com.wooteco.wiki.document.domain.dto; + +import com.wooteco.wiki.document.domain.Document; +import com.wooteco.wiki.document.domain.DocumentType; +import java.time.LocalDateTime; +import java.util.UUID; + +public record DocumentListResponse( + Long id, + String title, + String contents, + String writer, + Long documentBytes, + LocalDateTime generateTime, + UUID uuid, + Integer viewCount, + DocumentType documentType +) { + public static DocumentListResponse from(Document document) { + return new DocumentListResponse( + document.getId(), + document.getTitle(), + document.getContents(), + document.getWriter(), + document.getDocumentBytes(), + document.getGenerateTime(), + document.getUuid(), + document.getViewCount(), + document.type() + ); + } +} diff --git a/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentResponse.java b/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentResponse.java new file mode 100644 index 0000000..da1b8d5 --- /dev/null +++ b/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentResponse.java @@ -0,0 +1,39 @@ +package com.wooteco.wiki.document.domain.dto; + +import com.wooteco.wiki.document.domain.CrewDocument; +import com.wooteco.wiki.organizationdocument.dto.response.OrganizationDocumentResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +public record DocumentResponse( + Long documentId, + UUID documentUUID, + String title, + String contents, + String writer, + LocalDateTime generateTime, + Integer viewCount, + Long latestVersion, + List organizationDocumentResponses +) { + + public static DocumentResponse toDocumentResponse( + CrewDocument crewDocument, + Long latestVersion, + List organizationDocumentResponses + ) { + return new DocumentResponse( + crewDocument.getId(), + crewDocument.getUuid(), + crewDocument.getTitle(), + crewDocument.getContents(), + crewDocument.getWriter(), + crewDocument.getGenerateTime(), + crewDocument.getViewCount(), + latestVersion, + organizationDocumentResponses + ); + } +} + diff --git a/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentResponse.kt b/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentResponse.kt deleted file mode 100644 index cdc2db8..0000000 --- a/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentResponse.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.wooteco.wiki.document.domain.dto - -import com.wooteco.wiki.organizationdocument.dto.response.OrganizationDocumentResponse -import java.time.LocalDateTime -import java.util.* - -data class DocumentResponse( - val documentId: Long, - var documentUUID: UUID, - val title: String, - val contents: String, - val writer: String, - val generateTime: LocalDateTime, - val viewCount: Int, - val latestVersion: Long, - val organizationDocumentResponses : List -) diff --git a/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentSearchResponse.java b/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentSearchResponse.java new file mode 100644 index 0000000..ae114a8 --- /dev/null +++ b/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentSearchResponse.java @@ -0,0 +1,12 @@ +package com.wooteco.wiki.document.domain.dto; + +import com.wooteco.wiki.document.domain.DocumentType; +import java.util.UUID; + +public record DocumentSearchResponse( + String title, + UUID uuid, + DocumentType documentType +) { +} + diff --git a/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentSearchResponse.kt b/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentSearchResponse.kt deleted file mode 100644 index 79f6165..0000000 --- a/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentSearchResponse.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.wooteco.wiki.document.domain.dto - -import com.wooteco.wiki.document.domain.DocumentType -import java.util.* - -data class DocumentSearchResponse( - val title: String, - val uuid: UUID, - val documentType: DocumentType -) diff --git a/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentUpdateRequest.java b/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentUpdateRequest.java new file mode 100644 index 0000000..cbc7209 --- /dev/null +++ b/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentUpdateRequest.java @@ -0,0 +1,13 @@ +package com.wooteco.wiki.document.domain.dto; + +import java.util.UUID; + +public record DocumentUpdateRequest( + String title, + String contents, + String writer, + Long documentBytes, + UUID uuid +) { +} + diff --git a/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentUpdateRequest.kt b/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentUpdateRequest.kt deleted file mode 100644 index 2c0a6bb..0000000 --- a/src/main/java/com/wooteco/wiki/document/domain/dto/DocumentUpdateRequest.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.wooteco.wiki.document.domain.dto - -import java.util.* - -data class DocumentUpdateRequest( - val title: String, - val contents: String, - val writer: String, - val documentBytes: Long, - val uuid: UUID -) - diff --git a/src/main/java/com/wooteco/wiki/document/domain/dto/ViewFlushRequest.java b/src/main/java/com/wooteco/wiki/document/domain/dto/ViewFlushRequest.java new file mode 100644 index 0000000..0e9d6fe --- /dev/null +++ b/src/main/java/com/wooteco/wiki/document/domain/dto/ViewFlushRequest.java @@ -0,0 +1,10 @@ +package com.wooteco.wiki.document.domain.dto; + +import java.util.Map; +import java.util.UUID; + +public record ViewFlushRequest( + Map views +) { +} + diff --git a/src/main/java/com/wooteco/wiki/document/domain/dto/ViewFlushRequest.kt b/src/main/java/com/wooteco/wiki/document/domain/dto/ViewFlushRequest.kt deleted file mode 100644 index 793ddee..0000000 --- a/src/main/java/com/wooteco/wiki/document/domain/dto/ViewFlushRequest.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.wooteco.wiki.document.domain.dto - -import java.util.UUID - -data class ViewFlushRequest( - val views: Map -) diff --git a/src/main/java/com/wooteco/wiki/document/repository/DocumentRepository.java b/src/main/java/com/wooteco/wiki/document/repository/DocumentRepository.java index 628297b..d1e1e1a 100644 --- a/src/main/java/com/wooteco/wiki/document/repository/DocumentRepository.java +++ b/src/main/java/com/wooteco/wiki/document/repository/DocumentRepository.java @@ -26,4 +26,6 @@ public interface DocumentRepository extends JpaRepository { Optional findIdByUuid(@Param("uuid") UUID documentUuid); List findAllByUuidIn(Set uuids); + + void deleteByUuid(UUID documentUuid); } diff --git a/src/main/java/com/wooteco/wiki/document/service/DocumentService.java b/src/main/java/com/wooteco/wiki/document/service/DocumentService.java index d42c524..4767496 100644 --- a/src/main/java/com/wooteco/wiki/document/service/DocumentService.java +++ b/src/main/java/com/wooteco/wiki/document/service/DocumentService.java @@ -1,117 +1,38 @@ package com.wooteco.wiki.document.service; -import com.wooteco.wiki.document.domain.CrewDocument; import com.wooteco.wiki.document.domain.Document; -import com.wooteco.wiki.document.domain.dto.CrewDocumentCreateRequest; -import com.wooteco.wiki.document.domain.dto.DocumentResponse; -import com.wooteco.wiki.document.domain.dto.DocumentUpdateRequest; import com.wooteco.wiki.document.domain.dto.DocumentUuidResponse; import com.wooteco.wiki.document.repository.DocumentRepository; -import com.wooteco.wiki.global.common.PageRequestDto; +import com.wooteco.wiki.global.common.PagingRequest; import com.wooteco.wiki.global.exception.ErrorCode; import com.wooteco.wiki.global.exception.WikiException; -import com.wooteco.wiki.history.service.HistoryService; -import com.wooteco.wiki.organizationdocument.dto.response.OrganizationDocumentResponse; -import com.wooteco.wiki.organizationdocument.service.DocumentOrganizationLinkService; -import java.time.LocalDateTime; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; -import kotlin.random.Random; +import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@RequiredArgsConstructor +@Transactional(readOnly = true) @Service -@Transactional public class DocumentService { private final DocumentRepository documentRepository; - private final DocumentOrganizationLinkService organizationDocumentLinkService; - private final HistoryService historyService; - private final Random random; - - public DocumentService( - DocumentRepository documentRepository, - DocumentOrganizationLinkService organizationDocumentLinkService, - HistoryService historyService, - Random random - ) { - this.documentRepository = documentRepository; - this.organizationDocumentLinkService = organizationDocumentLinkService; - this.historyService = historyService; - this.random = random; - } - - public DocumentResponse postCrewDocument(CrewDocumentCreateRequest request) { - String title = request.title(); - if (documentRepository.existsByTitle(title)) { - throw new WikiException(ErrorCode.DOCUMENT_DUPLICATE); - } - - CrewDocument crewDocument = request.toCrewDocument(); - Document savedDocument = documentRepository.save(crewDocument); - historyService.save(savedDocument); - return mapToResponse(savedDocument); - } - - @Transactional(readOnly = true) - public DocumentResponse getRandom() { - List documents = documentRepository.findAll(); - if (documents.isEmpty()) { - throw new WikiException(ErrorCode.DOCUMENT_NOT_FOUND); - } - Document document = documents.get(random.nextInt(documents.size())); - return mapToResponse(document); - } @Transactional(readOnly = true) - public Page findAll(PageRequestDto requestDto) { - return documentRepository.findAll(requestDto.toPageable()); + public Page findAll(PagingRequest pagingRequest) { + return documentRepository.findAll(pagingRequest.toPageable()); } - @Transactional(readOnly = true) - public DocumentResponse get(String title) { - Document document = documentRepository.findByTitle(title) - .orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND)); - return mapToResponse(document); - } - - @Transactional(readOnly = true) public DocumentUuidResponse getUuidByTitle(String title) { return documentRepository.findUuidByTitle(title) - .map(DocumentUuidResponse::new) - .orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND)); - } - - @Transactional(readOnly = true) - public DocumentResponse getByUuid(UUID uuid) { - Document document = documentRepository.findByUuid(uuid) - .orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND)); - return mapToResponse(document); - } - - public DocumentResponse put(UUID uuid, DocumentUpdateRequest request) { - String title = request.getTitle(); - String contents = request.getContents(); - String writer = request.getWriter(); - Long documentBytes = request.getDocumentBytes(); - - Document document = documentRepository.findByUuid(uuid) - .orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND)); - - Document updateData = document.update(title, contents, writer, documentBytes, LocalDateTime.now()); - historyService.save(updateData); - return mapToResponse(document); - } - - public void deleteById(Long id) { - documentRepository.findById(id) - .orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND)); - documentRepository.deleteById(id); + .map(DocumentUuidResponse::new) + .orElseThrow(() -> new WikiException(ErrorCode.DOCUMENT_NOT_FOUND)); } + @Transactional public void flushViews(Map views) { List documents = documentRepository.findAllByUuidIn(views.keySet()); @@ -125,30 +46,5 @@ public void flushViews(Map views) { documentRepository.saveAll(documents); } - - private DocumentResponse mapToResponse(Document document) { - long latestVersion = historyService.findLatestVersionByDocument(document); - List organizationDocumentResponses = - (document instanceof CrewDocument crew) - ? organizationDocumentLinkService.findOrganizationDocumentResponsesByDocument(crew) - : Collections.emptyList(); - - return new DocumentResponse( - document.getId() != null ? document.getId() : - throwNotFound(), - document.getUuid(), - document.getTitle(), - document.getContents(), - document.getWriter(), - document.getGenerateTime(), - document.getViewCount(), - latestVersion, - organizationDocumentResponses - ); - } - - private Long throwNotFound() { - throw new WikiException(ErrorCode.DOCUMENT_NOT_FOUND); - } } diff --git a/src/main/java/com/wooteco/wiki/global/auth/JwtTokenProvider.java b/src/main/java/com/wooteco/wiki/global/auth/JwtTokenProvider.java index d68c1fe..3aa667f 100644 --- a/src/main/java/com/wooteco/wiki/global/auth/JwtTokenProvider.java +++ b/src/main/java/com/wooteco/wiki/global/auth/JwtTokenProvider.java @@ -1,6 +1,6 @@ package com.wooteco.wiki.global.auth; -import com.wooteco.wiki.global.auth.domain.dto.TokenInfoDto; +import com.wooteco.wiki.global.auth.domain.dto.TokenInfoResponse; import com.wooteco.wiki.global.exception.ErrorCode; import com.wooteco.wiki.global.exception.WikiException; import io.jsonwebtoken.Claims; @@ -30,7 +30,7 @@ private void init() { this.secretKey = Keys.hmacShaKeyFor(strSecretKey.getBytes()); } - public String createToken(TokenInfoDto tokenInfoDto) { + public String createToken(TokenInfoResponse tokenInfoDto) { Map claims = getMyClaimMap(tokenInfoDto); @@ -49,7 +49,7 @@ public String createToken(TokenInfoDto tokenInfoDto) { } } - private Map getMyClaimMap(TokenInfoDto tokenInfoDto) { + private Map getMyClaimMap(TokenInfoResponse tokenInfoDto) { Map map = new HashMap<>(); map.put("id", tokenInfoDto.id()); map.put("role", tokenInfoDto.role()); diff --git a/src/main/java/com/wooteco/wiki/global/auth/domain/dto/AuthTokens.java b/src/main/java/com/wooteco/wiki/global/auth/domain/dto/AuthTokens.java deleted file mode 100644 index d41bc66..0000000 --- a/src/main/java/com/wooteco/wiki/global/auth/domain/dto/AuthTokens.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.wooteco.wiki.global.auth.domain.dto; - -public record AuthTokens(String accessToken, String refreshToken) { -} diff --git a/src/main/java/com/wooteco/wiki/global/auth/domain/dto/AuthTokensResponse.java b/src/main/java/com/wooteco/wiki/global/auth/domain/dto/AuthTokensResponse.java new file mode 100644 index 0000000..9bf42ae --- /dev/null +++ b/src/main/java/com/wooteco/wiki/global/auth/domain/dto/AuthTokensResponse.java @@ -0,0 +1,4 @@ +package com.wooteco.wiki.global.auth.domain.dto; + +public record AuthTokensResponse(String accessToken, String refreshToken) { +} diff --git a/src/main/java/com/wooteco/wiki/global/auth/domain/dto/TokenInfoDto.java b/src/main/java/com/wooteco/wiki/global/auth/domain/dto/TokenInfoDto.java deleted file mode 100644 index 95a1c2d..0000000 --- a/src/main/java/com/wooteco/wiki/global/auth/domain/dto/TokenInfoDto.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.wooteco.wiki.global.auth.domain.dto; - -import com.wooteco.wiki.admin.domain.Admin; -import com.wooteco.wiki.global.auth.Role; - -public record TokenInfoDto (Long id, Role role) { - - public static TokenInfoDto of(Admin admin, Role role) { - return new TokenInfoDto(admin.getId(), role); - } -} diff --git a/src/main/java/com/wooteco/wiki/global/auth/domain/dto/TokenInfoResponse.java b/src/main/java/com/wooteco/wiki/global/auth/domain/dto/TokenInfoResponse.java new file mode 100644 index 0000000..88a76e8 --- /dev/null +++ b/src/main/java/com/wooteco/wiki/global/auth/domain/dto/TokenInfoResponse.java @@ -0,0 +1,11 @@ +package com.wooteco.wiki.global.auth.domain.dto; + +import com.wooteco.wiki.admin.domain.Admin; +import com.wooteco.wiki.global.auth.Role; + +public record TokenInfoResponse (Long id, Role role) { + + public static TokenInfoResponse of(Admin admin, Role role) { + return new TokenInfoResponse(admin.getId(), role); + } +} diff --git a/src/main/java/com/wooteco/wiki/global/auth/service/AuthService.java b/src/main/java/com/wooteco/wiki/global/auth/service/AuthService.java index a6613f1..e1eac58 100644 --- a/src/main/java/com/wooteco/wiki/global/auth/service/AuthService.java +++ b/src/main/java/com/wooteco/wiki/global/auth/service/AuthService.java @@ -4,7 +4,7 @@ import com.wooteco.wiki.admin.domain.dto.AdminResponse; import com.wooteco.wiki.admin.domain.dto.LoginRequest; import com.wooteco.wiki.global.auth.Role; -import com.wooteco.wiki.global.auth.domain.dto.TokenInfoDto; +import com.wooteco.wiki.global.auth.domain.dto.TokenInfoResponse; import com.wooteco.wiki.global.auth.domain.dto.TokenResponse; import com.wooteco.wiki.global.exception.ErrorCode; import com.wooteco.wiki.global.exception.WikiException; @@ -26,10 +26,10 @@ public AuthService(JwtTokenProvider jwtTokenProvider, AdminRepository adminRepos public TokenResponse login(LoginRequest loginRequest) { Admin admin = adminRepository.findOneByLoginIdAndPassword(loginRequest.loginId(), loginRequest.password()) .orElseThrow(() -> new WikiException(ErrorCode.ADMIN_NOT_FOUND)); - return createToken(TokenInfoDto.of(admin, Role.ROLE_ADMIN)); + return createToken(TokenInfoResponse.of(admin, Role.ROLE_ADMIN)); } - public TokenResponse createToken(TokenInfoDto tokenInfoDto) { + public TokenResponse createToken(TokenInfoResponse tokenInfoDto) { String accessToken = jwtTokenProvider.createToken(tokenInfoDto); return new TokenResponse(accessToken); } diff --git a/src/main/java/com/wooteco/wiki/global/common/ApiResponse.java b/src/main/java/com/wooteco/wiki/global/common/ApiResponse.java index 02c56e5..f6f0772 100644 --- a/src/main/java/com/wooteco/wiki/global/common/ApiResponse.java +++ b/src/main/java/com/wooteco/wiki/global/common/ApiResponse.java @@ -2,29 +2,18 @@ import com.wooteco.wiki.global.exception.ErrorCode; import com.wooteco.wiki.global.exception.SuccessCode; -import lombok.AllArgsConstructor; -import lombok.Getter; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -@Getter public class ApiResponse extends ResponseEntity { public ApiResponse(B body, HttpStatus status) { super(body, status); } - @Getter - @AllArgsConstructor - public static class SuccessBody { - private D data; - private SuccessCode code; + public static record SuccessBody(D data, SuccessCode code) { } - @Getter - @AllArgsConstructor - public static class FailureBody { - private ErrorCode code; - private String message; + public static record FailureBody(ErrorCode code, String message) { } } diff --git a/src/main/java/com/wooteco/wiki/global/common/PagedResponse.java b/src/main/java/com/wooteco/wiki/global/common/PagedResponse.java new file mode 100644 index 0000000..3b835cf --- /dev/null +++ b/src/main/java/com/wooteco/wiki/global/common/PagedResponse.java @@ -0,0 +1,8 @@ +package com.wooteco.wiki.global.common; + +public record PagedResponse (int page, int totalPage, T data){ + + public static PagedResponse of(int page, int totalPage, T data) { + return new PagedResponse<>(page, totalPage, data); + } +} diff --git a/src/main/java/com/wooteco/wiki/global/common/PageRequestDto.java b/src/main/java/com/wooteco/wiki/global/common/PagingRequest.java similarity index 97% rename from src/main/java/com/wooteco/wiki/global/common/PageRequestDto.java rename to src/main/java/com/wooteco/wiki/global/common/PagingRequest.java index c7cfbd3..bdb377b 100644 --- a/src/main/java/com/wooteco/wiki/global/common/PageRequestDto.java +++ b/src/main/java/com/wooteco/wiki/global/common/PagingRequest.java @@ -12,7 +12,7 @@ @Getter @Setter -public class PageRequestDto { +public class PagingRequest { @Min(value = 0, message = "페이지 번호는 0 이상이어야 합니다.") private int pageNumber = 0; diff --git a/src/main/java/com/wooteco/wiki/global/common/ResponseDto.java b/src/main/java/com/wooteco/wiki/global/common/ResponseDto.java deleted file mode 100644 index 7f71d7f..0000000 --- a/src/main/java/com/wooteco/wiki/global/common/ResponseDto.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.wooteco.wiki.global.common; - -public record ResponseDto (int page, int totalPage, T data){ - - public static ResponseDto of(int page, int totalPage, T data) { - return new ResponseDto<>(page, totalPage, data); - } -} diff --git a/src/main/java/com/wooteco/wiki/global/config/RandomConfig.java b/src/main/java/com/wooteco/wiki/global/config/RandomConfig.java new file mode 100644 index 0000000..29e0b33 --- /dev/null +++ b/src/main/java/com/wooteco/wiki/global/config/RandomConfig.java @@ -0,0 +1,15 @@ +package com.wooteco.wiki.global.config; + +import java.util.Random; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RandomConfig { + + @Bean + public Random random() { + return new Random(); + } +} + diff --git a/src/main/java/com/wooteco/wiki/global/config/RandomConfig.kt b/src/main/java/com/wooteco/wiki/global/config/RandomConfig.kt deleted file mode 100644 index 7bdc80c..0000000 --- a/src/main/java/com/wooteco/wiki/global/config/RandomConfig.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.wooteco.wiki.global.config - -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import kotlin.random.Random - -@Configuration -class RandomConfig { - - @Bean - fun random(): Random = Random.Default -} diff --git a/src/main/java/com/wooteco/wiki/history/service/HistoryService.java b/src/main/java/com/wooteco/wiki/history/service/HistoryService.java index 537a71b..4799e71 100644 --- a/src/main/java/com/wooteco/wiki/history/service/HistoryService.java +++ b/src/main/java/com/wooteco/wiki/history/service/HistoryService.java @@ -5,7 +5,7 @@ import com.wooteco.wiki.document.domain.Document; import com.wooteco.wiki.document.repository.DocumentRepository; -import com.wooteco.wiki.global.common.PageRequestDto; +import com.wooteco.wiki.global.common.PagingRequest; import com.wooteco.wiki.global.exception.WikiException; import com.wooteco.wiki.history.domain.History; import com.wooteco.wiki.history.domain.dto.HistoryDetailResponse; @@ -47,7 +47,7 @@ public HistoryDetailResponse getLogDetail(Long logId) { } @Transactional(readOnly = true) - public Page findAllByDocumentUuid(UUID documentUuid, PageRequestDto pageRequestDto) { + public Page findAllByDocumentUuid(UUID documentUuid, PagingRequest pageRequestDto) { Long documentId = documentRepository.findIdByUuid(documentUuid) .orElseThrow(() -> new WikiException(DOCUMENT_NOT_FOUND)); diff --git a/src/main/java/com/wooteco/wiki/organizationdocument/controller/OrganizationDocumentController.java b/src/main/java/com/wooteco/wiki/organizationdocument/controller/OrganizationDocumentController.java index 7e2f03f..580ad2f 100644 --- a/src/main/java/com/wooteco/wiki/organizationdocument/controller/OrganizationDocumentController.java +++ b/src/main/java/com/wooteco/wiki/organizationdocument/controller/OrganizationDocumentController.java @@ -4,6 +4,7 @@ import com.wooteco.wiki.global.common.ApiResponse.SuccessBody; import com.wooteco.wiki.global.common.ApiResponseGenerator; import com.wooteco.wiki.organizationdocument.dto.request.OrganizationDocumentCreateRequest; +import com.wooteco.wiki.organizationdocument.dto.request.OrganizationDocumentLinkRequest; import com.wooteco.wiki.organizationdocument.dto.request.OrganizationDocumentUpdateRequest; import com.wooteco.wiki.organizationdocument.dto.response.OrganizationDocumentAndEventResponse; import com.wooteco.wiki.organizationdocument.dto.response.OrganizationDocumentResponse; @@ -42,6 +43,16 @@ public ApiResponse> createOrganization return ApiResponseGenerator.success(organizationDocumentResponse); } + @Operation(summary = "기존 조직 문서를 크루 문서에 연결", description = "이미 존재하는 조직 문서를 크루 문서와 연결합니다.") + @PostMapping("/link") + public ApiResponse> linkOrganizationDocument( + @RequestBody OrganizationDocumentLinkRequest organizationDocumentLinkRequest) { + OrganizationDocumentResponse organizationDocumentResponse = organizationDocumentService.link( + organizationDocumentLinkRequest); + + return ApiResponseGenerator.success(organizationDocumentResponse); + } + @Operation(summary = "조직 위키 글 수정", description = "조직 위키 글을 수정합니다.") @PutMapping public ApiResponse> modifyOrganizationDocumentContents( diff --git a/src/main/java/com/wooteco/wiki/organizationdocument/dto/request/OrganizationDocumentLinkRequest.java b/src/main/java/com/wooteco/wiki/organizationdocument/dto/request/OrganizationDocumentLinkRequest.java new file mode 100644 index 0000000..722fff5 --- /dev/null +++ b/src/main/java/com/wooteco/wiki/organizationdocument/dto/request/OrganizationDocumentLinkRequest.java @@ -0,0 +1,9 @@ +package com.wooteco.wiki.organizationdocument.dto.request; + +import java.util.UUID; + +public record OrganizationDocumentLinkRequest( + UUID crewDocumentUuid, + UUID organizationDocumentUuid +) { +} \ No newline at end of file diff --git a/src/main/java/com/wooteco/wiki/organizationdocument/repository/DocumentOrganizationLinkRepository.java b/src/main/java/com/wooteco/wiki/organizationdocument/repository/DocumentOrganizationLinkRepository.java index 50b350e..5d6b498 100644 --- a/src/main/java/com/wooteco/wiki/organizationdocument/repository/DocumentOrganizationLinkRepository.java +++ b/src/main/java/com/wooteco/wiki/organizationdocument/repository/DocumentOrganizationLinkRepository.java @@ -18,4 +18,6 @@ void deleteByCrewDocumentAndOrganizationDocument(CrewDocument crewDocument, OrganizationDocument organizationDocument); List findAllByCrewDocument(CrewDocument crewDocument); + + void deleteAllByCrewDocument(CrewDocument crewDocument); } diff --git a/src/main/java/com/wooteco/wiki/organizationdocument/service/DocumentOrganizationLinkService.java b/src/main/java/com/wooteco/wiki/organizationdocument/service/DocumentOrganizationLinkService.java index a219e1f..fcce58a 100644 --- a/src/main/java/com/wooteco/wiki/organizationdocument/service/DocumentOrganizationLinkService.java +++ b/src/main/java/com/wooteco/wiki/organizationdocument/service/DocumentOrganizationLinkService.java @@ -27,6 +27,10 @@ public void unlink(CrewDocument crewDocument, OrganizationDocument organizationD documentOrgDocLinkRepository.deleteByCrewDocumentAndOrganizationDocument(crewDocument, organizationDocument); } + public void unlinkAll(CrewDocument crewDocument) { + documentOrgDocLinkRepository.deleteAllByCrewDocument(crewDocument); + } + @Transactional(readOnly = true) public List findOrganizationDocumentResponsesByDocument(CrewDocument crewDocument) { List documentOrganizationLinks = documentOrgDocLinkRepository.findAllByCrewDocument( diff --git a/src/main/java/com/wooteco/wiki/organizationdocument/service/OrganizationDocumentService.java b/src/main/java/com/wooteco/wiki/organizationdocument/service/OrganizationDocumentService.java index 479de67..7075111 100644 --- a/src/main/java/com/wooteco/wiki/organizationdocument/service/OrganizationDocumentService.java +++ b/src/main/java/com/wooteco/wiki/organizationdocument/service/OrganizationDocumentService.java @@ -7,8 +7,10 @@ import com.wooteco.wiki.document.repository.DocumentRepository; import com.wooteco.wiki.global.exception.ErrorCode; import com.wooteco.wiki.global.exception.WikiException; +import com.wooteco.wiki.history.service.HistoryService; import com.wooteco.wiki.organizationdocument.domain.OrganizationDocument; import com.wooteco.wiki.organizationdocument.dto.request.OrganizationDocumentCreateRequest; +import com.wooteco.wiki.organizationdocument.dto.request.OrganizationDocumentLinkRequest; import com.wooteco.wiki.organizationdocument.dto.request.OrganizationDocumentUpdateRequest; import com.wooteco.wiki.organizationdocument.dto.response.OrganizationDocumentAndEventResponse; import com.wooteco.wiki.organizationdocument.dto.response.OrganizationDocumentResponse; @@ -32,6 +34,7 @@ public class OrganizationDocumentService { private final DocumentRepository documentRepository; private final DocumentOrganizationLinkService documentOrganizationLinkService; private final CrewDocumentRepository crewDocumentRepository; + private final HistoryService historyService; public OrganizationDocumentResponse create(OrganizationDocumentCreateRequest organizationDocumentCreateRequest) { if (documentRepository.existsByTitle(organizationDocumentCreateRequest.title())) { @@ -39,7 +42,16 @@ public OrganizationDocumentResponse create(OrganizationDocumentCreateRequest org } CrewDocument crewDocument = getCrewDocument(organizationDocumentCreateRequest.crewDocumentUuid()); OrganizationDocument organizationDocument = organizationDocumentCreateRequest.toOrganizationDocument(); - organizationDocumentRepository.save(organizationDocument); + OrganizationDocument savedOrganizationDocument = organizationDocumentRepository.save(organizationDocument); + historyService.save(savedOrganizationDocument); + documentOrganizationLinkService.link(crewDocument, savedOrganizationDocument); + return new OrganizationDocumentResponse(savedOrganizationDocument); + } + + public OrganizationDocumentResponse link(OrganizationDocumentLinkRequest organizationDocumentLinkRequest) { + CrewDocument crewDocument = getCrewDocument(organizationDocumentLinkRequest.crewDocumentUuid()); + OrganizationDocument organizationDocument = getOrganizationDocument( + organizationDocumentLinkRequest.organizationDocumentUuid()); documentOrganizationLinkService.link(crewDocument, organizationDocument); return new OrganizationDocumentResponse(organizationDocument); } @@ -56,6 +68,7 @@ public OrganizationDocumentResponse update(OrganizationDocumentUpdateRequest org organizationDocumentUpdateRequest.documentBytes(), LocalDateTime.now() ); + historyService.save(organizationDocument); return new OrganizationDocumentResponse(organizationDocument); } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..773f800 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,22 @@ +server: + port: 8080 + +spring: + datasource: + url: ${DEV_DB_URL} + username: ${DEV_DB_USERNAME} + password: ${DEV_DB_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver + + jpa: + hibernate: + ddl-auto: update + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + +cors: + allowed-origins: http://localhost:3000, https://dev.crew-wiki.site, https://dev.api.crew-wiki.site + +swagger: + server-url: https://dev.api.crew-wiki.site diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000..100b669 --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,24 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb;MODE=MySQL + driver-class-name: org.h2.Driver + + jpa: + hibernate: + ddl-auto: update + open-in-view: false + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect + + h2: + console: + enabled: true + path: /h2-console + + +cors: + allowed-origins: http://localhost:3000, https://dev.crew-wiki.site, https://dev.api.crew-wiki.site + +swagger: + server-url: https://dev.api.crew-wiki.site diff --git a/src/main/resources/application-private.yml b/src/main/resources/application-private.yml new file mode 100644 index 0000000..fa7b38c --- /dev/null +++ b/src/main/resources/application-private.yml @@ -0,0 +1,17 @@ +security: + jwt: + token: + secret-key: ${JWT_SECRET_KEY} + expire-length: ${JWT_SECRET_EXPIRE_LENGTH} + +cloud: + aws: + credentials: + access-key: ${AWS_CREDENTIALS_ACCESS_KEY} + secret-key: ${AWS_CREDENTIALS_SECRET_KEY} + s3: + bucket: ${S3_BUCKET} + region: + static: ap-northeast-2 + stack: + auto: false diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e3c312c..713047c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,4 @@ spring: profiles: include: private - active: dev # prod (배포 환경) + active: local # prod (배포 환경) diff --git a/src/test/java/com/wooteco/wiki/admin/controller/AdminControllerTest.java b/src/test/java/com/wooteco/wiki/admin/controller/AdminControllerTest.java index a2e0a72..4771e2e 100644 --- a/src/test/java/com/wooteco/wiki/admin/controller/AdminControllerTest.java +++ b/src/test/java/com/wooteco/wiki/admin/controller/AdminControllerTest.java @@ -28,11 +28,11 @@ void setUp() { @Nested @DisplayName("adminController") - class adminController { + class DeleteDocumentByDocumentId { @DisplayName("/admin/** 은 token이 없을 시 예외 발생") @Test - void adminController_throwsException_byNonExistsToken() { + void deleteDocumentByDocumentId_fail_byMissingToken() { // then RestAssured .given().log().all() diff --git a/src/test/java/com/wooteco/wiki/admin/repository/AdminRepositoryTest.java b/src/test/java/com/wooteco/wiki/admin/repository/AdminRepositoryTest.java index 25b9cf8..8251c2e 100644 --- a/src/test/java/com/wooteco/wiki/admin/repository/AdminRepositoryTest.java +++ b/src/test/java/com/wooteco/wiki/admin/repository/AdminRepositoryTest.java @@ -19,7 +19,7 @@ class AdminRepositoryTest { @Nested @DisplayName("loginId와 password로 Admin을 찾는 기능") - class findOneByLoginIdAndPassword { + class FindOneByLoginIdAndPassword { @DisplayName("유효한 loginId와 password로 Admin을 반환한다.") @Test @@ -41,7 +41,7 @@ void findOneByLoginIdAndPassword_success_byValidLoginIdAndPassword() { @DisplayName("알맞지 않는 loginId와 password로 Optional.isEmpty를 반환한다.") @Test - void findOneByLoginIdAndPassword_isEmpty_byInValidLoginIdAndPassword() { + void findOneByLoginIdAndPassword_success_byInvalidCredentials() { // given adminRepository.save(new Admin("admin", "password")); diff --git a/src/test/java/com/wooteco/wiki/document/fixture/DocumentFixture.java b/src/test/java/com/wooteco/wiki/document/fixture/CrewDocumentFixture.java similarity index 74% rename from src/test/java/com/wooteco/wiki/document/fixture/DocumentFixture.java rename to src/test/java/com/wooteco/wiki/document/fixture/CrewDocumentFixture.java index aba4f2f..c80e1f4 100644 --- a/src/test/java/com/wooteco/wiki/document/fixture/DocumentFixture.java +++ b/src/test/java/com/wooteco/wiki/document/fixture/CrewDocumentFixture.java @@ -2,10 +2,9 @@ import com.wooteco.wiki.document.domain.CrewDocument; import com.wooteco.wiki.document.domain.dto.CrewDocumentCreateRequest; -import com.wooteco.wiki.document.domain.dto.DocumentUpdateRequest; import java.util.UUID; -public class DocumentFixture { +public class CrewDocumentFixture { public static CrewDocument createCrewDocument(String title, String content, String writer, Long documentBytes, UUID uuid) { @@ -24,9 +23,4 @@ public static CrewDocumentCreateRequest createDocumentCreateRequest(String title public static CrewDocumentCreateRequest createDocumentCreateRequestDefault() { return createDocumentCreateRequest("defaultTitle", "defaultContent", "defaultWriter", 10L, UUID.randomUUID()); } - - public static DocumentUpdateRequest createDocumentUpdateRequest(String title, String contents, String writer, - Long documentBytes) { - return new DocumentUpdateRequest(title, contents, writer, documentBytes, UUID.randomUUID()); - } } diff --git a/src/test/java/com/wooteco/wiki/document/repository/DocumentRepositoryTest.java b/src/test/java/com/wooteco/wiki/document/repository/DocumentRepositoryTest.java index f1798d9..b47514e 100644 --- a/src/test/java/com/wooteco/wiki/document/repository/DocumentRepositoryTest.java +++ b/src/test/java/com/wooteco/wiki/document/repository/DocumentRepositoryTest.java @@ -1,8 +1,12 @@ package com.wooteco.wiki.document.repository; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + import com.wooteco.wiki.document.domain.CrewDocument; import com.wooteco.wiki.document.domain.Document; -import com.wooteco.wiki.document.fixture.DocumentFixture; +import com.wooteco.wiki.document.domain.DocumentType; +import com.wooteco.wiki.document.fixture.CrewDocumentFixture; +import com.wooteco.wiki.organizationdocument.fixture.OrganizationDocumentFixture; import java.util.List; import java.util.Optional; import java.util.Set; @@ -23,7 +27,7 @@ public class DocumentRepositoryTest { @Nested @DisplayName("문서 제목으로 uuid를 조회하는 기능") - class findUuidByTitle { + class FindUuidByTitle { @DisplayName("존재하지 않는 문서 제목으로 조회했을 때 Optional.empty를 반환한다") @Test @@ -40,7 +44,7 @@ void findUuidByTitle_success_byNonExistsDocument() { @Test void findUuidByTitle_success_byExistsDocument() { // given - CrewDocument savedCrewDocument = documentRepository.save(DocumentFixture.createDefaultCrewDocument()); + CrewDocument savedCrewDocument = documentRepository.save(CrewDocumentFixture.createDefaultCrewDocument()); // when Optional actual = documentRepository.findUuidByTitle(savedCrewDocument.getTitle()); @@ -52,16 +56,76 @@ void findUuidByTitle_success_byExistsDocument() { @Nested @DisplayName("문서 전체 조회 기능") - class findAll { + class FindAll { + + @DisplayName("DocumentRepository로 조회 시 CrewDocument와 OrganizationDocument가 모두 조회된다") + @Test + void findAll_success_byMixedDocumentTypes() { + // given + documentRepository.save( + CrewDocumentFixture.createCrewDocument("crew문서", "content1", "writer1", 10L, UUID.randomUUID())); + documentRepository.save( + OrganizationDocumentFixture.create("org문서", "content2", "writer2", 15L, UUID.randomUUID())); + + // when + List result = documentRepository.findAll(); + + // then + assertSoftly(softly -> { + softly.assertThat(result).hasSize(2); + softly.assertThat(result).extracting(Document::type) + .containsExactlyInAnyOrder(DocumentType.CREW, DocumentType.ORGANIZATION); + softly.assertThat(result).extracting(Document::getTitle) + .containsExactlyInAnyOrder("crew문서", "org문서"); + }); + } + + @DisplayName("CrewDocument만 저장했을 때는 CrewDocument만 조회된다") + @Test + void findAll_success_byOnlyCrewDocuments() { + // given + documentRepository.save( + CrewDocumentFixture.createCrewDocument("crew1", "content1", "writer1", 10L, UUID.randomUUID())); + documentRepository.save( + CrewDocumentFixture.createCrewDocument("crew2", "content2", "writer2", 15L, UUID.randomUUID())); + + // when + List result = documentRepository.findAll(); + + // then + assertSoftly(softly -> { + softly.assertThat(result).hasSize(2); + softly.assertThat(result).allMatch(doc -> doc.type() == DocumentType.CREW); + }); + } + + @DisplayName("OrganizationDocument만 저장했을 때는 OrganizationDocument만 조회된다") + @Test + void findAll_success_byOnlyOrganizationDocuments() { + // given + documentRepository.save( + OrganizationDocumentFixture.create("org1", "content1", "writer1", 10L, UUID.randomUUID())); + documentRepository.save( + OrganizationDocumentFixture.create("org2", "content2", "writer2", 15L, UUID.randomUUID())); + + // when + List result = documentRepository.findAll(); + + // then + assertSoftly(softly -> { + softly.assertThat(result).hasSize(2); + softly.assertThat(result).allMatch(doc -> doc.type() == DocumentType.ORGANIZATION); + }); + } @DisplayName("문서가 여러개 존재했을 때 List 형태로 반환한다") @Test void findAll_success_bySomeData() { // given documentRepository.save( - DocumentFixture.createCrewDocument("title1", "content1", "writer1", 10L, UUID.randomUUID())); + CrewDocumentFixture.createCrewDocument("title1", "content1", "writer1", 10L, UUID.randomUUID())); documentRepository.save( - DocumentFixture.createCrewDocument("title2", "content2", "writer2", 11L, UUID.randomUUID())); + CrewDocumentFixture.createCrewDocument("title2", "content2", "writer2", 11L, UUID.randomUUID())); // when List crewDocuments = documentRepository.findAll(); @@ -83,7 +147,7 @@ void findAll_success_byNoData() { @Nested @DisplayName("uuid로 id 찾는 기능") - class findIdByUuid { + class FindIdByUuid { private UUID uuid; private CrewDocument savedCrewDocument; @@ -92,7 +156,7 @@ class findIdByUuid { void setUp() { uuid = UUID.randomUUID(); - CrewDocument crewDocument = DocumentFixture.createCrewDocument("titl1", "content1", "writer1", 10L, uuid); + CrewDocument crewDocument = CrewDocumentFixture.createCrewDocument("titl1", "content1", "writer1", 10L, uuid); savedCrewDocument = documentRepository.save(crewDocument); } @@ -114,12 +178,12 @@ class FindAllByUuidIn { @DisplayName("UUID 리스트로 조회 시 해당 문서 리스트를 반환한다") @Test - void findAllByUuidIn_success() { + void findAllByUuidIn_success_byUuidSet() { // given CrewDocument doc1 = documentRepository.save( - DocumentFixture.createCrewDocument("title1", "content1", "writer1", 10L, UUID.randomUUID())); + CrewDocumentFixture.createCrewDocument("title1", "content1", "writer1", 10L, UUID.randomUUID())); CrewDocument doc2 = documentRepository.save( - DocumentFixture.createCrewDocument("title2", "content2", "writer2", 20L, UUID.randomUUID())); + CrewDocumentFixture.createCrewDocument("title2", "content2", "writer2", 20L, UUID.randomUUID())); Set uuids = Set.of(doc1.getUuid(), doc2.getUuid()); diff --git a/src/test/java/com/wooteco/wiki/document/service/CrewDocumentServiceJavaTest.java b/src/test/java/com/wooteco/wiki/document/service/CrewDocumentServiceJavaTest.java index 8ad81c3..28e53a4 100644 --- a/src/test/java/com/wooteco/wiki/document/service/CrewDocumentServiceJavaTest.java +++ b/src/test/java/com/wooteco/wiki/document/service/CrewDocumentServiceJavaTest.java @@ -5,7 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.wooteco.wiki.document.domain.CrewDocument; -import com.wooteco.wiki.document.fixture.DocumentFixture; +import com.wooteco.wiki.document.fixture.CrewDocumentFixture; import com.wooteco.wiki.document.repository.DocumentRepository; import com.wooteco.wiki.global.exception.ErrorCode; import com.wooteco.wiki.global.exception.WikiException; @@ -47,14 +47,14 @@ class CrewDocumentServiceJavaTest { @BeforeEach void setUp() { - CrewDocument crewDocument = DocumentFixture.createDefaultCrewDocument(); + CrewDocument crewDocument = CrewDocumentFixture.createDefaultCrewDocument(); savedCrewDocument = documentRepository.save(crewDocument); savedDocumentUuid = savedCrewDocument.getUuid(); } @DisplayName("특정 문서에 대한 조직 문서 제목과 Uuid들을 조회할 때") @Nested - class searchOrganizationCrewDocument { + class SearchOrganizationDocument { @BeforeEach void setUp() { @@ -90,7 +90,7 @@ void searchOrganizationDocument_success_byExistingDocumentUuid() { @DisplayName("존재하지 않는 특정 문서의 Uuid로 요청한다면 예외가 발생한다 : DOCUMENT_NOT_FOUND") @Test - void searchOrganizationDocument_error_byNonExistingDocumentUuid() { + void searchOrganizationDocument_fail_byNonExistingDocumentUuid() { // when & then WikiException ex = assertThrows(WikiException.class, () -> documentService.searchOrganizationDocument(UUID.randomUUID())); @@ -100,7 +100,7 @@ void searchOrganizationDocument_error_byNonExistingDocumentUuid() { @DisplayName("특정 문서에 대해 조직 문서를 제거할 때에") @Nested - class deleteOrganizationCrewDocument { + class DeleteOrganizationDocument { private UUID savedOrganizationDocumentUuid; @@ -129,7 +129,7 @@ void deleteOrganizationDocument_success_byExistingDocumentUuid() { @DisplayName("존재하지 않는 특정 문서의 Uuid로 요청한다면 예외가 발생한다 : DOCUMENT_NOT_FOUND") @Test - void deleteOrganizationDocument_error_byNonExistingDocumentUuid() { + void deleteOrganizationDocument_fail_byNonExistingDocumentUuid() { // when & then WikiException ex = assertThrows(WikiException.class, () -> documentService.deleteOrganizationDocument(UUID.randomUUID(), savedOrganizationDocumentUuid)); @@ -138,7 +138,7 @@ void deleteOrganizationDocument_error_byNonExistingDocumentUuid() { @DisplayName("존재하지 않는 특정 조직 문서의 Uuid로 요청한다면 예외가 발생한다 : ORGANIZATION_DOCUMENT_NOT_FOUND") @Test - void deleteOrganizationDocument_error_byNonExistingOrganizationDocumentUuid() { + void deleteOrganizationDocument_fail_byNonExistingOrganizationDocumentUuid() { // when & then WikiException ex = assertThrows(WikiException.class, () -> documentService.deleteOrganizationDocument(savedDocumentUuid, UUID.randomUUID())); diff --git a/src/test/java/com/wooteco/wiki/document/service/CrewDocumentServiceTest.java b/src/test/java/com/wooteco/wiki/document/service/CrewDocumentServiceTest.java new file mode 100644 index 0000000..422355c --- /dev/null +++ b/src/test/java/com/wooteco/wiki/document/service/CrewDocumentServiceTest.java @@ -0,0 +1,159 @@ +package com.wooteco.wiki.document.service; + +import static com.wooteco.wiki.global.exception.ErrorCode.DOCUMENT_NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.wooteco.wiki.admin.service.CrewDocumentService; +import com.wooteco.wiki.document.domain.CrewDocument; +import com.wooteco.wiki.document.domain.dto.DocumentResponse; +import com.wooteco.wiki.document.domain.dto.DocumentUpdateRequest; +import com.wooteco.wiki.document.fixture.CrewDocumentFixture; +import com.wooteco.wiki.document.repository.DocumentRepository; +import com.wooteco.wiki.global.exception.WikiException; +import com.wooteco.wiki.history.domain.History; +import com.wooteco.wiki.history.fixture.HistoryFixture; +import com.wooteco.wiki.history.repository.HistoryRepository; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class CrewDocumentServiceTest { + + @Autowired + private CrewDocumentService crewDocumentService; + @Autowired + private DocumentRepository documentRepository; + @Autowired + private HistoryRepository historyRepository; + + @DisplayName("문서 조회 기능") + @Nested + class GetByUuid { + + @DisplayName("문서 조회시, 해당 문서의 마지막 로그 번호를 가져온다.") + @Test + void getByUuid_success_byLatestVersionFromHistory() { + // given + CrewDocument crewDocument = CrewDocumentFixture.createDefaultCrewDocument(); + CrewDocument savedCrewDocument = documentRepository.save(crewDocument); + + History history = HistoryFixture.create("test", "test", "tesst", 150, LocalDateTime.of(2025, 7, 15, 10, 0, 0), + savedCrewDocument, 20L); + historyRepository.save(history); + + // when + DocumentUpdateRequest documentUpdateRequest = new DocumentUpdateRequest("test", "test", "test", 150L, + savedCrewDocument.getUuid()); + + crewDocumentService.update(savedCrewDocument.getUuid(), documentUpdateRequest); + DocumentResponse documentResponse = crewDocumentService.getByUuid(savedCrewDocument.getUuid()); + + // then + assertThat(documentResponse.latestVersion()).isEqualTo(21L); + } + } + + @DisplayName("문서 조회 기능") + @Nested + class Find { + + @DisplayName("문서 조회시, 해당 문서의 마지막 로그 번호를 가져온다.") + @Test + void getDocumentLatestVersion_success_byExistsDocument() { + // given + CrewDocument crewDocument = CrewDocumentFixture.createDefaultCrewDocument(); + CrewDocument savedCrewDocument = documentRepository.save(crewDocument); + + History history = HistoryFixture.create("test", "test", "tesst", 150, + LocalDateTime.of(2025, 7, 15, 10, 0, 0), + savedCrewDocument, 20L); + historyRepository.save(history); + + // when + DocumentUpdateRequest documentUpdateRequest = new DocumentUpdateRequest("test", "test", "test", 150L, + savedCrewDocument.getUuid()); + + crewDocumentService.update(savedCrewDocument.getUuid(), documentUpdateRequest); + DocumentResponse documentResponse = crewDocumentService.getByUuid(savedCrewDocument.getUuid()); + + // then + assertThat(documentResponse.latestVersion()).isEqualTo(21L); + } + } + + @Nested + @DisplayName("문서 uuid로 삭제 기능") + class deleteByUuid { + + @DisplayName("존재하는 문서 id일 경우 문서가 로그들과 함께 삭제된다") + @Test + void deleteById_success_byExistsId() { + // given + DocumentResponse documentResponse = crewDocumentService.create( + CrewDocumentFixture.createDocumentCreateRequest("title1", "content1", "writer1", 10L, + UUID.randomUUID())); + + // before then + assertThat(documentRepository.findAll()).hasSize(1); + assertThat(historyRepository.findAll()).hasSize(1); + + // when + crewDocumentService.deleteByUuid(documentResponse.documentUUID()); + + // after then + assertThat(documentRepository.findAll()).hasSize(0); + assertThat(historyRepository.findAll()).hasSize(0); + } + + @DisplayName("존재하지 않는 문서의 id일 경우 예외가 발생한다 : WikiException.DOCUMENT_NOT_FOUND") + @Test + void deleteById_throwsException_byNonExistsId() { + // when & then + WikiException ex = assertThrows(WikiException.class, + () -> crewDocumentService.deleteByUuid(UUID.randomUUID())); + assertThat(ex.getErrorCode()).isEqualTo(DOCUMENT_NOT_FOUND); + } + } + + @Nested + @DisplayName("문서 uuid로 삭제 기능") + class DeleteByUuid { + + @DisplayName("존재하는 문서 id일 경우 문서가 로그들과 함께 삭제된다") + @Test + void deleteByUuid_success_byExistingDocument() { + // given + DocumentResponse documentResponse = crewDocumentService.create( + CrewDocumentFixture.createDocumentCreateRequest("title1", "content1", "writer1", 10L, + UUID.randomUUID())); + + // before then + assertThat(documentRepository.findAll()).hasSize(1); + assertThat(historyRepository.findAll()).hasSize(1); + + // when + crewDocumentService.deleteByUuid(documentResponse.documentUUID()); + + // after then + assertThat(documentRepository.findAll()).hasSize(0); + assertThat(historyRepository.findAll()).hasSize(0); + } + + @DisplayName("존재하지 않는 문서의 id일 경우 예외가 발생한다 : WikiException.DOCUMENT_NOT_FOUND") + @Test + void deleteByUuid_fail_byNonExistingDocument() { + // when & then + WikiException ex = assertThrows(WikiException.class, + () -> crewDocumentService.deleteByUuid(UUID.randomUUID())); + assertThat(ex.getErrorCode()).isEqualTo(DOCUMENT_NOT_FOUND); + } + } +} diff --git a/src/test/java/com/wooteco/wiki/document/service/DocumentSearchServiceTest.java b/src/test/java/com/wooteco/wiki/document/service/DocumentSearchServiceTest.java index 33c5f00..7f5bcf7 100644 --- a/src/test/java/com/wooteco/wiki/document/service/DocumentSearchServiceTest.java +++ b/src/test/java/com/wooteco/wiki/document/service/DocumentSearchServiceTest.java @@ -2,9 +2,10 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; +import com.wooteco.wiki.admin.service.CrewDocumentService; import com.wooteco.wiki.document.domain.DocumentType; import com.wooteco.wiki.document.domain.dto.DocumentSearchResponse; -import com.wooteco.wiki.document.fixture.DocumentFixture; +import com.wooteco.wiki.document.fixture.CrewDocumentFixture; import com.wooteco.wiki.organizationdocument.domain.OrganizationDocument; import com.wooteco.wiki.organizationdocument.fixture.OrganizationDocumentFixture; import com.wooteco.wiki.organizationdocument.repository.OrganizationDocumentRepository; @@ -25,19 +26,19 @@ class DocumentSearchServiceTest { private DocumentSearchService documentSearchService; @Autowired - private DocumentService documentService; + private CrewDocumentService crewDocumentService; @Autowired private OrganizationDocumentRepository organizationDocumentRepository; @DisplayName("검색어로 시작하는 문서들을 찾아 리스트로 반환한다.") @Test - void search_success() { + void search_success_byKeywordPrefix() { // given - documentService.postCrewDocument( - DocumentFixture.createDocumentCreateRequest("title1", "content1", "writer1", 10L, UUID.randomUUID())); - documentService.postCrewDocument( - DocumentFixture.createDocumentCreateRequest("title2", "content2", "writer2", 11L, UUID.randomUUID())); + crewDocumentService.create( + CrewDocumentFixture.createDocumentCreateRequest("title1", "content1", "writer1", 10L, UUID.randomUUID())); + crewDocumentService.create( + CrewDocumentFixture.createDocumentCreateRequest("title2", "content2", "writer2", 11L, UUID.randomUUID())); // when List result = documentSearchService.search("title"); @@ -48,7 +49,7 @@ void search_success() { @DisplayName("검색어와 일치하는 문서가 없으면 빈 리스트를 반환한다.") @Test - void search_empty() { + void search_success_byNoMatch() { // given String keyword = "존재하지않는문서"; @@ -61,10 +62,10 @@ void search_empty() { @DisplayName("조직 문서라면 타입이 조직문서로 나온다.") @Test - void search_success_has_valid_type() { + void search_success_byOrganizationDocumentType() { // given - documentService.postCrewDocument( - DocumentFixture.createDocumentCreateRequest("title1", "content1", "writer1", 10L, UUID.randomUUID())); + crewDocumentService.create( + CrewDocumentFixture.createDocumentCreateRequest("title1", "content1", "writer1", 10L, UUID.randomUUID())); OrganizationDocument organizationDocument = OrganizationDocumentFixture.create("title2", "content", "writer", 15L, UUID.randomUUID()); organizationDocumentRepository.save(organizationDocument); @@ -75,8 +76,8 @@ void search_success_has_valid_type() { // then assertSoftly(softly -> { softly.assertThat(result).hasSize(2); - softly.assertThat(result.get(0).getDocumentType()).isEqualTo(DocumentType.CREW); - softly.assertThat(result.get(1).getDocumentType()).isEqualTo(DocumentType.ORGANIZATION); + softly.assertThat(result.get(0).documentType()).isEqualTo(DocumentType.CREW); + softly.assertThat(result.get(1).documentType()).isEqualTo(DocumentType.ORGANIZATION); }); } } diff --git a/src/test/java/com/wooteco/wiki/document/service/DocumentServiceTest.java b/src/test/java/com/wooteco/wiki/document/service/DocumentServiceTest.java index ac7502b..45d252a 100644 --- a/src/test/java/com/wooteco/wiki/document/service/DocumentServiceTest.java +++ b/src/test/java/com/wooteco/wiki/document/service/DocumentServiceTest.java @@ -4,21 +4,18 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.wooteco.wiki.admin.service.CrewDocumentService; import com.wooteco.wiki.document.domain.CrewDocument; import com.wooteco.wiki.document.domain.Document; import com.wooteco.wiki.document.domain.dto.CrewDocumentCreateRequest; import com.wooteco.wiki.document.domain.dto.DocumentResponse; -import com.wooteco.wiki.document.domain.dto.DocumentUpdateRequest; import com.wooteco.wiki.document.domain.dto.DocumentUuidResponse; -import com.wooteco.wiki.document.fixture.DocumentFixture; +import com.wooteco.wiki.document.fixture.CrewDocumentFixture; import com.wooteco.wiki.document.repository.DocumentRepository; -import com.wooteco.wiki.global.common.PageRequestDto; +import com.wooteco.wiki.global.common.PagingRequest; import com.wooteco.wiki.global.exception.ErrorCode; import com.wooteco.wiki.global.exception.WikiException; -import com.wooteco.wiki.history.domain.History; -import com.wooteco.wiki.history.fixture.HistoryFixture; import com.wooteco.wiki.history.repository.HistoryRepository; -import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.UUID; @@ -41,58 +38,35 @@ class DocumentServiceTest { @Autowired private DocumentService documentService; @Autowired + private CrewDocumentService crewDocumentService; + @Autowired private DocumentRepository documentRepository; @Autowired private HistoryRepository historyRepository; - @DisplayName("문서 조회 기능") - @Nested - class Find { - - @DisplayName("문서 조회시, 해당 문서의 마지막 로그 번호를 가져온다.") - @Test - void getDocumentLatestVersion_success_byExistsDocument() { - // given - CrewDocument crewDocument = DocumentFixture.createDefaultCrewDocument(); - CrewDocument savedCrewDocument = documentRepository.save(crewDocument); - - History history = HistoryFixture.create("test", "test", "tesst", 150, LocalDateTime.of(2025, 7, 15, 10, 0, 0), - savedCrewDocument, 20L); - historyRepository.save(history); - // when - DocumentUpdateRequest documentUpdateRequest = new DocumentUpdateRequest("test", "test", "test", 150, - savedCrewDocument.getUuid()); - - documentService.put(savedCrewDocument.getUuid(), documentUpdateRequest); - DocumentResponse documentResponse = documentService.getByUuid(savedCrewDocument.getUuid()); - - // then - assertThat(documentResponse.getLatestVersion()).isEqualTo(21L); - } - } @Nested @DisplayName("문서 제목으로 조회하면 UUID를 반환하는 기능") - class getUuidByTitle { + class GetUuidByTitle { @DisplayName("존재하는 문서 제목으로 조회할 경우 UUID를 반환한다") @Test void getUuidByTitle_success_byExistsDocumentTitle() { // given - DocumentResponse documentResponse = documentService.postCrewDocument( - DocumentFixture.createDocumentCreateRequestDefault()); + DocumentResponse documentResponse = crewDocumentService.create( + CrewDocumentFixture.createDocumentCreateRequestDefault()); // when - DocumentUuidResponse documentUuidResponse = documentService.getUuidByTitle(documentResponse.getTitle()); + DocumentUuidResponse documentUuidResponse = documentService.getUuidByTitle(documentResponse.title()); // then - assertThat(documentUuidResponse.uuid()).isEqualTo(documentResponse.getDocumentUUID()); + assertThat(documentUuidResponse.uuid()).isEqualTo(documentResponse.documentUUID()); } @DisplayName("존재하지 않는 문서 제목으로 조회할 경우 예외를 반환한다 : WikiException.DOCUMENT_NOT_FOUND") @Test - void getUuidByTitle_success_byNonExistsDocumentTitle() { + void getUuidByTitle_fail_byNonExistsDocumentTitle() { // when & then WikiException ex = assertThrows(WikiException.class, @@ -103,192 +77,151 @@ void getUuidByTitle_success_byNonExistsDocumentTitle() { @Nested @DisplayName("문서 전체 조회 기능") - class findAll { - - @Nested - @DisplayName("문서 전체 조회 기능 : 데이터의 수") - class findAll_data { - - PageRequestDto pageRequestDto = new PageRequestDto(); - - @DisplayName("저장된 문서가 존재할 때 요청 시 List 형태로 반환한다") - @Test - void findAll_success_bySomeData() { - // given - List crewDocumentCreateRequests = List.of( - DocumentFixture.createDocumentCreateRequest("title1", "content1", "writer1", 10L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title2", "content2", "writer2", 11L, - UUID.randomUUID()) - ); - - // when - for (CrewDocumentCreateRequest documentRequestDto : crewDocumentCreateRequests) { - documentService.postCrewDocument(documentRequestDto); - } - - // then - assertThat(documentService.findAll(pageRequestDto)).hasSize(crewDocumentCreateRequests.size()); - } - - @DisplayName("저장된 문서가 존재하지 않을 때 요청 시 예외 없이 빈 리스트를 반환한다") - @Test - void findAll_success_byNoData() { - // when & then - assertThat(documentService.findAll(pageRequestDto)).hasSize(0); - } + class FindAll { + + List crewDocumentCreateRequests; + + @BeforeEach + public void beforeEach() { + crewDocumentCreateRequests = List.of( + CrewDocumentFixture.createDocumentCreateRequest("title1", "content1", "writer1", 10L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title2", "content2", "writer2", 11L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title3", "content3", "writer3", 13L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title4", "content4", "writer4", 14L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title5", "content5", "writer5", 15L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title6", "content6", "writer6", 16L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title7", "content7", "writer7", 17L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title8", "content8", "writer8", 18L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title9", "content9", "writer9", 19L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title10", "content10", "writer10", 110L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title11", "content11", "writer11", 11L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title12", "content12", "writer12", 11L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title13", "content13", "writer13", 11L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title14", "content14", "writer14", 11L, + UUID.randomUUID()) + ); } - @Nested - @DisplayName("문서 전체 조회 기능 : Pageable") - class findAll_pageable { - - List crewDocumentCreateRequests; - - @BeforeEach - public void beforeEach() { - crewDocumentCreateRequests = List.of( - DocumentFixture.createDocumentCreateRequest("title1", "content1", "writer1", 10L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title2", "content2", "writer2", 11L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title3", "content3", "writer3", 13L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title4", "content4", "writer4", 14L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title5", "content5", "writer5", 15L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title6", "content6", "writer6", 16L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title7", "content7", "writer7", 17L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title8", "content8", "writer8", 18L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title9", "content9", "writer9", 19L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title10", "content10", "writer10", 110L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title11", "content11", "writer11", 11L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title12", "content12", "writer12", 11L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title13", "content13", "writer13", 11L, - UUID.randomUUID()), - DocumentFixture.createDocumentCreateRequest("title14", "content14", "writer14", 11L, - UUID.randomUUID()) - ); - } + @DisplayName("저장된 문서가 존재할 때 요청 시 List 형태로 반환한다") + @Test + void findAll_success_bySomeData() { + // given + List requestDtos = List.of( + CrewDocumentFixture.createDocumentCreateRequest("title1", "content1", "writer1", 10L, + UUID.randomUUID()), + CrewDocumentFixture.createDocumentCreateRequest("title2", "content2", "writer2", 11L, + UUID.randomUUID()) + ); - @DisplayName("PageRequestDto의 default 값으로 동작하는 지 확인") - @Test - void findAll_success_byPageRequestDtoDefault() { - // given - PageRequestDto pageRequestDto = new PageRequestDto(); - - for (CrewDocumentCreateRequest documentRequestDto : crewDocumentCreateRequests) { - documentService.postCrewDocument(documentRequestDto); - } - - // when - Page<@NotNull Document> documentPages = documentService.findAll(pageRequestDto); - - // then - SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(documentPages.getTotalElements()).isEqualTo(crewDocumentCreateRequests.size()); - softAssertions.assertThat(documentPages.getNumber()).isEqualTo(0); - softAssertions.assertThat(documentPages.getTotalPages()).isEqualTo(2); - softAssertions.assertAll(); - } + PagingRequest pageRequestDto = new PagingRequest(); - @DisplayName("PageRequestDto의 필드 값을 수정한 값으로 동작하는 지 확인") - @Test - void findAll_success_byPageRequestDto() { - // given - PageRequestDto pageRequestDto = new PageRequestDto(); - pageRequestDto.setPageNumber(1); - pageRequestDto.setPageSize(5); - pageRequestDto.setSort("uuid"); - pageRequestDto.setSortDirection("DESC"); - - for (CrewDocumentCreateRequest documentRequestDto : crewDocumentCreateRequests) { - documentService.postCrewDocument(documentRequestDto); - } - - // when - Page<@NotNull Document> documentPages = documentService.findAll(pageRequestDto); - - // then - SoftAssertions softAssertions = new SoftAssertions(); - softAssertions.assertThat(documentPages.getTotalElements()).isEqualTo(crewDocumentCreateRequests.size()); - softAssertions.assertThat(documentPages.getNumber()).isEqualTo(1); - softAssertions.assertThat(documentPages.getTotalPages()).isEqualTo(3); - softAssertions.assertAll(); + // when + for (CrewDocumentCreateRequest documentRequestDto : requestDtos) { + crewDocumentService.create(documentRequestDto); } - @DisplayName("PageRequestDto 필드 중 pageNumber 와 pageSize는 음수가 불가능하도록 확인") - @Test - void findAll_throwsException_byNegativeNumber() { - // given - PageRequestDto pageRequestDto = new PageRequestDto(); - pageRequestDto.setPageNumber(-1); - pageRequestDto.setPageSize(5); - - for (CrewDocumentCreateRequest documentRequestDto : crewDocumentCreateRequests) { - documentService.postCrewDocument(documentRequestDto); - } - - // when & then - WikiException ex = assertThrows(WikiException.class, - () -> documentService.findAll(pageRequestDto)); - assertThat(ex.getErrorCode()).isEqualTo(ErrorCode.PAGE_BAD_REQUEST); - } + // then + assertThat(documentService.findAll(pageRequestDto)).hasSize(requestDtos.size()); } - } - @Nested - @DisplayName("문서 id로 삭제 기능") - class deleteById { + @DisplayName("저장된 문서가 존재하지 않을 때 요청 시 예외 없이 빈 리스트를 반환한다") + @Test + void findAll_success_byNoData() { + // given + PagingRequest pageRequestDto = new PagingRequest(); + + // when & then + assertThat(documentService.findAll(pageRequestDto)).hasSize(0); + } - @DisplayName("존재하는 문서 id일 경우 문서가 로그들과 함께 삭제된다") + @DisplayName("PagingRequest의 default 값으로 동작하는 지 확인") @Test - void deleteById_success_byExistsId() { + void findAll_success_byPagingRequestDefault() { // given - DocumentResponse documentResponse = documentService.postCrewDocument( - DocumentFixture.createDocumentCreateRequest("title1", "content1", "writer1", 10L, - UUID.randomUUID())); + PagingRequest pageRequestDto = new PagingRequest(); + + for (CrewDocumentCreateRequest documentRequestDto : crewDocumentCreateRequests) { + crewDocumentService.create(documentRequestDto); + } + + // when + Page<@NotNull Document> documentPages = documentService.findAll(pageRequestDto); + + // then + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(documentPages.getTotalElements()).isEqualTo(crewDocumentCreateRequests.size()); + softAssertions.assertThat(documentPages.getNumber()).isEqualTo(0); + softAssertions.assertThat(documentPages.getTotalPages()).isEqualTo(2); + softAssertions.assertAll(); + } - // before then - assertThat(documentRepository.findAll()).hasSize(1); - assertThat(historyRepository.findAll()).hasSize(1); + @DisplayName("PagingRequest의 필드 값을 수정한 값으로 동작하는 지 확인") + @Test + void findAll_success_byPagingRequest() { + // given + PagingRequest pageRequestDto = new PagingRequest(); + pageRequestDto.setPageNumber(1); + pageRequestDto.setPageSize(5); + pageRequestDto.setSort("uuid"); + pageRequestDto.setSortDirection("DESC"); + + for (CrewDocumentCreateRequest documentRequestDto : crewDocumentCreateRequests) { + crewDocumentService.create(documentRequestDto); + } // when - documentService.deleteById(documentResponse.getDocumentId()); + Page<@NotNull Document> documentPages = documentService.findAll(pageRequestDto); - // after then - assertThat(documentRepository.findAll()).hasSize(0); - assertThat(historyRepository.findAll()).hasSize(0); + // then + SoftAssertions softAssertions = new SoftAssertions(); + softAssertions.assertThat(documentPages.getTotalElements()).isEqualTo(crewDocumentCreateRequests.size()); + softAssertions.assertThat(documentPages.getNumber()).isEqualTo(1); + softAssertions.assertThat(documentPages.getTotalPages()).isEqualTo(3); + softAssertions.assertAll(); } - @DisplayName("존재하지 않는 문서의 id일 경우 예외가 발생한다 : WikiException.DOCUMENT_NOT_FOUND") + @DisplayName("PagingRequest 필드 중 pageNumber 와 pageSize는 음수가 불가능하도록 확인") @Test - void deleteById_throwsException_byNonExistsId() { + void findAll_fail_byNegativePageNumber() { + // given + PagingRequest pageRequestDto = new PagingRequest(); + pageRequestDto.setPageNumber(-1); + pageRequestDto.setPageSize(5); + + for (CrewDocumentCreateRequest documentRequestDto : crewDocumentCreateRequests) { + crewDocumentService.create(documentRequestDto); + } + // when & then WikiException ex = assertThrows(WikiException.class, - () -> documentService.deleteById(Long.MAX_VALUE)); - assertThat(ex.getErrorCode()).isEqualTo(DOCUMENT_NOT_FOUND); + () -> documentService.findAll(pageRequestDto)); + assertThat(ex.getErrorCode()).isEqualTo(ErrorCode.PAGE_BAD_REQUEST); } } @Test @DisplayName("flushViews 호출 시 uuid별로 조회수가 증가된다") - void flushViews_success() { + void flushViews_success_byAccumulatedViewCount() { // given UUID uuid1 = UUID.randomUUID(); UUID uuid2 = UUID.randomUUID(); CrewDocument doc1 = documentRepository.save( - DocumentFixture.createCrewDocument("title1", "content1", "writer1", 10L, uuid1)); + CrewDocumentFixture.createCrewDocument("title1", "content1", "writer1", 10L, uuid1)); CrewDocument doc2 = documentRepository.save( - DocumentFixture.createCrewDocument("title2", "content2", "writer2", 10L, uuid2)); + CrewDocumentFixture.createCrewDocument("title2", "content2", "writer2", 10L, uuid2)); Map viewMap = Map.of( uuid1, 5, diff --git a/src/test/java/com/wooteco/wiki/global/auth/controller/AuthControllerTest.java b/src/test/java/com/wooteco/wiki/global/auth/controller/AuthControllerTest.java index 30284f1..e248b68 100644 --- a/src/test/java/com/wooteco/wiki/global/auth/controller/AuthControllerTest.java +++ b/src/test/java/com/wooteco/wiki/global/auth/controller/AuthControllerTest.java @@ -43,11 +43,11 @@ void setUp() { @Nested @DisplayName("로그인 기능") - class login { + class Login { @DisplayName("유효한 이메일과 비밀번호로 로그인을 성공한다.") @Test - void login_success() { + void login_success_byValidCredentials() { // given // when @@ -66,7 +66,7 @@ void login_success() { @DisplayName("유효하지 않는 이메일과 비밀번호로 로그인을 시도하여 실패: 상태 코드 404를 반환한다.") @Test - void login_throwException_byInvalidEmail() { + void login_fail_byInvalidCredentials() { // given // when LoginRequest requestDto = new LoginRequest("invalidLoginId", "invalidPassword"); @@ -85,11 +85,11 @@ void login_throwException_byInvalidEmail() { @Nested @DisplayName("토큰 기반 어드민 조회 API 테스트") - class checkAuth { + class CheckAuth { @DisplayName("토큰으로 어드민 조회 시 200 OK 반환") @Test - void checkAuth_success() { + void checkAuth_success_byValidToken() { // given String expectedLoginId = "loginIdCS"; Admin savedAdmin = adminRepository.save(new Admin(expectedLoginId, "password")); diff --git a/src/test/java/com/wooteco/wiki/global/auth/service/AuthServiceTest.java b/src/test/java/com/wooteco/wiki/global/auth/service/AuthServiceTest.java index 0423274..c557f15 100644 --- a/src/test/java/com/wooteco/wiki/global/auth/service/AuthServiceTest.java +++ b/src/test/java/com/wooteco/wiki/global/auth/service/AuthServiceTest.java @@ -8,7 +8,7 @@ import com.wooteco.wiki.admin.repository.AdminRepository; import com.wooteco.wiki.global.auth.JwtTokenProvider; import com.wooteco.wiki.global.auth.Role; -import com.wooteco.wiki.global.auth.domain.dto.TokenInfoDto; +import com.wooteco.wiki.global.auth.domain.dto.TokenInfoResponse; import com.wooteco.wiki.global.auth.domain.dto.TokenResponse; import com.wooteco.wiki.global.exception.ErrorCode; import com.wooteco.wiki.global.exception.WikiException; @@ -32,14 +32,14 @@ class AuthServiceTest { private AdminRepository adminRepository; @Nested - @DisplayName("TokenInfoDto로 Jwt 토큰 생성") - class createToken { + @DisplayName("TokenInfoResponse로 Jwt 토큰 생성") + class CreateToken { - @DisplayName("TokenInfoDto로 Jwt 토큰 생성된다.") + @DisplayName("TokenInfoResponse로 Jwt 토큰 생성된다.") @Test - void createToken_success() { + void createToken_success_byTokenInfoResponse() { // given - TokenInfoDto tokenInfoDto = new TokenInfoDto(1L, Role.ROLE_ADMIN); + TokenInfoResponse tokenInfoDto = new TokenInfoResponse(1L, Role.ROLE_ADMIN); // when TokenResponse tokenResponse = authService.createToken(tokenInfoDto); @@ -52,7 +52,7 @@ void createToken_success() { @Nested @DisplayName("loginRequest로 Jwt 토큰 생성") - class login { + class Login { @DisplayName("존재하는 어드민 정보로 요청했을 시 Jwt 토큰을 반환한다.") @Test @@ -71,7 +71,7 @@ void login_success_byExistingAdmin() { @DisplayName("존재하지 않는 어드민 정보로 요청했을 때 예외 발생한다. : WikiException.ADMIN_NOT_FOUND") @Test - void login_throwException_byInValidAdmin() { + void login_fail_byInvalidAdmin() { // given LoginRequest loginRequest = new LoginRequest("invalidLoginId", "invalidPassword"); diff --git a/src/test/java/com/wooteco/wiki/history/service/HistoryServiceTest.java b/src/test/java/com/wooteco/wiki/history/service/HistoryServiceTest.java index 4be778a..7bbf86c 100644 --- a/src/test/java/com/wooteco/wiki/history/service/HistoryServiceTest.java +++ b/src/test/java/com/wooteco/wiki/history/service/HistoryServiceTest.java @@ -5,9 +5,9 @@ import com.wooteco.wiki.document.domain.CrewDocument; import com.wooteco.wiki.document.domain.Document; -import com.wooteco.wiki.document.fixture.DocumentFixture; +import com.wooteco.wiki.document.fixture.CrewDocumentFixture; import com.wooteco.wiki.document.repository.DocumentRepository; -import com.wooteco.wiki.global.common.PageRequestDto; +import com.wooteco.wiki.global.common.PagingRequest; import com.wooteco.wiki.global.exception.ErrorCode; import com.wooteco.wiki.global.exception.WikiException; import com.wooteco.wiki.history.domain.dto.HistoryResponse; @@ -39,25 +39,26 @@ public class HistoryServiceTest { @Autowired private HistoryRepository historyRepository; + private PagingRequest pageRequestDto; + private UUID documentUuid; + private CrewDocument savedCrewDocument; + + @BeforeEach + void setUp() { + pageRequestDto = new PagingRequest(); + savedCrewDocument = documentRepository.save( + CrewDocumentFixture.createCrewDocument("title", "content", "writer", 100L, UUID.randomUUID())); + documentUuid = savedCrewDocument.getUuid(); + + historyRepository.save( + HistoryFixture.create("t1", "c1", "w1", 10L, LocalDateTime.now(), savedCrewDocument, 1L)); + historyRepository.save( + HistoryFixture.create("t1", "c2", "w2", 20L, LocalDateTime.now(), savedCrewDocument, 2L)); + } + @Nested @DisplayName("documentUuid로 요청 시 로그 리스트 반환하는 기능") - class findAllByCrewDocumentUuid { - - private PageRequestDto pageRequestDto = new PageRequestDto(); - private UUID documentUuid; - private CrewDocument savedCrewDocument; - - @BeforeEach - void setUp() { - savedCrewDocument = documentRepository.save( - DocumentFixture.createCrewDocument("title", "content", "writer", 100L, UUID.randomUUID())); - documentUuid = savedCrewDocument.getUuid(); - - historyRepository.save( - HistoryFixture.create("t1", "c1", "w1", 10L, LocalDateTime.now(), savedCrewDocument, 1L)); - historyRepository.save( - HistoryFixture.create("t1", "c2", "w2", 20L, LocalDateTime.now(), savedCrewDocument, 2L)); - } + class FindAllByDocumentUuid { @DisplayName("documentUuid에 해당하는 로그들이 반환된다") @Test @@ -76,7 +77,7 @@ void findAllByDocumentUuid_success_bySomeData() { @DisplayName("버전 번호가 순차적으로 부여된다") @Test - void findAllByDocumentUuid_versionsAreNumberedCorrectly() { + void findAllByDocumentUuid_success_bySequentialVersions() { // when Page actual = historyService.findAllByDocumentUuid(documentUuid, pageRequestDto); @@ -90,7 +91,7 @@ void findAllByDocumentUuid_versionsAreNumberedCorrectly() { @DisplayName("존재하지 않는 documentUuid로 요청 시 예외가 발생한다 : WikiException.DOCUMENT_NOT_FOUND") @Test - void findAllByDocumentUuid_throwsException_byNonExistsDocumentUuid() { + void findAllByDocumentUuid_fail_byNonExistsDocumentUuid() { // given UUID invalidUuid = UUID.randomUUID(); @@ -100,9 +101,15 @@ void findAllByDocumentUuid_throwsException_byNonExistsDocumentUuid() { assertThat(ex.getErrorCode()).isEqualTo(ErrorCode.DOCUMENT_NOT_FOUND); } + } + + @Nested + @DisplayName("로그 저장 기능") + class Save { + @DisplayName("로그 저장 시 최신 version을 제공한다.") @Test - void save_versionIsNumberedCorrectly() { + void save_success_byIncrementedVersion() { // when Document updatedDocument = savedCrewDocument.update("test_document_2", "contents", "writer1", 120L, LocalDateTime.now()); diff --git a/src/test/java/com/wooteco/wiki/organizationdocument/service/CrewDocumentOrganizationLinkServiceTest.java b/src/test/java/com/wooteco/wiki/organizationdocument/service/CrewDocumentOrganizationLinkServiceTest.java index 31c50b9..fbe7613 100644 --- a/src/test/java/com/wooteco/wiki/organizationdocument/service/CrewDocumentOrganizationLinkServiceTest.java +++ b/src/test/java/com/wooteco/wiki/organizationdocument/service/CrewDocumentOrganizationLinkServiceTest.java @@ -4,7 +4,7 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; import com.wooteco.wiki.document.domain.CrewDocument; -import com.wooteco.wiki.document.fixture.DocumentFixture; +import com.wooteco.wiki.document.fixture.CrewDocumentFixture; import com.wooteco.wiki.document.repository.CrewDocumentRepository; import com.wooteco.wiki.organizationdocument.domain.DocumentOrganizationLink; import com.wooteco.wiki.organizationdocument.domain.OrganizationDocument; @@ -45,7 +45,7 @@ class Link { @BeforeEach void setUp() { - CrewDocument crewDocument = DocumentFixture.createDefaultCrewDocument(); + CrewDocument crewDocument = CrewDocumentFixture.createDefaultCrewDocument(); savedCrewDocument = crewDocumentRepository.save(crewDocument); OrganizationDocument organizationDocument = OrganizationDocumentFixture.createDefault(); @@ -54,7 +54,7 @@ void setUp() { @DisplayName("특정 문서와 특정 조직 문서로 둘을 연결한다.") @Test - void link_success_byDocumentAndOrganizationDocument() { + void link_success_byCrewAndOrganizationDocument() { // when documentOrgDocLinkService.link(savedCrewDocument, savedOrganizationDocument); @@ -73,14 +73,14 @@ void link_success_byDocumentAndOrganizationDocument() { @DisplayName("문서와 조직 문서 연결 해제 할 때에") @Nested - class UnLink { + class Unlink { private CrewDocument savedCrewDocument; private OrganizationDocument savedOrganizationDocument; @BeforeEach void setUp() { - CrewDocument crewDocument = DocumentFixture.createDefaultCrewDocument(); + CrewDocument crewDocument = CrewDocumentFixture.createDefaultCrewDocument(); savedCrewDocument = crewDocumentRepository.save(crewDocument); OrganizationDocument organizationDocument = OrganizationDocumentFixture.createDefault(); @@ -91,7 +91,7 @@ void setUp() { @DisplayName("특정 문서와 특정 조직 문서의 연결을 해제한다") @Test - void unLink_success_byDocumentAndOrganizationDocument() { + void unlink_success_byCrewAndOrganizationDocument() { // when documentOrgDocLinkService.unlink(savedCrewDocument, savedOrganizationDocument); diff --git a/src/test/java/com/wooteco/wiki/organizationdocument/service/OrganizationCrewDocumentServiceTest.java b/src/test/java/com/wooteco/wiki/organizationdocument/service/OrganizationCrewDocumentServiceTest.java index db9cbee..2cbd5ce 100644 --- a/src/test/java/com/wooteco/wiki/organizationdocument/service/OrganizationCrewDocumentServiceTest.java +++ b/src/test/java/com/wooteco/wiki/organizationdocument/service/OrganizationCrewDocumentServiceTest.java @@ -3,12 +3,18 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.wooteco.wiki.document.domain.CrewDocument; +import com.wooteco.wiki.document.fixture.CrewDocumentFixture; +import com.wooteco.wiki.document.repository.CrewDocumentRepository; import com.wooteco.wiki.global.exception.ErrorCode; import com.wooteco.wiki.global.exception.WikiException; +import com.wooteco.wiki.history.domain.History; +import com.wooteco.wiki.history.repository.HistoryRepository; import com.wooteco.wiki.organizationdocument.domain.OrganizationDocument; import com.wooteco.wiki.organizationdocument.dto.request.OrganizationDocumentCreateRequest; import com.wooteco.wiki.organizationdocument.dto.request.OrganizationDocumentUpdateRequest; import com.wooteco.wiki.organizationdocument.dto.response.OrganizationDocumentAndEventResponse; +import com.wooteco.wiki.organizationdocument.dto.response.OrganizationDocumentResponse; import com.wooteco.wiki.organizationdocument.fixture.OrganizationDocumentFixture; import com.wooteco.wiki.organizationdocument.repository.OrganizationDocumentRepository; import com.wooteco.wiki.organizationevent.domain.OrganizationEvent; @@ -21,6 +27,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.test.annotation.DirtiesContext; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) @@ -36,6 +44,12 @@ class OrganizationCrewDocumentServiceTest { @Autowired private OrganizationEventRepository organizationEventRepository; + @Autowired + private HistoryRepository historyRepository; + + @Autowired + private CrewDocumentRepository crewDocumentRepository; + @DisplayName("조직 문서를 수정할 때") @Nested class Update { @@ -61,22 +75,31 @@ void updateOrganizationDocument_success_byValidData() { organizationDocument.getUuid()).orElseThrow(); // then + Page histories = historyRepository.findAllByDocumentId(organizationDocument.getId(), Pageable.ofSize(1)); + assertSoftly(softly -> { softly.assertThat(foundOrganizationDocument.getTitle()).isEqualTo(updateTitle); softly.assertThat(foundOrganizationDocument.getContents()).isEqualTo(updateContents); softly.assertThat(foundOrganizationDocument.getWriter()).isEqualTo(updateWriter); softly.assertThat(foundOrganizationDocument.getDocumentBytes()).isEqualTo(updateDocumentBytes); + softly.assertThat(histories.hasContent()).isTrue(); + + History first = histories.getContent().get(0); + softly.assertThat(first.getTitle()).isEqualTo(updateTitle); + softly.assertThat(first.getContents()).isEqualTo(updateContents); + softly.assertThat(first.getWriter()).isEqualTo(updateWriter); + softly.assertThat(first.getDocumentBytes()).isEqualTo(updateDocumentBytes); }); } } @DisplayName("조직 문서를 조회할 때") @Nested - class Find { + class FindByUuid { @DisplayName("올바른 값으로 조회된다.") @Test - void find_success_byValidData() { + void findByUuid_success_byValidData() { // given UUID uuid = UUID.randomUUID(); OrganizationDocument organizationDocument = OrganizationDocumentFixture @@ -109,7 +132,7 @@ class Create { @DisplayName("이미 있는 문서 이름이라면 예외가 발생한다.") @Test - void create_success_byValidData() { + void create_fail_byDuplicateTitle() { // given UUID uuid = UUID.randomUUID(); OrganizationDocument organizationDocument = OrganizationDocumentFixture @@ -127,6 +150,36 @@ void create_success_byValidData() { Assertions.assertThat(ex.getErrorCode()).isEqualTo(ErrorCode.DOCUMENT_DUPLICATE); } + @DisplayName("첫 번째 로그가 저장된다.") + @Test + void create_success_byFirstHistorySaved() { + // given + CrewDocument crewDocument = CrewDocumentFixture.createDefaultCrewDocument(); + CrewDocument savedCrewDocument = crewDocumentRepository.save(crewDocument); + + OrganizationDocumentCreateRequest organizationDocumentCreateRequest = new OrganizationDocumentCreateRequest( + "newTitle", "newContents", "newWriter", 99L, savedCrewDocument.getUuid(), UUID.randomUUID()); + + // when + OrganizationDocumentResponse response = organizationDocumentService.create(organizationDocumentCreateRequest); + OrganizationDocument savedOrganizationDocument = organizationDocumentRepository.findByUuid( + response.organizationDocumentUuid()).orElseThrow(); + + // then + Page histories = historyRepository.findAllByDocumentId(savedOrganizationDocument.getId(), + Pageable.ofSize(1)); + + assertSoftly(softly -> { + softly.assertThat(histories.hasContent()).isTrue(); + History first = histories.getContent().get(0); + softly.assertThat(first.getVersion()).isEqualTo(1L); + softly.assertThat(first.getTitle()).isEqualTo("newTitle"); + softly.assertThat(first.getContents()).isEqualTo("newContents"); + softly.assertThat(first.getWriter()).isEqualTo("newWriter"); + softly.assertThat(first.getDocumentBytes()).isEqualTo(99L); + }); + } + // @DisplayName("존재하지 않는 특정 문서의 Uuid로 요청한다면 예외가 발생한다 : DOCUMENT_NOT_FOUND") // @Test // void addOrganizationDocument_error_byNonExistingDocumentUuid() { diff --git a/src/test/java/com/wooteco/wiki/organizationdocument/service/OrganizationDocumentServiceTest.java b/src/test/java/com/wooteco/wiki/organizationdocument/service/OrganizationDocumentServiceTest.java new file mode 100644 index 0000000..6715d6c --- /dev/null +++ b/src/test/java/com/wooteco/wiki/organizationdocument/service/OrganizationDocumentServiceTest.java @@ -0,0 +1,108 @@ +package com.wooteco.wiki.organizationdocument.service; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import com.wooteco.wiki.document.domain.CrewDocument; +import com.wooteco.wiki.document.fixture.CrewDocumentFixture; +import com.wooteco.wiki.document.repository.CrewDocumentRepository; +import com.wooteco.wiki.global.exception.WikiException; +import com.wooteco.wiki.organizationdocument.domain.DocumentOrganizationLink; +import com.wooteco.wiki.organizationdocument.domain.OrganizationDocument; +import com.wooteco.wiki.organizationdocument.dto.request.OrganizationDocumentLinkRequest; +import com.wooteco.wiki.organizationdocument.fixture.OrganizationDocumentFixture; +import com.wooteco.wiki.organizationdocument.repository.DocumentOrganizationLinkRepository; +import com.wooteco.wiki.organizationdocument.repository.OrganizationDocumentRepository; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class OrganizationDocumentServiceTest { + + @Autowired + private OrganizationDocumentService organizationDocumentService; + + @Autowired + private CrewDocumentRepository crewDocumentRepository; + + @Autowired + private OrganizationDocumentRepository organizationDocumentRepository; + + @Autowired + private DocumentOrganizationLinkRepository documentOrganizationLinkRepository; + + @DisplayName("기존 조직 문서를 크루 문서에 연결할 때") + @Nested + class LinkExistingOrganization { + + private CrewDocument savedCrewDocument; + private OrganizationDocument savedOrganizationDocument; + + @BeforeEach + void setUp() { + CrewDocument crewDocument = CrewDocumentFixture.createDefaultCrewDocument(); + savedCrewDocument = crewDocumentRepository.save(crewDocument); + + OrganizationDocument organizationDocument = OrganizationDocumentFixture.createDefault(); + savedOrganizationDocument = organizationDocumentRepository.save(organizationDocument); + } + + @DisplayName("성공적으로 기존 조직 문서를 크루 문서와 연결한다") + @Test + void link_success() { + // given + OrganizationDocumentLinkRequest request = new OrganizationDocumentLinkRequest( + savedCrewDocument.getUuid(), + savedOrganizationDocument.getUuid() + ); + + // when + organizationDocumentService.link(request); + + // then + DocumentOrganizationLink link = documentOrganizationLinkRepository + .findByCrewDocumentAndOrganizationDocument(savedCrewDocument, savedOrganizationDocument) + .orElseThrow(); + + assertSoftly(softly -> { + softly.assertThat(link.getCrewDocument().getId()).isEqualTo(savedCrewDocument.getId()); + softly.assertThat(link.getOrganizationDocument().getId()).isEqualTo(savedOrganizationDocument.getId()); + }); + } + + @DisplayName("존재하지 않는 크루 문서 UUID로 연결을 시도하면 예외가 발생한다") + @Test + void link_fail_crewDocumentNotFound() { + // given + OrganizationDocumentLinkRequest request = new OrganizationDocumentLinkRequest( + UUID.randomUUID(), + savedOrganizationDocument.getUuid() + ); + + // when & then + assertThatThrownBy(() -> organizationDocumentService.link(request)) + .isInstanceOf(WikiException.class); + } + + @DisplayName("존재하지 않는 조직 문서 UUID로 연결을 시도하면 예외가 발생한다") + @Test + void link_fail_organizationDocumentNotFound() { + // given + OrganizationDocumentLinkRequest request = new OrganizationDocumentLinkRequest( + savedCrewDocument.getUuid(), + UUID.randomUUID() + ); + + // when & then + assertThatThrownBy(() -> organizationDocumentService.link(request)) + .isInstanceOf(WikiException.class); + } + } +} diff --git a/src/test/java/com/wooteco/wiki/organizationevent/controller/OrganizationEventControllerTest.java b/src/test/java/com/wooteco/wiki/organizationevent/controller/OrganizationEventControllerTest.java index 64a91d3..46b6acc 100644 --- a/src/test/java/com/wooteco/wiki/organizationevent/controller/OrganizationEventControllerTest.java +++ b/src/test/java/com/wooteco/wiki/organizationevent/controller/OrganizationEventControllerTest.java @@ -49,11 +49,11 @@ void setUp() { @Nested @DisplayName("조직 이벤트 생성 시") - class Create { + class Post { @Test @DisplayName("유효한 값이면 저장된다.") - void create_success() { + void post_success_byValidRequest() { Map body = Map.of( "title", "분기 워크숍", "contents", "OKR 점검", @@ -74,7 +74,7 @@ void create_success() { @Test @DisplayName("검증 실패시 400 예외가 발생한다.") - void create_fail_400_validation() { + void post_fail_byValidationError() { Map body = Map.of( "title", " ", "contents", "메모", @@ -94,7 +94,7 @@ void create_fail_400_validation() { @Test @DisplayName("존재하지 않은 조직 문서 uuid일 경우 404 예외가 발생한다.") - void create_fail_404_orgDocNotFound() { + void post_fail_byOrganizationDocumentNotFound() { Map body = Map.of( "title", "회의", "contents", "아젠다", @@ -115,11 +115,11 @@ void create_fail_404_orgDocNotFound() { @Nested @DisplayName("조직 문서를 수정 시") - class Update { + class Put { @Test @DisplayName("전달된 값으로 갱신된다.") - void update_success() { + void put_success_byUpdatedValues() { OrganizationEvent event = OrganizationEventFixture.createDefault(doc); organizationEventRepository.save(event); UUID eventUuid = event.getUuid(); @@ -145,7 +145,7 @@ void update_success() { @Test @DisplayName("이벤트가 존재하지 않을 경우 404 예외가 발생한다.") - void update_fail_404_eventNotFound() { + void put_fail_byEventNotFound() { Map body = Map.of( "title", "x", "contents", "y", @@ -164,7 +164,7 @@ void update_fail_404_eventNotFound() { @Test @DisplayName("검증이 실패할 경우 400 예외가 발생한다.") - void update_fail_400_validation() { + void put_fail_byValidationError() { OrganizationEvent event = OrganizationEventFixture.createDefault(doc); organizationEventRepository.save(event); UUID eventUuid = event.getUuid(); @@ -192,7 +192,7 @@ class Delete { @Test @DisplayName("정상적으로 삭제된다.") - void delete_success() { + void delete_success_byExistingEvent() { OrganizationEvent event = OrganizationEventFixture.createDefault(doc); organizationEventRepository.save(event); UUID eventUuid = event.getUuid(); @@ -206,7 +206,7 @@ void delete_success() { @Test @DisplayName("이벤트 없을 경우 404 예외가 발생한다.") - void delete_fail_404_eventNotFound() { + void delete_fail_byEventNotFound() { RestAssured.given().log().all() .when() .delete("/organization-events/{uuid}", UUID.randomUUID()) diff --git a/src/test/java/com/wooteco/wiki/organizationevent/service/OrganizationEventServiceTest.java b/src/test/java/com/wooteco/wiki/organizationevent/service/OrganizationEventServiceTest.java index 672d9a3..dc31943 100644 --- a/src/test/java/com/wooteco/wiki/organizationevent/service/OrganizationEventServiceTest.java +++ b/src/test/java/com/wooteco/wiki/organizationevent/service/OrganizationEventServiceTest.java @@ -38,11 +38,11 @@ class OrganizationEventServiceTest { @Nested @DisplayName("조직 이벤트 생성 시") - class Create { + class Post { @Test @DisplayName("유효한 값이면 저장되고 UUID가 반환된다.") - void create_success() { + void post_success_byValidRequest() { // given OrganizationDocument orgDoc = OrganizationDocumentFixture.createDefault(); organizationDocumentRepository.save(orgDoc); @@ -75,7 +75,7 @@ void create_success() { @Test @DisplayName("존재하지 않는 조직 문서 UUID면 404 예외가 발생한다.") - void create_fail_orgDocNotFound() { + void post_fail_byOrganizationDocumentNotFound() { // given OrganizationEventCreateRequest req = new OrganizationEventCreateRequest( "회의", @@ -91,101 +91,102 @@ void create_fail_orgDocNotFound() { .hasFieldOrPropertyWithValue("errorCode", ErrorCode.ORGANIZATION_DOCUMENT_NOT_FOUND); } - @Nested - @DisplayName("조직 이벤트 수정 시") - class Update { - - @Test - @DisplayName("전달된 값으로 갱신된다.") - void update_success() { - // given: 선행으로 문서 + 이벤트 하나 생성 - OrganizationDocument orgDoc = OrganizationDocumentFixture.createDefault(); - organizationDocumentRepository.save(orgDoc); - - OrganizationEventCreateRequest createReq = new OrganizationEventCreateRequest( - "분기 워크숍", - "OKR 점검", - "밍트", - LocalDate.now(), - orgDoc.getUuid() - ); - UUID eventUuid = organizationEventService.post(createReq).organizationEventUuid(); - - OrganizationEventUpdateRequest req = new OrganizationEventUpdateRequest( - "분기 워크숍(보강)", - "OKR + 액션아이템", - "밍트2", - LocalDate.now().plusDays(1) - ); - - // when - OrganizationEventUpdateResponse res = organizationEventService.put(eventUuid, req); - - // then - OrganizationEvent found = organizationEventRepository.findByUuid(eventUuid).orElseThrow(); - - assertSoftly(softly -> { - softly.assertThat(res.organizationEventUuid()).isEqualTo(eventUuid); - softly.assertThat(found.getTitle()).isEqualTo(req.title()); - softly.assertThat(found.getContents()).isEqualTo(req.contents()); - softly.assertThat(found.getWriter()).isEqualTo(req.writer()); - softly.assertThat(found.getOccurredAt()).isEqualTo(req.occurredAt()); - }); - } - - @Test - @DisplayName("존재하지 않는 이벤트 UUID면 404 예외가 발생한다.") - void update_fail_eventNotFound() { - // given - OrganizationEventUpdateRequest updateReq = new OrganizationEventUpdateRequest( - "x", "y", "z", LocalDate.now() - ); - - // then - assertThatThrownBy(() -> - organizationEventService.put(UUID.randomUUID(), updateReq)) - .isInstanceOf(WikiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCode.ORGANIZATION_EVENT_NOT_FOUND); - } + } + + @Nested + @DisplayName("조직 이벤트 수정 시") + class Put { + + @Test + @DisplayName("전달된 값으로 갱신된다.") + void put_success_byUpdatedValues() { + // given: 선행으로 문서 + 이벤트 하나 생성 + OrganizationDocument orgDoc = OrganizationDocumentFixture.createDefault(); + organizationDocumentRepository.save(orgDoc); + + OrganizationEventCreateRequest createReq = new OrganizationEventCreateRequest( + "분기 워크숍", + "OKR 점검", + "밍트", + LocalDate.now(), + orgDoc.getUuid() + ); + UUID eventUuid = organizationEventService.post(createReq).organizationEventUuid(); + + OrganizationEventUpdateRequest req = new OrganizationEventUpdateRequest( + "분기 워크숍(보강)", + "OKR + 액션아이템", + "밍트2", + LocalDate.now().plusDays(1) + ); + + // when + OrganizationEventUpdateResponse res = organizationEventService.put(eventUuid, req); + + // then + OrganizationEvent found = organizationEventRepository.findByUuid(eventUuid).orElseThrow(); + + assertSoftly(softly -> { + softly.assertThat(res.organizationEventUuid()).isEqualTo(eventUuid); + softly.assertThat(found.getTitle()).isEqualTo(req.title()); + softly.assertThat(found.getContents()).isEqualTo(req.contents()); + softly.assertThat(found.getWriter()).isEqualTo(req.writer()); + softly.assertThat(found.getOccurredAt()).isEqualTo(req.occurredAt()); + }); + } + + @Test + @DisplayName("존재하지 않는 이벤트 UUID면 404 예외가 발생한다.") + void put_fail_byEventNotFound() { + // given + OrganizationEventUpdateRequest updateReq = new OrganizationEventUpdateRequest( + "x", "y", "z", LocalDate.now() + ); + + // then + assertThatThrownBy(() -> + organizationEventService.put(UUID.randomUUID(), updateReq)) + .isInstanceOf(WikiException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.ORGANIZATION_EVENT_NOT_FOUND); + } + } + + @Nested + @DisplayName("조직 이벤트 삭제 시") + class Delete { + + @Test + @DisplayName("정상적으로 삭제된다.") + void delete_success_byExistingEvent() { + // given + OrganizationDocument orgDoc = OrganizationDocumentFixture.createDefault(); + organizationDocumentRepository.save(orgDoc); + + OrganizationEventCreateRequest createReq = new OrganizationEventCreateRequest( + "분기 워크숍", + "OKR 점검", + "밍트", + LocalDate.now(), + orgDoc.getUuid() + ); + UUID eventUuid = organizationEventService.post(createReq).organizationEventUuid(); + + // when + organizationEventService.delete(eventUuid); + + // then + assertSoftly(softly -> { + softly.assertThat(organizationEventRepository.findByUuid(eventUuid)).isEmpty(); + }); } - @Nested - @DisplayName("조직 이벤트 삭제 시") - class Delete { - - @Test - @DisplayName("정상적으로 삭제된다.") - void delete_success() { - // given - OrganizationDocument orgDoc = OrganizationDocumentFixture.createDefault(); - organizationDocumentRepository.save(orgDoc); - - OrganizationEventCreateRequest createReq = new OrganizationEventCreateRequest( - "분기 워크숍", - "OKR 점검", - "밍트", - LocalDate.now(), - orgDoc.getUuid() - ); - UUID eventUuid = organizationEventService.post(createReq).organizationEventUuid(); - - // when - organizationEventService.delete(eventUuid); - - // then - assertSoftly(softly -> { - softly.assertThat(organizationEventRepository.findByUuid(eventUuid)).isEmpty(); - }); - } - - @Test - @DisplayName("존재하지 않는 이벤트 UUID면 404 예외가 발생한다.") - void delete_fail_eventNotFound() { - // then - assertThatThrownBy(() -> organizationEventService.delete(UUID.randomUUID())) - .isInstanceOf(WikiException.class) - .hasFieldOrPropertyWithValue("errorCode", ErrorCode.ORGANIZATION_EVENT_NOT_FOUND); - } + @Test + @DisplayName("존재하지 않는 이벤트 UUID면 404 예외가 발생한다.") + void delete_fail_byEventNotFound() { + // then + assertThatThrownBy(() -> organizationEventService.delete(UUID.randomUUID())) + .isInstanceOf(WikiException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.ORGANIZATION_EVENT_NOT_FOUND); } } }