| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- using Application.Helpers;
- using Domain.Entities.Stocks;
- using Infrastructure.StockData;
- using Microsoft.EntityFrameworkCore;
- namespace Application.Tests;
- /// <summary>
- /// SeibroDerivSyncService 의 DB 반영 로직(SeibroDerivImport) + rolling 대상 선정 통합 검증 —
- /// HTTP 는 파서 입력을 fixture/합성 XML 로 대체(SeibroBondSyncTests 방식 미러링). 실제 SQL Server(LocalDB).
- /// 발견체인(발행→마스터 rolling) / 기초자산·행사·상환조건 upsert / 상환종목 window(종류별) / 미상환규모 13×2 스탬핑 / Deriv 예산 상한.
- /// </summary>
- [TestClass]
- public sealed class SeibroDerivSyncTests
- {
- [TestMethod]
- public async Task UpsertDerivIssuances_FromSampleXml_StampsSecnKacdAndIssuDt_ThenIdempotent()
- {
- // 기대 행위: getDerivCombiIssuInfo(다수행) — 응답에 없는 SECN_KACD·ISSU_DT 를 요청값 스탬핑 upsert (UQ Isin).
- var rows = SeibroDerivCombiIssuParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getDerivCombiIssuInfo.xml")));
- var issuDt = new DateOnly(2018, 7, 2);
- using (var db = TestDb.Create())
- {
- var (inserted, _) = await SeibroDerivImport.UpsertDerivIssuancesAsync(db, "4101", issuDt, rows, CancellationToken.None);
- Assert.IsTrue(inserted > 1, "다수 발행종목 insert");
- }
- using (var db = TestDb.Create())
- {
- var (inserted, updated) = await SeibroDerivImport.UpsertDerivIssuancesAsync(db, "4101", issuDt, rows, CancellationToken.None);
- Assert.AreEqual(0, inserted, "재실행 idempotent");
- Assert.AreEqual(0, updated, "무변경 no-op");
- var saved = await db.DerivIssuance.AsNoTracking().Where(c => c.Isin == "KR6533308872").SingleAsync();
- Assert.AreEqual("4101", saved.SecnKacd, "종류 스탬핑");
- Assert.AreEqual(issuDt, saved.IssuDt, "발행일 스탬핑");
- Assert.AreEqual(1636, saved.IssucoCustno);
- }
- }
- [TestMethod]
- public async Task DerivMasterTarget_ComesFromDerivIssuance_AndUpsertStampsIsin()
- {
- // 발견체인: DerivMaster rolling 대상 = DerivIssuance.Isin. getDerivCombiIsinInfo 응답엔 ISIN 없어 요청값 스탬핑, PK Isin upsert.
- var isin = $"KR{Guid.NewGuid().ToString("N")[..10]}";
- using (var db = TestDb.Create())
- {
- await db.DerivIssuance.AddAsync(DerivIssuance.Create(isin, "테스트ELS", 1636, "4101", new DateOnly(2018, 7, 2)), CancellationToken.None);
- await db.SaveChangesAsync(CancellationToken.None);
- }
- using (var db = TestDb.Create())
- {
- // rolling 대상 = DerivIssuance ∖ DerivMaster (미수집 우선)
- var issued = await db.DerivIssuance.AsNoTracking().Select(c => c.Isin).ToListAsync();
- CollectionAssert.Contains(issued, isin);
- var rows = SeibroDerivCombiIsinParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getDerivCombiIsinInfo.xml")));
- Assert.IsTrue(await SeibroDerivImport.UpsertDerivMasterAsync(db, isin, rows, CancellationToken.None));
- }
- using (var db = TestDb.Create())
- {
- Assert.IsTrue(await SeibroDerivImport.UpsertDerivMasterAsync(db, isin, SeibroDerivCombiIsinParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getDerivCombiIsinInfo.xml"))), CancellationToken.None), "재수집 idempotent upsert");
- var saved = await db.DerivMaster.AsNoTracking().Where(c => c.Isin == isin).SingleAsync();
- Assert.AreEqual("ELS", saved.SecnKanm);
- Assert.AreEqual(new DateOnly(2021, 7, 2), saved.XpirDt, "만기");
- Assert.AreEqual("3", saved.PrcpPrsvTpcd, "원금비보장");
- Assert.AreEqual(3, saved.BassetCnt);
- Assert.IsTrue(saved.MidredPossYn);
- }
- }
- [TestMethod]
- public async Task UpsertDerivAssets_FromSampleXml_StampsIsin_PreservesRawBassetCode()
- {
- // 기대 행위: getAssetInfo(3행) — ISIN 요청값 스탬핑, UQ(Isin,AssetSeq) upsert, StndBassetCd 반환 원문 보존.
- var rows = SeibroAssetParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getAssetInfo.xml")));
- var isin = $"KR{Guid.NewGuid().ToString("N")[..10]}";
- using (var db = TestDb.Create())
- {
- var (inserted, _) = await SeibroDerivImport.UpsertDerivAssetsAsync(db, isin, rows, CancellationToken.None);
- Assert.AreEqual(3, inserted, "기초자산 3종");
- }
- using (var db = TestDb.Create())
- {
- var (inserted, updated) = await SeibroDerivImport.UpsertDerivAssetsAsync(db, isin, rows, CancellationToken.None);
- Assert.AreEqual(0, inserted, "재실행 idempotent");
- Assert.AreEqual(0, updated);
- var hscei = await db.DerivAsset.AsNoTracking().Where(c => c.Isin == isin && c.AssetSeq == 1).SingleAsync();
- Assert.AreEqual("KSD310000306", hscei.StndBassetCd, "반환 코드(KSD31*) 원문 저장");
- Assert.AreEqual(11073m, hscei.XrcStdprc);
- }
- }
- [TestMethod]
- public async Task UpsertDerivAssetExercises_FromSampleXml_StampsIsin()
- {
- // 기대 행위: getAssetXrcInfo(8행) — ISIN 요청값 스탬핑, UQ(Isin,AssetSeq,XrcStdSeq) upsert.
- var rows = SeibroAssetXrcParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getAssetXrcInfo.xml")));
- var isin = $"KR{Guid.NewGuid().ToString("N")[..10]}";
- using (var db = TestDb.Create())
- {
- var (inserted, _) = await SeibroDerivImport.UpsertDerivAssetExercisesAsync(db, isin, rows, CancellationToken.None);
- Assert.AreEqual(8, inserted);
- }
- using (var db = TestDb.Create())
- {
- var (inserted, _) = await SeibroDerivImport.UpsertDerivAssetExercisesAsync(db, isin, rows, CancellationToken.None);
- Assert.AreEqual(0, inserted, "재실행 idempotent");
- var last = await db.DerivAssetExercise.AsNoTracking().Where(c => c.Isin == isin && c.XrcStdSeq == 8).SingleAsync();
- Assert.AreEqual(65m, last.XrcStdRatio);
- Assert.AreEqual(181.2395m, last.XrcPrice);
- }
- }
- [TestMethod]
- public async Task UpsertRedemptionConditions_FromSampleXml_StampsIsin_1000CharText()
- {
- // 기대 행위: getRedCondiInfo(10행) — ISIN 요청값 스탬핑, UQ(Isin,RedCondiTpcd,ValatNtimesSeq) upsert, 1000자 텍스트 보존.
- var rows = SeibroRedCondiParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getRedCondiInfo.xml")));
- var isin = $"KR{Guid.NewGuid().ToString("N")[..10]}";
- using (var db = TestDb.Create())
- {
- var (inserted, _) = await SeibroDerivImport.UpsertDerivRedemptionConditionsAsync(db, isin, rows, CancellationToken.None);
- Assert.AreEqual(10, inserted, "중간 8 + 만기 2");
- }
- using (var db = TestDb.Create())
- {
- var (inserted, _) = await SeibroDerivImport.UpsertDerivRedemptionConditionsAsync(db, isin, rows, CancellationToken.None);
- Assert.AreEqual(0, inserted, "재실행 idempotent");
- var expiryLoss = await db.DerivRedemptionCondition.AsNoTracking().Where(c => c.Isin == isin && c.RedCondiTpcd == "2" && c.ValatNtimesSeq == 2).SingleAsync();
- StringAssert.Contains(expiryLoss.RedCondiContent, "65% 미만");
- Assert.AreEqual("액면금액 * (만기평가가격/최초기준가격)", expiryLoss.RedFormulaContent);
- }
- }
- [TestMethod]
- public async Task ReplaceRedemptionWindow_FromSampleXml_StampsRedDtAndSecnKacd_ThenReconstructs()
- {
- // 기대 행위: getRedIsinInfo — 상환일×종류 window delete+insert (UQ Isin+RedDt+DerisecExerTpcd, RED_DT·SECN_KACD 스탬핑). 재실행 창 재구성.
- var rows = SeibroRedIsinParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getRedIsinInfo.xml")));
- var day = new DateOnly(2018, 8, 6);
- using (var db = TestDb.Create())
- {
- var (inserted, deleted) = await SeibroDerivImport.ReplaceRedemptionWindowAsync(db, "4101", day, day, rows, CancellationToken.None);
- Assert.AreEqual(12, inserted);
- Assert.AreEqual(0, deleted);
- }
- using (var db = TestDb.Create())
- {
- var (inserted, deleted) = await SeibroDerivImport.ReplaceRedemptionWindowAsync(db, "4101", day, day, rows, CancellationToken.None);
- Assert.AreEqual(12, inserted, "창 재구성");
- Assert.AreEqual(12, deleted, "기존 창 삭제");
- var saved = await db.DerivRedemption.AsNoTracking().Where(c => c.Isin == "KR6552327845" && c.RedDt == day).SingleAsync();
- Assert.AreEqual("4101", saved.SecnKacd, "종류 스탬핑");
- Assert.AreEqual("3", saved.DerisecExerTpcd, "조기");
- Assert.AreEqual(132000000m, saved.RedQty);
- }
- }
- [TestMethod]
- public async Task UpsertUnredeemedScale_13x2_StampsAllKeys_EmptyRemaTolerated()
- {
- // 기대 행위: getBassetUnredScale — STD_DT·STND_BASSET_CD·SECN_TPCD 3종 스탬핑, UQ upsert. 실샘플 UNRED_REMA 빈값 → null 허용.
- var rows = SeibroBassetUnredScaleParser.Parse(SeibroXml.Parse(SeibroFixtures.Read("getBassetUnredScale.xml")));
- // 공유 DB 라 고정 기준일이 타 테스트와 충돌하지 않도록 이 테스트 전용 기초자산코드 프리픽스를 쓴다.
- var stdDt = new DateOnly(2018, 7, 31);
- var cdA = $"TSTA{Guid.NewGuid().ToString("N")[..8]}";
- var cdB = $"TSTB{Guid.NewGuid().ToString("N")[..8]}";
- using (var db = TestDb.Create())
- {
- // 같은 기준일에 서로 다른 기초자산×종목구분 조합이 각각 별 row 로 들어감 (13×2 스윕의 축소판)
- Assert.IsTrue(await SeibroDerivImport.UpsertUnredeemedScaleAsync(db, stdDt, cdA, "41", rows, CancellationToken.None));
- Assert.IsTrue(await SeibroDerivImport.UpsertUnredeemedScaleAsync(db, stdDt, cdA, "43", rows, CancellationToken.None), "같은 기초자산 다른 SECN_TPCD 는 별 row");
- Assert.IsTrue(await SeibroDerivImport.UpsertUnredeemedScaleAsync(db, stdDt, cdB, "41", rows, CancellationToken.None), "다른 기초자산");
- }
- using (var db = TestDb.Create())
- {
- var mine = await db.ElsUnredeemedScale.AsNoTracking().Where(c => c.StdDt == stdDt && (c.StndBassetCd == cdA || c.StndBassetCd == cdB)).ToListAsync();
- Assert.AreEqual(3, mine.Count, "13×2 조합 중 3건 스탬핑 (본 테스트 기초자산만)");
- var kospi41 = mine.Single(c => c.StndBassetCd == cdA && c.SecnTpcd == "41");
- Assert.AreEqual("EURO STOXX 50", kospi41.BassetContent, "응답 기초자산내용 그대로");
- Assert.AreEqual(2, kospi41.UnredSecncnt);
- Assert.IsNull(kospi41.UnredRema, "빈 잔액 → null");
- // 재실행 idempotent
- Assert.IsTrue(await SeibroDerivImport.UpsertUnredeemedScaleAsync(db, stdDt, cdA, "41", rows, CancellationToken.None));
- var again = await db.ElsUnredeemedScale.AsNoTracking().Where(c => c.StdDt == stdDt && (c.StndBassetCd == cdA || c.StndBassetCd == cdB)).CountAsync();
- Assert.AreEqual(3, again, "재실행 무증가 (upsert)");
- }
- }
- [TestMethod]
- public void Quota_DerivBudget_CapsConsumption()
- {
- // 기대 행위: 파생 예산 상한 — 소진 후 추가 소비 거부, 다른 카테고리 무영향.
- var quota = new SeibroQuota(() => new DateOnly(2026, 7, 7));
- Assert.IsTrue(quota.TryConsume(SeibroCategory.Deriv, 8000, 8000), "예산 전량 소비");
- Assert.IsFalse(quota.TryConsume(SeibroCategory.Deriv, 1, 8000), "파생 예산 소진");
- Assert.AreEqual(0, quota.Remaining(SeibroCategory.Deriv, 8000));
- Assert.AreEqual(8000, quota.Remaining(SeibroCategory.Bond, 8000), "채권 예산 무영향");
- }
- }
|