using Domain.Entities.Stocks.ValueObject; using Infrastructure.StockData; namespace Application.Tests; [TestClass] public sealed class IndexPriceParserTests { [TestMethod] public void ParseIndexPrices_MapsFields_CommasAndDash() { // KRX OpenAPI(kospi_dd_trd) 표준 응답 샘플 — 콤마 천단위 + "-"(결측) 혼재, 등락률 %→Bp 변환 검증 const string json = """ {"OutBlock_1":[ {"BAS_DD":"20260703","IDX_CLSS":"KOSPI","IDX_NM":"코스피","CLSPRC_IDX":"3,150.25","CMPPREVDD_IDX":"-22.10","FLUC_RT":"-0.70","OPNPRC_IDX":"3,172.00","HGPRC_IDX":"3,180.55","LWPRC_IDX":"3,145.10","ACC_TRDVOL":"512,345,678","ACC_TRDVAL":"9,870,000,000,000","MKTCAP":"2,100,870,169,275,000"}, {"BAS_DD":"20260703","IDX_CLSS":"KOSPI","IDX_NM":"코스피 200","CLSPRC_IDX":"420.15","CMPPREVDD_IDX":"6.05","FLUC_RT":"1.46","OPNPRC_IDX":"415.00","HGPRC_IDX":"421.00","LWPRC_IDX":"414.50","ACC_TRDVOL":"-","ACC_TRDVAL":"-","MKTCAP":"-"} ]} """; var rows = KrxIndexParser.ParseIndexPrices(json, MarketIndexSeries.KOSPI); Assert.AreEqual(2, rows.Count); var kospi = rows[0]; Assert.AreEqual(MarketIndexSeries.KOSPI, kospi.Series); Assert.AreEqual("코스피", kospi.IndexName); Assert.AreEqual(new DateOnly(2026, 7, 3), kospi.TradeDate); Assert.AreEqual(3150.25m, kospi.Close, "콤마 천단위를 제거하고 소수 종가를 파싱해야 한다"); Assert.AreEqual(-22.10m, kospi.ChangeVal); Assert.AreEqual(-70, kospi.FlucRateBp, "FLUC_RT -0.70% → -70 Bp"); Assert.AreEqual(3172.00m, kospi.Open); Assert.AreEqual(3180.55m, kospi.High); Assert.AreEqual(3145.10m, kospi.Low); Assert.AreEqual(512345678L, kospi.Volume, "거래량 콤마 제거"); Assert.AreEqual(9870000000000L, kospi.Value, "거래대금 콤마 제거"); Assert.AreEqual(2100870169275000L, kospi.MarketCap); var kospi200 = rows[1]; Assert.AreEqual("코스피 200", kospi200.IndexName); Assert.AreEqual(146, kospi200.FlucRateBp, "FLUC_RT 1.46% → 146 Bp"); Assert.AreEqual(0L, kospi200.Volume, "\"-\" 거래량은 0 으로 흡수"); Assert.AreEqual(0L, kospi200.Value, "\"-\" 거래대금은 0 으로 흡수"); Assert.IsNull(kospi200.MarketCap, "\"-\" 시가총액은 null"); } [TestMethod] public void ParseIndexPrices_EmptyOrMissingBlock_ReturnsEmpty() { // 결과 없음 — OutBlock_1 이 빈 배열 var empty = KrxIndexParser.ParseIndexPrices("""{"OutBlock_1":[]}""", MarketIndexSeries.KOSDAQ); Assert.AreEqual(0, empty.Count); // OutBlock_1 노드 자체가 없는 응답도 안전하게 0건 var missing = KrxIndexParser.ParseIndexPrices("""{"respMsg":"Unauthorized API Call","respCode":"401"}""", MarketIndexSeries.KRX); Assert.AreEqual(0, missing.Count); } [TestMethod] public void ParseIndexPrices_Derivative_SubsetShape_VolumeValueCapDefaults() { // 파생상품지수(idx/drvprod_dd_trd) 응답 — OHLC 는 있으나 ACC_TRDVOL/ACC_TRDVAL/MKTCAP 필드 자체가 없다. // KrxIndexParser 는 없는 필드를 0(거래량/거래대금)·null(시총)로 흡수하고 나머지는 IndexDailyPrice 로 매핑해야 한다. const string json = """ {"OutBlock_1":[ {"BAS_DD":"20250102","IDX_CLSS":"선물지수","IDX_NM":"미국달러선물지수","CLSPRC_IDX":"1,599.06","CMPPREVDD_IDX":"-8.83","FLUC_RT":"-0.55","OPNPRC_IDX":"1,606.04","HGPRC_IDX":"1,607.35","LWPRC_IDX":"1,596.77"} ]} """; var rows = KrxIndexParser.ParseIndexPrices(json, MarketIndexSeries.Derivative); Assert.AreEqual(1, rows.Count); var row = rows[0]; Assert.AreEqual(MarketIndexSeries.Derivative, row.Series); Assert.AreEqual("미국달러선물지수", row.IndexName); Assert.AreEqual(new DateOnly(2025, 1, 2), row.TradeDate); Assert.AreEqual(1599.06m, row.Close); Assert.AreEqual(-8.83m, row.ChangeVal); Assert.AreEqual(-55, row.FlucRateBp, "FLUC_RT -0.55% → -55 Bp"); Assert.AreEqual(1606.04m, row.Open); Assert.AreEqual(1607.35m, row.High); Assert.AreEqual(1596.77m, row.Low); Assert.AreEqual(0L, row.Volume, "ACC_TRDVOL 필드 부재 → 0"); Assert.AreEqual(0L, row.Value, "ACC_TRDVAL 필드 부재 → 0"); Assert.IsNull(row.MarketCap, "MKTCAP 필드 부재 → null"); } [TestMethod] public void ParseIndexPrices_RoundsHalfAwayFromZero() { // 등락률 반올림 경계 — 0.005% → 1 Bp (AwayFromZero), -0.005% → -1 Bp const string json = """ {"OutBlock_1":[ {"BAS_DD":"20260703","IDX_NM":"경계+","CLSPRC_IDX":"100","CMPPREVDD_IDX":"0","FLUC_RT":"0.005","OPNPRC_IDX":"100","HGPRC_IDX":"100","LWPRC_IDX":"100","ACC_TRDVOL":"0","ACC_TRDVAL":"0","MKTCAP":"0"}, {"BAS_DD":"20260703","IDX_NM":"경계-","CLSPRC_IDX":"100","CMPPREVDD_IDX":"0","FLUC_RT":"-0.005","OPNPRC_IDX":"100","HGPRC_IDX":"100","LWPRC_IDX":"100","ACC_TRDVOL":"0","ACC_TRDVAL":"0","MKTCAP":"0"} ]} """; var rows = KrxIndexParser.ParseIndexPrices(json, MarketIndexSeries.KRX); Assert.AreEqual(1, rows[0].FlucRateBp); Assert.AreEqual(-1, rows[1].FlucRateBp); } }