test(e2e): windows.mjs 补 L2 status/collect/verify 三条(T05)

字段与 manifest system.audit.l2 的 returns_schema(四 action 属性并集)
逐字对齐:status 全字段断言(platform/supported/auditpol/
legacy_policy_disabled/cmdline_logging/sysmon_installed/file/file_exists);
collect 断 since/limit 回显、written<=collected、信任边界 note 必返;
verify 断 ok/checked。

collect 对非提权环境(读 Security 日志需管理员)skip 容错 —— 与 winget
缺失同一哲学,不把「环境没条件」伪装成「功能坏了」。

Mac 上不跑 windows.mjs:node --check + 静态自证(参数名/能力名/字段
与 manifest returns_schema 逐字对齐),不假跑。e2e 总数 33 → 50。
This commit is contained in:
jkooe committed 2026-10-26 01:00:50 +08:00
1 parent 6152a68b4a
commit 654d694ff6
1 file changed
+52
+52
View File
@@ -223,6 +223,58 @@ async function main() {
assert.equal(r.status, 'failed');
assert.equal(r.error.name, 'E_CAPABILITY_DISABLED');
});
// ---------- v2.1.0 L2 系统级审计(T05,Windows 真机)----------
// 字段与 packages/protocol/src/capabilities/manifest/system.ts 的
// system.audit.l2 returns_schema(四种 action 的属性并集)逐字对齐:
// status → platform/supported/auditpol/legacy_policy_disabled/
// cmdline_logging/sysmon_installed/file/file_exists(/note)
// collect → collected/written/since/limit/file(/note)
// verify → ok/checked(/broken_at)
// 信任边界(不夸大):本地 audit-l2.log 只是归一化副本,防篡改依赖转发出机。
await test('v2.1.0 L2 status:Windows 审计策略状态(returns_schema 全字段)', async () => {
const c = await connect();
const r = await c.invoke('system.audit.l2', { action: 'status' }, 90_000);
c.close();
assert.equal(r.status, 'ok', `L2 status 失败: ${JSON.stringify(r.error)}`);
assert.equal(r.data.platform, 'windows', 'platform 必须回 windows');
assert.equal(r.data.supported, true, 'Windows 上必须 supported=true');
assert.equal(typeof r.data.auditpol, 'string', 'auditpol 原始输出应为字符串');
assert.equal(typeof r.data.legacy_policy_disabled, 'boolean', 'SCENoApplyLegacyAuditPolicy 判定应为布尔');
assert.equal(typeof r.data.cmdline_logging, 'boolean', '命令行记录开关应为布尔');
assert.equal(typeof r.data.sysmon_installed, 'boolean', 'Sysmon 检测结果应为布尔');
assert.equal(typeof r.data.file, 'string', 'L2 副本路径应为字符串');
assert.equal(typeof r.data.file_exists, 'boolean', 'file_exists 应为布尔');
});
await test('v2.1.0 L2 collect:拉取 Security 事件归一化落盘(since/limit 回显)', async () => {
const c = await connect();
const since = Date.now() - 60 * 60 * 1000; // 近 1 小时:缩小扫描窗口,CI 上更快
const r = await c.invoke('system.audit.l2', { action: 'collect', since, limit: 100 }, 120_000);
c.close();
// 读 Security 日志需要管理员;非提权环境明确跳过(与 winget 缺失同一容错哲学,
// 不把「环境没条件」伪装成「功能坏了」)
if (r.status === 'failed' && r.error?.name === 'E_EXECUTION_FAILED') return 'skip';
assert.equal(r.status, 'ok', `L2 collect 失败: ${JSON.stringify(r.error)}`);
assert.equal(r.data.since, since, 'since 必须原样回显(默认 24h 的参数契约)');
assert.equal(r.data.limit, 100, 'limit 必须原样回显');
assert.ok(Number.isInteger(r.data.collected) && r.data.collected >= 0, 'collected 应为非负整数');
assert.ok(
Number.isInteger(r.data.written) && r.data.written >= 0 && r.data.written <= r.data.collected,
'written 应为非负整数且不超过 collected',
);
assert.equal(typeof r.data.file, 'string', '落盘路径应为字符串');
assert.equal(typeof r.data.note, 'string', '信任边界提示必须随 collect 返回(不得假装本地副本不可篡改)');
});
await test('v2.1.0 L2 verify:归一化链完整性校验(空链亦为 ok)', async () => {
const c = await connect();
const r = await c.invoke('system.audit.l2', { action: 'verify' }, 60_000);
c.close();
assert.equal(r.status, 'ok', `L2 verify 失败: ${JSON.stringify(r.error)}`);
assert.equal(r.data.ok, true, `L2 链应完整${r.data.ok ? '' : `: ${JSON.stringify(r.data.broken_at)}`}`);
assert.ok(Number.isInteger(r.data.checked) && r.data.checked >= 0, 'checked 应为非负整数');
});
} finally {
agent.kill();
await new Promise((r) => setTimeout(r, 300));