From 654d694ff6b4fc4383512c2dffa26a29bffdbf12 Mon Sep 17 00:00:00 2001 From: jkooe <62494645+jkooe@users.noreply.github.com> Date: Mon, 26 Oct 2026 01:00:50 +0800 Subject: [PATCH] =?UTF-8?q?test(e2e):=20windows.mjs=20=E8=A1=A5=20L2=20sta?= =?UTF-8?q?tus/collect/verify=20=E4=B8=89=E6=9D=A1=EF=BC=88T05=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 字段与 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。 --- tests/e2e/windows.mjs | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/e2e/windows.mjs b/tests/e2e/windows.mjs index a9442b0..1d78b4a 100644 --- a/tests/e2e/windows.mjs +++ b/tests/e2e/windows.mjs @@ -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));