From 55ae6db004f24a5893a32c21296bdea688818c18 Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Tue, 14 Jan 2025 00:54:16 +0900 Subject: [PATCH 01/15] =?UTF-8?q?refactor:=20memory=20->=20category=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory/controller/CategoryController.java | 94 ++++++++++++++ .../memory/controller/CategoryDtoMapper.java | 89 ++++++++++++++ .../docs/CategoryControllerDocs.java | 115 ++++++++++++++++++ .../service/dto/request/CategoryRequest.java | 47 +++++++ .../dto/response/CategoryDetailResponse.java | 49 ++++++++ .../dto/response/CategoryIdResponse.java | 10 ++ .../dto/response/CategoryNameResponse.java | 17 +++ .../dto/response/CategoryNameResponses.java | 18 +++ .../dto/response/CategoryResponse.java | 35 ++++++ .../dto/response/CategoryResponses.java | 18 +++ .../dto/response/StaccatoResponse.java | 25 ++++ 11 files changed, 517 insertions(+) create mode 100644 backend/src/main/java/com/staccato/memory/controller/CategoryController.java create mode 100644 backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java create mode 100644 backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java create mode 100644 backend/src/main/java/com/staccato/memory/service/dto/request/CategoryRequest.java create mode 100644 backend/src/main/java/com/staccato/memory/service/dto/response/CategoryDetailResponse.java create mode 100644 backend/src/main/java/com/staccato/memory/service/dto/response/CategoryIdResponse.java create mode 100644 backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponse.java create mode 100644 backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponses.java create mode 100644 backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponse.java create mode 100644 backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponses.java create mode 100644 backend/src/main/java/com/staccato/memory/service/dto/response/StaccatoResponse.java diff --git a/backend/src/main/java/com/staccato/memory/controller/CategoryController.java b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java new file mode 100644 index 000000000..f53c4506d --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java @@ -0,0 +1,94 @@ +package com.staccato.memory.controller; + +import java.net.URI; +import java.time.LocalDate; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; + +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.staccato.config.auth.LoginMember; +import com.staccato.config.log.annotation.Trace; +import com.staccato.member.domain.Member; +import com.staccato.memory.controller.docs.CategoryControllerDocs; +import com.staccato.memory.service.MemoryService; +import com.staccato.memory.service.dto.request.CategoryRequest; +import com.staccato.memory.service.dto.response.CategoryDetailResponse; +import com.staccato.memory.service.dto.response.CategoryIdResponse; +import com.staccato.memory.service.dto.response.CategoryNameResponses; +import com.staccato.memory.service.dto.response.CategoryResponses; +import com.staccato.memory.service.dto.response.MemoryDetailResponse; +import com.staccato.memory.service.dto.response.MemoryIdResponse; +import com.staccato.memory.service.dto.response.MemoryNameResponses; +import com.staccato.memory.service.dto.response.MemoryResponses; + +import lombok.RequiredArgsConstructor; + +@Trace +@Validated +@RestController +@RequestMapping("/categories") +@RequiredArgsConstructor +public class CategoryController implements CategoryControllerDocs { + private final MemoryService memoryService; + + @PostMapping("/v2") + public ResponseEntity createCategory( + @Valid @RequestBody CategoryRequest categoryRequest, + @LoginMember Member member + ) { + MemoryIdResponse memoryIdResponse = memoryService.createMemory(CategoryDtoMapper.toMemoryRequest(categoryRequest), member); + return ResponseEntity.created(URI.create("/categories/" + memoryIdResponse.memoryId())).body(CategoryDtoMapper.toCategoryIdResponse(memoryIdResponse)); + } + + @GetMapping("/v2") + public ResponseEntity readAllCategories(@LoginMember Member member) { + MemoryResponses memoryResponses = memoryService.readAllMemories(member); + return ResponseEntity.ok(CategoryDtoMapper.toCategoryResponses(memoryResponses)); + } + + @GetMapping("/v2/candidates") + public ResponseEntity readAllCandidateCategories( + @LoginMember Member member, + @RequestParam(value = "currentDate") LocalDate currentDate + ) { + MemoryNameResponses memoryNameResponses = memoryService.readAllMemoriesIncludingDate(member, currentDate); + return ResponseEntity.ok(CategoryDtoMapper.toCategoryNameResponses(memoryNameResponses)); + } + + @GetMapping("/v2/{categoryId}") + public ResponseEntity readCategory( + @LoginMember Member member, + @PathVariable @Min(value = 1L, message = "추억 식별자는 양수로 이루어져야 합니다.") long categoryId) { + MemoryDetailResponse memoryDetailResponse = memoryService.readMemoryById(categoryId, member); + return ResponseEntity.ok(CategoryDtoMapper.toCategoryDetailResponse(memoryDetailResponse)); + } + + @PutMapping(path = "/v2/{categoryId}") + public ResponseEntity updateCategory( + @PathVariable @Min(value = 1L, message = "추억 식별자는 양수로 이루어져야 합니다.") long categoryId, + @Valid @RequestBody CategoryRequest categoryRequest, + @LoginMember Member member) { + memoryService.updateMemory(CategoryDtoMapper.toMemoryRequest(categoryRequest), categoryId, member); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/v2/{categoryId}") + public ResponseEntity deleteCategory( + @PathVariable @Min(value = 1L, message = "추억 식별자는 양수로 이루어져야 합니다.") long categoryId, + @LoginMember Member member) { + memoryService.deleteMemory(categoryId, member); + return ResponseEntity.ok().build(); + } +} diff --git a/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java b/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java new file mode 100644 index 000000000..14d5b3d0d --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java @@ -0,0 +1,89 @@ +package com.staccato.memory.controller; + +import java.util.List; + +import com.staccato.memory.service.dto.request.CategoryRequest; +import com.staccato.memory.service.dto.request.MemoryRequest; +import com.staccato.memory.service.dto.response.CategoryDetailResponse; +import com.staccato.memory.service.dto.response.CategoryIdResponse; +import com.staccato.memory.service.dto.response.CategoryNameResponse; +import com.staccato.memory.service.dto.response.CategoryNameResponses; +import com.staccato.memory.service.dto.response.CategoryResponse; +import com.staccato.memory.service.dto.response.CategoryResponses; +import com.staccato.memory.service.dto.response.MemoryDetailResponse; +import com.staccato.memory.service.dto.response.MemoryIdResponse; +import com.staccato.memory.service.dto.response.MemoryNameResponse; +import com.staccato.memory.service.dto.response.MemoryNameResponses; +import com.staccato.memory.service.dto.response.MemoryResponse; +import com.staccato.memory.service.dto.response.MemoryResponses; +import com.staccato.memory.service.dto.response.MomentResponse; +import com.staccato.memory.service.dto.response.StaccatoResponse; + +public class CategoryDtoMapper { + public static MemoryRequest toMemoryRequest(CategoryRequest categoryRequest) { + return new MemoryRequest( + categoryRequest.categoryThumbnailUrl(), + categoryRequest.categoryTitle(), + categoryRequest.description(), + categoryRequest.startAt(), + categoryRequest.endAt() + ); + } + + public static CategoryIdResponse toCategoryIdResponse(MemoryIdResponse memoryIdResponse) { + return new CategoryIdResponse(memoryIdResponse.memoryId()); + } + + public static CategoryResponse toCategoryResponse(MemoryResponse memoryResponse) { + return new CategoryResponse( + memoryResponse.memoryId(), + memoryResponse.memoryThumbnailUrl(), + memoryResponse.memoryTitle(), + memoryResponse.startAt(), + memoryResponse.endAt() + ); + } + + public static CategoryResponses toCategoryResponses(MemoryResponses memoryResponses) { + List categoryResponses = memoryResponses.memories().stream() + .map(CategoryDtoMapper::toCategoryResponse) + .toList(); + return new CategoryResponses(categoryResponses); + } + + public static CategoryNameResponse toCategoryNameResponse(MemoryNameResponse memoryNameResponse) { + return new CategoryNameResponse(memoryNameResponse.memoryId(), memoryNameResponse.memoryTitle()); + } + + public static CategoryNameResponses toCategoryNameResponses(MemoryNameResponses memoryNameResponses) { + List categoryNameResponses = memoryNameResponses.memories().stream() + .map(CategoryDtoMapper::toCategoryNameResponse) + .toList(); + return new CategoryNameResponses(categoryNameResponses); + } + + public static StaccatoResponse toStaccatoResponse(MomentResponse momentResponse) { + return new StaccatoResponse( + momentResponse.momentId(), + momentResponse.momentTitle(), + momentResponse.momentImageUrl(), + momentResponse.visitedAt() + ); + } + + public static CategoryDetailResponse toCategoryDetailResponse(MemoryDetailResponse memoryDetailResponse) { + List staccatoResponses = memoryDetailResponse.moments().stream() + .map(CategoryDtoMapper::toStaccatoResponse) + .toList(); + return new CategoryDetailResponse( + memoryDetailResponse.memoryId(), + memoryDetailResponse.memoryThumbnailUrl(), + memoryDetailResponse.memoryTitle(), + memoryDetailResponse.description(), + memoryDetailResponse.startAt(), + memoryDetailResponse.endAt(), + memoryDetailResponse.mates(), + staccatoResponses + ); + } +} diff --git a/backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java b/backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java new file mode 100644 index 000000000..1f01b6991 --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java @@ -0,0 +1,115 @@ +package com.staccato.memory.controller.docs; + +import java.time.LocalDate; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; + +import org.springframework.http.ResponseEntity; + +import com.staccato.member.domain.Member; +import com.staccato.memory.service.dto.request.CategoryRequest; +import com.staccato.memory.service.dto.response.CategoryDetailResponse; +import com.staccato.memory.service.dto.response.CategoryIdResponse; +import com.staccato.memory.service.dto.response.CategoryNameResponses; +import com.staccato.memory.service.dto.response.CategoryResponses; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Category", description = "Category API") +public interface CategoryControllerDocs { + @Operation(summary = "카테고리 생성", description = "카테고리(썸네일, 제목, 내용, 기간)을 생성합니다.") + @ApiResponses(value = { + @ApiResponse(description = "카테고리 생성 성공", responseCode = "201"), + @ApiResponse(description = """ + <발생 가능한 케이스> + + (1) 필수 값(카테고리 제목)이 누락되었을 때 + + (2) 날짜 형식(yyyy-MM-dd)이 잘못되었을 때 + + (3) 제목이 공백 포함 30자를 초과했을 때 + + (4) 내용이 공백 포함 500자를 초과했을 때 + + (5) 기간 설정이 잘못되었을 때 (시작 날짜와 끝날짜 중 하나만 설정할 수 없음) + + (6) 이미 존재하는 카테고리 이름일 때 + """, + responseCode = "400") + }) + ResponseEntity createCategory( + @Parameter(required = true) @Valid CategoryRequest categoryRequest, + @Parameter(hidden = true) Member member); + + @Operation(summary = "카테고리 목록 조회", description = "사용자의 모든 카테고리 목록을 조회합니다.") + @ApiResponse(description = "카테고리 목록 조회 성공", responseCode = "200") + ResponseEntity readAllCategories(@Parameter(hidden = true) Member member); + + @Operation(summary = "특정 날짜를 포함하는 사용자의 모든 카테고리 목록 조회", description = "특정 날짜를 포함하는 사용자의 모든 카테고리 목록을 조회합니다.") + @ApiResponses(value = { + @ApiResponse(description = "카테고리 목록 조회 성공", responseCode = "200"), + @ApiResponse(description = "입력받은 현재 날짜가 유효하지 않을 때 발생", responseCode = "400") + }) + ResponseEntity readAllCandidateCategories( + @Parameter(hidden = true) Member member, + @Parameter(description = "현재 날짜", example = "2024-08-21") LocalDate currentDate); + + @Operation(summary = "카테고리 조회", description = "사용자의 카테고리을 조회합니다.") + @ApiResponses(value = { + @ApiResponse(description = "카테고리 조회 성공", responseCode = "200"), + @ApiResponse(description = """ + <발생 가능한 케이스> + + (1) 존재하지 않는 카테고리을 조회하려고 했을 때 + + (2) Path Variable 형식이 잘못되었을 때 + """, + responseCode = "400") + }) + ResponseEntity readCategory( + @Parameter(hidden = true) Member member, + @Parameter(description = "카테고리 ID", example = "1") @Min(value = 1L, message = "카테고리 식별자는 양수로 이루어져야 합니다.") long categoryId); + + @Operation(summary = "카테고리 수정", description = "카테고리 정보(썸네일, 제목, 내용, 기간)를 수정합니다.") + @ApiResponses(value = { + @ApiResponse(description = "카테고리 수정 성공", responseCode = "200"), + @ApiResponse(description = """ + <발생 가능한 케이스> + + (1) 필수 값(카테고리 제목)이 누락되었을 때 + + (2) 날짜 형식(yyyy-MM-dd)이 잘못되었을 때 + + (3) 제목이 공백 포함 30자를 초과했을 때 + + (4) 내용이 공백 포함 500자를 초과했을 때 + + (5) 기간 설정이 잘못되었을 때 + + (6) 변경하려는 카테고리 기간이 이미 존재하는 스타카토를 포함하지 않을 때 + + (7) 수정하려는 카테고리이 존재하지 않을 때 + + (8) Path Variable 형식이 잘못되었을 때 + """, + responseCode = "400") + }) + ResponseEntity updateCategory( + @Parameter(description = "카테고리 ID", example = "1") @Min(value = 1L, message = "카테고리 식별자는 양수로 이루어져야 합니다.") long categoryId, + @Parameter(required = true) @Valid CategoryRequest categoryRequest, + @Parameter(hidden = true) Member member); + + @Operation(summary = "카테고리 삭제", description = "사용자의 카테고리을 삭제합니다.") + @ApiResponses(value = { + @ApiResponse(description = "카테고리 삭제 성공", responseCode = "200"), + @ApiResponse(description = "Path Variable 형식이 잘못되었을 때 발생", responseCode = "400") + }) + ResponseEntity deleteCategory( + @Parameter(description = "카테고리 ID", example = "1") @Min(value = 1L, message = "카테고리 식별자는 양수로 이루어져야 합니다.") long categoryId, + @Parameter(hidden = true) Member member); +} diff --git a/backend/src/main/java/com/staccato/memory/service/dto/request/CategoryRequest.java b/backend/src/main/java/com/staccato/memory/service/dto/request/CategoryRequest.java new file mode 100644 index 000000000..bc7b38314 --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/service/dto/request/CategoryRequest.java @@ -0,0 +1,47 @@ +package com.staccato.memory.service.dto.request; + +import java.time.LocalDate; +import java.util.Objects; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +import org.springframework.format.annotation.DateTimeFormat; + +import com.staccato.memory.domain.Memory; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "카테고리를 생성/수정하기 위한 요청 형식입니다.") +public record CategoryRequest( + @Schema(example = "http://example.com/london.png") + String categoryThumbnailUrl, + @Schema(example = "런던 추억") + @NotBlank(message = "카테고리 제목을 입력해주세요.") + @Size(max = 30, message = "제목은 공백 포함 30자 이하로 설정해주세요.") + String categoryTitle, + @Schema(example = "런던 시내 탐방") + @Size(max = 500, message = "내용의 최대 허용 글자수는 공백 포함 500자입니다.") + String description, + @Schema(example = "2024-07-27") + @DateTimeFormat(pattern = "yyyy-MM-dd") + LocalDate startAt, + @Schema(example = "2024-07-29") + @DateTimeFormat(pattern = "yyyy-MM-dd") + LocalDate endAt) { + public CategoryRequest { + if (Objects.nonNull(categoryTitle)) { + categoryTitle = categoryTitle.trim(); + } + } + + public Memory toMemory() { + return Memory.builder() + .thumbnailUrl(categoryThumbnailUrl) + .title(categoryTitle) + .description(description) + .startAt(startAt) + .endAt(endAt) + .build(); + } +} diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryDetailResponse.java b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryDetailResponse.java new file mode 100644 index 000000000..49a141b21 --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryDetailResponse.java @@ -0,0 +1,49 @@ +package com.staccato.memory.service.dto.response; + +import java.time.LocalDate; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.staccato.member.service.dto.response.MemberResponse; +import com.staccato.memory.domain.Memory; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "추억에 대한 응답 형식입니다.") +public record CategoryDetailResponse( + @Schema(example = "1") + Long categoryId, + @Schema(example = "https://example.com/categorys/geumohrm.jpg") + @JsonInclude(JsonInclude.Include.NON_NULL) + String categoryThumbnailUrl, + @Schema(example = "런던 추억") + String categoryTitle, + @Schema(example = "런던 시내 탐방") + @JsonInclude(JsonInclude.Include.NON_NULL) + String description, + @Schema(example = "2024-07-27") + @JsonInclude(JsonInclude.Include.NON_NULL) + LocalDate startAt, + @Schema(example = "2024-07-29") + @JsonInclude(JsonInclude.Include.NON_NULL) + LocalDate endAt, + List mates, + List staccatos +) { + public CategoryDetailResponse(Memory memory, List staccatoResponses) { + this( + memory.getId(), + memory.getThumbnailUrl(), + memory.getTitle(), + memory.getDescription(), + memory.getTerm().getStartAt(), + memory.getTerm().getEndAt(), + toMemberResponses(memory), + staccatoResponses + ); + } + + private static List toMemberResponses(Memory memory) { + return memory.getMates().stream().map(MemberResponse::new).toList(); + } +} diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryIdResponse.java b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryIdResponse.java new file mode 100644 index 000000000..46cccab73 --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryIdResponse.java @@ -0,0 +1,10 @@ +package com.staccato.memory.service.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "카테고리를 생성했을 때에 대한 응답 형식입니다.") +public record CategoryIdResponse( + @Schema(example = "1") + long categoryId +) { +} diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponse.java b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponse.java new file mode 100644 index 000000000..a56b50505 --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponse.java @@ -0,0 +1,17 @@ +package com.staccato.memory.service.dto.response; + +import com.staccato.memory.domain.Memory; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "특정 날짜를 포함하는 카테고리 목록 조회 시 각각의 카테고리에 대한 응답 형식입니다.") +public record CategoryNameResponse( + @Schema(example = "1") + Long categoryId, + @Schema(example = "런던 추억") + String categoryTitle +) { + public CategoryNameResponse(Memory memory) { + this(memory.getId(), memory.getTitle()); + } +} diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponses.java b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponses.java new file mode 100644 index 000000000..4cd16628e --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponses.java @@ -0,0 +1,18 @@ +package com.staccato.memory.service.dto.response; + +import java.util.List; + +import com.staccato.memory.domain.Memory; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "특정 날짜를 포함하는 카테고리 목록 조회 시 반환되는 응답 형식입니다.") +public record CategoryNameResponses( + List memories +) { + public static CategoryNameResponses from(List memories) { + return new CategoryNameResponses(memories.stream() + .map(CategoryNameResponse::new) + .toList()); + } +} diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponse.java b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponse.java new file mode 100644 index 000000000..b639889f6 --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponse.java @@ -0,0 +1,35 @@ +package com.staccato.memory.service.dto.response; + +import java.time.LocalDate; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.staccato.memory.domain.Memory; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "카테고리 목록 조회 시 각각의 카테고리에 대한 응답 형식입니다.") +public record CategoryResponse( + @Schema(example = "1") + Long categoryId, + @Schema(example = "https://example.com/memorys/geumohrm.jpg") + @JsonInclude(JsonInclude.Include.NON_NULL) + String categoryThumbnailUrl, + @Schema(example = "런던 추억") + String categoryTitle, + @Schema(example = "2024-07-27") + @JsonInclude(JsonInclude.Include.NON_NULL) + LocalDate startAt, + @Schema(example = "2024-07-29") + @JsonInclude(JsonInclude.Include.NON_NULL) + LocalDate endAt +) { + public CategoryResponse(Memory memory) { + this( + memory.getId(), + memory.getThumbnailUrl(), + memory.getTitle(), + memory.getTerm().getStartAt(), + memory.getTerm().getEndAt() + ); + } +} diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponses.java b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponses.java new file mode 100644 index 000000000..dfd8bbb37 --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponses.java @@ -0,0 +1,18 @@ +package com.staccato.memory.service.dto.response; + +import java.util.List; + +import com.staccato.memory.domain.Memory; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "추억 목록 조회 시 반환 되는 응답 형식입니다.") +public record CategoryResponses( + List categories +) { + public static CategoryResponses from(List memories) { + return new CategoryResponses(memories.stream() + .map(CategoryResponse::new) + .toList()); + } +} diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/StaccatoResponse.java b/backend/src/main/java/com/staccato/memory/service/dto/response/StaccatoResponse.java new file mode 100644 index 000000000..d6036033b --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/StaccatoResponse.java @@ -0,0 +1,25 @@ +package com.staccato.memory.service.dto.response; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.staccato.moment.domain.Moment; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "카테고리 조회 시 보여주는 스타카토의 정보에 대한 응답 형식입니다.") +public record StaccatoResponse( + @Schema(example = "1") + Long staccatoId, + @Schema(example = "런던 아이") + String staccatoTitle, + @Schema(example = "https://example.com/memorys/london_eye.jpg") + @JsonInclude(JsonInclude.Include.NON_NULL) + String staccatoImageUrl, + @Schema(example = "2024-07-27T11:58:20") + LocalDateTime visitedAt +) { + public StaccatoResponse(Moment moment, String momentImageUrl) { + this(moment.getId(), moment.getTitle(), momentImageUrl, moment.getVisitedAt()); + } +} From 11a41ee65524983d5873e0cee799c18a9fe40bd2 Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Tue, 14 Jan 2025 12:12:05 +0900 Subject: [PATCH 02/15] =?UTF-8?q?refactor:=20memory=20->=20category=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/staccato/memory/controller/CategoryController.java | 6 +++--- .../com/staccato/memory/controller/CategoryDtoMapper.java | 2 +- .../memory/service/dto/response/CategoryNameResponses.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/staccato/memory/controller/CategoryController.java b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java index f53c4506d..7f82ac2ec 100644 --- a/backend/src/main/java/com/staccato/memory/controller/CategoryController.java +++ b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java @@ -70,14 +70,14 @@ public ResponseEntity readAllCandidateCategories( @GetMapping("/v2/{categoryId}") public ResponseEntity readCategory( @LoginMember Member member, - @PathVariable @Min(value = 1L, message = "추억 식별자는 양수로 이루어져야 합니다.") long categoryId) { + @PathVariable @Min(value = 1L, message = "카테고리 식별자는 양수로 이루어져야 합니다.") long categoryId) { MemoryDetailResponse memoryDetailResponse = memoryService.readMemoryById(categoryId, member); return ResponseEntity.ok(CategoryDtoMapper.toCategoryDetailResponse(memoryDetailResponse)); } @PutMapping(path = "/v2/{categoryId}") public ResponseEntity updateCategory( - @PathVariable @Min(value = 1L, message = "추억 식별자는 양수로 이루어져야 합니다.") long categoryId, + @PathVariable @Min(value = 1L, message = "카테고리 식별자는 양수로 이루어져야 합니다.") long categoryId, @Valid @RequestBody CategoryRequest categoryRequest, @LoginMember Member member) { memoryService.updateMemory(CategoryDtoMapper.toMemoryRequest(categoryRequest), categoryId, member); @@ -86,7 +86,7 @@ public ResponseEntity updateCategory( @DeleteMapping("/v2/{categoryId}") public ResponseEntity deleteCategory( - @PathVariable @Min(value = 1L, message = "추억 식별자는 양수로 이루어져야 합니다.") long categoryId, + @PathVariable @Min(value = 1L, message = "카테고리 식별자는 양수로 이루어져야 합니다.") long categoryId, @LoginMember Member member) { memoryService.deleteMemory(categoryId, member); return ResponseEntity.ok().build(); diff --git a/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java b/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java index 14d5b3d0d..4a6dde62e 100644 --- a/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java +++ b/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java @@ -65,7 +65,7 @@ public static CategoryNameResponses toCategoryNameResponses(MemoryNameResponses public static StaccatoResponse toStaccatoResponse(MomentResponse momentResponse) { return new StaccatoResponse( momentResponse.momentId(), - momentResponse.momentTitle(), + momentResponse.staccatoTitle(), momentResponse.momentImageUrl(), momentResponse.visitedAt() ); diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponses.java b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponses.java index 4cd16628e..b6e35acb4 100644 --- a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponses.java +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponses.java @@ -8,7 +8,7 @@ @Schema(description = "특정 날짜를 포함하는 카테고리 목록 조회 시 반환되는 응답 형식입니다.") public record CategoryNameResponses( - List memories + List categories ) { public static CategoryNameResponses from(List memories) { return new CategoryNameResponses(memories.stream() From 5e2219ca7ddf31251f905fccb3ee518a73241dfa Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Tue, 14 Jan 2025 14:42:24 +0900 Subject: [PATCH 03/15] =?UTF-8?q?test:=20CategoryController=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryControllerTest.java | 450 ++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java diff --git a/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java b/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java new file mode 100644 index 000000000..6b985fbb3 --- /dev/null +++ b/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java @@ -0,0 +1,450 @@ +package com.staccato.memory.controller; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.staccato.auth.service.AuthService; +import com.staccato.exception.ExceptionResponse; +import com.staccato.fixture.Member.MemberFixture; +import com.staccato.fixture.memory.MemoryFixture; +import com.staccato.fixture.memory.MemoryNameResponsesFixture; +import com.staccato.fixture.memory.MemoryRequestFixture; +import com.staccato.fixture.memory.MemoryResponsesFixture; +import com.staccato.fixture.moment.MomentFixture; +import com.staccato.member.domain.Member; +import com.staccato.memory.domain.Memory; +import com.staccato.memory.service.MemoryService; +import com.staccato.memory.service.dto.request.CategoryRequest; +import com.staccato.memory.service.dto.request.MemoryRequest; +import com.staccato.memory.service.dto.response.MemoryDetailResponse; +import com.staccato.memory.service.dto.response.MemoryIdResponse; +import com.staccato.memory.service.dto.response.MemoryNameResponses; +import com.staccato.memory.service.dto.response.MemoryResponses; +import com.staccato.memory.service.dto.response.MomentResponse; + +@WebMvcTest(CategoryController.class) +class CategoryControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private MemoryService memoryService; + @MockBean + private AuthService authService; + + static Stream categoryRequestProvider() { + return Stream.of( + new CategoryRequest(null, "2023 여름 휴가", "친구들과 함께한 여름 휴가 카테고리", LocalDate.of(2023, 7, 1), LocalDate.of(2023, 7, 10)), + new CategoryRequest("https://example.com/memorys/geumohrm.jpg", "2023 여름 휴가", null, LocalDate.of(2023, 7, 1), LocalDate.of(2023, 7, 10)), + new CategoryRequest("https://example.com/memorys/geumohrm.jpg", "2023 여름 휴가", null, null, null) + ); + } + + static Stream invalidCategoryRequestProvider() { + return Stream.of( + Arguments.of( + new CategoryRequest("https://example.com/memorys/geumohrm.jpg", null, "친구들과 함께한 여름 휴가 카테고리", LocalDate.of(2023, 7, 1), LocalDate.of(2023, 7, 10)), + "카테고리 제목을 입력해주세요." + ), + Arguments.of( + new CategoryRequest("https://example.com/memorys/geumohrm.jpg", " ", "친구들과 함께한 여름 휴가 카테고리", LocalDate.of(2023, 7, 1), LocalDate.of(2023, 7, 10)), + "카테고리 제목을 입력해주세요." + ), + Arguments.of( + new CategoryRequest("https://example.com/memorys/geumohrm.jpg", "가".repeat(31), "친구들과 함께한 여름 휴가 카테고리", LocalDate.of(2023, 7, 1), LocalDate.of(2023, 7, 10)), + "제목은 공백 포함 30자 이하로 설정해주세요." + ), + Arguments.of( + new CategoryRequest("https://example.com/memorys/geumohrm.jpg", "2023 여름 휴가", "가".repeat(501), LocalDate.of(2023, 7, 1), LocalDate.of(2023, 7, 10)), + "내용의 최대 허용 글자수는 공백 포함 500자입니다." + ) + ); + } + + @DisplayName("카테고리를 생성하는 요청/응답의 역직렬화/직렬화에 성공한다.") + @Test + void createCategory() throws Exception { + // given + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + String categoryRequest = """ + { + "categoryThumbnailUrl": "https://example.com/memorys/geumohrm.jpg", + "categoryTitle": "2023 여름 휴가", + "description": "친구들과 함께한 여름 휴가 여행", + "startAt": "2023-07-01", + "endAt": "2023-07-10" + } + """; + when(memoryService.createMemory(any(), any())).thenReturn(new MemoryIdResponse(1)); + String expectedResponse = """ + { + "categoryId" : 1 + } + """; + + // when & then + mockMvc.perform(post("/categories/v2") + .contentType(MediaType.APPLICATION_JSON) + .content(categoryRequest) + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isCreated()) + .andExpect(header().string(HttpHeaders.LOCATION, "/categories/1")) + .andExpect(content().json(expectedResponse)); + } + + @DisplayName("기간이 없는 카테고리를 생성하는 요청/응답의 역직렬화/직렬화에 성공한다.") + @Test + void createCategoryWithoutTerm() throws Exception { + // given + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + String categoryRequest = """ + { + "categoryThumbnailUrl": "https://example.com/memorys/geumohrm.jpg", + "categoryTitle": "2023 여름 휴가", + "description": "친구들과 함께한 여름 휴가 여행" + } + """; + when(memoryService.createMemory(any(), any())).thenReturn(new MemoryIdResponse(1)); + String expectedResponse = """ + { + "categoryId" : 1 + } + """; + + // when & then + mockMvc.perform(post("/categories/v2") + .contentType(MediaType.APPLICATION_JSON) + .content(categoryRequest) + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isCreated()) + .andExpect(header().string(HttpHeaders.LOCATION, "/categories/1")) + .andExpect(content().json(expectedResponse)); + } + + @DisplayName("사용자가 선택적으로 카테고리 정보를 입력하면, 새로운 카테고리를 생성한다.") + @ParameterizedTest + @MethodSource("categoryRequestProvider") + void createCategoryWithoutOption(CategoryRequest categoryRequest) throws Exception { + // given + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + when(memoryService.createMemory(any(), any())).thenReturn(new MemoryIdResponse(1)); + + // when & then + mockMvc.perform(post("/categories/v2") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(categoryRequest)) + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isCreated()) + .andExpect(header().string(HttpHeaders.LOCATION, "/categories/1")) + .andExpect(jsonPath("$.categoryId").value(1)); + } + + @DisplayName("사용자가 잘못된 형식으로 정보를 입력하면, 카테고리를 생성할 수 없다.") + @ParameterizedTest + @MethodSource("invalidCategoryRequestProvider") + void failCreateCategory(CategoryRequest categoryRequest, String expectedMessage) throws Exception { + // given + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), expectedMessage); + + // when & then + mockMvc.perform(post("/categories/v2") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(categoryRequest)) + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isBadRequest()) + .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); + } + + @DisplayName("사용자가 모든 카테고리 목록을 조회하는 응답 직렬화에 성공한다.") + @Test + void readAllCategory() throws Exception { + // given + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + Memory memory = MemoryFixture.create(MemberFixture.create()); + MemoryResponses memoryResponses = MemoryResponsesFixture.create(memory); + when(memoryService.readAllMemories(any(Member.class))).thenReturn(memoryResponses); + String expectedResponse = """ + { + "categories": [ + { + "categoryId": null, + "categoryTitle": "2024 여름 휴가", + "categoryThumbnailUrl": "https://example.com/memorys/geumohrm.jpg", + "startAt": "2024-07-01", + "endAt": "2024-07-10" + } + ] + } + """; + + // when & then + mockMvc.perform(get("/categories/v2") + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isOk()) + .andExpect(content().json(expectedResponse)); + } + + @DisplayName("사용자가 기간이 없는 카테고리를 포함한 목록을 조회하는 응답 직렬화에 성공한다.") + @Test + void readAllCategoryWithoutTerm() throws Exception { + // given + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + Memory memory = MemoryFixture.create(MemberFixture.create()); + MemoryResponses memoryResponses = MemoryResponsesFixture.create(memory); + when(memoryService.readAllMemories(any(Member.class))).thenReturn(memoryResponses); + String expectedResponse = """ + { + "categories": [ + { + "categoryId": null, + "categoryTitle": "2024 여름 휴가", + "categoryThumbnailUrl": "https://example.com/memorys/geumohrm.jpg" + } + ] + } + """; + + // when & then + mockMvc.perform(get("/categories/v2") + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isOk()) + .andExpect(content().json(expectedResponse)); + } + + @DisplayName("특정 날짜를 포함하고 있는 모든 카테고리 목록을 조회하는 응답 직렬화에 성공한다.") + @Test + void readAllCategoryIncludingDate() throws Exception { + // given + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + Memory memory = MemoryFixture.create(MemberFixture.create()); + MemoryNameResponses memoryNameResponses = MemoryNameResponsesFixture.create(memory); + when(memoryService.readAllMemoriesIncludingDate(any(Member.class), any())).thenReturn(memoryNameResponses); + String expectedResponse = """ + { + "categories": [ + { + "categoryId": null, + "categoryTitle": "2024 여름 휴가" + } + ] + } + """; + + // when & then + mockMvc.perform(get("/categories/v2/candidates") + .header(HttpHeaders.AUTHORIZATION, "token") + .param("currentDate", LocalDate.now().toString())) + .andExpect(status().isOk()) + .andExpect(content().json(expectedResponse)); + } + + @DisplayName("잘못된 날짜 형식으로 카테고리 목록 조회를 시도하면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"2024.07.01", "2024-07", "2024", "a"}) + void cannotReadAllCategoryByInvalidDateFormat(String currentDate) throws Exception { + // given + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "올바르지 않은 쿼리 스트링 형식입니다."); + + // when & then + mockMvc.perform(get("/categories/v2/candidates") + .header(HttpHeaders.AUTHORIZATION, "token") + .param("currentDate", currentDate)) + .andExpect(status().isBadRequest()) + .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); + } + + @DisplayName("사용자가 특정 카테고리를 조회하는 응답 직렬화에 한다.") + @Test + void readCategory() throws Exception { + // given + long categoryId = 1; + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + Memory memory = MemoryFixture.create(MemberFixture.create()); + MomentResponse momentResponse = new MomentResponse(MomentFixture.create(memory), "image.jpg"); + MemoryDetailResponse memoryDetailResponse = new MemoryDetailResponse(memory, List.of(momentResponse)); + when(memoryService.readMemoryById(anyLong(), any(Member.class))).thenReturn(memoryDetailResponse); + String expectedResponse = """ + { + "categoryId": null, + "categoryThumbnailUrl": "https://example.com/memorys/geumohrm.jpg", + "categoryTitle": "2024 여름 휴가", + "startAt": "2024-07-01", + "endAt": "2024-07-10", + "description": "친구들과 함께한 여름 휴가 추억", + "mates": [ + { + "memberId": null, + "nickname": "staccato" + } + ], + "staccatos": [ + { + "staccatoId": null, + "staccatoTitle": "staccatoTitle", + "staccatoImageUrl": "image.jpg", + "visitedAt": "2024-07-01T10:00:00" + } + ] + } + """; + + // when & then + mockMvc.perform(get("/categories/v2/{categoryId}", categoryId) + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isOk()) + .andExpect(content().json(expectedResponse)); + } + + + @DisplayName("사용자가 기간이 없는 특정 카테고리를 조회하는 응답 직렬화에 한다.") + @Test + void readCategoryWithoutTerm() throws Exception { + // given + long categoryId = 1; + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + Memory memory = MemoryFixture.create(null, null, MemberFixture.create()); + MomentResponse momentResponse = new MomentResponse(MomentFixture.create(memory), "image.jpg"); + MemoryDetailResponse memoryDetailResponse = new MemoryDetailResponse(memory, List.of(momentResponse)); + when(memoryService.readMemoryById(anyLong(), any(Member.class))).thenReturn(memoryDetailResponse); + String expectedResponse = """ + { + "categoryId": null, + "categoryThumbnailUrl": "https://example.com/memorys/geumohrm.jpg", + "categoryTitle": "2024 여름 휴가", + "description": "친구들과 함께한 여름 휴가 추억", + "mates": [ + { + "memberId": null, + "nickname": "staccato" + } + ], + "staccatos": [ + { + "staccatoId": null, + "staccatoTitle": "staccatoTitle", + "staccatoImageUrl": "image.jpg", + "visitedAt": "2024-07-01T10:00:00" + } + ] + } + """; + + // when & then + mockMvc.perform(get("/categories/v2/{categoryId}", categoryId) + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isOk()) + .andExpect(content().json(expectedResponse)); + } + + @DisplayName("적합한 경로변수와 데이터를 통해 스타카토 수정에 성공한다.") + @ParameterizedTest + @MethodSource("categoryRequestProvider") + void updateCategory(CategoryRequest categoryRequest) throws Exception { + // given + long categoryId = 1L; + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + + // when & then + mockMvc.perform(put("/categories/v2/{categoryId}", categoryId) + .header(HttpHeaders.AUTHORIZATION, "token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(categoryRequest))) + .andExpect(status().isOk()); + } + + @DisplayName("사용자가 잘못된 형식으로 정보를 입력하면, 카테고리를 수정할 수 없다.") + @ParameterizedTest + @MethodSource("invalidCategoryRequestProvider") + void failUpdateCategory(CategoryRequest categoryRequest, String expectedMessage) throws Exception { + // given + long categoryId = 1L; + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), expectedMessage); + + // when & then + mockMvc.perform(put("/categories/v2/{categoryId}", categoryId) + .header(HttpHeaders.AUTHORIZATION, "token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(categoryRequest))) + .andExpect(status().isBadRequest()) + .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); + } + + // 수정 + @DisplayName("적합하지 않은 경로변수의 경우 카테고리 수정에 실패한다.") + @ParameterizedTest + @MethodSource("categoryRequestProvider") + void failUpdateCategoryByInvalidPath(CategoryRequest categoryRequest) throws Exception { + // given + long categoryId = 0L; + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "카테고리 식별자는 양수로 이루어져야 합니다."); + + // when & then + mockMvc.perform(put("/categories/v2/{categoryId}", categoryId) + .header(HttpHeaders.AUTHORIZATION, "token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(categoryRequest))) + .andExpect(status().isBadRequest()) + .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); + } + + @DisplayName("사용자가 카테고리 식별자로 카테고리를 삭제한다.") + @Test + void deleteCategory() throws Exception { + // given + long categoryId = 1; + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + + // when & then + mockMvc.perform(delete("/categories/v2/{categoryId}", categoryId) + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isOk()); + } + + @DisplayName("사용자가 잘못된 카테고리 식별자로 삭제하려고 하면 예외가 발생한다.") + @Test + void cannotDeleteCategoryByInvalidId() throws Exception { + // given + long invalidId = 0; + ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "카테고리 식별자는 양수로 이루어져야 합니다."); + + // when & then + mockMvc.perform(delete("/categories/v2/{categoryId}", invalidId) + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isBadRequest()) + .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); + } +} From e7654311bbf8c3fe11c9f934049102261cd3d12f Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Tue, 14 Jan 2025 14:42:54 +0900 Subject: [PATCH 04/15] =?UTF-8?q?refactor:=20v2=EB=A5=BC=20RequestMapping?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=95=9C=EB=B2=88=EC=97=90=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory/controller/CategoryController.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/com/staccato/memory/controller/CategoryController.java b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java index 7f82ac2ec..4961ac8da 100644 --- a/backend/src/main/java/com/staccato/memory/controller/CategoryController.java +++ b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java @@ -38,12 +38,12 @@ @Trace @Validated @RestController -@RequestMapping("/categories") +@RequestMapping("/categories/v2") @RequiredArgsConstructor public class CategoryController implements CategoryControllerDocs { private final MemoryService memoryService; - @PostMapping("/v2") + @PostMapping public ResponseEntity createCategory( @Valid @RequestBody CategoryRequest categoryRequest, @LoginMember Member member @@ -52,13 +52,13 @@ public ResponseEntity createCategory( return ResponseEntity.created(URI.create("/categories/" + memoryIdResponse.memoryId())).body(CategoryDtoMapper.toCategoryIdResponse(memoryIdResponse)); } - @GetMapping("/v2") + @GetMapping public ResponseEntity readAllCategories(@LoginMember Member member) { MemoryResponses memoryResponses = memoryService.readAllMemories(member); return ResponseEntity.ok(CategoryDtoMapper.toCategoryResponses(memoryResponses)); } - @GetMapping("/v2/candidates") + @GetMapping("/candidates") public ResponseEntity readAllCandidateCategories( @LoginMember Member member, @RequestParam(value = "currentDate") LocalDate currentDate @@ -67,7 +67,7 @@ public ResponseEntity readAllCandidateCategories( return ResponseEntity.ok(CategoryDtoMapper.toCategoryNameResponses(memoryNameResponses)); } - @GetMapping("/v2/{categoryId}") + @GetMapping("/{categoryId}") public ResponseEntity readCategory( @LoginMember Member member, @PathVariable @Min(value = 1L, message = "카테고리 식별자는 양수로 이루어져야 합니다.") long categoryId) { @@ -75,7 +75,7 @@ public ResponseEntity readCategory( return ResponseEntity.ok(CategoryDtoMapper.toCategoryDetailResponse(memoryDetailResponse)); } - @PutMapping(path = "/v2/{categoryId}") + @PutMapping(path = "/{categoryId}") public ResponseEntity updateCategory( @PathVariable @Min(value = 1L, message = "카테고리 식별자는 양수로 이루어져야 합니다.") long categoryId, @Valid @RequestBody CategoryRequest categoryRequest, @@ -84,7 +84,7 @@ public ResponseEntity updateCategory( return ResponseEntity.ok().build(); } - @DeleteMapping("/v2/{categoryId}") + @DeleteMapping("/{categoryId}") public ResponseEntity deleteCategory( @PathVariable @Min(value = 1L, message = "카테고리 식별자는 양수로 이루어져야 합니다.") long categoryId, @LoginMember Member member) { From 0e7ffdb4b00b89a5ab434862c5dfeeb6f52264f3 Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Tue, 14 Jan 2025 14:43:16 +0900 Subject: [PATCH 05/15] =?UTF-8?q?refactor:=20moment=20->=20staccato=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moment/controller/StaccatoController.java | 102 ++++++++++++++ .../moment/controller/StaccatoDtoMapper.java | 66 +++++++++ .../docs/StaccatoControllerDocs.java | 128 ++++++++++++++++++ .../service/dto/request/StaccatoRequest.java | 71 ++++++++++ .../dto/response/StaccatoDetailResponse.java | 63 +++++++++ .../dto/response/StaccatoIdResponse.java | 10 ++ .../response/StaccatoLocationResponse.java | 21 +++ .../response/StaccatoLocationResponses.java | 9 ++ 8 files changed, 470 insertions(+) create mode 100644 backend/src/main/java/com/staccato/moment/controller/StaccatoController.java create mode 100644 backend/src/main/java/com/staccato/moment/controller/StaccatoDtoMapper.java create mode 100644 backend/src/main/java/com/staccato/moment/controller/docs/StaccatoControllerDocs.java create mode 100644 backend/src/main/java/com/staccato/moment/service/dto/request/StaccatoRequest.java create mode 100644 backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoDetailResponse.java create mode 100644 backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoIdResponse.java create mode 100644 backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoLocationResponse.java create mode 100644 backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoLocationResponses.java diff --git a/backend/src/main/java/com/staccato/moment/controller/StaccatoController.java b/backend/src/main/java/com/staccato/moment/controller/StaccatoController.java new file mode 100644 index 000000000..118024851 --- /dev/null +++ b/backend/src/main/java/com/staccato/moment/controller/StaccatoController.java @@ -0,0 +1,102 @@ +package com.staccato.moment.controller; + +import java.net.URI; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; + +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.staccato.config.auth.LoginMember; +import com.staccato.config.log.annotation.Trace; +import com.staccato.member.domain.Member; +import com.staccato.moment.controller.docs.MomentControllerDocs; +import com.staccato.moment.controller.docs.StaccatoControllerDocs; +import com.staccato.moment.service.MomentService; +import com.staccato.moment.service.dto.request.FeelingRequest; +import com.staccato.moment.service.dto.request.MomentRequest; +import com.staccato.moment.service.dto.request.StaccatoRequest; +import com.staccato.moment.service.dto.response.MomentDetailResponse; +import com.staccato.moment.service.dto.response.MomentIdResponse; +import com.staccato.moment.service.dto.response.MomentLocationResponses; +import com.staccato.moment.service.dto.response.StaccatoDetailResponse; +import com.staccato.moment.service.dto.response.StaccatoIdResponse; +import com.staccato.moment.service.dto.response.StaccatoLocationResponses; + +import lombok.RequiredArgsConstructor; + +@Trace +@RestController +@RequestMapping("/staccatos/v2") +@RequiredArgsConstructor +@Validated +public class StaccatoController implements StaccatoControllerDocs { + private final MomentService momentService; + + @PostMapping + public ResponseEntity createStaccato( + @LoginMember Member member, + @Valid @RequestBody StaccatoRequest staccatoRequest + ) { + MomentRequest momentRequest = StaccatoDtoMapper.toMomentRequest(staccatoRequest); + MomentIdResponse momentIdResponse = momentService.createMoment(momentRequest, member); + StaccatoIdResponse staccatoIdResponse = StaccatoDtoMapper.toStaccatoIdResponse(momentIdResponse); + return ResponseEntity.created(URI.create("/staccatos/" + staccatoIdResponse.staccatoId())) + .body(staccatoIdResponse); + } + + @GetMapping + public ResponseEntity readAllStaccato(@LoginMember Member member) { + MomentLocationResponses momentLocationResponses = momentService.readAllMoment(member); + StaccatoLocationResponses staccatoLocationResponses = StaccatoDtoMapper.toStaccatoLocationResponses(momentLocationResponses); + return ResponseEntity.ok().body(staccatoLocationResponses); + } + + @GetMapping("/{staccatoId}") + public ResponseEntity readStaccatoById( + @LoginMember Member member, + @PathVariable @Min(value = 1L, message = "스타카토 식별자는 양수로 이루어져야 합니다.") long staccatoId) { + MomentDetailResponse momentDetailResponse = momentService.readMomentById(staccatoId, member); + StaccatoDetailResponse staccatoDetailResponse = StaccatoDtoMapper.toStaccatoDetailResponse(momentDetailResponse); + return ResponseEntity.ok().body(staccatoDetailResponse); + } + + @PutMapping(path = "/{staccatoId}") + public ResponseEntity updateStaccatoById( + @LoginMember Member member, + @PathVariable @Min(value = 1L, message = "스타카토 식별자는 양수로 이루어져야 합니다.") long staccatoId, + @Valid @RequestBody StaccatoRequest staccatoRequest + ) { + MomentRequest momentRequest = StaccatoDtoMapper.toMomentRequest(staccatoRequest); + momentService.updateMomentById(staccatoId, momentRequest, member); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/{staccatoId}") + public ResponseEntity deleteStaccatoById( + @LoginMember Member member, + @PathVariable @Min(value = 1L, message = "스타카토 식별자는 양수로 이루어져야 합니다.") long staccatoId + ) { + momentService.deleteMomentById(staccatoId, member); + return ResponseEntity.ok().build(); + } + + @PostMapping("/{staccatoId}/feeling") + public ResponseEntity updateStaccatoFeelingById( + @LoginMember Member member, + @PathVariable @Min(value = 1L, message = "스타카토 식별자는 양수로 이루어져야 합니다.") long staccatoId, + @Valid @RequestBody FeelingRequest feelingRequest + ) { + momentService.updateMomentFeelingById(staccatoId, member, feelingRequest); + return ResponseEntity.ok().build(); + } +} diff --git a/backend/src/main/java/com/staccato/moment/controller/StaccatoDtoMapper.java b/backend/src/main/java/com/staccato/moment/controller/StaccatoDtoMapper.java new file mode 100644 index 000000000..9e82290d6 --- /dev/null +++ b/backend/src/main/java/com/staccato/moment/controller/StaccatoDtoMapper.java @@ -0,0 +1,66 @@ +package com.staccato.moment.controller; + +import java.util.List; + +import com.staccato.moment.service.dto.request.MomentRequest; +import com.staccato.moment.service.dto.request.StaccatoRequest; +import com.staccato.moment.service.dto.response.MomentDetailResponse; +import com.staccato.moment.service.dto.response.MomentIdResponse; +import com.staccato.moment.service.dto.response.MomentLocationResponse; +import com.staccato.moment.service.dto.response.MomentLocationResponses; +import com.staccato.moment.service.dto.response.StaccatoDetailResponse; +import com.staccato.moment.service.dto.response.StaccatoIdResponse; +import com.staccato.moment.service.dto.response.StaccatoLocationResponse; +import com.staccato.moment.service.dto.response.StaccatoLocationResponses; + +public class StaccatoDtoMapper { + public static MomentRequest toMomentRequest(StaccatoRequest staccatoRequest) { + return new MomentRequest( + staccatoRequest.staccatoTitle(), + staccatoRequest.placeName(), + staccatoRequest.address(), + staccatoRequest.latitude(), + staccatoRequest.longitude(), + staccatoRequest.visitedAt(), + staccatoRequest.categoryId(), + staccatoRequest.staccatoImageUrls() + ); + } + + public static StaccatoIdResponse toStaccatoIdResponse(MomentIdResponse momentIdResponse) { + return new StaccatoIdResponse(momentIdResponse.momentId()); + } + + public static StaccatoLocationResponse toStaccatoLocationResponse(MomentLocationResponse momentLocationResponse) { + return new StaccatoLocationResponse( + momentLocationResponse.momentId(), + momentLocationResponse.latitude(), + momentLocationResponse.longitude() + ); + } + + public static StaccatoLocationResponses toStaccatoLocationResponses(MomentLocationResponses momentLocationResponses) { + List staccatoLocationResponses = momentLocationResponses.momentLocationResponses().stream() + .map(StaccatoDtoMapper::toStaccatoLocationResponse) + .toList(); + return new StaccatoLocationResponses(staccatoLocationResponses); + } + + public static StaccatoDetailResponse toStaccatoDetailResponse(MomentDetailResponse momentDetailResponse) { + return new StaccatoDetailResponse( + momentDetailResponse.memoryId(), + momentDetailResponse.momentId(), + momentDetailResponse.memoryTitle(), + momentDetailResponse.startAt(), + momentDetailResponse.endAt(), + momentDetailResponse.staccatoTitle(), + momentDetailResponse.momentImageUrls(), + momentDetailResponse.visitedAt(), + momentDetailResponse.feeling(), + momentDetailResponse.placeName(), + momentDetailResponse.address(), + momentDetailResponse.latitude(), + momentDetailResponse.longitude() + ); + } +} diff --git a/backend/src/main/java/com/staccato/moment/controller/docs/StaccatoControllerDocs.java b/backend/src/main/java/com/staccato/moment/controller/docs/StaccatoControllerDocs.java new file mode 100644 index 000000000..7d7189671 --- /dev/null +++ b/backend/src/main/java/com/staccato/moment/controller/docs/StaccatoControllerDocs.java @@ -0,0 +1,128 @@ +package com.staccato.moment.controller.docs; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; + +import com.staccato.member.domain.Member; +import com.staccato.moment.service.dto.request.FeelingRequest; +import com.staccato.moment.service.dto.request.MomentRequest; +import com.staccato.moment.service.dto.request.StaccatoRequest; +import com.staccato.moment.service.dto.response.MomentDetailResponse; +import com.staccato.moment.service.dto.response.MomentIdResponse; +import com.staccato.moment.service.dto.response.MomentLocationResponses; +import com.staccato.moment.service.dto.response.StaccatoDetailResponse; +import com.staccato.moment.service.dto.response.StaccatoIdResponse; +import com.staccato.moment.service.dto.response.StaccatoLocationResponses; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Staccato", description = "Staccato API") +public interface StaccatoControllerDocs { + @Operation(summary = "스타카토 생성", description = "스타카토를 생성합니다.") + @ApiResponses(value = { + @ApiResponse(description = "스타카토 생성 성공", responseCode = "201"), + @ApiResponse(description = """ + <발생 가능한 케이스> + + (1) 필수 값(사진을 제외한 모든 값)이 누락되었을 때 + + (2) 존재하지 않는 staccatoId일 때 + + (3) 올바르지 않은 날짜 형식일 때 + + (4) 사진이 5장을 초과했을 때 + + (5) 스타카토 날짜가 카테고리 기간에 포함되지 않을 때 + """, + responseCode = "400") + }) + ResponseEntity createStaccato( + @Parameter(hidden = true) Member member, + @Parameter(required = true) @Valid StaccatoRequest staccatoRequest + ); + + @Operation(summary = "스타카토 목록 조회", description = "스타카토 목록을 조회합니다.") + @ApiResponse(description = "스타카토 목록 조회 성공", responseCode = "200") + ResponseEntity readAllStaccato(@Parameter(hidden = true) Member member); + + @Operation(summary = "스타카토 조회", description = "스타카토를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(description = "스타카토 조회 성공", responseCode = "200"), + @ApiResponse(description = """ + <발생 가능한 케이스> + + (1) 조회하려는 스타카토가 존재하지 않을 때 + + (2) Path Variable 형식이 잘못되었을 때 + """, + responseCode = "400") + }) + ResponseEntity readStaccatoById( + @Parameter(hidden = true) Member member, + @Parameter(description = "스타카토 ID", example = "1") @PathVariable @Min(value = 1L, message = "스타카토 식별자는 양수로 이루어져야 합니다.") long staccatoId); + + @Operation(summary = "스타카토 수정", description = "스타카토를 수정합니다.") + @ApiResponses(value = { + @ApiResponse(description = "스타카토 수정 성공", responseCode = "200"), + @ApiResponse(description = """ + <발생 가능한 케이스> + + (1) 수정하려는 스타카토가 존재하지 않을 때 + + (2) Path Variable 형식이 잘못되었을 때 + + (3) 필수 값(사진을 제외한 모든 값)이 누락되었을 때 + + (4) 존재하지 않는 staccatoId일 때 + + (5) 올바르지 않은 날짜 형식일 때 + + (6) 사진이 5장을 초과했을 때 + + (7) 스타카토 날짜가 카테고리 기간에 포함되지 않을 때 + """, + responseCode = "400") + }) + ResponseEntity updateStaccatoById( + @Parameter(hidden = true) Member member, + @Parameter(description = "스타카토 ID", example = "1") @PathVariable @Min(value = 1L, message = "스타카토 식별자는 양수로 이루어져야 합니다.") long staccatoId, + @Parameter(required = true) @Valid StaccatoRequest staccatoRequest); + + @Operation(summary = "스타카토 삭제", description = "스타카토를 삭제합니다.") + @ApiResponses(value = { + @ApiResponse(description = "스타카토 삭제에 성공했거나 해당 스타카토가 존재하지 않는 경우", responseCode = "200"), + @ApiResponse(description = "스타카토 식별자에 양수가 아닌 값을 기입했을 경우", responseCode = "400") + }) + ResponseEntity deleteStaccatoById( + @Parameter(hidden = true) Member member, + @Parameter(description = "스타카토 ID", example = "1") @Min(value = 1L, message = "스타카토 식별자는 양수로 이루어져야 합니다.") long staccatoId + ); + + @Operation(summary = "스타카토 기분 선택", description = "스타카토의 기분을 선택합니다.") + @ApiResponses(value = { + @ApiResponse(description = "스타카토 기분 선택 성공", responseCode = "200"), + @ApiResponse(description = """ + <발생 가능한 케이스> + + (1) 조회하려는 스타카토가 존재하지 않을 때 + + (2) Path Variable 형식이 잘못되었을 때 + + (3) RequestBody 형식이 잘못되었을 때 + + (4) 요청한 기분 표현을 찾을 수 없을 때 + """, + responseCode = "400") + }) + ResponseEntity updateStaccatoFeelingById( + @Parameter(hidden = true) Member member, + @Parameter(description = "스타카토 ID", example = "1") @Min(value = 1L, message = "스타카토 식별자는 양수로 이루어져야 합니다.") long staccatoId, + @Parameter(required = true) @Valid FeelingRequest feelingRequest); +} diff --git a/backend/src/main/java/com/staccato/moment/service/dto/request/StaccatoRequest.java b/backend/src/main/java/com/staccato/moment/service/dto/request/StaccatoRequest.java new file mode 100644 index 000000000..8dba379b4 --- /dev/null +++ b/backend/src/main/java/com/staccato/moment/service/dto/request/StaccatoRequest.java @@ -0,0 +1,71 @@ +package com.staccato.moment.service.dto.request; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import org.springframework.format.annotation.DateTimeFormat; + +import com.staccato.memory.domain.Memory; +import com.staccato.moment.domain.Moment; +import com.staccato.moment.domain.MomentImages; + +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "스타카토 생성 시 요청 형식입니다. 단, 멀티파트로 보내는 사진 파일은 여기에 포함되지 않습니다.") +public record StaccatoRequest( + @Schema(example = "재밌었던 런던 박물관에서의 기억") + @NotBlank(message = "스타카토 제목을 입력해주세요.") + @Size(max = 30, message = "스타카토 제목은 공백 포함 30자 이하로 설정해주세요.") + String staccatoTitle, + @Schema(example = "Great Russell St, London WC1B 3DG") + @NotNull(message = "장소 이름을 입력해주세요.") + String placeName, + @Schema(example = "British Museum") + @NotNull(message = "스타카토의 주소를 입력해주세요.") + String address, + @Schema(example = "51.51978412729915") + @NotNull(message = "스타카토의 위도를 입력해주세요.") + BigDecimal latitude, + @Schema(example = "-0.12712788587027796") + @NotNull(message = "스타카토의 경도를 입력해주세요.") + BigDecimal longitude, + @Schema(example = "2024-07-27") + @NotNull(message = "스타카토 날짜를 입력해주세요.") + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + LocalDateTime visitedAt, + @Schema(example = "1") + @NotNull(message = "추억을 선택해주세요.") + @Min(value = 1L, message = "카테고리 식별자는 양수로 이루어져야 합니다.") + long categoryId, + @ArraySchema( + arraySchema = @Schema(example = "[\"https://example.com/images/namsan_tower.jpg\", \"https://example.com/images/namsan_tower2.jpg\"]")) + @Size(max = 5, message = "사진은 5장까지만 추가할 수 있어요.") + List staccatoImageUrls +) { + public StaccatoRequest { + if (Objects.nonNull(staccatoTitle)) { + staccatoTitle = staccatoTitle.trim(); + } + } + + public Moment toMoment(Memory memory) { + return Moment.builder() + .visitedAt(visitedAt) + .title(staccatoTitle) + .placeName(placeName) + .latitude(latitude) + .longitude(longitude) + .address(address) + .memory(memory) + .momentImages(new MomentImages(staccatoImageUrls)) + .build(); + } +} diff --git a/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoDetailResponse.java b/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoDetailResponse.java new file mode 100644 index 000000000..3de5d48fe --- /dev/null +++ b/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoDetailResponse.java @@ -0,0 +1,63 @@ +package com.staccato.moment.service.dto.response; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.staccato.moment.domain.Moment; +import com.staccato.moment.domain.MomentImage; + +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "스타카토를 조회했을 때 응답 형식입니다.") +public record StaccatoDetailResponse( + @Schema(example = "1") + long staccatoId, + @Schema(example = "1") + long categoryId, + @Schema(example = "2024 서울 투어") + String categoryTitle, + @Schema(example = "2024-06-30") + @JsonInclude(JsonInclude.Include.NON_NULL) + LocalDate startAt, + @Schema(example = "2024-07-04") + @JsonInclude(JsonInclude.Include.NON_NULL) + LocalDate endAt, + @Schema(example = "즐거웠던 남산에서의 기억") + String staccatoTitle, + @ArraySchema(arraySchema = @Schema(example = "[\"https://example.com/images/namsan_tower.jpg\", \"https://example.com/images/namsan_tower2.jpg\"]")) + List staccatoImageUrls, + @Schema(example = "2021-11-08T11:58:20") + LocalDateTime visitedAt, + @Schema(example = "happy") + String feeling, + @Schema(example = "남산서울타워") + String placeName, + @Schema(example = "서울 용산구 남산공원길 105") + String address, + @Schema(example = "51.51978412729915") + BigDecimal latitude, + @Schema(example = "-0.12712788587027796") + BigDecimal longitude +) { + public StaccatoDetailResponse(Moment moment) { + this( + moment.getId(), + moment.getMemory().getId(), + moment.getMemory().getTitle(), + moment.getMemory().getTerm().getStartAt(), + moment.getMemory().getTerm().getEndAt(), + moment.getTitle(), + moment.getMomentImages().getImages().stream().map(MomentImage::getImageUrl).toList(), + moment.getVisitedAt(), + moment.getFeeling().getValue(), + moment.getSpot().getPlaceName(), + moment.getSpot().getAddress(), + moment.getSpot().getLatitude(), + moment.getSpot().getLongitude() + ); + } +} diff --git a/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoIdResponse.java b/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoIdResponse.java new file mode 100644 index 000000000..56bae698a --- /dev/null +++ b/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoIdResponse.java @@ -0,0 +1,10 @@ +package com.staccato.moment.service.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "스타카토 생성 시 응답 형식입니다.") +public record StaccatoIdResponse( + @Schema(example = "1") + long staccatoId +) { +} diff --git a/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoLocationResponse.java b/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoLocationResponse.java new file mode 100644 index 000000000..cf4cbdc51 --- /dev/null +++ b/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoLocationResponse.java @@ -0,0 +1,21 @@ +package com.staccato.moment.service.dto.response; + +import java.math.BigDecimal; + +import com.staccato.moment.domain.Moment; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "스타카토 목록 중 하나의 스타카토에 해당하는 응답입니다.") +public record StaccatoLocationResponse( + @Schema(example = "1") + long staccatoId, + @Schema(example = "51.51978412729915") + BigDecimal latitude, + @Schema(example = "-0.12712788587027796") + BigDecimal longitude) { + + public StaccatoLocationResponse(Moment moment) { + this(moment.getId(), moment.getSpot().getLatitude(), moment.getSpot().getLongitude()); + } +} diff --git a/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoLocationResponses.java b/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoLocationResponses.java new file mode 100644 index 000000000..10d793ec3 --- /dev/null +++ b/backend/src/main/java/com/staccato/moment/service/dto/response/StaccatoLocationResponses.java @@ -0,0 +1,9 @@ +package com.staccato.moment.service.dto.response; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "스타카토 목록에 해당하는 응답입니다.") +public record StaccatoLocationResponses(List staccatoLocationResponses) { +} From 7c9bec25ee68e543c175cc98aff6a992a922cedd Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Tue, 14 Jan 2025 14:49:45 +0900 Subject: [PATCH 06/15] =?UTF-8?q?test:=20StaccatoController=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StaccatoControllerTest.java | 374 ++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java diff --git a/backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java b/backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java new file mode 100644 index 000000000..30fec7e8d --- /dev/null +++ b/backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java @@ -0,0 +1,374 @@ +package com.staccato.moment.controller; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.staccato.auth.service.AuthService; +import com.staccato.exception.ExceptionResponse; +import com.staccato.fixture.Member.MemberFixture; +import com.staccato.fixture.moment.MomentDetailResponseFixture; +import com.staccato.fixture.moment.MomentLocationResponsesFixture; +import com.staccato.member.domain.Member; +import com.staccato.moment.service.MomentService; +import com.staccato.moment.service.dto.request.FeelingRequest; +import com.staccato.moment.service.dto.request.MomentRequest; +import com.staccato.moment.service.dto.request.StaccatoRequest; +import com.staccato.moment.service.dto.response.MomentDetailResponse; +import com.staccato.moment.service.dto.response.MomentIdResponse; +import com.staccato.moment.service.dto.response.MomentLocationResponses; + +@WebMvcTest(controllers = StaccatoController.class) +class StaccatoControllerTest { + @Autowired + private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean + private MomentService momentService; + @MockBean + private AuthService authService; + + static Stream invalidStaccatoRequestProvider() { + return Stream.of( + Arguments.of( + new StaccatoRequest("staccatoTitle", "placeName", "address", BigDecimal.ONE, BigDecimal.ONE, LocalDateTime.of(2023, 7, 1, 10, 0), 0L, List.of("https://example.com/images/namsan_tower.jpg")), + "카테고리 식별자는 양수로 이루어져야 합니다." + ), + Arguments.of( + new StaccatoRequest(null, "placeName", "address", BigDecimal.ONE, BigDecimal.ONE, LocalDateTime.of(2023, 7, 1, 10, 0), 1L, List.of("https://example.com/images/namsan_tower.jpg")), + "스타카토 제목을 입력해주세요." + ), + Arguments.of( + new StaccatoRequest(" ", "placeName", "address", BigDecimal.ONE, BigDecimal.ONE, LocalDateTime.of(2023, 7, 1, 10, 0), 1L, List.of("https://example.com/images/namsan_tower.jpg")), + "스타카토 제목을 입력해주세요." + ), + Arguments.of( + new StaccatoRequest("staccatoTitle", null, "address", BigDecimal.ONE, BigDecimal.ONE, LocalDateTime.of(2023, 7, 1, 10, 0), 1L, List.of("https://example.com/images/namsan_tower.jpg")), + "장소 이름을 입력해주세요." + ), + Arguments.of( + new StaccatoRequest("가".repeat(31), "placeName", "address", BigDecimal.ONE, BigDecimal.ONE, LocalDateTime.of(2023, 7, 1, 10, 0), 1L, List.of("https://example.com/images/namsan_tower.jpg")), + "스타카토 제목은 공백 포함 30자 이하로 설정해주세요." + ), + Arguments.of( + new StaccatoRequest("staccatoTitle", "placeName", "address", null, BigDecimal.ONE, LocalDateTime.of(2023, 7, 1, 10, 0), 1L, List.of("https://example.com/images/namsan_tower.jpg")), + "스타카토의 위도를 입력해주세요." + ), + Arguments.of( + new StaccatoRequest("staccatoTitle", "placeName", "address", BigDecimal.ONE, null, LocalDateTime.of(2023, 7, 1, 10, 0), 1L, List.of("https://example.com/images/namsan_tower.jpg")), + "스타카토의 경도를 입력해주세요." + ), + Arguments.of( + new StaccatoRequest("staccatoTitle", "placeName", null, BigDecimal.ONE, BigDecimal.ONE, LocalDateTime.of(2023, 7, 1, 10, 0), 1L, List.of("https://example.com/images/namsan_tower.jpg")), + "스타카토의 주소를 입력해주세요." + ), + Arguments.of( + new StaccatoRequest("staccatoTitle", "placeName", "address", BigDecimal.ONE, BigDecimal.ONE, null, 1L, List.of("https://example.com/images/namsan_tower.jpg")), + "스타카토 날짜를 입력해주세요." + ) + ); + } + + @DisplayName("스타카토 생성 요청/응답을 역직렬화/직렬화하는 것을 성공한다.") + @Test + void createStaccatoWithValidRequest() throws Exception { + // given + String staccatoRequest = """ + { + "staccatoTitle": "staccatoTitle", + "placeName": "placeName", + "address": "address", + "latitude": 1.0, + "longitude": 1.0, + "visitedAt": "2023-07-01T10:00:00", + "categoryId": 1, + "staccatoImageUrls": ["https://example.com/images/namsan_tower.jpg"] + } + """; + String staccatoIdResponse = """ + { + "staccatoId": 1 + } + """; + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + when(momentService.createMoment(any(MomentRequest.class), any(Member.class))).thenReturn(new MomentIdResponse(1L)); + + // when & then + mockMvc.perform(post("/staccatos/v2") + .header(HttpHeaders.AUTHORIZATION, "token") + .contentType(MediaType.APPLICATION_JSON) + .content(staccatoRequest)) + .andExpect(status().isCreated()) + .andExpect(header().string(HttpHeaders.LOCATION, "/staccatos/1")) + .andExpect(content().json(staccatoIdResponse)); + } + + @DisplayName("올바르지 않은 날짜 형식으로 스타카토 생성을 요청하면 예외가 발생한다.") + @Test + void failCreateStaccatoWithInvalidVisitedAt() throws Exception { + // given + String staccatoRequest = """ + { + "staccatoTitle": "재밌었던 런던 박물관에서의 기억", + "placeName": "British Museum", + "address": "Great Russell St, London WC1B 3DG", + "latitude": 51.51978412729915, + "longitude": -0.12712788587027796, + "visitedAt": "2024/07/27T10:00:00", + "categoryId": 1 + } + """; + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + when(momentService.createMoment(any(MomentRequest.class), any(Member.class))).thenReturn(new MomentIdResponse(1L)); + + // when & then + mockMvc.perform(post("/staccatos/v2") + .header(HttpHeaders.AUTHORIZATION, "token") + .contentType(MediaType.APPLICATION_JSON) + .content(staccatoRequest)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("요청 본문을 읽을 수 없습니다. 올바른 형식으로 데이터를 제공해주세요.")); + } + + @DisplayName("사용자가 잘못된 요청 형식으로 정보를 입력하면, 스타카토를 생성할 수 없다.") + @ParameterizedTest + @MethodSource("invalidStaccatoRequestProvider") + void failCreateStaccato(StaccatoRequest staccatoRequest, String expectedMessage) throws Exception { + // given + ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), expectedMessage); + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + when(momentService.createMoment(any(MomentRequest.class), any(Member.class))).thenReturn(new MomentIdResponse(1L)); + + // when & then + mockMvc.perform(post("/staccatos/v2") + .header(HttpHeaders.AUTHORIZATION, "token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(staccatoRequest))) + .andExpect(status().isBadRequest()) + .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); + } + + @DisplayName("스타카토 목록 조회에 성공한다.") + @Test + void readAllStaccato() throws Exception { + // given + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + MomentLocationResponses responses = MomentLocationResponsesFixture.create(); + when(momentService.readAllMoment(any(Member.class))).thenReturn(responses); + String expectedResponse = """ + { + "staccatoLocationResponses": [ + { + "staccatoId": 1, + "latitude": 1, + "longitude": 0 + }, + { + "staccatoId": 2, + "latitude": 1, + "longitude": 0 + }, + { + "staccatoId": 3, + "latitude": 1, + "longitude": 0 + } + ] + } + """; + + // when & then + mockMvc.perform(get("/staccatos/v2") + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isOk()) + .andExpect(content().json(expectedResponse)); + } + + @DisplayName("적합한 경로변수를 통해 스타카토 조회에 성공한다.") + @Test + void readStaccatoById() throws Exception { + // given + long staccatoId = 1L; + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + MomentDetailResponse response = MomentDetailResponseFixture.create(staccatoId, LocalDateTime.parse("2021-11-08T11:58:20")); + when(momentService.readMomentById(anyLong(), any(Member.class))).thenReturn(response); + String expectedResponse = """ + { + "staccatoId": 1, + "categoryId": 1, + "categoryTitle": "memoryTitle", + "startAt": "2024-06-30", + "endAt": "2024-07-04", + "staccatoTitle": "staccatoTitle", + "staccatoImageUrls": ["https://example1.com.jpg"], + "visitedAt": "2021-11-08T11:58:20", + "feeling": "happy", + "placeName": "placeName", + "address": "address", + "latitude": 37.7749, + "longitude": -122.4194 + } + """; + + // when & then + mockMvc.perform(get("/staccatos/v2/{staccatoId}", staccatoId) + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isOk()) + .andExpect(content().json(expectedResponse)); + } + + @DisplayName("적합하지 않은 경로변수의 경우 스타카토 조회에 실패한다.") + @Test + void failReadStaccatoById() throws Exception { + // given + ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "스타카토 식별자는 양수로 이루어져야 합니다."); + + // when & then + mockMvc.perform(get("/staccatos/v2/{staccatoId}", 0)) + .andExpect(status().isBadRequest()) + .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); + } + + @DisplayName("적합한 경로변수를 통해 스타카토 수정에 성공한다.") + @Test + void updateStaccatoById() throws Exception { + // given + long staccatoId = 1L; + String staccatoRequest = """ + { + "staccatoTitle": "staccatoTitle", + "placeName": "placeName", + "address": "address", + "latitude": 1.0, + "longitude": 1.0, + "visitedAt": "2023-07-01T10:00:00", + "categoryId": 1, + "staccatoImageUrls": [ + "https://example.com/images/namsan_tower.jpg" + ] + } + """; + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + + // when & then + mockMvc.perform(put("/staccatos/v2/{staccatoId}", staccatoId) + .header(HttpHeaders.AUTHORIZATION, "token") + .contentType(MediaType.APPLICATION_JSON) + .content(staccatoRequest)) + .andExpect(status().isOk()); + } + + @DisplayName("추가하려는 사진이 5장이 넘는다면 스타카토 수정에 실패한다.") + @Test + void failUpdateStaccatoByImagesSize() throws Exception { + // given + long staccatoId = 1L; + ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "사진은 5장까지만 추가할 수 있어요."); + StaccatoRequest staccatoRequest = new StaccatoRequest("staccatoTitle", "placeName", "address", BigDecimal.ONE, BigDecimal.ONE, LocalDateTime.now(), 1L, + List.of("https://example.com/images/namsan_tower1.jpg", + "https://example.com/images/namsan_tower2.jpg", + "https://example.com/images/namsan_tower3.jpg", + "https://example.com/images/namsan_tower4.jpg", + "https://example.com/images/namsan_tower5.jpg", + "https://example.com/images/namsan_tower6.jpg")); + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + + // when & then + mockMvc.perform(put("/staccatos/v2/{StaccatoId}", staccatoId) + .header(HttpHeaders.AUTHORIZATION, "token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(staccatoRequest))) + .andExpect(status().isBadRequest()) + .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); + } + + @DisplayName("적합하지 않은 경로변수의 경우 스타카토 수정에 실패한다.") + @Test + void failUpdateStaccatoById() throws Exception { + // given + long staccatoId = 0L; + StaccatoRequest staccatoRequest = new StaccatoRequest("staccatoTitle", "placeName", "address", BigDecimal.ONE, BigDecimal.ONE, LocalDateTime.of(2023, 7, 1, 10, 0), 1L, List.of("https://example.com/images/namsan_tower.jpg")); + ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "스타카토 식별자는 양수로 이루어져야 합니다."); + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + + // when & then + mockMvc.perform(put("/staccatos/v2/{staccatoId}", staccatoId) + .header(HttpHeaders.AUTHORIZATION, "token") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(staccatoRequest))) + .andExpect(status().isBadRequest()) + .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); + } + + @DisplayName("스타카토를 삭제한다.") + @Test + void deleteStaccatoById() throws Exception { + // given + long staccatoId = 1L; + when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); + + // when & then + mockMvc.perform(delete("/staccatos/v2/{staccatoId}", staccatoId) + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isOk()); + } + + @DisplayName("양수가 아닌 id로 스타카토를 삭제할 수 없다.") + @Test + void failDeleteStaccatoById() throws Exception { + // given + long staccatoId = 0L; + ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "스타카토 식별자는 양수로 이루어져야 합니다."); + + // when & then + mockMvc.perform(delete("/staccatos/v2/{staccatoId}", staccatoId) + .header(HttpHeaders.AUTHORIZATION, "token")) + .andExpect(status().isBadRequest()) + .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); + } + + @DisplayName("기분 선택을 하지 않은 경우 기분 수정에 실패한다.") + @Test + void failUpdateStaccatoFeelingById() throws Exception { + // given + long staccatoId = 1L; + FeelingRequest feelingRequest = new FeelingRequest(null); + ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "기분 값을 입력해주세요."); + + // when & then + mockMvc.perform(post("/staccatos/v2/{staccatoId}/feeling", staccatoId) + .header(HttpHeaders.AUTHORIZATION, "token") + .content(objectMapper.writeValueAsString(feelingRequest))) + .andExpect(status().isBadRequest()) + .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); + } +} From 5469469301213ce1eb6fa1da7e9f5817777b7d04 Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Tue, 14 Jan 2025 14:54:15 +0900 Subject: [PATCH 07/15] =?UTF-8?q?refactor:=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/staccato/memory/controller/CategoryControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java b/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java index 6b985fbb3..4beea42c3 100644 --- a/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java +++ b/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java @@ -402,7 +402,6 @@ void failUpdateCategory(CategoryRequest categoryRequest, String expectedMessage) .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); } - // 수정 @DisplayName("적합하지 않은 경로변수의 경우 카테고리 수정에 실패한다.") @ParameterizedTest @MethodSource("categoryRequestProvider") From 0e17c6c7faf9c3204247bf379a5adb4964044023 Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Tue, 14 Jan 2025 14:59:33 +0900 Subject: [PATCH 08/15] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=95=EC=95=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../staccato/moment/controller/StaccatoController.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/com/staccato/moment/controller/StaccatoController.java b/backend/src/main/java/com/staccato/moment/controller/StaccatoController.java index 118024851..7cc3fe9c2 100644 --- a/backend/src/main/java/com/staccato/moment/controller/StaccatoController.java +++ b/backend/src/main/java/com/staccato/moment/controller/StaccatoController.java @@ -47,8 +47,7 @@ public ResponseEntity createStaccato( @LoginMember Member member, @Valid @RequestBody StaccatoRequest staccatoRequest ) { - MomentRequest momentRequest = StaccatoDtoMapper.toMomentRequest(staccatoRequest); - MomentIdResponse momentIdResponse = momentService.createMoment(momentRequest, member); + MomentIdResponse momentIdResponse = momentService.createMoment(StaccatoDtoMapper.toMomentRequest(staccatoRequest), member); StaccatoIdResponse staccatoIdResponse = StaccatoDtoMapper.toStaccatoIdResponse(momentIdResponse); return ResponseEntity.created(URI.create("/staccatos/" + staccatoIdResponse.staccatoId())) .body(staccatoIdResponse); @@ -57,8 +56,7 @@ public ResponseEntity createStaccato( @GetMapping public ResponseEntity readAllStaccato(@LoginMember Member member) { MomentLocationResponses momentLocationResponses = momentService.readAllMoment(member); - StaccatoLocationResponses staccatoLocationResponses = StaccatoDtoMapper.toStaccatoLocationResponses(momentLocationResponses); - return ResponseEntity.ok().body(staccatoLocationResponses); + return ResponseEntity.ok().body(StaccatoDtoMapper.toStaccatoLocationResponses(momentLocationResponses)); } @GetMapping("/{staccatoId}") @@ -66,8 +64,7 @@ public ResponseEntity readStaccatoById( @LoginMember Member member, @PathVariable @Min(value = 1L, message = "스타카토 식별자는 양수로 이루어져야 합니다.") long staccatoId) { MomentDetailResponse momentDetailResponse = momentService.readMomentById(staccatoId, member); - StaccatoDetailResponse staccatoDetailResponse = StaccatoDtoMapper.toStaccatoDetailResponse(momentDetailResponse); - return ResponseEntity.ok().body(staccatoDetailResponse); + return ResponseEntity.ok().body(StaccatoDtoMapper.toStaccatoDetailResponse(momentDetailResponse)); } @PutMapping(path = "/{staccatoId}") From 14970186b40b405c77f300f7f8e95dc4186f138a Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Tue, 14 Jan 2025 16:43:45 +0900 Subject: [PATCH 09/15] =?UTF-8?q?refactor:=20v2=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory/controller/CategoryController.java | 2 +- .../moment/controller/StaccatoController.java | 2 +- .../controller/CategoryControllerTest.java | 30 +++++++++---------- .../controller/StaccatoControllerTest.java | 24 +++++++-------- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/backend/src/main/java/com/staccato/memory/controller/CategoryController.java b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java index 4961ac8da..216fff6d9 100644 --- a/backend/src/main/java/com/staccato/memory/controller/CategoryController.java +++ b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java @@ -38,7 +38,7 @@ @Trace @Validated @RestController -@RequestMapping("/categories/v2") +@RequestMapping("/categories") @RequiredArgsConstructor public class CategoryController implements CategoryControllerDocs { private final MemoryService memoryService; diff --git a/backend/src/main/java/com/staccato/moment/controller/StaccatoController.java b/backend/src/main/java/com/staccato/moment/controller/StaccatoController.java index 7cc3fe9c2..da564c5cd 100644 --- a/backend/src/main/java/com/staccato/moment/controller/StaccatoController.java +++ b/backend/src/main/java/com/staccato/moment/controller/StaccatoController.java @@ -36,7 +36,7 @@ @Trace @RestController -@RequestMapping("/staccatos/v2") +@RequestMapping("/staccatos") @RequiredArgsConstructor @Validated public class StaccatoController implements StaccatoControllerDocs { diff --git a/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java b/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java index 4beea42c3..06f16e822 100644 --- a/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java +++ b/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java @@ -113,7 +113,7 @@ void createCategory() throws Exception { """; // when & then - mockMvc.perform(post("/categories/v2") + mockMvc.perform(post("/categories") .contentType(MediaType.APPLICATION_JSON) .content(categoryRequest) .header(HttpHeaders.AUTHORIZATION, "token")) @@ -142,7 +142,7 @@ void createCategoryWithoutTerm() throws Exception { """; // when & then - mockMvc.perform(post("/categories/v2") + mockMvc.perform(post("/categories") .contentType(MediaType.APPLICATION_JSON) .content(categoryRequest) .header(HttpHeaders.AUTHORIZATION, "token")) @@ -160,7 +160,7 @@ void createCategoryWithoutOption(CategoryRequest categoryRequest) throws Excepti when(memoryService.createMemory(any(), any())).thenReturn(new MemoryIdResponse(1)); // when & then - mockMvc.perform(post("/categories/v2") + mockMvc.perform(post("/categories") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(categoryRequest)) .header(HttpHeaders.AUTHORIZATION, "token")) @@ -178,7 +178,7 @@ void failCreateCategory(CategoryRequest categoryRequest, String expectedMessage) ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), expectedMessage); // when & then - mockMvc.perform(post("/categories/v2") + mockMvc.perform(post("/categories") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(categoryRequest)) .header(HttpHeaders.AUTHORIZATION, "token")) @@ -209,7 +209,7 @@ void readAllCategory() throws Exception { """; // when & then - mockMvc.perform(get("/categories/v2") + mockMvc.perform(get("/categories") .header(HttpHeaders.AUTHORIZATION, "token")) .andExpect(status().isOk()) .andExpect(content().json(expectedResponse)); @@ -236,7 +236,7 @@ void readAllCategoryWithoutTerm() throws Exception { """; // when & then - mockMvc.perform(get("/categories/v2") + mockMvc.perform(get("/categories") .header(HttpHeaders.AUTHORIZATION, "token")) .andExpect(status().isOk()) .andExpect(content().json(expectedResponse)); @@ -262,7 +262,7 @@ void readAllCategoryIncludingDate() throws Exception { """; // when & then - mockMvc.perform(get("/categories/v2/candidates") + mockMvc.perform(get("/categories/candidates") .header(HttpHeaders.AUTHORIZATION, "token") .param("currentDate", LocalDate.now().toString())) .andExpect(status().isOk()) @@ -278,7 +278,7 @@ void cannotReadAllCategoryByInvalidDateFormat(String currentDate) throws Excepti ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "올바르지 않은 쿼리 스트링 형식입니다."); // when & then - mockMvc.perform(get("/categories/v2/candidates") + mockMvc.perform(get("/categories/candidates") .header(HttpHeaders.AUTHORIZATION, "token") .param("currentDate", currentDate)) .andExpect(status().isBadRequest()) @@ -321,7 +321,7 @@ void readCategory() throws Exception { """; // when & then - mockMvc.perform(get("/categories/v2/{categoryId}", categoryId) + mockMvc.perform(get("/categories/{categoryId}", categoryId) .header(HttpHeaders.AUTHORIZATION, "token")) .andExpect(status().isOk()) .andExpect(content().json(expectedResponse)); @@ -362,7 +362,7 @@ void readCategoryWithoutTerm() throws Exception { """; // when & then - mockMvc.perform(get("/categories/v2/{categoryId}", categoryId) + mockMvc.perform(get("/categories/{categoryId}", categoryId) .header(HttpHeaders.AUTHORIZATION, "token")) .andExpect(status().isOk()) .andExpect(content().json(expectedResponse)); @@ -377,7 +377,7 @@ void updateCategory(CategoryRequest categoryRequest) throws Exception { when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); // when & then - mockMvc.perform(put("/categories/v2/{categoryId}", categoryId) + mockMvc.perform(put("/categories/{categoryId}", categoryId) .header(HttpHeaders.AUTHORIZATION, "token") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(categoryRequest))) @@ -394,7 +394,7 @@ void failUpdateCategory(CategoryRequest categoryRequest, String expectedMessage) ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), expectedMessage); // when & then - mockMvc.perform(put("/categories/v2/{categoryId}", categoryId) + mockMvc.perform(put("/categories/{categoryId}", categoryId) .header(HttpHeaders.AUTHORIZATION, "token") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(categoryRequest))) @@ -412,7 +412,7 @@ void failUpdateCategoryByInvalidPath(CategoryRequest categoryRequest) throws Exc ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "카테고리 식별자는 양수로 이루어져야 합니다."); // when & then - mockMvc.perform(put("/categories/v2/{categoryId}", categoryId) + mockMvc.perform(put("/categories/{categoryId}", categoryId) .header(HttpHeaders.AUTHORIZATION, "token") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(categoryRequest))) @@ -428,7 +428,7 @@ void deleteCategory() throws Exception { when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); // when & then - mockMvc.perform(delete("/categories/v2/{categoryId}", categoryId) + mockMvc.perform(delete("/categories/{categoryId}", categoryId) .header(HttpHeaders.AUTHORIZATION, "token")) .andExpect(status().isOk()); } @@ -441,7 +441,7 @@ void cannotDeleteCategoryByInvalidId() throws Exception { ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "카테고리 식별자는 양수로 이루어져야 합니다."); // when & then - mockMvc.perform(delete("/categories/v2/{categoryId}", invalidId) + mockMvc.perform(delete("/categories/{categoryId}", invalidId) .header(HttpHeaders.AUTHORIZATION, "token")) .andExpect(status().isBadRequest()) .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); diff --git a/backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java b/backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java index 30fec7e8d..56dddb540 100644 --- a/backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java +++ b/backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java @@ -123,7 +123,7 @@ void createStaccatoWithValidRequest() throws Exception { when(momentService.createMoment(any(MomentRequest.class), any(Member.class))).thenReturn(new MomentIdResponse(1L)); // when & then - mockMvc.perform(post("/staccatos/v2") + mockMvc.perform(post("/staccatos") .header(HttpHeaders.AUTHORIZATION, "token") .contentType(MediaType.APPLICATION_JSON) .content(staccatoRequest)) @@ -151,7 +151,7 @@ void failCreateStaccatoWithInvalidVisitedAt() throws Exception { when(momentService.createMoment(any(MomentRequest.class), any(Member.class))).thenReturn(new MomentIdResponse(1L)); // when & then - mockMvc.perform(post("/staccatos/v2") + mockMvc.perform(post("/staccatos") .header(HttpHeaders.AUTHORIZATION, "token") .contentType(MediaType.APPLICATION_JSON) .content(staccatoRequest)) @@ -169,7 +169,7 @@ void failCreateStaccato(StaccatoRequest staccatoRequest, String expectedMessage) when(momentService.createMoment(any(MomentRequest.class), any(Member.class))).thenReturn(new MomentIdResponse(1L)); // when & then - mockMvc.perform(post("/staccatos/v2") + mockMvc.perform(post("/staccatos") .header(HttpHeaders.AUTHORIZATION, "token") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(staccatoRequest))) @@ -207,7 +207,7 @@ void readAllStaccato() throws Exception { """; // when & then - mockMvc.perform(get("/staccatos/v2") + mockMvc.perform(get("/staccatos") .header(HttpHeaders.AUTHORIZATION, "token")) .andExpect(status().isOk()) .andExpect(content().json(expectedResponse)); @@ -240,7 +240,7 @@ void readStaccatoById() throws Exception { """; // when & then - mockMvc.perform(get("/staccatos/v2/{staccatoId}", staccatoId) + mockMvc.perform(get("/staccatos/{staccatoId}", staccatoId) .header(HttpHeaders.AUTHORIZATION, "token")) .andExpect(status().isOk()) .andExpect(content().json(expectedResponse)); @@ -253,7 +253,7 @@ void failReadStaccatoById() throws Exception { ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "스타카토 식별자는 양수로 이루어져야 합니다."); // when & then - mockMvc.perform(get("/staccatos/v2/{staccatoId}", 0)) + mockMvc.perform(get("/staccatos/{staccatoId}", 0)) .andExpect(status().isBadRequest()) .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); } @@ -280,7 +280,7 @@ void updateStaccatoById() throws Exception { when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); // when & then - mockMvc.perform(put("/staccatos/v2/{staccatoId}", staccatoId) + mockMvc.perform(put("/staccatos/{staccatoId}", staccatoId) .header(HttpHeaders.AUTHORIZATION, "token") .contentType(MediaType.APPLICATION_JSON) .content(staccatoRequest)) @@ -303,7 +303,7 @@ void failUpdateStaccatoByImagesSize() throws Exception { when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); // when & then - mockMvc.perform(put("/staccatos/v2/{StaccatoId}", staccatoId) + mockMvc.perform(put("/staccatos/{StaccatoId}", staccatoId) .header(HttpHeaders.AUTHORIZATION, "token") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(staccatoRequest))) @@ -321,7 +321,7 @@ void failUpdateStaccatoById() throws Exception { when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); // when & then - mockMvc.perform(put("/staccatos/v2/{staccatoId}", staccatoId) + mockMvc.perform(put("/staccatos/{staccatoId}", staccatoId) .header(HttpHeaders.AUTHORIZATION, "token") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(staccatoRequest))) @@ -337,7 +337,7 @@ void deleteStaccatoById() throws Exception { when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); // when & then - mockMvc.perform(delete("/staccatos/v2/{staccatoId}", staccatoId) + mockMvc.perform(delete("/staccatos/{staccatoId}", staccatoId) .header(HttpHeaders.AUTHORIZATION, "token")) .andExpect(status().isOk()); } @@ -350,7 +350,7 @@ void failDeleteStaccatoById() throws Exception { ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "스타카토 식별자는 양수로 이루어져야 합니다."); // when & then - mockMvc.perform(delete("/staccatos/v2/{staccatoId}", staccatoId) + mockMvc.perform(delete("/staccatos/{staccatoId}", staccatoId) .header(HttpHeaders.AUTHORIZATION, "token")) .andExpect(status().isBadRequest()) .andExpect(content().json(objectMapper.writeValueAsString(exceptionResponse))); @@ -365,7 +365,7 @@ void failUpdateStaccatoFeelingById() throws Exception { ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST.toString(), "기분 값을 입력해주세요."); // when & then - mockMvc.perform(post("/staccatos/v2/{staccatoId}/feeling", staccatoId) + mockMvc.perform(post("/staccatos/{staccatoId}/feeling", staccatoId) .header(HttpHeaders.AUTHORIZATION, "token") .content(objectMapper.writeValueAsString(feelingRequest))) .andExpect(status().isBadRequest()) From a1110a4713f3df12663fbdefe0c2aea9e8f1e3e9 Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Fri, 17 Jan 2025 13:10:22 +0900 Subject: [PATCH 10/15] =?UTF-8?q?refactor:=20=EC=A0=91=EA=B7=BC=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=9E=90=20private=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory/controller/CategoryDtoMapper.java | 18 +++++++++--------- .../moment/controller/StaccatoDtoMapper.java | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java b/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java index 4a6dde62e..1a695fa05 100644 --- a/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java +++ b/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java @@ -62,15 +62,6 @@ public static CategoryNameResponses toCategoryNameResponses(MemoryNameResponses return new CategoryNameResponses(categoryNameResponses); } - public static StaccatoResponse toStaccatoResponse(MomentResponse momentResponse) { - return new StaccatoResponse( - momentResponse.momentId(), - momentResponse.staccatoTitle(), - momentResponse.momentImageUrl(), - momentResponse.visitedAt() - ); - } - public static CategoryDetailResponse toCategoryDetailResponse(MemoryDetailResponse memoryDetailResponse) { List staccatoResponses = memoryDetailResponse.moments().stream() .map(CategoryDtoMapper::toStaccatoResponse) @@ -86,4 +77,13 @@ public static CategoryDetailResponse toCategoryDetailResponse(MemoryDetailRespon staccatoResponses ); } + + private static StaccatoResponse toStaccatoResponse(MomentResponse momentResponse) { + return new StaccatoResponse( + momentResponse.momentId(), + momentResponse.staccatoTitle(), + momentResponse.momentImageUrl(), + momentResponse.visitedAt() + ); + } } diff --git a/backend/src/main/java/com/staccato/moment/controller/StaccatoDtoMapper.java b/backend/src/main/java/com/staccato/moment/controller/StaccatoDtoMapper.java index 9e82290d6..2431d558a 100644 --- a/backend/src/main/java/com/staccato/moment/controller/StaccatoDtoMapper.java +++ b/backend/src/main/java/com/staccato/moment/controller/StaccatoDtoMapper.java @@ -31,14 +31,6 @@ public static StaccatoIdResponse toStaccatoIdResponse(MomentIdResponse momentIdR return new StaccatoIdResponse(momentIdResponse.momentId()); } - public static StaccatoLocationResponse toStaccatoLocationResponse(MomentLocationResponse momentLocationResponse) { - return new StaccatoLocationResponse( - momentLocationResponse.momentId(), - momentLocationResponse.latitude(), - momentLocationResponse.longitude() - ); - } - public static StaccatoLocationResponses toStaccatoLocationResponses(MomentLocationResponses momentLocationResponses) { List staccatoLocationResponses = momentLocationResponses.momentLocationResponses().stream() .map(StaccatoDtoMapper::toStaccatoLocationResponse) @@ -46,6 +38,14 @@ public static StaccatoLocationResponses toStaccatoLocationResponses(MomentLocati return new StaccatoLocationResponses(staccatoLocationResponses); } + private static StaccatoLocationResponse toStaccatoLocationResponse(MomentLocationResponse momentLocationResponse) { + return new StaccatoLocationResponse( + momentLocationResponse.momentId(), + momentLocationResponse.latitude(), + momentLocationResponse.longitude() + ); + } + public static StaccatoDetailResponse toStaccatoDetailResponse(MomentDetailResponse momentDetailResponse) { return new StaccatoDetailResponse( momentDetailResponse.memoryId(), From 877f50f6670f5be5abffc9857b23ab43a94b8e37 Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Fri, 17 Jan 2025 13:30:52 +0900 Subject: [PATCH 11/15] =?UTF-8?q?refactor:=20swagger=20=EC=84=A4=EB=AA=85?= =?UTF-8?q?=EC=9D=98=20=EC=B6=94=EC=96=B5=20->=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=B9=B4=ED=86=A0=20=EC=B6=94=EA=B0=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory/service/dto/response/CategoryDetailResponse.java | 4 ++-- .../memory/service/dto/response/CategoryNameResponse.java | 2 +- .../memory/service/dto/response/CategoryResponse.java | 2 +- .../memory/service/dto/response/CategoryResponses.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryDetailResponse.java b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryDetailResponse.java index 49a141b21..43dc9262d 100644 --- a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryDetailResponse.java +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryDetailResponse.java @@ -9,14 +9,14 @@ import io.swagger.v3.oas.annotations.media.Schema; -@Schema(description = "추억에 대한 응답 형식입니다.") +@Schema(description = "카테고리에 대한 응답 형식입니다.") public record CategoryDetailResponse( @Schema(example = "1") Long categoryId, @Schema(example = "https://example.com/categorys/geumohrm.jpg") @JsonInclude(JsonInclude.Include.NON_NULL) String categoryThumbnailUrl, - @Schema(example = "런던 추억") + @Schema(example = "런던 여행") String categoryTitle, @Schema(example = "런던 시내 탐방") @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponse.java b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponse.java index a56b50505..dcf6ee407 100644 --- a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponse.java +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryNameResponse.java @@ -8,7 +8,7 @@ public record CategoryNameResponse( @Schema(example = "1") Long categoryId, - @Schema(example = "런던 추억") + @Schema(example = "런던 여행") String categoryTitle ) { public CategoryNameResponse(Memory memory) { diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponse.java b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponse.java index b639889f6..d8927a3ec 100644 --- a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponse.java +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponse.java @@ -14,7 +14,7 @@ public record CategoryResponse( @Schema(example = "https://example.com/memorys/geumohrm.jpg") @JsonInclude(JsonInclude.Include.NON_NULL) String categoryThumbnailUrl, - @Schema(example = "런던 추억") + @Schema(example = "런던 여행") String categoryTitle, @Schema(example = "2024-07-27") @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponses.java b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponses.java index dfd8bbb37..c87028f7c 100644 --- a/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponses.java +++ b/backend/src/main/java/com/staccato/memory/service/dto/response/CategoryResponses.java @@ -6,7 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema; -@Schema(description = "추억 목록 조회 시 반환 되는 응답 형식입니다.") +@Schema(description = "카테고리 목록 조회 시 반환 되는 응답 형식입니다.") public record CategoryResponses( List categories ) { From d42316145fec2a4eb4415c5274422183cbe9bd4b Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Fri, 17 Jan 2025 13:34:17 +0900 Subject: [PATCH 12/15] =?UTF-8?q?refactor:=20swagger=20=EC=84=A4=EB=AA=85?= =?UTF-8?q?=EC=9D=98=20=EC=B6=94=EC=96=B5=20->=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=B9=B4=ED=86=A0=20=EC=B6=94=EA=B0=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../staccato/memory/service/dto/request/CategoryRequest.java | 2 +- .../staccato/moment/service/dto/request/StaccatoRequest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/staccato/memory/service/dto/request/CategoryRequest.java b/backend/src/main/java/com/staccato/memory/service/dto/request/CategoryRequest.java index bc7b38314..b58c94d32 100644 --- a/backend/src/main/java/com/staccato/memory/service/dto/request/CategoryRequest.java +++ b/backend/src/main/java/com/staccato/memory/service/dto/request/CategoryRequest.java @@ -16,7 +16,7 @@ public record CategoryRequest( @Schema(example = "http://example.com/london.png") String categoryThumbnailUrl, - @Schema(example = "런던 추억") + @Schema(example = "런던 여행") @NotBlank(message = "카테고리 제목을 입력해주세요.") @Size(max = 30, message = "제목은 공백 포함 30자 이하로 설정해주세요.") String categoryTitle, diff --git a/backend/src/main/java/com/staccato/moment/service/dto/request/StaccatoRequest.java b/backend/src/main/java/com/staccato/moment/service/dto/request/StaccatoRequest.java index 8dba379b4..6710a4f58 100644 --- a/backend/src/main/java/com/staccato/moment/service/dto/request/StaccatoRequest.java +++ b/backend/src/main/java/com/staccato/moment/service/dto/request/StaccatoRequest.java @@ -42,7 +42,7 @@ public record StaccatoRequest( @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime visitedAt, @Schema(example = "1") - @NotNull(message = "추억을 선택해주세요.") + @NotNull(message = "카테고리를 선택해주세요.") @Min(value = 1L, message = "카테고리 식별자는 양수로 이루어져야 합니다.") long categoryId, @ArraySchema( From 62f3de081f644e1aadb50c5aa847c78d8697752b Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Sun, 19 Jan 2025 19:30:18 +0900 Subject: [PATCH 13/15] =?UTF-8?q?fix:=20=EC=9D=B4=EC=A0=84=20PR=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory/controller/CategoryController.java | 11 ++++++++--- .../docs/CategoryControllerDocs.java | 7 ++++++- .../controller/CategoryControllerTest.java | 18 ++++++++++-------- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/com/staccato/memory/controller/CategoryController.java b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java index 216fff6d9..1b8c39191 100644 --- a/backend/src/main/java/com/staccato/memory/controller/CategoryController.java +++ b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java @@ -10,6 +10,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -24,6 +25,7 @@ import com.staccato.memory.controller.docs.CategoryControllerDocs; import com.staccato.memory.service.MemoryService; import com.staccato.memory.service.dto.request.CategoryRequest; +import com.staccato.memory.service.dto.request.MemoryReadRequest; import com.staccato.memory.service.dto.response.CategoryDetailResponse; import com.staccato.memory.service.dto.response.CategoryIdResponse; import com.staccato.memory.service.dto.response.CategoryNameResponses; @@ -53,8 +55,11 @@ public ResponseEntity createCategory( } @GetMapping - public ResponseEntity readAllCategories(@LoginMember Member member) { - MemoryResponses memoryResponses = memoryService.readAllMemories(member); + public ResponseEntity readAllCategories( + @LoginMember Member member, + @ModelAttribute("MemoryReadRequest") MemoryReadRequest memoryReadRequest + ) { + MemoryResponses memoryResponses = memoryService.readAllMemories(member, memoryReadRequest); return ResponseEntity.ok(CategoryDtoMapper.toCategoryResponses(memoryResponses)); } @@ -63,7 +68,7 @@ public ResponseEntity readAllCandidateCategories( @LoginMember Member member, @RequestParam(value = "currentDate") LocalDate currentDate ) { - MemoryNameResponses memoryNameResponses = memoryService.readAllMemoriesIncludingDate(member, currentDate); + MemoryNameResponses memoryNameResponses = memoryService.readAllMemoriesByDate(member, currentDate); return ResponseEntity.ok(CategoryDtoMapper.toCategoryNameResponses(memoryNameResponses)); } diff --git a/backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java b/backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java index 1f01b6991..a3bcd3b38 100644 --- a/backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java +++ b/backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java @@ -9,10 +9,12 @@ import com.staccato.member.domain.Member; import com.staccato.memory.service.dto.request.CategoryRequest; +import com.staccato.memory.service.dto.request.MemoryReadRequest; import com.staccato.memory.service.dto.response.CategoryDetailResponse; import com.staccato.memory.service.dto.response.CategoryIdResponse; import com.staccato.memory.service.dto.response.CategoryNameResponses; import com.staccato.memory.service.dto.response.CategoryResponses; +import com.staccato.memory.service.dto.response.MemoryResponses; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -48,7 +50,10 @@ ResponseEntity createCategory( @Operation(summary = "카테고리 목록 조회", description = "사용자의 모든 카테고리 목록을 조회합니다.") @ApiResponse(description = "카테고리 목록 조회 성공", responseCode = "200") - ResponseEntity readAllCategories(@Parameter(hidden = true) Member member); + ResponseEntity readAllCategories( + @Parameter(hidden = true) Member member, + @Parameter(description = "정렬 기준은 생략하거나 유효하지 않은 값에 대해서는 최근 수정 순(UPDATED)이 기본 정렬로 적용됩니다. 필터링 조건은 생략하거나 유효하지 않은 값이 들어오면 적용되지 않습니다.") MemoryReadRequest memoryReadRequest + ); @Operation(summary = "특정 날짜를 포함하는 사용자의 모든 카테고리 목록 조회", description = "특정 날짜를 포함하는 사용자의 모든 카테고리 목록을 조회합니다.") @ApiResponses(value = { diff --git a/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java b/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java index 06f16e822..874ad9d7d 100644 --- a/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java +++ b/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java @@ -32,6 +32,7 @@ import org.springframework.test.web.servlet.MockMvc; import com.fasterxml.jackson.databind.ObjectMapper; +import com.staccato.ControllerTest; import com.staccato.auth.service.AuthService; import com.staccato.exception.ExceptionResponse; import com.staccato.fixture.Member.MemberFixture; @@ -44,6 +45,7 @@ import com.staccato.memory.domain.Memory; import com.staccato.memory.service.MemoryService; import com.staccato.memory.service.dto.request.CategoryRequest; +import com.staccato.memory.service.dto.request.MemoryReadRequest; import com.staccato.memory.service.dto.request.MemoryRequest; import com.staccato.memory.service.dto.response.MemoryDetailResponse; import com.staccato.memory.service.dto.response.MemoryIdResponse; @@ -191,9 +193,9 @@ void failCreateCategory(CategoryRequest categoryRequest, String expectedMessage) void readAllCategory() throws Exception { // given when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); - Memory memory = MemoryFixture.create(MemberFixture.create()); + Memory memory = MemoryFixture.createWithMember(MemberFixture.create()); MemoryResponses memoryResponses = MemoryResponsesFixture.create(memory); - when(memoryService.readAllMemories(any(Member.class))).thenReturn(memoryResponses); + when(memoryService.readAllMemories(any(Member.class), any(MemoryReadRequest.class))).thenReturn(memoryResponses); String expectedResponse = """ { "categories": [ @@ -220,9 +222,9 @@ void readAllCategory() throws Exception { void readAllCategoryWithoutTerm() throws Exception { // given when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); - Memory memory = MemoryFixture.create(MemberFixture.create()); + Memory memory = MemoryFixture.createWithMember(MemberFixture.create()); MemoryResponses memoryResponses = MemoryResponsesFixture.create(memory); - when(memoryService.readAllMemories(any(Member.class))).thenReturn(memoryResponses); + when(memoryService.readAllMemories(any(Member.class), any(MemoryReadRequest.class))).thenReturn(memoryResponses); String expectedResponse = """ { "categories": [ @@ -247,9 +249,9 @@ void readAllCategoryWithoutTerm() throws Exception { void readAllCategoryIncludingDate() throws Exception { // given when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); - Memory memory = MemoryFixture.create(MemberFixture.create()); + Memory memory = MemoryFixture.createWithMember(MemberFixture.create()); MemoryNameResponses memoryNameResponses = MemoryNameResponsesFixture.create(memory); - when(memoryService.readAllMemoriesIncludingDate(any(Member.class), any())).thenReturn(memoryNameResponses); + when(memoryService.readAllMemoriesByDate(any(Member.class), any())).thenReturn(memoryNameResponses); String expectedResponse = """ { "categories": [ @@ -291,7 +293,7 @@ void readCategory() throws Exception { // given long categoryId = 1; when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); - Memory memory = MemoryFixture.create(MemberFixture.create()); + Memory memory = MemoryFixture.createWithMember(MemberFixture.create()); MomentResponse momentResponse = new MomentResponse(MomentFixture.create(memory), "image.jpg"); MemoryDetailResponse memoryDetailResponse = new MemoryDetailResponse(memory, List.of(momentResponse)); when(memoryService.readMemoryById(anyLong(), any(Member.class))).thenReturn(memoryDetailResponse); @@ -334,7 +336,7 @@ void readCategoryWithoutTerm() throws Exception { // given long categoryId = 1; when(authService.extractFromToken(anyString())).thenReturn(MemberFixture.create()); - Memory memory = MemoryFixture.create(null, null, MemberFixture.create()); + Memory memory = MemoryFixture.createWithMember(null, null, MemberFixture.create()); MomentResponse momentResponse = new MomentResponse(MomentFixture.create(memory), "image.jpg"); MemoryDetailResponse memoryDetailResponse = new MemoryDetailResponse(memory, List.of(momentResponse)); when(memoryService.readMemoryById(anyLong(), any(Member.class))).thenReturn(memoryDetailResponse); From bd5e3da2b6e19523bfaae4a87ddaf37e9696b87e Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Sun, 19 Jan 2025 19:32:48 +0900 Subject: [PATCH 14/15] =?UTF-8?q?refactor:=20ControllerTest=20extend=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory/controller/CategoryControllerTest.java | 11 +---------- .../moment/controller/StaccatoControllerTest.java | 13 ++----------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java b/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java index 874ad9d7d..103dd5de0 100644 --- a/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java +++ b/backend/src/test/java/com/staccato/memory/controller/CategoryControllerTest.java @@ -53,16 +53,7 @@ import com.staccato.memory.service.dto.response.MemoryResponses; import com.staccato.memory.service.dto.response.MomentResponse; -@WebMvcTest(CategoryController.class) -class CategoryControllerTest { - @Autowired - private MockMvc mockMvc; - @Autowired - private ObjectMapper objectMapper; - @MockBean - private MemoryService memoryService; - @MockBean - private AuthService authService; +class CategoryControllerTest extends ControllerTest { static Stream categoryRequestProvider() { return Stream.of( diff --git a/backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java b/backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java index 56dddb540..530893728 100644 --- a/backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java +++ b/backend/src/test/java/com/staccato/moment/controller/StaccatoControllerTest.java @@ -32,6 +32,7 @@ import org.springframework.test.web.servlet.MockMvc; import com.fasterxml.jackson.databind.ObjectMapper; +import com.staccato.ControllerTest; import com.staccato.auth.service.AuthService; import com.staccato.exception.ExceptionResponse; import com.staccato.fixture.Member.MemberFixture; @@ -46,17 +47,7 @@ import com.staccato.moment.service.dto.response.MomentIdResponse; import com.staccato.moment.service.dto.response.MomentLocationResponses; -@WebMvcTest(controllers = StaccatoController.class) -class StaccatoControllerTest { - @Autowired - private MockMvc mockMvc; - @Autowired - private ObjectMapper objectMapper; - @MockBean - private MomentService momentService; - @MockBean - private AuthService authService; - +class StaccatoControllerTest extends ControllerTest { static Stream invalidStaccatoRequestProvider() { return Stream.of( Arguments.of( From dddb31082044810bf6306dd054e5587656449fb2 Mon Sep 17 00:00:00 2001 From: BurningFalls Date: Sun, 19 Jan 2025 19:42:14 +0900 Subject: [PATCH 15/15] =?UTF-8?q?refactor:=20MemoryReadRequest=20->=20Cate?= =?UTF-8?q?goryReadRequest=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory/controller/CategoryController.java | 5 +- .../memory/controller/CategoryDtoMapper.java | 9 +++ .../docs/CategoryControllerDocs.java | 3 +- .../dto/request/CategoryReadRequest.java | 38 +++++++++++++ .../dto/request/CategoryReadRequestTest.java | 56 +++++++++++++++++++ .../dto/request/CategoryRequestTest.java | 30 ++++++++++ 6 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/com/staccato/memory/service/dto/request/CategoryReadRequest.java create mode 100644 backend/src/test/java/com/staccato/memory/service/dto/request/CategoryReadRequestTest.java create mode 100644 backend/src/test/java/com/staccato/memory/service/dto/request/CategoryRequestTest.java diff --git a/backend/src/main/java/com/staccato/memory/controller/CategoryController.java b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java index 1b8c39191..425adae13 100644 --- a/backend/src/main/java/com/staccato/memory/controller/CategoryController.java +++ b/backend/src/main/java/com/staccato/memory/controller/CategoryController.java @@ -24,6 +24,7 @@ import com.staccato.member.domain.Member; import com.staccato.memory.controller.docs.CategoryControllerDocs; import com.staccato.memory.service.MemoryService; +import com.staccato.memory.service.dto.request.CategoryReadRequest; import com.staccato.memory.service.dto.request.CategoryRequest; import com.staccato.memory.service.dto.request.MemoryReadRequest; import com.staccato.memory.service.dto.response.CategoryDetailResponse; @@ -57,9 +58,9 @@ public ResponseEntity createCategory( @GetMapping public ResponseEntity readAllCategories( @LoginMember Member member, - @ModelAttribute("MemoryReadRequest") MemoryReadRequest memoryReadRequest + @ModelAttribute("CategoryReadRequest") CategoryReadRequest categoryReadRequest ) { - MemoryResponses memoryResponses = memoryService.readAllMemories(member, memoryReadRequest); + MemoryResponses memoryResponses = memoryService.readAllMemories(member, CategoryDtoMapper.toMemoryReadRequest(categoryReadRequest)); return ResponseEntity.ok(CategoryDtoMapper.toCategoryResponses(memoryResponses)); } diff --git a/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java b/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java index 1a695fa05..280d5315a 100644 --- a/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java +++ b/backend/src/main/java/com/staccato/memory/controller/CategoryDtoMapper.java @@ -2,7 +2,9 @@ import java.util.List; +import com.staccato.memory.service.dto.request.CategoryReadRequest; import com.staccato.memory.service.dto.request.CategoryRequest; +import com.staccato.memory.service.dto.request.MemoryReadRequest; import com.staccato.memory.service.dto.request.MemoryRequest; import com.staccato.memory.service.dto.response.CategoryDetailResponse; import com.staccato.memory.service.dto.response.CategoryIdResponse; @@ -86,4 +88,11 @@ private static StaccatoResponse toStaccatoResponse(MomentResponse momentResponse momentResponse.visitedAt() ); } + + public static MemoryReadRequest toMemoryReadRequest(CategoryReadRequest categoryReadRequest) { + return new MemoryReadRequest( + categoryReadRequest.filters(), + categoryReadRequest.sort() + ); + } } diff --git a/backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java b/backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java index a3bcd3b38..28b640cfb 100644 --- a/backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java +++ b/backend/src/main/java/com/staccato/memory/controller/docs/CategoryControllerDocs.java @@ -8,6 +8,7 @@ import org.springframework.http.ResponseEntity; import com.staccato.member.domain.Member; +import com.staccato.memory.service.dto.request.CategoryReadRequest; import com.staccato.memory.service.dto.request.CategoryRequest; import com.staccato.memory.service.dto.request.MemoryReadRequest; import com.staccato.memory.service.dto.response.CategoryDetailResponse; @@ -52,7 +53,7 @@ ResponseEntity createCategory( @ApiResponse(description = "카테고리 목록 조회 성공", responseCode = "200") ResponseEntity readAllCategories( @Parameter(hidden = true) Member member, - @Parameter(description = "정렬 기준은 생략하거나 유효하지 않은 값에 대해서는 최근 수정 순(UPDATED)이 기본 정렬로 적용됩니다. 필터링 조건은 생략하거나 유효하지 않은 값이 들어오면 적용되지 않습니다.") MemoryReadRequest memoryReadRequest + @Parameter(description = "정렬 기준은 생략하거나 유효하지 않은 값에 대해서는 최근 수정 순(UPDATED)이 기본 정렬로 적용됩니다. 필터링 조건은 생략하거나 유효하지 않은 값이 들어오면 적용되지 않습니다.") CategoryReadRequest categoryReadRequest ); @Operation(summary = "특정 날짜를 포함하는 사용자의 모든 카테고리 목록 조회", description = "특정 날짜를 포함하는 사용자의 모든 카테고리 목록을 조회합니다.") diff --git a/backend/src/main/java/com/staccato/memory/service/dto/request/CategoryReadRequest.java b/backend/src/main/java/com/staccato/memory/service/dto/request/CategoryReadRequest.java new file mode 100644 index 000000000..3c1d9e307 --- /dev/null +++ b/backend/src/main/java/com/staccato/memory/service/dto/request/CategoryReadRequest.java @@ -0,0 +1,38 @@ +package com.staccato.memory.service.dto.request; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import com.staccato.memory.service.MemoryFilter; +import com.staccato.memory.service.MemorySort; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "카테고리 목록 조회시 정렬과 필터링 조건을 위한 요청 형식입니다.") +public record CategoryReadRequest( + @Schema(description = "사용할 필터링을 구분자(,)로 구분하여 나열 (TERM / 대소문자 구분 X)", example = "TERM") + String filters, + @Schema(description = "정렬 기준 (UPDATED, NEWEST, OLDEST / 대소문자 구분 X)", example = "NEWEST") + String sort +) { + private static final String DELIMITER = ","; + + public List getFilters() { + List filters = parseFilters().stream() + .map(String::trim) + .toList(); + return MemoryFilter.findAllByName(filters); + } + + private List parseFilters() { + if (Objects.isNull(filters)) { + return List.of(); + } + return Arrays.stream(filters.split(DELIMITER)).toList(); + } + + public MemorySort getSort() { + return MemorySort.findByName(sort); + } +} diff --git a/backend/src/test/java/com/staccato/memory/service/dto/request/CategoryReadRequestTest.java b/backend/src/test/java/com/staccato/memory/service/dto/request/CategoryReadRequestTest.java new file mode 100644 index 000000000..4937f69c8 --- /dev/null +++ b/backend/src/test/java/com/staccato/memory/service/dto/request/CategoryReadRequestTest.java @@ -0,0 +1,56 @@ +package com.staccato.memory.service.dto.request; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +import com.staccato.memory.service.MemoryFilter; +import com.staccato.memory.service.MemorySort; + +class CategoryReadRequestTest { + @DisplayName("필터가 주어졌을 때 올바른 필터 목록을 반환한다") + @Test + void getFiltersWithValidFilters() { + // given + CategoryReadRequest request = new CategoryReadRequest("TERM, term", "NEWEST"); + + // when + List filters = request.getFilters(); + + // then + assertThat(filters).hasSize(1).containsOnly(MemoryFilter.TERM); + } + + @DisplayName("필터가 주어지지 않았을 때 빈 필터 목록을 반환한다") + @ParameterizedTest + @NullAndEmptySource + void getFiltersWithNullOrEmptyFilters(String filters) { + // given + CategoryReadRequest request = new CategoryReadRequest(filters, "NEWEST"); + + // when + List result = request.getFilters(); + + // then + assertThat(result).isEmpty(); + } + + @DisplayName("정렬이 주어지지 않았을 때 기본 정렬 기준을 반환한다") + @ParameterizedTest + @NullAndEmptySource + void getSortWithNullOrEmptyFilters(String sort) { + // given + CategoryReadRequest request = new CategoryReadRequest(null, sort); + + // when + MemorySort result = request.getSort(); + + // then + assertThat(result).isEqualTo(MemorySort.UPDATED); + } +} diff --git a/backend/src/test/java/com/staccato/memory/service/dto/request/CategoryRequestTest.java b/backend/src/test/java/com/staccato/memory/service/dto/request/CategoryRequestTest.java new file mode 100644 index 000000000..52a8745d3 --- /dev/null +++ b/backend/src/test/java/com/staccato/memory/service/dto/request/CategoryRequestTest.java @@ -0,0 +1,30 @@ +package com.staccato.memory.service.dto.request; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDate; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CategoryRequestTest { + @DisplayName("CategoryRequest를 생성할 때 title에는 trim이 적용된다.") + @Test + void trimTitle() { + // given + String categoryTitle = " title "; + String expectedTitle = "title"; + + // when + CategoryRequest categoryRequest = new CategoryRequest( + "thumbnail", + categoryTitle, + "description", + LocalDate.of(2024, 8, 22), + LocalDate.of(2024, 8, 22) + ); + + // then + assertThat(categoryRequest.categoryTitle()).isEqualTo(expectedTitle); + } +}