Files

236 lines
9.6 KiB
Go
Raw Permalink 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.
package notifywatcher
import (
"strings"
"testing"
)
// 真机实测数据(2026-08-30,红米 k60 MIUI14 Android 13)。
// 数据来源:dumpsys notification --noredact 实测输出(转账 0.10 元触发微信/支付宝收款通知)。
// realWechatMain 微信主号收款通知(uid=10277,文本含"(朋友到店)"场景标签)。
const realWechatMain = ` NotificationRecord(0x0eb536c3: pkg=com.tencent.mm user=UserHandle{0} id=-124356843 tag=null importance=4 key=0|com.tencent.mm|-124356843|null|10277: Notification(channel=message_channel_new_id shortcut=null contentView=null vibrate=null sound=null tick defaults=0x0 flags=0x11 color=0x00000000 category=msg vis=PRIVATE))
uid=10277 userId=0
opPkg=com.tencent.mm
icon=Icon(typ=RESOURCE pkg=com.tencent.mm id=0x7f080bc8)
flags=0x11
originalFlags=0x11
pri=1
key=0|com.tencent.mm|-124356843|null|10277
seen=true
groupKey=0|com.tencent.mm|-124356843|null|10277
notification=
fullscreenIntent=null
contentIntent=PendingIntent{7a9eb40: PendingIntentRecord{e3b9b0e com.tencent.mm startActivity (allowlist: 8cc672e:+30s0ms/0/NOTIFICATION_SERVICE/NotificationManagerService)}}
deleteIntent=null
number=700
groupAlertBehavior=0
when=1788083827846
tickerText=微信收款助手: 微信支付收款0.10元(朋友到店)
contentView=null
bigContentView=null
headsUpContentView=null
color=0x00000000
timeout=unknown
extras={
android.title=String (微信收款助手)
android.reduced.images=Boolean (true)
android.subText=null
android.showChronometer=Boolean (false)
android.text=String (微信支付收款0.10元(朋友到店))
android.progress=Integer (0)
android.progressMax=Integer (0)
android.appInfo=ApplicationInfo (ApplicationInfo{c86da79 com.tencent.mm})
android.showWhen=Boolean (true)
android.largeIcon=Icon (Icon(typ=BITMAP size=130x130))
android.infoText=null
android.progressIndeterminate=Boolean (false)
android.remoteInputHistory=null
}`
// realWechatDual 微信双开(XSpace)收款通知(uid=99910277,聚合 [2条])。
const realWechatDual = ` NotificationRecord(0x0e9213be: pkg=com.tencent.mm user=UserHandle{999} id=-124356843 tag=null importance=4 key=999|com.tencent.mm|-124356843|null|99910277: Notification(channel=message_channel_new_id shortcut=null contentView=null vibrate=null sound=null tick defaults=0x0 flags=0x11 color=0x00000000 category=msg vis=PRIVATE))
uid=99910277 userId=999
icon=Icon(typ=RESOURCE pkg=com.tencent.mm id=0x7f080bc8)
pri=1
key=999|com.tencent.mm|-124356843|null|99910277
when=1788083804845
tickerText=微信收款助手: 微信支付收款0.10元
extras={
android.title=String (微信收款助手)
android.subText=null
android.text=String ([2条]微信收款助手: 微信支付收款0.10元)
android.showWhen=Boolean (true)
android.infoText=null
}`
// realAlipay 支付宝收款通知(uid=10250,标题含"(老顾客消费)"场景标签)。
const realAlipay = ` NotificationRecord(0x03b84a6f: pkg=com.eg.android.AlipayGphone user=UserHandle{0} id=1223552217 tag=null importance=4 key=0|com.eg.android.AlipayGphone|1223552217|null|10250: Notification(channel=alipay_default shortcut=null contentView=null vibrate=null sound=null tick defaults=0x4 flags=0x11 color=0x00000000 groupKey=com.eg.android.AlipayGphone.POPULAR_EVENT_GROUP vis=PRIVATE))
uid=10250 userId=0
when=1788083627490
tickerText=已转入余额 发起店铺充值活动>>
extras={
android.title=String (你已成功收款0.10元(老顾客消费))
android.text=String (已转入余额 发起店铺充值活动>>)
android.showWhen=Boolean (true)
}`
// realAlipayDual 支付宝双开(XSpace)收款通知(uid=99910250)。
const realAlipayDual = ` NotificationRecord(0x0d7d01ac: pkg=com.eg.android.AlipayGphone user=UserHandle{999} id=1027977183 tag=null importance=4 key=999|com.eg.android.AlipayGphone|1027977183|null|99910250: Notification(channel=alipay_default shortcut=null contentView=null vibrate=null sound=null tick defaults=0x4 flags=0x11 color=0x00000000 groupKey=com.eg.android.AlipayGphone.POPULAR_EVENT_GROUP vis=PRIVATE))
uid=99910250 userId=999
when=1788083627491
extras={
android.title=String (你已成功收款0.10元)
android.text=String (已转入余额)
}`
// nonPaymentAlipay 支付宝非收款通知(活动/营销,无"收款"文本,应被跳过)。
const nonPaymentAlipay = ` NotificationRecord(0x03c8c250: pkg=com.eg.android.AlipayGphone user=UserHandle{0} id=2049664768 tag=null importance=4 key=0|com.eg.android.AlipayGphone|2049664768|null|10250: Notification(channel=alipay_default shortcut=null contentView=null vibrate=null sound=null defaults=0x0 flags=0x200 color=0x00000000 groupKey=com.eg.android.AlipayGphone.POPULAR_EVENT_GROUP vis=PRIVATE))
uid=10250 userId=0
when=1788083628051
extras={
android.title=String (活动通知)
android.text=String (限时优惠)
}`
// unrelated 其他 APP 通知(MIUI 系统/视频),应被忽略。
const unrelated = ` NotificationRecord(0x0015f4bf: pkg=com.android.updater user=UserHandle{0} id=0 tag=null importance=5 key=0|com.android.updater|0|null|6102: Notification(channel=updater_channel_id shortcut=null contentView=null vibrate=null sound=null tick defaults=0x4 flags=0x22 color=0x00000000 vis=PRIVATE))
uid=6102 userId=0
when=1788080945841
extras={
android.title=String (发现系统有新版本)
android.text=String (1.0.16.0.UMNCNXM)
}`
func TestParse_WechatMain(t *testing.T) {
evs := Parse(realWechatMain, "dev-01")
if len(evs) != 1 {
t.Fatalf("期望 1 条事件,实际 %d", len(evs))
}
e := evs[0]
if e.Provider != "wechat" {
t.Errorf("provider = %q, 期望 wechat", e.Provider)
}
if e.Space != 0 {
t.Errorf("space = %d, 期望 0(主空间)", e.Space)
}
if e.AmountMinor != 10 {
t.Errorf("amount = %d, 期望 10(0.10元)", e.AmountMinor)
}
if e.When != 1788083827846 {
t.Errorf("when = %d, 期望 1788083827846", e.When)
}
if !strings.Contains(e.Key, "com.tencent.mm") {
t.Errorf("key 异常: %q", e.Key)
}
// 指纹稳定:同一事件两次生成必须相同
if e.Fingerprint() != evs[0].Fingerprint() {
t.Error("指纹不稳定")
}
}
func TestParse_WechatDual(t *testing.T) {
evs := Parse(realWechatDual, "dev-01")
if len(evs) != 1 {
t.Fatalf("期望 1 条事件,实际 %d", len(evs))
}
e := evs[0]
if e.Provider != "wechat" {
t.Errorf("provider = %q, 期望 wechat", e.Provider)
}
if e.Space != 999 {
t.Errorf("space = %d, 期望 999(双开)", e.Space)
}
if e.AmountMinor != 10 {
t.Errorf("amount = %d, 期望 10", e.AmountMinor)
}
}
func TestParse_Alipay(t *testing.T) {
evs := Parse(realAlipay, "dev-01")
if len(evs) != 1 {
t.Fatalf("期望 1 条事件,实际 %d", len(evs))
}
e := evs[0]
if e.Provider != "alipay" {
t.Errorf("provider = %q, 期望 alipay", e.Provider)
}
if e.Space != 0 {
t.Errorf("space = %d, 期望 0", e.Space)
}
if e.AmountMinor != 10 {
t.Errorf("amount = %d, 期望 10", e.AmountMinor)
}
}
func TestParse_AlipayDual(t *testing.T) {
evs := Parse(realAlipayDual, "dev-01")
if len(evs) != 1 {
t.Fatalf("期望 1 条事件,实际 %d", len(evs))
}
e := evs[0]
if e.Space != 999 {
t.Errorf("space = %d, 期望 999", e.Space)
}
if e.AmountMinor != 10 {
t.Errorf("amount = %d, 期望 10", e.AmountMinor)
}
}
func TestParse_SkipNonPayment(t *testing.T) {
// 非收款通知(活动/营销/其他APP)→ 0 条
evs := Parse(nonPaymentAlipay+unrelated, "dev-01")
if len(evs) != 0 {
t.Errorf("期望 0 条事件(非收款通知应跳过),实际 %d", len(evs))
}
}
func TestParse_MixedFullDump(t *testing.T) {
// 完整通知栏混合输出:微信主+双开、支付宝主+双开、非收款、其他APP
out := strings.Join([]string{realWechatMain, realWechatDual, realAlipay, realAlipayDual,
nonPaymentAlipay, unrelated}, "\n")
evs := Parse(out, "dev-01")
if len(evs) != 4 {
t.Fatalf("期望 4 条收款事件,实际 %d", len(evs))
}
// 设备ID必须写入(跨设备隔离)
for _, e := range evs {
if e.DeviceID != "dev-01" {
t.Errorf("device_id 未写入: %q", e.DeviceID)
}
}
}
func TestParse_DedupAggregated(t *testing.T) {
// 同一条通知在 dump 中重复出现(模拟轮询间隔内通知未变)→ 幂等
out := realWechatMain + "\n" + realWechatMain
evs := Parse(out, "dev-01")
if len(evs) != 1 {
t.Errorf("重复记录应去重为 1 条,实际 %d", len(evs))
}
}
func TestExtractAmount(t *testing.T) {
cases := []struct {
text string
want int64
}{
{"微信支付收款0.10元(朋友到店)", 10},
{"你已成功收款0.10元(老顾客消费)", 10},
{"[2条]微信收款助手: 微信支付收款0.10元", 10},
{"收款1元", 100},
{"收款 12.5元", 1250},
{"到账 100.00元", 10000},
{"收款0.10", 0}, // 无"元"单位 → 不匹配(避免误提取)
{"已转入余额", 0}, // 无金额
{"限时优惠", 0}, // 非收款
{"充值10元话费", 0}, // 有"元"但非收款/到账语境 → 不匹配
}
for _, c := range cases {
got := extractAmount(c.text)
if got != c.want {
t.Errorf("extractAmount(%q) = %d, 期望 %d", c.text, got, c.want)
}
}
}