using Domain.Entities.Stocks; using Infrastructure.StockData; using Microsoft.EntityFrameworkCore; using SharedKernel; namespace Application.Tests; /// /// KosisImport 의 DB 반영 로직(config ClassCode 필터 + Code+Period upsert) 통합 검증 — HTTP 는 파서 입력을 fixture/합성으로 대체 /// (SeibroDividendSyncTests 방식 미러링). 실제 SQL Server(LocalDB)로 UQ upsert·idempotency 확인. /// [TestClass] public sealed class KosisSyncTests { private static string FixtureRoot => Path.Combine(AppContext.BaseDirectory, "Fixtures", "Kosis"); private static AppSettings.KosisSection.KosisIndicator Cpi(string classCode = "T10") => new() { Code = $"CPI-{Guid.NewGuid():N}", Name = "소비자물가지수", OrgId = "101", TblId = "DT_1J22003", ItmId = "T", ObjL1 = "ALL", Cycle = "M", ClassCode = classCode }; [TestMethod] public async Task Upsert_CpiFixture_FiltersToNationalSeries_ThenIdempotent() { // 기대: CPI fixture(전국 T10 2행 + 서울 11 1행 + 빈DT 1행)에서 ClassCode=T10 필터로 전국 2행만 적재. 재실행은 무변경. var rows = KosisParser.Parse(File.ReadAllText(Path.Combine(FixtureRoot, "cpi.json"))); var indicator = Cpi(); using (var db = TestDb.Create()) { var (inserted, updated) = await KosisImport.UpsertAsync(db, indicator, rows, CancellationToken.None); Assert.AreEqual(2, inserted, "ClassCode=T10(전국) 계열만, DT 빈값 제외 → 2행"); Assert.AreEqual(0, updated); } using (var db = TestDb.Create()) { var (inserted, updated) = await KosisImport.UpsertAsync(db, indicator, rows, CancellationToken.None); Assert.AreEqual(0, inserted, "동일 스냅샷 재실행은 insert 없음"); Assert.AreEqual(0, updated, "무변경 행은 update 로 세지 않는다"); var saved = await db.MacroIndicator.AsNoTracking().Where(c => c.Code == indicator.Code && c.Period == "202401").SingleAsync(); Assert.AreEqual(113.17m, saved.Value); Assert.AreEqual("2020=100", saved.Unit); var count = await db.MacroIndicator.AsNoTracking().CountAsync(c => c.Code == indicator.Code); Assert.AreEqual(2, count, "서울(11) 계열은 적재되지 않음"); } } [TestMethod] public async Task Upsert_EmptyClassCode_AutoLocksToFirstSeries() { // ClassCode 미지정이면 응답 첫 계열(첫 등장 C1=T10)로 자동 고정 — 서울(11) 혼입 배제 var rows = KosisParser.Parse(File.ReadAllText(Path.Combine(FixtureRoot, "cpi.json"))); var indicator = Cpi(classCode: ""); using var db = TestDb.Create(); var (inserted, _) = await KosisImport.UpsertAsync(db, indicator, rows, CancellationToken.None); Assert.AreEqual(2, inserted, "첫 계열(T10)만 2행"); } [TestMethod] public async Task Upsert_ChangedValue_UpdatesRow() { var indicator = Cpi(); var original = new List { new("202501", 120.0m, "2020=100", "소비자물가지수(총지수)", "T10", "전국") }; var changed = new List { new("202501", 121.5m, "2020=100", "소비자물가지수(총지수)", "T10", "전국") }; using (var db = TestDb.Create()) { await KosisImport.UpsertAsync(db, indicator, original, CancellationToken.None); } using (var db = TestDb.Create()) { var (inserted, updated) = await KosisImport.UpsertAsync(db, indicator, changed, CancellationToken.None); Assert.AreEqual(0, inserted); Assert.AreEqual(1, updated, "수치 변경 → update"); var saved = await db.MacroIndicator.AsNoTracking().Where(c => c.Code == indicator.Code && c.Period == "202501").SingleAsync(); Assert.AreEqual(121.5m, saved.Value); } } [TestMethod] public async Task Upsert_NoMatchingClassCode_InsertsNothing() { var indicator = Cpi(classCode: "ZZZ"); var rows = KosisParser.Parse(File.ReadAllText(Path.Combine(FixtureRoot, "cpi.json"))); using var db = TestDb.Create(); var (inserted, updated) = await KosisImport.UpsertAsync(db, indicator, rows, CancellationToken.None); Assert.AreEqual(0, inserted); Assert.AreEqual(0, updated); } }