using Domain.Entities.Stocks.ValueObject; using Infrastructure.StockData; namespace Application.Tests; [TestClass] public sealed class KrxWarrantParserTests { [TestMethod] public void ParseDaily_SubscriptionWarrant_MapsFields_FromSampleJson() { // KRX OpenAPI(sw_bydd_trd) 실검증 샘플 — ISU_CD 8자리 영숫자, EXER_PRC/EXST_* 행사정보, TARSTK_* 대상주식, FLUC_RT 소수 % const string json = """ {"OutBlock_1":[ {"BAS_DD":"20250102","MKT_NM":"KOSDAQ","ISU_CD":"1099621D","ISU_NM":"AP헬스케어 6WR","TDD_CLSPRC":"99","CMPPREVDD_PRC":"0","FLUC_RT":"0.00","TDD_OPNPRC":"100","TDD_HGPRC":"100","TDD_LWPRC":"98","ACC_TRDVOL":"4,037","ACC_TRDVAL":"397,282","MKTCAP":"1,685,106,027","LIST_SHRS":"17,021,273","EXER_PRC":"809","EXST_STRT_DD":"20230825","EXST_END_DD":"20260625","TARSTK_ISU_SRT_CD":"109960","TARSTK_ISU_NM":"AP헬스케어","TARSTK_ISU_PRSNT_PRC":"510"}, {"BAS_DD":"20250102","MKT_NM":"KOSPI","ISU_CD":"0036221D","ISU_NM":"KG모빌리티 122WR","TDD_CLSPRC":"655","CMPPREVDD_PRC":"18","FLUC_RT":"2.83","TDD_OPNPRC":"728","TDD_HGPRC":"728","TDD_LWPRC":"650","ACC_TRDVOL":"15,563","ACC_TRDVAL":"10,411,492","MKTCAP":"11,719,775,485","LIST_SHRS":"17,892,787","EXER_PRC":"6,729","EXST_STRT_DD":"20240105","EXST_END_DD":"20281105","TARSTK_ISU_SRT_CD":"003620","TARSTK_ISU_NM":"KG모빌리티","TARSTK_ISU_PRSNT_PRC":"3,710"} ]} """; var rows = KrxWarrantParser.ParseDaily(json, WarrantType.SubscriptionWarrant); Assert.AreEqual(2, rows.Count); var w = rows[0]; Assert.AreEqual(WarrantType.SubscriptionWarrant, w.WarrantType, "유형은 호출 인자로 결정"); Assert.AreEqual("1099621D", w.Code, "8자리 영숫자 코드는 그대로 유지"); Assert.AreEqual("AP헬스케어 6WR", w.Name); Assert.AreEqual(StockMarket.KOSDAQ, w.Market, "MKT_NM → Market"); Assert.AreEqual(new DateOnly(2025, 1, 2), w.TradeDate); Assert.AreEqual(99m, w.Close); Assert.AreEqual(100m, w.Open); Assert.AreEqual(98m, w.Low); Assert.AreEqual(0m, w.ChangeAmount); Assert.AreEqual(0.00m, w.ChangeRate, "FLUC_RT 소수 % 그대로"); Assert.AreEqual(4037L, w.Volume, "거래량 콤마 제거"); Assert.AreEqual(397282L, w.TradeValue, "거래대금 콤마 제거"); Assert.AreEqual(1685106027L, w.MarketCap); Assert.AreEqual(17021273L, w.ListedShares); Assert.AreEqual("109960", w.TargetStockCode, "TARSTK_ISU_SRT_CD → 대상주식 코드"); Assert.AreEqual("AP헬스케어", w.TargetStockName); Assert.AreEqual(510m, w.TargetStockPrice, "TARSTK_ISU_PRSNT_PRC → 대상주식 현재가"); Assert.AreEqual(809m, w.ExercisePrice, "EXER_PRC → 행사가"); Assert.AreEqual(new DateOnly(2023, 8, 25), w.ExerciseStartDate, "EXST_STRT_DD → 행사 시작일"); Assert.AreEqual(new DateOnly(2026, 6, 25), w.ExerciseEndDate, "EXST_END_DD → 행사 종료일"); Assert.IsNull(w.IssuePrice, "증권은 발행가 없음"); Assert.IsNull(w.DelistDate, "증권은 상장폐지일 없음"); Assert.AreEqual(StockMarket.KOSPI, rows[1].Market, "두 번째 행 MKT_NM=KOSPI"); Assert.AreEqual(6729m, rows[1].ExercisePrice, "행사가 콤마 제거"); } [TestMethod] public void ParseDaily_SubscriptionRight_MapsIssuePriceAndDelistDate() { // KRX OpenAPI(sr_bydd_trd) 실검증 샘플 — ISU_PRC(발행가)/DELIST_DD(상장폐지일), 행사정보 없음. FLUC_RT 음수. const string json = """ {"OutBlock_1":[ {"BAS_DD":"20240628","MKT_NM":"KOSDAQ","ISU_CD":"1013601E","ISU_NM":"에코앤드림 14R","TDD_CLSPRC":"16,450","CMPPREVDD_PRC":"2","FLUC_RT":"-2.26","TDD_OPNPRC":"16,820","TDD_HGPRC":"16,920","TDD_LWPRC":"16,290","ACC_TRDVOL":"148,924","ACC_TRDVAL":"2,466,653,620","MKTCAP":"55,771,981,300","LIST_SHRS":"3,390,394","ISU_PRC":"30,150","DELIST_DD":"20240702","TARSTK_ISU_SRT_CD":"101360","TARSTK_ISU_NM":"에코앤드림","TARSTK_ISU_PRSNT_PRC":"52,900"} ]} """; var rows = KrxWarrantParser.ParseDaily(json, WarrantType.SubscriptionRight); Assert.AreEqual(1, rows.Count); var r = rows[0]; Assert.AreEqual(WarrantType.SubscriptionRight, r.WarrantType); Assert.AreEqual("1013601E", r.Code); Assert.AreEqual("에코앤드림 14R", r.Name); Assert.AreEqual(StockMarket.KOSDAQ, r.Market); Assert.AreEqual(16450m, r.Close, "종가 콤마 제거"); Assert.AreEqual(-2.26m, r.ChangeRate, "FLUC_RT 음수 소수 %"); Assert.AreEqual(148924L, r.Volume); Assert.AreEqual(2466653620L, r.TradeValue); Assert.AreEqual(30150m, r.IssuePrice, "ISU_PRC → 발행가"); Assert.AreEqual(new DateOnly(2024, 7, 2), r.DelistDate, "DELIST_DD → 상장폐지일"); Assert.AreEqual("101360", r.TargetStockCode); Assert.AreEqual(52900m, r.TargetStockPrice, "대상주식 현재가 콤마 제거"); Assert.IsNull(r.ExercisePrice, "증서는 행사가 없음"); Assert.IsNull(r.ExerciseStartDate, "증서는 행사기간 없음"); Assert.IsNull(r.ExerciseEndDate); } [TestMethod] public void ParseDaily_MissingOptionalValues_AreNull() { // 대상주식/행사가 결측치("-" 또는 "") → null, 코드 "-" 은 행 skip const string json = """ {"OutBlock_1":[ {"BAS_DD":"20250102","MKT_NM":"KOSPI","ISU_CD":"9999991D","ISU_NM":"테스트 1WR","TDD_CLSPRC":"100","CMPPREVDD_PRC":"0","FLUC_RT":"0.00","TDD_OPNPRC":"100","TDD_HGPRC":"100","TDD_LWPRC":"100","ACC_TRDVOL":"0","ACC_TRDVAL":"0","MKTCAP":"-","LIST_SHRS":"-","EXER_PRC":"-","EXST_STRT_DD":"","EXST_END_DD":"","TARSTK_ISU_SRT_CD":"-","TARSTK_ISU_NM":"-","TARSTK_ISU_PRSNT_PRC":"-"} ]} """; var rows = KrxWarrantParser.ParseDaily(json, WarrantType.SubscriptionWarrant); Assert.AreEqual(1, rows.Count); var w = rows[0]; Assert.IsNull(w.MarketCap, "\"-\" 시가총액은 null"); Assert.IsNull(w.ListedShares, "\"-\" 상장증권수는 null"); Assert.IsNull(w.ExercisePrice, "\"-\" 행사가는 null"); Assert.IsNull(w.ExerciseStartDate, "빈 행사 시작일은 null"); Assert.IsNull(w.ExerciseEndDate); Assert.IsNull(w.TargetStockCode, "\"-\" 대상주식 코드는 null"); Assert.IsNull(w.TargetStockName); Assert.IsNull(w.TargetStockPrice); } [TestMethod] public void ParseDaily_EmptyOrMissingBlock_ReturnsEmpty() { // 증서(sr)는 대부분 영업일에 0건이 정상 — 빈 배열 안전 처리 Assert.AreEqual(0, KrxWarrantParser.ParseDaily("""{"OutBlock_1":[]}""", WarrantType.SubscriptionRight).Count); // 인증 실패 등 OutBlock_1 노드 자체가 없는 응답도 안전하게 0건 Assert.AreEqual(0, KrxWarrantParser.ParseDaily("""{"respMsg":"Unauthorized API Call","respCode":"401"}""", WarrantType.SubscriptionWarrant).Count); // 존재하지 않는 경로의 404 바디도 0건 Assert.AreEqual(0, KrxWarrantParser.ParseDaily("""{"respMsg":"API referenced by the path does not exist.","respCode":"404"}""", WarrantType.SubscriptionWarrant).Count); } }