using MMoneyCoderSharp.Data; namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests; public sealed class LegacyTradingHaltWorkflowTests { private static readonly TradingHaltSelection KospiAlpha = new(TradingHaltMarket.Kospi, "005930", "가온전자"); private static readonly TradingHaltSelection KospiBeta = new(TradingHaltMarket.Kospi, "000660", "한빛반도체"); private static readonly TradingHaltSelection KosdaqAlpha = new(TradingHaltMarket.Kosdaq, "035720", "다온테크"); [Fact] public async Task SearchSelect_MaterializesClosedCurrentAndCandleDraftsFromActionIdOnly() { var service = new DelegateTradingHaltService { Search = (query, _, _) => Task.FromResult( SearchResult(query, KospiAlpha, KospiBeta, KosdaqAlpha)) }; var controller = new LegacyTradingHaltWorkflowController(service); var search = await controller.SearchAsync(" 거래 "); var selected = controller.SelectResult(search.Results[2].SelectionId); controller.SetMovingAverages(ma5: true, ma20: true); var current = controller.MaterializeSelectedAction( LegacyTradingHaltWorkflowController.CurrentActionId); var candle = controller.MaterializeSelectedAction( LegacyTradingHaltWorkflowController.CandleActionId); Assert.Equal("거래", service.SearchQuery); Assert.Equal(500, service.SearchMaximum); Assert.Equal("다온테크", selected.Selected?.DisplayName); Assert.Collection( selected.Actions, action => { Assert.Equal("current", action.ActionId); Assert.True(action.IsAvailable); }, action => { Assert.Equal("candle-120d", action.ActionId); Assert.True(action.IsAvailable); }); Assert.Equal("s5001", current.BuilderKey); Assert.Equal("5001", current.CutCode); Assert.Equal(new[] { "5001" }, current.Aliases); Assert.Equal("거래정지-현재가", current.Detail); Assert.Equal("stock", current.Category); Assert.Equal("kosdaq", current.Market); Assert.Equal("KOSDAQ", current.Selection.GroupCode); Assert.Equal("다온테크", current.Selection.Subject); Assert.Equal("1열판기본", current.Selection.GraphicType); Assert.Equal("HALTED", current.Selection.Subtype); Assert.Equal("035720", current.Selection.DataCode); Assert.False(current.Ma5); Assert.False(current.Ma20); Assert.Null(current.CandlePeriodDays); Assert.Equal("s8010", candle.BuilderKey); Assert.Equal("8051", candle.CutCode); Assert.Equal(new[] { "8051" }, candle.Aliases); Assert.Equal("캔들그래프-거래정지 · 120일", candle.Detail); Assert.Equal("chart", candle.Category); Assert.Equal("kosdaq", candle.Market); Assert.Equal("KOSDAQ_STOCK", candle.Selection.GroupCode); Assert.Equal("다온테크", candle.Selection.Subject); Assert.Equal("MA5,MA20", candle.Selection.GraphicType); Assert.Equal("TRADING_HALT", candle.Selection.Subtype); Assert.Equal("035720", candle.Selection.DataCode); Assert.True(candle.Ma5); Assert.True(candle.Ma20); Assert.Equal(120, candle.CandlePeriodDays); Assert.Equal("1/1", candle.PageText); Assert.True(candle.IsEnabled); Assert.Equal(6, candle.FadeDuration); Assert.Equal("legacy-trading-halt-workflow", candle.Source); Assert.Equal(candle.DraftId, candle.ToLegacyPlaylistEntry().EntryId); var method = typeof(LegacyTradingHaltWorkflowController).GetMethod( nameof(LegacyTradingHaltWorkflowController.MaterializeSelectedAction)); Assert.NotNull(method); Assert.Collection( method.GetParameters(), parameter => Assert.Equal("actionId", parameter.Name)); Assert.Throws( () => controller.MaterializeSelectedAction("8051-caller-override")); } [Fact] public async Task NameSort_FirstClickIsDescendingThenAscending_AndKeepsSelection() { var service = new DelegateTradingHaltService { Search = (query, _, _) => Task.FromResult( SearchResult(query, KospiAlpha, KospiBeta, KosdaqAlpha)) }; var controller = new LegacyTradingHaltWorkflowController(service); var source = await controller.SearchAsync(string.Empty); controller.SelectResult(source.Results[0].SelectionId); var descending = controller.ToggleNameSort(); var ascending = controller.ToggleNameSort(); Assert.Equal( new[] { "가온전자", "한빛반도체", "다온테크" }, source.Results.Select(row => row.DisplayName)); Assert.Equal(LegacyTradingHaltNameSort.Descending, descending.NameSort); Assert.Equal( new[] { "한빛반도체", "다온테크", "가온전자" }, descending.Results.Select(row => row.DisplayName)); Assert.Equal(LegacyTradingHaltNameSort.Ascending, ascending.NameSort); Assert.Equal( new[] { "가온전자", "다온테크", "한빛반도체" }, ascending.Results.Select(row => row.DisplayName)); Assert.Equal("005930", ascending.Selected?.StockCode); Assert.True(Assert.Single(ascending.Results, row => row.IsSelected).IsSelected); } [Fact] public async Task LateSearchResponse_CannotReplaceNewerTradingHaltResult() { var first = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); var second = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); var service = new DelegateTradingHaltService { Search = (query, _, _) => query == "첫째" ? first.Task : second.Task }; var controller = new LegacyTradingHaltWorkflowController(service); var firstRequest = controller.SearchAsync("첫째"); var secondRequest = controller.SearchAsync("둘째"); second.SetResult(SearchResult("둘째", KosdaqAlpha)); var newest = await secondRequest; first.SetResult(SearchResult("첫째", KospiAlpha)); var staleCompletion = await firstRequest; Assert.Equal("둘째", newest.Query); Assert.Equal("다온테크", Assert.Single(newest.Results).DisplayName); Assert.Equal("둘째", staleCompletion.Query); Assert.Equal("다온테크", Assert.Single(staleCompletion.Results).DisplayName); Assert.Equal("둘째", controller.Current.Query); } [Fact] public async Task InvalidIdentityOrSourceOrder_IsRejectedAndLeavesErrorSnapshot() { var duplicateName = new TradingHaltSelection( TradingHaltMarket.Kospi, "005931", KospiAlpha.DisplayName); var service = new DelegateTradingHaltService { Search = (query, _, _) => Task.FromResult( SearchResult(query, KospiAlpha, duplicateName)) }; var controller = new LegacyTradingHaltWorkflowController(service); await Assert.ThrowsAsync( () => controller.SearchAsync(string.Empty)); Assert.Equal(LegacyTradingHaltRequestStatus.Error, controller.Current.Status); Assert.Empty(controller.Current.Results); Assert.Equal(LegacyOperatorStatusKind.Error, controller.Current.StatusKind); } [Fact] public async Task TradingHaltSnapshots_AreImmutableAcrossLaterQueries() { var service = new DelegateTradingHaltService { Search = (query, _, _) => Task.FromResult( query == "A" ? SearchResult(query, KospiAlpha) : SearchResult(query, KosdaqAlpha)) }; var controller = new LegacyTradingHaltWorkflowController(service); var before = await controller.SearchAsync("A"); var after = await controller.SearchAsync("B"); Assert.Equal("가온전자", Assert.Single(before.Results).DisplayName); Assert.Equal("다온테크", Assert.Single(after.Results).DisplayName); var rows = Assert.IsAssignableFrom>( before.Results); Assert.Throws(() => rows.Clear()); var actions = Assert.IsAssignableFrom>( before.Actions); Assert.Throws(() => actions.Add(before.Actions[0])); } private static TradingHaltSelectionResult SearchResult( string query, params TradingHaltSelection[] selections) => new( query, DateTimeOffset.Parse("2026-07-15T00:00:00+09:00"), selections, false); private sealed class DelegateTradingHaltService : ITradingHaltSelectionService { public Func> Search { get; init; } = (query, _, _) => Task.FromResult(SearchResult(query)); public string? SearchQuery { get; private set; } public int SearchMaximum { get; private set; } public Task SearchAsync( string query = "", int maximumResults = LegacyTradingHaltSelectionService.DefaultMaximumResults, CancellationToken cancellationToken = default) { SearchQuery = query; SearchMaximum = maximumResults; return Search(query, maximumResults, cancellationToken); } } }