feat: add two-folder first-run playout setup
This commit is contained in:
@@ -81,10 +81,16 @@ public sealed class LegacyDevelopmentLiveStartupContractTests
|
||||
var bootstrap = app.IndexOf(
|
||||
"DevelopmentLiveLaunchBootstrap.TryApply(",
|
||||
StringComparison.Ordinal);
|
||||
var constructWindow = app.IndexOf("_window = new MainWindow();", StringComparison.Ordinal);
|
||||
var constructSetupWindow = app.IndexOf(
|
||||
"new FirstRunSetupWindow()",
|
||||
StringComparison.Ordinal);
|
||||
var constructMainWindow = app.IndexOf(
|
||||
"new MainWindow();",
|
||||
StringComparison.Ordinal);
|
||||
|
||||
Assert.True(bootstrap >= 0);
|
||||
Assert.True(constructWindow > bootstrap);
|
||||
Assert.True(constructSetupWindow > bootstrap);
|
||||
Assert.True(constructMainWindow > constructSetupWindow);
|
||||
Assert.Contains("#if DEBUG", app, StringComparison.Ordinal);
|
||||
Assert.Contains("const bool isDebugBuild = true;", app, StringComparison.Ordinal);
|
||||
Assert.Contains("const bool isDebugBuild = false;", app, StringComparison.Ordinal);
|
||||
@@ -106,6 +112,10 @@ public sealed class LegacyDevelopmentLiveStartupContractTests
|
||||
"IsDevelopmentLiveLaunch = developmentLive.IsApplied;",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"_window = showFirstRunSetup",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
|
||||
|
||||
public sealed class LegacyFirstRunSetupNativeContractTests
|
||||
{
|
||||
private static readonly string RepositoryRoot = FindRepositoryRoot();
|
||||
private static readonly string AppRoot = Path.Combine(
|
||||
RepositoryRoot,
|
||||
"src",
|
||||
"MBN_STOCK_WEBVIEW.LegacyParityApp");
|
||||
|
||||
[Fact]
|
||||
public void SourceOnlyLaunchShowsOnlyTheNativeFirstRunWindow()
|
||||
{
|
||||
var app = File.ReadAllText(Path.Combine(AppRoot, "App.xaml.cs"))
|
||||
.ReplaceLineEndings("\n");
|
||||
|
||||
Assert.Contains(
|
||||
"var showFirstRunSetup = isSourceOnlyBuild && isDebugBuild;\n" +
|
||||
" _window = showFirstRunSetup\n" +
|
||||
" ? new FirstRunSetupWindow()\n" +
|
||||
" : new MainWindow();",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"var launchArguments = isSourceOnlyBuild\n" +
|
||||
" ? null",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"new FirstRunSetupWindow(App.IsDevelopmentLiveLaunch",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstRunUiRequestsOnlyCutsAndTheWholeResFolder()
|
||||
{
|
||||
var xaml = XDocument.Load(Path.Combine(
|
||||
AppRoot,
|
||||
"FirstRunSetupWindow.xaml"));
|
||||
XNamespace presentation =
|
||||
"http://schemas.microsoft.com/winfx/2006/xaml/presentation";
|
||||
var buttons = xaml.Descendants(presentation + "Button")
|
||||
.Select(element => element.Attribute("Content")?.Value)
|
||||
.Where(value => value is not null)
|
||||
.ToArray();
|
||||
var source = File.ReadAllText(Path.Combine(
|
||||
AppRoot,
|
||||
"FirstRunSetupWindow.xaml"));
|
||||
|
||||
Assert.Contains("Cuts 폴더 선택", buttons);
|
||||
Assert.Contains("Res 폴더 선택", buttons);
|
||||
Assert.DoesNotContain("INI 파일 선택", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("비밀번호", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("포트 입력", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("해시 입력", source, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BothFoldersAndAllActiveIniContractsAreValidatedBeforeSetup()
|
||||
{
|
||||
var setup = File.ReadAllText(Path.Combine(
|
||||
AppRoot,
|
||||
"FirstRunSetupWindow.xaml.cs"));
|
||||
var settings = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot,
|
||||
"src",
|
||||
"MBN_STOCK_WEBVIEW.LegacyApplication",
|
||||
"LegacyOperatorSettings.cs"));
|
||||
|
||||
Assert.Contains(
|
||||
"LegacyOperatorSettingsFolderKind.Scene",
|
||||
setup,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"LegacyOperatorSettingsFolderKind.Resource",
|
||||
setup,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"validation.Folder.DatabaseIniDetected",
|
||||
setup,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"new LegacyIniDatabaseOptionsLoader",
|
||||
setup,
|
||||
StringComparison.Ordinal);
|
||||
var clearCuts = setup.IndexOf(
|
||||
"_cutsDirectory = null;",
|
||||
StringComparison.Ordinal);
|
||||
var validateCuts = setup.IndexOf(
|
||||
"LegacyOperatorSettingsFolderKind.Scene",
|
||||
StringComparison.Ordinal);
|
||||
var clearResource = setup.IndexOf(
|
||||
"_resourceDirectory = null;",
|
||||
StringComparison.Ordinal);
|
||||
var validateResource = setup.IndexOf(
|
||||
"LegacyOperatorSettingsFolderKind.Resource",
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(clearCuts >= 0);
|
||||
Assert.True(validateCuts > clearCuts);
|
||||
Assert.True(clearResource > validateCuts);
|
||||
Assert.True(validateResource > clearResource);
|
||||
Assert.Contains(
|
||||
"Path.GetFileName(cuts),\n" +
|
||||
" \"Cuts\"",
|
||||
setup.ReplaceLineEndings("\n"),
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Path.GetFileName(resource),\n" +
|
||||
" \"Res\"",
|
||||
setup.ReplaceLineEndings("\n"),
|
||||
StringComparison.Ordinal);
|
||||
|
||||
foreach (var ini in new[]
|
||||
{
|
||||
"종목.ini",
|
||||
"업종_코스피.ini",
|
||||
"업종_코스닥.ini",
|
||||
"해외.ini",
|
||||
"환율.ini",
|
||||
"지수.ini",
|
||||
"종목비교.ini"
|
||||
})
|
||||
{
|
||||
Assert.Contains($"\"{ini}\"", settings, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetupUsesArgumentListAndPinsTheVerifiedLocalK3dAtTheDefaultEndpoint()
|
||||
{
|
||||
var setup = File.ReadAllText(Path.Combine(
|
||||
AppRoot,
|
||||
"FirstRunSetupWindow.xaml.cs"));
|
||||
|
||||
Assert.Contains(
|
||||
"startInfo.ArgumentList.Add(argument);",
|
||||
setup,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("\"-NoFolderPicker\"", setup, StringComparison.Ordinal);
|
||||
Assert.Contains("\"-DatabaseIniPath\"", setup, StringComparison.Ordinal);
|
||||
Assert.Contains("\"-ConfigureDevelopmentLive\"", setup, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"private const string DefaultPlayoutHost = \"127.0.0.1\";",
|
||||
setup,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"private const int DefaultPlayoutPort = 30001;",
|
||||
setup,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("\"-PinRegisteredK3D\"", setup, StringComparison.Ordinal);
|
||||
Assert.Contains("\"-ReplaceRuntimeBinding\"", setup, StringComparison.Ordinal);
|
||||
Assert.Contains("\"-ReplaceDatabaseProfile\"", setup, StringComparison.Ordinal);
|
||||
Assert.Contains("\"-ReplaceLiveConfig\"", setup, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"result.LocalConfigurationReplacementRequired",
|
||||
setup,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"await ConfirmLocalConfigurationReplacementAsync()",
|
||||
setup,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("\"-ReplaceK3DPin\"", setup, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("\"-SkipBuild\"", setup, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SourceOnlyBuildEmbedsOnlyItsOwnClonePathForTheReviewedSetupScript()
|
||||
{
|
||||
var project = XDocument.Load(Path.Combine(
|
||||
AppRoot,
|
||||
"MBN_STOCK_WEBVIEW.LegacyParityApp.csproj"));
|
||||
var metadata = Assert.Single(project.Descendants("AssemblyMetadata"));
|
||||
|
||||
Assert.Equal("SourceRepositoryRoot", metadata.Attribute("Include")?.Value);
|
||||
Assert.Equal("$(SourceRepositoryRoot)", metadata.Attribute("Value")?.Value);
|
||||
Assert.Contains(
|
||||
"'$(LegacyRuntimeAssetsEnabled)' != 'true'",
|
||||
metadata.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"'$(Configuration)' == 'Debug'",
|
||||
metadata.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AutomaticK3dPinningIsPersistentAndCannotApproveAChangedDll()
|
||||
{
|
||||
var initializer = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot,
|
||||
"scripts",
|
||||
"Initialize-ExistingDevelopmentPc.ps1"));
|
||||
|
||||
Assert.Contains("[switch] $PinRegisteredK3D", initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("[switch] $ReplaceK3DPin", initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Join-Path $ConfigurationRoot 'k3d-pins.local.json'",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"$approvedNativeSha256 = $actualNativeHash",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"$approvedInteropSha256 = $actualInteropHash",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"-PinRegisteredK3D cannot be combined with independently",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Automatic pinning cannot approve a changed DLL.",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"-ReplaceK3DPin cannot be combined with -PinRegisteredK3D.",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"A replacement requires independently supplied native and interop hashes.",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Write-ProtectedUtf8TextAtomically",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"This is a durable trust anchor, not transactional runtime state.",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"K3D persistent pin rollback failed:",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
var pinPreflight = initializer.LastIndexOf(
|
||||
"$k3dPinPreflight = Get-K3DPinPreflight",
|
||||
StringComparison.Ordinal);
|
||||
var pinWrite = initializer.LastIndexOf(
|
||||
"Write-ProtectedUtf8TextAtomically",
|
||||
StringComparison.Ordinal);
|
||||
var livePreflight = initializer.LastIndexOf(
|
||||
"$livePreflight = Get-DevelopmentLivePreflight",
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(pinPreflight >= 0);
|
||||
Assert.True(pinWrite > pinPreflight);
|
||||
Assert.True(livePreflight > pinWrite);
|
||||
Assert.Contains(
|
||||
"nativeSha256 = $ApprovedNativeHash.ToUpperInvariant()",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"interopSha256 = $ApprovedInteropHash.ToUpperInvariant()",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Get-VerifiedK3DRegistration -Reports $registrationReports",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("Activator.CreateInstance", initializer,
|
||||
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("Repository root was not found.");
|
||||
}
|
||||
}
|
||||
@@ -342,11 +342,11 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
|
||||
sourceOnlyMessage.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"source-only DryRun",
|
||||
"safe first-run setup app",
|
||||
sourceOnlyMessage.Attribute("Text")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Initialize-ExistingDevelopmentPc.ps1",
|
||||
"select the existing Cuts and Res folders",
|
||||
sourceOnlyMessage.Attribute("Text")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
@@ -434,7 +434,7 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
|
||||
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,
|
||||
Assert.Contains(".first-run-runtime-not-configured-0F72B3C8", sourceRoot.Value,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
var target = Assert.Single(
|
||||
@@ -787,7 +787,19 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
Assert.Contains("& dotnet build $ProjectPath -c Debug '-p:Platform=x64'",
|
||||
Assert.Contains("& $DotNetPath build $ProjectPath",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"'-p:LegacyRuntimeAssetsMode=Required'",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"$runtimeRootProperty = \"-p:LegacyRuntimeSourceRoot=$ExpectedRuntimeRoot\"",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Get-Command -Name 'dotnet.exe' -CommandType Application",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
@@ -808,23 +820,31 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
|
||||
var runtimeWrite = initializer.LastIndexOf(
|
||||
"Set-RuntimeBindingAtomically",
|
||||
StringComparison.Ordinal);
|
||||
var pinWrite = initializer.LastIndexOf(
|
||||
"Write-ProtectedUtf8TextAtomically",
|
||||
StringComparison.Ordinal);
|
||||
var databaseWrite = initializer.LastIndexOf(
|
||||
"Copy-DatabaseProfileAtomically",
|
||||
StringComparison.Ordinal);
|
||||
var build = initializer.LastIndexOf(
|
||||
"Invoke-DebugBuildAndVerify",
|
||||
StringComparison.Ordinal);
|
||||
var cleanup = initializer.LastIndexOf(
|
||||
"-Path $rollbackRoot",
|
||||
var finalK3dVerification = initializer.LastIndexOf(
|
||||
"$finalNativeHash = (",
|
||||
StringComparison.Ordinal);
|
||||
var liveWrite = initializer.LastIndexOf(
|
||||
"& $liveInitializerPath @liveArguments",
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(runtimeWrite >= 0);
|
||||
Assert.True(databaseWrite > runtimeWrite);
|
||||
Assert.True(build > databaseWrite);
|
||||
Assert.True(cleanup > build);
|
||||
Assert.True(liveWrite > cleanup);
|
||||
var cleanup = initializer.LastIndexOf(
|
||||
"-Path $rollbackRoot",
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(pinWrite >= 0);
|
||||
Assert.True(build > pinWrite);
|
||||
Assert.True(databaseWrite > build);
|
||||
Assert.True(finalK3dVerification > databaseWrite);
|
||||
Assert.True(liveWrite > finalK3dVerification);
|
||||
Assert.True(runtimeWrite > liveWrite);
|
||||
Assert.True(cleanup > runtimeWrite);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -856,6 +876,26 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
|
||||
"$Description rollback did not restore the exact bytes and ACL.",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"'Local\\MBN_STOCK_WEBVIEW.ExistingDevelopmentPcInitialization.v1'",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Another existing-development-PC initialization is already running.",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Development Live base configuration rollback failed:",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"The durable pin remains at",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"anchor could not be restored:",
|
||||
initializer,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Set-UserOnlyDirectoryAcl -Path $rollbackRoot",
|
||||
initializer,
|
||||
@@ -968,8 +1008,11 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
|
||||
var buildVerification = initializer.LastIndexOf(
|
||||
"Invoke-DebugBuildAndVerify",
|
||||
StringComparison.Ordinal);
|
||||
var setupCleanup = initializer.LastIndexOf(
|
||||
"-Path $rollbackRoot",
|
||||
var pinIssue = initializer.LastIndexOf(
|
||||
"Write-ProtectedUtf8TextAtomically",
|
||||
StringComparison.Ordinal);
|
||||
var finalK3dVerification = initializer.LastIndexOf(
|
||||
"$finalNativeHash = (",
|
||||
StringComparison.Ordinal);
|
||||
var liveIssue = initializer.LastIndexOf(
|
||||
"$liveResults = @(& $liveInitializerPath @liveArguments)",
|
||||
@@ -977,14 +1020,24 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
|
||||
var liveJsonVerification = initializer.LastIndexOf(
|
||||
"-ExpectedJson ([string] $livePreflight.AuthorizationJson)",
|
||||
StringComparison.Ordinal);
|
||||
var bindingCommit = initializer.LastIndexOf(
|
||||
"Set-RuntimeBindingAtomically",
|
||||
StringComparison.Ordinal);
|
||||
var setupCleanup = initializer.LastIndexOf(
|
||||
"-Path $rollbackRoot",
|
||||
StringComparison.Ordinal);
|
||||
var failClosedLiveRemoval = initializer.LastIndexOf(
|
||||
"Remove-DevelopmentLiveAuthorizationFailClosed",
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(firstPersistentLiveInvalidation >= 0);
|
||||
Assert.True(pinIssue >= 0);
|
||||
Assert.True(pinIssue < firstPersistentLiveInvalidation);
|
||||
Assert.True(firstPersistentLiveInvalidation < setupDirectoryCreation);
|
||||
Assert.True(buildVerification < setupCleanup);
|
||||
Assert.True(setupCleanup < liveIssue);
|
||||
Assert.True(setupDirectoryCreation < buildVerification);
|
||||
Assert.True(buildVerification < finalK3dVerification);
|
||||
Assert.True(finalK3dVerification < liveIssue);
|
||||
Assert.True(liveIssue < liveJsonVerification);
|
||||
Assert.True(liveJsonVerification < bindingCommit);
|
||||
Assert.True(bindingCommit < setupCleanup);
|
||||
Assert.True(liveJsonVerification < failClosedLiveRemoval);
|
||||
Assert.Contains(
|
||||
"Development Live initializer returned unexpected success-stream output.",
|
||||
|
||||
Reference in New Issue
Block a user