Files
MBN_STOCK_WEBVIEW/tests/MBN_STOCK_WEBVIEW.LegacyApplication.Tests/ManualFinancial/LegacyManualFinancialOperatorControllerTests.cs

331 lines
13 KiB
C#

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<LegacySceneSelection>(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<LegacyStockSearchData> SearchAsync(
string query,
CancellationToken cancellationToken = default) =>
Task.FromResult(new LegacyStockSearchData(query, [], []));
}
private sealed class FakeStockService : IStockSearchService
{
public Task<StockSearchResult> 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<ManualFinancialIdentity, ManualFinancialSnapshot>? GetOverride
{
get;
init;
}
public Task<ManualFinancialSearchResult> 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<ManualFinancialSnapshot> GetAsync(
ManualFinancialIdentity identity,
CancellationToken cancellationToken = default)
{
GetCalls++;
return Task.FromResult(
GetOverride?.Invoke(identity) ??
_rows[(identity.Screen, identity.StockName)]);
}
public Task<ManualFinancialMutationReceipt> 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<ManualFinancialMutationReceipt> 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<ManualFinancialMutationReceipt> 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<ManualFinancialMutationReceipt> 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);
}
}