| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- using Application.Helpers;
- using Infrastructure.StockData;
- using Microsoft.EntityFrameworkCore;
- namespace Application.Tests;
- /// <summary>
- /// SeibroSupplySyncService 의 DB 반영 로직(SeibroSupplyImport) 통합 검증 — HTTP 는 파서 입력을 fixture 로 대체
- /// (SeibroDividendSyncTests 방식 미러링). 실제 SQL Server(LocalDB)로 대차 upsert·보호예수/유통변경 window·증감 rolling·비상장 확인.
- /// </summary>
- [TestClass]
- public sealed class SeibroSupplySyncTests
- {
- [TestMethod]
- public async Task UpsertLending_FromSampleXml_StampsIsinAndDate_ThenIdempotent()
- {
- // 기대 행위: getSlbDealingByIsin(1행) — 응답에 없는 Isin/StdDt 를 요청값으로 스탬핑 upsert (UQ Isin+StdDt). 재실행 무변경.
- var rows = SeibroSlbDealingParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getSlbDealingByIsin.xml")));
- var isin = $"KR7{Guid.NewGuid().ToString("N")[..9]}";
- var stdDt = new DateOnly(2018, 8, 22);
- using (var db = TestDb.Create())
- {
- var (inserted, updated) = await SeibroSupplyImport.UpsertLendingAsync(db, isin, stdDt, rows, CancellationToken.None);
- Assert.AreEqual(1, inserted);
- Assert.AreEqual(0, updated);
- }
- using (var db = TestDb.Create())
- {
- var (inserted, updated) = await SeibroSupplyImport.UpsertLendingAsync(db, isin, stdDt, rows, CancellationToken.None);
- Assert.AreEqual(0, inserted, "재실행 idempotent");
- Assert.AreEqual(0, updated, "무변경 no-op");
- var saved = await db.SecuritiesLending.AsNoTracking().Where(c => c.Isin == isin && c.StdDt == stdDt).SingleAsync();
- Assert.AreEqual(21976319L, saved.SlbTrRemQty);
- Assert.AreEqual(0.01m, saved.FrinerLendRemRatio);
- }
- }
- [TestMethod]
- public async Task ReplaceLockupWindow_FromSampleXml_DeletesThenInserts()
- {
- // 기대 행위: getSafeDpDutyDepoStatus — 예수일 window(day) delete+insert. 같은 날 재실행은 delete=1,insert=1 로 재구성.
- var rows = SeibroLockupParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getSafeDpDutyDepoStatus.xml")));
- var day = new DateOnly(2019, 2, 25);
- using (var db = TestDb.Create())
- {
- var (inserted, deleted) = await SeibroSupplyImport.ReplaceLockupWindowAsync(db, day, day, rows, CancellationToken.None);
- Assert.AreEqual(1, inserted);
- Assert.AreEqual(0, deleted, "첫 창은 삭제할 기존 행 없음");
- }
- using (var db = TestDb.Create())
- {
- var (inserted, deleted) = await SeibroSupplyImport.ReplaceLockupWindowAsync(db, day, day, rows, CancellationToken.None);
- Assert.AreEqual(1, inserted, "window 재구성 → insert");
- Assert.AreEqual(1, deleted, "기존 창 삭제");
- var saved = await db.LockupEvent.AsNoTracking().Where(c => c.SafedpDt == day).ToListAsync();
- Assert.AreEqual(1, saved.Count, "중복 없이 1행");
- Assert.AreEqual("케이비아이동국실업", saved[0].KorSecnNm);
- Assert.AreEqual("02", saved[0].DutySafedpRacd);
- }
- }
- [TestMethod]
- public async Task UpsertIssuanceChanges_FromSampleXml_KeepsBothSameNtimesRows()
- {
- // 기대 행위: getStkIncdecDetails(3행) upsert. 동일 발행횟수(75) 2행이 IssuDt/Racd 로 구분되어 둘 다 저장.
- var rows = SeibroStkIncdecParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getStkIncdecDetails.xml")));
- using (var db = TestDb.Create())
- {
- var (inserted, _) = await SeibroSupplyImport.UpsertIssuanceChangesAsync(db, rows, CancellationToken.None);
- Assert.AreEqual(3, inserted);
- }
- using (var db = TestDb.Create())
- {
- var (inserted, updated) = await SeibroSupplyImport.UpsertIssuanceChangesAsync(db, rows, CancellationToken.None);
- Assert.AreEqual(0, inserted, "재실행 idempotent");
- Assert.AreEqual(0, updated);
- var ntimes75 = await db.StockIssuanceChange.AsNoTracking().Where(c => c.Isin == "KR7005930003" && c.SecnIssuNtimes == 75).ToListAsync();
- Assert.AreEqual(2, ntimes75.Count, "발행횟수 75 2행 보존 (충돌 없음)");
- }
- }
- [TestMethod]
- public async Task ReplaceListingChangeWindow_FromSampleXml_ReconstructsWindow()
- {
- // 기대 행위: getStkListInfo — 적용일 window(2018-12) delete+insert. 재실행은 창 재구성(insert=deleted).
- var rows = SeibroStkListParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getStkListInfo.xml")));
- var begin = new DateOnly(2018, 12, 1);
- var end = new DateOnly(2018, 12, 31);
- int firstInserted;
- using (var db = TestDb.Create())
- {
- var (inserted, deleted) = await SeibroSupplyImport.ReplaceListingChangeWindowAsync(db, begin, end, rows, CancellationToken.None);
- Assert.IsTrue(inserted > 0, "12월 유통변경 다수 insert");
- Assert.AreEqual(0, deleted);
- firstInserted = inserted;
- }
- using (var db = TestDb.Create())
- {
- var (inserted, deleted) = await SeibroSupplyImport.ReplaceListingChangeWindowAsync(db, begin, end, rows, CancellationToken.None);
- Assert.AreEqual(firstInserted, inserted, "동일 창 재구성");
- Assert.AreEqual(firstInserted, deleted, "기존 창 전부 삭제 후 재삽입");
- var etf = await db.ListingChangeEvent.AsNoTracking().Where(c => c.Isin == "KR7314700006").SingleAsync();
- Assert.AreEqual("110", etf.AfaltCirclForm);
- }
- }
- [TestMethod]
- public async Task UpsertUnlisted_FromSampleXml_StampsStdDt()
- {
- // 기대 행위: getUnlistCirclInfo(1행) — StdDt 요청값 스탬핑 upsert (UQ Isin+StdDt).
- var rows = SeibroUnlistCirclParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getUnlistCirclInfo.xml")));
- var stdDt = new DateOnly(2018, 8, 7);
- using (var db = TestDb.Create())
- {
- var (inserted, _) = await SeibroSupplyImport.UpsertUnlistedAsync(db, stdDt, rows, CancellationToken.None);
- Assert.AreEqual(1, inserted);
- }
- using (var db = TestDb.Create())
- {
- var (inserted, updated) = await SeibroSupplyImport.UpsertUnlistedAsync(db, stdDt, rows, CancellationToken.None);
- Assert.AreEqual(0, inserted, "재실행 idempotent");
- Assert.AreEqual(0, updated);
- var saved = await db.UnlistedCirculation.AsNoTracking().Where(c => c.Isin == "KR7064400005" && c.StdDt == stdDt).SingleAsync();
- Assert.AreEqual(1400L, saved.AcnttrQty);
- Assert.AreEqual("보통주", saved.SecnKacdNm);
- }
- }
- [TestMethod]
- public void Quota_StockBudget_StopsAtCap()
- {
- // 기대 행위: 주식 카테고리 예산 상한 준수 — 상한까지만 소비 성공, 초과분은 false + Remaining 0
- var quota = new SeibroQuota(() => new DateOnly(2026, 7, 7));
- const int budget = 5;
- var consumed = 0;
- for (var i = 0; i < 10; i++)
- {
- if (quota.TryConsume(SeibroCategory.Stock, 1, budget))
- {
- consumed++;
- }
- }
- Assert.AreEqual(budget, consumed, "예산(5) 만큼만 소비");
- Assert.AreEqual(0, quota.Remaining(SeibroCategory.Stock, budget));
- }
- }
|