456 lines
16 KiB
C#
456 lines
16 KiB
C#
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 SourceOnlyLaunchUsesSetupAndFullRuntimeMayRecoverBeforeMainWindow()
|
|
{
|
|
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.Contains("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 RecoveryPrefillsValidatedStoredFoldersAndRequiresExplicitReplacement()
|
|
{
|
|
var setup = File.ReadAllText(Path.Combine(
|
|
AppRoot,
|
|
"FirstRunSetupWindow.xaml.cs"))
|
|
.ReplaceLineEndings("\n");
|
|
|
|
Assert.Contains(
|
|
"RestoreStoredRuntimeFolders();",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"var loaded = new LegacyOperatorSettingsStore().Load();",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"loaded.Failure != LegacyOperatorSettingsStoreFailure.None",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"loaded.Settings.SceneDirectory",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"loaded.Settings.ResourceDirectory",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"_liveConfigReplacementAvailable =\n" +
|
|
" liveConfigReplacementAvailable &&\n" +
|
|
" IsVerifiedSetupAvailable();",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"liveConfigReplacementAvailable && _runtimeFoldersReady",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"await RunSetupFromUiAsync(replaceLiveConfig: true);",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"internal static bool IsVerifiedSetupAvailable() =>\n" +
|
|
" TryResolveRepositoryRoot(out _);",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"SingleOrDefault(attribute => string.Equals(",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"if (!File.Exists(project))",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"if (replaceLiveConfig)\n" +
|
|
" {\n" +
|
|
" AddArgument(\"-ReplaceLiveConfig\");",
|
|
setup,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain(
|
|
"RunSetupFromUiAsync(replaceLiveConfig: true);",
|
|
setup[
|
|
setup.IndexOf("internal FirstRunSetupWindow(", StringComparison.Ordinal)..
|
|
setup.IndexOf(
|
|
"private async void OnChooseCutsClicked",
|
|
StringComparison.Ordinal)],
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void DebugBuildEmbedsItsOwnClonePathForFirstRunAndRecovery()
|
|
{
|
|
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.Equal(
|
|
"'$(Configuration)' == 'Debug'",
|
|
metadata.Attribute("Condition")?.Value);
|
|
}
|
|
|
|
[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.");
|
|
}
|
|
}
|