StockQueryHandlerTests.cs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. using Domain.Entities.Stocks;
  2. using Domain.Entities.Stocks.ValueObject;
  3. using Infrastructure.Persistence;
  4. using QuotesHandler = Application.Features.Api.Stocks.GetQuotes.Handler;
  5. using QuotesQuery = Application.Features.Api.Stocks.GetQuotes.Query;
  6. using SuggestHandler = Application.Features.Api.Stocks.Suggest.Handler;
  7. using SuggestQuery = Application.Features.Api.Stocks.Suggest.Query;
  8. namespace Application.Tests;
  9. [TestClass]
  10. public sealed class StockQueryHandlerTests
  11. {
  12. private static async Task<Stock> CreateStockAsync(AppDbContext db, string code, string name, StockMarket market = StockMarket.KOSPI, bool delisted = false)
  13. {
  14. var stock = Stock.Create(code, name, market, new DateOnly(2020, 1, 2));
  15. if (delisted)
  16. {
  17. stock.MarkDelisted(new DateOnly(2025, 12, 30));
  18. }
  19. await db.Stock.AddAsync(stock);
  20. await db.SaveChangesAsync(default);
  21. return stock;
  22. }
  23. [TestMethod]
  24. public async Task Suggest_NamePrefix_ReturnsUpTo10()
  25. {
  26. // 기대 행위: 이름 prefix 매칭은 최대 10건까지만 반환한다.
  27. using var db = TestDb.Create();
  28. for (var i = 1; i <= 12; i++)
  29. {
  30. await CreateStockAsync(db, $"9100{i:D2}", $"ZQA자동완성{i:D2}");
  31. }
  32. var handler = new SuggestHandler(db);
  33. var result = await handler.Handle(new SuggestQuery("ZQA자동완성"), default);
  34. Assert.AreEqual(10, result.List.Count, "prefix 매칭은 10건으로 제한된다");
  35. Assert.IsTrue(result.List.All(c => c.Name.StartsWith("ZQA자동완성")), "이름 prefix 만 매칭되어야 한다");
  36. }
  37. [TestMethod]
  38. public async Task Suggest_CodePrefix_ExcludesDelisted()
  39. {
  40. // 기대 행위: 코드 prefix 로도 매칭하되 상폐(soft-off) 종목은 제외한다.
  41. using var db = TestDb.Create();
  42. await CreateStockAsync(db, "920001", "QXB활성종목");
  43. await CreateStockAsync(db, "920002", "QXB상폐종목", delisted: true);
  44. var handler = new SuggestHandler(db);
  45. var result = await handler.Handle(new SuggestQuery("92000"), default);
  46. Assert.AreEqual(1, result.List.Count, "상폐 종목은 자동완성에서 제외된다");
  47. Assert.AreEqual("920001", result.List[0].Code);
  48. Assert.AreEqual("KOSPI", result.List[0].Market);
  49. }
  50. [TestMethod]
  51. public async Task Suggest_EmptyQuery_ReturnsEmpty()
  52. {
  53. // 기대 행위: 빈 검색어는 DB 조회 없이 빈 목록을 반환한다.
  54. using var db = TestDb.Create();
  55. var handler = new SuggestHandler(db);
  56. var result = await handler.Handle(new SuggestQuery(" "), default);
  57. Assert.AreEqual(0, result.List.Count);
  58. }
  59. [TestMethod]
  60. public async Task Quotes_ReturnsDenormClosePrice_IgnoresUnknownCodes()
  61. {
  62. // 기대 행위: Stock denorm(종가/등락률)만 읽어 반환하고, 미존재 코드는 조용히 제외한다.
  63. using var db = TestDb.Create();
  64. var stock = await CreateStockAsync(db, "930001", "QZC시세종목", StockMarket.KOSDAQ);
  65. stock.UpdateLastPrice(new DateOnly(2026, 7, 2), 70500, 1.25m, 420_000_000_000L);
  66. await CreateStockAsync(db, "930002", "QZC무시세종목");
  67. await db.SaveChangesAsync(default);
  68. var handler = new QuotesHandler(db);
  69. var result = await handler.Handle(new QuotesQuery(["930001", "930002", "939999"]), default);
  70. Assert.IsTrue(result.IsSuccess);
  71. Assert.AreEqual(2, result.Value.List.Count, "미존재 코드는 제외된다");
  72. var quoted = result.Value.List.Single(c => c.Code == "930001");
  73. Assert.AreEqual(70500, quoted.ClosePrice);
  74. Assert.AreEqual(1.25m, quoted.ChangeRate);
  75. var noPrice = result.Value.List.Single(c => c.Code == "930002");
  76. Assert.IsNull(noPrice.ClosePrice, "시세 미수집 종목은 null 로 반환된다");
  77. }
  78. [TestMethod]
  79. public async Task Quotes_Over50Codes_Fails()
  80. {
  81. // 기대 행위: 50개 초과 요청은 DB 조회 전에 거절한다.
  82. using var db = TestDb.Create();
  83. var codes = Enumerable.Range(100000, 51).Select(c => c.ToString()).ToList();
  84. var handler = new QuotesHandler(db);
  85. var result = await handler.Handle(new QuotesQuery(codes), default);
  86. Assert.IsFalse(result.IsSuccess, "최대 50개 제한을 넘으면 실패해야 한다");
  87. Assert.AreEqual("Stock.TooManyCodes", result.Error.Code);
  88. }
  89. }