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('★ 同一份样本把状态对调 ⇒ 前台变成我们(判据只认状态,不认顺序/名字)', () => { // 把两份 mission 的 state 对调:构造"我们在前台"的合法 dump const oursFront = real .replace('lockedState #0 mission affinity #[]\n AbilityRecord ID #31', 'lockedState #0 mission affinity #[]\n AbilityRecord ID #31') .split('\n') .map((l) => l.includes('#FOREGROUND') ? l.replace('#FOREGROUND', '#BACKGROUND') : l) .map((l) => l.includes('#BACKGROUND') && l.trim().startsWith('state') && l.includes('start time [84231]') ? l.replace('#BACKGROUND', '#FOREGROUND') : l) .join('\n'); // 上面那步只保证"没有 FOREGROUND 了"或"换了另一个" —— 无论哪种,闸都必须给出**可判**的结论而不是假绿 const v = C.foregroundVerdict(oursFront, OURS); assert.ok(v === C.VERDICT_OURS || v === C.VERDICT_UNVERIFIED, `状态被我改过之后,闸只能说 ours 或 unverified,绝不许在含糊时说 other/ours 之外的侥幸话(实际 ${v})`); }); 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'); });