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"); var sourceOnlyStart = app.IndexOf("#if SOURCE_ONLY_RUNTIME", StringComparison.Ordinal); var fullRuntimeStart = app.IndexOf("#else", sourceOnlyStart, StringComparison.Ordinal); var runtimeEnd = app.IndexOf("#endif", fullRuntimeStart, StringComparison.Ordinal); Assert.True(sourceOnlyStart >= 0); Assert.True(fullRuntimeStart > sourceOnlyStart); Assert.True(runtimeEnd > fullRuntimeStart); var sourceOnly = app[sourceOnlyStart..fullRuntimeStart]; var fullRuntime = app[fullRuntimeStart..runtimeEnd]; Assert.Contains("new FirstRunSetupWindow()", sourceOnly, StringComparison.Ordinal); Assert.DoesNotContain("new MainWindow()", sourceOnly, StringComparison.Ordinal); Assert.DoesNotContain( "DevelopmentLiveLaunchBootstrap.TryApply(", sourceOnly, StringComparison.Ordinal); Assert.Contains("DevelopmentLiveLaunchBootstrap.TryApply(", fullRuntime, StringComparison.Ordinal); Assert.Contains("new MainWindow()", fullRuntime, StringComparison.Ordinal); Assert.DoesNotContain("new FirstRunSetupWindow()", fullRuntime, 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.Contains("기존 Live 설정 교체 후 다시 시도", 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 BothFolderSelectionsValidateCanonicalPathsAndTheSelectedDatabaseIni() { var setup = File.ReadAllText(Path.Combine( AppRoot, "FirstRunSetupWindow.xaml.cs")); Assert.Contains( "LegacyOperatorSettingsFolderKind.Scene", setup, StringComparison.Ordinal); Assert.Contains( "LegacyOperatorSettingsFolderKind.Resource", setup, StringComparison.Ordinal); Assert.Contains( "Path.Combine(resource, \"MmoneyCoder.ini\")", setup, StringComparison.Ordinal); Assert.Contains("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); } [Fact] public void SetupRunsTheVerifiedInitializerAndVerifiesEveryDurableOutput() { var setup = File.ReadAllText(Path.Combine( AppRoot, "FirstRunSetupWindow.xaml.cs")); var initializer = File.ReadAllText(Path.Combine( RepositoryRoot, "scripts", "Initialize-ExistingDevelopmentPc.ps1")); Assert.Contains( "await RunVerifiedSetupAsync(", setup, StringComparison.Ordinal); Assert.Contains( "new ProcessStartInfo", setup, StringComparison.Ordinal); Assert.Contains( "UseShellExecute = false", setup, StringComparison.Ordinal); Assert.Contains( "CreateNoWindow = true", setup, StringComparison.Ordinal); Assert.Contains( "RedirectStandardOutput = true", setup, StringComparison.Ordinal); Assert.Contains( "RedirectStandardError = true", setup, StringComparison.Ordinal); Assert.Contains( "startInfo.ArgumentList.Add(value)", setup, StringComparison.Ordinal); Assert.Contains( "Initialize-ExistingDevelopmentPc.ps1", setup, StringComparison.Ordinal); Assert.Contains( "AddArgument(\"-SkipDatabaseProfile\")", setup, StringComparison.Ordinal); Assert.Contains( "AddArgument(\"-ConfigureDevelopmentLive\")", setup, StringComparison.Ordinal); Assert.Contains( "AddArgument(\"127.0.0.1\")", setup, StringComparison.Ordinal); Assert.Contains( "AddArgument(\"30001\")", setup, StringComparison.Ordinal); Assert.Contains( "AddArgument(\"-PinRegisteredK3D\")", setup, StringComparison.Ordinal); Assert.Contains( "if (replaceLiveConfig)", setup, StringComparison.Ordinal); Assert.Contains( "AddArgument(\"-ReplaceLiveConfig\")", setup, StringComparison.Ordinal); Assert.DoesNotContain( "AddArgument(\"-ReplaceRuntimeBinding\")", setup, StringComparison.Ordinal); Assert.DoesNotContain( "AddArgument(\"-ReplaceK3DPin\")", setup, StringComparison.Ordinal); Assert.Contains( "CaptureOutputTailAsync(process.StandardOutput)", setup, StringComparison.Ordinal); Assert.Contains( "CaptureOutputTailAsync(process.StandardError)", setup, StringComparison.Ordinal); Assert.Contains( "MaximumCapturedOutputCharacters = 16 * 1024", setup, StringComparison.Ordinal); Assert.Contains( "RunSetupFromUiAsync(replaceLiveConfig: false)", setup, StringComparison.Ordinal); Assert.Contains( "RunSetupFromUiAsync(replaceLiveConfig: true)", setup, StringComparison.Ordinal); Assert.Contains( "MBN_SETUP_LIVE_CONFIG_REPLACEMENT_REQUIRED", setup, StringComparison.Ordinal); Assert.Contains( "[MBN_SETUP_LIVE_CONFIG_REPLACEMENT_REQUIRED]", initializer, StringComparison.Ordinal); Assert.Contains( "loaded.Settings.SceneDirectory", setup, StringComparison.Ordinal); Assert.Contains( "loaded.Settings.ResourceDirectory", setup, StringComparison.Ordinal); Assert.Contains( "Directory.Build.local.props", setup, StringComparison.Ordinal); Assert.Contains( "\"k3d-pins.local.json\"", setup, StringComparison.Ordinal); Assert.Contains( "\"playout.local.json\"", setup, StringComparison.Ordinal); Assert.Contains( "\"playout.development-live.local.json\"", setup, StringComparison.Ordinal); Assert.DoesNotContain( "Environment.SetEnvironmentVariable", setup, StringComparison.Ordinal); } [Fact] public void SourceOnlyBuildEmbedsOnlyItsOwnClonePathForTheLocalBinding() { 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 SolutionDeclaresTheVisualStudioWinUiWorkload() { var configuration = File.ReadAllText(Path.Combine( RepositoryRoot, ".vsconfig")); Assert.Contains( "\"Microsoft.VisualStudio.Workload.Universal\"", configuration, 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.IndexOf( "Write-ProtectedUtf8TextAtomically", pinPreflight, StringComparison.Ordinal); var livePreflight = initializer.IndexOf( "$livePreflight = Get-DevelopmentLivePreflight", pinWrite, 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."); } }