Files
MailUI4Agents/client/electron/test/harmony-push-optin.test.mjs
JianFeeeee f44b15684b fix(push): 通知开关默认改开(修正误解「可配置项=默认关」致 token 从不上报)
★ 功能性硬伤修复:用户实测「新邮件根本没有通知」。

根因:我上一版把 PushService.isEnabled 默认设成关(读不到即 false),
误解了「接入系统通知是可配置项」的本意 —— 用户要的是「服务端没配凭证时
客户端优雅降级」(enabled:false 是正常态,不报错不阻塞),不是「默认关掉
推送功能」。结果 reportToken 最前 gate 直接 return,device token 从未上报,
DB 里 0 个 token,NotifyNewMail 在 len(tokens)==0 时静默返回 ⇒ 永远发不出。

修正:
- PushContract.DEFAULT_PUSH_ENABLED: false → true(契约层默认开)
- PushService.isEnabled: getSync 默认值用 DEFAULT_PUSH_ENABLED(true),
  catch 也返回 true —— 只有用户显式关才不上报
- 判据同步:harmony-push-optin ①② 改默认开口径

验证(服务端→HMS 链路实打已通):
- OAuth access_token 取得(app_secret 有效)
- POST /v1/{appId}/messages:send + test_message 调通,
  HMS 回 80300007 "All tokens are invalid"(假 token 无效,但凭证/形状/认证全被接受)
⇒ 只要 DB 有真实 device token 就能送达(token 由客户端上报)
2026-09-16 08:29:24 +08:00

86 lines
4.7 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.

// 通知开关判据 —— 钉「接入系统通知」**必须是可配置项**(用户强调多次,非可选)。
//
// 用户原话:「还有我强调多少遍了,接入系统通知是一个可配置项,因为用户可以自由的部署自己的
// agentmail」。
//
// 自部署常态:服务器没有配置华为推送凭证。关着 = 不取 token、不请求系统通知权限、
// 不上报、不打网关 —— 零开销、不弹任何东西、不阻塞启动。这条判据钉的就是这套"开关必须存在
// 且默认关、关着要走零路径"的契约。
//
// 四条:
// ① 默认关(契约常量 + PushService.isEnabled 读不到即 false);
// ② reportToken 在**最前** gate(关 ⇒ getToken 都不调 —— 否则零开销就不成立);
// ③ SettingsPage 有 Toggle 且接了回调(用户能看见开关);
// ④ EntryAbility 不因开关关而报错(gate 在 PushService 内部 ⇒ onCreate 不用改)。
import { code, prose, stripComments } from './lib/read.mjs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { test } from 'node:test';
import assert from 'node:assert/strict';
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..', '..', '..');
const HARMONY_ETS = join(ROOT, 'client/harmony/entry/src/main/ets');
const read = p => prose(join(HARMONY_ETS, p));
const C = await import(join(HARMONY_ETS, 'model/PushContract.ts'));
test('① 契约默认开:DEFAULT_PUSH_ENABLED = true(用户要能默认收到通知)', () => {
assert.equal(C.DEFAULT_PUSH_ENABLED, true,
'默认必须开 —— 接入系统通知是"服务端没配凭证时优雅降级",不是"默认关掉推送"(2026-09-16 用户实测收不到通知的根因)');
// 标签:关 ⇒ 'off'、开 ⇒ 'on'(口径稳定,给设置页用)
assert.equal(C.pushEnabledLabel(false), 'off');
assert.equal(C.pushEnabledLabel(true), 'on');
});
test('② PushService.isEnabled 读不到即开(默认开落点) + setEnabled 存在', () => {
const svc = read('api/PushService.ets');
const code_ = stripComments(svc);
assert.match(code_, /static isEnabled\(context: common\.Context\): boolean \{/,
'要有静态 isEnabled(ability 阶段 onCreate 要读它,不能是实例方法)');
assert.match(code_, /getSync\(SETTING_KEY, DEFAULT_PUSH_ENABLED\)/,
'读不到时用 DEFAULT_PUSH_ENABLED(默认开)—— 只有显式存了 false 才关');
assert.match(code_, /static setEnabled\(context: common\.Context, enabled: boolean\): void \{/,
'要有静态 setEnabled(设置页 Toggle 回调用)');
});
test('③ reportToken 在最前 gate(关 ⇒ getToken 都不调 ⇒ 零开销)', () => {
const svc = read('api/PushService.ets');
const code_ = stripComments(svc);
/*
* 取 reportToken 的正文(从方法签名到下一个 async/方法声明),
* 判"开关检查在 getToken 之前" —— 这是"零开销"的关键:
* 关时根本不碰 Push Kit(不取 token、不触网络/权限路径)。
*/
const lines = code_.split('\n');
const start = lines.findIndex(l => /async reportToken\(/.test(l));
assert.ok(start > 0, '要能找到 reportToken');
let stop = lines.length;
for (let i = start + 1; i < lines.length; i++) {
if (/^\s*(async |private |static )\w/.test(lines[i]) || /^}\s*$/.test(lines[i])) {
// 粗略:下一段方法或闭合 — 取 200 行作截断
stop = Math.min(i + 1, start + 200);
break;
}
}
const body = lines.slice(start, stop).join('\n');
const gateAt = body.indexOf('PushService.isEnabled');
const getTokenAt = body.indexOf('this.getToken()');
assert.ok(gateAt > 0, 'reportToken 里要有 isEnabled gate');
assert.ok(getTokenAt > 0, 'reportToken 里要有 getToken');
assert.ok(gateAt < getTokenAt,
'开关 gate 必须在 getToken 之前 —— 否则关时仍会取 token(零开销不成立)');
});
test('④ SettingsPage 有 Toggle 且接了 togglePush 回调(用户能看见开关)', () => {
const settings = read('pages/SettingsPage.ets');
assert.match(settings, /Toggle\(\{ type: ToggleType\.Switch, isOn: this\.pushEnabled \}\)/,
'设置页要有 Toggle(Switch) 绑定 pushEnabled');
assert.match(settings, /togglePush\(isOn\)/, 'Toggle 的 onChange 要调 togglePush');
assert.match(settings, /PushService\.isEnabled/, 'aboutToAppear 要读一次开关回填');
assert.match(settings, /PushService\.setEnabled/, 'togglePush 要写持久化');
// 状态提示(服务器未配置推送时如实告诉用户)
assert.match(settings, /服务器未配置推送/, '服务器没配通道时要说清(否则用户开了以为有用)');
});