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;
///
/// 관심종목(StockWatch) 통합 테스트 — 토글 등록/해제(멱등), 미존재 종목 거부, 관심여부 맵, 목록 조인(denorm 시세).
/// 공유 antooza_test DB — 종목코드 94xxxx 고범위 + GUID 회원으로 격리.
///
[TestClass]
public sealed class StockWatchTests
{
private static async Task 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 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);
}
}
}