Files
MBN_STOCK_WEBVIEW/tests/MBN_STOCK_WEBVIEW.LegacyApplication.Tests/TradingHalt/LegacyTradingHaltWorkflowTests.cs

252 lines
11 KiB
C#

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 Search_AutomaticallyActivatesFirstFarPointRowAndActions()
{
var service = new DelegateTradingHaltService
{
Search = (query, _, _) => Task.FromResult(
SearchResult(query, KospiAlpha, KospiBeta, KosdaqAlpha))
};
var controller = new LegacyTradingHaltWorkflowController(service);
var searched = await controller.SearchAsync(string.Empty);
var current = controller.MaterializeSelectedAction(
LegacyTradingHaltWorkflowController.CurrentActionId);
Assert.Equal(searched.Results[0].SelectionId, searched.Selected?.SelectionId);
Assert.True(searched.Results[0].IsSelected);
Assert.All(searched.Actions, action => Assert.True(action.IsAvailable));
Assert.Equal("005930", current.StockCode);
Assert.Equal("가온전자", current.DisplayName);
}
[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<ArgumentOutOfRangeException>(
() => 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<TradingHaltSelectionResult>(
TaskCreationOptions.RunContinuationsAsynchronously);
var second = new TaskCompletionSource<TradingHaltSelectionResult>(
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<TradingHaltSelectionDataException>(
() => 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<ICollection<LegacyTradingHaltResultView>>(
before.Results);
Assert.Throws<NotSupportedException>(() => rows.Clear());
var actions = Assert.IsAssignableFrom<ICollection<LegacyTradingHaltActionView>>(
before.Actions);
Assert.Throws<NotSupportedException>(() => 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<string, int, CancellationToken, Task<TradingHaltSelectionResult>> Search
{
get;
init;
} = (query, _, _) => Task.FromResult(SearchResult(query));
public string? SearchQuery { get; private set; }
public int SearchMaximum { get; private set; }
public Task<TradingHaltSelectionResult> SearchAsync(
string query = "",
int maximumResults = LegacyTradingHaltSelectionService.DefaultMaximumResults,
CancellationToken cancellationToken = default)
{
SearchQuery = query;
SearchMaximum = maximumResults;
return Search(query, maximumResults, cancellationToken);
}
}
}