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."); } }