| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- using Application.Helpers;
- using Domain.Entities.Forum.Briefings;
- using Domain.Entities.Stocks;
- using Domain.Entities.Stocks.ValueObject;
- using Infrastructure.Persistence;
- using Microsoft.EntityFrameworkCore;
- namespace Application.Tests;
- /// <summary>
- /// D2 M3 장전 브리핑 자동 발행 통합 검증 — DailyBriefingComposer.
- /// 발행 시 briefing 보드 lazy 시드 + 시스템 계정(MemberID=null) IsNotice Post + DailyBriefing 기록,
- /// BriefingDate 멱등(같은 날 재실행 skip), 본문에 지수/등락/공시 취합을 검증한다.
- /// 공유 antooza_test DB — 종목코드는 98xxxx 고범위 + GUID 격리.
- /// </summary>
- [TestClass]
- public sealed class DailyBriefingTests
- {
- private static int _codeSeq = 980000;
- private static string NextCode() => System.Threading.Interlocked.Increment(ref _codeSeq).ToString("D6");
- private static async Task SeedIndexAsync(AppDbContext db, MarketIndexSeries series, string name, DateOnly date, decimal close, decimal changeVal, int flucRateBp)
- {
- await db.IndexDailyPrice.AddAsync(IndexDailyPrice.Create(series, name, date, close, changeVal, flucRateBp, close, close, close, 1000L, 1_000_000L));
- await db.SaveChangesAsync(default);
- }
- private static async Task<int> SeedStockWithPriceAsync(AppDbContext db, string code, string name, DateOnly date, int close, decimal changeRate)
- {
- var stock = Stock.Create(code, name, StockMarket.KOSPI, new DateOnly(2020, 1, 2));
- await db.Stock.AddAsync(stock);
- await db.SaveChangesAsync(default);
- await db.StockDailyPrice.AddAsync(StockDailyPrice.Create(stock.ID, date, close, close, close, close, 1000L, 1_000_000L, 0, changeRate));
- await db.SaveChangesAsync(default);
- return stock.ID;
- }
- [TestMethod]
- public async Task Publish_CreatesSystemNoticePost_AndBriefingRecord()
- {
- var date = new DateOnly(2026, 7, 6);
- using (var seed = TestDb.Create())
- {
- await SeedIndexAsync(seed, MarketIndexSeries.KOSPI, "코스피", new DateOnly(2026, 7, 3), 2800.55m, 12.30m, 44);
- }
- DailyBriefingComposer.PublishResult result;
- using (var db = TestDb.Create())
- {
- result = await DailyBriefingComposer.PublishAsync(db, date, default);
- }
- Assert.IsTrue(result.Published, "최초 발행은 Published");
- Assert.IsFalse(result.AlreadyExisted);
- using var verify = TestDb.Create();
- var briefing = await verify.DailyBriefing.AsNoTracking().SingleAsync(c => c.BriefingDate == date);
- Assert.AreEqual(result.PostID, briefing.PostID);
- var post = await verify.Post.AsNoTracking().SingleAsync(c => c.ID == result.PostID);
- Assert.IsNull(post.MemberID, "시스템 계정 = MemberID null");
- Assert.AreEqual(DailyBriefingComposer.SystemAuthorName, post.Name);
- Assert.IsTrue(post.IsNotice, "브리핑은 공지로 발행");
- StringAssert.Contains(post.Subject, "장전 브리핑");
- // briefing 보드가 lazy 시드됐는지
- var board = await verify.Board.AsNoTracking().SingleAsync(c => c.ID == post.BoardID);
- Assert.AreEqual(BoardSeeder.BriefingBoardCode, board.Code);
- }
- [TestMethod]
- public async Task Publish_Idempotent_SameDate_SkipsSecondRun()
- {
- var date = new DateOnly(2026, 7, 7);
- using (var seed = TestDb.Create())
- {
- await SeedIndexAsync(seed, MarketIndexSeries.KOSDAQ, "코스닥", new DateOnly(2026, 7, 3), 850.10m, -3.20m, -37);
- }
- int firstPostID;
- using (var db = TestDb.Create())
- {
- var r1 = await DailyBriefingComposer.PublishAsync(db, date, default);
- Assert.IsTrue(r1.Published);
- firstPostID = r1.PostID;
- }
- using (var db = TestDb.Create())
- {
- var r2 = await DailyBriefingComposer.PublishAsync(db, date, default);
- Assert.IsFalse(r2.Published, "같은 날 재실행은 발행하지 않는다");
- Assert.IsTrue(r2.AlreadyExisted);
- Assert.AreEqual(firstPostID, r2.PostID, "기존 PostID 를 반환");
- }
- using var verify = TestDb.Create();
- Assert.AreEqual(1, await verify.DailyBriefing.CountAsync(c => c.BriefingDate == date), "브리핑 기록은 1건뿐");
- }
- [TestMethod]
- public async Task Publish_ComposesIndicesMoversAndDisclosures_IntoBody()
- {
- var date = new DateOnly(2026, 12, 31);
- // 공유 DB 의 다른 테스트 시세보다 뒤 거래일이어야 movers 쿼리(글로벌 최신 거래일)가 이 종목을 잡는다.
- var tradeDate = new DateOnly(2026, 12, 30);
- var gainerCode = NextCode();
- var loserCode = NextCode();
- using (var db = TestDb.Create())
- {
- await SeedIndexAsync(db, MarketIndexSeries.KOSPI, "코스피", tradeDate, 2801.00m, 10.00m, 36);
- await SeedStockWithPriceAsync(db, gainerCode, "급등주식회사", tradeDate, close: 15000, changeRate: 12.34m);
- await SeedStockWithPriceAsync(db, loserCode, "급락주식회사", tradeDate, close: 8000, changeRate: -9.87m);
- await db.Disclosure.AddAsync(Disclosure.Create("20260703000001", "00000001", "공시법인", gainerCode, "Y", "주요사항보고서", "공시법인", tradeDate));
- await db.SaveChangesAsync(default);
- }
- int postID;
- using (var db = TestDb.Create())
- {
- var r = await DailyBriefingComposer.PublishAsync(db, date, default);
- postID = r.PostID;
- }
- using var verify = TestDb.Create();
- var body = (await verify.Post.AsNoTracking().SingleAsync(c => c.ID == postID)).Content;
- StringAssert.Contains(body, "전일 지수");
- StringAssert.Contains(body, "코스피");
- StringAssert.Contains(body, "등락 상위");
- StringAssert.Contains(body, "급등주식회사");
- StringAssert.Contains(body, "등락 하위");
- StringAssert.Contains(body, "급락주식회사");
- StringAssert.Contains(body, "주요 공시");
- StringAssert.Contains(body, "주요사항보고서");
- }
- }
|