Skip to content

Commit

Permalink
[feat] 포트폴리오 차트 API 수정 (#389)
Browse files Browse the repository at this point in the history
* #386 test: 액세스 토큰 갱신 테스트 코드 문제 해결

- redis에 액세스 토큰에 대한 로그아웃한 기록 존재한 것이 원인
- Date 타입 객체의 toInstant() 호출시 UTC 기준으로 반환된 것이 문제

* #386 feat: 차트 조회시 포트폴리오 디테일 프로퍼티 추가

- 포트폴리오 디테일 프로퍼티에는 등록번호(id), 증권사(securitiesFirm), 이름(name)이 포함되어 있습니다.
- 추가 이유 : 프론트 모바일 환경에서 차트 조회시 포트폴리오에 대한 디테일 정보가 필요하기 때문
  • Loading branch information
yonghwankim-dev authored Jun 25, 2024
1 parent 94b5fd4 commit e42abd9
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
@Builder
@ToString
public class PortfolioChartResponse {
private PortfolioDetails portfolioDetails;
private List<PortfolioPieChartItem> pieChart;
private List<PortfolioDividendChartItem> dividendChart;
private List<PortfolioSectorChartItem> sectorChart;

public static PortfolioChartResponse create(
PortfolioDetails portfolioDetails,
List<PortfolioPieChartItem> pieChart,
List<PortfolioDividendChartItem> dividendChart,
List<PortfolioSectorChartItem> sectorChart) {
return new PortfolioChartResponse(pieChart, dividendChart, sectorChart);
return new PortfolioChartResponse(portfolioDetails, pieChart, dividendChart, sectorChart);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package codesquad.fineants.domain.holding.domain.dto.response;

import codesquad.fineants.domain.portfolio.domain.entity.Portfolio;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@ToString
public class PortfolioDetails {
private Long id;
private String securitiesFirm;
private String name;

public static PortfolioDetails from(Portfolio portfolio) {
return new PortfolioDetails(portfolio.getId(), portfolio.getSecuritiesFirm(), portfolio.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioChartResponse;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioDetailRealTimeItem;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioDetailResponse;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioDetails;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioDividendChartItem;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioHoldingItem;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioHoldingRealTimeItem;
Expand Down Expand Up @@ -174,9 +175,10 @@ public PortfolioHoldingsRealTimeResponse readMyPortfolioStocksInRealTime(Long po

public PortfolioChartResponse readPortfolioCharts(Long portfolioId, LocalDate currentLocalDate) {
Portfolio portfolio = findPortfolio(portfolioId);
PortfolioDetails portfolioDetails = PortfolioDetails.from(portfolio);
List<PortfolioPieChartItem> pieChartItems = pieChart.createBy(portfolio);
List<PortfolioDividendChartItem> dividendChartItems = dividendChart.createBy(portfolio, currentLocalDate);
List<PortfolioSectorChartItem> sectorChartItems = sectorChart.createBy(portfolio);
return PortfolioChartResponse.create(pieChartItems, dividendChartItems, sectorChartItems);
return PortfolioChartResponse.create(portfolioDetails, pieChartItems, dividendChartItems, sectorChartItems);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package codesquad.fineants.domain.member.service;

import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RequiredArgsConstructor
@Service
@Slf4j
public class OauthMemberRedisService {

private static final String LOGOUT = "logout";
Expand Down Expand Up @@ -43,4 +46,14 @@ public void saveEmailVerifCode(String email, String verifCode) {
long expirationTimeInMinutes = 5; // 5 minutes
redisTemplate.opsForValue().set(email, verifCode, expirationTimeInMinutes, TimeUnit.MINUTES);
}

public void clear() {
Set<String> keys = redisTemplate.keys("*");
if (keys == null) {
return;
}
for (String key : keys) {
redisTemplate.delete(key);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,15 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
log.debug("requestURL : {}", ((HttpServletRequest)request).getRequestURL());
if (accessToken != null && !oauthMemberRedisService.isAlreadyLogout(accessToken)) {
if (tokenService.isExpiredToken(accessToken)) {
log.debug("accessToken is Expired");
token = tokenService.refreshToken(refreshToken, LocalDateTime.now());
setTokenCookie((HttpServletResponse)response, token);
} else if (tokenService.verifyToken(accessToken) && tokenService.isRefreshTokenNearExpiry(refreshToken)) {
log.debug("accessToken is verified but, refreshToken is near expired");
token = tokenService.refreshToken(refreshToken, LocalDateTime.now());
setTokenCookie((HttpServletResponse)response, token);
} else if (tokenService.verifyToken(accessToken)) {
log.debug("accessToken is verified and refreshToken is not near expired");
token = Token.create(accessToken, refreshToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.sql.Timestamp;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Base64;
Expand Down Expand Up @@ -141,13 +140,18 @@ public boolean isRefreshTokenNearExpiry(String token) {
}

// 현재 시간
Instant now = Instant.now();
LocalDateTime now = LocalDateTime.now();
log.debug("now : {}", now);

// 만료 시간
Instant expirationInstant = expiration.toInstant();
LocalDateTime expirationDateTime = expiration.toInstant()
.atZone(java.time.ZoneId.systemDefault())
.toLocalDateTime();
log.debug("expirationDateTime : {}", expirationDateTime);

// 만료 까지의 시간 간격
Duration duration = Duration.between(now, expirationInstant);
Duration duration = Duration.between(now, expirationDateTime);
log.debug("duration : {}", duration);

// 만료 까지의 시간 간격이 EXPIRY_IMMINENT_TIME 미만 인지 확인
return duration.compareTo(EXPIRY_IMMINENT_TIME) < 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import codesquad.fineants.domain.holding.controller.PortfolioHoldingRestController;
import codesquad.fineants.domain.holding.domain.dto.request.PortfolioHoldingCreateRequest;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioChartResponse;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioDetails;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioDividendChartItem;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioHoldingsResponse;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioPieChartItem;
Expand Down Expand Up @@ -335,6 +336,7 @@ void readPortfolioCharts() throws Exception {
int samsungValuation = 600000;
int samsungTotalGain = 100000;
int cash = 500000;
PortfolioDetails portfolioDetails = PortfolioDetails.from(portfolio);
List<PortfolioPieChartItem> pieChartItems = List.of(
PortfolioPieChartItem.stock(
"삼성전자보통주",
Expand Down Expand Up @@ -367,7 +369,8 @@ void readPortfolioCharts() throws Exception {
);

given(service.readPortfolioCharts(anyLong(), ArgumentMatchers.any(LocalDate.class)))
.willReturn(PortfolioChartResponse.create(pieChartItems, dividendChartItems, sectorChartItems));
.willReturn(PortfolioChartResponse.create(portfolioDetails, pieChartItems, dividendChartItems,
sectorChartItems));

// when
mockMvc.perform(
Expand All @@ -377,6 +380,9 @@ void readPortfolioCharts() throws Exception {
.andExpect(jsonPath("code").value(equalTo(200)))
.andExpect(jsonPath("status").value(equalTo("OK")))
.andExpect(jsonPath("message").value(equalTo("포트폴리오에 대한 차트 조회가 완료되었습니다")))
.andExpect(jsonPath("data.portfolioDetails.id").value(equalTo(1)))
.andExpect(jsonPath("data.portfolioDetails.securitiesFirm").value(equalTo("토스증권")))
.andExpect(jsonPath("data.portfolioDetails.name").value(equalTo("내꿈은 워렌버핏")))
.andExpect(jsonPath("data.pieChart[0].name").value(equalTo("삼성전자보통주")))
.andExpect(jsonPath("data.pieChart[0].valuation").value(equalTo(600000)))
.andExpect(jsonPath("data.pieChart[0].weight").value(closeTo(54.55, 0.1)))
Expand Down Expand Up @@ -432,6 +438,12 @@ void readPortfolioCharts() throws Exception {
.description("메시지"),
fieldWithPath("data").type(JsonFieldType.OBJECT)
.description("응답 데이터"),
fieldWithPath("data.portfolioDetails.id").type(JsonFieldType.NUMBER)
.description("포트폴리오 등록번호"),
fieldWithPath("data.portfolioDetails.securitiesFirm").type(JsonFieldType.STRING)
.description("포트폴리오 증권사"),
fieldWithPath("data.portfolioDetails.name").type(JsonFieldType.STRING)
.description("포트폴리오 이름"),
fieldWithPath("data.pieChart").type(JsonFieldType.ARRAY)
.description("파이 차트 리스트"),
fieldWithPath("data.pieChart[].name").type(JsonFieldType.STRING)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import codesquad.fineants.domain.holding.domain.dto.request.PortfolioHoldingCreateRequest;
import codesquad.fineants.domain.holding.domain.dto.request.PortfolioStocksDeleteRequest;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioChartResponse;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioDetails;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioDividendChartItem;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioHoldingsResponse;
import codesquad.fineants.domain.holding.domain.dto.response.PortfolioPieChartItem;
Expand Down Expand Up @@ -350,12 +351,13 @@ void readMyPortfolioCharts() throws Exception {
DividendChart dividendChart = new DividendChart(currentPriceRepository);
SectorChart sectorChart = new SectorChart(currentPriceRepository);

PortfolioDetails portfolioDetails = PortfolioDetails.from(portfolio);
List<PortfolioPieChartItem> pieChartItems = pieChart.createBy(portfolio);
List<PortfolioDividendChartItem> dividendChartItems = dividendChart.createBy(portfolio,
LocalDate.of(2024, 1, 16));
List<PortfolioSectorChartItem> sectorChartItems = sectorChart.createBy(portfolio);
PortfolioChartResponse response = PortfolioChartResponse.create(pieChartItems, dividendChartItems,
sectorChartItems);
PortfolioChartResponse response = PortfolioChartResponse.create(portfolioDetails, pieChartItems,
dividendChartItems, sectorChartItems);
given(portfolioHoldingService.readPortfolioCharts(anyLong(), any(LocalDate.class)))
.willReturn(response);

Expand All @@ -365,6 +367,9 @@ void readMyPortfolioCharts() throws Exception {
.andExpect(jsonPath("code").value(equalTo(200)))
.andExpect(jsonPath("status").value(equalTo("OK")))
.andExpect(jsonPath("message").value(equalTo("포트폴리오에 대한 차트 조회가 완료되었습니다")))
.andExpect(jsonPath("data.portfolioDetails.id").value(equalTo(1)))
.andExpect(jsonPath("data.portfolioDetails.securitiesFirm").value(equalTo("토스증권")))
.andExpect(jsonPath("data.portfolioDetails.name").value(equalTo("내꿈은 워렌버핏")))
.andExpect(jsonPath("data.pieChart[0].name").value(equalTo("현금")))
.andExpect(jsonPath("data.pieChart[0].valuation").value(equalTo(850000)))
.andExpect(jsonPath("data.pieChart[0].weight").value(equalTo(82.52)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ void readMyPortfolioCharts() {

// then
assertAll(
() -> assertThat(response)
.extracting("portfolioDetails")
.extracting("id", "securitiesFirm", "name")
.containsExactly(portfolio.getId(), "토스증권", "내꿈은 워렌버핏"),
() -> assertThat(response)
.extracting("pieChart")
.asList()
Expand Down Expand Up @@ -291,6 +295,9 @@ void readMyPortfolioCharts_whenPortfolioBudgetIsZero_thenOK() {

// then
assertAll(
() -> assertThat(response.getPortfolioDetails())
.extracting("id", "securitiesFirm", "name")
.containsExactly(portfolio.getId(), "토스증권", "내꿈은 워렌버핏"),
() -> assertThat(response.getPieChart())
.asList()
.hasSize(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import java.util.stream.Stream;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
Expand All @@ -23,6 +23,7 @@
import codesquad.fineants.AbstractContainerBaseTest;
import codesquad.fineants.domain.member.domain.entity.Member;
import codesquad.fineants.domain.member.repository.MemberRepository;
import codesquad.fineants.domain.member.service.OauthMemberRedisService;
import codesquad.fineants.global.security.factory.TokenFactory;
import codesquad.fineants.global.security.oauth.dto.MemberAuthentication;
import codesquad.fineants.global.security.oauth.dto.Token;
Expand All @@ -43,6 +44,9 @@ public class AuthenticationIntegrationTest extends AbstractContainerBaseTest {
@Autowired
private TokenFactory tokenFactory;

@Autowired
private OauthMemberRedisService oauthMemberRedisService;

@LocalServerPort
private int port;

Expand All @@ -51,6 +55,11 @@ void setUp() {
RestAssured.port = port;
}

@AfterEach
void tearDown() {
oauthMemberRedisService.clear();
}

@SuppressWarnings("checkstyle:NoWhitespaceBefore")
@DisplayName("사용자는 일반 로그인한다")
@Test
Expand Down Expand Up @@ -142,7 +151,6 @@ void logout() {
* @param accessTokenCreateDate AccessToken 생성 시간
* @param refreshTokenCreateDate RefreshToken 생성 시간
*/
@Disabled("타임존 문제로 임시 차단")
@DisplayName("사용자는 액세스 토큰이 만료된 상태에서 액세스 토큰을 갱신한다")
@MethodSource(value = {"validJwtTokenCreateDateSource"})
@ParameterizedTest(name = "{index} ==> the tokenCreateDate is {0}, {1} ")
Expand Down Expand Up @@ -201,7 +209,6 @@ public static Stream<Arguments> validJwtTokenCreateDateSource() {
* @param accessTokenCreateDate AccessToken 생성 시간
* @param refreshTokenCreateDate RefreshToken 생성 시간
*/
@Disabled("타임존 문제로 임시 차단")
@DisplayName("사용자는 리프레시 토큰이 만료된 상태에서는 액세스 토큰을 갱신할 수 없다")
@MethodSource(value = {"invalidJwtTokenCreateDateSource"})
@ParameterizedTest(name = "{index} ==> the tokenCreateDate is {0}, {1} ")
Expand Down

0 comments on commit e42abd9

Please sign in to comment.