From 4f5a335e939a401f84221864013419e52bdde5cd Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sat, 5 Jul 2025 12:03:23 +0900 Subject: [PATCH 01/27] =?UTF-8?q?[feat]=20=EA=B8=B0=EB=A1=9D=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B4=80=EB=A0=A8=20dto=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/request/DummyRequest.java | 7 ----- .../in/web/request/RecordCreateRequest.java | 30 +++++++++++++++++++ .../in/web/response/DummyResponse.java | 7 ----- .../in/web/response/RecordCreateResponse.java | 12 ++++++++ 4 files changed, 42 insertions(+), 14 deletions(-) delete mode 100644 src/main/java/konkuk/thip/record/adapter/in/web/request/DummyRequest.java create mode 100644 src/main/java/konkuk/thip/record/adapter/in/web/request/RecordCreateRequest.java delete mode 100644 src/main/java/konkuk/thip/record/adapter/in/web/response/DummyResponse.java create mode 100644 src/main/java/konkuk/thip/record/adapter/in/web/response/RecordCreateResponse.java diff --git a/src/main/java/konkuk/thip/record/adapter/in/web/request/DummyRequest.java b/src/main/java/konkuk/thip/record/adapter/in/web/request/DummyRequest.java deleted file mode 100644 index ad0881008..000000000 --- a/src/main/java/konkuk/thip/record/adapter/in/web/request/DummyRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package konkuk.thip.record.adapter.in.web.request; - -import lombok.Getter; - -@Getter -public class DummyRequest { -} diff --git a/src/main/java/konkuk/thip/record/adapter/in/web/request/RecordCreateRequest.java b/src/main/java/konkuk/thip/record/adapter/in/web/request/RecordCreateRequest.java new file mode 100644 index 000000000..8c3972a65 --- /dev/null +++ b/src/main/java/konkuk/thip/record/adapter/in/web/request/RecordCreateRequest.java @@ -0,0 +1,30 @@ +package konkuk.thip.record.adapter.in.web.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import konkuk.thip.record.application.port.in.dto.RecordCreateCommand; +import lombok.Getter; + +@Getter +public record RecordCreateRequest ( + @NotNull(message = "page는 필수입니다.") + Integer page, + + @NotNull(message = "isOverview(= 총평 여부)는 필수입니다.") + Boolean isOverview, + + @NotBlank(message = "기록 내용은 필수입니다.") + @Size(max = 500, message = "기록 내용은 최대 500자 입니다.") + String content +) { + public RecordCreateCommand toCommand(Long roomId, Long creatorId) { + return new RecordCreateCommand( + creatorId, + roomId, + page, + isOverview, + content + ); + } +} \ No newline at end of file diff --git a/src/main/java/konkuk/thip/record/adapter/in/web/response/DummyResponse.java b/src/main/java/konkuk/thip/record/adapter/in/web/response/DummyResponse.java deleted file mode 100644 index 1a676ce2e..000000000 --- a/src/main/java/konkuk/thip/record/adapter/in/web/response/DummyResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package konkuk.thip.record.adapter.in.web.response; - -import lombok.Getter; - -@Getter -public class DummyResponse { -} diff --git a/src/main/java/konkuk/thip/record/adapter/in/web/response/RecordCreateResponse.java b/src/main/java/konkuk/thip/record/adapter/in/web/response/RecordCreateResponse.java new file mode 100644 index 000000000..52a42cd7e --- /dev/null +++ b/src/main/java/konkuk/thip/record/adapter/in/web/response/RecordCreateResponse.java @@ -0,0 +1,12 @@ +package konkuk.thip.record.adapter.in.web.response; + +import lombok.Getter; + +@Getter +public record RecordCreateResponse( + Long recordId +) { + public static RecordCreateResponse of(Long recordId) { + return new RecordCreateResponse(recordId); + } +} From 405a5b2232c7215494f5d01a1cfec3008e466650 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sat, 5 Jul 2025 12:03:33 +0900 Subject: [PATCH 02/27] =?UTF-8?q?[feat]=20=EA=B8=B0=EB=A1=9D=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20Usecase=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/port/in/DummyUseCase.java | 5 ----- .../port/in/RecordCreateUseCase.java | 9 +++++++++ .../service/RecordCreateService.java | 18 ++++++++++++++++++ .../application/service/RecordService.java | 11 ----------- 4 files changed, 27 insertions(+), 16 deletions(-) delete mode 100644 src/main/java/konkuk/thip/record/application/port/in/DummyUseCase.java create mode 100644 src/main/java/konkuk/thip/record/application/port/in/RecordCreateUseCase.java create mode 100644 src/main/java/konkuk/thip/record/application/service/RecordCreateService.java delete mode 100644 src/main/java/konkuk/thip/record/application/service/RecordService.java diff --git a/src/main/java/konkuk/thip/record/application/port/in/DummyUseCase.java b/src/main/java/konkuk/thip/record/application/port/in/DummyUseCase.java deleted file mode 100644 index 190e4992c..000000000 --- a/src/main/java/konkuk/thip/record/application/port/in/DummyUseCase.java +++ /dev/null @@ -1,5 +0,0 @@ -package konkuk.thip.record.application.port.in; - -public interface DummyUseCase { - -} diff --git a/src/main/java/konkuk/thip/record/application/port/in/RecordCreateUseCase.java b/src/main/java/konkuk/thip/record/application/port/in/RecordCreateUseCase.java new file mode 100644 index 000000000..957f58484 --- /dev/null +++ b/src/main/java/konkuk/thip/record/application/port/in/RecordCreateUseCase.java @@ -0,0 +1,9 @@ +package konkuk.thip.record.application.port.in; + +import konkuk.thip.record.application.port.in.dto.RecordCreateCommand; + +public interface RecordCreateUseCase { + + Long createRecord(RecordCreateCommand command); + +} diff --git a/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java b/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java new file mode 100644 index 000000000..c475d888a --- /dev/null +++ b/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java @@ -0,0 +1,18 @@ +package konkuk.thip.record.application.service; + +import jakarta.transaction.Transactional; +import konkuk.thip.record.application.port.in.RecordCreateUseCase; +import konkuk.thip.record.application.port.in.dto.RecordCreateCommand; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RecordCreateService implements RecordCreateUseCase { + + @Transactional + @Override + public Long createRecord(RecordCreateCommand command) { + return null; + } +} diff --git a/src/main/java/konkuk/thip/record/application/service/RecordService.java b/src/main/java/konkuk/thip/record/application/service/RecordService.java deleted file mode 100644 index 123c0adbe..000000000 --- a/src/main/java/konkuk/thip/record/application/service/RecordService.java +++ /dev/null @@ -1,11 +0,0 @@ -package konkuk.thip.record.application.service; - -import konkuk.thip.record.application.port.in.DummyUseCase; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class RecordService implements DummyUseCase { - -} From 983492edd5d878940d47b334e4ed0219697872f1 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sat, 5 Jul 2025 12:03:40 +0900 Subject: [PATCH 03/27] =?UTF-8?q?[feat]=20=EA=B8=B0=EB=A1=9D=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20Controller=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/RecordCommandController.java | 20 ++++++++++++++++++- .../application/port/in/dto/DummyCommand.java | 10 ---------- .../port/in/dto/RecordCreateCommand.java | 15 ++++++++++++++ 3 files changed, 34 insertions(+), 11 deletions(-) delete mode 100644 src/main/java/konkuk/thip/record/application/port/in/dto/DummyCommand.java create mode 100644 src/main/java/konkuk/thip/record/application/port/in/dto/RecordCreateCommand.java diff --git a/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java b/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java index 4102d9811..27c3503a9 100644 --- a/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java +++ b/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java @@ -1,10 +1,28 @@ package konkuk.thip.record.adapter.in.web; +import jakarta.validation.Valid; +import konkuk.thip.common.dto.BaseResponse; +import konkuk.thip.common.security.annotation.UserId; +import konkuk.thip.record.adapter.in.web.request.RecordCreateRequest; +import konkuk.thip.record.adapter.in.web.response.RecordCreateResponse; +import konkuk.thip.record.application.port.in.RecordCreateUseCase; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor public class RecordCommandController { + private final RecordCreateUseCase recordCreateUseCase; + + @PostMapping("/rooms/{roomId}/records") + public BaseResponse createRecord( + @UserId Long userId, + @PathVariable Long roomId, + @Valid @RequestBody RecordCreateRequest recordCreateRequest) { + return BaseResponse.ok( + RecordCreateResponse.of( + recordCreateUseCase.createRecord(recordCreateRequest.toCommand(roomId, userId)) + )); + } } diff --git a/src/main/java/konkuk/thip/record/application/port/in/dto/DummyCommand.java b/src/main/java/konkuk/thip/record/application/port/in/dto/DummyCommand.java deleted file mode 100644 index 9e48b6a52..000000000 --- a/src/main/java/konkuk/thip/record/application/port/in/dto/DummyCommand.java +++ /dev/null @@ -1,10 +0,0 @@ -package konkuk.thip.record.application.port.in.dto; - -import lombok.Builder; -import lombok.Getter; - -@Builder -@Getter -public class DummyCommand { - -} diff --git a/src/main/java/konkuk/thip/record/application/port/in/dto/RecordCreateCommand.java b/src/main/java/konkuk/thip/record/application/port/in/dto/RecordCreateCommand.java new file mode 100644 index 000000000..c9e5c526a --- /dev/null +++ b/src/main/java/konkuk/thip/record/application/port/in/dto/RecordCreateCommand.java @@ -0,0 +1,15 @@ +package konkuk.thip.record.application.port.in.dto; + +public record RecordCreateCommand( + Long userId, + + Long roomId, + + int page, + + boolean isOverview, + + String content +) { + +} From 2fd757d83489f7c087c5f74bf04502a527b42060 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 6 Jul 2025 01:35:41 +0900 Subject: [PATCH 04/27] =?UTF-8?q?[feat]=20Record=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EA=B7=9C=EC=B9=99=20=EC=84=A0=EC=96=B8=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/record/domain/Record.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/main/java/konkuk/thip/record/domain/Record.java b/src/main/java/konkuk/thip/record/domain/Record.java index 39734cd08..bbd941dc9 100644 --- a/src/main/java/konkuk/thip/record/domain/Record.java +++ b/src/main/java/konkuk/thip/record/domain/Record.java @@ -1,9 +1,12 @@ package konkuk.thip.record.domain; import konkuk.thip.common.entity.BaseDomainEntity; +import konkuk.thip.common.exception.InvalidStateException; import lombok.Getter; import lombok.experimental.SuperBuilder; +import static konkuk.thip.common.exception.code.ErrorCode.*; + @Getter @SuperBuilder public class Record extends BaseDomainEntity { @@ -19,4 +22,44 @@ public class Record extends BaseDomainEntity { private boolean isOverview; private Long roomId; + + public static Record withoutId( + String content, + Long creatorId, + Integer page, + boolean isOverview, + Long roomId + ) { + return Record.builder() + .content(content) + .creatorId(creatorId) + .page(page) + .isOverview(isOverview) + .roomId(roomId) + .build(); + } + + + public void validateOverview(int totalPageCount) { + double ratio = (double) page / totalPageCount; + if (isOverview && ratio < 0.8) { + String message = String.format( + "총평(isOverview)은 진행률이 80%% 이상일 때만 가능합니다. 현재 진행률 = %.2f%% (%d/%d)", + ratio * 100, page, totalPageCount + ); + throw new InvalidStateException(RECORD_CANNOT_BE_OVERVIEW, new IllegalStateException(message)); + } + } + + public void validatePage(int totalPageCount) { + if (page < 1 || page > totalPageCount) { + String message = String.format( + "페이지 범위가 잘못되었습니다. 현재 기록할 page = %d, 책 전체 page = %d", + page, totalPageCount + ); + throw new InvalidStateException(INVALID_RECORD_PAGE_RANGE, + new IllegalArgumentException(message) + ); + } + } } From 475a13f02e75b3b0292a389a4cf76450add0c58f Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 6 Jul 2025 01:35:52 +0900 Subject: [PATCH 05/27] =?UTF-8?q?[feat]=20RecordCommandPort=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/record/application/port/out/RecordCommandPort.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java b/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java index 8719b6fb1..d57a5d6c3 100644 --- a/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java +++ b/src/main/java/konkuk/thip/record/application/port/out/RecordCommandPort.java @@ -1,6 +1,10 @@ package konkuk.thip.record.application.port.out; +import konkuk.thip.record.domain.Record; + public interface RecordCommandPort { + Long saveRecord(Record record); + } From 7f9c6c9b274ead3bfc000c86c3c3111fcb2022a8 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 6 Jul 2025 01:36:13 +0900 Subject: [PATCH 06/27] =?UTF-8?q?[feat]=20Controller=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20dto=20=EC=A0=95=EC=9D=98=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../record/adapter/in/web/request/RecordCreateRequest.java | 2 -- .../record/adapter/in/web/response/RecordCreateResponse.java | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/main/java/konkuk/thip/record/adapter/in/web/request/RecordCreateRequest.java b/src/main/java/konkuk/thip/record/adapter/in/web/request/RecordCreateRequest.java index 8c3972a65..856061bd0 100644 --- a/src/main/java/konkuk/thip/record/adapter/in/web/request/RecordCreateRequest.java +++ b/src/main/java/konkuk/thip/record/adapter/in/web/request/RecordCreateRequest.java @@ -4,9 +4,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import konkuk.thip.record.application.port.in.dto.RecordCreateCommand; -import lombok.Getter; -@Getter public record RecordCreateRequest ( @NotNull(message = "page는 필수입니다.") Integer page, diff --git a/src/main/java/konkuk/thip/record/adapter/in/web/response/RecordCreateResponse.java b/src/main/java/konkuk/thip/record/adapter/in/web/response/RecordCreateResponse.java index 52a42cd7e..f871a07fd 100644 --- a/src/main/java/konkuk/thip/record/adapter/in/web/response/RecordCreateResponse.java +++ b/src/main/java/konkuk/thip/record/adapter/in/web/response/RecordCreateResponse.java @@ -1,8 +1,5 @@ package konkuk.thip.record.adapter.in.web.response; -import lombok.Getter; - -@Getter public record RecordCreateResponse( Long recordId ) { From 09e032704474317ff483bb14e5172afd4539a3f1 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 6 Jul 2025 01:36:39 +0900 Subject: [PATCH 07/27] =?UTF-8?q?[feat]=20=EA=B8=B0=EB=A1=9D=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20api=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/record/adapter/in/web/RecordCommandController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java b/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java index 27c3503a9..b7834ff5b 100644 --- a/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java +++ b/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java @@ -14,7 +14,7 @@ public class RecordCommandController { private final RecordCreateUseCase recordCreateUseCase; - @PostMapping("/rooms/{roomId}/records") + @PostMapping("/rooms/{roomId}/record") public BaseResponse createRecord( @UserId Long userId, @PathVariable Long roomId, From 9dde2d20ab65da5fe6dbbd7497ec92c1c1ac5613 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 6 Jul 2025 01:36:46 +0900 Subject: [PATCH 08/27] =?UTF-8?q?[feat]=20=EA=B8=B0=EB=A1=9D=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/RecordCreateService.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java b/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java index c475d888a..980fbd43a 100644 --- a/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java +++ b/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java @@ -1,8 +1,14 @@ package konkuk.thip.record.application.service; import jakarta.transaction.Transactional; +import konkuk.thip.book.application.port.out.BookCommandPort; +import konkuk.thip.book.domain.Book; import konkuk.thip.record.application.port.in.RecordCreateUseCase; import konkuk.thip.record.application.port.in.dto.RecordCreateCommand; +import konkuk.thip.record.application.port.out.RecordCommandPort; +import konkuk.thip.record.domain.Record; +import konkuk.thip.room.application.port.out.RoomCommandPort; +import konkuk.thip.room.domain.Room; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -10,9 +16,34 @@ @RequiredArgsConstructor public class RecordCreateService implements RecordCreateUseCase { + private final RecordCommandPort recordCommandPort; + private final RoomCommandPort roomCommandPort; + private final BookCommandPort bookCommandPort; + @Transactional @Override public Long createRecord(RecordCreateCommand command) { - return null; + Record record = Record.withoutId( + command.content(), + command.userId(), + command.page(), + command.isOverview(), + command.roomId() + ); + + validateRecord(record); + + return recordCommandPort.saveRecord(record); + } + + private void validateRecord(Record record) { + Room room = roomCommandPort.findById(record.getRoomId()); + Book book = bookCommandPort.findById(room.getBookId()); + + // 페이지 유효성 검증 + record.validatePage(book.getPageCount()); + + // 총평 유효성 검증 + record.validateOverview(book.getPageCount()); } } From 7c70201cb3782634d7a61c4f28555f7a00e34cd4 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 6 Jul 2025 01:36:57 +0900 Subject: [PATCH 09/27] =?UTF-8?q?[feat]=20=EA=B8=B0=EB=A1=9D=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B4=80=EB=A0=A8=20persistence=20=EC=84=A0?= =?UTF-8?q?=EC=96=B8=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordCommandPersistenceAdapter.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java index 33e98dfc4..5cce52852 100644 --- a/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/record/adapter/out/persistence/RecordCommandPersistenceAdapter.java @@ -1,15 +1,41 @@ package konkuk.thip.record.adapter.out.persistence; +import konkuk.thip.common.exception.EntityNotFoundException; import konkuk.thip.record.adapter.out.mapper.RecordMapper; import konkuk.thip.record.application.port.out.RecordCommandPort; +import konkuk.thip.record.domain.Record; +import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.persistence.RoomJpaRepository; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.UserJpaRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import static konkuk.thip.common.exception.code.ErrorCode.ROOM_NOT_FOUND; +import static konkuk.thip.common.exception.code.ErrorCode.USER_NOT_FOUND; + @Repository @RequiredArgsConstructor public class RecordCommandPersistenceAdapter implements RecordCommandPort { private final RecordJpaRepository recordJpaRepository; + private final UserJpaRepository userJpaRepository; + private final RoomJpaRepository roomJpaRepository; private final RecordMapper recordMapper; + @Override + public Long saveRecord(Record record) { + UserJpaEntity userJpaEntity = userJpaRepository.findById(record.getCreatorId()).orElseThrow( + () -> new EntityNotFoundException(USER_NOT_FOUND) + ); + + RoomJpaEntity roomJpaEntity = roomJpaRepository.findById(record.getRoomId()).orElseThrow( + () -> new EntityNotFoundException(ROOM_NOT_FOUND) + ); + + return recordJpaRepository.save( + recordMapper.toJpaEntity(record, userJpaEntity, roomJpaEntity) + ).getPostId(); + } + } From 9e5d3e8dd7b14b74f4a47e548b3fa1ec7ae00e0b Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 6 Jul 2025 01:37:12 +0900 Subject: [PATCH 10/27] =?UTF-8?q?[feat]=20=EA=B8=B0=EB=A1=9D=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=9F=AC=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/konkuk/thip/common/exception/code/ErrorCode.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index 5de2c7ec8..026250539 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -70,7 +70,11 @@ public enum ErrorCode implements ResponseCode { */ VOTE_NOT_FOUND(HttpStatus.NOT_FOUND, 110000, "존재하지 않는 VOTE 입니다."), VOTE_CANNOT_BE_OVERVIEW(HttpStatus.BAD_REQUEST, 110001, "총평이 될 수 없는 VOTE 입니다."), - INVALID_VOTE_PAGE_RANGE(HttpStatus.BAD_REQUEST, 110002, "VOTE의 page 값이 유효하지 않습니다.") + INVALID_VOTE_PAGE_RANGE(HttpStatus.BAD_REQUEST, 110002, "VOTE의 page 값이 유효하지 않습니다."), + + RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, 120000, "존재하지 않는 RECORD 입니다."), + RECORD_CANNOT_BE_OVERVIEW(HttpStatus.BAD_REQUEST, 120001, "총평이 될 수 없는 RECORD 입니다."), + INVALID_RECORD_PAGE_RANGE(HttpStatus.BAD_REQUEST, 120002, "RECORD의 page 값이 유효하지 않습니다."), ; From 9c2ff070d3d1c38a024b028d4e0f5e59b75c5762 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 6 Jul 2025 01:37:29 +0900 Subject: [PATCH 11/27] =?UTF-8?q?[test]=20Record=20=EC=83=9D=EC=84=B1=20ap?= =?UTF-8?q?i=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/RecordCreateControllerTest.java | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java diff --git a/src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java b/src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java new file mode 100644 index 000000000..96fb4e14c --- /dev/null +++ b/src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java @@ -0,0 +1,213 @@ +package konkuk.thip.record.adapter.in.web; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; +import konkuk.thip.book.adapter.out.persistence.BookJpaRepository; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.record.adapter.in.web.request.RecordCreateRequest; +import konkuk.thip.record.adapter.out.jpa.RecordJpaEntity; +import konkuk.thip.record.adapter.out.persistence.RecordJpaRepository; +import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.persistence.CategoryJpaRepository; +import konkuk.thip.room.adapter.out.persistence.RoomJpaRepository; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.AliasJpaRepository; +import konkuk.thip.user.adapter.out.persistence.UserJpaRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +import java.util.Map; + +import static konkuk.thip.common.exception.code.ErrorCode.API_INVALID_PARAM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@DisplayName("[통합] RecordCommandController 테스트") +class RecordCreateControllerTest { + + @Autowired + MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private AliasJpaRepository aliasJpaRepository; + + @Autowired + private UserJpaRepository userJpaRepository; + + @Autowired + private CategoryJpaRepository categoryJpaRepository; + + @Autowired + private BookJpaRepository bookJpaRepository; + + @Autowired + private RoomJpaRepository roomJpaRepository; + + @Autowired + private RecordJpaRepository recordJpaRepository; + + @AfterEach + void tearDown() { + recordJpaRepository.deleteAll(); + roomJpaRepository.deleteAll(); + bookJpaRepository.deleteAll(); + categoryJpaRepository.deleteAll(); + userJpaRepository.deleteAll(); + aliasJpaRepository.deleteAll(); + } + + private void saveUserAndRoom() { + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createAlias()); + + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(alias)); + + BookJpaEntity book = bookJpaRepository.save(TestEntityFactory.createBook()); + + CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createCategory(alias)); + + RoomJpaEntity room = roomJpaRepository.save(TestEntityFactory.createRoom(book, category)); + } + + @Test + @DisplayName("[페이지 넘버, 총평 여부, 기록 내용]을 받아, 기록을 생성한다.") + void record_create_success() throws Exception { + //given + saveUserAndRoom(); + + int page = 10; + boolean isOverview = false; + String content = "기록 내용"; + + RecordCreateRequest request = new RecordCreateRequest( + page, + isOverview, + content + ); + + Long userId = userJpaRepository.findAll().get(0).getUserId(); + Long roomId = roomJpaRepository.findAll().get(0).getRoomId(); + + //when + ResultActions result = mockMvc.perform(post("/rooms/{roomId}/record", roomId) + .requestAttr("userId", userId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request) + )); + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.recordId").exists()); + + String json = result.andReturn().getResponse().getContentAsString(); + JsonNode jsonNode = objectMapper.readTree(json); + Long recordId = jsonNode.path("data").path("recordId").asLong(); + + RecordJpaEntity recordJpaEntity = recordJpaRepository.findById(recordId).orElse(null); + + assertThat(recordJpaEntity).isNotNull(); + assertThat(recordJpaEntity.getUserJpaEntity().getUserId()).isEqualTo(userId); + assertThat(recordJpaEntity.getRoomJpaEntity().getRoomId()).isEqualTo(roomId); + assertThat(recordJpaEntity.getPage()).isEqualTo(page); + assertThat(recordJpaEntity.getContent()).isEqualTo(content); + } + + @Test + @DisplayName("[page]가 누락되었을 때 400 Bad Request 반환") + void vote_create_page_null() throws Exception { + // given: page 누락 + Map request = Map.of( + "isOverview", false, + "content", "내용" + ); + + // when & then + mockMvc.perform(post("/rooms/{roomId}/record", 1L) + .requestAttr("userId", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(API_INVALID_PARAM.getCode())) + .andExpect(jsonPath("$.message", containsString("page는 필수입니다."))); + } + + @Test + @DisplayName("[isOverview]가 누락되었을 때 400 Bad Request 반환") + void vote_create_is_over_view_null() throws Exception { + // given: isOverview 누락 + Map request = Map.of( + "page", 1, + "content", "내용" + ); + + // when & then + mockMvc.perform(post("/rooms/{roomId}/record", 1L) + .requestAttr("userId", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(API_INVALID_PARAM.getCode())) + .andExpect(jsonPath("$.message", containsString("isOverview(= 총평 여부)는 필수입니다."))); + } + + @Test + @DisplayName("[content]가 빈 문자열일 때 400 Bad Request 반환") + void vote_create_content_blank() throws Exception { + // given + Map request = Map.of( + "page", 1, + "isOverview", false, + "content", "" + ); + + // when & then + mockMvc.perform(post("/rooms/{roomId}/record", 1L) + .requestAttr("userId", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(API_INVALID_PARAM.getCode())) + .andExpect(jsonPath("$.message", containsString("기록 내용은 필수입니다."))); + } + + @Test + @DisplayName("[content]가 500자 초과일 때 400 Bad Request 반환") + void vote_create_content_too_long() throws Exception { + // given + String longContent = "가".repeat(501); + Map request = Map.of( + "page", 1, + "isOverview", false, + "content", longContent + ); + + // when & then + mockMvc.perform(post("/rooms/{roomId}/record", 1L) + .requestAttr("userId", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(API_INVALID_PARAM.getCode())) + .andExpect(jsonPath("$.message", containsString("기록 내용은 최대 500자 입니다."))); + } + +} \ No newline at end of file From 02ae8263ba0e4cae9a6a3df47a1c2958e9a9d200 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 6 Jul 2025 01:37:38 +0900 Subject: [PATCH 12/27] =?UTF-8?q?[test]=20Record=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/record/domain/RecordTest.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/test/java/konkuk/thip/record/domain/RecordTest.java diff --git a/src/test/java/konkuk/thip/record/domain/RecordTest.java b/src/test/java/konkuk/thip/record/domain/RecordTest.java new file mode 100644 index 000000000..a2d87e163 --- /dev/null +++ b/src/test/java/konkuk/thip/record/domain/RecordTest.java @@ -0,0 +1,63 @@ +package konkuk.thip.record.domain; + +import konkuk.thip.common.exception.InvalidStateException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("[단위] Record 도메인 테스트") +class RecordTest { + + @Test + @DisplayName("validatePage: 유효한 페이지 범위일 때, 예외가 발생하지 않는다.") + void validate_page_valid_range() { + Record record = Record.withoutId("content", 1L, 10, false, 1L); + assertDoesNotThrow(() -> record.validatePage(20)); + assertDoesNotThrow(() -> record.validatePage(10)); + } + + @Test + @DisplayName("validatePage: page가 1보다 작을 때, InvalidStateException 발생한다.") + void validate_page_lower_than_zero() { + Record record = Record.withoutId("content", 1L, 0, false, 1L); + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> record.validatePage(20)); + assertInstanceOf(IllegalArgumentException.class, ex.getCause()); + assertTrue(ex.getCause().getMessage().contains("현재 기록할 page = 0, 책 전체 page = 20")); + } + + @Test + @DisplayName("validatePage: page가 전체 페이지 수를 초과할 때, InvalidStateException 발생한다.") + void validate_page_bigger_than_total() { + Record record = Record.withoutId("content", 1L, 25, false, 1L); + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> record.validatePage(20)); + assertInstanceOf(IllegalArgumentException.class, ex.getCause()); + assertTrue(ex.getCause().getMessage().contains("현재 기록할 page = 25, 책 전체 page = 20")); + } + + @Test + @DisplayName("validateOverview: isOverview=false 이면, 예외가 발생하지 않는다.") + void validate_overview_not_overview_no_exception() { + Record record = Record.withoutId("content", 1L, 5, false, 1L); + assertDoesNotThrow(() -> record.validateOverview(20)); + } + + @Test + @DisplayName("validateOverview: 진행률 80% 이상이고 isOverview=true 이면, 예외가 발생하지 않는다.") + void validate_overview_ratio_at_least_80_percent() { + Record record = Record.withoutId("content", 1L, 16, true, 1L); + assertDoesNotThrow(() -> record.validateOverview(20)); + } + + @Test + @DisplayName("validateOverview: 진행률 80% 미만이고 isOverview=true 이면, InvalidStateException 발생한다.") + void validate_overview_ratio_below_80_percent() { + Record record = Record.withoutId("content", 1L, 15, true, 1L); // 15/20 = 0.75 + InvalidStateException ex = assertThrows(InvalidStateException.class, + () -> record.validateOverview(20)); + assertInstanceOf(IllegalStateException.class, ex.getCause()); + assertTrue(ex.getCause().getMessage().contains("현재 진행률 = 75.00% (15/20)")); + } +} \ No newline at end of file From b16f76720628da48101440df62169e502b7bd62e Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 6 Jul 2025 01:37:54 +0900 Subject: [PATCH 13/27] =?UTF-8?q?[test]=20=EB=AA=A8=EB=93=A0=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=97=90=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20DisplayName=20=EC=A7=80=EC=A0=95=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/book/adapter/in/web/BookChangeSavedControllerTest.java | 1 + .../thip/book/adapter/in/web/BookDetailSearchControllerTest.java | 1 + .../konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java | 1 + .../java/konkuk/thip/book/adapter/out/api/NaverApiUtilTest.java | 1 + .../java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntityTest.java | 1 + .../konkuk/thip/room/adapter/out/jpa/RecordJpaEntityTest.java | 1 + .../java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntityTest.java | 1 + .../java/konkuk/thip/room/adapter/out/jpa/VoteJpaEntityTest.java | 1 + .../thip/user/adapter/in/web/UserSignupControllerTest.java | 1 + .../user/adapter/in/web/UserVerifyNicknameControllerTest.java | 1 + .../user/adapter/in/web/UserViewAliasChoiceControllerTest.java | 1 + .../java/konkuk/thip/user/adapter/out/jpa/UserJpaEntityTest.java | 1 + .../thip/vote/adapter/in/web/VoteCreateControllerTest.java | 1 + src/test/java/konkuk/thip/vote/domain/VoteTest.java | 1 + 14 files changed, 14 insertions(+) diff --git a/src/test/java/konkuk/thip/book/adapter/in/web/BookChangeSavedControllerTest.java b/src/test/java/konkuk/thip/book/adapter/in/web/BookChangeSavedControllerTest.java index 871ded7f7..204ac4cde 100644 --- a/src/test/java/konkuk/thip/book/adapter/in/web/BookChangeSavedControllerTest.java +++ b/src/test/java/konkuk/thip/book/adapter/in/web/BookChangeSavedControllerTest.java @@ -35,6 +35,7 @@ @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("test") +@DisplayName("[통합] BookChangeSavedController 테스트") class BookChangeSavedControllerTest { @Autowired diff --git a/src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchControllerTest.java b/src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchControllerTest.java index 4c7910c28..b86063d35 100644 --- a/src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchControllerTest.java +++ b/src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchControllerTest.java @@ -31,6 +31,7 @@ @SpringBootTest @AutoConfigureMockMvc(addFilters = false) @ActiveProfiles("test") +@DisplayName("[통합] BookDetailSearchController 테스트") class BookDetailSearchControllerTest { @Autowired diff --git a/src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java b/src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java index dddbbfe96..6aba36187 100644 --- a/src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java +++ b/src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java @@ -29,6 +29,7 @@ @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("test") +@DisplayName("[통합] BookQueryController 테스트") class BookQueryControllerTest { @Autowired diff --git a/src/test/java/konkuk/thip/book/adapter/out/api/NaverApiUtilTest.java b/src/test/java/konkuk/thip/book/adapter/out/api/NaverApiUtilTest.java index b7ad7ae63..6b62f1178 100644 --- a/src/test/java/konkuk/thip/book/adapter/out/api/NaverApiUtilTest.java +++ b/src/test/java/konkuk/thip/book/adapter/out/api/NaverApiUtilTest.java @@ -10,6 +10,7 @@ import static konkuk.thip.common.exception.code.ErrorCode.BOOK_NAVER_API_REQUEST_ERROR; import static org.assertj.core.api.Assertions.*; +@DisplayName("[단위] NaverApiUtil 테스트") class NaverApiUtilTest { private NaverApiUtil createTestUtil() { diff --git a/src/test/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntityTest.java b/src/test/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntityTest.java index d956cf296..03d6484a1 100644 --- a/src/test/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntityTest.java +++ b/src/test/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntityTest.java @@ -20,6 +20,7 @@ @DataJpaTest @ActiveProfiles("test") +@DisplayName("[JPA] FeedJpaEntity 테스트") @Import(konkuk.thip.config.TestQuerydslConfig.class) // DataJpaTest 이므로 JPA 제외 빈 추가로 import class FeedJpaEntityTest { diff --git a/src/test/java/konkuk/thip/room/adapter/out/jpa/RecordJpaEntityTest.java b/src/test/java/konkuk/thip/room/adapter/out/jpa/RecordJpaEntityTest.java index 4a807fe95..0d60b0088 100644 --- a/src/test/java/konkuk/thip/room/adapter/out/jpa/RecordJpaEntityTest.java +++ b/src/test/java/konkuk/thip/room/adapter/out/jpa/RecordJpaEntityTest.java @@ -24,6 +24,7 @@ @DataJpaTest @ActiveProfiles("test") @Import(konkuk.thip.config.TestQuerydslConfig.class) // DataJpaTest 이므로 JPA 제외 빈 추가로 import +@DisplayName("[JPA] RecordJpaEntity 테스트") class RecordJpaEntityTest { @Autowired diff --git a/src/test/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntityTest.java b/src/test/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntityTest.java index 97610553e..3d18de653 100644 --- a/src/test/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntityTest.java +++ b/src/test/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntityTest.java @@ -21,6 +21,7 @@ @DataJpaTest @ActiveProfiles("test") @Import(konkuk.thip.config.TestQuerydslConfig.class) // DataJpaTest 이므로 JPA 제외 빈 추가로 import +@DisplayName("[JPA] RoomJpaEntity 테스트") class RoomJpaEntityTest { @PersistenceContext diff --git a/src/test/java/konkuk/thip/room/adapter/out/jpa/VoteJpaEntityTest.java b/src/test/java/konkuk/thip/room/adapter/out/jpa/VoteJpaEntityTest.java index 692dfe4ef..79f3aea6a 100644 --- a/src/test/java/konkuk/thip/room/adapter/out/jpa/VoteJpaEntityTest.java +++ b/src/test/java/konkuk/thip/room/adapter/out/jpa/VoteJpaEntityTest.java @@ -24,6 +24,7 @@ @DataJpaTest @ActiveProfiles("test") @Import(konkuk.thip.config.TestQuerydslConfig.class) // DataJpaTest 이므로 JPA 제외 빈 추가로 import +@DisplayName("[JPA] VoteJpaEntity 테스트") class VoteJpaEntityTest { @Autowired diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java index 9947800a1..899d914ac 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserSignupControllerTest.java @@ -30,6 +30,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc +@DisplayName("[통합] UserSignupController 테스트") class UserSignupControllerTest { @Autowired diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java index e56c806cc..8afcd9bf8 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserVerifyNicknameControllerTest.java @@ -29,6 +29,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@DisplayName("[통합] UserVerifyNicknameController 테스트") class UserVerifyNicknameControllerTest { @Autowired diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserViewAliasChoiceControllerTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserViewAliasChoiceControllerTest.java index 2514b10d7..15c999bf6 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserViewAliasChoiceControllerTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserViewAliasChoiceControllerTest.java @@ -29,6 +29,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@DisplayName("[통합] UserViewAliasChoiceController 테스트") class UserViewAliasChoiceControllerTest { @Autowired diff --git a/src/test/java/konkuk/thip/user/adapter/out/jpa/UserJpaEntityTest.java b/src/test/java/konkuk/thip/user/adapter/out/jpa/UserJpaEntityTest.java index f303de831..ffd06ca78 100644 --- a/src/test/java/konkuk/thip/user/adapter/out/jpa/UserJpaEntityTest.java +++ b/src/test/java/konkuk/thip/user/adapter/out/jpa/UserJpaEntityTest.java @@ -17,6 +17,7 @@ @DataJpaTest @ActiveProfiles("test") @Import(konkuk.thip.config.TestQuerydslConfig.class) // DataJpaTest 이므로 JPA 제외 빈 추가로 import +@DisplayName("[JPA] UserJpaEntity 테스트") class UserJpaEntityTest { @PersistenceContext diff --git a/src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java b/src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java index 76d4c7648..b103037ef 100644 --- a/src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java +++ b/src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java @@ -44,6 +44,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@DisplayName("[통합] VoteCreateController 테스트") class VoteCreateControllerTest { @Autowired diff --git a/src/test/java/konkuk/thip/vote/domain/VoteTest.java b/src/test/java/konkuk/thip/vote/domain/VoteTest.java index 49b0e1366..8992a8dd5 100644 --- a/src/test/java/konkuk/thip/vote/domain/VoteTest.java +++ b/src/test/java/konkuk/thip/vote/domain/VoteTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.*; +@DisplayName("[단위] Vote 도메인 테스트") class VoteTest { @Test From 55a7afaf01b8c7c2115401179918109e71b5be68 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 6 Jul 2025 11:14:01 +0900 Subject: [PATCH 14/27] =?UTF-8?q?[test]=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../record/adapter/in/web/RecordCreateControllerTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java b/src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java index 96fb4e14c..f2fce4ecd 100644 --- a/src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java +++ b/src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java @@ -133,7 +133,7 @@ void record_create_success() throws Exception { @Test @DisplayName("[page]가 누락되었을 때 400 Bad Request 반환") - void vote_create_page_null() throws Exception { + void record_create_page_null() throws Exception { // given: page 누락 Map request = Map.of( "isOverview", false, @@ -152,7 +152,7 @@ void vote_create_page_null() throws Exception { @Test @DisplayName("[isOverview]가 누락되었을 때 400 Bad Request 반환") - void vote_create_is_over_view_null() throws Exception { + void record_create_is_over_view_null() throws Exception { // given: isOverview 누락 Map request = Map.of( "page", 1, @@ -171,7 +171,7 @@ void vote_create_is_over_view_null() throws Exception { @Test @DisplayName("[content]가 빈 문자열일 때 400 Bad Request 반환") - void vote_create_content_blank() throws Exception { + void record_create_content_blank() throws Exception { // given Map request = Map.of( "page", 1, @@ -191,7 +191,7 @@ void vote_create_content_blank() throws Exception { @Test @DisplayName("[content]가 500자 초과일 때 400 Bad Request 반환") - void vote_create_content_too_long() throws Exception { + void record_create_content_too_long() throws Exception { // given String longContent = "가".repeat(501); Map request = Map.of( From 36cbc0d4b34c447dea5608fee4433ebb41c6d3c4 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:40:40 +0900 Subject: [PATCH 15/27] =?UTF-8?q?[feat]=20Room,=20UserRoom=EC=97=90=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20=EC=A0=95=EC=9D=98=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/room/domain/Room.java | 9 +++++++++ .../java/konkuk/thip/user/domain/UserRoom.java | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/main/java/konkuk/thip/room/domain/Room.java b/src/main/java/konkuk/thip/room/domain/Room.java index f3cf40a04..f4bb359b1 100644 --- a/src/main/java/konkuk/thip/room/domain/Room.java +++ b/src/main/java/konkuk/thip/room/domain/Room.java @@ -1,6 +1,7 @@ package konkuk.thip.room.domain; import konkuk.thip.common.entity.BaseDomainEntity; +import konkuk.thip.common.entity.StatusType; import lombok.Getter; import lombok.experimental.SuperBuilder; @@ -31,4 +32,12 @@ public class Room extends BaseDomainEntity { private Long bookId; private Long categoryId; + + public boolean isExpired() { + return this.getStatus() == StatusType.EXPIRED; + } + + public void updateRoomPercentage(double roomPercentage) { + this.roomPercentage = roomPercentage; + } } diff --git a/src/main/java/konkuk/thip/user/domain/UserRoom.java b/src/main/java/konkuk/thip/user/domain/UserRoom.java index 675a73cfc..2eed98669 100644 --- a/src/main/java/konkuk/thip/user/domain/UserRoom.java +++ b/src/main/java/konkuk/thip/user/domain/UserRoom.java @@ -19,4 +19,19 @@ public class UserRoom extends BaseDomainEntity { private Long userId; private Long roomId; + + public boolean canWriteOverview() { + return userPercentage >= 80; + } + + // 기록(투표) 요청 페이지와 책 전체 페이지 + public boolean updateUserProgress(int requestPage, int totalPageCount) { + if (currentPage < requestPage) { + currentPage = requestPage; + userPercentage = ((double) currentPage / totalPageCount) * 100; + return true; + } + + return false; + } } From fb3ec7d8139b46dd845ed5f68334b5cb48601471 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:40:59 +0900 Subject: [PATCH 16/27] =?UTF-8?q?[feat]=20UserRoom=20Persistence=20port=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserRoomCommandPersistenceAdapter.java | 36 +++++++++++++++++++ .../port/out/UserRoomCommandPort.java | 12 +++++++ 2 files changed, 48 insertions(+) create mode 100644 src/main/java/konkuk/thip/user/adapter/out/persistence/UserRoomCommandPersistenceAdapter.java create mode 100644 src/main/java/konkuk/thip/user/application/port/out/UserRoomCommandPort.java diff --git a/src/main/java/konkuk/thip/user/adapter/out/persistence/UserRoomCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/user/adapter/out/persistence/UserRoomCommandPersistenceAdapter.java new file mode 100644 index 000000000..cd412215a --- /dev/null +++ b/src/main/java/konkuk/thip/user/adapter/out/persistence/UserRoomCommandPersistenceAdapter.java @@ -0,0 +1,36 @@ +package konkuk.thip.user.adapter.out.persistence; + +import konkuk.thip.common.exception.EntityNotFoundException; +import konkuk.thip.common.exception.code.ErrorCode; +import konkuk.thip.user.adapter.out.jpa.UserRoomJpaEntity; +import konkuk.thip.user.adapter.out.mapper.UserRoomMapper; +import konkuk.thip.user.application.port.out.UserRoomCommandPort; +import konkuk.thip.user.domain.UserRoom; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class UserRoomCommandPersistenceAdapter implements UserRoomCommandPort { + + private final UserRoomJpaRepository userRoomJpaRepository; + private final UserRoomMapper userRoomMapper; + + @Override + public UserRoom findByUserIdAndRoomId(Long userId, Long roomId) { + UserRoomJpaEntity userRoomJpaEntity = userRoomJpaRepository.findByUserJpaEntity_UserIdAndRoomJpaEntity_RoomId(userId, roomId).orElseThrow( + () -> new EntityNotFoundException(ErrorCode.USER_ROOM_NOT_FOUND) + ); + + return userRoomMapper.toDomainEntity(userRoomJpaEntity); + } + + @Override + public List findAllByRoomId(Long roomId) { + return userRoomJpaRepository.findAllByRoomJpaEntity_RoomId(roomId).stream() + .map(userRoomMapper::toDomainEntity) + .toList(); + } +} diff --git a/src/main/java/konkuk/thip/user/application/port/out/UserRoomCommandPort.java b/src/main/java/konkuk/thip/user/application/port/out/UserRoomCommandPort.java new file mode 100644 index 000000000..6d3ced065 --- /dev/null +++ b/src/main/java/konkuk/thip/user/application/port/out/UserRoomCommandPort.java @@ -0,0 +1,12 @@ +package konkuk.thip.user.application.port.out; + +import konkuk.thip.user.domain.UserRoom; + +import java.util.List; + +public interface UserRoomCommandPort { + + UserRoom findByUserIdAndRoomId(Long userId, Long roomId); + List findAllByRoomId(Long roomId); + +} From b5e14ee90443737e709f50a3628e17b142b125d4 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:41:07 +0900 Subject: [PATCH 17/27] =?UTF-8?q?[feat]=20UserRoom=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/adapter/out/persistence/UserRoomJpaRepository.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/konkuk/thip/user/adapter/out/persistence/UserRoomJpaRepository.java b/src/main/java/konkuk/thip/user/adapter/out/persistence/UserRoomJpaRepository.java index c073b4238..c77aadf5d 100644 --- a/src/main/java/konkuk/thip/user/adapter/out/persistence/UserRoomJpaRepository.java +++ b/src/main/java/konkuk/thip/user/adapter/out/persistence/UserRoomJpaRepository.java @@ -3,5 +3,11 @@ import konkuk.thip.user.adapter.out.jpa.UserRoomJpaEntity; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; +import java.util.Optional; + public interface UserRoomJpaRepository extends JpaRepository{ + + Optional findByUserJpaEntity_UserIdAndRoomJpaEntity_RoomId(Long userId, Long roomId); + List findAllByRoomJpaEntity_RoomId(Long roomId); } From 76541863a9138c7eaefdbfa486fb08074ca8812e Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:41:17 +0900 Subject: [PATCH 18/27] =?UTF-8?q?[feat]=20UserRoomQueryPort=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/user/application/port/out/UserRoomQueryPort.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/konkuk/thip/user/application/port/out/UserRoomQueryPort.java diff --git a/src/main/java/konkuk/thip/user/application/port/out/UserRoomQueryPort.java b/src/main/java/konkuk/thip/user/application/port/out/UserRoomQueryPort.java new file mode 100644 index 000000000..c75d09f35 --- /dev/null +++ b/src/main/java/konkuk/thip/user/application/port/out/UserRoomQueryPort.java @@ -0,0 +1,4 @@ +package konkuk.thip.user.application.port.out; + +public interface UserRoomQueryPort { +} From f24405540855831ce5630ca3a5a54f202cea186f Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:41:56 +0900 Subject: [PATCH 19/27] =?UTF-8?q?[feat]=20validateOverview=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/record/domain/Record.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/konkuk/thip/record/domain/Record.java b/src/main/java/konkuk/thip/record/domain/Record.java index bbd941dc9..ae46fa255 100644 --- a/src/main/java/konkuk/thip/record/domain/Record.java +++ b/src/main/java/konkuk/thip/record/domain/Record.java @@ -41,13 +41,13 @@ public static Record withoutId( public void validateOverview(int totalPageCount) { - double ratio = (double) page / totalPageCount; - if (isOverview && ratio < 0.8) { + // 총평 기록 생성 요청인데 page가 책의 전체 페이지 수가 아니라면 에러 + if (isOverview && page != totalPageCount) { String message = String.format( - "총평(isOverview)은 진행률이 80%% 이상일 때만 가능합니다. 현재 진행률 = %.2f%% (%d/%d)", - ratio * 100, page, totalPageCount + "총평(isOverview)은 책의 전체 페이지 수(%d)와 동일한 페이지에서만 작성할 수 있습니다. 현재 페이지 = %d", + totalPageCount, page ); - throw new InvalidStateException(RECORD_CANNOT_BE_OVERVIEW, new IllegalStateException(message)); + throw new InvalidStateException(RECORD_CANNOT_BE_OVERVIEW, new IllegalArgumentException(message)); } } From e5aca74ba5cb9e54dff4a5511eb5c5ff9ee5aba2 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:42:05 +0900 Subject: [PATCH 20/27] =?UTF-8?q?[feat]=20=EA=B8=B0=EB=A1=9D=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20api=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20(#4?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/RecordCreateService.java | 57 +++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java b/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java index 980fbd43a..315157834 100644 --- a/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java +++ b/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java @@ -3,15 +3,23 @@ import jakarta.transaction.Transactional; import konkuk.thip.book.application.port.out.BookCommandPort; import konkuk.thip.book.domain.Book; +import konkuk.thip.common.exception.BusinessException; +import konkuk.thip.common.exception.InvalidStateException; import konkuk.thip.record.application.port.in.RecordCreateUseCase; import konkuk.thip.record.application.port.in.dto.RecordCreateCommand; import konkuk.thip.record.application.port.out.RecordCommandPort; import konkuk.thip.record.domain.Record; import konkuk.thip.room.application.port.out.RoomCommandPort; import konkuk.thip.room.domain.Room; +import konkuk.thip.user.application.port.out.UserRoomCommandPort; +import konkuk.thip.user.domain.UserRoom; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.List; + +import static konkuk.thip.common.exception.code.ErrorCode.*; + @Service @RequiredArgsConstructor public class RecordCreateService implements RecordCreateUseCase { @@ -19,10 +27,13 @@ public class RecordCreateService implements RecordCreateUseCase { private final RecordCommandPort recordCommandPort; private final RoomCommandPort roomCommandPort; private final BookCommandPort bookCommandPort; + private final UserRoomCommandPort userRoomCommandPort; @Transactional @Override + //todo updateRoomPercentage 스케줄러로 책임을 분리할지 논의 public Long createRecord(RecordCreateCommand command) { + // 1. Record 생성 Record record = Record.withoutId( command.content(), command.userId(), @@ -31,15 +42,53 @@ public Long createRecord(RecordCreateCommand command) { command.roomId() ); - validateRecord(record); + // 2. UserRoom, Room, Book 조회 + UserRoom userRoom = userRoomCommandPort.findByUserIdAndRoomId(command.userId(), command.roomId()); + Room room = roomCommandPort.findById(record.getRoomId()); + Book book = bookCommandPort.findById(room.getBookId()); + // 3. 유효성 검증 + validateRoom(room); + validateUserRoom(userRoom); + validateRecord(record, book); + + // 4. UserRoom의 currentPage, userPercentage 업데이트 + updateRoomProgress(userRoom, record, book, room); + + // 5. Record 저장 return recordCommandPort.saveRecord(record); } - private void validateRecord(Record record) { - Room room = roomCommandPort.findById(record.getRoomId()); - Book book = bookCommandPort.findById(room.getBookId()); + private void updateRoomProgress(UserRoom userRoom, Record record, Book book, Room room) { + if(userRoom.updateUserProgress(record.getPage(), book.getPageCount())) { + // userPercentage가 업데이트되었으면 Room의 roomPercentage 업데이트 + List userRoomList = userRoomCommandPort.findAllByRoomId(record.getRoomId()); + Double totalUserPercentage = userRoomList.stream() + .map(UserRoom::getUserPercentage) + .reduce(0.0, Double::sum); + room.updateRoomPercentage(totalUserPercentage / userRoomList.size()); + } + } + + private void validateUserRoom(UserRoom userRoom) { + // UserRoom의 총평 작성 가능 여부 검증 + if (!userRoom.canWriteOverview()) { + String message = String.format( + "총평(isOverview)은 사용자 진행률이 80%% 이상일 때만 가능합니다. 현재 사용자 진행률 = %.2f%%", + userRoom.getUserPercentage() * 100 + ); + throw new InvalidStateException(RECORD_CANNOT_BE_OVERVIEW, new IllegalStateException(message)); + } + } + + private void validateRoom(Room room) { + // 방이 만료되었는지 검증 + if (room.isExpired()) { + throw new BusinessException(RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM); + } + } + private void validateRecord(Record record, Book book) { // 페이지 유효성 검증 record.validatePage(book.getPageCount()); From 2e657bbaf237d15700d546730e3d745bbc5d7977 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:42:46 +0900 Subject: [PATCH 21/27] =?UTF-8?q?[teat]=20Record,=20UserRoom=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/record/domain/RecordTest.java | 16 ++-- .../konkuk/thip/user/domain/UserRoomTest.java | 84 +++++++++++++++++++ 2 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 src/test/java/konkuk/thip/user/domain/UserRoomTest.java diff --git a/src/test/java/konkuk/thip/record/domain/RecordTest.java b/src/test/java/konkuk/thip/record/domain/RecordTest.java index a2d87e163..75b14fdf2 100644 --- a/src/test/java/konkuk/thip/record/domain/RecordTest.java +++ b/src/test/java/konkuk/thip/record/domain/RecordTest.java @@ -45,19 +45,19 @@ void validate_overview_not_overview_no_exception() { } @Test - @DisplayName("validateOverview: 진행률 80% 이상이고 isOverview=true 이면, 예외가 발생하지 않는다.") - void validate_overview_ratio_at_least_80_percent() { - Record record = Record.withoutId("content", 1L, 16, true, 1L); - assertDoesNotThrow(() -> record.validateOverview(20)); + @DisplayName("validateOverview: isOverview=true 이고 page가 전체 페이지 수와 같으면, 예외가 발생하지 않는다.") + void validate_overview_page_is_book_page_count() { + Record record = Record.withoutId("content", 1L, 100, true, 1L); + assertDoesNotThrow(() -> record.validateOverview(100)); } @Test - @DisplayName("validateOverview: 진행률 80% 미만이고 isOverview=true 이면, InvalidStateException 발생한다.") - void validate_overview_ratio_below_80_percent() { + @DisplayName("validateOverview: isOverview=true 이고 page가 전체 페이지 수와 다르면, InvalidStateException 발생한다.") + void validate_overview_page_is_not_book_page_count() { Record record = Record.withoutId("content", 1L, 15, true, 1L); // 15/20 = 0.75 InvalidStateException ex = assertThrows(InvalidStateException.class, () -> record.validateOverview(20)); - assertInstanceOf(IllegalStateException.class, ex.getCause()); - assertTrue(ex.getCause().getMessage().contains("현재 진행률 = 75.00% (15/20)")); + assertInstanceOf(IllegalArgumentException.class, ex.getCause()); + assertTrue(ex.getCause().getMessage().contains("현재 페이지 = 15")); } } \ No newline at end of file diff --git a/src/test/java/konkuk/thip/user/domain/UserRoomTest.java b/src/test/java/konkuk/thip/user/domain/UserRoomTest.java new file mode 100644 index 000000000..517e4a8d1 --- /dev/null +++ b/src/test/java/konkuk/thip/user/domain/UserRoomTest.java @@ -0,0 +1,84 @@ +package konkuk.thip.user.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("[단위] UserRoom 도메인 테스트") +class UserRoomTest { + + @Test + @DisplayName("canWriteOverview: 사용자 퍼센트가 80 이상일 때, true를 반환한다.") + void can_writeOverview() { + //given + UserRoom userRoom = UserRoom.builder() + .userPercentage(80.0) + .build(); + + //when + boolean canWrite = userRoom.canWriteOverview(); + + //then + assertThat(canWrite).isTrue(); + } + + @Test + @DisplayName("canWriteOverview: 사용자 퍼센트가 80 미만일 때, false를 반환한다.") + void cannot_writeOverview() { + //given + UserRoom userRoom = UserRoom.builder() + .userPercentage(79.9) + .build(); + + //when + boolean canWrite = userRoom.canWriteOverview(); + + //then + assertThat(canWrite).isFalse(); + } + + @Test + @DisplayName("updateUserProgress: 요청 페이지가 현재 페이지보다 클 때, 현재 페이지와 사용자 퍼센트가 업데이트된다.") + void update_userProgress_success() throws Exception { + //given + UserRoom userRoom = UserRoom.builder() + .currentPage(1) + .userPercentage(5.0) + .userId(1L) + .roomId(1L) + .build(); + + //when + int totalPageCount = 20; + boolean isUpdated = userRoom.updateUserProgress(5, totalPageCount); + + //then + assertThat(isUpdated).isTrue(); + assertThat(userRoom.getCurrentPage()).isEqualTo(5); + double ratio = (double) userRoom.getCurrentPage() / totalPageCount; + assertThat(userRoom.getUserPercentage()).isEqualTo(ratio * 100); + } + + @Test + @DisplayName("updateUserProgress: 요청 페이지가 현재 페이지보다 작을 때, 현재 페이지와 사용자 퍼센트가 업데이트되지 않는다.") + void update_userProgress_when_request_isLower_than_bookPage() throws Exception { + //given + UserRoom userRoom = UserRoom.builder() + .currentPage(5) + .userPercentage(25.0) + .userId(1L) + .roomId(1L) + .build(); + + //when + int totalPageCount = 20; + boolean isUpdated = userRoom.updateUserProgress(3, totalPageCount); + + //then + assertThat(isUpdated).isFalse(); + assertThat(userRoom.getCurrentPage()).isEqualTo(5); + double ratio = (double) userRoom.getCurrentPage() / totalPageCount; + assertThat(userRoom.getUserPercentage()).isEqualTo(ratio * 100); + } +} \ No newline at end of file From ee10143e6c6e41aa09028dcf583359185a22b5b8 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:42:55 +0900 Subject: [PATCH 22/27] =?UTF-8?q?[teat]=20Record=20=ED=86=B5=ED=95=A9=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/RecordCreateControllerTest.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java b/src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java index f2fce4ecd..04279b9ce 100644 --- a/src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java +++ b/src/test/java/konkuk/thip/record/adapter/in/web/RecordCreateControllerTest.java @@ -12,10 +12,10 @@ import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; import konkuk.thip.room.adapter.out.persistence.CategoryJpaRepository; import konkuk.thip.room.adapter.out.persistence.RoomJpaRepository; -import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; -import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.jpa.*; import konkuk.thip.user.adapter.out.persistence.AliasJpaRepository; import konkuk.thip.user.adapter.out.persistence.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.UserRoomJpaRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -66,9 +66,13 @@ class RecordCreateControllerTest { @Autowired private RecordJpaRepository recordJpaRepository; + @Autowired + private UserRoomJpaRepository userRoomJpaRepository; + @AfterEach void tearDown() { recordJpaRepository.deleteAll(); + userRoomJpaRepository.deleteAll(); roomJpaRepository.deleteAll(); bookJpaRepository.deleteAll(); categoryJpaRepository.deleteAll(); @@ -86,6 +90,17 @@ private void saveUserAndRoom() { CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createCategory(alias)); RoomJpaEntity room = roomJpaRepository.save(TestEntityFactory.createRoom(book, category)); + + //UserRoomJpaEntity 생성 및 저장 + UserRoomJpaEntity userRoom = UserRoomJpaEntity.builder() + .currentPage(10) + .userPercentage(80.0) + .userRoomRole(UserRoomRole.HOST) + .userJpaEntity(user) + .roomJpaEntity(room) + .build(); + + userRoomJpaRepository.save(userRoom); } @Test From 3e9f385062e18cf85662f7aa59c571e5d074924c Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:43:11 +0900 Subject: [PATCH 23/27] =?UTF-8?q?[teat]=20record,=20userroom=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=95=84=EC=9A=94=ED=95=9C=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/common/exception/code/ErrorCode.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index 026250539..502e695b6 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -72,9 +72,18 @@ public enum ErrorCode implements ResponseCode { VOTE_CANNOT_BE_OVERVIEW(HttpStatus.BAD_REQUEST, 110001, "총평이 될 수 없는 VOTE 입니다."), INVALID_VOTE_PAGE_RANGE(HttpStatus.BAD_REQUEST, 110002, "VOTE의 page 값이 유효하지 않습니다."), + /** + * 120000 : record error + */ RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, 120000, "존재하지 않는 RECORD 입니다."), RECORD_CANNOT_BE_OVERVIEW(HttpStatus.BAD_REQUEST, 120001, "총평이 될 수 없는 RECORD 입니다."), INVALID_RECORD_PAGE_RANGE(HttpStatus.BAD_REQUEST, 120002, "RECORD의 page 값이 유효하지 않습니다."), + RECORD_CANNOT_WRITE_IN_EXPIRED_ROOM(HttpStatus.BAD_REQUEST, 120003, "만료된 방에는 기록을 남길 수 없습니다."), + + /** + * 130000 : userRoom error + */ + USER_ROOM_NOT_FOUND(HttpStatus.NOT_FOUND, 130000, "존재하지 않는 USER_ROOM (방과 사용자 관계) 입니다."), ; From 63e1675522bcfdad8d36831f58ec50ebb7373f69 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:43:32 +0900 Subject: [PATCH 24/27] =?UTF-8?q?[chore]=20VoteCreateService=20todo=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/vote/application/service/VoteCreateService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/konkuk/thip/vote/application/service/VoteCreateService.java b/src/main/java/konkuk/thip/vote/application/service/VoteCreateService.java index 22b7c38e2..96cf49e75 100644 --- a/src/main/java/konkuk/thip/vote/application/service/VoteCreateService.java +++ b/src/main/java/konkuk/thip/vote/application/service/VoteCreateService.java @@ -27,6 +27,7 @@ public class VoteCreateService implements VoteCreateUseCase { @Transactional @Override + //todo UserRoom 업데이트 로직 추가 필요!! public Long createVote(VoteCreateCommand command) { // 1. validate Vote vote = Vote.withoutId( From 5a3fdf7fcd6b74d7bdb8b57e67a4a399115a654a Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:48:00 +0900 Subject: [PATCH 25/27] =?UTF-8?q?[refactor]=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EA=B5=AC=EB=AC=B8=20=EB=B0=B1=EB=B6=84=EC=9C=A8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=98=A4=EB=A5=98=20=EC=A0=9C=EA=B1=B0=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/record/application/service/RecordCreateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java b/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java index 315157834..63adae25a 100644 --- a/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java +++ b/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java @@ -75,7 +75,7 @@ private void validateUserRoom(UserRoom userRoom) { if (!userRoom.canWriteOverview()) { String message = String.format( "총평(isOverview)은 사용자 진행률이 80%% 이상일 때만 가능합니다. 현재 사용자 진행률 = %.2f%%", - userRoom.getUserPercentage() * 100 + userRoom.getUserPercentage() ); throw new InvalidStateException(RECORD_CANNOT_BE_OVERVIEW, new IllegalStateException(message)); } From dc9212e59f113a44db331a9e74781b26f62151d2 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Mon, 7 Jul 2025 01:48:46 +0900 Subject: [PATCH 26/27] =?UTF-8?q?[refactor]=20userpercentage=20100=20?= =?UTF-8?q?=EB=84=98=EC=96=B4=EA=B0=88=20=EA=B2=BD=EC=9A=B0=20100=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/user/domain/UserRoom.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/user/domain/UserRoom.java b/src/main/java/konkuk/thip/user/domain/UserRoom.java index 2eed98669..fa1d233b0 100644 --- a/src/main/java/konkuk/thip/user/domain/UserRoom.java +++ b/src/main/java/konkuk/thip/user/domain/UserRoom.java @@ -28,7 +28,7 @@ public boolean canWriteOverview() { public boolean updateUserProgress(int requestPage, int totalPageCount) { if (currentPage < requestPage) { currentPage = requestPage; - userPercentage = ((double) currentPage / totalPageCount) * 100; + userPercentage = Math.min(((double) currentPage / totalPageCount) * 100, 100.0); return true; } From 05328fd7f6f3c40f7d04e213d0484253533dd1c0 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Tue, 8 Jul 2025 04:10:31 +0900 Subject: [PATCH 27/27] =?UTF-8?q?[refactor]=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EB=B6=88=EB=B3=80=EC=84=B1=20=EB=B3=B4?= =?UTF-8?q?=EC=9E=A5=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/record/adapter/in/web/RecordCommandController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java b/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java index b7834ff5b..c041b3737 100644 --- a/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java +++ b/src/main/java/konkuk/thip/record/adapter/in/web/RecordCommandController.java @@ -16,9 +16,9 @@ public class RecordCommandController { @PostMapping("/rooms/{roomId}/record") public BaseResponse createRecord( - @UserId Long userId, - @PathVariable Long roomId, - @Valid @RequestBody RecordCreateRequest recordCreateRequest) { + @UserId final Long userId, + @PathVariable final Long roomId, + @Valid @RequestBody final RecordCreateRequest recordCreateRequest) { return BaseResponse.ok( RecordCreateResponse.of( recordCreateUseCase.createRecord(recordCreateRequest.toCommand(roomId, userId))