diff --git a/server/src/docs/asciidoc/billActionDetail.adoc b/server/src/docs/asciidoc/billActionDetail.adoc new file mode 100644 index 000000000..a8f19f21b --- /dev/null +++ b/server/src/docs/asciidoc/billActionDetail.adoc @@ -0,0 +1,93 @@ +== 지출 상세 + +=== 지출 상세 조회 + +operation::findBillActionDetailsTest[snippets="path-parameters,http-request,http-response,request-cookies"] + +==== [.red]#Exceptions# + +[source,json,options="nowrap"] +---- +[ + { + "code": "EVENT_NOT_FOUND", + "message": "존재하지 않는 행사입니다." + }, + { + "code": "BILL_ACTION_NOT_FOUND", + "message": "존재하지 않는 지출 액션입니다." + }, + { + "code": "BILL_ACTION_DETAIL_NOT_FOUND", + "message": "존재하지 않는 참여자 지출입니다." + }, + { + "code": "TOKEN_NOT_FOUND", + "message": "토큰이 존재하지 않습니다." + }, + { + "code": "TOKEN_EXPIRED", + "message": "만료된 토큰입니다." + }, + { + "code": "TOKEN_INVALID", + "message": "유효하지 않은 토큰입니다." + }, + { + "code": "FORBIDDEN", + "message": "접근할 수 없는 행사입니다." + } +] +---- + +=== 지출 상세 수정 + +operation::updateBillActionDetailsTest[snippets="path-parameters,http-request,request-body,request-fields,http-response,request-cookies"] + +==== [.red]#Exceptions# + +[source,json,options="nowrap"] +---- +[ + { + "code": "REQUEST_EMPTY", + "message": "멤버 이름은 공백일 수 없습니다." + }, + { + "code": "REQUEST_EMPTY", + "message": "지출 금액은 공백일 수 없습니다." + }, + { + "code": "EVENT_NOT_FOUND", + "message": "존재하지 않는 행사입니다." + }, + { + "code": "BILL_ACTION_NOT_FOUND", + "message": "존재하지 않는 지출 액션입니다." + }, + { + "code": "BILL_ACTION_DETAIL_NOT_FOUND", + "message": "존재하지 않는 참여자 지출입니다." + }, + { + "code": "BILL_ACTION_PRICE_NOT_MATCHED", + "message": "지출 총액이 일치하지 않습니다." + }, + { + "code": "TOKEN_NOT_FOUND", + "message": "토큰이 존재하지 않습니다." + }, + { + "code": "TOKEN_EXPIRED", + "message": "만료된 토큰입니다." + }, + { + "code": "TOKEN_INVALID", + "message": "유효하지 않은 토큰입니다." + }, + { + "code": "FORBIDDEN", + "message": "접근할 수 없는 행사입니다." + } +] +---- diff --git a/server/src/docs/asciidoc/index.adoc b/server/src/docs/asciidoc/index.adoc index 3039f2bbf..8e1bcd2d0 100644 --- a/server/src/docs/asciidoc/index.adoc +++ b/server/src/docs/asciidoc/index.adoc @@ -1,18 +1,15 @@ -:source-highlighter: highlightjs -:hardbreaks: -:toc: left -:doctype: book -:icons: font -:toc-title: 전체 API 목록 -:toclevels: 2 -:sectlinks: +ifndef::snippets[] +:snippets: ../../build/generated-snippets +endif::[] += 행동대장 +:source-highlighter: highlightjs :hardbreaks: +:toc: left :doctype: book :icons: font :toc-title: 전체 API 목록 :toclevels: 2 :sectlinks: :sectnums: :sectnumlevels: 2 -= 행동대장 include::{docdir}/event.adoc[] include::{docdir}/memberBillReport.adoc[] include::{docdir}/memberAction.adoc[] include::{docdir}/billAction.adoc[] - +include::{docdir}/billActionDetail.adoc[] diff --git a/server/src/main/java/server/haengdong/application/BillActionDetailService.java b/server/src/main/java/server/haengdong/application/BillActionDetailService.java index 14a7e8a06..5d4cf335a 100644 --- a/server/src/main/java/server/haengdong/application/BillActionDetailService.java +++ b/server/src/main/java/server/haengdong/application/BillActionDetailService.java @@ -1,12 +1,78 @@ package server.haengdong.application; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import server.haengdong.application.request.BillActionDetailUpdateAppRequest; +import server.haengdong.application.request.BillActionDetailsUpdateAppRequest; +import server.haengdong.application.response.BillActionDetailsAppResponse; +import server.haengdong.domain.action.BillAction; +import server.haengdong.domain.action.BillActionDetail; +import server.haengdong.domain.action.BillActionDetailRepository; import server.haengdong.domain.action.BillActionRepository; +import server.haengdong.domain.event.Event; +import server.haengdong.exception.HaengdongErrorCode; +import server.haengdong.exception.HaengdongException; @RequiredArgsConstructor +@Transactional(readOnly = true) @Service public class BillActionDetailService { + private final BillActionDetailRepository billActionDetailRepository; private final BillActionRepository billActionRepository; + + public BillActionDetailsAppResponse findBillActionDetails(String token, Long actionId) { + BillAction billAction = billActionRepository.findByAction_Id(actionId) + .orElseThrow(() -> new HaengdongException(HaengdongErrorCode.BILL_ACTION_NOT_FOUND)); + validateToken(token, billAction); + + List billActionDetails = billActionDetailRepository.findAllByBillAction(billAction); + + return BillActionDetailsAppResponse.of(billActionDetails); + } + + @Transactional + public void updateBillActionDetails(String token, Long actionId, BillActionDetailsUpdateAppRequest request) { + BillAction billAction = billActionRepository.findByAction_Id(actionId) + .orElseThrow(() -> new HaengdongException(HaengdongErrorCode.BILL_ACTION_NOT_FOUND)); + + List billActionDetailUpdateAppRequests = request.billActionDetailUpdateAppRequests(); + + validateToken(token, billAction); + validateTotalPrice(billActionDetailUpdateAppRequests, billAction); + + List billActionDetails = billActionDetailRepository.findAllByBillAction(billAction); + + for (BillActionDetailUpdateAppRequest updateRequest : billActionDetailUpdateAppRequests) { + BillActionDetail detailToUpdate = billActionDetails.stream() + .filter(detail -> detail.isSameName(updateRequest.name())) + .findFirst() + .orElseThrow(() -> new HaengdongException(HaengdongErrorCode.BILL_ACTION_DETAIL_NOT_FOUND)); + + detailToUpdate.updatePrice(updateRequest.price()); + } + } + + private void validateToken(String token, BillAction billAction) { + Event event = billAction.getEvent(); + if (event.isTokenMismatch(token)) { + throw new HaengdongException(HaengdongErrorCode.BILL_ACTION_NOT_FOUND); + } + } + + private void validateTotalPrice(List billActionDetailUpdateAppRequests, + BillAction billAction) { + Long requestsPriceSum = calculateUpdatePriceSum(billActionDetailUpdateAppRequests); + if (!billAction.isSamePrice(requestsPriceSum)) { + throw new HaengdongException(HaengdongErrorCode.BILL_ACTION_PRICE_NOT_MATCHED); + } + } + + private Long calculateUpdatePriceSum(List billActionDetailUpdateAppRequests) { + return billActionDetailUpdateAppRequests.stream() + .map(BillActionDetailUpdateAppRequest::price) + .reduce(0L, Long::sum); + } } diff --git a/server/src/main/java/server/haengdong/application/BillActionService.java b/server/src/main/java/server/haengdong/application/BillActionService.java index 803a21055..95717d825 100644 --- a/server/src/main/java/server/haengdong/application/BillActionService.java +++ b/server/src/main/java/server/haengdong/application/BillActionService.java @@ -75,7 +75,7 @@ public void updateBillAction(String token, Long actionId, BillActionUpdateAppReq private void resetBillActionDetail(BillAction billAction, Long updatePrice) { if (billAction.getPrice() != updatePrice) { - List billActionDetails = billActionDetailRepository.findByBillAction(billAction); + List billActionDetails = billActionDetailRepository.findAllByBillAction(billAction); int memberCount = billActionDetails.size(); if (memberCount != 0) { Long eachPrice = updatePrice / memberCount; diff --git a/server/src/main/java/server/haengdong/application/request/BillActionDetailUpdateAppRequest.java b/server/src/main/java/server/haengdong/application/request/BillActionDetailUpdateAppRequest.java new file mode 100644 index 000000000..a815e4afa --- /dev/null +++ b/server/src/main/java/server/haengdong/application/request/BillActionDetailUpdateAppRequest.java @@ -0,0 +1,7 @@ +package server.haengdong.application.request; + +public record BillActionDetailUpdateAppRequest( + String name, + Long price +) { +} diff --git a/server/src/main/java/server/haengdong/application/request/BillActionDetailsUpdateAppRequest.java b/server/src/main/java/server/haengdong/application/request/BillActionDetailsUpdateAppRequest.java new file mode 100644 index 000000000..1fe19798e --- /dev/null +++ b/server/src/main/java/server/haengdong/application/request/BillActionDetailsUpdateAppRequest.java @@ -0,0 +1,8 @@ +package server.haengdong.application.request; + +import java.util.List; + +public record BillActionDetailsUpdateAppRequest( + List billActionDetailUpdateAppRequests +) { +} diff --git a/server/src/main/java/server/haengdong/application/response/BillActionDetailAppResponse.java b/server/src/main/java/server/haengdong/application/response/BillActionDetailAppResponse.java new file mode 100644 index 000000000..1ddb51671 --- /dev/null +++ b/server/src/main/java/server/haengdong/application/response/BillActionDetailAppResponse.java @@ -0,0 +1,13 @@ +package server.haengdong.application.response; + +import server.haengdong.domain.action.BillActionDetail; + +public record BillActionDetailAppResponse( + String name, + Long price +) { + + public static BillActionDetailAppResponse of(BillActionDetail billActionDetail) { + return new BillActionDetailAppResponse(billActionDetail.getMemberName(), billActionDetail.getPrice()); + } +} diff --git a/server/src/main/java/server/haengdong/application/response/BillActionDetailsAppResponse.java b/server/src/main/java/server/haengdong/application/response/BillActionDetailsAppResponse.java new file mode 100644 index 000000000..061f07816 --- /dev/null +++ b/server/src/main/java/server/haengdong/application/response/BillActionDetailsAppResponse.java @@ -0,0 +1,14 @@ +package server.haengdong.application.response; + +import java.util.List; +import java.util.stream.Collectors; +import server.haengdong.domain.action.BillActionDetail; + +public record BillActionDetailsAppResponse(List billActionDetailAppResponses) { + + public static BillActionDetailsAppResponse of(List billActionDetails) { + return billActionDetails.stream() + .map(BillActionDetailAppResponse::of) + .collect(Collectors.collectingAndThen(Collectors.toList(), BillActionDetailsAppResponse::new)); + } +} diff --git a/server/src/main/java/server/haengdong/domain/action/BillAction.java b/server/src/main/java/server/haengdong/domain/action/BillAction.java index 65db11734..0b35040e6 100644 --- a/server/src/main/java/server/haengdong/domain/action/BillAction.java +++ b/server/src/main/java/server/haengdong/domain/action/BillAction.java @@ -77,6 +77,10 @@ public void update(String title, Long price) { this.price = price; } + public boolean isSamePrice(Long price) { + return this.price.equals(price); + } + public Long getSequence() { return action.getSequence(); } diff --git a/server/src/main/java/server/haengdong/domain/action/BillActionDetail.java b/server/src/main/java/server/haengdong/domain/action/BillActionDetail.java index 550499aa5..06847c2bb 100644 --- a/server/src/main/java/server/haengdong/domain/action/BillActionDetail.java +++ b/server/src/main/java/server/haengdong/domain/action/BillActionDetail.java @@ -31,15 +31,14 @@ public BillActionDetail(String memberName, Long price) { this.price = price; } - public void setBillAction(BillAction billAction) { + public BillActionDetail(BillAction billAction, String memberName, Long price) { this.billAction = billAction; + this.memberName = memberName; + this.price = price; } - public boolean hasMemberName(String memberName) { - return this.memberName.equals(memberName); - } - - public BillActionDetail(BillAction billAction, String memberName, Long price) { + public BillActionDetail(Long id, BillAction billAction, String memberName, Long price) { + this.id = id; this.billAction = billAction; this.memberName = memberName; this.price = price; @@ -48,4 +47,16 @@ public BillActionDetail(BillAction billAction, String memberName, Long price) { public void updatePrice(Long price) { this.price = price; } + + public boolean hasMemberName(String memberName) { + return this.memberName.equals(memberName); + } + + public boolean isSameName(String memberName) { + return this.memberName.equals(memberName); + } + + public void setBillAction(BillAction billAction) { + this.billAction = billAction; + } } diff --git a/server/src/main/java/server/haengdong/domain/action/BillActionDetailRepository.java b/server/src/main/java/server/haengdong/domain/action/BillActionDetailRepository.java index 5d74462ff..35b331bee 100644 --- a/server/src/main/java/server/haengdong/domain/action/BillActionDetailRepository.java +++ b/server/src/main/java/server/haengdong/domain/action/BillActionDetailRepository.java @@ -2,13 +2,19 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import server.haengdong.domain.event.Event; @Repository public interface BillActionDetailRepository extends JpaRepository { - List findByBillAction(BillAction billAction); + @Query(""" + select bd + from BillActionDetail bd + where bd.billAction = :billAction + """) + List findAllByBillAction(BillAction billAction); void deleteAllByBillAction(BillAction billAction); diff --git a/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java b/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java index 4020879d2..ff61d4898 100644 --- a/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java +++ b/server/src/main/java/server/haengdong/exception/HaengdongErrorCode.java @@ -35,6 +35,8 @@ public enum HaengdongErrorCode { BillAction.MIN_TITLE_LENGTH, BillAction.MAX_TITLE_LENGTH)), BILL_ACTION_PRICE_INVALID(String.format("지출 금액은 %,d 이하의 자연수여야 합니다.", BillAction.MAX_PRICE)), + BILL_ACTION_DETAIL_NOT_FOUND("존재하지 않는 참여자 지출입니다."), + BILL_ACTION_PRICE_NOT_MATCHED("지출 총액이 일치하지 않습니다."), /* Authentication */ @@ -55,7 +57,8 @@ public enum HaengdongErrorCode { MESSAGE_NOT_READABLE("읽을 수 없는 요청입니다."), REQUEST_METHOD_NOT_SUPPORTED("지원하지 않는 요청 메서드입니다."), NO_RESOURCE_REQUEST("존재하지 않는 자원입니다."), - INTERNAL_SERVER_ERROR("서버 내부에서 에러가 발생했습니다."); + INTERNAL_SERVER_ERROR("서버 내부에서 에러가 발생했습니다."), + ; private final String message; diff --git a/server/src/main/java/server/haengdong/presentation/BillActionDetailController.java b/server/src/main/java/server/haengdong/presentation/BillActionDetailController.java index 376b4ff26..5602e6777 100644 --- a/server/src/main/java/server/haengdong/presentation/BillActionDetailController.java +++ b/server/src/main/java/server/haengdong/presentation/BillActionDetailController.java @@ -1,12 +1,42 @@ package server.haengdong.presentation; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import server.haengdong.application.BillActionDetailService; +import server.haengdong.application.response.BillActionDetailsAppResponse; +import server.haengdong.presentation.request.BillActionDetailsUpdateRequest; +import server.haengdong.presentation.response.BillActionDetailsResponse; @RequiredArgsConstructor @RestController public class BillActionDetailController { private final BillActionDetailService billActionDetailService; + + @GetMapping("/api/events/{eventId}/bill-actions/{actionId}/fixed") + public ResponseEntity findBillActionDetails( + @PathVariable("eventId") String token, + @PathVariable("actionId") Long actionId + ) { + BillActionDetailsAppResponse appResponse = billActionDetailService.findBillActionDetails(token, actionId); + + return ResponseEntity.ok(BillActionDetailsResponse.of(appResponse)); + } + + @PutMapping("/api/events/{eventId}/bill-actions/{actionId}/fixed") + public ResponseEntity updateBillActionDetails( + @PathVariable("eventId") String token, + @PathVariable("actionId") Long actionId, + @Valid @RequestBody BillActionDetailsUpdateRequest request + ) { + billActionDetailService.updateBillActionDetails(token, actionId, request.toAppRequest()); + + return ResponseEntity.ok().build(); + } } diff --git a/server/src/main/java/server/haengdong/presentation/request/BillActionDetailUpdateRequest.java b/server/src/main/java/server/haengdong/presentation/request/BillActionDetailUpdateRequest.java new file mode 100644 index 000000000..2988f2ace --- /dev/null +++ b/server/src/main/java/server/haengdong/presentation/request/BillActionDetailUpdateRequest.java @@ -0,0 +1,18 @@ +package server.haengdong.presentation.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import server.haengdong.application.request.BillActionDetailUpdateAppRequest; + +public record BillActionDetailUpdateRequest( + + @NotBlank(message = "맴버 이름은 공백일 수 없습니다.") + String name, + + @NotNull(message = "지출 금액은 공백일 수 없습니다.") + Long price +) { + public BillActionDetailUpdateAppRequest toAppRequest() { + return new BillActionDetailUpdateAppRequest(this.name, this.price); + } +} diff --git a/server/src/main/java/server/haengdong/presentation/request/BillActionDetailsUpdateRequest.java b/server/src/main/java/server/haengdong/presentation/request/BillActionDetailsUpdateRequest.java new file mode 100644 index 000000000..2cd79b61d --- /dev/null +++ b/server/src/main/java/server/haengdong/presentation/request/BillActionDetailsUpdateRequest.java @@ -0,0 +1,15 @@ +package server.haengdong.presentation.request; + +import jakarta.validation.Valid; +import java.util.List; +import server.haengdong.application.request.BillActionDetailsUpdateAppRequest; + +public record BillActionDetailsUpdateRequest( + @Valid List members +) { + public BillActionDetailsUpdateAppRequest toAppRequest() { + return new BillActionDetailsUpdateAppRequest(members.stream() + .map(BillActionDetailUpdateRequest::toAppRequest) + .toList()); + } +} diff --git a/server/src/main/java/server/haengdong/presentation/response/BillActionDetailResponse.java b/server/src/main/java/server/haengdong/presentation/response/BillActionDetailResponse.java new file mode 100644 index 000000000..46d56438c --- /dev/null +++ b/server/src/main/java/server/haengdong/presentation/response/BillActionDetailResponse.java @@ -0,0 +1,13 @@ +package server.haengdong.presentation.response; + +import server.haengdong.application.response.BillActionDetailAppResponse; + +public record BillActionDetailResponse( + String name, + Long price +) { + + public static BillActionDetailResponse of(BillActionDetailAppResponse billActionDetailAppResponse) { + return new BillActionDetailResponse(billActionDetailAppResponse.name(), billActionDetailAppResponse.price()); + } +} diff --git a/server/src/main/java/server/haengdong/presentation/response/BillActionDetailsResponse.java b/server/src/main/java/server/haengdong/presentation/response/BillActionDetailsResponse.java new file mode 100644 index 000000000..182e76db6 --- /dev/null +++ b/server/src/main/java/server/haengdong/presentation/response/BillActionDetailsResponse.java @@ -0,0 +1,16 @@ +package server.haengdong.presentation.response; + +import java.util.List; +import java.util.stream.Collectors; +import server.haengdong.application.response.BillActionDetailsAppResponse; + +public record BillActionDetailsResponse( + List members +) { + + public static BillActionDetailsResponse of(BillActionDetailsAppResponse billActionDetailsAppResponse) { + return billActionDetailsAppResponse.billActionDetailAppResponses().stream() + .map(BillActionDetailResponse::of) + .collect(Collectors.collectingAndThen(Collectors.toList(), BillActionDetailsResponse::new)); + } +} diff --git a/server/src/test/java/server/haengdong/application/BillActionDetailServiceTest.java b/server/src/test/java/server/haengdong/application/BillActionDetailServiceTest.java new file mode 100644 index 000000000..3e7aee56e --- /dev/null +++ b/server/src/test/java/server/haengdong/application/BillActionDetailServiceTest.java @@ -0,0 +1,111 @@ +package server.haengdong.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.tuple; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import server.haengdong.application.request.BillActionDetailUpdateAppRequest; +import server.haengdong.application.request.BillActionDetailsUpdateAppRequest; +import server.haengdong.application.response.BillActionDetailAppResponse; +import server.haengdong.application.response.BillActionDetailsAppResponse; +import server.haengdong.domain.action.Action; +import server.haengdong.domain.action.BillAction; +import server.haengdong.domain.action.BillActionDetail; +import server.haengdong.domain.action.BillActionDetailRepository; +import server.haengdong.domain.action.BillActionRepository; +import server.haengdong.domain.event.Event; +import server.haengdong.domain.event.EventRepository; +import server.haengdong.exception.HaengdongException; +import server.haengdong.support.fixture.Fixture; + +class BillActionDetailServiceTest extends ServiceTestSupport { + + @Autowired + private BillActionDetailService billActionDetailService; + + @Autowired + private EventRepository eventRepository; + + @Autowired + private BillActionRepository billActionRepository; + + @Autowired + private BillActionDetailRepository billActionDetailRepository; + + @DisplayName("참여자별 지출 금액을 조회한다.") + @Test + void findBillActionDetailsTest() { + Event event1 = Fixture.EVENT1; + eventRepository.save(event1); + Action action = new Action(event1, 1L); + BillAction billAction = new BillAction(action, "뽕족", 10000L); + billActionRepository.save(billAction); + BillActionDetail billActionDetail1 = new BillActionDetail(1L, billAction, "토다리", 6000L); + BillActionDetail billActionDetail2 = new BillActionDetail(2L, billAction, "쿠키", 4000L); + billActionDetailRepository.saveAll(List.of(billActionDetail1, billActionDetail2)); + + BillActionDetailsAppResponse response = billActionDetailService.findBillActionDetails( + event1.getToken(), action.getId()); + + assertThat(response.billActionDetailAppResponses()).hasSize(2) + .extracting(BillActionDetailAppResponse::name, BillActionDetailAppResponse::price) + .containsExactly( + tuple("토다리", 6000L), + tuple("쿠키", 4000L) + ); + } + + @DisplayName("지출 금액 수정 요청의 총합이 지출 금액과 일치하지 않으면 예외가 발생한다.") + @Test + void updateBillActionDetailsTest1() { + Event event1 = Fixture.EVENT1; + eventRepository.save(event1); + Action action = new Action(event1, 1L); + BillAction billAction = new BillAction(action, "뽕족", 10000L); + billActionRepository.save(billAction); + BillActionDetail billActionDetail1 = new BillActionDetail(1L, billAction, "토다리", 5000L); + BillActionDetail billActionDetail2 = new BillActionDetail(2L, billAction, "쿠키", 5000L); + billActionDetailRepository.saveAll(List.of(billActionDetail1, billActionDetail2)); + + BillActionDetailsUpdateAppRequest request = new BillActionDetailsUpdateAppRequest(List.of( + new BillActionDetailUpdateAppRequest("토다리", 3000L), + new BillActionDetailUpdateAppRequest("쿠키", 4000L) + )); + assertThatCode( + () -> billActionDetailService.updateBillActionDetails(event1.getToken(), action.getId(), request)) + .isInstanceOf(HaengdongException.class) + .hasMessage("지출 총액이 일치하지 않습니다."); + } + + @DisplayName("지출 고정 금액을 수정한다.") + @Test + void updateBillActionDetailsTest2() { + Event event1 = Fixture.EVENT1; + eventRepository.save(event1); + Action action = new Action(event1, 1L); + BillAction billAction = new BillAction(action, "뽕족", 10000L); + billActionRepository.save(billAction); + BillActionDetail billActionDetail1 = new BillActionDetail(1L, billAction, "토다리", 5000L); + BillActionDetail billActionDetail2 = new BillActionDetail(2L, billAction, "쿠키", 5000L); + billActionDetailRepository.saveAll(List.of(billActionDetail1, billActionDetail2)); + + BillActionDetailsUpdateAppRequest request = new BillActionDetailsUpdateAppRequest(List.of( + new BillActionDetailUpdateAppRequest("토다리", 3000L), + new BillActionDetailUpdateAppRequest("쿠키", 7000L) + )); + billActionDetailService.updateBillActionDetails(event1.getToken(), action.getId(), request); + + List results = billActionDetailRepository.findAll(); + + assertThat(results).hasSize(2) + .extracting(BillActionDetail::getMemberName, BillActionDetail::getPrice) + .containsExactly( + tuple("토다리", 3000L), + tuple("쿠키", 7000L) + ); + } +} diff --git a/server/src/test/java/server/haengdong/application/BillActionServiceTest.java b/server/src/test/java/server/haengdong/application/BillActionServiceTest.java index be535411d..c0e588288 100644 --- a/server/src/test/java/server/haengdong/application/BillActionServiceTest.java +++ b/server/src/test/java/server/haengdong/application/BillActionServiceTest.java @@ -174,7 +174,7 @@ void updateBillAction2() { billActionService.updateBillAction(event.getToken(), actionId, request); BillAction updatedBillAction = billActionRepository.findById(savedBillAction.getId()).get(); - List billActionDetails = billActionDetailRepository.findByBillAction(updatedBillAction); + List billActionDetails = billActionDetailRepository.findAllByBillAction(updatedBillAction); assertThat(billActionDetails).hasSize(4) .extracting("memberName", "price") diff --git a/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java b/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java index 14a7d4225..06368a9fb 100644 --- a/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java +++ b/server/src/test/java/server/haengdong/application/MemberActionServiceTest.java @@ -153,7 +153,7 @@ void deleteMember1() { memberActionService.deleteMember(event.getToken(), "쿠키"); - List billActionDetails = billActionDetailRepository.findByBillAction(billAction); + List billActionDetails = billActionDetailRepository.findAllByBillAction(billAction); assertThat(billActionDetails).hasSize(2) .extracting("memberName", "price") @@ -226,7 +226,7 @@ void deleteMemberAction1() { billActionDetailRepository.saveAll(List.of(billActionDetail1, billActionDetail2, billActionDetail3)); memberActionService.deleteMemberAction(event.getToken(), targetAction.getId()); - List billActionDetails = billActionDetailRepository.findByBillAction(billAction); + List billActionDetails = billActionDetailRepository.findAllByBillAction(billAction); assertThat(billActionDetails).hasSize(4) .extracting("memberName", "price") diff --git a/server/src/test/java/server/haengdong/docs/BillActionDetailControllerDocsTest.java b/server/src/test/java/server/haengdong/docs/BillActionDetailControllerDocsTest.java new file mode 100644 index 000000000..3116cba5e --- /dev/null +++ b/server/src/test/java/server/haengdong/docs/BillActionDetailControllerDocsTest.java @@ -0,0 +1,122 @@ +package server.haengdong.docs; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; +import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static server.haengdong.support.fixture.Fixture.EVENT_COOKIE; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; +import server.haengdong.application.BillActionDetailService; +import server.haengdong.application.response.BillActionDetailAppResponse; +import server.haengdong.application.response.BillActionDetailsAppResponse; +import server.haengdong.presentation.BillActionDetailController; +import server.haengdong.presentation.request.BillActionDetailUpdateRequest; +import server.haengdong.presentation.request.BillActionDetailsUpdateRequest; + +public class BillActionDetailControllerDocsTest extends RestDocsSupport { + + private final BillActionDetailService billActionDetailService = mock(BillActionDetailService.class); + + @Override + protected Object initController() { + return new BillActionDetailController(billActionDetailService); + } + + @DisplayName("참여자별 지출 금액을 조회한다.") + @Test + void findBillActionDetailsTest() throws Exception { + BillActionDetailsAppResponse appResponse = new BillActionDetailsAppResponse( + List.of(new BillActionDetailAppResponse("토다리", 1000L))); + given(billActionDetailService.findBillActionDetails(anyString(), anyLong())) + .willReturn(appResponse); + + mockMvc.perform(get("/api/events/{eventId}/bill-actions/{actionId}/fixed", "TOKEN", 1L) + .cookie(EVENT_COOKIE)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.members").isArray()) + .andExpect(jsonPath("$.members[0].name").value("토다리")) + .andExpect(jsonPath("$.members[0].price").value(1000L)) + .andDo( + document("findBillActionDetailsTest", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("eventId").description("행사 ID"), + parameterWithName("actionId").description("액션 ID") + ), requestCookies( + cookieWithName("eventToken").description("행사 관리자 토큰") + ), responseFields( + fieldWithPath("members").type(JsonFieldType.ARRAY) + .description("전체 정산 수정 요청 목록"), + fieldWithPath("members[0].name").type(JsonFieldType.STRING) + .description("참여자 이름"), + fieldWithPath("members[0].price").type(JsonFieldType.NUMBER) + .description("참여자 정산 금액") + ) + ) + ); + } + + @DisplayName("참여자별 지출 금액을 수정한다.") + @Test + void updateBillActionDetailsTest() throws Exception { + List billActionDetailUpdateRequests = List.of( + new BillActionDetailUpdateRequest("소하", 10000L), + new BillActionDetailUpdateRequest("웨디", 20000L) + ); + BillActionDetailsUpdateRequest request = new BillActionDetailsUpdateRequest( + billActionDetailUpdateRequests); + + String json = objectMapper.writeValueAsString(request); + + mockMvc.perform(put("/api/events/{eventId}/bill-actions/{actionId}/fixed", "TOKEN", 1L) + .cookie(EVENT_COOKIE) + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andDo(print()) + .andExpect(status().isOk()) + .andDo( + document("updateBillActionDetailsTest", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("eventId").description("행사 ID"), + parameterWithName("actionId").description("액션 ID") + ), + requestCookies( + cookieWithName("eventToken").description("행사 관리자 토큰") + ), + requestFields( + fieldWithPath("members").type(JsonFieldType.ARRAY) + .description("전체 정산 수정 요청 목록"), + fieldWithPath("members[0].name").type(JsonFieldType.STRING) + .description("참여자 이름"), + fieldWithPath("members[0].price").type(JsonFieldType.NUMBER) + .description("참여자 정산 금액") + ) + ) + ); + } +} diff --git a/server/src/test/java/server/haengdong/presentation/BillActionDetailControllerTest.java b/server/src/test/java/server/haengdong/presentation/BillActionDetailControllerTest.java new file mode 100644 index 000000000..f098153d4 --- /dev/null +++ b/server/src/test/java/server/haengdong/presentation/BillActionDetailControllerTest.java @@ -0,0 +1,56 @@ +package server.haengdong.presentation; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import server.haengdong.application.response.BillActionDetailAppResponse; +import server.haengdong.application.response.BillActionDetailsAppResponse; +import server.haengdong.presentation.request.BillActionDetailUpdateRequest; +import server.haengdong.presentation.request.BillActionDetailsUpdateRequest; + +class BillActionDetailControllerTest extends ControllerTestSupport { + + @DisplayName("참여자별 지출 금액을 조회한다.") + @Test + void findBillActionDetails() throws Exception { + BillActionDetailsAppResponse appResponse = new BillActionDetailsAppResponse( + List.of(new BillActionDetailAppResponse("토다리", 1000L))); + given(billActionDetailService.findBillActionDetails(anyString(), anyLong())) + .willReturn(appResponse); + + mockMvc.perform(get("/api/events/{eventId}/bill-actions/{actionId}/fixed", "TOKEN", 1L)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.members").isArray()) + .andExpect(jsonPath("$.members[0].name").value("토다리")) + .andExpect(jsonPath("$.members[0].price").value(1000L)); + } + + @DisplayName("참여자별 지출 금액을 수정한다.") + @Test + void updateBillActionDetailsTest() throws Exception { + List billActionDetailUpdateRequests = List.of( + new BillActionDetailUpdateRequest("소하", 10000L), + new BillActionDetailUpdateRequest("웨디", 20000L) + ); + BillActionDetailsUpdateRequest request = new BillActionDetailsUpdateRequest( + billActionDetailUpdateRequests); + + String json = objectMapper.writeValueAsString(request); + + mockMvc.perform(put("/api/events/{eventId}/bill-actions/{actionId}/fixed", "TOKEN", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andDo(print()) + .andExpect(status().isOk()); + } +} diff --git a/server/src/test/java/server/haengdong/presentation/ControllerTestSupport.java b/server/src/test/java/server/haengdong/presentation/ControllerTestSupport.java index a9897d9f0..46489b1a5 100644 --- a/server/src/test/java/server/haengdong/presentation/ControllerTestSupport.java +++ b/server/src/test/java/server/haengdong/presentation/ControllerTestSupport.java @@ -10,6 +10,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import server.haengdong.application.ActionService; import server.haengdong.application.AuthService; +import server.haengdong.application.BillActionDetailService; import server.haengdong.application.BillActionService; import server.haengdong.application.EventService; import server.haengdong.application.MemberActionService; @@ -19,7 +20,8 @@ EventController.class, ActionController.class, BillActionController.class, - MemberActionController.class + MemberActionController.class, + BillActionDetailController.class }, excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {WebMvcConfigurer.class})} ) @@ -27,6 +29,7 @@ abstract class ControllerTestSupport { @Autowired protected MockMvc mockMvc; + @Autowired protected ObjectMapper objectMapper; @@ -44,4 +47,7 @@ abstract class ControllerTestSupport { @MockBean protected BillActionService billActionService; + + @MockBean + protected BillActionDetailService billActionDetailService; }