using Infrastructure.StockData; namespace Application.Tests; [TestClass] public sealed class BondIndexParserTests { [TestMethod] public void ParseBondIndexPrices_MapsFields_CommasAndEmptyToNull() { // KRX OpenAPI(idx/bon_dd_trd) 라이브 응답 샘플 — 1행은 전 지표 제공, 2행은 제로/Call재투자·시장가격 미제공("") → null. const string json = """ {"OutBlock_1":[ {"BAS_DD":"20250102","BND_IDX_GRP_NM":"KRX 채권지수","TOT_EARNG_IDX":"200.78","TOT_EARNG_IDX_CMPPREVDD":"1.12","NETPRC_IDX":"103.84","NETPRC_IDX_CMPPREVDD":"0.58","ZERO_REINVST_IDX":"193.89","ZERO_REINVST_IDX_CMPPREVDD":"1.08","CALL_REINVST_IDX":"199.47","CALL_REINVST_IDX_CMPPREVDD":"1.12","MKT_PRC_IDX":"104.55","MKT_PRC_IDX_CMPPREVDD":"0.56","AVG_DURATION":"5.646","AVG_CONVEXITY_PRC":"91.702","BND_IDX_AVG_YD":"2.886"}, {"BAS_DD":"20250102","BND_IDX_GRP_NM":"국고채프라임지수","TOT_EARNG_IDX":"1,199.20","TOT_EARNG_IDX_CMPPREVDD":"0.72","NETPRC_IDX":"109.08","NETPRC_IDX_CMPPREVDD":"0.39","ZERO_REINVST_IDX":"","ZERO_REINVST_IDX_CMPPREVDD":"","CALL_REINVST_IDX":"-","CALL_REINVST_IDX_CMPPREVDD":"-","MKT_PRC_IDX":"","MKT_PRC_IDX_CMPPREVDD":"","AVG_DURATION":"4.105","AVG_CONVEXITY_PRC":"22.500","BND_IDX_AVG_YD":"2.700"} ]} """; var rows = KrxBondIndexParser.ParseBondIndexPrices(json); Assert.AreEqual(2, rows.Count); var krx = rows[0]; Assert.AreEqual("KRX 채권지수", krx.GroupName); Assert.AreEqual(new DateOnly(2025, 1, 2), krx.TradeDate); Assert.AreEqual(200.78m, krx.TotalEarningIndex); Assert.AreEqual(1.12m, krx.TotalEarningChange); Assert.AreEqual(103.84m, krx.NetPriceIndex); Assert.AreEqual(0.58m, krx.NetPriceChange); Assert.AreEqual(193.89m, krx.ZeroReinvestIndex); Assert.AreEqual(1.08m, krx.ZeroReinvestChange); Assert.AreEqual(199.47m, krx.CallReinvestIndex); Assert.AreEqual(1.12m, krx.CallReinvestChange); Assert.AreEqual(104.55m, krx.MarketPriceIndex); Assert.AreEqual(0.56m, krx.MarketPriceChange); Assert.AreEqual(5.646m, krx.AvgDuration); Assert.AreEqual(91.702m, krx.AvgConvexity); Assert.AreEqual(2.886m, krx.AvgYield); var prime = rows[1]; Assert.AreEqual("국고채프라임지수", prime.GroupName); Assert.AreEqual(1199.20m, prime.TotalEarningIndex, "콤마 천단위 제거"); Assert.IsNull(prime.ZeroReinvestIndex, "\"\" 제로재투자지수는 null"); Assert.IsNull(prime.ZeroReinvestChange); Assert.IsNull(prime.CallReinvestIndex, "\"-\" Call재투자지수는 null"); Assert.IsNull(prime.MarketPriceIndex, "\"\" 시장가격지수는 null"); Assert.AreEqual(4.105m, prime.AvgDuration); Assert.AreEqual(2.700m, prime.AvgYield); } [TestMethod] public void ParseBondIndexPrices_EmptyOrMissingBlock_ReturnsEmpty() { var empty = KrxBondIndexParser.ParseBondIndexPrices("""{"OutBlock_1":[]}"""); Assert.AreEqual(0, empty.Count); var missing = KrxBondIndexParser.ParseBondIndexPrices("""{"respMsg":"Unauthorized API Call","respCode":"401"}"""); Assert.AreEqual(0, missing.Count); } [TestMethod] public void ParseBondIndexPrices_SkipsRowsWithoutGroupNameOrDate() { // 그룹명 없거나 날짜 파싱 실패 행은 스킵 const string json = """ {"OutBlock_1":[ {"BAS_DD":"20250102","BND_IDX_GRP_NM":"","TOT_EARNG_IDX":"100"}, {"BAS_DD":"","BND_IDX_GRP_NM":"그룹","TOT_EARNG_IDX":"100"}, {"BAS_DD":"20250102","BND_IDX_GRP_NM":"정상","TOT_EARNG_IDX":"100"} ]} """; var rows = KrxBondIndexParser.ParseBondIndexPrices(json); Assert.AreEqual(1, rows.Count); Assert.AreEqual("정상", rows[0].GroupName); } }