Skip to content

Commit

Permalink
Merge pull request #115 from modu-menu/feat/add-save-vote
Browse files Browse the repository at this point in the history
투표 생성 API 구현
  • Loading branch information
eelseungmin authored Jun 4, 2024
2 parents 0d5fdf9 + 6bc3a79 commit 34fde45
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/main/java/modu/menu/core/response/ErrorMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public enum ErrorMessage {

// Vote
NOT_EXIST_VOTE("해당 ID와 일치하는 투표가 존재하지 않습니다."),
CANT_INVITE_TO_END_VOTE("종료된 투표에는 초대할 수 없습니다.");
CANT_INVITE_TO_END_VOTE("종료된 투표에는 초대할 수 없습니다."),
NOT_EXIST_PLACE("해당 ID와 일치하는 음식점이 존재하지 않습니다.");

// domainEx1

Expand Down
20 changes: 20 additions & 0 deletions src/main/java/modu/menu/vote/api/VoteController.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import modu.menu.core.response.ApiFailResponse;
import modu.menu.core.response.ApiSuccessResponse;
import modu.menu.core.response.ErrorMessage;
import modu.menu.vote.api.request.SaveVoteRequest;
import modu.menu.vote.api.request.VoteResultRequest;
import modu.menu.vote.api.response.VoteResultResponse;
import modu.menu.vote.service.VoteService;
Expand All @@ -29,6 +30,25 @@ public class VoteController {

private final VoteService voteService;

@Operation(summary = "투표 생성", description = "투표를 생성합니다.")
@SecurityRequirement(name = "Authorization")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "투표 생성이 성공한 경우"),
@ApiResponse(responseCode = "400", description = "RequestBody가 형식에 맞지 않을 경우", content = @Content(schema = @Schema(implementation = ApiFailResponse.class))),
@ApiResponse(responseCode = "401", description = "토큰 인증이 실패한 경우", content = @Content(schema = @Schema(implementation = ApiFailResponse.class))),
@ApiResponse(responseCode = "404", description = "투표에 포함시키려는 음식점이 존재하지 않을 경우", content = @Content(schema = @Schema(implementation = ApiFailResponse.class))),
@ApiResponse(responseCode = "500", description = "그 외 서버에서 처리하지 못한 에러가 발생했을 경우", content = @Content(schema = @Schema(implementation = ApiFailResponse.class)))
})
@PostMapping("/api/vote")
public ResponseEntity<ApiSuccessResponse> saveVote(
@Valid @RequestBody SaveVoteRequest saveVoteRequest
) {
voteService.saveVote(saveVoteRequest);

return ResponseEntity.ok()
.body(new ApiSuccessResponse<>());
}

