SeibroIssuerSyncTests.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. using Domain.Entities.Stocks;
  2. using Domain.Entities.Stocks.ValueObject;
  3. using Infrastructure.StockData;
  4. using Microsoft.EntityFrameworkCore;
  5. namespace Application.Tests;
  6. /// <summary>
  7. /// SeibroIssuerSyncService 의 DB 반영 로직(SeibroIssuerImport) 통합 검증 — HTTP 는 파서 입력을 fixture 로 대체
  8. /// (KRX 테스트들이 파서/로직을 분리 검증하는 방식 미러링). 실제 SQL Server(LocalDB)로 UQ(Market, ShortCode)·스탬핑 동작 확인.
  9. /// </summary>
  10. [TestClass]
  11. public sealed class SeibroIssuerSyncTests
  12. {
  13. [TestMethod]
  14. public async Task UpsertIssuerCodes_FromSampleXml_InsertsAll_ThenIdempotent()
  15. {
  16. // 기대 행위: fixture 전 행 insert → 동일 스냅샷 재실행은 무변경 (UQ Market+ShortCode upsert, resumable)
  17. var rows = SeibroShotnByMartParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getShotnByMart.xml")));
  18. using (var db = TestDb.Create())
  19. {
  20. var (inserted, updated) = await SeibroIssuerImport.UpsertIssuerCodesAsync(db, "13", rows, CancellationToken.None);
  21. Assert.AreEqual(rows.Count, inserted, "첫 실행은 전 행 insert");
  22. Assert.AreEqual(0, updated);
  23. }
  24. using (var db = TestDb.Create())
  25. {
  26. var (inserted, updated) = await SeibroIssuerImport.UpsertIssuerCodesAsync(db, "13", rows, CancellationToken.None);
  27. Assert.AreEqual(0, inserted, "동일 스냅샷 재실행은 insert 없음");
  28. Assert.AreEqual(0, updated, "무변경 행은 update 로 세지 않는다 (UpdatedAt 미갱신)");
  29. var saved = await db.SeibroIssuerCode.AsNoTracking().Where(c => c.Market == "13" && c.ShortCode == "000800").SingleAsync();
  30. Assert.AreEqual("경남기업", saved.Name);
  31. Assert.AreEqual(80, saved.IssucoCustno);
  32. }
  33. }
  34. [TestMethod]
  35. public async Task UpsertIssuerCodes_ChangedNameOrCustno_UpdatesRow()
  36. {
  37. var original = new List<SeibroShotnByMartParser.Row> { new("777001", "변경전상호", 91001) };
  38. var renamed = new List<SeibroShotnByMartParser.Row> { new("777001", "변경후상호", 91002) };
  39. using (var db = TestDb.Create())
  40. {
  41. await SeibroIssuerImport.UpsertIssuerCodesAsync(db, "11", original, CancellationToken.None);
  42. }
  43. using (var db = TestDb.Create())
  44. {
  45. var (inserted, updated) = await SeibroIssuerImport.UpsertIssuerCodesAsync(db, "11", renamed, CancellationToken.None);
  46. Assert.AreEqual(0, inserted);
  47. Assert.AreEqual(1, updated, "이름/회사번호 변경 행은 update");
  48. var saved = await db.SeibroIssuerCode.AsNoTracking().Where(c => c.Market == "11" && c.ShortCode == "777001").SingleAsync();
  49. Assert.AreEqual("변경후상호", saved.Name);
  50. Assert.AreEqual(91002, saved.IssucoCustno);
  51. }
  52. }
  53. [TestMethod]
  54. public async Task UpsertIssuerCodes_SameShortCode_DifferentMarket_BothPersisted()
  55. {
  56. // 기대 행위: UQ 는 (Market, ShortCode) — 같은 단축코드라도 시장이 다르면 별도 행 (K-OTC 13 vs 코넥스 14)
  57. var rows = new List<SeibroShotnByMartParser.Row> { new("777002", "양시장종목", 91003) };
  58. using var db = TestDb.Create();
  59. await SeibroIssuerImport.UpsertIssuerCodesAsync(db, "13", rows, CancellationToken.None);
  60. await SeibroIssuerImport.UpsertIssuerCodesAsync(db, "14", rows, CancellationToken.None);
  61. var saved = await db.SeibroIssuerCode.AsNoTracking().Where(c => c.ShortCode == "777002").ToListAsync();
  62. Assert.AreEqual(2, saved.Count);
  63. }
  64. [TestMethod]
  65. public async Task StampStockIssucoCustno_MatchesByCode_SkipsUnchangedAndUnmapped()
  66. {
  67. // 기대 행위: Stock.Code = 단축코드 조인으로 스탬핑, 이미 같은 값이면 무변경, 매핑 없는 종목은 null 유지
  68. using (var db = TestDb.Create())
  69. {
  70. await db.Stock.AddAsync(Stock.Create("890001", "세이브로스탬핑1", StockMarket.KOSPI, new DateOnly(2020, 1, 2)));
  71. await db.Stock.AddAsync(Stock.Create("890002", "세이브로스탬핑2", StockMarket.KOSDAQ, new DateOnly(2020, 1, 2)));
  72. await db.SaveChangesAsync(default);
  73. }
  74. var custnoByShortCode = new Dictionary<string, int> { ["890001"] = 77001 };
  75. using (var db = TestDb.Create())
  76. {
  77. var stamped = await SeibroIssuerImport.StampStockIssucoCustnoAsync(db, custnoByShortCode, CancellationToken.None);
  78. Assert.AreEqual(1, stamped, "매핑된 종목만 스탬핑");
  79. }
  80. using (var db = TestDb.Create())
  81. {
  82. var stampedAgain = await SeibroIssuerImport.StampStockIssucoCustnoAsync(db, custnoByShortCode, CancellationToken.None);
  83. Assert.AreEqual(0, stampedAgain, "이미 같은 값이면 무변경 (no-op 재실행)");
  84. var s1 = await db.Stock.AsNoTracking().Where(c => c.Code == "890001").SingleAsync();
  85. var s2 = await db.Stock.AsNoTracking().Where(c => c.Code == "890002").SingleAsync();
  86. Assert.AreEqual(77001, s1.IssucoCustno);
  87. Assert.IsNull(s2.IssucoCustno, "스냅샷에 없는 종목은 null 유지");
  88. }
  89. }
  90. [TestMethod]
  91. public async Task EnrichFromSeibro_StkStatFixture_PersistsExtensionColumns()
  92. {
  93. // 기대 행위: getStkStatInfo fixture 행으로 Stock 확장 5컬럼이 채워지고 DB 에 보존된다 (rolling 보강 경로)
  94. var row = SeibroStkStatParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getStkStatInfo.xml")))[0];
  95. using (var db = TestDb.Create())
  96. {
  97. var stock = Stock.Create("890003", "세이브로보강", StockMarket.KOSPI, new DateOnly(2020, 1, 2));
  98. stock.EnrichFromSeibro(row.IssucoCustno, row.FaceValue, row.VotingRights, row.TotalIssuedShares, row.IsElectronicSecurity);
  99. await db.Stock.AddAsync(stock);
  100. await db.SaveChangesAsync(default);
  101. }
  102. using (var db = TestDb.Create())
  103. {
  104. var saved = await db.Stock.AsNoTracking().Where(c => c.Code == "890003").SingleAsync();
  105. Assert.AreEqual(593, saved.IssucoCustno);
  106. Assert.AreEqual(100m, saved.FaceValue);
  107. Assert.AreEqual(1m, saved.VotingRights);
  108. Assert.AreEqual(6419324700L, saved.TotalIssuedShares);
  109. Assert.IsFalse(saved.IsElectronicSecurity!.Value);
  110. }
  111. }
  112. }