|
@@ -0,0 +1,127 @@
|
|
|
|
|
+using Domain.Entities.Stocks.ValueObject;
|
|
|
|
|
+using Infrastructure.StockData;
|
|
|
|
|
+
|
|
|
|
|
+namespace Application.Tests;
|
|
|
|
|
+
|
|
|
|
|
+[TestClass]
|
|
|
|
|
+public sealed class KrxStockParserTests
|
|
|
|
|
+{
|
|
|
|
|
+ [TestMethod]
|
|
|
|
|
+ public void ParseDailyPrices_MapsFields_FromSampleJson()
|
|
|
|
|
+ {
|
|
|
|
|
+ // KRX OpenAPI(stk_bydd_trd) 표준 응답 샘플 — ISU_CD=6자리 단축코드, FLUC_RT 는 소수 % 그대로 저장(Bp 아님)
|
|
|
|
|
+ const string json = """
|
|
|
|
|
+ {"OutBlock_1":[
|
|
|
|
|
+ {"BAS_DD":"20250102","ISU_CD":"005930","ISU_NM":"삼성전자","MKT_NM":"KOSPI","SECT_TP_NM":"","TDD_CLSPRC":"70,500","CMPPREVDD_PRC":"-500","FLUC_RT":"-0.70","TDD_OPNPRC":"71,000","TDD_HGPRC":"71,200","TDD_LWPRC":"70,300","ACC_TRDVOL":"12,345,678","ACC_TRDVAL":"870,000,000,000","MKTCAP":"420,870,169,275,000","LIST_SHRS":"5,969,782,550"},
|
|
|
|
|
+ {"BAS_DD":"20250102","ISU_CD":"000660","ISU_NM":"SK하이닉스","MKT_NM":"KOSPI","SECT_TP_NM":"","TDD_CLSPRC":"210000","CMPPREVDD_PRC":"3000","FLUC_RT":"1.45","TDD_OPNPRC":"208000","TDD_HGPRC":"211500","TDD_LWPRC":"207000","ACC_TRDVOL":"2345678","ACC_TRDVAL":"490000000000","MKTCAP":"-","LIST_SHRS":"-"}
|
|
|
|
|
+ ]}
|
|
|
|
|
+ """;
|
|
|
|
|
+
|
|
|
|
|
+ var rows = KrxStockParser.ParseDailyPrices(json, StockMarket.KOSPI);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.AreEqual(2, rows.Count);
|
|
|
|
|
+
|
|
|
|
|
+ var samsung = rows[0];
|
|
|
|
|
+ Assert.AreEqual("005930", samsung.Code);
|
|
|
|
|
+ Assert.AreEqual(new DateOnly(2025, 1, 2), samsung.TradingDate);
|
|
|
|
|
+ Assert.AreEqual(71000, samsung.Open, "콤마 천단위를 제거하고 시가 파싱");
|
|
|
|
|
+ Assert.AreEqual(71200, samsung.High);
|
|
|
|
|
+ Assert.AreEqual(70300, samsung.Low);
|
|
|
|
|
+ Assert.AreEqual(70500, samsung.Close);
|
|
|
|
|
+ Assert.AreEqual(12345678L, samsung.Volume, "거래량 콤마 제거");
|
|
|
|
|
+ Assert.AreEqual(870000000000L, samsung.TradingValue, "거래대금 콤마 제거");
|
|
|
|
|
+ Assert.AreEqual(-500, samsung.ChangeAmount);
|
|
|
|
|
+ Assert.AreEqual(-0.70m, samsung.ChangeRate, "FLUC_RT 는 소수 % 그대로 (DataGoKr fltRt 와 동일 스케일, Bp 아님)");
|
|
|
|
|
+ Assert.AreEqual(420870169275000L, samsung.MarketCap);
|
|
|
|
|
+ Assert.AreEqual(5969782550L, samsung.ListedShares);
|
|
|
|
|
+
|
|
|
|
|
+ var hynix = rows[1];
|
|
|
|
|
+ Assert.AreEqual("000660", hynix.Code);
|
|
|
|
|
+ Assert.AreEqual(1.45m, hynix.ChangeRate);
|
|
|
|
|
+ Assert.IsNull(hynix.MarketCap, "\"-\" 시가총액은 null");
|
|
|
|
|
+ Assert.IsNull(hynix.ListedShares, "\"-\" 상장주식수는 null");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [TestMethod]
|
|
|
|
|
+ public void ParseDailyPrices_NormalizesPrefixedCode()
|
|
|
|
|
+ {
|
|
|
|
|
+ // 'A' 프리픽스 단축코드는 뒤 6자리로 정규화
|
|
|
|
|
+ const string json = """
|
|
|
|
|
+ {"OutBlock_1":[
|
|
|
|
|
+ {"BAS_DD":"20250102","ISU_CD":"A000660","ISU_NM":"SK하이닉스","MKT_NM":"KOSPI","TDD_CLSPRC":"210000","CMPPREVDD_PRC":"0","FLUC_RT":"0.00","TDD_OPNPRC":"210000","TDD_HGPRC":"210000","TDD_LWPRC":"210000","ACC_TRDVOL":"0","ACC_TRDVAL":"0","MKTCAP":"0","LIST_SHRS":"0"}
|
|
|
|
|
+ ]}
|
|
|
|
|
+ """;
|
|
|
|
|
+
|
|
|
|
|
+ var rows = KrxStockParser.ParseDailyPrices(json, StockMarket.KOSPI);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.AreEqual(1, rows.Count);
|
|
|
|
|
+ Assert.AreEqual("000660", rows[0].Code, "'A' 프리픽스 단축코드는 뒤 6자리로 정규화한다");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [TestMethod]
|
|
|
|
|
+ public void ParseDailyPrices_EmptyOrMissingBlock_ReturnsEmpty()
|
|
|
|
|
+ {
|
|
|
|
|
+ // basDd 미반영 — OutBlock_1 이 빈 배열
|
|
|
|
|
+ Assert.AreEqual(0, KrxStockParser.ParseDailyPrices("""{"OutBlock_1":[]}""", StockMarket.KOSDAQ).Count);
|
|
|
|
|
+
|
|
|
|
|
+ // 인증 실패 등 OutBlock_1 노드 자체가 없는 응답도 안전하게 0건
|
|
|
|
|
+ Assert.AreEqual(0, KrxStockParser.ParseDailyPrices("""{"respMsg":"Unauthorized API Call","respCode":"401"}""", StockMarket.KOSPI).Count);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [TestMethod]
|
|
|
|
|
+ public void ParseMasterInfo_MapsFields_KospiAndKosdaq()
|
|
|
|
|
+ {
|
|
|
|
|
+ // KRX OpenAPI(stk_isu_base_info/ksq_isu_base_info) 표준 응답 샘플 — ISU_SRT_CD=단축코드, ISU_CD=표준코드 ISIN
|
|
|
|
|
+ const string kospiJson = """
|
|
|
|
|
+ {"OutBlock_1":[
|
|
|
|
|
+ {"ISU_CD":"KR7005930003","ISU_SRT_CD":"005930","ISU_NM":"삼성전자보통주","ISU_ABBRV":"삼성전자","ISU_ENG_NM":"SAMSUNG ELECTRONICS CO.,LTD.","LIST_DD":"19750611","MKT_TP_NM":"KOSPI","SECUGRP_NM":"주권","SECT_TP_NM":"","KIND_STKCERT_TP_NM":"보통주","PARVAL":"100","LIST_SHRS":"5969782550"}
|
|
|
|
|
+ ]}
|
|
|
|
|
+ """;
|
|
|
|
|
+
|
|
|
|
|
+ var kospi = KrxStockParser.ParseMasterInfo(kospiJson, StockMarket.KOSPI);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.AreEqual(1, kospi.Count);
|
|
|
|
|
+ var samsung = kospi[0];
|
|
|
|
|
+ Assert.AreEqual("005930", samsung.Code, "ISU_SRT_CD → Code");
|
|
|
|
|
+ Assert.AreEqual("KR7005930003", samsung.Isin, "ISU_CD(표준코드) → ISIN");
|
|
|
|
|
+ Assert.AreEqual("삼성전자", samsung.Name, "ISU_ABBRV(약칭) → Name");
|
|
|
|
|
+ Assert.AreEqual("SAMSUNG ELECTRONICS CO.,LTD.", samsung.EnglishName);
|
|
|
|
|
+ Assert.AreEqual(StockMarket.KOSPI, samsung.Market, "market 은 호출 엔드포인트 인자로 결정");
|
|
|
|
|
+ Assert.IsNull(samsung.SectorName, "빈 SECT_TP_NM 은 null");
|
|
|
|
|
+ Assert.AreEqual(new DateOnly(1975, 6, 11), samsung.ListedDate, "LIST_DD → ListedDate");
|
|
|
|
|
+
|
|
|
|
|
+ // KOSDAQ 은 SECT_TP_NM(소속부) 이 채워져 온다
|
|
|
|
|
+ const string kosdaqJson = """
|
|
|
|
|
+ {"OutBlock_1":[
|
|
|
|
|
+ {"ISU_CD":"KR7058820002","ISU_SRT_CD":"058820","ISU_NM":"CMG제약","ISU_ABBRV":"CMG제약","ISU_ENG_NM":"CMG Pharmaceutical Co., Ltd.","LIST_DD":"20010831","MKT_TP_NM":"KOSDAQ","SECUGRP_NM":"주권","SECT_TP_NM":"우량기업부","KIND_STKCERT_TP_NM":"보통주","PARVAL":"500","LIST_SHRS":"111111111"}
|
|
|
|
|
+ ]}
|
|
|
|
|
+ """;
|
|
|
|
|
+
|
|
|
|
|
+ var kosdaq = KrxStockParser.ParseMasterInfo(kosdaqJson, StockMarket.KOSDAQ);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.AreEqual(1, kosdaq.Count);
|
|
|
|
|
+ Assert.AreEqual(StockMarket.KOSDAQ, kosdaq[0].Market);
|
|
|
|
|
+ Assert.AreEqual("우량기업부", kosdaq[0].SectorName, "KOSDAQ SECT_TP_NM(소속부) → SectorName");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [TestMethod]
|
|
|
|
|
+ public void ParseMasterInfo_InvalidIsin_And_EmptyBlock()
|
|
|
|
|
+ {
|
|
|
|
|
+ // ISU_CD 가 12자리가 아니면 ISIN=null (방어)
|
|
|
|
|
+ const string json = """
|
|
|
|
|
+ {"OutBlock_1":[
|
|
|
|
|
+ {"ISU_CD":"BADISIN","ISU_SRT_CD":"005930","ISU_NM":"삼성전자보통주","ISU_ABBRV":"삼성전자","ISU_ENG_NM":"","LIST_DD":"19750611","MKT_TP_NM":"KOSPI","SECT_TP_NM":"","LIST_SHRS":"1"}
|
|
|
|
|
+ ]}
|
|
|
|
|
+ """;
|
|
|
|
|
+
|
|
|
|
|
+ var rows = KrxStockParser.ParseMasterInfo(json, StockMarket.KOSPI);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.AreEqual(1, rows.Count);
|
|
|
|
|
+ Assert.IsNull(rows[0].Isin, "12자리 아닌 표준코드는 ISIN=null");
|
|
|
|
|
+ Assert.IsNull(rows[0].EnglishName, "빈 ISU_ENG_NM 은 null");
|
|
|
|
|
+
|
|
|
|
|
+ // 빈 스냅샷 / 노드 없음 안전 처리
|
|
|
|
|
+ Assert.AreEqual(0, KrxStockParser.ParseMasterInfo("""{"OutBlock_1":[]}""", StockMarket.KOSPI).Count);
|
|
|
|
|
+ Assert.AreEqual(0, KrxStockParser.ParseMasterInfo("""{"respCode":"401"}""", StockMarket.KOSDAQ).Count);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|