using Infrastructure.StockData; namespace Application.Tests; [TestClass] public sealed class KrxEsgSecurityParserTests { [TestMethod] public void ParseDaily_MapsFields_FromSampleJson() { // KRX OpenAPI(esg/esg_etp_info) 실검증 샘플 — ISU_CD 없음(종목명만), FLUC_RT 는 소수 % 그대로(".06"/"-.43" 형태 포함), 천단위 콤마 방어 const string json = """ {"OutBlock_1":[ {"BAS_DD":"20250102","ISU_ABBRV":"HANARO 탄소효율그린뉴딜","TDD_CLSPRC":"7,880","CMPPREVDD_PRC":"5","FLUC_RT":".06","LIST_SHRS":"900,000","ACC_TRDVOL":"2,723","ACC_TRDVAL":"21,348,240"}, {"BAS_DD":"20250102","ISU_ABBRV":"KODEX 200ESG","TDD_CLSPRC":"12860","CMPPREVDD_PRC":"-55","FLUC_RT":"-.43","LIST_SHRS":"2700000","ACC_TRDVOL":"5276","ACC_TRDVAL":"67842290"} ]} """; var rows = KrxEsgSecurityParser.ParseDaily(json); Assert.AreEqual(2, rows.Count); var a = rows[0]; Assert.AreEqual("HANARO 탄소효율그린뉴딜", a.Name); Assert.AreEqual(new DateOnly(2025, 1, 2), a.TradeDate); Assert.AreEqual(7880m, a.Close, "종가 콤마 제거"); Assert.AreEqual(5m, a.ChangeAmount); Assert.AreEqual(0.06m, a.ChangeRate, "FLUC_RT '.06' → 0.06 소수 % 그대로 (Bp 아님)"); Assert.AreEqual(900000L, a.ListedShares, "상장좌수 콤마 제거"); Assert.AreEqual(2723L, a.Volume); Assert.AreEqual(21348240L, a.TradeValue); var b = rows[1]; Assert.AreEqual("KODEX 200ESG", b.Name); Assert.AreEqual(12860m, b.Close); Assert.AreEqual(-55m, b.ChangeAmount); Assert.AreEqual(-0.43m, b.ChangeRate, "FLUC_RT '-.43' → -0.43"); } [TestMethod] public void ParseDaily_ZeroChangeAndVolume_AreZero() { // 무거래일 종목 — CMPPREVDD_PRC/FLUC_RT/ACC_TRDVOL/ACC_TRDVAL 모두 "0" const string json = """ {"OutBlock_1":[ {"BAS_DD":"20250102","ISU_ABBRV":"FOCUS ESG리더스","TDD_CLSPRC":"10385","CMPPREVDD_PRC":"0","FLUC_RT":"0","LIST_SHRS":"450000","ACC_TRDVOL":"0","ACC_TRDVAL":"0"} ]} """; var rows = KrxEsgSecurityParser.ParseDaily(json); Assert.AreEqual(1, rows.Count); var r = rows[0]; Assert.AreEqual(10385m, r.Close); Assert.AreEqual(0m, r.ChangeAmount); Assert.AreEqual(0m, r.ChangeRate); Assert.AreEqual(0L, r.Volume); Assert.AreEqual(0L, r.TradeValue); } [TestMethod] public void ParseDaily_EmptyOrMissingBlock_ReturnsEmpty() { // basDd 미반영 — OutBlock_1 빈 배열 Assert.AreEqual(0, KrxEsgSecurityParser.ParseDaily("""{"OutBlock_1":[]}""").Count); // 인증 실패 등 OutBlock_1 노드 자체가 없는 응답도 안전하게 0건 Assert.AreEqual(0, KrxEsgSecurityParser.ParseDaily("""{"respMsg":"Unauthorized API Call","respCode":"401"}""").Count); } }