using Domain.Entities.Stocks.ValueObject; using Infrastructure.StockData; namespace Application.Tests; [TestClass] public sealed class KrxCommodityParserTests { [TestMethod] public void ParseDaily_Gold_MapsIsuCodeOhlcChangeAndRate() { // KRX OpenAPI(gold_bydd_trd) 실검증 샘플 — ISU_CD/ISU_NM + 주식형 OHLC(TDD_*) + 전일대비(CMPPREVDD_PRC)·등락률(FLUC_RT). 협의가격은 금 미제공 → null const string json = """ {"OutBlock_1":[ {"BAS_DD":"20250102","ISU_CD":"04020000","ISU_NM":"금 99.99_1Kg","TDD_CLSPRC":"128790","CMPPREVDD_PRC":"940","FLUC_RT":"0.74","TDD_OPNPRC":"128600","TDD_HGPRC":"129060","TDD_LWPRC":"128000","ACC_TRDVOL":"219723","ACC_TRDVAL":"28313945030"} ]} """; var rows = KrxCommodityParser.ParseDaily(json, CommodityMarket.Gold); Assert.AreEqual(1, rows.Count); var c = rows[0]; Assert.AreEqual(CommodityMarket.Gold, c.CommodityMarket, "시장은 호출 인자로 결정"); Assert.AreEqual("04020000", c.Code, "ISU_CD"); Assert.AreEqual("금 99.99_1Kg", c.Name); Assert.AreEqual(new DateOnly(2025, 1, 2), c.TradeDate); Assert.AreEqual(128790m, c.Close, "TDD_CLSPRC"); Assert.AreEqual(128600m, c.Open, "TDD_OPNPRC"); Assert.AreEqual(129060m, c.High, "TDD_HGPRC"); Assert.AreEqual(128000m, c.Low, "TDD_LWPRC"); Assert.AreEqual(940m, c.ChangeAmount, "CMPPREVDD_PRC"); Assert.AreEqual(0.74m, c.ChangeRate, "FLUC_RT"); Assert.IsNull(c.WeightedDiscussionPrice, "협의가격은 금 미제공 → null"); Assert.AreEqual(219723L, c.Volume); Assert.AreEqual(28313945030L, c.TradeValue); } [TestMethod] public void ParseDaily_Emission_MapsFields_WithNegativeChangeAndThousandsCommas() { // KRX OpenAPI(ets_bydd_trd) 실검증 샘플 — 배출권(KAU/KCU/KOC). 음수 전일대비/등락률 + 천단위 콤마 방어 const string json = """ {"OutBlock_1":[ {"BAS_DD":"20250102","ISU_CD":"05003240","ISU_NM":"KAU24","TDD_CLSPRC":"9,280","CMPPREVDD_PRC":"-210","FLUC_RT":"-2.21","TDD_OPNPRC":"9,300","TDD_HGPRC":"9,500","TDD_LWPRC":"9,280","ACC_TRDVOL":"859,512","ACC_TRDVAL":"8,011,657,000"} ]} """; var rows = KrxCommodityParser.ParseDaily(json, CommodityMarket.Emission); Assert.AreEqual(1, rows.Count); var c = rows[0]; Assert.AreEqual(CommodityMarket.Emission, c.CommodityMarket); Assert.AreEqual("05003240", c.Code); Assert.AreEqual("KAU24", c.Name); Assert.AreEqual(9280m, c.Close, "종가 콤마 제거"); Assert.AreEqual(9300m, c.Open); Assert.AreEqual(9500m, c.High); Assert.AreEqual(9280m, c.Low); Assert.AreEqual(-210m, c.ChangeAmount, "음수 전일대비"); Assert.AreEqual(-2.21m, c.ChangeRate, "음수 등락률"); Assert.IsNull(c.WeightedDiscussionPrice); Assert.AreEqual(859512L, c.Volume, "거래량 콤마 제거"); Assert.AreEqual(8011657000L, c.TradeValue, "거래대금 콤마 제거"); } [TestMethod] public void ParseDaily_Oil_UsesOilNameAsCode_WeightedPrices_NoOhlcNoChange() { // KRX OpenAPI(oil_bydd_trd) 실검증 샘플 — ISU_CD 없음, OIL_NM(유종구분)을 Code·Name 으로. 가중평균가격 경쟁→Close / 협의→WeightedDiscussionPrice. OHLC 0, 등락 null const string json = """ {"OutBlock_1":[ {"BAS_DD":"20250102","OIL_NM":"휘발유","WT_AVG_PRC":"1595.88","WT_DIS_AVG_PRC":"1549.24","ACC_TRDVOL":"3365180","ACC_TRDVAL":"5292762275"} ]} """; var rows = KrxCommodityParser.ParseDaily(json, CommodityMarket.Oil); Assert.AreEqual(1, rows.Count); var c = rows[0]; Assert.AreEqual(CommodityMarket.Oil, c.CommodityMarket); Assert.AreEqual("휘발유", c.Code, "석유는 OIL_NM 을 코드로 사용"); Assert.AreEqual("휘발유", c.Name); Assert.AreEqual(new DateOnly(2025, 1, 2), c.TradeDate); Assert.AreEqual(1595.88m, c.Close, "가중평균가격_경쟁(WT_AVG_PRC) → Close"); Assert.AreEqual(1549.24m, c.WeightedDiscussionPrice, "가중평균가격_협의(WT_DIS_AVG_PRC)"); Assert.AreEqual(0m, c.Open, "석유 OHLC 미제공 → 0"); Assert.AreEqual(0m, c.High); Assert.AreEqual(0m, c.Low); Assert.IsNull(c.ChangeAmount, "석유 전일대비 미제공 → null"); Assert.IsNull(c.ChangeRate, "석유 등락률 미제공 → null"); Assert.AreEqual(3365180L, c.Volume); Assert.AreEqual(5292762275L, c.TradeValue); } [TestMethod] public void ParseDaily_NullOrDashValues_AreHandled() { // 미반영 행 — 금/배출권의 전일대비/등락률이 ""/"-" → null (0 과 구분), 가격은 "-" → 0. const string goldJson = """ {"OutBlock_1":[ {"BAS_DD":"20250102","ISU_CD":"04020100","ISU_NM":"미니금 99.99_100g","TDD_CLSPRC":"-","CMPPREVDD_PRC":"","FLUC_RT":"-","TDD_OPNPRC":"-","TDD_HGPRC":"-","TDD_LWPRC":"-","ACC_TRDVOL":"0","ACC_TRDVAL":"0"} ]} """; var goldRows = KrxCommodityParser.ParseDaily(goldJson, CommodityMarket.Gold); Assert.AreEqual(1, goldRows.Count); Assert.AreEqual(0m, goldRows[0].Close, "\"-\" 가격은 0"); Assert.IsNull(goldRows[0].ChangeAmount, "\"\" 전일대비는 null"); Assert.IsNull(goldRows[0].ChangeRate, "\"-\" 등락률은 null"); // 석유 — 협의가격 미체결("") → null const string oilJson = """ {"OutBlock_1":[ {"BAS_DD":"20250102","OIL_NM":"등유","WT_AVG_PRC":"1000.00","WT_DIS_AVG_PRC":"","ACC_TRDVOL":"3963954","ACC_TRDVAL":"3973954000"} ]} """; var oilRows = KrxCommodityParser.ParseDaily(oilJson, CommodityMarket.Oil); Assert.AreEqual(1, oilRows.Count); Assert.AreEqual(1000.00m, oilRows[0].Close); Assert.IsNull(oilRows[0].WeightedDiscussionPrice, "\"\" 협의가격은 null"); } [TestMethod] public void ParseDaily_EmptyOrMissingBlock_ReturnsEmpty() { // basDd 미반영 — OutBlock_1 이 빈 배열 Assert.AreEqual(0, KrxCommodityParser.ParseDaily("""{"OutBlock_1":[]}""", CommodityMarket.Gold).Count); // 인증 실패 등 OutBlock_1 노드 자체가 없는 응답도 안전하게 0건 Assert.AreEqual(0, KrxCommodityParser.ParseDaily("""{"respMsg":"Unauthorized API Call","respCode":"401"}""", CommodityMarket.Oil).Count); } }