Files
YMhut-box-C-/src/YMhut.Box.Tests/StartupCheckTests.cs
T
QWQLwToo 6f20021da4
build-winui / winui (push) Waiting to run
Update package versions and tighten layout overflow
2026-06-30 12:42:59 +08:00

649 lines
30 KiB
C#

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Data.Sqlite;
using YMhut.Box.Core.App;
using YMhut.Box.Core.Startup;
namespace YMhut.Box.Tests;
[TestClass]
[DoNotParallelize]
public sealed class StartupCheckTests
{
[TestMethod]
public async Task FastPreflightPassesForPureLangInstallLayout()
{
using var workspace = TestWorkspace.Create("ymhut-startup-fast");
var installRoot = CreateHealthyInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var store = new StartupCheckStore(paths);
var service = new InstallIntegrityCheckService(paths, store);
var report = await service.RunFastPreflightAsync();
Assert.AreEqual(StartupCheckKind.FastPreflight, report.Kind);
Assert.AreEqual(0, report.CriticalIssueCount);
Assert.IsTrue(report.Items.Any(item => item.Id == "lang:zh-CN" && item.Status == StartupCheckStatus.Passed));
Assert.IsTrue(report.Items.Any(item => item.Id == "lang:en-US" && item.Status == StartupCheckStatus.Passed));
Assert.IsFalse(Directory.Exists(Path.Combine(appRoot, "Runtime")));
StringAssert.StartsWith(store.DatabasePath, Path.Combine(appRoot, "Logs"));
}
[TestMethod]
public async Task FastPreflightDoesNotBlockDevelopmentRootCultureLayout()
{
using var workspace = TestWorkspace.Create("ymhut-startup-dev-layout");
var installRoot = CreateDevelopmentInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));
var report = await service.RunFastPreflightAsync();
var resourcesPri = report.Items.FirstOrDefault(item => item.Id == "file:resources.pri");
var zhCn = report.Items.FirstOrDefault(item => item.Id == "lang:zh-CN");
var enUs = report.Items.FirstOrDefault(item => item.Id == "lang:en-US");
Assert.AreEqual(0, report.CriticalIssueCount);
Assert.IsNotNull(resourcesPri);
Assert.AreEqual(StartupCheckStatus.Missing, resourcesPri.Status);
Assert.AreEqual(StartupCheckSeverity.Warning, resourcesPri.Severity);
Assert.IsNotNull(zhCn);
Assert.AreEqual(StartupCheckStatus.Passed, zhCn.Status);
Assert.IsNotNull(enUs);
Assert.AreEqual(StartupCheckStatus.Passed, enUs.Status);
Assert.IsFalse(report.Items.Any(item => item.Id.StartsWith("lang:root:", StringComparison.OrdinalIgnoreCase)));
}
[TestMethod]
public async Task FastPreflightAcceptsWinUiDevelopmentPriAndRootCultureSatellites()
{
using var workspace = TestWorkspace.Create("ymhut-startup-dev-pri");
var installRoot = CreateDevelopmentInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
workspace.CreateFile(["install", "YMhutBox.pri"], string.Empty);
workspace.CreateFile(["install", "fr-FR", "Microsoft.ui.xaml.dll.mui"], string.Empty);
workspace.CreateFile(["install", "ja-JP", "Microsoft.ui.xaml.dll.mui"], string.Empty);
workspace.CreateFile(["install", "Tools", "Sample", "tool.exe"], string.Empty);
workspace.CreateFile(["install", "Metadata", "tools.json"], """{ "tools": [] }""");
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));
var report = await service.RunFastPreflightAsync();
Assert.AreEqual(0, report.VisibleIssueCount);
Assert.IsTrue(report.Items.Any(item => item.Id == "manifest" && item.Status == StartupCheckStatus.Skipped));
Assert.IsTrue(report.Items.Any(item => item.Id == "file:resources.pri" && item.Status == StartupCheckStatus.Passed));
Assert.IsTrue(report.Items.Any(item => item.Id == "lang:root-culture-summary" && item.Status == StartupCheckStatus.Skipped));
Assert.IsFalse(report.Items.Any(item => item.Id.StartsWith("lang:root:", StringComparison.OrdinalIgnoreCase)));
}
[TestMethod]
public async Task CurrentHistoryNormalizesLegacyDevelopmentLayoutFalsePositives()
{
using var workspace = TestWorkspace.Create("ymhut-startup-legacy-false-positive");
var installRoot = CreateDevelopmentInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
workspace.CreateFile(["install", "YMhutBox.pri"], string.Empty);
workspace.CreateFile(["install", "fr-FR", "Microsoft.ui.xaml.dll.mui"], string.Empty);
workspace.CreateFile(["install", "Tools", "Sample", "tool.exe"], string.Empty);
workspace.CreateFile(["install", "Metadata", "tools.json"], """{ "tools": [] }""");
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var store = new StartupCheckStore(paths);
var manifestIdentity = InstallRootContext.BuildManifestIdentity(installRoot);
var oldReport = StartupCheckReport.Create(
StartupCheckKind.FastPreflight,
new DateTimeOffset(2026, 6, 1, 8, 0, 0, TimeSpan.Zero),
installRoot,
manifestIdentity,
InstallRootContext.BuildInstallIdentity(installRoot, manifestIdentity),
$"installRoot={installRoot}; manifest={manifestIdentity}",
[
new StartupCheckItem("manifest", "安装清单", StartupCheckSeverity.Warning, StartupCheckStatus.Missing, "文件缺失", Path.Combine(installRoot, "config", "install-manifest.ini")),
new StartupCheckItem("file:resources.pri", "resources.pri", StartupCheckSeverity.Warning, StartupCheckStatus.Missing, "文件缺失", Path.Combine(installRoot, "resources.pri")),
new StartupCheckItem("lang:zh-CN", "语言资源 zh-CN", StartupCheckSeverity.Warning, StartupCheckStatus.Missing, "语言目录缺失", Path.Combine(installRoot, "lang", "zh-CN")),
new StartupCheckItem("lang:root:fr-FR", "根目录语言资源 fr-FR", StartupCheckSeverity.Warning, StartupCheckStatus.Failed, "旧版误判", Path.Combine(installRoot, "fr-FR"))
]);
await store.SaveAsync(oldReport);
var service = new InstallIntegrityCheckService(paths, store);
var latest = await service.GetLatestCurrentReportAsync(StartupCheckKind.FastPreflight);
var history = await service.GetCurrentHistoryAsync(10);
Assert.IsNotNull(latest);
Assert.AreEqual(0, latest.VisibleIssueCount);
Assert.AreEqual(StartupCheckStatus.Skipped, latest.Items.First(item => item.Id == "manifest").Status);
Assert.AreEqual(StartupCheckStatus.Passed, latest.Items.First(item => item.Id == "file:resources.pri").Status);
Assert.AreEqual(StartupCheckStatus.Passed, latest.Items.First(item => item.Id == "lang:zh-CN").Status);
Assert.AreEqual(StartupCheckStatus.Skipped, latest.Items.First(item => item.Id == "lang:root:fr-FR").Status);
Assert.AreEqual(0, history[0].VisibleIssueCount);
}
[TestMethod]
public async Task MonthlyIntegrityUsesRecentReportUntilManifestIdentityChanges()
{
using var workspace = TestWorkspace.Create("ymhut-startup-monthly");
var installRoot = CreateHealthyInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
var now = new DateTimeOffset(2026, 6, 14, 9, 0, 0, TimeSpan.Zero);
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var store = new StartupCheckStore(paths);
var service = new InstallIntegrityCheckService(paths, store, clock: () => now);
var first = await service.RunMonthlyIntegrityCheckAsync();
now = now.AddDays(5);
var cached = await service.RunMonthlyIntegrityCheckAsync();
Assert.AreEqual(first.Id, cached.Id);
File.AppendAllText(Path.Combine(installRoot, InstallManifest.RelativePath.Replace('/', Path.DirectorySeparatorChar)), Environment.NewLine + "; changed");
now = now.AddDays(1);
var changed = await service.RunMonthlyIntegrityCheckAsync();
Assert.AreNotEqual(first.Id, changed.Id);
}
[TestMethod]
public async Task MonthlyIntegrityDoesNotReuseReportFromDifferentInstallRoot()
{
using var workspace = TestWorkspace.Create("ymhut-startup-monthly-root");
var installRoot = CreateHealthyInstall(workspace);
var secondRoot = workspace.CreateDirectory("install2");
CopyDirectory(installRoot, secondRoot);
var appRoot = workspace.CreateDirectory("user");
var now = new DateTimeOffset(2026, 6, 14, 9, 0, 0, TimeSpan.Zero);
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var store = new StartupCheckStore(paths);
var service = new InstallIntegrityCheckService(paths, store, clock: () => now);
var first = await service.RunMonthlyIntegrityCheckAsync();
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, secondRoot);
now = now.AddDays(1);
var second = await service.RunMonthlyIntegrityCheckAsync();
Assert.AreNotEqual(first.Id, second.Id);
Assert.AreNotEqual(first.InstallIdentity, second.InstallIdentity);
}
[TestMethod]
public async Task IntegrityReportsMissingExternalToolLaunchableAsWarning()
{
using var workspace = TestWorkspace.Create("ymhut-startup-tool-missing");
var installRoot = CreateHealthyInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
Directory.CreateDirectory(Path.Combine(installRoot, "Tools", "Sample"));
File.WriteAllText(Path.Combine(installRoot, "Tools", "Sample", "present.exe"), string.Empty);
File.AppendAllText(
Path.Combine(installRoot, InstallManifest.RelativePath.Replace('/', Path.DirectorySeparatorChar)),
Environment.NewLine + "Tools/Sample/missing.exe");
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));
var report = await service.RunManualIntegrityCheckAsync();
var missing = report.Items.FirstOrDefault(item => item.Id == "file:Tools/Sample/missing.exe");
Assert.IsNotNull(missing);
Assert.AreEqual(StartupCheckStatus.Missing, missing.Status);
Assert.AreEqual(StartupCheckSeverity.Warning, missing.Severity);
}
[TestMethod]
public async Task UserDataRuntimePayloadIsRemovedBeforePreflight()
{
using var workspace = TestWorkspace.Create("ymhut-startup-userdata");
var installRoot = CreateHealthyInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
var runtimeRoot = Path.Combine(appRoot, "Runtime");
var runtimesRoot = Path.Combine(appRoot, "runtimes");
Directory.CreateDirectory(runtimeRoot);
Directory.CreateDirectory(runtimesRoot);
File.WriteAllText(Path.Combine(runtimeRoot, "YMhutBox.exe"), string.Empty);
File.WriteAllText(Path.Combine(runtimesRoot, "Microsoft.ui.xaml.dll"), string.Empty);
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
Assert.IsFalse(Directory.Exists(runtimeRoot));
Assert.IsFalse(Directory.Exists(runtimesRoot));
var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));
var report = await service.RunFastPreflightAsync();
var runtime = report.Items.FirstOrDefault(item => item.Id == "userdata:runtime");
Assert.IsNotNull(runtime);
Assert.AreEqual(StartupCheckStatus.Passed, runtime.Status);
Assert.IsFalse(Directory.Exists(runtimeRoot));
Assert.IsFalse(Directory.Exists(runtimesRoot));
}
[TestMethod]
public async Task UserDataToolsAndMetadataPayloadsAreRemovedBeforePreflight()
{
using var workspace = TestWorkspace.Create("ymhut-startup-userdata-tools");
var installRoot = CreateHealthyInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var toolsRoot = Path.Combine(appRoot, "Tools");
var metadataRoot = Path.Combine(appRoot, "Metadata");
Directory.CreateDirectory(toolsRoot);
Directory.CreateDirectory(metadataRoot);
File.WriteAllText(Path.Combine(toolsRoot, "YMhutBox.exe"), string.Empty);
File.WriteAllText(Path.Combine(metadataRoot, "tools.json"), "{}");
var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));
var report = await service.RunFastPreflightAsync();
var tools = report.Items.FirstOrDefault(item => item.Id == "userdata:tools");
var metadata = report.Items.FirstOrDefault(item => item.Id == "userdata:metadata");
Assert.IsNotNull(tools);
Assert.IsNotNull(metadata);
Assert.AreEqual(StartupCheckStatus.Passed, tools.Status);
Assert.AreEqual(StartupCheckStatus.Passed, metadata.Status);
Assert.IsFalse(Directory.Exists(toolsRoot));
Assert.IsFalse(Directory.Exists(metadataRoot));
Assert.AreEqual(0, report.VisibleIssueCount);
}
[TestMethod]
public async Task CurrentHistoryFiltersOutHistoricalInstallReports()
{
using var workspace = TestWorkspace.Create("ymhut-startup-current-history");
var installRoot = CreateHealthyInstall(workspace);
var secondRoot = workspace.CreateDirectory("install2");
CopyDirectory(installRoot, secondRoot);
var appRoot = workspace.CreateDirectory("user");
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var store = new StartupCheckStore(paths);
var service = new InstallIntegrityCheckService(paths, store);
var oldReport = await service.RunManualIntegrityCheckAsync();
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, secondRoot);
var currentReport = await service.RunManualIntegrityCheckAsync();
var all = await service.GetHistorySummariesAsync();
var current = await service.GetCurrentHistoryAsync();
Assert.IsTrue(all.Any(summary => summary.Id == oldReport.Id && !summary.IsCurrentInstall));
Assert.IsTrue(current.All(summary => summary.IsCurrentInstall));
Assert.IsTrue(current.Any(summary => summary.Id == currentReport.Id));
Assert.IsFalse(current.Any(summary => summary.Id == oldReport.Id));
}
[TestMethod]
public async Task RecheckItemReportsCurrentPathPassesAfterFileRestored()
{
using var workspace = TestWorkspace.Create("ymhut-startup-recheck");
var installRoot = CreateHealthyInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
var executable = Path.Combine(installRoot, "YMhutBox.exe");
File.Delete(executable);
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));
var report = await service.RunManualIntegrityCheckAsync();
var missing = report.Items.First(item => item.Id == "file:YMhutBox.exe");
File.WriteAllText(executable, string.Empty);
var rechecked = await service.RecheckItemAsync(report.Id, missing.Id);
Assert.IsNotNull(rechecked);
Assert.AreEqual(StartupCheckStatus.Passed, rechecked.Status);
StringAssert.Contains(rechecked.Detail, "old record");
}
[TestMethod]
public async Task IntegrityDoesNotTreatNormalRootFoldersAsCultureFolders()
{
using var workspace = TestWorkspace.Create("ymhut-startup-root-folders");
var installRoot = CreateHealthyInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
Directory.CreateDirectory(Path.Combine(installRoot, "Assets"));
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var service = new InstallIntegrityCheckService(paths, new StartupCheckStore(paths));
var report = await service.RunManualIntegrityCheckAsync();
Assert.IsFalse(report.Items.Any(item => string.Equals(item.Id, "lang:root:Assets", StringComparison.OrdinalIgnoreCase)));
Assert.IsFalse(report.Items.Any(item => string.Equals(item.Id, "lang:root:Tools", StringComparison.OrdinalIgnoreCase)));
}
[TestMethod]
public async Task StartupCheckStorePersistsHistoryAndLatestReport()
{
using var workspace = TestWorkspace.Create("ymhut-startup-store");
var installRoot = CreateHealthyInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var store = new StartupCheckStore(paths);
var service = new InstallIntegrityCheckService(paths, store);
var report = await service.RunManualIntegrityCheckAsync();
var latest = await store.GetLatestReportAsync();
var history = await store.GetHistoryAsync();
var summaries = await store.GetHistorySummariesAsync();
var loaded = await store.GetReportAsync(report.Id);
Assert.IsNotNull(latest);
Assert.AreEqual(report.Id, latest.Id);
Assert.IsGreaterThanOrEqualTo(history.Count, 1);
Assert.IsNotEmpty(history[0].Items);
Assert.IsGreaterThanOrEqualTo(summaries.Count, 1);
Assert.AreEqual(report.Id, summaries[0].Id);
Assert.AreEqual(report.Items.Count, summaries[0].ItemCount);
Assert.AreEqual(report.IssueCount, summaries[0].IssueCount);
Assert.IsNotNull(loaded);
Assert.AreEqual(report.Id, loaded.Id);
Assert.IsNotEmpty(loaded.Items);
}
[TestMethod]
public async Task StartupCheckStoreMigratesLegacyDatabaseIntoMainSqlite()
{
using var workspace = TestWorkspace.Create("ymhut-startup-store-migrate");
var installRoot = CreateHealthyInstall(workspace);
var appRoot = workspace.CreateDirectory("user");
using var environment = EnvironmentScope.Capture(InstallLayoutPaths.InstallRootEnvironmentVariable);
environment.Set(InstallLayoutPaths.InstallRootEnvironmentVariable, installRoot);
var paths = AppPaths.ForCurrentUser(appRoot);
var legacyPath = Path.Combine(paths.Data, "startup-checks.db");
Directory.CreateDirectory(Path.GetDirectoryName(legacyPath)!);
await using (var connection = new SqliteConnection($"Data Source={legacyPath};Pooling=False"))
{
connection.Open();
await using var command = connection.CreateCommand();
command.CommandText = """
CREATE TABLE startup_check_reports (
id TEXT PRIMARY KEY,
kind TEXT NOT NULL,
started_at TEXT NOT NULL,
completed_at TEXT NOT NULL,
install_root TEXT NOT NULL,
manifest_identity TEXT NOT NULL,
issue_count INTEGER NOT NULL,
critical_issue_count INTEGER NOT NULL,
warning_issue_count INTEGER NOT NULL
);
CREATE TABLE startup_check_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
report_id TEXT NOT NULL,
item_id TEXT NOT NULL,
name TEXT NOT NULL,
severity TEXT NOT NULL,
status TEXT NOT NULL,
detail TEXT NOT NULL,
path TEXT NULL
);
INSERT INTO startup_check_reports(
id, kind, started_at, completed_at, install_root, manifest_identity,
issue_count, critical_issue_count, warning_issue_count)
VALUES(
'11111111111111111111111111111111',
'FastPreflight',
'2026-06-01T00:00:00.0000000+00:00',
'2026-06-01T00:00:01.0000000+00:00',
'C:\Legacy',
'legacy',
1,
0,
1);
INSERT INTO startup_check_items(report_id, item_id, name, severity, status, detail, path)
VALUES(
'11111111111111111111111111111111',
'legacy:item',
'旧记录',
'Warning',
'Failed',
'从旧库导入',
NULL);
""";
await command.ExecuteNonQueryAsync();
}
var store = new StartupCheckStore(paths);
var latest = await store.GetLatestReportAsync();
Assert.IsNotNull(latest);
Assert.AreEqual("legacy", latest.ManifestIdentity);
Assert.HasCount(1, latest.Items);
Assert.IsFalse(File.Exists(legacyPath));
Assert.AreEqual(Path.Combine(paths.Logs, AppDatabasePaths.MainDatabaseFileName), store.DatabasePath);
}
[TestMethod]
public async Task StartupInitializationPipelineReportsMonotonicWeightedProgress()
{
var progress = new List<StartupInitializationStageProgress>();
var pipeline = new StartupInitializationPipeline([
new StartupInitializationStage("first", "First", 1, true, (context, token) =>
{
context.Report(0.5, "Half");
return Task.CompletedTask;
}),
new StartupInitializationStage("second", "Second", 3, true, (context, token) =>
{
context.Report(0.25, "Quarter");
context.Report(0.75, "Almost");
return Task.CompletedTask;
})
]);
await pipeline.RunAsync(new InlineProgress<StartupInitializationStageProgress>(progress.Add));
Assert.IsNotEmpty(progress);
Assert.AreEqual(100, progress[^1].Progress);
for (var index = 1; index < progress.Count; index++)
{
Assert.IsGreaterThanOrEqualTo(
progress[index - 1].Progress,
progress[index].Progress,
$"Progress moved backwards at {index}: {progress[index - 1].Progress} -> {progress[index].Progress}");
}
CollectionAssert.AreEqual(new[] { "first", "second" }, pipeline.Stages.Select(stage => stage.Id).ToArray());
}
[TestMethod]
public void StartupSplashMessageSerializesStableContract()
{
var message = new StartupSplashMessage(
"progress",
"正在检查",
42,
"fast-preflight",
"快速预检",
8,
10,
"检查关键文件",
"warning",
IsRepairStep: true,
Theme: "dark",
ReducedMotion: true);
var json = message.ToJson();
StringAssert.Contains(json, "\"type\": \"progress\"");
StringAssert.Contains(json, "\"stageId\": \"fast-preflight\"");
StringAssert.Contains(json, "\"isRepairStep\": true");
StringAssert.Contains(json, "\"reducedMotion\": true");
}
private static string CreateHealthyInstall(TestWorkspace workspace)
{
var installRoot = workspace.CreateDirectory("install");
workspace.CreateFile(["install", "YMhutBox.exe"], string.Empty);
workspace.CreateFile(["install", "YMhutBox.dll"], string.Empty);
workspace.CreateFile(["install", "resources.pri"], string.Empty);
workspace.CreateFile(["install", "lang", "zh-CN", "Microsoft.ui.xaml.dll.mui"], string.Empty);
workspace.CreateFile(["install", "lang", "en-US", "Microsoft.ui.xaml.dll.mui"], string.Empty);
workspace.CreateFile(["install", "Tools", "Sample", "tool.exe"], string.Empty);
workspace.CreateFile(["install", "Metadata", "tools.json"], """{ "tools": [] }""");
workspace.CreateFile(["install", "config", "install-manifest.ini"],
"""
[Release]
Version=2.0.6
Build=1
Channel=stable
PackageVersion=2.0.6.1
[RequiredFiles]
YMhutBox.exe
YMhutBox.dll
resources.pri
[Files]
YMhutBox.exe
YMhutBox.dll
resources.pri
lang/zh-CN/Microsoft.ui.xaml.dll.mui
lang/en-US/Microsoft.ui.xaml.dll.mui
Tools/Sample/tool.exe
Metadata/tools.json
""");
return installRoot;
}
private static string CreateDevelopmentInstall(TestWorkspace workspace)
{
var installRoot = workspace.CreateDirectory("install");
workspace.CreateFile(["install", "YMhutBox.exe"], string.Empty);
workspace.CreateFile(["install", "YMhutBox.dll"], string.Empty);
workspace.CreateFile(["install", "zh-CN", "Microsoft.ui.xaml.dll.mui"], string.Empty);
workspace.CreateFile(["install", "en-US", "Microsoft.ui.xaml.dll.mui"], string.Empty);
return installRoot;
}
private static void CopyDirectory(string source, string destination)
{
Directory.CreateDirectory(destination);
foreach (var directory in Directory.EnumerateDirectories(source, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(Path.Combine(destination, Path.GetRelativePath(source, directory)));
}
foreach (var file in Directory.EnumerateFiles(source, "*", SearchOption.AllDirectories))
{
var target = Path.Combine(destination, Path.GetRelativePath(source, file));
Directory.CreateDirectory(Path.GetDirectoryName(target)!);
File.Copy(file, target, overwrite: true);
}
}
private sealed class EnvironmentScope : IDisposable
{
private readonly Dictionary<string, string?> _snapshot;
private EnvironmentScope(IEnumerable<string> names)
{
_snapshot = names.ToDictionary(name => name, Environment.GetEnvironmentVariable, StringComparer.Ordinal);
}
public static EnvironmentScope Capture(params string[] names) => new(names);
public void Set(string name, string? value) => Environment.SetEnvironmentVariable(name, value);
public void Dispose()
{
foreach (var item in _snapshot)
{
Environment.SetEnvironmentVariable(item.Key, item.Value);
}
}
}
private sealed class InlineProgress<T>(Action<T> report) : IProgress<T>
{
public void Report(T value) => report(value);
}
private sealed class TestWorkspace : IDisposable
{
private TestWorkspace(string root)
{
Root = root;
}
public string Root { get; }
public static TestWorkspace Create(string prefix)
{
var root = Path.Combine(Path.GetTempPath(), prefix, Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(root);
return new TestWorkspace(root);
}
public string CreateDirectory(params string[] parts)
{
var directory = Path.Combine([Root, .. parts]);
Directory.CreateDirectory(directory);
return directory;
}
public string CreateFile(string[] parts, string contents)
{
var path = Path.Combine([Root, .. parts]);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
File.WriteAllText(path, contents);
return path;
}
public void Dispose()
{
if (Directory.Exists(Root))
{
Directory.Delete(Root, recursive: true);
}
}
}
}