From 3d36bce6d1f6d9883bdee0aa2cb12a6a99e2bb94 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Thu, 7 Nov 2024 17:05:11 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20fetch=20AP?= =?UTF-8?q?I=20axios=EB=A1=9C=20=EB=AA=A8=EB=91=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/stock/index/stock.index.service.ts | 64 +++++++++++-------- .../websocket/interface/socket.interface.ts | 3 + BE/src/websocket/socket.service.ts | 26 +++----- 3 files changed, 48 insertions(+), 45 deletions(-) create mode 100644 BE/src/websocket/interface/socket.interface.ts diff --git a/BE/src/stock/index/stock.index.service.ts b/BE/src/stock/index/stock.index.service.ts index f466a461..3b355f35 100644 --- a/BE/src/stock/index/stock.index.service.ts +++ b/BE/src/stock/index/stock.index.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import axios from 'axios'; import { StockIndexListChartElementDto } from './dto/stock.index.list.chart.element.dto'; import { StockIndexListElementDto } from './dto/stock.index.list.element.dto'; import { StockIndexValueElementDto } from './dto/stock.index.value.element.dto'; @@ -10,22 +11,26 @@ import { @Injectable() export class StockIndexService { async getDomesticStockIndexListByCode(code: string, accessToken: string) { - const url = `${process.env.KOREA_INVESTMENT_BASE_URL}/uapi/domestic-stock/v1/quotations/inquire-index-timeprice`; - const queryParams = `?FID_INPUT_HOUR_1=300&FID_COND_MRKT_DIV_CODE=U&FID_INPUT_ISCD=${code}`; - - const response = await fetch(url + queryParams, { - method: 'GET', - headers: { - 'content-type': 'application/json; charset=utf-8', - authorization: `Bearer ${accessToken}`, - appkey: process.env.KOREA_INVESTMENT_APP_KEY, - appsecret: process.env.KOREA_INVESTMENT_APP_SECRET, - tr_id: 'FHPUP02110200', - custtype: 'P', + const response = await axios.get( + `${process.env.KOREA_INVESTMENT_BASE_URL}/uapi/domestic-stock/v1/quotations/inquire-index-timeprice`, + { + headers: { + 'content-type': 'application/json; charset=utf-8', + authorization: `Bearer ${accessToken}`, + appkey: process.env.KOREA_INVESTMENT_APP_KEY, + appsecret: process.env.KOREA_INVESTMENT_APP_SECRET, + tr_id: 'FHPUP02110200', + custtype: 'P', + }, + params: { + fid_input_hour_1: 300, + fid_cond_mrkt_div_code: 'U', + fid_input_iscd: code, + }, }, - }); + ); - const result: StockIndexChartInterface = await response.json(); + const result = response.data; if (result.rt_cd !== '0') throw new Error('유효하지 않은 토큰'); return new StockIndexListElementDto( @@ -40,22 +45,25 @@ export class StockIndexService { } async getDomesticStockIndexValueByCode(code: string, accessToken: string) { - const url = `${process.env.KOREA_INVESTMENT_BASE_URL}/uapi/domestic-stock/v1/quotations/inquire-index-price`; - const queryParams = `?FID_COND_MRKT_DIV_CODE=U&FID_INPUT_ISCD=${code}`; - - const response = await fetch(url + queryParams, { - method: 'GET', - headers: { - 'content-type': 'application/json; charset=utf-8', - authorization: `Bearer ${accessToken}`, - appkey: process.env.KOREA_INVESTMENT_APP_KEY, - appsecret: process.env.KOREA_INVESTMENT_APP_SECRET, - tr_id: 'FHPUP02100000', - custtype: 'P', + const response = await axios.get( + `${process.env.KOREA_INVESTMENT_BASE_URL}/uapi/domestic-stock/v1/quotations/inquire-index-price`, + { + headers: { + 'content-type': 'application/json; charset=utf-8', + authorization: `Bearer ${accessToken}`, + appkey: process.env.KOREA_INVESTMENT_APP_KEY, + appsecret: process.env.KOREA_INVESTMENT_APP_SECRET, + tr_id: 'FHPUP02100000', + custtype: 'P', + }, + params: { + fid_cond_mrkt_div_code: 'U', + fid_input_iscd: code, + }, }, - }); + ); - const result: StockIndexValueInterface = await response.json(); + const result = response.data; return new StockIndexValueElementDto( code, result.output.bstp_nmix_prpr, diff --git a/BE/src/websocket/interface/socket.interface.ts b/BE/src/websocket/interface/socket.interface.ts new file mode 100644 index 00000000..d9a6aabb --- /dev/null +++ b/BE/src/websocket/interface/socket.interface.ts @@ -0,0 +1,3 @@ +export interface SocketConnectTokenInterface { + approval_key: string; +} diff --git a/BE/src/websocket/socket.service.ts b/BE/src/websocket/socket.service.ts index 543517c0..0c25bf03 100644 --- a/BE/src/websocket/socket.service.ts +++ b/BE/src/websocket/socket.service.ts @@ -1,7 +1,9 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; import { WebSocket } from 'ws'; +import axios from 'axios'; import { SocketGateway } from './socket.gateway'; import { StockIndexValueElementDto } from '../stock/index/dto/stock.index.value.element.dto'; +import { SocketConnectTokenInterface } from './interface/socket.interface'; @Injectable() export class SocketService implements OnModuleInit { @@ -50,20 +52,16 @@ export class SocketService implements OnModuleInit { } private async getSocketConnectionKey() { - const url = `${process.env.KOREA_INVESTMENT_BASE_URL}/oauth2/Approval`; - - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - }, - body: JSON.stringify({ + const response = await axios.post( + `${process.env.KOREA_INVESTMENT_BASE_URL}/oauth2/Approval`, + { grant_type: 'client_credentials', appkey: process.env.KOREA_INVESTMENT_APP_KEY, secretkey: process.env.KOREA_INVESTMENT_APP_SECRET, - }), - }); - const result: SocketConnectTokenInterface = await response.json(); + }, + ); + + const result = response.data; return result.approval_key; } @@ -86,9 +84,3 @@ export class SocketService implements OnModuleInit { ); } } - -// interfaces - -interface SocketConnectTokenInterface { - approval_key: string; -} From bb3cdbe9e7ae907dfc68eb29438d14cf457618ba Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Thu, 7 Nov 2024 17:23:19 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20stock=20in?= =?UTF-8?q?dex=20=EA=B4=80=EB=A0=A8=20API=20=EC=9A=94=EC=B2=AD=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=95=A8=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/stock/index/stock.index.service.ts | 64 +++++++++++++++-------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/BE/src/stock/index/stock.index.service.ts b/BE/src/stock/index/stock.index.service.ts index 3b355f35..e0690231 100644 --- a/BE/src/stock/index/stock.index.service.ts +++ b/BE/src/stock/index/stock.index.service.ts @@ -11,6 +11,43 @@ import { @Injectable() export class StockIndexService { async getDomesticStockIndexListByCode(code: string, accessToken: string) { + const result = await this.requestDomesticStockIndexListApi( + code, + accessToken, + ); + + if (result.rt_cd !== '0') throw new Error('유효하지 않은 토큰'); + + return new StockIndexListElementDto( + code, + result.output.map((element) => { + return new StockIndexListChartElementDto( + element.bsop_hour, + element.bstp_nmix_prpr, + ); + }), + ); + } + + async getDomesticStockIndexValueByCode(code: string, accessToken: string) { + const result = await this.requestDomesticStockIndexValueApi( + code, + accessToken, + ); + + return new StockIndexValueElementDto( + code, + result.output.bstp_nmix_prpr, + result.output.bstp_nmix_prdy_vrss, + result.output.bstp_nmix_prdy_vrss, + result.output.prdy_vrss_sign, + ); + } + + private async requestDomesticStockIndexListApi( + code: string, + accessToken: string, + ) { const response = await axios.get( `${process.env.KOREA_INVESTMENT_BASE_URL}/uapi/domestic-stock/v1/quotations/inquire-index-timeprice`, { @@ -30,21 +67,13 @@ export class StockIndexService { }, ); - const result = response.data; - if (result.rt_cd !== '0') throw new Error('유효하지 않은 토큰'); - - return new StockIndexListElementDto( - code, - result.output.map((element) => { - return new StockIndexListChartElementDto( - element.bsop_hour, - element.bstp_nmix_prpr, - ); - }), - ); + return response.data; } - async getDomesticStockIndexValueByCode(code: string, accessToken: string) { + private async requestDomesticStockIndexValueApi( + code: string, + accessToken: string, + ) { const response = await axios.get( `${process.env.KOREA_INVESTMENT_BASE_URL}/uapi/domestic-stock/v1/quotations/inquire-index-price`, { @@ -63,13 +92,6 @@ export class StockIndexService { }, ); - const result = response.data; - return new StockIndexValueElementDto( - code, - result.output.bstp_nmix_prpr, - result.output.bstp_nmix_prdy_vrss, - result.output.bstp_nmix_prdy_vrss, - result.output.prdy_vrss_sign, - ); + return response.data; } } From 2e10d75fe16e91771f8b728920edfbe8fff54866 Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Thu, 7 Nov 2024 19:08:33 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=85=20test:=20=EC=A3=BC=EA=B0=80=20?= =?UTF-8?q?=EC=A7=80=EC=88=98=20=EC=B0=A8=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/stock/index/stock.index.service.ts | 3 +- .../mockdata/stock.index.list.mockdata.ts | 29 +++++++++++ .../stock/index/stock.index.list.e2e-spec.ts | 49 +++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 BE/test/stock/index/mockdata/stock.index.list.mockdata.ts create mode 100644 BE/test/stock/index/stock.index.list.e2e-spec.ts diff --git a/BE/src/stock/index/stock.index.service.ts b/BE/src/stock/index/stock.index.service.ts index e0690231..1a7b500a 100644 --- a/BE/src/stock/index/stock.index.service.ts +++ b/BE/src/stock/index/stock.index.service.ts @@ -16,7 +16,8 @@ export class StockIndexService { accessToken, ); - if (result.rt_cd !== '0') throw new Error('유효하지 않은 토큰'); + if (result.rt_cd !== '0') + throw new Error('데이터를 정상적으로 조회하지 못했습니다.'); return new StockIndexListElementDto( code, diff --git a/BE/test/stock/index/mockdata/stock.index.list.mockdata.ts b/BE/test/stock/index/mockdata/stock.index.list.mockdata.ts new file mode 100644 index 00000000..7fb74649 --- /dev/null +++ b/BE/test/stock/index/mockdata/stock.index.list.mockdata.ts @@ -0,0 +1,29 @@ +export const STOCK_INDEX_LIST_MOCK = { + VALID_DATA: { + data: { + output: [ + { + bsop_hour: '100600', + bstp_nmix_prpr: '916.77', + bstp_nmix_prdy_vrss: '11.27', + prdy_vrss_sign: '2', + bstp_nmix_prdy_ctrt: '1.24', + acml_tr_pbmn: '3839797', + acml_vol: '313374', + cntg_vol: '870', + }, + ], + rt_cd: '0', + msg_cd: 'MCA00000', + msg1: '정상처리 되었습니다.', + }, + }, + INVALID_DATA: { + data: { + output: [], + rt_cd: '1', + msg_cd: 'MCA00000', + msg1: '유효하지 않은 토큰입니다.', + }, + }, +}; diff --git a/BE/test/stock/index/stock.index.list.e2e-spec.ts b/BE/test/stock/index/stock.index.list.e2e-spec.ts new file mode 100644 index 00000000..6079f011 --- /dev/null +++ b/BE/test/stock/index/stock.index.list.e2e-spec.ts @@ -0,0 +1,49 @@ +import { Test } from '@nestjs/testing'; +import axios from 'axios'; +import { StockIndexService } from '../../../src/stock/index/stock.index.service'; +import { STOCK_INDEX_LIST_MOCK } from './mockdata/stock.index.list.mockdata'; + +jest.mock('axios'); + +describe('stock index list test', () => { + let stockIndexService: StockIndexService; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [StockIndexService], + }).compile(); + + stockIndexService = module.get(StockIndexService); + }); + + it('주가 지수 API에서 정상적인 데이터를 조회한 경우, 형식에 맞춰 정상적으로 반환한다.', async () => { + (axios.get as jest.Mock).mockResolvedValue( + STOCK_INDEX_LIST_MOCK.VALID_DATA, + ); + + expect( + await stockIndexService.getDomesticStockIndexListByCode( + 'code', + 'accessToken', + ), + ).toEqual({ + code: 'code', + chart: [ + { + time: STOCK_INDEX_LIST_MOCK.VALID_DATA.data.output[0].bsop_hour, + value: STOCK_INDEX_LIST_MOCK.VALID_DATA.data.output[0].bstp_nmix_prpr, + }, + ], + }); + }); + + it('주가 지수 API에서 데이터를 조회하지 못한 경우, 에러를 발생시킨다.', async () => { + (axios.get as jest.Mock).mockResolvedValue( + STOCK_INDEX_LIST_MOCK.INVALID_DATA, + ); + + await expect( + stockIndexService.getDomesticStockIndexListByCode('code', 'accessToken'), + ).rejects.toThrow('데이터를 정상적으로 조회하지 못했습니다.'); + }); +}); From 9d8ff3320736db17694540345876d1f1fb83d1bf Mon Sep 17 00:00:00 2001 From: anjdydhody Date: Thu, 7 Nov 2024 19:19:22 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9C=85=20test:=20=EC=A3=BC=EA=B0=80=20?= =?UTF-8?q?=EC=A7=80=EC=88=98=20=EA=B0=92=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/stock/index/stock.index.service.ts | 5 +- .../mockdata/stock.index.value.mockdata.ts | 55 +++++++++++++++++++ .../stock/index/stock.index.list.e2e-spec.ts | 4 +- .../stock/index/stock.index.value.e2e-spec.ts | 48 ++++++++++++++++ 4 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 BE/test/stock/index/mockdata/stock.index.value.mockdata.ts create mode 100644 BE/test/stock/index/stock.index.value.e2e-spec.ts diff --git a/BE/src/stock/index/stock.index.service.ts b/BE/src/stock/index/stock.index.service.ts index 1a7b500a..875be12d 100644 --- a/BE/src/stock/index/stock.index.service.ts +++ b/BE/src/stock/index/stock.index.service.ts @@ -36,11 +36,14 @@ export class StockIndexService { accessToken, ); + if (result.rt_cd !== '0') + throw new Error('데이터를 정상적으로 조회하지 못했습니다.'); + return new StockIndexValueElementDto( code, result.output.bstp_nmix_prpr, result.output.bstp_nmix_prdy_vrss, - result.output.bstp_nmix_prdy_vrss, + result.output.bstp_nmix_prdy_ctrt, result.output.prdy_vrss_sign, ); } diff --git a/BE/test/stock/index/mockdata/stock.index.value.mockdata.ts b/BE/test/stock/index/mockdata/stock.index.value.mockdata.ts new file mode 100644 index 00000000..0faa49d9 --- /dev/null +++ b/BE/test/stock/index/mockdata/stock.index.value.mockdata.ts @@ -0,0 +1,55 @@ +export const STOCK_INDEX_VALUE_MOCK = { + VALID_DATA: { + data: { + output: { + bstp_nmix_prpr: '857.60', + bstp_nmix_prdy_vrss: '-1.61', + prdy_vrss_sign: '5', + bstp_nmix_prdy_ctrt: '-0.19', + acml_vol: '1312496', + prdy_vol: '1222188', + acml_tr_pbmn: '11507962', + prdy_tr_pbmn: '11203385', + bstp_nmix_oprc: '863.69', + prdy_nmix_vrss_nmix_oprc: '4.48', + oprc_vrss_prpr_sign: '2', + bstp_nmix_oprc_prdy_ctrt: '0.52', + bstp_nmix_hgpr: '864.24', + prdy_nmix_vrss_nmix_hgpr: '5.03', + hgpr_vrss_prpr_sign: '2', + bstp_nmix_hgpr_prdy_ctrt: '0.59', + bstp_nmix_lwpr: '854.72', + prdy_clpr_vrss_lwpr: '-4.49', + lwpr_vrss_prpr_sign: '5', + prdy_clpr_vrss_lwpr_rate: '-0.52', + ascn_issu_cnt: '828', + uplm_issu_cnt: '5', + stnr_issu_cnt: '94', + down_issu_cnt: '716', + lslm_issu_cnt: '1', + dryy_bstp_nmix_hgpr: '890.06', + dryy_hgpr_vrss_prpr_rate: '3.65', + dryy_bstp_nmix_hgpr_date: '20240109', + dryy_bstp_nmix_lwpr: '786.28', + dryy_lwpr_vrss_prpr_rate: '-9.07', + dryy_bstp_nmix_lwpr_date: '20240201', + total_askp_rsqn: '24146999', + total_bidp_rsqn: '40450437', + seln_rsqn_rate: '37.38', + shnu_rsqn_rate: '62.62', + ntby_rsqn: '16303438', + }, + rt_cd: '0', + msg_cd: 'MCA00000', + msg1: '정상처리 되었습니다.', + }, + }, + INVALID_DATA: { + data: { + output: {}, + rt_cd: '1', + msg_cd: 'MCA00000', + msg1: '유효하지 않은 토큰입니다.', + }, + }, +}; diff --git a/BE/test/stock/index/stock.index.list.e2e-spec.ts b/BE/test/stock/index/stock.index.list.e2e-spec.ts index 6079f011..7518b75d 100644 --- a/BE/test/stock/index/stock.index.list.e2e-spec.ts +++ b/BE/test/stock/index/stock.index.list.e2e-spec.ts @@ -16,7 +16,7 @@ describe('stock index list test', () => { stockIndexService = module.get(StockIndexService); }); - it('주가 지수 API에서 정상적인 데이터를 조회한 경우, 형식에 맞춰 정상적으로 반환한다.', async () => { + it('주가 지수 차트 조회 API에서 정상적인 데이터를 조회한 경우, 형식에 맞춰 정상적으로 반환한다.', async () => { (axios.get as jest.Mock).mockResolvedValue( STOCK_INDEX_LIST_MOCK.VALID_DATA, ); @@ -37,7 +37,7 @@ describe('stock index list test', () => { }); }); - it('주가 지수 API에서 데이터를 조회하지 못한 경우, 에러를 발생시킨다.', async () => { + it('주가 지수 차트 조회 API에서 데이터를 조회하지 못한 경우, 에러를 발생시킨다.', async () => { (axios.get as jest.Mock).mockResolvedValue( STOCK_INDEX_LIST_MOCK.INVALID_DATA, ); diff --git a/BE/test/stock/index/stock.index.value.e2e-spec.ts b/BE/test/stock/index/stock.index.value.e2e-spec.ts new file mode 100644 index 00000000..12ff2590 --- /dev/null +++ b/BE/test/stock/index/stock.index.value.e2e-spec.ts @@ -0,0 +1,48 @@ +import { Test } from '@nestjs/testing'; +import axios from 'axios'; +import { StockIndexService } from '../../../src/stock/index/stock.index.service'; +import { STOCK_INDEX_VALUE_MOCK } from './mockdata/stock.index.value.mockdata'; + +jest.mock('axios'); + +describe('stock index list test', () => { + let stockIndexService: StockIndexService; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [StockIndexService], + }).compile(); + + stockIndexService = module.get(StockIndexService); + }); + + it('주가 지수 값 조회 API에서 정상적인 데이터를 조회한 경우, 형식에 맞춰 정상적으로 반환한다.', async () => { + (axios.get as jest.Mock).mockResolvedValue( + STOCK_INDEX_VALUE_MOCK.VALID_DATA, + ); + + expect( + await stockIndexService.getDomesticStockIndexValueByCode( + 'code', + 'accessToken', + ), + ).toEqual({ + code: 'code', + value: STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.bstp_nmix_prpr, + diff: STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.bstp_nmix_prdy_vrss, + diffRate: + STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.bstp_nmix_prdy_ctrt, + sign: STOCK_INDEX_VALUE_MOCK.VALID_DATA.data.output.prdy_vrss_sign, + }); + }); + + it('주가 지수 값 조회 API에서 데이터를 조회하지 못한 경우, 에러를 발생시킨다.', async () => { + (axios.get as jest.Mock).mockResolvedValue( + STOCK_INDEX_VALUE_MOCK.INVALID_DATA, + ); + + await expect( + stockIndexService.getDomesticStockIndexValueByCode('code', 'accessToken'), + ).rejects.toThrow('데이터를 정상적으로 조회하지 못했습니다.'); + }); +});