From 70c30c18a4cd2db622a0423e399d1932a3eaaeef Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sun, 10 Aug 2025 17:56:53 +0900 Subject: [PATCH 01/13] =?UTF-8?q?[refactor]=20=EB=B0=A9=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20api=20service=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#?= =?UTF-8?q?181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 방장에 해당하는 RoomParticipant create & DB save 하는 코드 추가 --- .../application/service/RoomCreateService.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/konkuk/thip/room/application/service/RoomCreateService.java b/src/main/java/konkuk/thip/room/application/service/RoomCreateService.java index 48ae56693..e846e42ba 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomCreateService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomCreateService.java @@ -3,12 +3,14 @@ import konkuk.thip.book.application.port.out.BookApiQueryPort; import konkuk.thip.book.application.port.out.BookCommandPort; import konkuk.thip.book.domain.Book; -import konkuk.thip.common.exception.EntityNotFoundException; +import konkuk.thip.room.adapter.out.jpa.RoomParticipantRole; import konkuk.thip.room.application.port.in.RoomCreateUseCase; import konkuk.thip.room.application.port.in.dto.RoomCreateCommand; import konkuk.thip.room.application.port.out.RoomCommandPort; +import konkuk.thip.room.application.port.out.RoomParticipantCommandPort; import konkuk.thip.room.domain.Category; import konkuk.thip.room.domain.Room; +import konkuk.thip.room.domain.RoomParticipant; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,6 +20,7 @@ public class RoomCreateService implements RoomCreateUseCase { private final RoomCommandPort roomCommandPort; + private final RoomParticipantCommandPort roomParticipantCommandPort; private final BookCommandPort bookCommandPort; private final BookApiQueryPort bookApiQueryPort; @@ -42,11 +45,14 @@ public Long createRoom(RoomCreateCommand command, Long userId) { bookId, category ); + Long savedRoomId = roomCommandPort.save(room); - // TODO : 방 생성한 사람 (= api 호출 토큰에 포함된 userId) 이 해당 방에 속한 멤버라는 사실을 DB에 영속화 해야함 - // UserRoom 도메인이 정리되면 개발 ㄱㄱ + // 4. 방장 RoomParticipant 생성 및 DB save + RoomParticipant roomParticipant = RoomParticipant.withoutId( + userId, savedRoomId, RoomParticipantRole.HOST.getType()); + roomParticipantCommandPort.save(roomParticipant); - return roomCommandPort.save(room); + return savedRoomId; } private Long resolveBookAndEnsurePage(String isbn) { From 0163960d0cbe95ca9a1492a34e3a84573b4e6f7b Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sun, 10 Aug 2025 17:57:47 +0900 Subject: [PATCH 02/13] =?UTF-8?q?[test]=20=EB=B0=A9=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?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RoomParticipantRepository 의존성 추가 - RoomParticipant DB save 되는지 확인하는 테스트 코드 추가 --- .../adapter/in/web/RoomCreateAPITest.java | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateAPITest.java b/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateAPITest.java index 906dca091..806381c01 100644 --- a/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateAPITest.java +++ b/src/test/java/konkuk/thip/room/adapter/in/web/RoomCreateAPITest.java @@ -7,8 +7,10 @@ import konkuk.thip.common.util.TestEntityFactory; import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomParticipantJpaEntity; import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserRole; @@ -24,6 +26,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @@ -63,13 +66,17 @@ class RoomCreateAPITest { @Autowired private RoomJpaRepository roomJpaRepository; + @Autowired + private RoomParticipantJpaRepository roomParticipantJpaRepository; + @AfterEach void tearDown() { - roomJpaRepository.deleteAll(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAll(); - categoryJpaRepository.deleteAll(); - aliasJpaRepository.deleteAll(); + roomParticipantJpaRepository.deleteAllInBatch(); + roomJpaRepository.deleteAllInBatch(); + bookJpaRepository.deleteAllInBatch(); + userJpaRepository.deleteAllInBatch(); + categoryJpaRepository.deleteAllInBatch(); + aliasJpaRepository.deleteAllInBatch(); } private void saveUserAndCategory() { @@ -273,4 +280,35 @@ void room_create_book_not_exist() throws Exception { ); } + @Test + @DisplayName("방 생성에 성공하면, 방장의 정보가 DB에 저장된다.") + @Transactional // RoomParticipant -> Room, User 의 manyToOne 지연로딩을 위해 추가 + void room_create_room_participant_save_success() throws Exception { + //given : user, category, pageCount값이 있는 book 생성, request 생성 + saveUserAndCategory(); + saveBookWithPageCount(); + + Long userId = userJpaRepository.findAll().get(0).getUserId(); + Long bookId = bookJpaRepository.findAll().get(0).getBookId(); + Long categoryId = categoryJpaRepository.findAll().get(0).getCategoryId(); + + Map request = buildRoomCreateRequest(); + + //when + ResultActions result = mockMvc.perform(post("/rooms") + .requestAttr("userId", userId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request) + )); + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.roomId").exists()); + + RoomParticipantJpaEntity roomParticipantJpaEntity = roomParticipantJpaRepository.findAll().get(0); + assertThat(roomParticipantJpaEntity.getUserJpaEntity().getUserId()).isEqualTo(userId); + + RoomJpaEntity savedRoomJpaEntity = roomJpaRepository.findAll().get(0); + assertThat(roomParticipantJpaEntity.getRoomJpaEntity().getRoomId()).isEqualTo(savedRoomJpaEntity.getRoomId()); + } } From dad52a6d5a3e17018d11d9f872499827e7dd7978 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sun, 10 Aug 2025 17:58:25 +0900 Subject: [PATCH 03/13] =?UTF-8?q?[refactor]=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C=20userPercentage,=20roomPercenta?= =?UTF-8?q?ge=20=EA=B0=92=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 투표 생성한 사람의 userPercentage 값 수정 - 해당 방의 roomPercentage 값 수정 (기록 생성 서비스 코드 참고하였습니다) --- .../service/VoteCreateService.java | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) 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 543fe9deb..dfee7c94f 100644 --- a/src/main/java/konkuk/thip/vote/application/service/VoteCreateService.java +++ b/src/main/java/konkuk/thip/vote/application/service/VoteCreateService.java @@ -3,7 +3,10 @@ import konkuk.thip.book.application.port.out.BookCommandPort; import konkuk.thip.book.domain.Book; import konkuk.thip.room.application.port.out.RoomCommandPort; +import konkuk.thip.room.application.port.out.RoomParticipantCommandPort; +import konkuk.thip.room.application.service.validator.RoomParticipantValidator; import konkuk.thip.room.domain.Room; +import konkuk.thip.room.domain.RoomParticipant; import konkuk.thip.vote.application.port.in.VoteCreateUseCase; import konkuk.thip.vote.application.port.in.dto.VoteCreateCommand; import konkuk.thip.vote.application.port.in.dto.VoteCreateResult; @@ -22,14 +25,17 @@ @Slf4j public class VoteCreateService implements VoteCreateUseCase { + private final RoomParticipantValidator roomParticipantValidator; + private final RoomParticipantCommandPort roomParticipantCommandPort; private final VoteCommandPort voteCommandPort; private final RoomCommandPort roomCommandPort; private final BookCommandPort bookCommandPort; @Transactional @Override - //todo UserRoom 업데이트 로직 추가 필요!! public VoteCreateResult createVote(VoteCreateCommand command) { + roomParticipantValidator.validateUserIsRoomMember(command.roomId(), command.userId()); + // 1. validate Vote vote = Vote.withoutId( command.content(), @@ -39,7 +45,9 @@ public VoteCreateResult createVote(VoteCreateCommand command) { command.roomId() ); - validateVote(vote); + Room room = roomCommandPort.getByIdOrThrow(vote.getRoomId()); + Book book = bookCommandPort.findById(room.getBookId()); + validateVote(vote, room, book); // 2. vote 저장 Long savedVoteId = voteCommandPort.saveVote(vote); @@ -54,13 +62,31 @@ public VoteCreateResult createVote(VoteCreateCommand command) { .toList(); voteCommandPort.saveAllVoteItems(voteItems); + // 4. RoomParticipant 정보 update + updateRoomProgress(vote, room, book); + return VoteCreateResult.of(savedVoteId, command.roomId()); } - private void validateVote(Vote vote) { - Room room = roomCommandPort.getByIdOrThrow(vote.getRoomId()); - Book book = bookCommandPort.findById(room.getBookId()); + private void updateRoomProgress(Vote vote, Room room, Book book) { + RoomParticipant roomParticipant = roomParticipantCommandPort.getByUserIdAndRoomIdOrThrow(vote.getCreatorId(), vote.getRoomId()); + + if(roomParticipant.updateUserProgress(vote.getPage(), book.getPageCount())) { + // userPercentage가 업데이트되었으면 Room의 roomPercentage 업데이트 + List roomParticipantList = roomParticipantCommandPort.findAllByRoomId(vote.getRoomId()); + Double totalUserPercentage = roomParticipantList.stream() + .filter(participant -> !roomParticipant.getId().equals(participant.getId())) // 현재 업데이트 중인 사용자 제외 + .map(RoomParticipant::getUserPercentage) + .reduce(0.0, Double::sum); + totalUserPercentage += roomParticipant.getUserPercentage(); + room.updateRoomPercentage(totalUserPercentage / roomParticipantList.size()); + } + + roomCommandPort.update(room); + roomParticipantCommandPort.update(roomParticipant); + } + private void validateVote(Vote vote, Room room, Book book) { // 페이지 유효성 검증 vote.validatePage(book.getPageCount()); From 1afa3e21bf1e4650d1df1a84c03f426297a51145 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sun, 10 Aug 2025 17:58:58 +0900 Subject: [PATCH 04/13] =?UTF-8?q?[refactor]=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EB=A5=BC=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=EC=83=9D=EC=84=B1=20controller=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8,=20api=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/in/web/VoteCreateApiTest.java | 151 +++++++++++++++++ .../in/web/VoteCreateControllerTest.java | 154 +----------------- 2 files changed, 153 insertions(+), 152 deletions(-) create mode 100644 src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateApiTest.java diff --git a/src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateApiTest.java b/src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateApiTest.java new file mode 100644 index 000000000..17586a69f --- /dev/null +++ b/src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateApiTest.java @@ -0,0 +1,151 @@ +package konkuk.thip.vote.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.repository.BookJpaRepository; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomParticipantRole; +import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; +import konkuk.thip.vote.adapter.in.web.request.VoteCreateRequest; +import konkuk.thip.vote.adapter.out.jpa.VoteItemJpaEntity; +import konkuk.thip.vote.adapter.out.jpa.VoteJpaEntity; +import konkuk.thip.vote.adapter.out.persistence.repository.VoteItemJpaRepository; +import konkuk.thip.vote.adapter.out.persistence.repository.VoteJpaRepository; +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.jdbc.core.JdbcTemplate; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +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("[통합] 투표 생성 api 통합 테스트") +class VoteCreateApiTest { + @Autowired + private 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 RoomParticipantJpaRepository roomParticipantJpaRepository; + + @Autowired + private VoteJpaRepository voteJpaRepository; + + @Autowired + private VoteItemJpaRepository voteItemJpaRepository; + + @Autowired + private JdbcTemplate jdbcTemplate; + + @AfterEach + void tearDown() { + voteItemJpaRepository.deleteAllInBatch(); + voteJpaRepository.deleteAllInBatch(); + roomParticipantJpaRepository.deleteAllInBatch(); + roomJpaRepository.deleteAllInBatch(); + bookJpaRepository.deleteAllInBatch(); + userJpaRepository.deleteAllInBatch(); + categoryJpaRepository.deleteAllInBatch(); + aliasJpaRepository.deleteAllInBatch(); + } + + private void saveUserAndRoom() { + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(alias, "user")); + + BookJpaEntity book = bookJpaRepository.save(TestEntityFactory.createBook()); + CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createScienceCategory(alias)); + RoomJpaEntity room = roomJpaRepository.save(TestEntityFactory.createRoom(book, category)); + + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, user, RoomParticipantRole.MEMBER, 0.0)); + } + + @Test + @DisplayName("[페이지 넘버, 총평 여부, 투표 내용, List<투표 항목>] 을 받아, 투표를 생성한다.") + void vote_create_success() throws Exception { + //given : user, room, request 생성 + saveUserAndRoom(); + + int page = 10; + boolean isOverview = false; + String content = "투표 내용 입니다."; + List voteItems = List.of( + new VoteCreateRequest.VoteItemCreateRequest("찬성"), + new VoteCreateRequest.VoteItemCreateRequest("반대") + ); + + VoteCreateRequest request = new VoteCreateRequest( + page, isOverview, content, voteItems + ); + + Long userId = userJpaRepository.findAll().get(0).getUserId(); + Long roomId = roomJpaRepository.findAll().get(0).getRoomId(); + + //when : 투표 생성 api 호출 (filter 통과 없이) + ResultActions result = mockMvc.perform(post("/rooms/{roomId}/vote", roomId) + .requestAttr("userId", userId) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request) + )); + + //then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.voteId").exists()); + + String json = result.andReturn().getResponse().getContentAsString(); + JsonNode jsonNode = objectMapper.readTree(json); + Long voteId = jsonNode.path("data").path("voteId").asLong(); + + VoteJpaEntity voteJpaEntity = voteJpaRepository.findById(voteId).orElse(null); + List voteItemJpaEntityList = voteItemJpaRepository.findAllByVoteJpaEntity_PostId(voteJpaEntity.getPostId()); + + assertThat(voteJpaEntity.getUserJpaEntity().getUserId()).isEqualTo(userId); + assertThat(voteJpaEntity.getRoomJpaEntity().getRoomId()).isEqualTo(roomId); + assertThat(voteJpaEntity.getPage()).isEqualTo(page); + assertThat(voteJpaEntity.getContent()).isEqualTo(content); + + assertThat(voteItemJpaEntityList).hasSize(2) + .extracting(VoteItemJpaEntity::getItemName) + .containsExactlyInAnyOrder("찬성", "반대"); + } +} 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 389c4285b..06238791a 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 @@ -1,25 +1,6 @@ package konkuk.thip.vote.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.repository.BookJpaRepository; -import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; -import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; -import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; -import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; -import konkuk.thip.room.domain.Category; -import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; -import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; -import konkuk.thip.user.adapter.out.jpa.UserRole; -import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; -import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; -import konkuk.thip.vote.adapter.in.web.request.VoteCreateRequest; -import konkuk.thip.vote.adapter.out.jpa.VoteItemJpaEntity; -import konkuk.thip.vote.adapter.out.jpa.VoteJpaEntity; -import konkuk.thip.vote.adapter.out.persistence.repository.VoteItemJpaRepository; -import konkuk.thip.vote.adapter.out.persistence.repository.VoteJpaRepository; -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; @@ -28,15 +9,11 @@ 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.time.LocalDate; import java.util.List; 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; @@ -45,7 +22,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) -@DisplayName("[통합] VoteCreateController 테스트") +@DisplayName("[단위] 투표 생성 api controller 단위 테스트") class VoteCreateControllerTest { @Autowired @@ -54,133 +31,6 @@ class VoteCreateControllerTest { @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 VoteJpaRepository voteJpaRepository; - - @Autowired - private VoteItemJpaRepository voteItemJpaRepository; - - @AfterEach - void tearDown() { - voteItemJpaRepository.deleteAll(); - voteJpaRepository.deleteAll(); - roomJpaRepository.deleteAll(); - bookJpaRepository.deleteAll(); - userJpaRepository.deleteAll(); - categoryJpaRepository.deleteAll(); - aliasJpaRepository.deleteAll(); - } - - private void saveUserAndRoom() { - AliasJpaEntity alias = aliasJpaRepository.save(AliasJpaEntity.builder() - .value("책벌레") - .color("blue") - .imageUrl("http://image.url") - .build()); - - UserJpaEntity user = userJpaRepository.save(UserJpaEntity.builder() - .oauth2Id("kakao_432708231") - .nickname("User1") - .nicknameUpdatedAt(LocalDate.now().minusMonths(7)) - .role(UserRole.USER) - .aliasForUserJpaEntity(alias) - .build()); - - BookJpaEntity book = bookJpaRepository.save(BookJpaEntity.builder() - .title("작별하지 않는다") - .isbn("9788954682152") - .authorName("한강") - .bestSeller(false) - .publisher("문학동네") - .imageUrl("https://image1.jpg") - .pageCount(300) - .description("한강의 소설") - .build()); - - - CategoryJpaEntity category = categoryJpaRepository.save(CategoryJpaEntity.builder() - .value(Category.SCIENCE_IT.getValue()) - .imageUrl(Category.SCIENCE_IT.getImageUrl()) - .aliasForCategoryJpaEntity(alias) - .build()); - - RoomJpaEntity room = roomJpaRepository.save(RoomJpaEntity.builder() - .title("한강 독서모임") - .description("한강 작품 읽기 모임") - .isPublic(true) - .roomPercentage(0.0) - .startDate(LocalDate.now().plusDays(2)) - .endDate(LocalDate.now().plusDays(30)) - .recruitCount(10) - .bookJpaEntity(book) - .categoryJpaEntity(category) - .build()); - } - - @Test - @DisplayName("[페이지 넘버, 총평 여부, 투표 내용, List<투표 항목>] 을 받아, 투표를 생성한다.") - void vote_create_success() throws Exception { - //given : user, room, request 생성 - saveUserAndRoom(); - - int page = 10; - boolean isOverview = false; - String content = "투표 내용 입니다."; - List voteItems = List.of( - new VoteCreateRequest.VoteItemCreateRequest("찬성"), - new VoteCreateRequest.VoteItemCreateRequest("반대") - ); - - VoteCreateRequest request = new VoteCreateRequest( - page, isOverview, content, voteItems - ); - - Long userId = userJpaRepository.findAll().get(0).getUserId(); - Long roomId = roomJpaRepository.findAll().get(0).getRoomId(); - - //when : 투표 생성 api 호출 (filter 통과 없이) - ResultActions result = mockMvc.perform(post("/rooms/{roomId}/vote", roomId) - .requestAttr("userId", userId) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request) - )); - - //then - result.andExpect(status().isOk()) - .andExpect(jsonPath("$.data.voteId").exists()); - - String json = result.andReturn().getResponse().getContentAsString(); - JsonNode jsonNode = objectMapper.readTree(json); - Long voteId = jsonNode.path("data").path("voteId").asLong(); - - VoteJpaEntity voteJpaEntity = voteJpaRepository.findById(voteId).orElse(null); - List voteItemJpaEntityList = voteItemJpaRepository.findAllByVoteJpaEntity_PostId(voteJpaEntity.getPostId()); - - assertThat(voteJpaEntity.getUserJpaEntity().getUserId()).isEqualTo(userId); - assertThat(voteJpaEntity.getRoomJpaEntity().getRoomId()).isEqualTo(roomId); - assertThat(voteJpaEntity.getPage()).isEqualTo(page); - assertThat(voteJpaEntity.getContent()).isEqualTo(content); - - assertThat(voteItemJpaEntityList).hasSize(2) - .extracting(VoteItemJpaEntity::getItemName) - .containsExactlyInAnyOrder("찬성", "반대"); - } - @Test @DisplayName("[page]가 누락되었을 때 400 Bad Request 반환") void vote_create_page_null() throws Exception { @@ -353,4 +203,4 @@ void vote_create_vote_item_name_too_long() throws Exception { .andExpect(jsonPath("$.code").value(API_INVALID_PARAM.getCode())) .andExpect(jsonPath("$.message", containsString("투표 항목 이름은 최대 20자입니다."))); } -} \ No newline at end of file +} From d8674b95f56e7a6a973a390183ef76220cf206bd Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Sun, 10 Aug 2025 17:59:39 +0900 Subject: [PATCH 05/13] =?UTF-8?q?[test]=20=ED=88=AC=ED=91=9C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20DB=EC=97=90=20userPercentage,=20roomPer?= =?UTF-8?q?centage=20=EA=B0=92=20=EC=9E=98=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EB=90=98=EB=8A=94=EC=A7=80=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/common/util/TestEntityFactory.java | 16 +++ .../service/VoteCreateServiceTest.java | 114 ++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/test/java/konkuk/thip/vote/application/service/VoteCreateServiceTest.java diff --git a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java index 0faddb97c..d5e971a56 100644 --- a/src/test/java/konkuk/thip/common/util/TestEntityFactory.java +++ b/src/test/java/konkuk/thip/common/util/TestEntityFactory.java @@ -103,6 +103,22 @@ public static BookJpaEntity createBook() { .build(); } + /** + * page 값을 지정할 수 있는 custom 생성자 + */ + public static BookJpaEntity createBook(int page) { + return BookJpaEntity.builder() + .title("책제목") + .authorName("저자") + .isbn(UUID.randomUUID().toString().replace("-", "").substring(0, 13)) + .bestSeller(false) + .publisher("출판사") + .imageUrl("img") + .pageCount(page) + .description("설명") + .build(); + } + public static BookJpaEntity createBookWithISBN(String isbn) { return BookJpaEntity.builder() .title("책제목") diff --git a/src/test/java/konkuk/thip/vote/application/service/VoteCreateServiceTest.java b/src/test/java/konkuk/thip/vote/application/service/VoteCreateServiceTest.java new file mode 100644 index 000000000..118d2b261 --- /dev/null +++ b/src/test/java/konkuk/thip/vote/application/service/VoteCreateServiceTest.java @@ -0,0 +1,114 @@ +package konkuk.thip.vote.application.service; + +import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; +import konkuk.thip.book.adapter.out.persistence.repository.BookJpaRepository; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.room.adapter.out.jpa.CategoryJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomParticipantJpaEntity; +import konkuk.thip.room.adapter.out.jpa.RoomParticipantRole; +import konkuk.thip.room.adapter.out.persistence.repository.RoomJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.category.CategoryJpaRepository; +import konkuk.thip.room.adapter.out.persistence.repository.roomparticipant.RoomParticipantJpaRepository; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; +import konkuk.thip.vote.adapter.out.persistence.repository.VoteItemJpaRepository; +import konkuk.thip.vote.adapter.out.persistence.repository.VoteJpaRepository; +import konkuk.thip.vote.application.port.in.dto.VoteCreateCommand; +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.test.context.ActiveProfiles; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@DisplayName("[단위] 투표 생성 service 단위 테스트") +class VoteCreateServiceTest { + + @Autowired + private AliasJpaRepository aliasJpaRepository; + + @Autowired + private UserJpaRepository userJpaRepository; + + @Autowired + private CategoryJpaRepository categoryJpaRepository; + + @Autowired + private BookJpaRepository bookJpaRepository; + + @Autowired + private RoomJpaRepository roomJpaRepository; + + @Autowired + private RoomParticipantJpaRepository roomParticipantJpaRepository; + + @Autowired + private VoteJpaRepository voteJpaRepository; + + @Autowired + private VoteItemJpaRepository voteItemJpaRepository; + + @Autowired + private VoteCreateService voteCreateService; + + @AfterEach + void tearDown() { + voteItemJpaRepository.deleteAllInBatch(); + voteJpaRepository.deleteAllInBatch(); + roomParticipantJpaRepository.deleteAllInBatch(); + roomJpaRepository.deleteAllInBatch(); + bookJpaRepository.deleteAllInBatch(); + userJpaRepository.deleteAllInBatch(); + categoryJpaRepository.deleteAllInBatch(); + aliasJpaRepository.deleteAllInBatch(); + } + + @Test + @DisplayName("유저가 투표를 생성하면, 해당 유저의 [RoomParticipant의 currentPage, userPercentage]와 해당 방의 [Room의 roomPercentage] 값이 변경된다.") + void vote_create_room_participant_and_room_percentage_update() throws Exception { + //given + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createScienceAlias()); + UserJpaEntity me = userJpaRepository.save(TestEntityFactory.createUser(alias, "me")); + + BookJpaEntity book = bookJpaRepository.save(TestEntityFactory.createBook(369)); + CategoryJpaEntity category = categoryJpaRepository.save(TestEntityFactory.createScienceCategory(alias)); + RoomJpaEntity room = roomJpaRepository.save(TestEntityFactory.createRoom(book, category)); + + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, me, RoomParticipantRole.MEMBER, 0.0)); + + UserJpaEntity user = userJpaRepository.save(TestEntityFactory.createUser(alias, "user")); + roomParticipantJpaRepository.save(TestEntityFactory.createRoomParticipant(room, user, RoomParticipantRole.MEMBER, 7.7)); // userPercentage = 7.7 인 RoomParticipant 생성 + + //when + List voteItems = List.of( + new VoteCreateCommand.VoteItemCreateCommand("투표 항목1"), + new VoteCreateCommand.VoteItemCreateCommand("투표 항목2") + ); + + VoteCreateCommand command = new VoteCreateCommand( + me.getUserId(), room.getRoomId(), 89, false, "투표 내용", voteItems + ); + + //then + voteCreateService.createVote(command); + + //then + RoomParticipantJpaEntity roomParticipant = roomParticipantJpaRepository.findByUserIdAndRoomId(me.getUserId(), room.getRoomId()).get(); + RoomJpaEntity refreshRoom = roomJpaRepository.findAll().get(0); + + // userPercentage, roomPercentage 값 update 확인 + assertThat(roomParticipant.getUserPercentage()).isEqualTo((double) 89 / 369 * 100); + assertThat(refreshRoom.getRoomPercentage()).isEqualTo((7.7 + (double) 89 / 369 * 100) / 2); + } +} From 5d5f23f3375d5c198dd1feaf8d8766730b7ba391 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Thu, 14 Aug 2025 16:41:57 +0900 Subject: [PATCH 06/13] =?UTF-8?q?[refactor]=20RoomParticipant=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=83=9D=EC=84=B1=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20member=20=EC=83=9D=EC=84=B1=EC=9A=A9,=20ho?= =?UTF-8?q?st=20=EC=83=9D=EC=84=B1=EC=9A=A9=EC=9C=BC=EB=A1=9C=20=EA=B5=AC?= =?UTF-8?q?=EB=B6=84=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konkuk/thip/room/domain/RoomParticipant.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/room/domain/RoomParticipant.java b/src/main/java/konkuk/thip/room/domain/RoomParticipant.java index c4f2131b5..926a9d3fe 100644 --- a/src/main/java/konkuk/thip/room/domain/RoomParticipant.java +++ b/src/main/java/konkuk/thip/room/domain/RoomParticipant.java @@ -21,13 +21,23 @@ public class RoomParticipant extends BaseDomainEntity { private Long roomId; - public static RoomParticipant withoutId(Long userId, Long roomId, String roomParticipantRole) { + public static RoomParticipant memberWithoutId(Long userId, Long roomId) { return RoomParticipant.builder() .currentPage(0) .userPercentage(0.0) .userId(userId) .roomId(roomId) - .roomParticipantRole(roomParticipantRole) + .roomParticipantRole(RoomParticipantRole.MEMBER.getType()) + .build(); + } + + public static RoomParticipant hostWithoutId(Long userId, Long roomId) { + return RoomParticipant.builder() + .currentPage(0) + .userPercentage(0.0) + .userId(userId) + .roomId(roomId) + .roomParticipantRole(RoomParticipantRole.HOST.getType()) .build(); } From 1d110f33db56c2a0b022d22928fa625ccb84852f Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Thu, 14 Aug 2025 16:43:02 +0900 Subject: [PATCH 07/13] =?UTF-8?q?[refactor]=20RoomParticipant=EB=A5=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=98=EB=A0=A4=EB=8A=94=20=EB=AA=A9?= =?UTF-8?q?=EC=A0=81=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=A5=BC=20=ED=98=B8=EC=B6=9C=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/room/application/service/RoomCreateService.java | 3 +-- .../konkuk/thip/room/application/service/RoomJoinService.java | 2 +- .../thip/room/application/service/RoomJoinServiceTest.java | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/konkuk/thip/room/application/service/RoomCreateService.java b/src/main/java/konkuk/thip/room/application/service/RoomCreateService.java index e846e42ba..83bad6feb 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomCreateService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomCreateService.java @@ -48,8 +48,7 @@ public Long createRoom(RoomCreateCommand command, Long userId) { Long savedRoomId = roomCommandPort.save(room); // 4. 방장 RoomParticipant 생성 및 DB save - RoomParticipant roomParticipant = RoomParticipant.withoutId( - userId, savedRoomId, RoomParticipantRole.HOST.getType()); + RoomParticipant roomParticipant = RoomParticipant.hostWithoutId(userId, savedRoomId); roomParticipantCommandPort.save(roomParticipant); return savedRoomId; diff --git a/src/main/java/konkuk/thip/room/application/service/RoomJoinService.java b/src/main/java/konkuk/thip/room/application/service/RoomJoinService.java index 5ecbfd252..f95a239b8 100644 --- a/src/main/java/konkuk/thip/room/application/service/RoomJoinService.java +++ b/src/main/java/konkuk/thip/room/application/service/RoomJoinService.java @@ -75,7 +75,7 @@ private void handleJoin(RoomJoinCommand roomJoinCommand, Optional roomJoinService.changeJoinState(command)) .isInstanceOf(BusinessException.class) @@ -100,7 +100,7 @@ void notParticipated() { @DisplayName("정상적으로 취소 시 참여자 제거 및 인원수 감소") void successCancel() { RoomJoinCommand command = new RoomJoinCommand(USER_ID, ROOM_ID, "cancel"); - RoomParticipant participant = RoomParticipant.withoutId(USER_ID, ROOM_ID, MEMBER.getType()); + RoomParticipant participant = RoomParticipant.memberWithoutId(USER_ID, ROOM_ID); given(roomCommandPort.findById(ROOM_ID)).willReturn(Optional.of(room)); given(roomParticipantCommandPort.findByUserIdAndRoomIdOptional(USER_ID, ROOM_ID)) From db185f3ee71300a653f58ddd1e72f2b90ebb7362 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Thu, 14 Aug 2025 16:43:30 +0900 Subject: [PATCH 08/13] =?UTF-8?q?[refactor]=20VoteItem=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=83=9D=EC=84=B1=EC=8B=9C=20count?= =?UTF-8?q?=EA=B0=92=EC=9D=84=200=EC=9C=BC=EB=A1=9C=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=ED=95=98=EB=8F=84=EB=A1=9D=20=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/vote/domain/VoteItem.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/konkuk/thip/vote/domain/VoteItem.java b/src/main/java/konkuk/thip/vote/domain/VoteItem.java index c9109bfd2..9d4400ae1 100644 --- a/src/main/java/konkuk/thip/vote/domain/VoteItem.java +++ b/src/main/java/konkuk/thip/vote/domain/VoteItem.java @@ -22,11 +22,11 @@ public class VoteItem extends BaseDomainEntity { private Long voteId; - public static VoteItem withoutId(String itemName, int count, Long voteId) { + public static VoteItem withoutId(String itemName, Long voteId) { return VoteItem.builder() .id(null) .itemName(itemName) - .count(count) + .count(0) .voteId(voteId) .build(); } From 34e46ae143ed650a381e2a5023f0302b605113ed Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Thu, 14 Aug 2025 16:44:05 +0900 Subject: [PATCH 09/13] =?UTF-8?q?[feat]=20=EB=B0=A9=20=EC=B0=B8=EC=97=AC?= =?UTF-8?q?=EC=9E=90=EC=9D=98=20=ED=99=9C=EB=8F=99=20=EC=9D=B4=ED=9B=84,?= =?UTF-8?q?=20=EB=B0=A9=20=EC=B0=B8=EC=97=AC=EC=9E=90=EC=99=80=20=EB=B0=A9?= =?UTF-8?q?=EC=9D=98=20progress=20=EA=B0=92=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=ED=97=AC=ED=8D=BC=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=B6=94=EA=B0=80=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/helper/RoomProgressHelper.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java diff --git a/src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java b/src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java new file mode 100644 index 000000000..db561b432 --- /dev/null +++ b/src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java @@ -0,0 +1,46 @@ +package konkuk.thip.roompost.application.service.helper; + +import konkuk.thip.book.application.port.out.BookCommandPort; +import konkuk.thip.book.domain.Book; +import konkuk.thip.room.application.port.out.RoomCommandPort; +import konkuk.thip.room.application.port.out.RoomParticipantCommandPort; +import konkuk.thip.room.domain.Room; +import konkuk.thip.room.domain.RoomParticipant; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class RoomProgressHelper { + + private final RoomParticipantCommandPort roomParticipantCommandPort; + private final RoomCommandPort roomCommandPort; + private final BookCommandPort bookCommandPort; + + @Transactional + public void updateUserAndRoomProgress(Long userId, Long roomId, int currentPage) { + RoomParticipant roomParticipant = roomParticipantCommandPort.getByUserIdAndRoomIdOrThrow(userId, roomId); + Room room = roomCommandPort.getByIdOrThrow(roomId); + Book book = bookCommandPort.findById(room.getBookId()); + + // 1. 유저 진행률 update + boolean updated = roomParticipant.updateUserProgress(currentPage, book.getPageCount()); + if (!updated) return; // update 되지 않았으면 종료 + + // 2. 방 평균 진행률 update + List all = roomParticipantCommandPort.findAllByRoomId(roomId); + double total = all.stream() + .filter(p -> !roomParticipant.getId().equals(p.getId())) // 현재 유저 제외 + .mapToDouble(RoomParticipant::getUserPercentage) + .sum(); + total += roomParticipant.getUserPercentage(); + room.updateRoomPercentage(total / all.size()); + + // 3. 영속화 + roomCommandPort.update(room); + roomParticipantCommandPort.update(roomParticipant); + } +} From f2c3faf595831cc0d1277de27eac3d6731d72f35 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Thu, 14 Aug 2025 16:44:26 +0900 Subject: [PATCH 10/13] =?UTF-8?q?[refactor]=20=ED=97=AC=ED=8D=BC=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=8F=84=EC=9E=85=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=9D=BC=20=EA=B8=B0=EC=A1=B4=20=EA=B8=B0=EB=A1=9D,?= =?UTF-8?q?=20=ED=88=AC=ED=91=9C=20=EC=83=9D=EC=84=B1=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/RecordCreateService.java | 27 +++------------- .../service/VoteCreateService.java | 31 +++++-------------- 2 files changed, 12 insertions(+), 46 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 c0fda34de..69d0601ff 100644 --- a/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java +++ b/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java @@ -14,11 +14,10 @@ import konkuk.thip.room.application.service.validator.RoomParticipantValidator; import konkuk.thip.room.domain.Room; import konkuk.thip.room.domain.RoomParticipant; +import konkuk.thip.roompost.application.service.helper.RoomProgressHelper; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; - import static konkuk.thip.common.exception.code.ErrorCode.RECORD_CANNOT_BE_OVERVIEW; @Service @@ -31,6 +30,7 @@ public class RecordCreateService implements RecordCreateUseCase { private final RoomParticipantCommandPort roomParticipantCommandPort; private final RoomParticipantValidator roomParticipantValidator; + private final RoomProgressHelper roomProgressHelper; @Transactional @Override @@ -57,32 +57,15 @@ public RecordCreateResult createRecord(RecordCreateCommand command) { validateRoomParticipant(roomParticipant, command.isOverview()); validateRecord(record, book); - // 4. RoomParticipant의 currentPage, userPercentage 업데이트 - updateRoomProgress(roomParticipant, record, book, room); - - // 5. Record 저장 + // 4. 문제없는 경우 Record 저장 Long newRecordId = recordCommandPort.saveRecord(record); - // 6. Room, RoomParticipant 업데이트 - roomCommandPort.update(room); - roomParticipantCommandPort.update(roomParticipant); + // 5. RoomParticipant, Room progress 정보 update + roomProgressHelper.updateUserAndRoomProgress(record.getCreatorId(), record.getRoomId(), record.getPage()); return RecordCreateResult.of(newRecordId, command.roomId()); } - private void updateRoomProgress(RoomParticipant roomParticipant, Record record, Book book, Room room) { - if(roomParticipant.updateUserProgress(record.getPage(), book.getPageCount())) { - // userPercentage가 업데이트되었으면 Room의 roomPercentage 업데이트 - List roomParticipantList = roomParticipantCommandPort.findAllByRoomId(record.getRoomId()); - Double totalUserPercentage = roomParticipantList.stream() - .filter(participant -> !roomParticipant.getId().equals(participant.getId())) // 현재 업데이트 중인 사용자 제외 - .map(RoomParticipant::getUserPercentage) - .reduce(0.0, Double::sum); - totalUserPercentage += roomParticipant.getUserPercentage(); - room.updateRoomPercentage(totalUserPercentage / roomParticipantList.size()); - } - } - private void validateRoomParticipant(RoomParticipant roomParticipant, boolean isOverview) { // UserRoom의 총평 작성 가능 여부 검증 if (!roomParticipant.canWriteOverview() && isOverview) { 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 dfee7c94f..45b71917e 100644 --- a/src/main/java/konkuk/thip/vote/application/service/VoteCreateService.java +++ b/src/main/java/konkuk/thip/vote/application/service/VoteCreateService.java @@ -6,7 +6,7 @@ import konkuk.thip.room.application.port.out.RoomParticipantCommandPort; import konkuk.thip.room.application.service.validator.RoomParticipantValidator; import konkuk.thip.room.domain.Room; -import konkuk.thip.room.domain.RoomParticipant; +import konkuk.thip.roompost.application.service.helper.RoomProgressHelper; import konkuk.thip.vote.application.port.in.VoteCreateUseCase; import konkuk.thip.vote.application.port.in.dto.VoteCreateCommand; import konkuk.thip.vote.application.port.in.dto.VoteCreateResult; @@ -31,6 +31,8 @@ public class VoteCreateService implements VoteCreateUseCase { private final RoomCommandPort roomCommandPort; private final BookCommandPort bookCommandPort; + private final RoomProgressHelper roomProgressHelper; + @Transactional @Override public VoteCreateResult createVote(VoteCreateCommand command) { @@ -47,7 +49,7 @@ public VoteCreateResult createVote(VoteCreateCommand command) { Room room = roomCommandPort.getByIdOrThrow(vote.getRoomId()); Book book = bookCommandPort.findById(room.getBookId()); - validateVote(vote, room, book); + validateVote(vote, book); // 2. vote 저장 Long savedVoteId = voteCommandPort.saveVote(vote); @@ -56,37 +58,18 @@ public VoteCreateResult createVote(VoteCreateCommand command) { List voteItems = command.voteItemCreateCommands().stream() .map(itemCmd -> VoteItem.withoutId( itemCmd.itemName(), - 0, savedVoteId )) .toList(); voteCommandPort.saveAllVoteItems(voteItems); - // 4. RoomParticipant 정보 update - updateRoomProgress(vote, room, book); + // 4. RoomParticipant, Room progress 정보 update + roomProgressHelper.updateUserAndRoomProgress(vote.getCreatorId(), vote.getRoomId(), vote.getPage()); return VoteCreateResult.of(savedVoteId, command.roomId()); } - private void updateRoomProgress(Vote vote, Room room, Book book) { - RoomParticipant roomParticipant = roomParticipantCommandPort.getByUserIdAndRoomIdOrThrow(vote.getCreatorId(), vote.getRoomId()); - - if(roomParticipant.updateUserProgress(vote.getPage(), book.getPageCount())) { - // userPercentage가 업데이트되었으면 Room의 roomPercentage 업데이트 - List roomParticipantList = roomParticipantCommandPort.findAllByRoomId(vote.getRoomId()); - Double totalUserPercentage = roomParticipantList.stream() - .filter(participant -> !roomParticipant.getId().equals(participant.getId())) // 현재 업데이트 중인 사용자 제외 - .map(RoomParticipant::getUserPercentage) - .reduce(0.0, Double::sum); - totalUserPercentage += roomParticipant.getUserPercentage(); - room.updateRoomPercentage(totalUserPercentage / roomParticipantList.size()); - } - - roomCommandPort.update(room); - roomParticipantCommandPort.update(roomParticipant); - } - - private void validateVote(Vote vote, Room room, Book book) { + private void validateVote(Vote vote, Book book) { // 페이지 유효성 검증 vote.validatePage(book.getPageCount()); From ef2444b8f4ba58055e88d7c29fe20b7e89564013 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Thu, 14 Aug 2025 16:45:23 +0900 Subject: [PATCH 11/13] =?UTF-8?q?[refactor]=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20api=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EC=97=90?= =?UTF-8?q?=EC=84=9C=20progress=20=EA=B0=92=20=EA=B2=80=EC=A6=9D=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - double 값의 비교이므로 isCloseTo 메서드로 검증하도록 수정 --- .../vote/application/service/VoteCreateServiceTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/konkuk/thip/vote/application/service/VoteCreateServiceTest.java b/src/test/java/konkuk/thip/vote/application/service/VoteCreateServiceTest.java index 118d2b261..cbaf3ac72 100644 --- a/src/test/java/konkuk/thip/vote/application/service/VoteCreateServiceTest.java +++ b/src/test/java/konkuk/thip/vote/application/service/VoteCreateServiceTest.java @@ -28,6 +28,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; @SpringBootTest @ActiveProfiles("test") @@ -108,7 +109,8 @@ void vote_create_room_participant_and_room_percentage_update() throws Exception RoomJpaEntity refreshRoom = roomJpaRepository.findAll().get(0); // userPercentage, roomPercentage 값 update 확인 - assertThat(roomParticipant.getUserPercentage()).isEqualTo((double) 89 / 369 * 100); - assertThat(refreshRoom.getRoomPercentage()).isEqualTo((7.7 + (double) 89 / 369 * 100) / 2); + // 허용 오차범위를 10의 -6제곱(= 0.000001) 로 설정 + assertThat(roomParticipant.getUserPercentage()).isCloseTo((double) 89 / 369 * 100, within(1e-6)); + assertThat(refreshRoom.getRoomPercentage()).isCloseTo((7.7 + (double) 89 / 369 * 100) / 2, within(1e-6)); } } From 3cc47d59bb13c16bbc2b61804b842851418b0400 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Thu, 14 Aug 2025 22:39:05 +0900 Subject: [PATCH 12/13] =?UTF-8?q?[refactor]=20helper=20service=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/helper/RoomProgressHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java b/src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java index db561b432..aab119d35 100644 --- a/src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java +++ b/src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java @@ -2,6 +2,7 @@ import konkuk.thip.book.application.port.out.BookCommandPort; import konkuk.thip.book.domain.Book; +import konkuk.thip.common.annotation.HelperService; import konkuk.thip.room.application.port.out.RoomCommandPort; import konkuk.thip.room.application.port.out.RoomParticipantCommandPort; import konkuk.thip.room.domain.Room; @@ -12,7 +13,7 @@ import java.util.List; -@Service +@HelperService @RequiredArgsConstructor public class RoomProgressHelper { From 3b7cbc541142ab6087aa44420d24058b8a4674c6 Mon Sep 17 00:00:00 2001 From: seongjunnoh Date: Thu, 14 Aug 2025 22:39:31 +0900 Subject: [PATCH 13/13] =?UTF-8?q?[refactor]=20helper=20service=20=EC=97=90?= =?UTF-8?q?=20transactional=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=82=AD=EC=A0=9C=20(#181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roompost/application/service/helper/RoomProgressHelper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java b/src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java index aab119d35..b6d7cbb62 100644 --- a/src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java +++ b/src/main/java/konkuk/thip/roompost/application/service/helper/RoomProgressHelper.java @@ -21,7 +21,6 @@ public class RoomProgressHelper { private final RoomCommandPort roomCommandPort; private final BookCommandPort bookCommandPort; - @Transactional public void updateUserAndRoomProgress(Long userId, Long roomId, int currentPage) { RoomParticipant roomParticipant = roomParticipantCommandPort.getByUserIdAndRoomIdOrThrow(userId, roomId); Room room = roomCommandPort.getByIdOrThrow(roomId);