Files
MailUI4Agents/client/electron/test/harmony-deviceprobe.test.mjs
JianFeeeee b413fbb43e 跨端: fix(设备闸): 按真机形状改写解析(前台 = state #FOREGROUND 那个 mission 的 bundle),判据改喂真机样本
第一版是**猜的**格式(`bundleName: com.x`),真机上根本没有这种写法;退一步"取第一个 bundle name"
又会取到别人的(真机多 mission,`#31 com.example.homeagent` 排在我们 `#32` 前)⇒ 假绿(pi 独立复现)。

真机形状(样本 `test/fixtures/aa-dump-l-real.txt`,实采):每块 `Mission ID #N … ` +
`bundle name [com.x]` + `state #FOREGROUND/#BACKGROUND`;**前台只由 `state #FOREGROUND` 决定**。

- `parseMissions` / `foregroundBundle`:按块解析,取不到返回空(不猜、不兜底);
- `foregroundVerdict` 三态不变:`ours` / `other` / `unverified`;
- 有 mission 但**全无 FOREGROUND** ⇒ `unverified`(不是"没我们所以算别人");
- 空 dump / 截断 ⇒ `unverified`,状态词写明"**缺证据 ≠ 没有那个现象**"。

判据 5 条(喂真样本 + 对调状态 + 空/截断/无前台 + 撞名撞文案反证)全绿。
真样本本身含"对方在前台"那一刻 —— 争用在一分钟内真实发生过,所以这不是构造出来的场景。
2026-09-15 12:05:17 +08:00

69 lines
4.3 KiB
JavaScript
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.

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');
});