From 938d6bb8d9229d08fb05a1052a1580d1022be2f6 Mon Sep 17 00:00:00 2001 From: YongHwan Kim Date: Thu, 28 Dec 2023 14:28:19 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20=ED=8F=AC=ED=8A=B8=ED=8F=B4=EB=A6=AC?= =?UTF-8?q?=EC=98=A4=20=EB=8B=A8=EC=9D=BC=20=EC=82=AD=EC=A0=9C=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20(#92)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #90 fix: 포트폴리오 손익내역 삭제 추가로 버그 해결 * #83 fix: 변수명 변경 * #90 test: 포트폴리오 테스트 코드 정리 * [docs] redirect uri 변경 (#94) * #93 docs: redirect-uri 변경 * #93 docs: redirect-uri 변경 * #90 fix: 포트폴리오 손익내역 삭제 추가로 버그 해결 * #83 fix: 변수명 변경 * #90 test: 포트폴리오 테스트 코드 정리 --- .../PortfolioGainHistoryRepository.java | 2 + .../api/portfolio/PortFolioService.java | 9 +- .../api/portfolio/PortFolioServiceTest.java | 249 ++++++++++-------- 3 files changed, 153 insertions(+), 107 deletions(-) diff --git a/src/main/java/codesquad/fineants/domain/portfolio_gain_history/PortfolioGainHistoryRepository.java b/src/main/java/codesquad/fineants/domain/portfolio_gain_history/PortfolioGainHistoryRepository.java index 5a39b5bc..3dce8138 100644 --- a/src/main/java/codesquad/fineants/domain/portfolio_gain_history/PortfolioGainHistoryRepository.java +++ b/src/main/java/codesquad/fineants/domain/portfolio_gain_history/PortfolioGainHistoryRepository.java @@ -18,4 +18,6 @@ Optional findFirstByPortfolioAndCreateAtIsLessThanEqualOrd @Param("portfolioId") Long portfolioId, @Param("createAt") LocalDateTime createAt); List findAllByPortfolioId(Long portfolioId); + + int deleteAllByPortfolioId(Long portfolioId); } diff --git a/src/main/java/codesquad/fineants/spring/api/portfolio/PortFolioService.java b/src/main/java/codesquad/fineants/spring/api/portfolio/PortFolioService.java index b9bcbd2c..fd52db61 100644 --- a/src/main/java/codesquad/fineants/spring/api/portfolio/PortFolioService.java +++ b/src/main/java/codesquad/fineants/spring/api/portfolio/PortFolioService.java @@ -24,7 +24,6 @@ import codesquad.fineants.spring.api.errors.exception.ConflictException; import codesquad.fineants.spring.api.errors.exception.ForBiddenException; import codesquad.fineants.spring.api.errors.exception.NotFoundResourceException; -import codesquad.fineants.spring.api.kis.KisService; import codesquad.fineants.spring.api.kis.manager.CurrentPriceManager; import codesquad.fineants.spring.api.portfolio.request.PortfolioCreateRequest; import codesquad.fineants.spring.api.portfolio.request.PortfolioModifyRequest; @@ -47,7 +46,6 @@ public class PortFolioService { private final PurchaseHistoryRepository purchaseHistoryRepository; private final PortfolioGainHistoryRepository portfolioGainHistoryRepository; private final CurrentPriceManager currentPriceManager; - private final KisService kisService; @Transactional public PortFolioCreateResponse addPortFolio(PortfolioCreateRequest request, AuthMember authMember) { @@ -117,11 +115,14 @@ public void deletePortfolio(Long portfolioId, AuthMember authMember) { Portfolio findPortfolio = findPortfolio(portfolioId); validatePortfolioAuthorization(findPortfolio, authMember.getMemberId()); - List portfolioStockIds = portfolioHoldingRepository.findAllByPortfolio(findPortfolio).stream() + List portfolioHoldingIds = portfolioHoldingRepository.findAllByPortfolio(findPortfolio).stream() .map(PortfolioHolding::getId) .collect(Collectors.toList()); - int delTradeHistoryCnt = purchaseHistoryRepository.deleteAllByPortfolioHoldingIdIn(portfolioStockIds); + int delPortfolioGainHistoryCnt = portfolioGainHistoryRepository.deleteAllByPortfolioId(portfolioId); + log.info("포트폴리오 손익 내역 삭제 개수 : {}", delPortfolioGainHistoryCnt); + + int delTradeHistoryCnt = purchaseHistoryRepository.deleteAllByPortfolioHoldingIdIn(portfolioHoldingIds); log.info("매매이력 삭제 개수 : {}", delTradeHistoryCnt); int delPortfolioCnt = portfolioHoldingRepository.deleteAllByPortfolioId(findPortfolio.getId()); diff --git a/src/test/java/codesquad/fineants/spring/api/portfolio/PortFolioServiceTest.java b/src/test/java/codesquad/fineants/spring/api/portfolio/PortFolioServiceTest.java index 2f4c78d7..3264a79c 100644 --- a/src/test/java/codesquad/fineants/spring/api/portfolio/PortFolioServiceTest.java +++ b/src/test/java/codesquad/fineants/spring/api/portfolio/PortFolioServiceTest.java @@ -3,15 +3,16 @@ import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -29,10 +30,16 @@ import codesquad.fineants.domain.oauth.support.AuthMember; import codesquad.fineants.domain.portfolio.Portfolio; import codesquad.fineants.domain.portfolio.PortfolioRepository; -import codesquad.fineants.domain.portfolio_holding.PortfolioHoldingRepository; +import codesquad.fineants.domain.portfolio_gain_history.PortfolioGainHistory; +import codesquad.fineants.domain.portfolio_gain_history.PortfolioGainHistoryRepository; import codesquad.fineants.domain.portfolio_holding.PortfolioHolding; +import codesquad.fineants.domain.portfolio_holding.PortfolioHoldingRepository; import codesquad.fineants.domain.purchase_history.PurchaseHistory; import codesquad.fineants.domain.purchase_history.PurchaseHistoryRepository; +import codesquad.fineants.domain.stock.Market; +import codesquad.fineants.domain.stock.Stock; +import codesquad.fineants.domain.stock.StockRepository; +import codesquad.fineants.domain.stock_dividend.StockDividend; import codesquad.fineants.spring.api.errors.exception.BadRequestException; import codesquad.fineants.spring.api.errors.exception.ConflictException; import codesquad.fineants.spring.api.portfolio.request.PortfolioCreateRequest; @@ -56,41 +63,25 @@ class PortFolioServiceTest { @Autowired private PortfolioRepository portfolioRepository; - private Member member; @Autowired private PurchaseHistoryRepository purchaseHistoryRepository; @Autowired private PortfolioHoldingRepository portFolioHoldingRepository; - private static Stream provideInvalidTargetGain() { - return Stream.of( - Arguments.of(900000L), - Arguments.of(1000000L) - ); - } - - private static Stream provideInvalidMaximumLoss() { - return Stream.of( - Arguments.of(1000000L), - Arguments.of(1100000L) - ); - } + @Autowired + private PortfolioGainHistoryRepository portfolioGainHistoryRepository; - @BeforeEach - void init() { - Member member = Member.builder() - .nickname("일개미1234") - .email("kim1234@gmail.com") - .password("kim1234@") - .provider("local") - .build(); - this.member = memberRepository.save(member); - } + @Autowired + private StockRepository stockRepository; @AfterEach void tearDown() { + purchaseHistoryRepository.deleteAllInBatch(); + portFolioHoldingRepository.deleteAllInBatch(); + portfolioGainHistoryRepository.deleteAllInBatch(); portfolioRepository.deleteAllInBatch(); + stockRepository.deleteAllInBatch(); memberRepository.deleteAllInBatch(); } @@ -98,6 +89,7 @@ void tearDown() { @Test void addPortfolio() throws JsonProcessingException { // given + Member member = memberRepository.save(createMember()); Map body = new HashMap<>(); body.put("name", "내꿈은 워렌버핏"); body.put("securitiesFirm", "토스"); @@ -112,7 +104,10 @@ void addPortfolio() throws JsonProcessingException { PortFolioCreateResponse response = service.addPortFolio(request, AuthMember.from(member)); // then - assertThat(response).isNotNull(); + assertAll( + () -> assertThat(response).isNotNull(), + () -> assertThat(portfolioRepository.existsById(response.getPortfolioId())).isTrue() + ); } @DisplayName("회원은 포트폴리오를 추가할때 목표수익금액이 예산보다 같거나 작으면 안된다") @@ -120,6 +115,7 @@ void addPortfolio() throws JsonProcessingException { @ParameterizedTest void addPortfolioWithTargetGainIsEqualLessThanBudget(Long targetGain) throws JsonProcessingException { // given + Member member = memberRepository.save(createMember()); Map body = new HashMap<>(); body.put("name", "내꿈은 워렌버핏"); body.put("securitiesFirm", "토스"); @@ -145,6 +141,7 @@ void addPortfolioWithTargetGainIsEqualLessThanBudget(Long targetGain) throws Jso @ParameterizedTest void addPortfolioWithMaximumLossIsEqualGreaterThanBudget(Long maximumLoss) throws JsonProcessingException { // given + Member member = memberRepository.save(createMember()); Map body = new HashMap<>(); body.put("name", "내꿈은 워렌버핏"); body.put("securitiesFirm", "토스"); @@ -169,14 +166,8 @@ void addPortfolioWithMaximumLossIsEqualGreaterThanBudget(Long maximumLoss) throw @Test void addPortfolioWithDuplicateName() throws JsonProcessingException { // given - portfolioRepository.save(Portfolio.builder() - .name("내꿈은 워렌버핏") - .securitiesFirm("토스") - .budget(1000000L) - .targetGain(1500000L) - .maximumLoss(900000L) - .member(member) - .build()); + Member member = memberRepository.save(createMember()); + portfolioRepository.save(createPortfolio(member)); Map body = new HashMap<>(); body.put("name", "내꿈은 워렌버핏"); @@ -202,14 +193,8 @@ void addPortfolioWithDuplicateName() throws JsonProcessingException { @Test void modifyPortfolio() throws JsonProcessingException { // given - Portfolio originPortfolio = portfolioRepository.save(Portfolio.builder() - .name("내꿈은 워렌버핏") - .securitiesFirm("토스") - .budget(1000000L) - .targetGain(1500000L) - .maximumLoss(900000L) - .member(member) - .build()); + Member member = memberRepository.save(createMember()); + Portfolio originPortfolio = portfolioRepository.save(createPortfolio(member)); Map body = new HashMap<>(); body.put("name", "내꿈은 워렌버핏2"); @@ -227,7 +212,6 @@ void modifyPortfolio() throws JsonProcessingException { // then Portfolio changePortfolio = portfolioRepository.findById(portfolioId).orElseThrow(); - assertThat(changePortfolio) .extracting("name", "securitiesFirm", "budget", "targetGain", "maximumLoss") .containsExactly("내꿈은 워렌버핏2", "미래에셋증권", 1500000L, 2000000L, 900000L); @@ -237,40 +221,34 @@ void modifyPortfolio() throws JsonProcessingException { @Test void deletePortfolio() { // given - Portfolio portfolio = portfolioRepository.save(Portfolio.builder() - .name("내꿈은 워렌버핏") - .securitiesFirm("토스") - .budget(1000000L) - .targetGain(1500000L) - .maximumLoss(900000L) - .member(member) - .build()); + Member member = memberRepository.save(createMember()); + Stock stock = stockRepository.save(createStock()); + Portfolio portfolio = portfolioRepository.save(createPortfolio(member)); + PortfolioGainHistory portfolioGainHistory = portfolioGainHistoryRepository.save( + createPortfolioGainHistory(portfolio)); + PortfolioHolding portfolioHolding = portFolioHoldingRepository.save(createPortfolioHolding(portfolio, stock)); + PurchaseHistory purchaseHistory = purchaseHistoryRepository.save(createPurchaseHistory(portfolioHolding)); // when service.deletePortfolio(portfolio.getId(), AuthMember.from(member)); // then - boolean result = portfolioRepository.existsById(portfolio.getId()); - - assertThat(result).isFalse(); + assertAll( + () -> assertThat(portfolioRepository.existsById(portfolio.getId())).isFalse(), + () -> assertThat(portfolioGainHistoryRepository.existsById(portfolioGainHistory.getId())).isFalse(), + () -> assertThat(portFolioHoldingRepository.existsById(portfolioHolding.getId())).isFalse(), + () -> assertThat(purchaseHistoryRepository.existsById(purchaseHistory.getId())).isFalse() + ); } @DisplayName("사용자가 나의 포트폴리오들을 처음 조회한다") @Test void readMyAllPortfolio() { // given + Member member = memberRepository.save(createMember()); List portfolios = new ArrayList<>(); for (int i = 1; i <= 25; i++) { - portfolios.add(Portfolio.builder() - .name("내꿈은 워렌버핏" + i) - .securitiesFirm("토스") - .budget(1000000L) - .targetGain(1500000L) - .maximumLoss(900000L) - .member(member) - .targetGainIsActive(false) - .maximumIsActive(false) - .build()); + portfolios.add(createPortfolioWithRandomName(member)); } portfolioRepository.saveAll(portfolios); @@ -289,57 +267,122 @@ void readMyAllPortfolio() { @Test void deletePortfolios() { // given - Portfolio portfolio = portfolioRepository.save(Portfolio.builder() + Member member = memberRepository.save(createMember()); + Portfolio portfolio = portfolioRepository.save(createPortfolio(member)); + Stock stock = stockRepository.save(createStock()); + PortfolioHolding portfolioHolding = portFolioHoldingRepository.save(createPortfolioHolding(portfolio, stock)); + PurchaseHistory purchaseHistory1 = purchaseHistoryRepository.save(createPurchaseHistory(portfolioHolding)); + PurchaseHistory purchaseHistory2 = purchaseHistoryRepository.save(createPurchaseHistory(portfolioHolding)); + Portfolio portfolio2 = portfolioRepository.save(createPortfolioWithRandomName(member)); + + PortfoliosDeleteRequest request = new PortfoliosDeleteRequest(List.of(portfolio.getId(), portfolio2.getId())); + // when + service.deletePortfolios(request, AuthMember.from(member)); + + // then + assertThat(portfolioRepository.existsById(portfolio.getId())).isFalse(); + assertThat(portFolioHoldingRepository.existsById(portfolioHolding.getId())).isFalse(); + assertThat(purchaseHistoryRepository.existsById(purchaseHistory1.getId())).isFalse(); + assertThat(purchaseHistoryRepository.existsById(purchaseHistory2.getId())).isFalse(); + assertThat(portfolioRepository.existsById(portfolio2.getId())).isFalse(); + } + + private Member createMember() { + return Member.builder() + .nickname("일개미1234") + .email("kim1234@gmail.com") + .password("kim1234@") + .provider("local") + .build(); + } + + private Portfolio createPortfolio(Member member) { + return Portfolio.builder() .name("내꿈은 워렌버핏") .securitiesFirm("토스") .budget(1000000L) .targetGain(1500000L) .maximumLoss(900000L) .member(member) - .build()); - - PortfolioHolding portfolioHolding = portFolioHoldingRepository.save( - PortfolioHolding.builder() - .portfolio(portfolio) - .build() - ); - - PurchaseHistory purchaseHistory1 = purchaseHistoryRepository.save( - PurchaseHistory.builder() - .portfolioHolding(portfolioHolding) - .purchaseDate(LocalDateTime.now()) - .purchasePricePerShare(10000.0) - .numShares(10L) - .build() - ); - - PurchaseHistory purchaseHistory2 = purchaseHistoryRepository.save( - PurchaseHistory.builder() - .portfolioHolding(portfolioHolding) - .purchaseDate(LocalDateTime.now()) - .purchasePricePerShare(10000.0) - .numShares(10L) - .build() - ); + .targetGainIsActive(false) + .maximumIsActive(false) + .build(); + } - Portfolio portfolio2 = portfolioRepository.save(Portfolio.builder() - .name("내꿈은 워렌버핏") + private Portfolio createPortfolioWithRandomName(Member member) { + String randomPostfix = UUID.randomUUID().toString().substring(0, 10); + return Portfolio.builder() + .name("내꿈은 워렌버핏" + randomPostfix) .securitiesFirm("토스") .budget(1000000L) .targetGain(1500000L) .maximumLoss(900000L) .member(member) - .build()); + .targetGainIsActive(false) + .maximumIsActive(false) + .build(); + } - // when - PortfoliosDeleteRequest request = new PortfoliosDeleteRequest(List.of(portfolio.getId(), portfolio2.getId())); - service.deletePortfolios(request, AuthMember.from(member)); + private PortfolioGainHistory createPortfolioGainHistory(Portfolio portfolio) { + return PortfolioGainHistory.builder() + .totalGain(portfolio.calculateTotalGain()) + .dailyGain(portfolio.calculateDailyGain(PortfolioGainHistory.empty())) + .cash(portfolio.calculateBalance()) + .currentValuation(portfolio.calculateTotalCurrentValuation()) + .portfolio(portfolio) + .build(); + } - // then - assertThat(portfolioRepository.existsById(portfolio.getId())).isFalse(); - assertThat(portFolioHoldingRepository.existsById(portfolioHolding.getId())).isFalse(); - assertThat(purchaseHistoryRepository.existsById(purchaseHistory1.getId())).isFalse(); - assertThat(purchaseHistoryRepository.existsById(purchaseHistory2.getId())).isFalse(); - assertThat(portfolioRepository.existsById(portfolio2.getId())).isFalse(); + private Stock createStock() { + return Stock.builder() + .companyName("삼성전자보통주") + .tickerSymbol("005930") + .companyNameEng("SamsungElectronics") + .stockCode("KR7005930003") + .sector("전기전자") + .market(Market.KOSPI) + .build(); + } + + private StockDividend createStockDividend(LocalDate exDividendDate, LocalDate recordDate, LocalDate paymentDate, + Stock stock) { + return StockDividend.builder() + .dividend(361L) + .exDividendDate(exDividendDate) + .recordDate(recordDate) + .paymentDate(paymentDate) + .stock(stock) + .build(); + } + + private PortfolioHolding createPortfolioHolding(Portfolio portfolio, Stock stock) { + return PortfolioHolding.builder() + .portfolio(portfolio) + .stock(stock) + .build(); + } + + private PurchaseHistory createPurchaseHistory(PortfolioHolding portfolioHolding) { + return PurchaseHistory.builder() + .purchaseDate(LocalDateTime.of(2023, 9, 26, 9, 30, 0)) + .numShares(3L) + .purchasePricePerShare(50000.0) + .memo("첫구매") + .portfolioHolding(portfolioHolding) + .build(); + } + + private static Stream provideInvalidTargetGain() { + return Stream.of( + Arguments.of(900000L), + Arguments.of(1000000L) + ); + } + + private static Stream provideInvalidMaximumLoss() { + return Stream.of( + Arguments.of(1000000L), + Arguments.of(1100000L) + ); } }