Files
Gateway-keepaliveForDotnet/tests/GatewayKeepalive.Tests/LoginStartupTests.cs
T
chuzhongzai 7be117057a 重做设置界面并修复登录自启:页签单选、滚轮归属、任务缺失判定、发布去掉自带运行时
设置窗口
- 改为独立非模态窗口(进任务栏、可最小化),不再被托盘浮窗挡住导致点不到
- 位置改为按屏幕可用区域居中裁剪,不再默认落在右下角
- 三个页签重构为卡片式布局:统一卡片内边距、页签行独立成行、正文左右留白、
  帮助文字按实际换行测量高度、标题栏与底栏加分隔线
- 直连 DNS 复选框、下拉框、滚动条改为自绘控件,配色与整体统一

页签
- PanelChip 的选中/强调改为值变化即重绘,修掉“切换页签后旧页签仍显示为激活、
  鼠标移上去才更新”的陈旧绘制

滚轮
- 输入框滚不动自己时把滚轮交给外层页面,单行输入框不再吞掉滚轮事件

登录自启
- 改用任务 XML + schtasks 写入,COM 失败时用 schtasks 复核;写入后读回校验
- 任务不存在按 HRESULT 判定(0x80070002 / 0x80070003 / 0x8004130F),修掉
  “关闭登录自启必定报错并回滚”(晚绑定 COM 抛的是 FileNotFoundException)
- 状态读取 COM 失败时回退到 schtasks,失败按异常类型 + HResult 去重记录

诊断
- 界面报错统一写日志;控制器记录自启读写结果;启动自检记录管理员/自启/进程/参数

发布
- 去掉 -SelfContained 分支,只保留单文件运行时依赖包,脚本与文档同步
- 新增 tools/unblock-publish.ps1,清除发布产物的网络来源标记

测试
- 新增登录自启任务 XML 用例与“任务缺失按 HRESULT 判定”回归测试(46 → 47),探测 16/16
2026-10-24 01:32:03 +08:00

68 lines
4.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Xml.Linq;
using GatewayKeepalive.Windows;
namespace GatewayKeepalive.Tests;
/// <summary>
/// 登录自启任务的定义完全靠 XML 表达,错了只会在“下次登录”时才暴露。
/// 这里把生成出来的 XML 当场验一遍:启动命令、隐藏启动、最高权限、电源与超时设置。
/// </summary>
internal static class LoginStartupTests
{
private const string SchedulerNamespace = "http://schemas.microsoft.com/windows/2004/02/mit/task";
public static Task LogonTaskXmlAsync()
{
const string executable = @"D:\Program Files\网关 保活\GatewayKeepalive.exe";
var root = XDocument.Parse(LoginStartup.BuildTaskXml(executable)).Root!;
XNamespace ns = SchedulerNamespace;
Check.Equal("Task", root.Name.LocalName, "任务定义的根元素应当是 Task");
Check.Equal(SchedulerNamespace, root.Name.NamespaceName, "任务定义必须使用计划任务的命名空间");
var exec = root.Element(ns + "Actions")?.Element(ns + "Exec");
Check.True(exec is not null, "任务必须有一个启动程序的 Exec 动作");
Check.Equal(executable, exec!.Element(ns + "Command")?.Value, "动作命令必须是当前可执行文件");
Check.Contains("--minimized", exec.Element(ns + "Arguments")?.Value ?? string.Empty, "登录启动必须带 --minimized,不能弹出设置窗口");
Check.Equal(@"D:\Program Files\网关 保活", exec.Element(ns + "WorkingDirectory")?.Value, "工作目录应当跟随可执行文件");
var principal = root.Element(ns + "Principals")?.Element(ns + "Principal");
Check.True(principal is not null, "任务必须声明主体");
Check.Equal("InteractiveToken", principal!.Element(ns + "LogonType")?.Value, "必须以交互令牌运行,托盘才能出现在桌面上");
Check.Equal("HighestAvailable", principal.Element(ns + "RunLevel")?.Value, "必须是最高权限,否则每次登录都会弹 UAC");
Check.False(string.IsNullOrWhiteSpace(principal.Element(ns + "UserId")?.Value), "主体必须绑定当前用户");
var trigger = root.Element(ns + "Triggers")?.Element(ns + "LogonTrigger");
Check.True(trigger is not null, "必须由登录事件触发");
Check.Equal("true", trigger!.Element(ns + "Enabled")?.Value, "登录触发器必须启用");
Check.False(string.IsNullOrWhiteSpace(trigger.Element(ns + "UserId")?.Value), "登录触发器必须绑定当前用户");
var settings = root.Element(ns + "Settings");
Check.True(settings is not null, "任务必须带设置节");
Check.Equal("false", settings!.Element(ns + "DisallowStartIfOnBatteries")?.Value, "电池供电时也要启动");
Check.Equal("false", settings.Element(ns + "StopIfGoingOnBatteries")?.Value, "切换到电池供电时不能结束进程");
Check.Equal("true", settings.Element(ns + "StartWhenAvailable")?.Value, "错过触发时间后应当补跑");
Check.Equal("PT0S", settings.Element(ns + "ExecutionTimeLimit")?.Value, "常驻托盘程序不能设置执行时长上限");
Check.Equal("IgnoreNew", settings.Element(ns + "MultipleInstancesPolicy")?.Value, "不允许重复实例,避免两个托盘互相改写网络");
return Task.CompletedTask;
}
/// <summary>
/// 晚绑定 COM 抛出的异常类型由 CLR 按 HRESULT 翻译:任务不存在时拿到的是 <see cref="FileNotFoundException"/>,
/// 不是 <see cref="System.Runtime.InteropServices.COMException"/>。早先只按类型抓 0x80070002,
/// 于是一次都没抓到——“关闭登录自启”永远在读回时失败,界面报「找不到文件或目录」。
/// </summary>
public static Task TaskMissingDetectionAsync()
{
Check.True(LoginStartup.IsTaskMissing(new FileNotFoundException()), "文件不存在(0x80070002)应当判定为任务不存在");
Check.True(LoginStartup.IsTaskMissing(new DirectoryNotFoundException()), "目录不存在(0x80070003)应当判定为任务不存在");
Check.True(LoginStartup.IsTaskMissing(new System.Runtime.InteropServices.COMException("任务不存在", unchecked((int)0x8004130F))),
"SCHED_E 的任务不存在代码应当被识别");
Check.False(LoginStartup.IsTaskMissing(new System.Runtime.InteropServices.COMException("拒绝访问", unchecked((int)0x80070005))),
"0x80070005 是权限问题,不能静默当成任务不存在");
Check.False(LoginStartup.IsTaskMissing(new InvalidOperationException("别的错误")), "其他异常不能被当成任务不存在");
return Task.CompletedTask;
}
}