| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- using Domain.Entities.Members;
- using Domain.Entities.Stocks;
- using Domain.Entities.Stocks.ValueObject;
- using Infrastructure.Persistence;
- using Microsoft.EntityFrameworkCore;
- using ToggleCommand = Application.Features.Api.Stocks.Watch.Toggle.Command;
- using ToggleHandler = Application.Features.Api.Stocks.Watch.Toggle.Handler;
- using ListHandler = Application.Features.Api.Stocks.Watch.List.Handler;
- using ListQuery = Application.Features.Api.Stocks.Watch.List.Query;
- using IsWatchingHandler = Application.Features.Api.Stocks.Watch.IsWatching.Handler;
- using IsWatchingQuery = Application.Features.Api.Stocks.Watch.IsWatching.Query;
- namespace Application.Tests;
- /// <summary>
- /// 관심종목(StockWatch) 통합 테스트 — 토글 등록/해제(멱등), 미존재 종목 거부, 관심여부 맵, 목록 조인(denorm 시세).
- /// 공유 antooza_test DB — 종목코드 94xxxx 고범위 + GUID 회원으로 격리.
- /// </summary>
- [TestClass]
- public sealed class StockWatchTests
- {
- private static async Task<int> CreateMemberAsync(AppDbContext db)
- {
- var member = Member.Create($"watch-{Guid.NewGuid():N}@antooza.test");
- await db.Member.AddAsync(member);
- await db.SaveChangesAsync(default);
- return member.ID;
- }
- private static async Task<string> CreateStockAsync(AppDbContext db, string code, int? lastClose = null)
- {
- var stock = Stock.Create(code, $"관심테스트{code}", StockMarket.KOSPI, new DateOnly(2020, 1, 2));
- if (lastClose.HasValue)
- {
- stock.UpdateLastPrice(new DateOnly(2026, 7, 13), lastClose.Value, 1.5m, 1_000_000_000);
- }
- await db.Stock.AddAsync(stock);
- await db.SaveChangesAsync(default);
- return code;
- }
- private static string NextCode()
- {
- // 940000~949999 범위 (StockBoard 970xxx·Paper 95/9600xx 와 비충돌)
- var n = 940000 + Random.Shared.Next(0, 10000);
- return n.ToString("D6");
- }
- [TestMethod]
- public async Task Toggle_AddsThenRemoves_Idempotent()
- {
- var code = NextCode();
- int memberID;
- using (var db = TestDb.Create())
- {
- memberID = await CreateMemberAsync(db);
- await CreateStockAsync(db, code);
- }
- using (var db = TestDb.Create())
- {
- var r1 = await new ToggleHandler(db).Handle(new ToggleCommand(memberID, code), default);
- Assert.IsTrue(r1.IsSuccess);
- Assert.IsTrue(r1.Value.IsWatched, "최초 토글 → 등록");
- }
- using (var verify = TestDb.Create())
- {
- Assert.AreEqual(1, await verify.StockWatch.CountAsync(c => c.MemberID == memberID && c.StockCode == code));
- }
- using (var db = TestDb.Create())
- {
- var r2 = await new ToggleHandler(db).Handle(new ToggleCommand(memberID, code), default);
- Assert.IsTrue(r2.IsSuccess);
- Assert.IsFalse(r2.Value.IsWatched, "재토글 → 해제");
- }
- using (var verify = TestDb.Create())
- {
- Assert.AreEqual(0, await verify.StockWatch.CountAsync(c => c.MemberID == memberID && c.StockCode == code));
- }
- }
- [TestMethod]
- public async Task Toggle_UnknownStock_NotFound()
- {
- var code = NextCode();
- int memberID;
- using (var db = TestDb.Create())
- {
- memberID = await CreateMemberAsync(db);
- // 종목 미시드
- }
- using (var db = TestDb.Create())
- {
- var r = await new ToggleHandler(db).Handle(new ToggleCommand(memberID, code), default);
- Assert.IsTrue(r.IsFailure, "존재하지 않는 종목은 거부");
- Assert.AreEqual("StockWatch.StockNotFound", r.Error.Code);
- }
- }
- [TestMethod]
- public async Task IsWatching_ReturnsMap()
- {
- var watched = NextCode();
- var notWatched = NextCode();
- int memberID;
- using (var db = TestDb.Create())
- {
- memberID = await CreateMemberAsync(db);
- await CreateStockAsync(db, watched);
- await CreateStockAsync(db, notWatched);
- }
- using (var db = TestDb.Create())
- {
- await new ToggleHandler(db).Handle(new ToggleCommand(memberID, watched), default);
- }
- using (var verify = TestDb.Create())
- {
- var res = await new IsWatchingHandler(verify).Handle(new IsWatchingQuery(memberID, [watched, notWatched]), default);
- Assert.IsTrue(res.Map[watched], "등록 종목 true");
- Assert.IsFalse(res.Map[notWatched], "미등록 종목 false");
- }
- }
- [TestMethod]
- public async Task List_ReturnsWatched_WithDenormPrice()
- {
- var code = NextCode();
- int memberID;
- using (var db = TestDb.Create())
- {
- memberID = await CreateMemberAsync(db);
- await CreateStockAsync(db, code, lastClose: 73000);
- }
- using (var db = TestDb.Create())
- {
- await new ToggleHandler(db).Handle(new ToggleCommand(memberID, code), default);
- }
- using (var verify = TestDb.Create())
- {
- var res = await new ListHandler(verify).Handle(new ListQuery(memberID), default);
- var row = res.List.SingleOrDefault(c => c.Code == code);
- Assert.IsNotNull(row, "관심 등록 종목이 목록에 포함");
- Assert.AreEqual($"관심테스트{code}", row.Name);
- Assert.AreEqual(73000, row.ClosePrice);
- Assert.AreEqual("KOSPI", row.Market);
- }
- }
- }
|