KrxWarrantParserTests.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. using Domain.Entities.Stocks.ValueObject;
  2. using Infrastructure.StockData;
  3. namespace Application.Tests;
  4. [TestClass]
  5. public sealed class KrxWarrantParserTests
  6. {
  7. [TestMethod]
  8. public void ParseDaily_SubscriptionWarrant_MapsFields_FromSampleJson()
  9. {
  10. // KRX OpenAPI(sw_bydd_trd) 실검증 샘플 — ISU_CD 8자리 영숫자, EXER_PRC/EXST_* 행사정보, TARSTK_* 대상주식, FLUC_RT 소수 %
  11. const string json = """
  12. {"OutBlock_1":[
  13. {"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"},
  14. {"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"}
  15. ]}
  16. """;
  17. var rows = KrxWarrantParser.ParseDaily(json, WarrantType.SubscriptionWarrant);
  18. Assert.AreEqual(2, rows.Count);
  19. var w = rows[0];
  20. Assert.AreEqual(WarrantType.SubscriptionWarrant, w.WarrantType, "유형은 호출 인자로 결정");
  21. Assert.AreEqual("1099621D", w.Code, "8자리 영숫자 코드는 그대로 유지");
  22. Assert.AreEqual("AP헬스케어 6WR", w.Name);
  23. Assert.AreEqual(StockMarket.KOSDAQ, w.Market, "MKT_NM → Market");
  24. Assert.AreEqual(new DateOnly(2025, 1, 2), w.TradeDate);
  25. Assert.AreEqual(99m, w.Close);
  26. Assert.AreEqual(100m, w.Open);
  27. Assert.AreEqual(98m, w.Low);
  28. Assert.AreEqual(0m, w.ChangeAmount);
  29. Assert.AreEqual(0.00m, w.ChangeRate, "FLUC_RT 소수 % 그대로");
  30. Assert.AreEqual(4037L, w.Volume, "거래량 콤마 제거");
  31. Assert.AreEqual(397282L, w.TradeValue, "거래대금 콤마 제거");
  32. Assert.AreEqual(1685106027L, w.MarketCap);
  33. Assert.AreEqual(17021273L, w.ListedShares);
  34. Assert.AreEqual("109960", w.TargetStockCode, "TARSTK_ISU_SRT_CD → 대상주식 코드");
  35. Assert.AreEqual("AP헬스케어", w.TargetStockName);
  36. Assert.AreEqual(510m, w.TargetStockPrice, "TARSTK_ISU_PRSNT_PRC → 대상주식 현재가");
  37. Assert.AreEqual(809m, w.ExercisePrice, "EXER_PRC → 행사가");
  38. Assert.AreEqual(new DateOnly(2023, 8, 25), w.ExerciseStartDate, "EXST_STRT_DD → 행사 시작일");
  39. Assert.AreEqual(new DateOnly(2026, 6, 25), w.ExerciseEndDate, "EXST_END_DD → 행사 종료일");
  40. Assert.IsNull(w.IssuePrice, "증권은 발행가 없음");
  41. Assert.IsNull(w.DelistDate, "증권은 상장폐지일 없음");
  42. Assert.AreEqual(StockMarket.KOSPI, rows[1].Market, "두 번째 행 MKT_NM=KOSPI");
  43. Assert.AreEqual(6729m, rows[1].ExercisePrice, "행사가 콤마 제거");
  44. }
  45. [TestMethod]
  46. public void ParseDaily_SubscriptionRight_MapsIssuePriceAndDelistDate()
  47. {
  48. // KRX OpenAPI(sr_bydd_trd) 실검증 샘플 — ISU_PRC(발행가)/DELIST_DD(상장폐지일), 행사정보 없음. FLUC_RT 음수.
  49. const string json = """
  50. {"OutBlock_1":[
  51. {"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"}
  52. ]}
  53. """;
  54. var rows = KrxWarrantParser.ParseDaily(json, WarrantType.SubscriptionRight);
  55. Assert.AreEqual(1, rows.Count);
  56. var r = rows[0];
  57. Assert.AreEqual(WarrantType.SubscriptionRight, r.WarrantType);
  58. Assert.AreEqual("1013601E", r.Code);
  59. Assert.AreEqual("에코앤드림 14R", r.Name);
  60. Assert.AreEqual(StockMarket.KOSDAQ, r.Market);
  61. Assert.AreEqual(16450m, r.Close, "종가 콤마 제거");
  62. Assert.AreEqual(-2.26m, r.ChangeRate, "FLUC_RT 음수 소수 %");
  63. Assert.AreEqual(148924L, r.Volume);
  64. Assert.AreEqual(2466653620L, r.TradeValue);
  65. Assert.AreEqual(30150m, r.IssuePrice, "ISU_PRC → 발행가");
  66. Assert.AreEqual(new DateOnly(2024, 7, 2), r.DelistDate, "DELIST_DD → 상장폐지일");
  67. Assert.AreEqual("101360", r.TargetStockCode);
  68. Assert.AreEqual(52900m, r.TargetStockPrice, "대상주식 현재가 콤마 제거");
  69. Assert.IsNull(r.ExercisePrice, "증서는 행사가 없음");
  70. Assert.IsNull(r.ExerciseStartDate, "증서는 행사기간 없음");
  71. Assert.IsNull(r.ExerciseEndDate);
  72. }
  73. [TestMethod]
  74. public void ParseDaily_MissingOptionalValues_AreNull()
  75. {
  76. // 대상주식/행사가 결측치("-" 또는 "") → null, 코드 "-" 은 행 skip
  77. const string json = """
  78. {"OutBlock_1":[
  79. {"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":"-"}
  80. ]}
  81. """;
  82. var rows = KrxWarrantParser.ParseDaily(json, WarrantType.SubscriptionWarrant);
  83. Assert.AreEqual(1, rows.Count);
  84. var w = rows[0];
  85. Assert.IsNull(w.MarketCap, "\"-\" 시가총액은 null");
  86. Assert.IsNull(w.ListedShares, "\"-\" 상장증권수는 null");
  87. Assert.IsNull(w.ExercisePrice, "\"-\" 행사가는 null");
  88. Assert.IsNull(w.ExerciseStartDate, "빈 행사 시작일은 null");
  89. Assert.IsNull(w.ExerciseEndDate);
  90. Assert.IsNull(w.TargetStockCode, "\"-\" 대상주식 코드는 null");
  91. Assert.IsNull(w.TargetStockName);
  92. Assert.IsNull(w.TargetStockPrice);
  93. }
  94. [TestMethod]
  95. public void ParseDaily_EmptyOrMissingBlock_ReturnsEmpty()
  96. {
  97. // 증서(sr)는 대부분 영업일에 0건이 정상 — 빈 배열 안전 처리
  98. Assert.AreEqual(0, KrxWarrantParser.ParseDaily("""{"OutBlock_1":[]}""", WarrantType.SubscriptionRight).Count);
  99. // 인증 실패 등 OutBlock_1 노드 자체가 없는 응답도 안전하게 0건
  100. Assert.AreEqual(0, KrxWarrantParser.ParseDaily("""{"respMsg":"Unauthorized API Call","respCode":"401"}""", WarrantType.SubscriptionWarrant).Count);
  101. // 존재하지 않는 경로의 404 바디도 0건
  102. Assert.AreEqual(0, KrxWarrantParser.ParseDaily("""{"respMsg":"API referenced by the path does not exist.","respCode":"404"}""", WarrantType.SubscriptionWarrant).Count);
  103. }
  104. }