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 e7e9e3762..33ec5f39a 100644 --- a/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java +++ b/src/main/java/konkuk/thip/record/application/service/RecordCreateService.java @@ -13,12 +13,11 @@ 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 org.springframework.transaction.annotation.Transactional; -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; @Override @Transactional @@ -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/room/application/service/RoomCreateService.java b/src/main/java/konkuk/thip/room/application/service/RoomCreateService.java index 48ae56693..83bad6feb 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,13 @@ 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.hostWithoutId(userId, savedRoomId); + roomParticipantCommandPort.save(roomParticipant); - return roomCommandPort.save(room); + return savedRoomId; } private Long resolveBookAndEnsurePage(String isbn) { 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 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); + } +} 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..45b71917e 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.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; @@ -22,14 +25,19 @@ @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; + private final RoomProgressHelper roomProgressHelper; + @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 +47,9 @@ public VoteCreateResult createVote(VoteCreateCommand command) { command.roomId() ); - validateVote(vote); + Room room = roomCommandPort.getByIdOrThrow(vote.getRoomId()); + Book book = bookCommandPort.findById(room.getBookId()); + validateVote(vote, book); // 2. vote 저장 Long savedVoteId = voteCommandPort.saveVote(vote); @@ -48,19 +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, Room progress 정보 update + roomProgressHelper.updateUserAndRoomProgress(vote.getCreatorId(), vote.getRoomId(), vote.getPage()); + 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 validateVote(Vote vote, Book book) { // 페이지 유효성 검증 vote.validatePage(book.getPageCount()); 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(); } 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/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()); + } } diff --git a/src/test/java/konkuk/thip/room/application/service/RoomJoinServiceTest.java b/src/test/java/konkuk/thip/room/application/service/RoomJoinServiceTest.java index 691240004..aa0719139 100644 --- a/src/test/java/konkuk/thip/room/application/service/RoomJoinServiceTest.java +++ b/src/test/java/konkuk/thip/room/application/service/RoomJoinServiceTest.java @@ -55,7 +55,7 @@ void alreadyParticipated() { given(roomCommandPort.findById(ROOM_ID)).willReturn(Optional.of(room)); given(roomParticipantCommandPort.findByUserIdAndRoomIdOptional(USER_ID, ROOM_ID)) - .willReturn(Optional.of(RoomParticipant.withoutId(USER_ID, ROOM_ID, MEMBER.getType()))); + .willReturn(Optional.of(RoomParticipant.memberWithoutId(USER_ID, ROOM_ID))); assertThatThrownBy(() -> 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)) 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 8ab2fcecf..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.deleteAllInBatch(); - 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 +} 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..cbaf3ac72 --- /dev/null +++ b/src/test/java/konkuk/thip/vote/application/service/VoteCreateServiceTest.java @@ -0,0 +1,116 @@ +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; +import static org.assertj.core.api.Assertions.within; + +@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 확인 + // 허용 오차범위를 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)); + } +}