import test from 'node:test'; import assert from 'node:assert/strict'; import { dirname, join } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { prose } from './lib/read.mjs'; /* 设备读回的**前置闸**(pi 邮件 `971c58fa` §4 / `bf583b0f` §2)。 危险形状不是"读不到",而是**读到别人的界面却当成我们的**:断言可能通过、也可能红, **两者都不是在讲我们的界面** —— 前者是假绿。 ★ 本判据喂的是**真机样本**(`fixtures/aa-dump-l-real.txt`,`hdc … shell "aa dump -l"` 实采), 不是我构造的形状。原因:我第一版按**猜的**格式写(`bundleName: com.x`), 真机上根本没有那种写法 ⇒ 闸会永远返回 `unverified`(安全但没法用); 而改成"取第一个 `bundle name`" ⇒ 真机多 mission、对方可能排在前面 ⇒ **取到别人的**(假绿)。 pi 独立复现过后者。所以样本必须是真机的。 */ const HERE = dirname(fileURLToPath(import.meta.url)); const MODEL = join(HERE, '..', '..', 'harmony', 'entry', 'src', 'main', 'ets', 'model'); const C = await import(pathToFileURL(join(MODEL, 'DeviceProbe.ts')).href); const OURS = 'com.jianf.agentmail'; const THEIRS = 'com.example.homeagent'; const real = prose(join(HERE, 'fixtures', 'aa-dump-l-real.txt')); test('★ 真机样本:两个 mission 并存,前台是**对方**(这份样本就是争用发生的那一刻)', () => { const ms = C.parseMissions(real); assert.equal(ms.length, 2, '真机 dump 里有两个 mission(我们 + 对方)'); assert.deepEqual(ms.map((m) => m.bundle), [THEIRS, OURS], '对方排在前面 —— 所以"取第一个 bundle name"必然取错'); assert.equal(C.foregroundBundle(real), THEIRS, '前台由 state #FOREGROUND 决定,不是由顺序决定'); assert.equal(C.foregroundVerdict(real, OURS), C.VERDICT_OTHER, '对方在前台 ⇒ other,绝不算我们的'); assert.equal(C.mayAssertOn(real, OURS), false, '拿不到独占 ⇒ 不许断言'); assert.ok(C.unverifiedReason(C.VERDICT_OTHER).includes('未验')); }); test('★ 真样本②(12:08 实采):**我们真的在前台** ⇒ ours + 允许断言(严格正例)', () => { // 此前这条正例是**对调状态合成**的,而且合成得自相矛盾(state FG 却 app state BG)—— // pi 抓到这点:那种样本不是合法 dump,正例等于建在非法输入上。现在换成**真机实采**的。 const oursReal = prose(join(HERE, 'fixtures', 'aa-dump-l-ours-foreground.txt')); assert.equal(C.parseMissions(oursReal).length, 2, '真样本里两个 mission 都在(对方 + 我们)'); assert.equal(C.foregroundVerdict(oursReal, OURS), C.VERDICT_OURS); assert.equal(C.mayAssertOn(oursReal, OURS), true, '★ 没有这条,"闸能放行"这个性质就没人验(失效方式是永远未验)'); assert.equal(C.foregroundBundle(oursReal), OURS); }); test('★ 自相矛盾的 dump(state FG 但 app state BG)⇒ unverified,不许挑一个信', () => { const contradictory = prose(join(HERE, 'fixtures', 'aa-dump-l-ours-foreground.txt')) .split('\n').map((l) => l.includes('app state #FOREGROUND') && l.trim().startsWith('app state') ? l.replace('#FOREGROUND', '#BACKGROUND') : l).join('\n'); assert.equal(C.foregroundVerdict(contradictory, OURS), C.VERDICT_UNVERIFIED, '同一块里 state 与 app state 打架 ⇒ 这份 dump 自己就不自洽 ⇒ 拿不准,而不是"挑一个信"'); }); test('★ 空 dump / 截断 / 格式变了 ⇒ unverified(缺证据 ≠ 没有那个现象)', () => { assert.equal(C.foregroundVerdict('', OURS), C.VERDICT_UNVERIFIED); assert.equal(C.foregroundVerdict('User ID #100\n current mission lists:{\n', OURS), C.VERDICT_UNVERIFIED); assert.equal(C.mayAssertOn('{不是 dump', OURS), false); assert.ok(C.unverifiedReason(C.VERDICT_UNVERIFIED).includes('缺证据'), '状态词必须点明"缺证据≠没有现象"'); }); test('★ 有 mission 但全都没有 FOREGROUND 状态 ⇒ unverified(不是"没我们就算别人")', () => { const noFg = real.split('\n').map((l) => l.replace('#FOREGROUND', '#BACKGROUND')).join('\n'); assert.equal(C.foregroundBundle(noFg), '', '没有 FOREGROUND ⇒ 取不到前台'); assert.equal(C.foregroundVerdict(noFg, OURS), C.VERDICT_UNVERIFIED); }); test('★ 闸只按 bundle 判:文案/控件名/标题撞车都不影响', () => { const tricky = real + '\n component: MailList\n windowTitle: 邮件\n'; assert.equal(C.foregroundVerdict(tricky, OURS), C.VERDICT_OTHER, '加上与我们要判控件同名同文案的东西,也不许变成 ours'); }); test('★ 第二份真样本:单 mission(**我们根本不在列表里**)⇒ other,不是 unverified', () => { // 由第一份真样本删掉我们那块得到;形状与 pi 12:03 那份活 dump 一致(他信里也是单 mission) const single = prose(join(HERE, 'fixtures', 'aa-dump-l-single-theirs.txt')); assert.equal(C.parseMissions(single).length, 1); assert.equal(C.foregroundVerdict(single, OURS), C.VERDICT_OTHER, '有 FOREGROUND 且不是我们 ⇒ other。若这里报 unverified,就把"别人占着"和"没读到"混成一种了'); assert.equal(C.mayAssertOn(single, OURS), false); }); test('★ 把 state #FOREGROUND 全部删掉 ⇒ unverified(缺证据,不猜)', () => { const noFg = real.split('\n').filter((l) => !l.includes('#FOREGROUND')).join('\n'); assert.equal(C.foregroundVerdict(noFg, OURS), C.VERDICT_UNVERIFIED); });