SeibroSupplySyncTests.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. using Application.Helpers;
  2. using Infrastructure.StockData;
  3. using Microsoft.EntityFrameworkCore;
  4. namespace Application.Tests;
  5. /// <summary>
  6. /// SeibroSupplySyncService 의 DB 반영 로직(SeibroSupplyImport) 통합 검증 — HTTP 는 파서 입력을 fixture 로 대체
  7. /// (SeibroDividendSyncTests 방식 미러링). 실제 SQL Server(LocalDB)로 대차 upsert·보호예수/유통변경 window·증감 rolling·비상장 확인.
  8. /// </summary>
  9. [TestClass]
  10. public sealed class SeibroSupplySyncTests
  11. {
  12. [TestMethod]
  13. public async Task UpsertLending_FromSampleXml_StampsIsinAndDate_ThenIdempotent()
  14. {
  15. // 기대 행위: getSlbDealingByIsin(1행) — 응답에 없는 Isin/StdDt 를 요청값으로 스탬핑 upsert (UQ Isin+StdDt). 재실행 무변경.
  16. var rows = SeibroSlbDealingParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getSlbDealingByIsin.xml")));
  17. var isin = $"KR7{Guid.NewGuid().ToString("N")[..9]}";
  18. var stdDt = new DateOnly(2018, 8, 22);
  19. using (var db = TestDb.Create())
  20. {
  21. var (inserted, updated) = await SeibroSupplyImport.UpsertLendingAsync(db, isin, stdDt, rows, CancellationToken.None);
  22. Assert.AreEqual(1, inserted);
  23. Assert.AreEqual(0, updated);
  24. }
  25. using (var db = TestDb.Create())
  26. {
  27. var (inserted, updated) = await SeibroSupplyImport.UpsertLendingAsync(db, isin, stdDt, rows, CancellationToken.None);
  28. Assert.AreEqual(0, inserted, "재실행 idempotent");
  29. Assert.AreEqual(0, updated, "무변경 no-op");
  30. var saved = await db.SecuritiesLending.AsNoTracking().Where(c => c.Isin == isin && c.StdDt == stdDt).SingleAsync();
  31. Assert.AreEqual(21976319L, saved.SlbTrRemQty);
  32. Assert.AreEqual(0.01m, saved.FrinerLendRemRatio);
  33. }
  34. }
  35. [TestMethod]
  36. public async Task ReplaceLockupWindow_FromSampleXml_DeletesThenInserts()
  37. {
  38. // 기대 행위: getSafeDpDutyDepoStatus — 예수일 window(day) delete+insert. 같은 날 재실행은 delete=1,insert=1 로 재구성.
  39. var rows = SeibroLockupParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getSafeDpDutyDepoStatus.xml")));
  40. var day = new DateOnly(2019, 2, 25);
  41. using (var db = TestDb.Create())
  42. {
  43. var (inserted, deleted) = await SeibroSupplyImport.ReplaceLockupWindowAsync(db, day, day, rows, CancellationToken.None);
  44. Assert.AreEqual(1, inserted);
  45. Assert.AreEqual(0, deleted, "첫 창은 삭제할 기존 행 없음");
  46. }
  47. using (var db = TestDb.Create())
  48. {
  49. var (inserted, deleted) = await SeibroSupplyImport.ReplaceLockupWindowAsync(db, day, day, rows, CancellationToken.None);
  50. Assert.AreEqual(1, inserted, "window 재구성 → insert");
  51. Assert.AreEqual(1, deleted, "기존 창 삭제");
  52. var saved = await db.LockupEvent.AsNoTracking().Where(c => c.SafedpDt == day).ToListAsync();
  53. Assert.AreEqual(1, saved.Count, "중복 없이 1행");
  54. Assert.AreEqual("케이비아이동국실업", saved[0].KorSecnNm);
  55. Assert.AreEqual("02", saved[0].DutySafedpRacd);
  56. }
  57. }
  58. [TestMethod]
  59. public async Task UpsertIssuanceChanges_FromSampleXml_KeepsBothSameNtimesRows()
  60. {
  61. // 기대 행위: getStkIncdecDetails(3행) upsert. 동일 발행횟수(75) 2행이 IssuDt/Racd 로 구분되어 둘 다 저장.
  62. var rows = SeibroStkIncdecParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getStkIncdecDetails.xml")));
  63. using (var db = TestDb.Create())
  64. {
  65. var (inserted, _) = await SeibroSupplyImport.UpsertIssuanceChangesAsync(db, rows, CancellationToken.None);
  66. Assert.AreEqual(3, inserted);
  67. }
  68. using (var db = TestDb.Create())
  69. {
  70. var (inserted, updated) = await SeibroSupplyImport.UpsertIssuanceChangesAsync(db, rows, CancellationToken.None);
  71. Assert.AreEqual(0, inserted, "재실행 idempotent");
  72. Assert.AreEqual(0, updated);
  73. var ntimes75 = await db.StockIssuanceChange.AsNoTracking().Where(c => c.Isin == "KR7005930003" && c.SecnIssuNtimes == 75).ToListAsync();
  74. Assert.AreEqual(2, ntimes75.Count, "발행횟수 75 2행 보존 (충돌 없음)");
  75. }
  76. }
  77. [TestMethod]
  78. public async Task ReplaceListingChangeWindow_FromSampleXml_ReconstructsWindow()
  79. {
  80. // 기대 행위: getStkListInfo — 적용일 window(2018-12) delete+insert. 재실행은 창 재구성(insert=deleted).
  81. var rows = SeibroStkListParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getStkListInfo.xml")));
  82. var begin = new DateOnly(2018, 12, 1);
  83. var end = new DateOnly(2018, 12, 31);
  84. int firstInserted;
  85. using (var db = TestDb.Create())
  86. {
  87. var (inserted, deleted) = await SeibroSupplyImport.ReplaceListingChangeWindowAsync(db, begin, end, rows, CancellationToken.None);
  88. Assert.IsTrue(inserted > 0, "12월 유통변경 다수 insert");
  89. Assert.AreEqual(0, deleted);
  90. firstInserted = inserted;
  91. }
  92. using (var db = TestDb.Create())
  93. {
  94. var (inserted, deleted) = await SeibroSupplyImport.ReplaceListingChangeWindowAsync(db, begin, end, rows, CancellationToken.None);
  95. Assert.AreEqual(firstInserted, inserted, "동일 창 재구성");
  96. Assert.AreEqual(firstInserted, deleted, "기존 창 전부 삭제 후 재삽입");
  97. var etf = await db.ListingChangeEvent.AsNoTracking().Where(c => c.Isin == "KR7314700006").SingleAsync();
  98. Assert.AreEqual("110", etf.AfaltCirclForm);
  99. }
  100. }
  101. [TestMethod]
  102. public async Task UpsertUnlisted_FromSampleXml_StampsStdDt()
  103. {
  104. // 기대 행위: getUnlistCirclInfo(1행) — StdDt 요청값 스탬핑 upsert (UQ Isin+StdDt).
  105. var rows = SeibroUnlistCirclParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getUnlistCirclInfo.xml")));
  106. var stdDt = new DateOnly(2018, 8, 7);
  107. using (var db = TestDb.Create())
  108. {
  109. var (inserted, _) = await SeibroSupplyImport.UpsertUnlistedAsync(db, stdDt, rows, CancellationToken.None);
  110. Assert.AreEqual(1, inserted);
  111. }
  112. using (var db = TestDb.Create())
  113. {
  114. var (inserted, updated) = await SeibroSupplyImport.UpsertUnlistedAsync(db, stdDt, rows, CancellationToken.None);
  115. Assert.AreEqual(0, inserted, "재실행 idempotent");
  116. Assert.AreEqual(0, updated);
  117. var saved = await db.UnlistedCirculation.AsNoTracking().Where(c => c.Isin == "KR7064400005" && c.StdDt == stdDt).SingleAsync();
  118. Assert.AreEqual(1400L, saved.AcnttrQty);
  119. Assert.AreEqual("보통주", saved.SecnKacdNm);
  120. }
  121. }
  122. [TestMethod]
  123. public void Quota_StockBudget_StopsAtCap()
  124. {
  125. // 기대 행위: 주식 카테고리 예산 상한 준수 — 상한까지만 소비 성공, 초과분은 false + Remaining 0
  126. var quota = new SeibroQuota(() => new DateOnly(2026, 7, 7));
  127. const int budget = 5;
  128. var consumed = 0;
  129. for (var i = 0; i < 10; i++)
  130. {
  131. if (quota.TryConsume(SeibroCategory.Stock, 1, budget))
  132. {
  133. consumed++;
  134. }
  135. }
  136. Assert.AreEqual(budget, consumed, "예산(5) 만큼만 소비");
  137. Assert.AreEqual(0, quota.Remaining(SeibroCategory.Stock, budget));
  138. }
  139. }