using Domain.Entities.Stocks; using Domain.Entities.Stocks.ValueObject; using MeetingHandler = Application.Features.Api.Stocks.GetMeetingCalendar.Handler; using MeetingQuery = Application.Features.Api.Stocks.GetMeetingCalendar.Query; using CbHandler = Application.Features.Api.Stocks.GetCbExercises.Handler; using CbQuery = Application.Features.Api.Stocks.GetCbExercises.Query; namespace Application.Tests; [TestClass] public sealed class SeibroCorpActionQueryHandlerTests { [TestMethod] public async Task GetMeetingCalendar_FiltersByGmetDt_JoinsStockName_CountsAgendas() { // 기대 행위: 총회일 from~to 필터 + Stock(IssucoCustno) 조인 종목명 + 안건수 집계 + 최신순. var custno = 810000 + Random.Shared.Next(1, 99999); var code = $"74{Random.Shared.Next(1000, 9999)}"; var stdDt = new DateOnly(2024, 12, 31); var gmetDt = new DateOnly(2025, 3, 20); using var db = TestDb.Create(); var stock = Stock.Create(code, "총회종목", StockMarket.KOSPI, new DateOnly(2020, 1, 2), isin: $"KR7{code}003"); stock.SetIssucoCustno(custno); await db.Stock.AddAsync(stock); await db.GeneralMeeting.AddAsync(GeneralMeeting.Create(custno, "00001", "001", "99", null, null, true, "1", null, "서울", false, stdDt, gmetDt, "0900", null, false, true, false)); // 범위 밖 총회 await db.GeneralMeeting.AddAsync(GeneralMeeting.Create(custno, "00001", "002", "99", null, null, true, "1", null, "부산", false, new DateOnly(2023, 6, 1), new DateOnly(2023, 6, 20), "1000", null, false, false, false)); // 안건 2건 (in-range 총회) await db.MeetingAgenda.AddAsync(MeetingAgenda.Create(custno, "00001", stdDt, gmetDt, "001", 1, "1", "1", "001", null, "재무제표승인", null, null, null, "9", "1")); await db.MeetingAgenda.AddAsync(MeetingAgenda.Create(custno, "00001", stdDt, gmetDt, "001", 2, "2", "2", "101", null, "이사 선임", null, "홍길동", null, "9", "1")); await db.SaveChangesAsync(default); var handler = new MeetingHandler(db); var result = await handler.Handle(new MeetingQuery(new DateOnly(2025, 1, 1), new DateOnly(2025, 12, 31), 1, 50), default); Assert.IsTrue(result.IsSuccess); var mine = result.Value.List.Where(c => c.IssucoCustno == custno).ToList(); Assert.AreEqual(1, mine.Count, "2025 총회 1건만 (2023 제외)"); Assert.AreEqual("총회종목", mine[0].Name, "Stock 조인 종목명"); Assert.AreEqual(code, mine[0].Code); Assert.AreEqual("001", mine[0].MeetingKind); Assert.IsTrue(mine[0].Evote); Assert.AreEqual(2, mine[0].AgendaCount, "안건 2건 집계"); } [TestMethod] public async Task GetCbExercises_ByExerciseStock_Default_OrdersDesc() { // 기대 행위: 기본(byBond=false) 은 XRC_STK_ISIN(행사주식) 기준 조회, 최신 행사일 순. var code = $"75{Random.Shared.Next(1000, 9999)}"; var stkIsin = $"KR7{code}003"; var bondIsin1 = $"KR6{Guid.NewGuid().ToString("N")[..9]}"; var bondIsin2 = $"KR6{Guid.NewGuid().ToString("N")[..9]}"; using var db = TestDb.Create(); var stock = Stock.Create(code, "행사주식", StockMarket.KOSDAQ, new DateOnly(2019, 5, 5), isin: stkIsin); await db.Stock.AddAsync(stock); await db.ConvertibleExercise.AddAsync(ConvertibleExercise.Create(bondIsin1, "씨비1", "CB", stkIsin, "행사주식", new DateOnly(2025, 2, 10), null, null, null, 1000m, 5000m, null)); await db.ConvertibleExercise.AddAsync(ConvertibleExercise.Create(bondIsin2, "비더블유2", "BW", stkIsin, "행사주식", new DateOnly(2025, 4, 1), null, null, null, 2000m, 5200m, null)); // 다른 행사주식 — 제외되어야 await db.ConvertibleExercise.AddAsync(ConvertibleExercise.Create(bondIsin1, "씨비1", "CB", $"KR7{Guid.NewGuid().ToString("N")[..9]}", "다른주식", new DateOnly(2025, 3, 1), null, null, null, 500m, 5000m, null)); await db.SaveChangesAsync(default); var handler = new CbHandler(db); var result = await handler.Handle(new CbQuery(code, false, 1, 50), default); Assert.IsTrue(result.IsSuccess); Assert.AreEqual(stkIsin, result.Value.Isin); Assert.AreEqual(2, result.Value.Total, "행사주식 = code 인 2건만"); Assert.AreEqual(new DateOnly(2025, 4, 1), result.Value.List[0].RgtStdDt, "최신 행사일 순"); Assert.AreEqual("BW", result.Value.List[0].BondKind); Assert.AreEqual(5200m, result.Value.List[0].ExercisePrice); } [TestMethod] public async Task GetCbExercises_ByBond_UsesIsinDirectly() { // 기대 행위: byBond=true 는 BOND_ISIN 기준 조회 (Stock 미조인, 입력을 ISIN 으로 간주). var bondIsin = $"KR6{Guid.NewGuid().ToString("N")[..9]}"; var stkIsin = $"KR7{Guid.NewGuid().ToString("N")[..9]}"; using var db = TestDb.Create(); await db.ConvertibleExercise.AddAsync(ConvertibleExercise.Create(bondIsin, "씨비", "CB", stkIsin, "주식", new DateOnly(2025, 1, 15), null, null, null, 300m, 4800m, null)); await db.SaveChangesAsync(default); var handler = new CbHandler(db); var result = await handler.Handle(new CbQuery(bondIsin, true, 1, 50), default); Assert.IsTrue(result.IsSuccess); Assert.AreEqual(bondIsin, result.Value.Isin); Assert.AreEqual(1, result.Value.Total); Assert.AreEqual(stkIsin, result.Value.List[0].ExerciseStockIsin, "행사주식 ISIN 링크"); } }