198 lines
10 KiB
C#
198 lines
10 KiB
C#
using System.Text.Json;
|
|
using MBN_STOCK_WEBVIEW.LegacyApplication;
|
|
using MBN_STOCK_WEBVIEW.LegacyBridge;
|
|
using MMoneyCoderSharp.Data;
|
|
|
|
namespace MBN_STOCK_WEBVIEW.LegacyBridge.Tests;
|
|
|
|
public sealed class LegacyOverseasBridgeTests
|
|
{
|
|
[Theory]
|
|
[InlineData(
|
|
"{\"type\":\"select-overseas-fixed-index\",\"payload\":{\"targetId\":\"nasdaq\"}}",
|
|
typeof(LegacySelectOverseasFixedIndexIntent))]
|
|
[InlineData(
|
|
"{\"type\":\"search-overseas-industries\",\"payload\":{\"query\":\"반도체\"}}",
|
|
typeof(LegacySearchOverseasIndustriesIntent))]
|
|
[InlineData(
|
|
"{\"type\":\"search-overseas-stocks\",\"payload\":{\"query\":\"Apple\"}}",
|
|
typeof(LegacySearchOverseasStocksIntent))]
|
|
[InlineData(
|
|
"{\"type\":\"select-overseas-industry\",\"payload\":{\"selectionId\":\"overseas-industry-00000001-0001\"}}",
|
|
typeof(LegacySelectOverseasIndustryIntent))]
|
|
[InlineData(
|
|
"{\"type\":\"select-overseas-stock\",\"payload\":{\"selectionId\":\"overseas-stock-00000001-0001\"}}",
|
|
typeof(LegacySelectOverseasStockIntent))]
|
|
[InlineData(
|
|
"{\"type\":\"activate-overseas-action\",\"payload\":{\"actionId\":\"stock-candle-20d\"}}",
|
|
typeof(LegacyActivateOverseasActionIntent))]
|
|
public void ParsesStrictOverseasIntents(string json, Type expectedType)
|
|
{
|
|
Assert.True(LegacyBridgeProtocol.TryParseIntent(json, out var intent, out var error));
|
|
Assert.Null(error);
|
|
Assert.IsType(expectedType, intent);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("{\"type\":\"select-overseas-fixed-index\",\"payload\":{\"targetId\":\"not-registered\"}}")]
|
|
[InlineData("{\"type\":\"activate-overseas-action\",\"payload\":{\"actionId\":\"8061\"}}")]
|
|
[InlineData("{\"type\":\"select-overseas-stock\",\"payload\":{\"selectionId\":\"../../stock\"}}")]
|
|
[InlineData("{\"type\":\"search-overseas-industries\",\"payload\":{\"query\":\"bad\\u0001query\"}}")]
|
|
[InlineData("{\"type\":\"search-overseas-stocks\",\"payload\":{\"query\":\"Apple\",\"symbol\":\"NAS@AAPL\"}}")]
|
|
[InlineData("{\"type\":\"activate-overseas-action\",\"payload\":{\"actionId\":12}}")]
|
|
public void RejectsUnknownMalformedAndAuthorityBearingOverseasIntents(string json)
|
|
{
|
|
Assert.False(LegacyBridgeProtocol.TryParseIntent(json, out var intent, out var error));
|
|
Assert.Null(intent);
|
|
Assert.NotNull(error);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProjectionExposesDisplayStateAndOpaqueIdsButNoSceneAuthority()
|
|
{
|
|
var workflow = new LegacyOverseasSelectionWorkflow(
|
|
new StaticIndustryService(),
|
|
new StaticStockService());
|
|
workflow.SelectFixedIndex("nasdaq");
|
|
var industries = await workflow.SearchIndustriesAsync("반도체");
|
|
workflow.SelectIndustry(industries.Industry.Results[0].SelectionId);
|
|
var stocks = await workflow.SearchStocksAsync("Apple");
|
|
workflow.SelectStock(stocks.Stock.Results[0].SelectionId);
|
|
var snapshot = new LegacyOperatorSnapshot(
|
|
7,
|
|
string.Empty,
|
|
[],
|
|
null,
|
|
[],
|
|
[],
|
|
null,
|
|
string.Empty,
|
|
LegacyOperatorStatusKind.Neutral,
|
|
Overseas: workflow.Current);
|
|
|
|
var json = LegacyBridgeProtocol.SerializeState(snapshot);
|
|
using var document = JsonDocument.Parse(json);
|
|
var overseas = document.RootElement.GetProperty("payload").GetProperty("overseas");
|
|
|
|
Assert.Equal(10, overseas.GetProperty("fixedIndexTargets").GetArrayLength());
|
|
Assert.Equal("nasdaq", overseas.GetProperty("selectedFixedIndexId").GetString());
|
|
Assert.Equal("나스닥", overseas.GetProperty("selectedFixedIndexName").GetString());
|
|
Assert.Equal(
|
|
"overseas-industry-00000001-0001",
|
|
overseas.GetProperty("industry").GetProperty("selectedId").GetString());
|
|
Assert.Equal(
|
|
"overseas-stock-00000001-0001",
|
|
overseas.GetProperty("stock").GetProperty("selectedId").GetString());
|
|
Assert.Equal(
|
|
0,
|
|
overseas.GetProperty("stock").GetProperty("isolatedInvalidRowCount").GetInt32());
|
|
Assert.Equal(12, overseas.GetProperty("actions").GetArrayLength());
|
|
Assert.All(
|
|
overseas.GetProperty("actions").EnumerateArray(),
|
|
action => Assert.True(action.GetProperty("isAvailable").GetBoolean()));
|
|
Assert.DoesNotContain("builderKey", json, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("sceneAlias", json, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("dataCode", json, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("nationCode", json, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("NAS@AAPL", json, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("NAS@SOX", json, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProjectionKeepsNonUsTwRowOpaqueAndReportsItAvailable()
|
|
{
|
|
var workflow = new LegacyOverseasSelectionWorkflow(
|
|
new StaticIndustryService(),
|
|
new StaticStockService([new WorldStockSearchItem("London Stock", "LNS@LON", "GB")]));
|
|
var searched = await workflow.SearchStocksAsync("London");
|
|
workflow.SelectStock(Assert.Single(searched.Stock.Results).SelectionId);
|
|
var snapshot = new LegacyOperatorSnapshot(
|
|
7, string.Empty, [], null, [], [], null, string.Empty,
|
|
LegacyOperatorStatusKind.Neutral, Overseas: workflow.Current);
|
|
|
|
using var document = JsonDocument.Parse(LegacyBridgeProtocol.SerializeState(snapshot));
|
|
var overseas = document.RootElement.GetProperty("payload").GetProperty("overseas");
|
|
var result = Assert.Single(overseas.GetProperty("stock").GetProperty("results").EnumerateArray());
|
|
Assert.Equal("London Stock", result.GetProperty("displayName").GetString());
|
|
Assert.False(result.TryGetProperty("nationCode", out _));
|
|
var action = overseas.GetProperty("actions").EnumerateArray().Single(value =>
|
|
value.GetProperty("actionId").GetString() == "stock-current");
|
|
Assert.True(action.GetProperty("isAvailable").GetBoolean());
|
|
Assert.Equal(JsonValueKind.Null, action.GetProperty("reason").ValueKind);
|
|
Assert.DoesNotContain("LNS@LON", document.RootElement.GetRawText(), StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProjectionKeepsThreeLetterNationOpaqueAndFailsClosed()
|
|
{
|
|
var workflow = new LegacyOverseasSelectionWorkflow(
|
|
new StaticIndustryService(),
|
|
new StaticStockService([new WorldStockSearchItem("Unsupported", "LNS@UNSUPPORTED", "GBR")]));
|
|
var searched = await workflow.SearchStocksAsync("unsupported");
|
|
workflow.SelectStock(Assert.Single(searched.Stock.Results).SelectionId);
|
|
var snapshot = new LegacyOperatorSnapshot(
|
|
7, string.Empty, [], null, [], [], null, string.Empty,
|
|
LegacyOperatorStatusKind.Neutral, Overseas: workflow.Current);
|
|
|
|
using var document = JsonDocument.Parse(LegacyBridgeProtocol.SerializeState(snapshot));
|
|
var overseas = document.RootElement.GetProperty("payload").GetProperty("overseas");
|
|
var action = overseas.GetProperty("actions").EnumerateArray().Single(value =>
|
|
value.GetProperty("actionId").GetString() == "stock-current");
|
|
|
|
Assert.False(action.GetProperty("isAvailable").GetBoolean());
|
|
Assert.Equal("선택한 해외종목의 국가 코드는 두 글자여야 합니다.",
|
|
action.GetProperty("reason").GetString());
|
|
Assert.DoesNotContain("GBR", document.RootElement.GetRawText(), StringComparison.Ordinal);
|
|
Assert.DoesNotContain("LNS@UNSUPPORTED", document.RootElement.GetRawText(), StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ProjectionReportsRowsIsolatedByTheReadBoundary()
|
|
{
|
|
var workflow = new LegacyOverseasSelectionWorkflow(
|
|
new StaticIndustryService(),
|
|
new StaticStockService(isolatedInvalidRowCount: 16));
|
|
await workflow.SearchStocksAsync(string.Empty);
|
|
var snapshot = new LegacyOperatorSnapshot(
|
|
7, string.Empty, [], null, [], [], null, string.Empty,
|
|
LegacyOperatorStatusKind.Neutral, Overseas: workflow.Current);
|
|
|
|
using var document = JsonDocument.Parse(LegacyBridgeProtocol.SerializeState(snapshot));
|
|
var stock = document.RootElement.GetProperty("payload")
|
|
.GetProperty("overseas").GetProperty("stock");
|
|
|
|
Assert.Equal(16, stock.GetProperty("isolatedInvalidRowCount").GetInt32());
|
|
Assert.True(stock.GetProperty("isTruncated").GetBoolean());
|
|
Assert.Single(stock.GetProperty("results").EnumerateArray());
|
|
}
|
|
|
|
private sealed class StaticIndustryService : IOverseasIndustryIndexSearchService
|
|
{
|
|
public Task<OverseasIndustryIndexSearchResult> SearchAsync(
|
|
string query,
|
|
int maximumResults = LegacyOverseasIndustryIndexSearchService.DefaultMaximumResults,
|
|
CancellationToken cancellationToken = default) =>
|
|
Task.FromResult(new OverseasIndustryIndexSearchResult(
|
|
query,
|
|
DateTimeOffset.Parse("2026-07-15T10:00:00+09:00"),
|
|
[new OverseasIndustryIndexItem("반도체", "US Semiconductor", "NAS@SOX")],
|
|
IsTruncated: false));
|
|
}
|
|
|
|
private sealed class StaticStockService(
|
|
IReadOnlyList<WorldStockSearchItem>? items = null,
|
|
int isolatedInvalidRowCount = 0) : IOverseasStockSearchService
|
|
{
|
|
public Task<WorldStockSearchResult> SearchAsync(
|
|
string query,
|
|
int maximumResults = LegacyOverseasStockSearchService.DefaultMaximumResults,
|
|
CancellationToken cancellationToken = default) =>
|
|
Task.FromResult(new WorldStockSearchResult(
|
|
query,
|
|
DateTimeOffset.Parse("2026-07-15T10:00:00+09:00"),
|
|
items ?? [new WorldStockSearchItem("Apple", "NAS@AAPL", "US")],
|
|
IsTruncated: isolatedInvalidRowCount > 0,
|
|
IsolatedInvalidRowCount: isolatedInvalidRowCount));
|
|
}
|
|
}
|