Add launcher debug settings, recovery & version fixes

Introduce a persistent LauncherDebugSettingsStore and wire it into ErrorWindow and SplashWindow so dev-mode and custom host path can be saved/loaded. Harden DeploymentLocator/FlexibleHostLocator to safely normalize and validate saved debug paths and log warnings for malformed values. Add a WaitingForShell startup state and recoverable-activation logic across App and LauncherFlowCoordinator (with registry updates) so Launcher can attach to an in-progress desktop shell rather than failing. Clean up ErrorDebugWindow UI/flow (WasAccepted flag, localization fixes, event wiring) and improve splash version population. Improve AppVersionProvider to trim surrounding quotes, robustly parse version.json via JsonDocument and read string properties; add unit tests for AppVersionProvider, DeploymentLocator and LauncherDebugSettingsStore. Also quote Exec commands in the csproj and harden scripts/Generate-VersionFile.ps1 (argument normalization, LiteralPath, error handling).
This commit is contained in:
lincube
2026-04-23 19:04:39 +08:00
parent 2d9391f930
commit d4901e436f
17 changed files with 742 additions and 198 deletions

View File

@@ -0,0 +1,83 @@
using LanMountainDesktop.Shared.Contracts.Launcher;
using Xunit;
namespace LanMountainDesktop.Tests;
public sealed class AppVersionProviderTests
{
[Fact]
public void ResolveFromPackageRoot_WhenVersionJsonExists_UsesVersionFile()
{
using var temp = TemporaryPackage.Create();
temp.CreateDeployment("app-0.8.5.7", """
{"Version":"0.8.5.7","Codename":"Administrate"}
""");
var info = AppVersionProvider.ResolveFromPackageRoot(temp.Root, "LanMountainDesktop.exe");
Assert.Equal("0.8.5.7", info.Version);
Assert.Equal("Administrate", info.Codename);
}
[Fact]
public void ResolveFromPackageRoot_WhenVersionJsonIsMissing_FallsBackToDeploymentDirectory()
{
using var temp = TemporaryPackage.Create();
temp.CreateDeployment("app-0.8.5.7");
var info = AppVersionProvider.ResolveFromPackageRoot(temp.Root, "LanMountainDesktop.exe");
Assert.Equal("0.8.5.7", info.Version);
}
[Fact]
public void ResolveFromPackageRoot_WhenVersionJsonContainsQuotedValues_NormalizesValues()
{
using var temp = TemporaryPackage.Create();
temp.CreateDeployment("app-1.2.3", """
{"Version":"'1.2.3'","Codename":"'Administrate'"}
""");
var info = AppVersionProvider.ResolveFromPackageRoot(temp.Root, "LanMountainDesktop.exe");
Assert.Equal("1.2.3", info.Version);
Assert.Equal("Administrate", info.Codename);
}
private sealed class TemporaryPackage : IDisposable
{
private TemporaryPackage(string root)
{
Root = root;
}
public string Root { get; }
public static TemporaryPackage Create()
{
var root = Path.Combine(Path.GetTempPath(), "LanMountainDesktop.VersionTests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(root);
return new TemporaryPackage(root);
}
public void CreateDeployment(string name, string? versionJson = null)
{
var deployment = Path.Combine(Root, name);
Directory.CreateDirectory(deployment);
File.WriteAllText(Path.Combine(deployment, "LanMountainDesktop.exe"), string.Empty);
File.WriteAllText(Path.Combine(deployment, ".current"), string.Empty);
if (versionJson is not null)
{
File.WriteAllText(Path.Combine(deployment, "version.json"), versionJson);
}
}
public void Dispose()
{
if (Directory.Exists(Root))
{
Directory.Delete(Root, recursive: true);
}
}
}
}

View File

@@ -0,0 +1,43 @@
using LanMountainDesktop.Launcher;
using LanMountainDesktop.Launcher.Services;
using Xunit;
namespace LanMountainDesktop.Tests;
[Collection("LauncherDebugSettingsStore")]
public sealed class DeploymentLocatorTests : IDisposable
{
private readonly string _appRoot;
private readonly string _configRoot;
public DeploymentLocatorTests()
{
var testRoot = Path.Combine(Path.GetTempPath(), "LanMountainDesktop.DeploymentLocatorTests", Guid.NewGuid().ToString("N"));
_appRoot = Path.Combine(testRoot, "app-root");
_configRoot = Path.Combine(testRoot, "config");
Directory.CreateDirectory(_appRoot);
Directory.CreateDirectory(_configRoot);
LauncherDebugSettingsStore.ConfigBaseDirectoryOverride = _configRoot;
}
[Fact]
public void ResolveHostExecutable_WhenSavedDebugPathIsMalformed_DoesNotThrow()
{
LauncherDebugSettingsStore.Save(new LauncherDebugSettings(true, "bad\0path"));
var locator = new DeploymentLocator(_appRoot);
var result = locator.ResolveHostExecutable(CommandContext.FromArgs(["launch", "--debug"]));
Assert.NotEqual("debug_saved_custom_path", result.ResolutionSource);
}
public void Dispose()
{
LauncherDebugSettingsStore.ConfigBaseDirectoryOverride = null;
var testRoot = Directory.GetParent(_appRoot)?.FullName;
if (!string.IsNullOrWhiteSpace(testRoot) && Directory.Exists(testRoot))
{
Directory.Delete(testRoot, recursive: true);
}
}
}

View File

@@ -0,0 +1,50 @@
using LanMountainDesktop.Launcher.Services;
using Xunit;
namespace LanMountainDesktop.Tests;
[Collection("LauncherDebugSettingsStore")]
public sealed class LauncherDebugSettingsStoreTests : IDisposable
{
private readonly string _tempDirectory;
public LauncherDebugSettingsStoreTests()
{
_tempDirectory = Path.Combine(Path.GetTempPath(), "LanMountainDesktop.DebugSettingsTests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_tempDirectory);
LauncherDebugSettingsStore.ConfigBaseDirectoryOverride = _tempDirectory;
}
[Fact]
public void Load_WhenOnlyLegacyFilesExist_ReadsLegacySettings()
{
var customPath = Path.Combine(_tempDirectory, "legacy-host.exe");
File.WriteAllText(Path.Combine(_tempDirectory, "devmode.config"), "1");
File.WriteAllText(Path.Combine(_tempDirectory, "custom-host-path.config"), customPath);
var settings = LauncherDebugSettingsStore.Load();
Assert.True(settings.DevModeEnabled);
Assert.Equal(customPath, settings.CustomHostPath);
}
[Fact]
public void Save_WritesNewSettingsFiles()
{
var customPath = Path.Combine(_tempDirectory, "host.exe");
LauncherDebugSettingsStore.Save(new LauncherDebugSettings(true, customPath));
Assert.Equal("True", File.ReadAllText(Path.Combine(_tempDirectory, "dev-mode.flag")).Trim());
Assert.Equal(customPath, File.ReadAllText(Path.Combine(_tempDirectory, "custom-host-path.txt")).Trim());
}
public void Dispose()
{
LauncherDebugSettingsStore.ConfigBaseDirectoryOverride = null;
if (Directory.Exists(_tempDirectory))
{
Directory.Delete(_tempDirectory, recursive: true);
}
}
}