using MBN_STOCK_WEBVIEW.LegacyApplication; using MMoneyCoderSharp.Data; namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests; public sealed class LegacyOverseasOperatorControllerTests { private static readonly DateTimeOffset RetrievedAt = new(2026, 7, 15, 10, 0, 0, TimeSpan.FromHours(9)); [Fact] public void OverseasSnapshotAndFixedIndexActionRemainControllerAuthoritative() { var controller = CreateController(); var selectedTab = controller.SelectTab(LegacyOperatorTabId.OverseasStocks); Assert.NotNull(selectedTab.Overseas); Assert.Equal(10, selectedTab.Overseas.FixedIndexTargets.Count); Assert.Equal(12, selectedTab.Overseas.Actions.Count); Assert.Equal(new LegacyMovingAverageSelection(true, true), selectedTab.MovingAverages); Assert.Equal( new LegacyOverseasMovingAverageSelection(true, true), selectedTab.Overseas.MovingAverages); controller.SelectOverseasFixedIndex("nasdaq"); controller.SetMovingAverages(ma5: false, ma20: true); var state = controller.ActivateOverseasAction("index-candle-20d"); var row = Assert.Single(state.Playlist); var entry = row.ToPlayoutEntry(); var selection = Assert.IsType( entry.Selection); Assert.Equal("legacy-overseas-00000001", row.RowId); Assert.Equal("해외지수", row.MarketText); Assert.Equal("나스닥", row.StockName); Assert.Equal("캔들그래프", row.GraphicType); Assert.Equal("20일", row.Subtype); Assert.Equal("1/1", row.PageText); Assert.Equal("8040", entry.CutCode); Assert.Equal("FOREIGN_INDEX", selection.GroupCode); Assert.Equal("나스닥", selection.Subject); Assert.Equal("NAS@IXIC|US|0", selection.DataCode); Assert.Equal( "MA20", Assert.Single(controller.CreatePlayoutRequest().Playlist).Selection!.GraphicType); } [Fact] public async Task OverseasIndustryAndStockSearchSelectionsMaterializeExactPlaylistRows() { var controller = CreateController( industries: [new("반도체", "US Semiconductor", "NAS@SOX")], stocks: [new("Apple", "NAS@AAPL", "US")]); controller.SelectTab(LegacyOperatorTabId.OverseasStocks); var industries = await controller.SearchOverseasIndustriesAsync("반도체"); var industryId = Assert.Single(industries.Overseas!.Industry.Results).SelectionId; controller.SelectOverseasIndustry(industryId); var industryState = controller.ActivateOverseasAction("industry-current"); var industryRow = Assert.Single(industryState.Playlist); Assert.Equal("5001", industryRow.ToPlayoutEntry().CutCode); Assert.Equal("FOREIGN_INDUSTRY", industryRow.PlayoutSelection!.GroupCode); Assert.Equal("반도체", industryRow.PlayoutSelection.Subject); Assert.Equal("1열판기본", industryRow.PlayoutSelection.GraphicType); Assert.Equal("CURRENT", industryRow.PlayoutSelection.Subtype); Assert.Equal("US Semiconductor|NAS@SOX|US|0", industryRow.PlayoutSelection.DataCode); var stocks = await controller.SearchOverseasStocksAsync("apple"); var stockId = Assert.Single(stocks.Overseas!.Stock.Results).SelectionId; controller.SelectOverseasStock(stockId); var stockState = controller.ActivateOverseasAction("stock-candle-5d"); Assert.Equal(2, stockState.Playlist.Count); var stockRow = stockState.Playlist[1]; Assert.Equal("8061", stockRow.ToPlayoutEntry().CutCode); Assert.Equal("캔들그래프", stockRow.GraphicType); Assert.Equal("FOREIGN_STOCK", stockRow.PlayoutSelection!.GroupCode); Assert.Equal("Apple", stockRow.PlayoutSelection.Subject); Assert.Equal(string.Empty, stockRow.PlayoutSelection.GraphicType); Assert.Equal("PRICE", stockRow.PlayoutSelection.Subtype); Assert.Equal("NAS@AAPL|US|1", stockRow.PlayoutSelection.DataCode); Assert.Equal( "MA5,MA20", controller.CreatePlayoutRequest().Playlist[1].Selection!.GraphicType); } [Fact] public async Task DuplicateIndustryDisplayNamesKeepChosenTupleInRuntimePlaylist() { var controller = CreateController(industries: [ new("Shared", "First Input", "NAS@ONE"), new("Shared", "Second Input", "NAS@TWO") ]); controller.SelectTab(LegacyOperatorTabId.OverseasStocks); var searched = await controller.SearchOverseasIndustriesAsync(string.Empty); controller.SelectOverseasIndustry(searched.Overseas!.Industry.Results[1].SelectionId); controller.ActivateOverseasAction("industry-current"); var runtimeSelection = Assert.Single( controller.CreatePlayoutRequest().Playlist).Selection!; Assert.Equal("FOREIGN_INDUSTRY", runtimeSelection.GroupCode); Assert.Equal("Shared", runtimeSelection.Subject); Assert.Equal("Second Input|NAS@TWO|US|0", runtimeSelection.DataCode); } [Fact] public async Task NonUsStockKeepsOneExactIdentityThroughBothActualUc5SceneAliases() { var controller = CreateController( stocks: [new("London Stock", "LNS@LON", "GB")]); controller.SelectTab(LegacyOperatorTabId.OverseasStocks); // UC5's first inserted result is immediately the active RowMode row. var searched = await controller.SearchOverseasStocksAsync("london"); Assert.Equal("London Stock", searched.Overseas!.Stock.Selected?.InputName); var oneColumn = controller.ActivateOverseasAction("stock-current"); var candle = controller.ActivateOverseasAction("stock-candle-5d"); var rows = candle.Playlist; Assert.Equal("5001", rows[0].ToPlayoutEntry().CutCode); Assert.Equal("8061", rows[1].ToPlayoutEntry().CutCode); Assert.All(rows, row => { Assert.Equal("FOREIGN_STOCK", row.PlayoutSelection?.GroupCode); Assert.Equal("London Stock", row.PlayoutSelection?.Subject); Assert.Equal("LNS@LON|GB|1", row.PlayoutSelection?.DataCode); }); Assert.Single(oneColumn.Playlist); } [Fact] public async Task OverseasActionsRejectInactiveTabAndStaleSearchIds() { var controller = CreateController( industries: [new("반도체", "US Semiconductor", "NAS@SOX")]); var inactive = controller.SelectOverseasFixedIndex("nasdaq"); Assert.Equal(LegacyOperatorStatusKind.Error, inactive.StatusKind); Assert.Null(inactive.Overseas); controller.SelectTab(LegacyOperatorTabId.OverseasStocks); var first = await controller.SearchOverseasIndustriesAsync(""); var staleId = Assert.Single(first.Overseas!.Industry.Results).SelectionId; await controller.SearchOverseasIndustriesAsync("new generation"); var stale = controller.SelectOverseasIndustry(staleId); Assert.Equal(LegacyOperatorStatusKind.Error, stale.StatusKind); Assert.Equal("반도체", stale.Overseas!.Industry.Selected?.KoreanName); } private static LegacyOperatorController CreateController( IReadOnlyList? industries = null, IReadOnlyList? stocks = null) { var workflow = new LegacyOverseasSelectionWorkflow( new StaticIndustryService(industries ?? []), new StaticStockService(stocks ?? [])); return new LegacyOperatorController( new EmptyStockLookup(), overseasWorkflow: workflow); } private sealed class EmptyStockLookup : ILegacyStockLookup { public Task SearchAsync( string query, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(new LegacyStockSearchData(query, [], [])); } } private sealed class StaticIndustryService( IReadOnlyList items) : IOverseasIndustryIndexSearchService { public Task SearchAsync( string query, int maximumResults = LegacyOverseasIndustryIndexSearchService.DefaultMaximumResults, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(new OverseasIndustryIndexSearchResult( query.Trim(), RetrievedAt, items, IsTruncated: false)); } } private sealed class StaticStockService( IReadOnlyList items) : IOverseasStockSearchService { public Task SearchAsync( string query, int maximumResults = LegacyOverseasStockSearchService.DefaultMaximumResults, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(new WorldStockSearchResult( query.Trim(), RetrievedAt, items, IsTruncated: false)); } } }