using MBN_STOCK_WEBVIEW.Core.Playout.Scenes; using MMoneyCoderSharp.Data; namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests; public sealed class LegacyManualFinancialOperatorControllerTests { [Fact] public async Task GraphE_result_double_click_fresh_loads_appends_once_and_closes_on_success() { var service = new FakeManualService(Sales("Alpha", 1)); var controller = Controller(service); var opened = await controller.OpenManualFinancialAsync( ManualFinancialScreenKind.Sales); var resultId = Assert.Single(opened.ManualFinancial!.Rows).ResultId; var activated = await controller.ActivateManualFinancialResultAsync(resultId); Assert.Null(activated.ManualFinancial); Assert.Equal(1, service.GetCalls); Assert.Equal(0, service.MutationCalls); Assert.Equal("5080", Assert.Single(activated.Playlist).ToPlayoutEntry().CutCode); Assert.Equal("activate-manual-financial-result", activated.CommandResult?.Command); Assert.Equal(resultId, activated.CommandResult?.TargetId); Assert.True(activated.CommandResult?.Succeeded); } [Fact] public async Task GraphE_result_double_click_rejects_identity_change_without_append_or_close() { var service = new FakeManualService(Sales("Alpha", 1)) { GetOverride = _ => Sales("Beta", 2) }; var controller = Controller(service); var opened = await controller.OpenManualFinancialAsync( ManualFinancialScreenKind.Sales); var resultId = Assert.Single(opened.ManualFinancial!.Rows).ResultId; var rejected = await controller.ActivateManualFinancialResultAsync(resultId); Assert.NotNull(rejected.ManualFinancial); Assert.Empty(rejected.Playlist); Assert.Equal(1, service.GetCalls); Assert.Equal(0, service.MutationCalls); Assert.NotNull(rejected.Dialog); Assert.False(rejected.CommandResult?.Succeeded); } [Fact] public async Task Existing_GraphE_row_loads_verifies_and_adds_CSharp_owned_playlist_row() { var service = new FakeManualService(Sales("Alpha", 1)); var controller = Controller(service); var opened = await controller.OpenManualFinancialAsync( ManualFinancialScreenKind.Sales); var rowId = Assert.Single(opened.ManualFinancial!.Rows).ResultId; var loaded = await controller.LoadManualFinancialAsync(rowId); Assert.True(loaded.ManualFinancial!.CanMaterializePlaylist); Assert.Equal(0, service.MutationCalls); var added = controller.ActivateManualFinancialAction("sales"); var playlist = Assert.Single(added.Playlist); var entry = playlist.ToPlayoutEntry(); Assert.Equal("5080", entry.CutCode); var selection = Assert.IsType(entry.Selection); Assert.Equal("Alpha", selection.Subject); Assert.Equal("SALES", selection.GraphicType); Assert.Equal("1/1", playlist.PageText); } [Fact] public async Task GraphE_find_loads_fresh_detail_and_stale_sort_generation_opens_modal() { var service = new FakeManualService(Sales("Alpha", 1)); var controller = Controller(service); var opened = await controller.OpenManualFinancialAsync( ManualFinancialScreenKind.Sales); var generation = opened.ManualFinancial!.Revision; var found = await controller.FindManualFinancialAsync( "Alpha", LegacyManualFinancialFindDirection.Down, generation); Assert.Equal("Alpha", found.ManualFinancial!.SelectedRecord!.Identity.StockName); Assert.Equal(1, service.GetCalls); var sorted = controller.ToggleManualFinancialNameSort( found.ManualFinancial.Revision); var stale = controller.ToggleManualFinancialNameSort( found.ManualFinancial.Revision); Assert.NotNull(stale.Dialog); Assert.Equal( sorted.ManualFinancial!.NameSortDirection, stale.ManualFinancial!.NameSortDirection); } [Fact] public async Task Create_does_not_write_until_explicit_save_and_fresh_readback_completes() { var service = new FakeManualService(); var controller = Controller(service); await controller.OpenManualFinancialAsync(ManualFinancialScreenKind.OperatingProfit); controller.BeginManualFinancialCreate(); var searched = await controller.SearchManualFinancialStocksAsync("Alpha"); controller.SelectManualFinancialStock( Assert.Single(searched.ManualFinancial!.StockCandidates).ResultId); Assert.Equal(0, service.MutationCalls); var saved = await controller.SaveManualFinancialAsync(Profit("Alpha", 10)); Assert.Equal(1, service.MutationCalls); Assert.Equal(1, service.GetCalls); Assert.Equal( LegacyManualFinancialWorkflowStatus.WriteCommitted, saved.ManualFinancial!.Status); Assert.True(saved.ManualFinancial.CanMaterializePlaylist); Assert.DoesNotContain("password", saved.StatusMessage, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task Delete_and_delete_all_are_only_reached_by_explicit_controller_methods() { var service = new FakeManualService(Sales("Alpha", 1)); var controller = Controller(service); var opened = await controller.OpenManualFinancialAsync(ManualFinancialScreenKind.Sales); await controller.LoadManualFinancialAsync( Assert.Single(opened.ManualFinancial!.Rows).ResultId); Assert.Equal(0, service.MutationCalls); var deleted = await controller.DeleteManualFinancialAsync(all: false); Assert.Equal(1, service.MutationCalls); Assert.Empty(deleted.ManualFinancial!.Rows); var all = await controller.DeleteManualFinancialAsync(all: true); Assert.Equal(2, service.MutationCalls); Assert.Empty(all.ManualFinancial!.Rows); } private static LegacyOperatorController Controller(FakeManualService service) { var workflow = new LegacyManualFinancialWorkflow( service, new FakeStockService(), new LegacyManualFinancialWriteSafetyState()); return new LegacyOperatorController( new EmptyStockLookup(), manualFinancialWorkflow: workflow); } private static ManualFinancialSnapshot Sales(string stockName, int version) => new( new ManualSalesRecord( new ManualFinancialIdentity(ManualFinancialScreenKind.Sales, stockName), Enumerable.Range(1, 6) .Select(index => new ManualQuarterValue($"{index}Q", index)) .ToArray()), Version(version)); private static ManualOperatingProfitRecord Profit(string stockName, int offset) => new( new ManualFinancialIdentity( ManualFinancialScreenKind.OperatingProfit, stockName), Enumerable.Range(1, 6) .Select(index => new ManualQuarterValue($"{index}Q", offset + index)) .ToArray()); private static string Version(int value) => value.ToString("X64"); private sealed class EmptyStockLookup : ILegacyStockLookup { public Task SearchAsync( string query, CancellationToken cancellationToken = default) => Task.FromResult(new LegacyStockSearchData(query, [], [])); } private sealed class FakeStockService : IStockSearchService { public Task SearchAsync( string query, int maximumResults = LegacyStockSearchService.DefaultMaximumResults, CancellationToken cancellationToken = default) => Task.FromResult(new StockSearchResult( query, DateTimeOffset.UtcNow, [new StockSearchItem( StockMarket.Kospi, DataSourceKind.Oracle, "Alpha", "000001")], false)); } private sealed class FakeManualService : IManualFinancialScreenService { private readonly Dictionary<(ManualFinancialScreenKind, string), ManualFinancialSnapshot> _rows = new(); private int _version = 10; internal FakeManualService(params ManualFinancialSnapshot[] rows) { foreach (var row in rows) { _rows[(row.Record.Identity.Screen, row.Record.Identity.StockName)] = row; } } public bool CanMutate => true; internal int MutationCalls { get; private set; } internal int GetCalls { get; private set; } internal Func? GetOverride { get; init; } public Task SearchAsync( ManualFinancialScreenKind screen, string query = "", int maximumResults = LegacyManualFinancialScreenService.DefaultMaximumResults, CancellationToken cancellationToken = default) { var rows = _rows.Values .Where(row => row.Record.Identity.Screen == screen) .Where(row => query.Length == 0 || row.Record.Identity.StockName.Contains( query, StringComparison.OrdinalIgnoreCase)) .Take(maximumResults) .ToArray(); return Task.FromResult(new ManualFinancialSearchResult( screen, query, DateTimeOffset.UtcNow, rows, false)); } public Task GetAsync( ManualFinancialIdentity identity, CancellationToken cancellationToken = default) { GetCalls++; return Task.FromResult( GetOverride?.Invoke(identity) ?? _rows[(identity.Screen, identity.StockName)]); } public Task CreateAsync( ManualFinancialRecord record, CancellationToken cancellationToken = default) { MutationCalls++; _rows[(record.Identity.Screen, record.Identity.StockName)] = new ManualFinancialSnapshot(record, Version(++_version)); return Task.FromResult(Receipt( ManualFinancialMutationKind.Create, record.Identity.Screen, record.Identity.StockName, 1)); } public Task UpdateAsync( ManualFinancialSnapshot expected, ManualFinancialRecord replacement, CancellationToken cancellationToken = default) { MutationCalls++; _rows[(replacement.Identity.Screen, replacement.Identity.StockName)] = new ManualFinancialSnapshot(replacement, Version(++_version)); return Task.FromResult(Receipt( ManualFinancialMutationKind.Update, replacement.Identity.Screen, replacement.Identity.StockName, 1)); } public Task DeleteAsync( ManualFinancialSnapshot expected, CancellationToken cancellationToken = default) { MutationCalls++; _rows.Remove(( expected.Record.Identity.Screen, expected.Record.Identity.StockName)); return Task.FromResult(Receipt( ManualFinancialMutationKind.DeleteOne, expected.Record.Identity.Screen, expected.Record.Identity.StockName, 1)); } public Task DeleteAllAsync( ManualFinancialScreenKind screen, CancellationToken cancellationToken = default) { MutationCalls++; var keys = _rows.Keys.Where(key => key.Item1 == screen).ToArray(); foreach (var key in keys) { _rows.Remove(key); } return Task.FromResult(Receipt( ManualFinancialMutationKind.DeleteAll, screen, string.Empty, keys.Length)); } private static ManualFinancialMutationReceipt Receipt( ManualFinancialMutationKind kind, ManualFinancialScreenKind screen, string stockName, int affectedRows) => new( Guid.NewGuid(), kind, screen, stockName, DateTimeOffset.UtcNow, affectedRows); } }