feat: load legacy Cuts and Res runtime assets
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
using System.Text;
|
||||
using MBN_STOCK_WEBVIEW.Infrastructure;
|
||||
using MMoneyCoderSharp.Data;
|
||||
|
||||
namespace MBN_STOCK_WEBVIEW.Infrastructure.Tests;
|
||||
|
||||
public sealed class LegacyIniDatabaseOptionsLoaderTests
|
||||
{
|
||||
private const string IntegrationPathEnvironmentVariable =
|
||||
"MBN_STOCK_LEGACY_INI_INTEGRATION_PATH";
|
||||
|
||||
[Fact]
|
||||
public void Load_Utf8LegacyFile_MapsBothProvidersAndKeepsLastValue()
|
||||
{
|
||||
using var file = TemporaryLegacyIni.Create(
|
||||
Encoding.UTF8.GetBytes(
|
||||
"""
|
||||
[Oracle]
|
||||
//ConnectionName=ignored.invalid:1/IGNORED
|
||||
ConnectionName=oracle.dev.invalid:1522/DEVORA
|
||||
ID=old-user
|
||||
ID=oracle-user
|
||||
Pass=oracle=password
|
||||
|
||||
[Maria]
|
||||
ConnectionName=maria.dev.invalid:3307/dev_stock
|
||||
ID=maria-user
|
||||
Pass=maria=password
|
||||
"""));
|
||||
var loader = new LegacyIniDatabaseOptionsLoader(_ => null);
|
||||
|
||||
var options = loader.Load(file.Path);
|
||||
|
||||
Assert.True(options.IsConfigured(DataSourceKind.Oracle));
|
||||
Assert.True(options.IsConfigured(DataSourceKind.MariaDb));
|
||||
Assert.Equal("oracle.dev.invalid", options.Oracle.Host);
|
||||
Assert.Equal(1522, options.Oracle.Port);
|
||||
Assert.Equal("DEVORA", options.Oracle.Sid);
|
||||
Assert.Null(options.Oracle.ServiceName);
|
||||
Assert.Equal("oracle-user", options.Oracle.UserName);
|
||||
Assert.Equal("oracle=password", options.Oracle.Password);
|
||||
Assert.Equal("maria.dev.invalid", options.MariaDb.Host);
|
||||
Assert.Equal(3307, options.MariaDb.Port);
|
||||
Assert.Equal("dev_stock", options.MariaDb.Database);
|
||||
Assert.Equal("maria-user", options.MariaDb.UserName);
|
||||
Assert.Equal("maria=password", options.MariaDb.Password);
|
||||
Assert.Equal(MariaDbTlsMode.Disabled, options.MariaDb.TlsMode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Cp949LegacyFile_PreservesKoreanValues()
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
var cp949 = Encoding.GetEncoding(
|
||||
949,
|
||||
EncoderFallback.ExceptionFallback,
|
||||
DecoderFallback.ExceptionFallback);
|
||||
using var file = TemporaryLegacyIni.Create(cp949.GetBytes(
|
||||
"""
|
||||
[Oracle]
|
||||
ConnectionName=oracle.dev.invalid:1521/개발
|
||||
ID=오라클사용자
|
||||
Pass=암호
|
||||
[Maria]
|
||||
ConnectionName=maria.dev.invalid:3306/주식
|
||||
ID=마리아사용자
|
||||
Pass=암호2
|
||||
"""));
|
||||
|
||||
var options = new LegacyIniDatabaseOptionsLoader(_ => null).Load(file.Path);
|
||||
|
||||
Assert.Equal("개발", options.Oracle.Sid);
|
||||
Assert.Equal("오라클사용자", options.Oracle.UserName);
|
||||
Assert.Equal("주식", options.MariaDb.Database);
|
||||
Assert.Equal("마리아사용자", options.MariaDb.UserName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_EnvironmentStillOverridesLegacyValues()
|
||||
{
|
||||
using var file = TemporaryLegacyIni.CreateValidUtf8();
|
||||
var environment = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["MBN_STOCK_ORACLE_HOST"] = "override-oracle.invalid",
|
||||
["MBN_STOCK_MARIADB_TLS_MODE"] = "Required"
|
||||
};
|
||||
var loader = new LegacyIniDatabaseOptionsLoader(
|
||||
name => environment.TryGetValue(name, out var value) ? value : null);
|
||||
|
||||
var options = loader.Load(file.Path);
|
||||
|
||||
Assert.Equal("override-oracle.invalid", options.Oracle.Host);
|
||||
Assert.Equal(MariaDbTlsMode.Required, options.MariaDb.TlsMode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_InvalidFile_DoesNotExposeFileValues()
|
||||
{
|
||||
const string secret = "sensitive-password-sentinel";
|
||||
using var file = TemporaryLegacyIni.Create(Encoding.UTF8.GetBytes(
|
||||
$"[Oracle]\nConnectionName=invalid\nID=user\nPass={secret}"));
|
||||
|
||||
var exception = Assert.Throws<DatabaseConfigurationException>(
|
||||
() => new LegacyIniDatabaseOptionsLoader(_ => null).Load(file.Path));
|
||||
|
||||
Assert.DoesNotContain(secret, exception.ToString(), StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(file.Path, exception.ToString(), StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Runtime_PrefersExecutableResIni_AndFallsBackWhenAbsent()
|
||||
{
|
||||
using var directory = TemporaryDirectory.Create();
|
||||
var res = Path.Combine(directory.Path, "Res");
|
||||
Directory.CreateDirectory(res);
|
||||
File.WriteAllBytes(
|
||||
Path.Combine(res, "MmoneyCoder.ini"),
|
||||
TemporaryLegacyIni.ValidUtf8Bytes());
|
||||
|
||||
var runtime = DatabaseRuntime.CreateLegacyExecutableDefault(
|
||||
directory.Path,
|
||||
Path.Combine(directory.Path, "missing.json"));
|
||||
|
||||
Assert.Equal("oracle.dev.invalid", runtime.Options.Oracle.Host);
|
||||
|
||||
File.Delete(Path.Combine(res, "MmoneyCoder.ini"));
|
||||
var fallback = DatabaseRuntime.CreateLegacyExecutableDefault(
|
||||
directory.Path,
|
||||
Path.Combine(directory.Path, "missing.json"),
|
||||
Path.Combine(directory.Path, "missing-legacy.ini"));
|
||||
Assert.False(fallback.Options.IsConfigured(DataSourceKind.Oracle));
|
||||
Assert.False(fallback.Options.IsConfigured(DataSourceKind.MariaDb));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Runtime_UsesLocalLegacyOverrideWhenPackageDoesNotContainCredentials()
|
||||
{
|
||||
using var directory = TemporaryDirectory.Create();
|
||||
using var overrideFile = TemporaryLegacyIni.CreateValidUtf8();
|
||||
|
||||
var runtime = DatabaseRuntime.CreateLegacyExecutableDefault(
|
||||
directory.Path,
|
||||
Path.Combine(directory.Path, "missing.json"),
|
||||
overrideFile.Path);
|
||||
|
||||
Assert.Equal("oracle.dev.invalid", runtime.Options.Oracle.Host);
|
||||
Assert.Equal("maria.dev.invalid", runtime.Options.MariaDb.Host);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_OptionalRealLegacyIni_IsConfiguredWithoutPrintingValues()
|
||||
{
|
||||
var path = Environment.GetEnvironmentVariable(
|
||||
IntegrationPathEnvironmentVariable);
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var options = new LegacyIniDatabaseOptionsLoader().Load(path);
|
||||
|
||||
Assert.True(options.IsConfigured(DataSourceKind.Oracle));
|
||||
Assert.True(options.IsConfigured(DataSourceKind.MariaDb));
|
||||
}
|
||||
|
||||
private sealed class TemporaryLegacyIni : IDisposable
|
||||
{
|
||||
private TemporaryLegacyIni(string directory, string path)
|
||||
{
|
||||
Directory = directory;
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public string Directory { get; }
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public static TemporaryLegacyIni CreateValidUtf8() =>
|
||||
Create(ValidUtf8Bytes());
|
||||
|
||||
public static byte[] ValidUtf8Bytes() => Encoding.UTF8.GetBytes(
|
||||
"""
|
||||
[Oracle]
|
||||
ConnectionName=oracle.dev.invalid:1521/DEVORA
|
||||
ID=oracle-user
|
||||
Pass=oracle-password
|
||||
[Maria]
|
||||
ConnectionName=maria.dev.invalid:3306/dev_stock
|
||||
ID=maria-user
|
||||
Pass=maria-password
|
||||
""");
|
||||
|
||||
public static TemporaryLegacyIni Create(byte[] bytes)
|
||||
{
|
||||
var directory = System.IO.Path.Combine(
|
||||
System.IO.Path.GetTempPath(),
|
||||
Guid.NewGuid().ToString("N"));
|
||||
System.IO.Directory.CreateDirectory(directory);
|
||||
var path = System.IO.Path.Combine(directory, "MmoneyCoder.ini");
|
||||
File.WriteAllBytes(path, bytes);
|
||||
return new TemporaryLegacyIni(directory, path);
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
System.IO.Directory.Delete(Directory, recursive: true);
|
||||
}
|
||||
|
||||
private sealed class TemporaryDirectory : IDisposable
|
||||
{
|
||||
private TemporaryDirectory(string path) => Path = path;
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public static TemporaryDirectory Create()
|
||||
{
|
||||
var path = System.IO.Path.Combine(
|
||||
System.IO.Path.GetTempPath(),
|
||||
Guid.NewGuid().ToString("N"));
|
||||
System.IO.Directory.CreateDirectory(path);
|
||||
return new TemporaryDirectory(path);
|
||||
}
|
||||
|
||||
public void Dispose() =>
|
||||
System.IO.Directory.Delete(Path, recursive: true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
using System.Text;
|
||||
using MMoneyCoderSharp.Data;
|
||||
|
||||
namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests;
|
||||
|
||||
public sealed class LegacyRuntimeUiCatalogLoaderTests
|
||||
{
|
||||
private static readonly Encoding Cp949 = CreateCp949Encoding();
|
||||
|
||||
[Fact]
|
||||
public void ConfiguredLegacyBinDebugFiles_MatchEveryRuntimeProjection()
|
||||
{
|
||||
var legacyBinDebug = Environment.GetEnvironmentVariable(
|
||||
"MBN_STOCK_N_LEGACY_BIN_DEBUG");
|
||||
if (string.IsNullOrWhiteSpace(legacyBinDebug))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var composition = LegacyRuntimeUiCatalogLoader.ResolveFromBaseDirectory(
|
||||
legacyBinDebug);
|
||||
|
||||
Assert.Empty(composition.Warnings);
|
||||
AssertRuntimeFixedFile(
|
||||
legacyBinDebug,
|
||||
"해외.ini",
|
||||
LegacyFixedMarket.Overseas,
|
||||
LegacyFixedActionCatalog.ExpectedOverseasCount,
|
||||
composition.FixedCatalog);
|
||||
AssertRuntimeFixedFile(
|
||||
legacyBinDebug,
|
||||
"환율.ini",
|
||||
LegacyFixedMarket.Exchange,
|
||||
LegacyFixedActionCatalog.ExpectedExchangeCount,
|
||||
composition.FixedCatalog);
|
||||
AssertRuntimeFixedFile(
|
||||
legacyBinDebug,
|
||||
"지수.ini",
|
||||
LegacyFixedMarket.Index,
|
||||
LegacyFixedActionCatalog.ExpectedIndexCount,
|
||||
composition.FixedCatalog);
|
||||
AssertRuntimeIndustryFile(
|
||||
legacyBinDebug,
|
||||
"업종_코스피.ini",
|
||||
IndustryMarket.Kospi,
|
||||
composition.IndustryLayout);
|
||||
AssertRuntimeIndustryFile(
|
||||
legacyBinDebug,
|
||||
"업종_코스닥.ini",
|
||||
IndustryMarket.Kosdaq,
|
||||
composition.IndustryLayout);
|
||||
AssertRuntimeComparisonFile(
|
||||
legacyBinDebug,
|
||||
composition.ComparisonLayout);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuntimeFiles_ControlOnlyTrustedDisplayOrder()
|
||||
{
|
||||
using var directory = new TemporaryDirectory();
|
||||
var fixedOrders = Enum.GetValues<LegacyFixedMarket>()
|
||||
.ToDictionary(
|
||||
market => market,
|
||||
market => LegacyFixedActionCatalog.Default.GetActions(market)
|
||||
.Reverse()
|
||||
.ToArray());
|
||||
var kospi = LegacyIndustryActionCatalog.GetActions(IndustryMarket.Kospi)
|
||||
.Reverse()
|
||||
.ToArray();
|
||||
var kosdaq = LegacyIndustryActionCatalog.GetActions(IndustryMarket.Kosdaq)
|
||||
.Reverse()
|
||||
.ToArray();
|
||||
var targets = LegacyComparisonWorkflowCatalog.FixedTargets
|
||||
.Reverse()
|
||||
.ToArray();
|
||||
var directActions = LegacyComparisonWorkflowCatalog.Actions
|
||||
.Where(action => action.Kind != LegacyComparisonActionKind.Return)
|
||||
.Reverse()
|
||||
.ToArray();
|
||||
var returnActions = LegacyComparisonWorkflowCatalog.Actions
|
||||
.Where(action => action.Kind == LegacyComparisonActionKind.Return)
|
||||
.Reverse()
|
||||
.ToArray();
|
||||
|
||||
WriteAllValidFiles(
|
||||
directory.Path,
|
||||
fixedOrders,
|
||||
kospi,
|
||||
kosdaq,
|
||||
targets,
|
||||
directActions,
|
||||
returnActions,
|
||||
returnSectionFirst: true);
|
||||
|
||||
var composition = LegacyRuntimeUiCatalogLoader.ResolveFromBaseDirectory(
|
||||
directory.Path);
|
||||
|
||||
Assert.Empty(composition.Warnings);
|
||||
foreach (var market in Enum.GetValues<LegacyFixedMarket>())
|
||||
{
|
||||
Assert.Equal(
|
||||
FlattenForIni(fixedOrders[market]).Select(action => action.Id),
|
||||
composition.FixedCatalog.GetActions(market).Select(action => action.Id));
|
||||
}
|
||||
|
||||
Assert.Equal(
|
||||
kospi.Select(action => action.ActionId),
|
||||
composition.IndustryLayout.GetActions(IndustryMarket.Kospi)
|
||||
.Select(action => action.ActionId));
|
||||
Assert.Equal(
|
||||
kosdaq.Select(action => action.ActionId),
|
||||
composition.IndustryLayout.GetActions(IndustryMarket.Kosdaq)
|
||||
.Select(action => action.ActionId));
|
||||
Assert.Equal(
|
||||
targets.Select(target => target.TargetId),
|
||||
composition.ComparisonLayout.FixedTargets.Select(target => target.TargetId));
|
||||
Assert.Equal(
|
||||
returnActions.Select(action => action.ActionId)
|
||||
.Concat(directActions.Select(action => action.ActionId)),
|
||||
composition.ComparisonLayout.CreateActionViews(null, null)
|
||||
.Select(action => action.ActionId));
|
||||
|
||||
var industryWorkflow = new LegacyIndustrySelectionWorkflow(
|
||||
new EmptyIndustrySelectionService(),
|
||||
actionLayout: composition.IndustryLayout);
|
||||
Assert.Equal(
|
||||
kospi.Select(action => action.ActionId),
|
||||
industryWorkflow.ResetForTabActivation(IndustryMarket.Kospi)
|
||||
.Actions.Select(action => action.ActionId));
|
||||
|
||||
var comparisonWorkflow = new LegacyComparisonWorkflowController(
|
||||
new EmptyComparisonDataService(),
|
||||
new EmptyComparisonPairStore(),
|
||||
composition.ComparisonLayout);
|
||||
Assert.Equal(
|
||||
targets.Select(target => target.DisplayName),
|
||||
comparisonWorkflow.Current.FixedTargets.Select(target => target.DisplayName));
|
||||
Assert.Equal(
|
||||
returnActions.Select(action => action.ActionId)
|
||||
.Concat(directActions.Select(action => action.ActionId)),
|
||||
comparisonWorkflow.Current.Actions.Select(action => action.ActionId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownFixedRow_FallsBackWithoutRegisteringAnAction()
|
||||
{
|
||||
using var directory = new TemporaryDirectory();
|
||||
WriteAllValidFiles(directory.Path);
|
||||
WriteFixedFile(
|
||||
directory.Path,
|
||||
LegacyFixedMarket.Overseas,
|
||||
LegacyFixedActionCatalog.Default.GetActions(LegacyFixedMarket.Overseas),
|
||||
(action, index) => index == 0 ? "untrusted-new-action" : action.Label);
|
||||
|
||||
var composition = LegacyRuntimeUiCatalogLoader.ResolveFromBaseDirectory(
|
||||
directory.Path);
|
||||
|
||||
var warning = Assert.Single(composition.Warnings);
|
||||
Assert.Equal("해외.ini", warning.FileName);
|
||||
Assert.Equal(LegacyRuntimeUiCatalogFailure.UnknownEntry, warning.Failure);
|
||||
Assert.Equal(
|
||||
LegacyFixedActionCatalog.Default.GetActions(LegacyFixedMarket.Overseas)
|
||||
.Select(action => action.Id),
|
||||
composition.FixedCatalog.GetActions(LegacyFixedMarket.Overseas)
|
||||
.Select(action => action.Id));
|
||||
Assert.DoesNotContain(
|
||||
composition.FixedCatalog.Actions,
|
||||
action => string.Equals(
|
||||
action.Label,
|
||||
"untrusted-new-action",
|
||||
StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExcessDuplicateRow_FallsBackWithDuplicateWarning()
|
||||
{
|
||||
using var directory = new TemporaryDirectory();
|
||||
WriteAllValidFiles(directory.Path);
|
||||
WriteFixedFile(
|
||||
directory.Path,
|
||||
LegacyFixedMarket.Exchange,
|
||||
LegacyFixedActionCatalog.Default.GetActions(LegacyFixedMarket.Exchange),
|
||||
appendDuplicate: true);
|
||||
|
||||
var composition = LegacyRuntimeUiCatalogLoader.ResolveFromBaseDirectory(
|
||||
directory.Path);
|
||||
|
||||
var warning = Assert.Single(composition.Warnings);
|
||||
Assert.Equal("환율.ini", warning.FileName);
|
||||
Assert.Equal(LegacyRuntimeUiCatalogFailure.DuplicateEntry, warning.Failure);
|
||||
Assert.Equal(
|
||||
LegacyFixedActionCatalog.Default.GetActions(LegacyFixedMarket.Exchange)
|
||||
.Select(action => action.Id),
|
||||
composition.FixedCatalog.GetActions(LegacyFixedMarket.Exchange)
|
||||
.Select(action => action.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidCp949_FallsBackOnlyForThatFile()
|
||||
{
|
||||
using var directory = new TemporaryDirectory();
|
||||
WriteAllValidFiles(directory.Path);
|
||||
File.WriteAllBytes(
|
||||
System.IO.Path.Combine(directory.Path, "Res", "지수.ini"),
|
||||
[0x81]);
|
||||
|
||||
var composition = LegacyRuntimeUiCatalogLoader.ResolveFromBaseDirectory(
|
||||
directory.Path);
|
||||
|
||||
var warning = Assert.Single(composition.Warnings);
|
||||
Assert.Equal("지수.ini", warning.FileName);
|
||||
Assert.Equal(LegacyRuntimeUiCatalogFailure.InvalidEncoding, warning.Failure);
|
||||
Assert.Equal(
|
||||
LegacyFixedActionCatalog.Default.GetActions(LegacyFixedMarket.Index)
|
||||
.Select(action => action.Id),
|
||||
composition.FixedCatalog.GetActions(LegacyFixedMarket.Index)
|
||||
.Select(action => action.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RowLimit_FailsClosedBeforeCatalogJoin()
|
||||
{
|
||||
using var directory = new TemporaryDirectory();
|
||||
WriteAllValidFiles(directory.Path);
|
||||
var section = LegacyFixedActionCatalog.Default
|
||||
.GetActions(LegacyFixedMarket.Overseas)[0].Section;
|
||||
WriteCp949(
|
||||
System.IO.Path.Combine(directory.Path, "Res", "해외.ini"),
|
||||
[$"[{section}]", .. Enumerable.Range(1, 51).Select(index => $"row-{index}")]);
|
||||
|
||||
var composition = LegacyRuntimeUiCatalogLoader.ResolveFromBaseDirectory(
|
||||
directory.Path);
|
||||
|
||||
var warning = Assert.Single(composition.Warnings);
|
||||
Assert.Equal("해외.ini", warning.FileName);
|
||||
Assert.Equal(LegacyRuntimeUiCatalogFailure.TooManyRows, warning.Failure);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComparisonDirectActionCannotBeReinterpretedAsAChildRow()
|
||||
{
|
||||
using var directory = new TemporaryDirectory();
|
||||
WriteAllValidFiles(directory.Path);
|
||||
var directActions = LegacyComparisonWorkflowCatalog.Actions
|
||||
.Where(action => action.Kind != LegacyComparisonActionKind.Return)
|
||||
.ToArray();
|
||||
var returnActions = LegacyComparisonWorkflowCatalog.Actions
|
||||
.Where(action => action.Kind == LegacyComparisonActionKind.Return)
|
||||
.ToArray();
|
||||
var lines = new List<string> { "[지수]" };
|
||||
lines.AddRange(LegacyComparisonWorkflowCatalog.FixedTargets
|
||||
.Select(target => target.DisplayName));
|
||||
foreach (var action in directActions)
|
||||
{
|
||||
lines.Add($"[{action.Label}]");
|
||||
}
|
||||
|
||||
lines.Add($"[{returnActions[0].Section}]");
|
||||
lines.Add(directActions[0].Label);
|
||||
lines.AddRange(returnActions.Select(action => action.Label));
|
||||
WriteCp949(
|
||||
System.IO.Path.Combine(directory.Path, "Res", "종목비교.ini"),
|
||||
lines);
|
||||
|
||||
var composition = LegacyRuntimeUiCatalogLoader.ResolveFromBaseDirectory(
|
||||
directory.Path);
|
||||
|
||||
var warning = Assert.Single(composition.Warnings);
|
||||
Assert.Equal("종목비교.ini", warning.FileName);
|
||||
Assert.Equal(LegacyRuntimeUiCatalogFailure.UnknownEntry, warning.Failure);
|
||||
Assert.Equal(
|
||||
LegacyComparisonWorkflowCatalog.Actions.Select(action => action.ActionId),
|
||||
composition.ComparisonLayout.CreateActionViews(null, null)
|
||||
.Select(action => action.ActionId));
|
||||
}
|
||||
|
||||
private static void WriteAllValidFiles(
|
||||
string baseDirectory,
|
||||
IReadOnlyDictionary<LegacyFixedMarket, LegacyFixedAction[]>? fixedOrders = null,
|
||||
IReadOnlyList<LegacyIndustryActionDefinition>? kospi = null,
|
||||
IReadOnlyList<LegacyIndustryActionDefinition>? kosdaq = null,
|
||||
IReadOnlyList<LegacyComparisonTarget>? targets = null,
|
||||
IReadOnlyList<LegacyComparisonActionDefinition>? directActions = null,
|
||||
IReadOnlyList<LegacyComparisonActionDefinition>? returnActions = null,
|
||||
bool returnSectionFirst = false)
|
||||
{
|
||||
Directory.CreateDirectory(System.IO.Path.Combine(baseDirectory, "Res"));
|
||||
foreach (var market in Enum.GetValues<LegacyFixedMarket>())
|
||||
{
|
||||
WriteFixedFile(
|
||||
baseDirectory,
|
||||
market,
|
||||
fixedOrders is not null && fixedOrders.TryGetValue(market, out var ordered)
|
||||
? ordered
|
||||
: LegacyFixedActionCatalog.Default.GetActions(market));
|
||||
}
|
||||
|
||||
WriteIndustryFile(
|
||||
baseDirectory,
|
||||
IndustryMarket.Kospi,
|
||||
kospi ?? LegacyIndustryActionCatalog.GetActions(IndustryMarket.Kospi));
|
||||
WriteIndustryFile(
|
||||
baseDirectory,
|
||||
IndustryMarket.Kosdaq,
|
||||
kosdaq ?? LegacyIndustryActionCatalog.GetActions(IndustryMarket.Kosdaq));
|
||||
WriteComparisonFile(
|
||||
baseDirectory,
|
||||
targets ?? LegacyComparisonWorkflowCatalog.FixedTargets,
|
||||
directActions ?? LegacyComparisonWorkflowCatalog.Actions
|
||||
.Where(action => action.Kind != LegacyComparisonActionKind.Return)
|
||||
.ToArray(),
|
||||
returnActions ?? LegacyComparisonWorkflowCatalog.Actions
|
||||
.Where(action => action.Kind == LegacyComparisonActionKind.Return)
|
||||
.ToArray(),
|
||||
returnSectionFirst);
|
||||
}
|
||||
|
||||
private static void WriteFixedFile(
|
||||
string baseDirectory,
|
||||
LegacyFixedMarket market,
|
||||
IReadOnlyList<LegacyFixedAction> actions,
|
||||
Func<LegacyFixedAction, int, string>? labelSelector = null,
|
||||
bool appendDuplicate = false)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
var rowIndex = 0;
|
||||
foreach (var section in actions.GroupBy(action => action.Section, StringComparer.Ordinal))
|
||||
{
|
||||
lines.Add($"[{section.Key}]");
|
||||
foreach (var action in section)
|
||||
{
|
||||
lines.Add(labelSelector?.Invoke(action, rowIndex) ?? action.Label);
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
if (appendDuplicate)
|
||||
{
|
||||
lines.Add(section.First().Label);
|
||||
appendDuplicate = false;
|
||||
}
|
||||
}
|
||||
|
||||
WriteCp949(
|
||||
System.IO.Path.Combine(baseDirectory, "Res", FixedFileName(market)),
|
||||
lines);
|
||||
}
|
||||
|
||||
private static IEnumerable<LegacyFixedAction> FlattenForIni(
|
||||
IEnumerable<LegacyFixedAction> actions) =>
|
||||
actions.GroupBy(action => action.Section, StringComparer.Ordinal)
|
||||
.SelectMany(group => group);
|
||||
|
||||
private static void WriteIndustryFile(
|
||||
string baseDirectory,
|
||||
IndustryMarket market,
|
||||
IReadOnlyList<LegacyIndustryActionDefinition> actions)
|
||||
{
|
||||
var section = market == IndustryMarket.Kospi ? "코스피" : "코스닥";
|
||||
WriteCp949(
|
||||
System.IO.Path.Combine(baseDirectory, "Res", IndustryFileName(market)),
|
||||
[$"[{section}]", .. actions.Select(action => action.Label)]);
|
||||
}
|
||||
|
||||
private static void WriteComparisonFile(
|
||||
string baseDirectory,
|
||||
IReadOnlyList<LegacyComparisonTarget> targets,
|
||||
IReadOnlyList<LegacyComparisonActionDefinition> directActions,
|
||||
IReadOnlyList<LegacyComparisonActionDefinition> returnActions,
|
||||
bool returnSectionFirst)
|
||||
{
|
||||
var lines = new List<string> { "[지수]" };
|
||||
lines.AddRange(targets.Select(target => target.DisplayName));
|
||||
void AddDirectSections()
|
||||
{
|
||||
foreach (var action in directActions)
|
||||
{
|
||||
lines.Add($"[{action.Label}]");
|
||||
}
|
||||
}
|
||||
|
||||
void AddReturnSection()
|
||||
{
|
||||
lines.Add($"[{returnActions[0].Section}]");
|
||||
lines.AddRange(returnActions.Select(action => action.Label));
|
||||
}
|
||||
|
||||
if (returnSectionFirst)
|
||||
{
|
||||
AddReturnSection();
|
||||
AddDirectSections();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddDirectSections();
|
||||
AddReturnSection();
|
||||
}
|
||||
|
||||
WriteCp949(
|
||||
System.IO.Path.Combine(baseDirectory, "Res", "종목비교.ini"),
|
||||
lines);
|
||||
}
|
||||
|
||||
private static void WriteCp949(string path, IEnumerable<string> lines) =>
|
||||
File.WriteAllText(path, string.Join("\r\n", lines) + "\r\n", Cp949);
|
||||
|
||||
private static void AssertRuntimeFixedFile(
|
||||
string baseDirectory,
|
||||
string fileName,
|
||||
LegacyFixedMarket market,
|
||||
int expectedCount,
|
||||
LegacyFixedActionCatalog catalog)
|
||||
{
|
||||
var document = ReadTestIni(baseDirectory, fileName);
|
||||
var actions = catalog.GetActions(market);
|
||||
Assert.Equal(expectedCount, actions.Count);
|
||||
Assert.Equal(
|
||||
document.SelectMany(section => section.Rows),
|
||||
actions.Select(action => action.Label));
|
||||
Assert.Equal(
|
||||
document.SelectMany(section => section.Rows.Select(_ => section.Name)),
|
||||
actions.Select(action => action.Section));
|
||||
}
|
||||
|
||||
private static void AssertRuntimeIndustryFile(
|
||||
string baseDirectory,
|
||||
string fileName,
|
||||
IndustryMarket market,
|
||||
LegacyIndustryActionLayout layout)
|
||||
{
|
||||
var section = Assert.Single(ReadTestIni(baseDirectory, fileName));
|
||||
var actions = layout.GetActions(market);
|
||||
Assert.Equal(LegacyIndustryActionCatalog.ActionsPerMarket, actions.Count);
|
||||
Assert.Equal(section.Rows, actions.Select(action => action.Label));
|
||||
Assert.All(actions, action => Assert.Equal(section.Name, action.Section));
|
||||
}
|
||||
|
||||
private static void AssertRuntimeComparisonFile(
|
||||
string baseDirectory,
|
||||
LegacyComparisonUiLayout layout)
|
||||
{
|
||||
var document = ReadTestIni(baseDirectory, "종목비교.ini");
|
||||
Assert.Equal(6, document.Count);
|
||||
Assert.Equal(24, layout.FixedTargets.Count);
|
||||
Assert.Equal(
|
||||
document[0].Rows,
|
||||
layout.FixedTargets.Select(target => target.DisplayName));
|
||||
var expectedActions = document.Skip(1).SelectMany(section =>
|
||||
section.Rows.Count == 0
|
||||
? new[] { (Label: section.Name, Section: section.Name) }
|
||||
: section.Rows.Select(row => (Label: row, Section: section.Name)));
|
||||
var actualActions = layout.CreateActionViews(null, null);
|
||||
Assert.Equal(9, actualActions.Count);
|
||||
Assert.Equal(
|
||||
expectedActions,
|
||||
actualActions.Select(action => (action.Label, action.Section)));
|
||||
}
|
||||
|
||||
private static IReadOnlyList<TestIniSection> ReadTestIni(
|
||||
string baseDirectory,
|
||||
string fileName)
|
||||
{
|
||||
var sections = new List<TestIniSection>();
|
||||
List<string>? rows = null;
|
||||
foreach (var source in File.ReadAllLines(
|
||||
System.IO.Path.Combine(baseDirectory, "Res", fileName),
|
||||
Cp949))
|
||||
{
|
||||
var line = source.Trim();
|
||||
if (line.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] == '[' && line[^1] == ']')
|
||||
{
|
||||
rows = [];
|
||||
sections.Add(new TestIniSection(line[1..^1].Trim(), rows));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(rows);
|
||||
rows.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
private static string FixedFileName(LegacyFixedMarket market) => market switch
|
||||
{
|
||||
LegacyFixedMarket.Overseas => "해외.ini",
|
||||
LegacyFixedMarket.Exchange => "환율.ini",
|
||||
LegacyFixedMarket.Index => "지수.ini",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(market), market, null)
|
||||
};
|
||||
|
||||
private static string IndustryFileName(IndustryMarket market) => market switch
|
||||
{
|
||||
IndustryMarket.Kospi => "업종_코스피.ini",
|
||||
IndustryMarket.Kosdaq => "업종_코스닥.ini",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(market), market, null)
|
||||
};
|
||||
|
||||
private static Encoding CreateCp949Encoding()
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
return Encoding.GetEncoding(
|
||||
949,
|
||||
EncoderFallback.ExceptionFallback,
|
||||
DecoderFallback.ExceptionFallback);
|
||||
}
|
||||
|
||||
private sealed class EmptyIndustrySelectionService : IIndustrySelectionService
|
||||
{
|
||||
public Task<IReadOnlyList<IndustrySelection>> GetAsync(
|
||||
IndustryMarket market,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult<IReadOnlyList<IndustrySelection>>(
|
||||
Array.Empty<IndustrySelection>());
|
||||
}
|
||||
|
||||
private sealed class EmptyComparisonDataService : ILegacyComparisonDataService
|
||||
{
|
||||
public Task<IReadOnlyList<LegacyComparisonTarget>> SearchDomesticAsync(
|
||||
string query,
|
||||
int maximumResults,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult<IReadOnlyList<LegacyComparisonTarget>>(
|
||||
Array.Empty<LegacyComparisonTarget>());
|
||||
|
||||
public Task<IReadOnlyList<LegacyComparisonTarget>> SearchWorldAsync(
|
||||
string query,
|
||||
int maximumResults,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult<IReadOnlyList<LegacyComparisonTarget>>(
|
||||
Array.Empty<LegacyComparisonTarget>());
|
||||
|
||||
public Task<LegacyComparisonTarget?> ResolveStoredTargetAsync(
|
||||
LegacyComparisonStoredTargetReference target,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult<LegacyComparisonTarget?>(null);
|
||||
}
|
||||
|
||||
private sealed class EmptyComparisonPairStore : ILegacyComparisonPairStore
|
||||
{
|
||||
public Task<byte[]?> ReadCp949Async(
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult<byte[]?>(null);
|
||||
|
||||
public Task WriteCp949Async(
|
||||
IReadOnlyList<LegacyComparisonPairWriteRow> rows,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
private sealed class TemporaryDirectory : IDisposable
|
||||
{
|
||||
public TemporaryDirectory()
|
||||
{
|
||||
Path = System.IO.Path.Combine(
|
||||
System.IO.Path.GetTempPath(),
|
||||
$"MBN_STOCK_WEBVIEW_UI_INI_{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(Path);
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(Path))
|
||||
{
|
||||
Directory.Delete(Path, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record TestIniSection(string Name, IReadOnlyList<string> Rows);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
|
||||
|
||||
public sealed class LegacyDbSmokeRuntimeSelectionContractTests
|
||||
{
|
||||
private static readonly string RepositoryRoot = FindRepositoryRoot();
|
||||
private static readonly string Program = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot,
|
||||
"tools",
|
||||
"MBN_STOCK_WEBVIEW.DbSmoke",
|
||||
"Program.cs"));
|
||||
private static readonly string NxtAudit = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot,
|
||||
"tools",
|
||||
"MBN_STOCK_WEBVIEW.DbSmoke",
|
||||
"NxtThemeRestoreDbAudit.cs"));
|
||||
|
||||
[Fact]
|
||||
public void SelectedLegacyRuntimeEndpointIsInjectedIntoNxtAudit()
|
||||
{
|
||||
Assert.Contains(
|
||||
"var iniPath = Path.Combine(root, \"Res\", \"MmoneyCoder.ini\");",
|
||||
Program,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"new LegacyIniDatabaseOptionsLoader(_ => null).Load(iniPath)",
|
||||
Program,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"MariaDbEndpointReference.From(selectedOptions.MariaDb)",
|
||||
Program,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"selection.ReferenceMariaDb",
|
||||
Program,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"MariaDbEndpointReference referenceOptions",
|
||||
NxtAudit,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"referenceOptions.Host",
|
||||
NxtAudit,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"referenceOptions.Port",
|
||||
NxtAudit,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"referenceOptions.Database",
|
||||
NxtAudit,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonRuntimeEndpointIsCapturedBeforeEnvironmentOverrides()
|
||||
{
|
||||
Assert.Contains(
|
||||
"new DatabaseOptionsLoader(_ => null).Load(configurationPath)",
|
||||
Program,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"var runtime = DatabaseRuntime.CreateDefault(configurationPath);",
|
||||
Program,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"HasCompleteEndpointIdentity(configuredMariaDb)",
|
||||
Program,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"MariaDbEndpointReference.From(configuredMariaDb)",
|
||||
Program,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"MariaDbEndpointReference.From(runtime.Options.MariaDb)",
|
||||
Program,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NxtAuditHasNoMachineSpecificOriginalConfigurationDependency()
|
||||
{
|
||||
Assert.DoesNotContain("OriginalConfigurationPath", NxtAudit, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("ReadOriginalEndpoint", NxtAudit, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("MBN_STOCK_N", NxtAudit, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("MmoneyCoder.ini", NxtAudit, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InjectedReferenceContainsEndpointIdentityOnly()
|
||||
{
|
||||
Assert.Contains(
|
||||
"internal sealed record MariaDbEndpointReference(",
|
||||
NxtAudit,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("string? Host", NxtAudit, StringComparison.Ordinal);
|
||||
Assert.Contains("int Port", NxtAudit, StringComparison.Ordinal);
|
||||
Assert.Contains("string? Database", NxtAudit, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("UserName", NxtAudit, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("Password", NxtAudit, StringComparison.Ordinal);
|
||||
|
||||
Assert.Contains("originalSha256=", NxtAudit, StringComparison.Ordinal);
|
||||
Assert.Contains("currentSha256=", NxtAudit, StringComparison.Ordinal);
|
||||
Assert.Contains("configuredEqual=", NxtAudit, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static string FindRepositoryRoot()
|
||||
{
|
||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (current is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(current.FullName, "MBN_STOCK_WEBVIEW.sln")))
|
||||
{
|
||||
return current.FullName;
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("Could not locate the repository root.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
|
||||
|
||||
public sealed class LegacyRuntimeAssetDeploymentContractTests
|
||||
{
|
||||
private static readonly string RepositoryRoot = FindRepositoryRoot();
|
||||
private static readonly string ProjectPath = Path.Combine(
|
||||
RepositoryRoot,
|
||||
"src",
|
||||
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
||||
"MBN_STOCK_WEBVIEW.LegacyParityApp.csproj");
|
||||
|
||||
[Fact]
|
||||
public void LegacyParityProjectLinksCutsAndResIntoOutputPublishAndMsix()
|
||||
{
|
||||
var project = XDocument.Load(ProjectPath);
|
||||
var contents = project.Descendants("Content").ToArray();
|
||||
|
||||
AssertLinkedRuntimeTree(contents, "$(LegacyCutsSourceRoot)\\**\\*", "Cuts\\");
|
||||
AssertLinkedRuntimeTree(contents, "@(LegacyPackagedResAsset)", "Res\\");
|
||||
|
||||
var packagedResNames = project.Descendants("LegacyPackagedResAsset")
|
||||
.Select(element => Path.GetFileName(element.Attribute("Include")?.Value))
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
string[] requiredUiMenus =
|
||||
[
|
||||
"setup.ini",
|
||||
"기타.ini",
|
||||
"업종.ini",
|
||||
"업종_코스닥.ini",
|
||||
"업종_코스피.ini",
|
||||
"종목.ini",
|
||||
"종목_0111.ini",
|
||||
"종목비교.ini",
|
||||
"지수.ini",
|
||||
"지수_0318.ini",
|
||||
"지수_250401.ini",
|
||||
"해외.ini",
|
||||
"환율.ini"
|
||||
];
|
||||
|
||||
Assert.Equal(34, packagedResNames.Count);
|
||||
Assert.All(requiredUiMenus, name => Assert.Contains(name, packagedResNames));
|
||||
Assert.DoesNotContain("MmoneyCoder.ini", packagedResNames);
|
||||
Assert.DoesNotContain("MmoneyCoder - 복사본.ini", packagedResNames);
|
||||
Assert.DoesNotContain("MmoneyCoder.ini.bak", packagedResNames);
|
||||
Assert.DoesNotContain("MmoneyCoder.zip", packagedResNames);
|
||||
Assert.DoesNotContain("afiedt.buf.txt", packagedResNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveDatabaseIniIsPostBuildOutputOnlyAndNeverAPackagingItem()
|
||||
{
|
||||
var project = XDocument.Load(ProjectPath);
|
||||
Assert.DoesNotContain(
|
||||
project.Descendants().Where(element =>
|
||||
element.Name.LocalName is "Content" or "None"),
|
||||
element => (element.Attribute("Include")?.Value ?? string.Empty)
|
||||
.Contains("MmoneyCoder.ini", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var upToDateInput = Assert.Single(project.Descendants("UpToDateCheckInput"));
|
||||
Assert.Equal(
|
||||
"$(LegacyResSourceRoot)\\MmoneyCoder.ini",
|
||||
upToDateInput.Attribute("Include")?.Value);
|
||||
var upToDateBuilt = Assert.Single(project.Descendants("UpToDateCheckBuilt"));
|
||||
Assert.Equal(
|
||||
"$(TargetDir)Res\\MmoneyCoder.ini",
|
||||
upToDateBuilt.Attribute("Include")?.Value);
|
||||
|
||||
var copyTarget = Assert.Single(
|
||||
project.Descendants("Target"),
|
||||
element => string.Equals(
|
||||
element.Attribute("Name")?.Value,
|
||||
"CopyLegacyDatabaseIniToLocalOutput",
|
||||
StringComparison.Ordinal));
|
||||
Assert.Equal("Build", copyTarget.Attribute("AfterTargets")?.Value);
|
||||
Assert.Contains(
|
||||
"GenerateAppxPackageOnBuild",
|
||||
copyTarget.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"PublishAppxPackage",
|
||||
copyTarget.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Equal(
|
||||
"$(LegacyResSourceRoot)\\MmoneyCoder.ini",
|
||||
copyTarget.Attribute("Inputs")?.Value);
|
||||
Assert.Equal(
|
||||
"$(TargetDir)Res\\MmoneyCoder.ini",
|
||||
copyTarget.Attribute("Outputs")?.Value);
|
||||
var copy = Assert.Single(copyTarget.Elements("Copy"));
|
||||
Assert.Equal(
|
||||
"$(LegacyResSourceRoot)\\MmoneyCoder.ini",
|
||||
copy.Attribute("SourceFiles")?.Value);
|
||||
Assert.Equal(
|
||||
"$(TargetDir)Res\\MmoneyCoder.ini",
|
||||
copy.Attribute("DestinationFiles")?.Value);
|
||||
var fileWrite = Assert.Single(copyTarget.Descendants("FileWrites"));
|
||||
Assert.Equal(
|
||||
"$(TargetDir)Res\\MmoneyCoder.ini",
|
||||
fileWrite.Attribute("Include")?.Value);
|
||||
|
||||
var cleanTarget = Assert.Single(
|
||||
project.Descendants("Target"),
|
||||
element => string.Equals(
|
||||
element.Attribute("Name")?.Value,
|
||||
"RemoveLegacyDatabaseIniFromLocalOutput",
|
||||
StringComparison.Ordinal));
|
||||
Assert.Equal("Clean", cleanTarget.Attribute("BeforeTargets")?.Value);
|
||||
var delete = Assert.Single(cleanTarget.Elements("Delete"));
|
||||
Assert.Equal(
|
||||
"$(TargetDir)Res\\MmoneyCoder.ini",
|
||||
delete.Attribute("Files")?.Value);
|
||||
|
||||
var rejectionTarget = Assert.Single(
|
||||
project.Descendants("Target"),
|
||||
element => string.Equals(
|
||||
element.Attribute("Name")?.Value,
|
||||
"RejectLegacyDatabaseIniFromPackagePayload",
|
||||
StringComparison.Ordinal));
|
||||
Assert.Equal(
|
||||
"_ComputeAppxPackagePayload",
|
||||
rejectionTarget.Attribute("BeforeTargets")?.Value);
|
||||
Assert.Single(rejectionTarget.Elements("Error"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LegacyRuntimeSourceRootIsOverrideableAndValidatedBeforeBuild()
|
||||
{
|
||||
var project = XDocument.Load(ProjectPath);
|
||||
var sourceRoot = Assert.Single(
|
||||
project.Descendants("LegacyRuntimeSourceRoot"),
|
||||
element => element.Attribute("Condition") is not null);
|
||||
|
||||
Assert.Equal("'$(LegacyRuntimeSourceRoot)' == ''", sourceRoot.Attribute("Condition")!.Value);
|
||||
Assert.Contains("MBN_STOCK_N\\MBN_STOCK_N\\bin\\Debug", sourceRoot.Value,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
var target = Assert.Single(
|
||||
project.Descendants("Target"),
|
||||
element => string.Equals(
|
||||
element.Attribute("Name")?.Value,
|
||||
"ValidateLegacyRuntimeAssets",
|
||||
StringComparison.Ordinal));
|
||||
Assert.Equal("PrepareForBuild", target.Attribute("BeforeTargets")?.Value);
|
||||
|
||||
var conditions = target.Elements("Error")
|
||||
.Select(error => error.Attribute("Condition")?.Value)
|
||||
.Where(condition => condition is not null)
|
||||
.ToArray();
|
||||
string[] requiredSentinels =
|
||||
[
|
||||
"$(LegacyRuntimeSourceRoot)",
|
||||
"$(LegacyCutsSourceRoot)",
|
||||
"$(LegacyResSourceRoot)",
|
||||
"$(LegacyCutsSourceRoot)\\5001.t2s",
|
||||
"$(LegacyResSourceRoot)\\MmoneyCoder.ini",
|
||||
"$(LegacyResSourceRoot)\\종목.ini",
|
||||
"$(LegacyResSourceRoot)\\업종_코스피.ini",
|
||||
"$(LegacyResSourceRoot)\\업종_코스닥.ini",
|
||||
"$(LegacyResSourceRoot)\\해외.ini",
|
||||
"$(LegacyResSourceRoot)\\환율.ini",
|
||||
"$(LegacyResSourceRoot)\\지수.ini"
|
||||
];
|
||||
|
||||
Assert.All(requiredSentinels, sentinel =>
|
||||
Assert.Contains(conditions, condition =>
|
||||
condition!.Contains(sentinel, StringComparison.Ordinal)));
|
||||
|
||||
var databaseIniError = Assert.Single(
|
||||
target.Elements("Error"),
|
||||
error => (error.Attribute("Condition")?.Value ?? string.Empty)
|
||||
.Contains("$(LegacyResSourceRoot)\\MmoneyCoder.ini", StringComparison.Ordinal));
|
||||
Assert.Contains(
|
||||
"GenerateAppxPackageOnBuild",
|
||||
databaseIniError.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"PublishAppxPackage",
|
||||
databaseIniError.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RepositoryDoesNotContainCopiedLegacyRuntimeTrees()
|
||||
{
|
||||
Assert.False(Directory.Exists(Path.Combine(RepositoryRoot, "Cuts")));
|
||||
Assert.False(Directory.Exists(Path.Combine(RepositoryRoot, "Res")));
|
||||
Assert.False(Directory.Exists(Path.Combine(
|
||||
RepositoryRoot,
|
||||
"src",
|
||||
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
||||
"Cuts")));
|
||||
Assert.False(Directory.Exists(Path.Combine(
|
||||
RepositoryRoot,
|
||||
"src",
|
||||
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
||||
"Res")));
|
||||
}
|
||||
|
||||
private static void AssertLinkedRuntimeTree(
|
||||
IReadOnlyCollection<XElement> contents,
|
||||
string include,
|
||||
string targetPrefix)
|
||||
{
|
||||
var content = Assert.Single(
|
||||
contents,
|
||||
element => string.Equals(
|
||||
element.Attribute("Include")?.Value,
|
||||
include,
|
||||
StringComparison.Ordinal));
|
||||
|
||||
Assert.StartsWith(targetPrefix, content.Element("Link")?.Value, StringComparison.Ordinal);
|
||||
Assert.StartsWith(
|
||||
targetPrefix,
|
||||
content.Element("TargetPath")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Equal("PreserveNewest", content.Element("CopyToOutputDirectory")?.Value);
|
||||
Assert.Equal("PreserveNewest", content.Element("CopyToPublishDirectory")?.Value);
|
||||
}
|
||||
|
||||
private static string FindRepositoryRoot()
|
||||
{
|
||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (current is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(current.FullName, "MBN_STOCK_WEBVIEW.sln")))
|
||||
{
|
||||
return current.FullName;
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("Could not locate the repository root.");
|
||||
}
|
||||
}
|
||||
@@ -38,32 +38,37 @@ public sealed class LegacyRuntimeStockCutMenuNativeContractTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DatabaseInitializationErrorWinsOverTheSingleCutMenuFallbackWarning()
|
||||
public void DatabaseInitializationErrorWinsOverCombinedRuntimeCatalogWarnings()
|
||||
{
|
||||
var errorBranch = MainWindow.IndexOf(
|
||||
"if (initializationError is not null)",
|
||||
StringComparison.Ordinal);
|
||||
var warningBranch = MainWindow.IndexOf(
|
||||
"else if (cutMenuComposition.WarningMessage is { } cutMenuWarning)",
|
||||
"cutMenuComposition.WarningMessage,",
|
||||
errorBranch,
|
||||
StringComparison.Ordinal);
|
||||
var runtimeUiWarning = MainWindow.IndexOf(
|
||||
"runtimeUiCatalogs.WarningMessage",
|
||||
warningBranch,
|
||||
StringComparison.Ordinal);
|
||||
var errorReport = MainWindow.IndexOf(
|
||||
"_controller.ReportError(initializationError);",
|
||||
errorBranch,
|
||||
StringComparison.Ordinal);
|
||||
var warningReport = MainWindow.IndexOf(
|
||||
"_controller.ReportWarning(cutMenuWarning);",
|
||||
"_controller.ReportWarning(string.Join(\" \", catalogWarnings));",
|
||||
warningBranch,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
Assert.True(errorBranch >= 0);
|
||||
Assert.True(errorReport > errorBranch);
|
||||
Assert.True(warningBranch > errorReport);
|
||||
Assert.True(runtimeUiWarning > warningBranch);
|
||||
Assert.True(warningReport > warningBranch);
|
||||
Assert.Equal(
|
||||
warningReport,
|
||||
MainWindow.LastIndexOf(
|
||||
"_controller.ReportWarning(cutMenuWarning);",
|
||||
"_controller.ReportWarning(string.Join(\" \", catalogWarnings));",
|
||||
StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,39 @@ public sealed class PlayoutOptionsLoaderTests
|
||||
Assert.Null(options.MaximumAutomaticRefreshesPerTakeIn);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyExecutableAssetDefaults_UsesCutsBesideExecutable()
|
||||
{
|
||||
var directory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
||||
var cuts = Path.Combine(directory, "Cuts");
|
||||
Directory.CreateDirectory(cuts);
|
||||
try
|
||||
{
|
||||
var options = new PlayoutOptions();
|
||||
|
||||
PlayoutOptionsLoader.ApplyExecutableAssetDefaults(options, directory);
|
||||
|
||||
Assert.Equal(Path.GetFullPath(cuts), options.SceneDirectory);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(directory, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyExecutableAssetDefaults_DoesNotReplaceExplicitSceneDirectory()
|
||||
{
|
||||
var options = new PlayoutOptions
|
||||
{
|
||||
SceneDirectory = "C:\\explicit-cuts"
|
||||
};
|
||||
|
||||
PlayoutOptionsLoader.ApplyExecutableAssetDefaults(options, "C:\\runtime");
|
||||
|
||||
Assert.Equal("C:\\explicit-cuts", options.SceneDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_EnvironmentOverridesJson_ExceptFileOnlySafetyGates()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user