@Operation(summary = "투표 초대", description = "투표에 회원을 초대합니다.")
@SecurityRequirement(name = "Authorization")
@ApiResponses(value = {
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/modu/menu/vote/api/request/SaveVoteRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package modu.menu.vote.api.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Schema(description = "투표 생성 요청 DTO")
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class SaveVoteRequest {

@Schema(description = "투표에 포함시킬 음식점 ID 목록")
@Size(min = 2, message = "투표 항목으로 최소 2개의 음식점이 포함되어야 합니다.")
private List<@Positive(message = "ID는 양수여야 합니다.") Long> placeIds;
}
24 changes: 24 additions & 0 deletions src/main/java/modu/menu/vote/service/VoteService.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import modu.menu.user.domain.User;
import modu.menu.user.repository.UserRepository;
import modu.menu.vibe.repository.VibeRepository;
import modu.menu.vote.api.request.SaveVoteRequest;
import modu.menu.vote.api.request.VoteResultRequest;
import modu.menu.vote.api.response.VoteResultResponse;
import modu.menu.vote.domain.Vote;
Expand Down Expand Up @@ -46,6 +47,29 @@ public class VoteService {
private final UserRepository userRepository;
private final ParticipantRepository participantRepository;

// 투표 생성
@Transactional
public void saveVote(SaveVoteRequest saveVoteRequest) {

List<Place> places = placeRepository.findAllById(saveVoteRequest.getPlaceIds());

if (places.isEmpty() || places.size() != saveVoteRequest.getPlaceIds().size()) {
throw new Exception404(ErrorMessage.NOT_EXIST_PLACE);
}

Vote vote = voteRepository.save(Vote.builder()
.voteStatus(VoteStatus.ACTIVE)
.build());
places.forEach(place -> {
VoteItem voteItem = voteItemRepository.save(VoteItem.builder()
.vote(vote)
.place(place)
.build());

vote.addVoteItem(voteItem);
});
}

// 투표 초대
@Transactional
public void invite(Long voteId, Long userId) {
Expand Down
81 changes: 81 additions & 0 deletions src/test/java/modu/menu/vote/api/VoteControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import modu.menu.ControllerTestSupporter;
import modu.menu.food.domain.FoodType;
import modu.menu.vibe.domain.VibeType;
import modu.menu.vote.api.request.SaveVoteRequest;
import modu.menu.vote.api.request.VoteResultRequest;
import modu.menu.vote.api.response.VoteResultResponse;
import modu.menu.vote.service.dto.VoteResultServiceResponse;
Expand All @@ -23,6 +24,86 @@
@DisplayName("VoteController 단위테스트")
class VoteControllerTest extends ControllerTestSupporter {

@DisplayName("투표를 생성하면 성공한다.")
@Test
void saveVote() throws Exception {
// given
SaveVoteRequest saveVoteRequest = SaveVoteRequest.builder()
.placeIds(List.of(1L, 2L, 3L))
.build();

// when
doNothing().when(voteService).saveVote(saveVoteRequest);

// then
mockMvc.perform(post("/api/vote")
.content(objectMapper.writeValueAsBytes(saveVoteRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status").value(200))
.andExpect(jsonPath("$.reason").value("OK"))
.andExpect(jsonPath("$.data").isEmpty());
}

@DisplayName("투표를 생성할 때 ID 목록이 비어있으면 실패한다.")
@Test
void saveVoteWithEmptyIdList() throws Exception {
// given
SaveVoteRequest saveVoteRequest = SaveVoteRequest.builder()
.placeIds(List.of())
.build();

// when
doNothing().when(voteService).saveVote(saveVoteRequest);

// then
mockMvc.perform(post("/api/vote")
.content(objectMapper.writeValueAsBytes(saveVoteRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.reason").value("Bad Request"))
.andExpect(jsonPath("$.message").value("투표 항목으로 최소 2개의 음식점이 포함되어야 합니다."));
}

@DisplayName("투표를 생성할 때 ID가 하나만 있으면 실패한다.")
@Test
void saveVoteWithOneId() throws Exception {
// given
SaveVoteRequest saveVoteRequest = SaveVoteRequest.builder()
.placeIds(List.of(1L))
.build();

// when
doNothing().when(voteService).saveVote(saveVoteRequest);

// then
mockMvc.perform(post("/api/vote")
.content(objectMapper.writeValueAsBytes(saveVoteRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.reason").value("Bad Request"))
.andExpect(jsonPath("$.message").value("투표 항목으로 최소 2개의 음식점이 포함되어야 합니다."));
}

@DisplayName("투표를 생성할 때 ID 목록 중에 0이 포함되어 있으면 실패한다.")
@Test
void saveVoteWithZeroIdValue() throws Exception {
// given
SaveVoteRequest saveVoteRequest = SaveVoteRequest.builder()
.placeIds(List.of(0L, 1L, 2L))
.build();

// when
doNothing().when(voteService).saveVote(saveVoteRequest);

// then
mockMvc.perform(post("/api/vote")
.content(objectMapper.writeValueAsBytes(saveVoteRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.reason").value("Bad Request"))
.andExpect(jsonPath("$.message").value("ID는 양수여야 합니다."));
}

@DisplayName("투표에 회원을 초대하면 성공한다.")
@Test
void invite() throws Exception {
Expand Down
98 changes: 98 additions & 0 deletions src/test/java/modu/menu/vote/service/VoteServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import modu.menu.vibe.domain.Vibe;
import modu.menu.vibe.domain.VibeType;
import modu.menu.vibe.repository.VibeRepository;
import modu.menu.vote.api.request.SaveVoteRequest;
import modu.menu.vote.api.request.VoteResultRequest;
import modu.menu.vote.api.response.VoteResultResponse;
import modu.menu.vote.domain.Vote;
Expand Down Expand Up @@ -67,6 +68,103 @@ class VoteServiceTest extends IntegrationTestSupporter {
@Autowired
private ParticipantRepository participantRepository;

@DisplayName("투표를 생성한다.")
@Test
void saveVote() {
// given
Place place1 = createPlace("타코벨");
Place place2 = createPlace("이자카야모리");
Place place3 = createPlace("서가앤쿡 노원역점");
Vibe vibe1 = createVibe(VibeType.NOISY);
Vibe vibe2 = createVibe(VibeType.QUIET);
Vibe vibe3 = createVibe(VibeType.GOOD_SERVICE);
PlaceVibe placeVibe1 = createPlaceVibe(place1, vibe1);
place1.addPlaceVibe(placeVibe1);
PlaceVibe placeVibe2 = createPlaceVibe(place2, vibe2);
place2.addPlaceVibe(placeVibe2);
PlaceVibe placeVibe3 = createPlaceVibe(place3, vibe3);
place3.addPlaceVibe(placeVibe3);
placeRepository.saveAll(List.of(place1, place2, place3));
vibeRepository.saveAll(List.of(vibe1, vibe2, vibe3));
placeVibeRepository.saveAll(List.of(placeVibe1, placeVibe2, placeVibe3));
Food food1 = createFood(FoodType.LATIN);
Food food2 = createFood(FoodType.MEAT);
PlaceFood placeFood1 = createPlaceFood(place1, food1);
place1.addPlaceFood(placeFood1);
PlaceFood placeFood2 = createPlaceFood(place2, food1);
place2.addPlaceFood(placeFood2);
PlaceFood placeFood3 = createPlaceFood(place3, food2);
place3.addPlaceFood(placeFood3);
foodRepository.saveAll(List.of(food1, food2));
placeFoodRepository.saveAll(List.of(placeFood1, placeFood2, placeFood3));

SaveVoteRequest saveVoteRequest = SaveVoteRequest.builder()
.placeIds(List.of(1L, 2L, 3L))
.build();

// when
voteService.saveVote(saveVoteRequest);


// then
assertThat(voteRepository.findAll()).isNotEmpty();
}

@DisplayName("투표를 생성할 때 ID 목록에 있는 음식점들이 존재하지 않으면 실패한다.")
@Test
void saveVoteWithNotExistIds() {
// given
SaveVoteRequest saveVoteRequest = SaveVoteRequest.builder()
.placeIds(List.of(1L, 2L, 3L))
.build();

// when


// then
assertThatThrownBy(() -> voteService.saveVote(saveVoteRequest))
.isInstanceOf(Exception404.class)
.hasMessage("해당 ID와 일치하는 음식점이 존재하지 않습니다.");
}

@DisplayName("투표를 생성할 때 ID 목록 중 존재하지 않는 음식점의 ID가 있다면 실패한다.")
@Test
void saveVoteWithNotExistId() {
// given
Place place1 = createPlace("타코벨");
Place place2 = createPlace("이자카야모리");
Vibe vibe1 = createVibe(VibeType.NOISY);
Vibe vibe2 = createVibe(VibeType.QUIET);
Vibe vibe3 = createVibe(VibeType.GOOD_SERVICE);
PlaceVibe placeVibe1 = createPlaceVibe(place1, vibe1);
place1.addPlaceVibe(placeVibe1);
PlaceVibe placeVibe2 = createPlaceVibe(place2, vibe2);
place2.addPlaceVibe(placeVibe2);
placeRepository.saveAll(List.of(place1, place2));
vibeRepository.saveAll(List.of(vibe1, vibe2, vibe3));
placeVibeRepository.saveAll(List.of(placeVibe1, placeVibe2));
Food food1 = createFood(FoodType.LATIN);
Food food2 = createFood(FoodType.MEAT);
PlaceFood placeFood1 = createPlaceFood(place1, food1);
place1.addPlaceFood(placeFood1);
PlaceFood placeFood2 = createPlaceFood(place2, food1);
place2.addPlaceFood(placeFood2);
foodRepository.saveAll(List.of(food1, food2));
placeFoodRepository.saveAll(List.of(placeFood1, placeFood2));

SaveVoteRequest saveVoteRequest = SaveVoteRequest.builder()
.placeIds(List.of(1L, 2L, 3L))
.build();

// when


// then
assertThatThrownBy(() -> voteService.saveVote(saveVoteRequest))
.isInstanceOf(Exception404.class)
.hasMessage("해당 ID와 일치하는 음식점이 존재하지 않습니다.");
}

@DisplayName("회원을 투표에 초대한다.")
@Test
void invite() {
Expand Down

0 comments on commit 34fde45

Please sign in to comment.