Files
chuzhongzai 545d96174d 修复登录自启的权限问题:低完整性目录、日志兜底与数据目录自检
登录任务本身没问题(InteractiveToken + HighestAvailable + --minimized),
ICACLS 也没有拒绝 ACE;真因是 exe 所在的 D:\dsh workspace 带 Low 强制标签,
Windows 用低完整性令牌运行这里的 exe,它写不了中完整性的 %LOCALAPPDATA%,
于是第一步写日志就 UnauthorizedAccessException,托盘起不来。

- FileLog 改为 fail-safe:首选目录失败依次回退 LocalLow / TEMP / ProgramData,
  缓存待写行,永不抛异常,暴露 ActiveDirectory 与 LastFailure;
- 启动自检增加数据目录可写性探测,致命对话框写出真实日志目录与低完整性判断;
- 新增 --logcheck 只读自检:输出程序路径、数据目录可写性、实际日志目录与结论;
- README 增加放置目录说明,validation.md 记录第八轮;
- 新增两条 FileLog 回归测试(本地 49/49,探测 16/16,Release 0 警告 0 错误)。
2026-10-24 11:18:09 +08:00

82 lines
7.6 KiB
C#

namespace GatewayKeepalive.Tests;
internal static class Program
{
private static async Task<int> Main()
{
var tests = new (string Name, Func<Task> Run)[]
{
("SwitchPolicy: success threshold, cap, and recovery cooldown", SwitchPolicyTests.SuccessThresholdAndCooldownAsync),
("SwitchPolicy: failure threshold and failure cooldown", SwitchPolicyTests.FailureThresholdAndCooldownAsync),
("SwitchPolicy: failure retry has a 30 second floor", SwitchPolicyTests.FailureRetryUsesThirtySecondFloorAsync),
("SwitchPolicy: paused, fixed, and observe-only modes reset counts", SwitchPolicyTests.NonAutoModesResetCountsAsync),
("SwitchPolicy: switch, failure, and explicit reset clear counters", SwitchPolicyTests.CountsResetAfterEventsAsync),
("SwitchService: DNS failure after route write rolls back", SwitchServiceTests.DnsFailureAfterRouteWriteRollsBackAsync),
("SwitchService: egress verification failure rolls back", SwitchServiceTests.EgressFailureRollsBackAsync),
("SwitchService: rollback failure retains journal and raises RecoveryRequired", SwitchServiceTests.RollbackFailureRetainsJournalAsync),
("SwitchService: external modification is never overwritten", SwitchServiceTests.ExternalModificationIsNotOverwrittenAsync),
("SwitchService: cross-network rollback is rejected", SwitchServiceTests.CrossNetworkRollbackIsRejectedAsync),
("SwitchService: profile DNS is rejected", SwitchServiceTests.ProfileDnsIsRejectedAsync),
("SwitchService: destination gateway must be on the adapter subnet", SwitchServiceTests.GatewayMustBeOnAdapterSubnetAsync),
("SwitchService: failed baseline restore compensates original configuration", SwitchServiceTests.BaselineRestoreFailureCompensatesAsync),
("SwitchService: plan preserves an already preferred gateway metric", SwitchServiceTests.PlanPreservesPreferredMetricAsync),
("SwitchService: identical configuration skips route and DNS writes", SwitchServiceTests.SwitchSameConfigurationSkipsWritesAsync),
("SwitchService: only changed DNS is written", SwitchServiceTests.SwitchWritesOnlyChangedDnsAsync),
("SwitchService: only changed routes are written", SwitchServiceTests.SwitchWritesOnlyChangedRoutesAsync),
("SwitchService: unchanged rollback skips route and DNS writes", SwitchServiceTests.RollbackSameConfigurationSkipsWritesAsync),
("SwitchService: unchanged baseline restore skips route and DNS writes", SwitchServiceTests.BaselineRestoreSameConfigurationSkipsWritesAsync),
("GatewayController: unconfigured refresh does no probe or write", GatewayControllerTests.InitialUnconfiguredDoesNotProbeOrWriteAsync),
("GatewayController: first paused save preserves real direct DNS", GatewayControllerTests.ExplicitPausedSavePreservesRealDirectDnsAsync),
("GatewayController: external configuration is not overwritten", GatewayControllerTests.ExternalConfigurationChangeIsNotOverwrittenAsync),
("GatewayController: legacy empty session fingerprint is not writable", GatewayControllerTests.LegacySessionWithoutFingerprintIsNotWritableAsync),
("GatewayController: failed save does not become writable", GatewayControllerTests.SaveFailureAfterSessionWriteDoesNotBecomeWritableAsync),
("GatewayController: unfinished journal blocks automatic control", GatewayControllerTests.UnfinishedJournalBlocksAutomaticControlAsync),
("GatewayController: fixed rollback failure keeps fixed policy", GatewayControllerTests.FixedFailureKeepsFixedPolicyAfterRollbackAsync),
("GatewayController: concurrent refreshes are coalesced", GatewayControllerTests.ConcurrentRefreshIsCoalescedAsync),
("GatewayController: save and refresh writes are serialized", GatewayControllerTests.SaveAndRefreshAreSerializedAsync),
("GatewayController: stop restores when RestoreOnExit is enabled", GatewayControllerTests.StopRestoresWhenRequestedAsync),
("GatewayController: stop restores persisted session before first refresh", GatewayControllerTests.StopRestoresPersistedSessionBeforeFirstRefreshAsync),
("GatewayController: stop leaves network when restore is disabled", GatewayControllerTests.StopLeavesNetworkWhenRestoreIsDisabledAsync),
("GatewayController: failed stop restore leaves controller available", GatewayControllerTests.StopRestoreFailureLeavesControllerAvailableAsync),
("Recovery: failed durable Pause prevents all restore writes", RecoveryRegressionTests.RestorePauseWriteFailurePreventsNetworkWritesAsync),
("Recovery: Pause precedes network restore and survives restart", RecoveryRegressionTests.RestorePersistsPauseBeforeWritesAndRestartAsync),
("Recovery: baseline metadata failure retains journal and durable Pause", RecoveryRegressionTests.RestoreSessionFailureRetainsBaselineJournalAsync),
("Recovery: explicit rollback metadata failure retains journal and durable Pause", RecoveryRegressionTests.RestoreSessionFailureRetainsRollbackJournalAsync),
("Recovery: Stop resolves pending journal then restores original baseline", RecoveryRegressionTests.StopPendingJournalContinuesToOriginalBaselineAsync),
("Guarded switch: foreign route after preflight is preserved", RecoveryRegressionTests.ForeignRouteAfterPreflightIsProtectedAsync),
("Guarded switch: foreign DNS after preflight is preserved", RecoveryRegressionTests.ForeignDnsAfterPreflightIsProtectedAsync),
("Guarded switch: foreign route after routes and before DNS is preserved", RecoveryRegressionTests.ForeignRouteBeforeDnsIsProtectedAsync),
("Guarded switch: foreign DNS after routes and before DNS is preserved", RecoveryRegressionTests.ForeignDnsBeforeDnsIsProtectedAsync),
("NetworkComparison: every owned route, DNS, and network field participates", NetworkComparisonTests.EachOwnedFieldParticipatesAsync),
("NetworkComparison: transaction recovery accepts only exact before/intended rows", NetworkComparisonTests.RecoveryRejectsForeignRowsAsync),
("JsonStore: round trip, backup, and failed serialization preserve the last complete file", JsonStoreTests.AtomicReplacementAsync),
("FileLog: a writable primary directory is used and reports no failure", LogFallbackTests.WritablePrimaryDirectoryIsUsedAsync),
("FileLog: an unusable primary directory falls back instead of throwing", LogFallbackTests.UnusablePrimaryDirectoryFallsBackAsync),
("Windows NativeLayout: x64 structure sizes and metric offsets", NativeLayoutTests.X64LayoutAsync),
("LoginStartup: logon task XML starts minimized with highest privileges", LoginStartupTests.LogonTaskXmlAsync),
("LoginStartup: missing-task detection follows HRESULT, not exception type", LoginStartupTests.TaskMissingDetectionAsync),
};
var failures = 0;
var started = DateTime.UtcNow;
Console.WriteLine($"Running {tests.Length} offline GatewayKeepalive tests.");
foreach (var test in tests)
{
try
{
await test.Run();
Console.WriteLine($"PASS {test.Name}");
}
catch (Exception exception)
{
failures++;
Console.WriteLine($"FAIL {test.Name}");
Console.WriteLine(exception);
}
}
Console.WriteLine($"Completed in {(DateTime.UtcNow - started).TotalSeconds:F2}s: {tests.Length - failures} passed, {failures} failed.");
return failures == 0 ? 0 : 1;
}
}