2026-06-17 20:41:55 +08:00
|
|
|
|
package vendor
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
|
|
"with_you/internal/config"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// TestPayloadEmptyConfigMatchesLegacy 验证:未配置任何 channel/模板时,
|
|
|
|
|
|
// Factory 产出的推送 payload 与改动前 BuildNotification 行为一致。
|
|
|
|
|
|
//
|
|
|
|
|
|
// 改动前 BuildNotification(title, body, "message", extras) 产出:
|
|
|
|
|
|
// notification: { alert, android:{alert,title,extras:{notification_type}+extras,priority:1}, ios:{alert:{title,body},sound:default,badge:+1,extras} }
|
|
|
|
|
|
// options: { time_to_live: 86400, apns_production } // 无 third_party_channel
|
|
|
|
|
|
func TestPayloadEmptyConfigMatchesLegacy(t *testing.T) {
|
|
|
|
|
|
factory := NewFactory()
|
|
|
|
|
|
cfg := config.ChannelConfig{} // 全空配置
|
|
|
|
|
|
params := MessageParams{
|
|
|
|
|
|
Scene: SceneChat,
|
|
|
|
|
|
Title: "张三",
|
|
|
|
|
|
Body: "你好",
|
|
|
|
|
|
MessageType: "message",
|
|
|
|
|
|
Extra: map[string]any{
|
|
|
|
|
|
"message_id": "m1",
|
|
|
|
|
|
"conversation_id": "c1",
|
|
|
|
|
|
"sender_id": "u1",
|
|
|
|
|
|
"category": "chat",
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
payload := factory.Build(params, cfg)
|
|
|
|
|
|
|
|
|
|
|
|
// 1. TPC 必须为 nil(无 third_party_channel 下发,与改动前一致)
|
|
|
|
|
|
if payload.TPC != nil {
|
|
|
|
|
|
t.Fatalf("TPC should be nil when all channel config empty, got: %+v", payload.TPC)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
notif := payload.Notification
|
|
|
|
|
|
if notif == nil || notif.Android == nil || notif.IOS == nil {
|
|
|
|
|
|
t.Fatalf("notification/android/ios must not be nil")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Android 字段对比
|
|
|
|
|
|
if notif.Android.Alert != "你好" || notif.Android.Title != "张三" || notif.Android.Priority != 1 {
|
|
|
|
|
|
t.Errorf("Android basic fields mismatch: %+v", notif.Android)
|
|
|
|
|
|
}
|
|
|
|
|
|
if notif.Android.ChannelID != "" {
|
|
|
|
|
|
t.Errorf("Android.ChannelID = %q, want empty", notif.Android.ChannelID)
|
|
|
|
|
|
}
|
|
|
|
|
|
if notif.Android.Extras["notification_type"] != "message" || notif.Android.Extras["message_id"] != "m1" {
|
|
|
|
|
|
t.Errorf("Android.Extras mismatch: %+v", notif.Android.Extras)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. iOS 字段对比
|
|
|
|
|
|
if notif.IOS.Sound != "default" || notif.IOS.Badge != "+1" {
|
|
|
|
|
|
t.Errorf("IOS basic fields mismatch: %+v", notif.IOS)
|
|
|
|
|
|
}
|
|
|
|
|
|
if notif.IOS.ThreadID != "" {
|
|
|
|
|
|
t.Errorf("IOS.ThreadID = %q, want empty", notif.IOS.ThreadID)
|
|
|
|
|
|
}
|
|
|
|
|
|
if notif.IOS.Extras["notification_type"] != "message" {
|
|
|
|
|
|
t.Errorf("IOS.Extras[notification_type] mismatch: %+v", notif.IOS.Extras)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 序列化 JSON 确认厂商私有字段不出现
|
|
|
|
|
|
jsonBytes, err := json.Marshal(notif)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("marshal: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
jsonStr := string(jsonBytes)
|
|
|
|
|
|
for _, key := range []string{`"channel_id"`, `"thread-id"`, `"third_party_channel"`, `"mi_template"`, `"private_msg_template_id"`} {
|
|
|
|
|
|
if strings.Contains(jsonStr, key) {
|
|
|
|
|
|
t.Errorf("notification JSON should not contain %s when config empty, got: %s", key, jsonStr)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
t.Logf("notification JSON: %s", jsonStr)
|
2026-06-18 19:41:32 +08:00
|
|
|
|
|
|
|
|
|
|
// 5. classification 必须为系统消息(1):聊天即时消息统一归系统消息避免限速
|
|
|
|
|
|
if payload.Classification != ClassificationSystem {
|
|
|
|
|
|
t.Errorf("Classification = %d, want %d (system)", payload.Classification, ClassificationSystem)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestClassificationByScene 验证:system 与 chat 场景的 classification 均为 1(系统消息)
|
|
|
|
|
|
// 聊天即时消息归为系统消息,避免厂商运营消息限速影响送达
|
|
|
|
|
|
func TestClassificationByScene(t *testing.T) {
|
|
|
|
|
|
factory := NewFactory()
|
|
|
|
|
|
cfg := config.ChannelConfig{}
|
|
|
|
|
|
|
|
|
|
|
|
for _, scene := range []string{SceneSystem, SceneChat} {
|
|
|
|
|
|
params := MessageParams{Scene: scene, Title: "T", Body: "B"}
|
|
|
|
|
|
payload := factory.Build(params, cfg)
|
|
|
|
|
|
if payload.Classification != ClassificationSystem {
|
|
|
|
|
|
t.Errorf("scene=%s: Classification = %d, want %d", scene, payload.Classification, ClassificationSystem)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-17 20:41:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestPayloadWithChannelConfig 验证:配置 channel_id 后正确下发,且不影响通用通知字段
|
|
|
|
|
|
func TestPayloadWithChannelConfig(t *testing.T) {
|
|
|
|
|
|
factory := NewFactory()
|
|
|
|
|
|
cfg := config.ChannelConfig{
|
|
|
|
|
|
System: "sys_channel",
|
|
|
|
|
|
Chat: "chat_channel",
|
|
|
|
|
|
}
|
|
|
|
|
|
params := MessageParams{Scene: SceneChat, Title: "T", Body: "B", MessageType: "chat_message"}
|
|
|
|
|
|
|
|
|
|
|
|
payload := factory.Build(params, cfg)
|
|
|
|
|
|
|
|
|
|
|
|
// 通用通知仍正常
|
|
|
|
|
|
if payload.Notification.Android.Title != "T" {
|
|
|
|
|
|
t.Errorf("Android.Title = %q, want T", payload.Notification.Android.Title)
|
|
|
|
|
|
}
|
|
|
|
|
|
// Android channel_id 跟随场景(chat)
|
|
|
|
|
|
if payload.Notification.Android.ChannelID != "chat_channel" {
|
|
|
|
|
|
t.Errorf("Android.ChannelID = %q, want chat_channel", payload.Notification.Android.ChannelID)
|
|
|
|
|
|
}
|
|
|
|
|
|
// TPC 各厂商 channel_id 下发(华为/VIVO 等回退全局)
|
|
|
|
|
|
if payload.TPC == nil {
|
|
|
|
|
|
t.Fatal("TPC should not be nil when channel config present")
|
|
|
|
|
|
}
|
|
|
|
|
|
if payload.TPC.Huawei["channel_id"] != "chat_channel" {
|
|
|
|
|
|
t.Errorf("Huawei channel_id = %v, want chat_channel", payload.TPC.Huawei["channel_id"])
|
|
|
|
|
|
}
|
|
|
|
|
|
t.Logf("TPC JSON: %s", mustJSON(payload.TPC))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestPayloadXiaomiTemplateOptional 验证:仅配 channel_id 不配模板时,小米不下发模板字段
|
|
|
|
|
|
func TestPayloadXiaomiTemplateOptional(t *testing.T) {
|
|
|
|
|
|
factory := NewFactory()
|
|
|
|
|
|
cfg := config.ChannelConfig{
|
|
|
|
|
|
Vendor: config.VendorChannel{
|
|
|
|
|
|
Xiaomi: config.VendorChannelID{Chat: "mi_chat"},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
params := MessageParams{Scene: SceneChat, Title: "T", Body: "B"}
|
|
|
|
|
|
|
|
|
|
|
|
payload := factory.Build(params, cfg)
|
|
|
|
|
|
if payload.TPC == nil || payload.TPC.Xiaomi == nil {
|
|
|
|
|
|
t.Fatal("Xiaomi TPC should exist")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, ok := payload.TPC.Xiaomi["mi_template_id"]; ok {
|
|
|
|
|
|
t.Error("Xiaomi should NOT contain mi_template_id when template not configured")
|
|
|
|
|
|
}
|
|
|
|
|
|
if payload.TPC.Xiaomi["channel_id"] != "mi_chat" {
|
|
|
|
|
|
t.Errorf("Xiaomi channel_id = %v, want mi_chat", payload.TPC.Xiaomi["channel_id"])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-18 19:41:32 +08:00
|
|
|
|
// TestOppoCategoryNotifyLevel 验证:OPPO category/notify_level 按场景区分,且 notify_level 必须伴 category
|
|
|
|
|
|
func TestOppoCategoryNotifyLevel(t *testing.T) {
|
|
|
|
|
|
factory := NewFactory()
|
|
|
|
|
|
|
|
|
|
|
|
// 场景1:chat 用 IM + 16(强提醒)
|
|
|
|
|
|
cfgChat := config.ChannelConfig{
|
|
|
|
|
|
Vendor: config.VendorChannel{
|
|
|
|
|
|
OPPO: config.VendorChannelID{
|
|
|
|
|
|
Chat: "oppo_chat",
|
|
|
|
|
|
OppoChatCategory: "IM",
|
|
|
|
|
|
OppoChatNotifyLevel: 16,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
payload := factory.Build(MessageParams{Scene: SceneChat, Title: "T", Body: "B"}, cfgChat)
|
|
|
|
|
|
if payload.TPC == nil || payload.TPC.OPPO == nil {
|
|
|
|
|
|
t.Fatal("OPPO TPC should exist for chat scene")
|
|
|
|
|
|
}
|
|
|
|
|
|
if payload.TPC.OPPO["category"] != "IM" {
|
|
|
|
|
|
t.Errorf("OPPO chat category = %v, want IM", payload.TPC.OPPO["category"])
|
|
|
|
|
|
}
|
|
|
|
|
|
if payload.TPC.OPPO["notify_level"] != 16 {
|
|
|
|
|
|
t.Errorf("OPPO chat notify_level = %v, want 16", payload.TPC.OPPO["notify_level"])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 场景2:system 用 ACCOUNT + 2(通知栏+锁屏)
|
|
|
|
|
|
cfgSys := config.ChannelConfig{
|
|
|
|
|
|
Vendor: config.VendorChannel{
|
|
|
|
|
|
OPPO: config.VendorChannelID{
|
|
|
|
|
|
System: "oppo_sys",
|
|
|
|
|
|
OppoSystemCategory: "ACCOUNT",
|
|
|
|
|
|
OppoSystemNotifyLevel: 2,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
payload = factory.Build(MessageParams{Scene: SceneSystem, Title: "T", Body: "B"}, cfgSys)
|
|
|
|
|
|
if payload.TPC.OPPO["category"] != "ACCOUNT" {
|
|
|
|
|
|
t.Errorf("OPPO system category = %v, want ACCOUNT", payload.TPC.OPPO["category"])
|
|
|
|
|
|
}
|
|
|
|
|
|
if payload.TPC.OPPO["notify_level"] != 2 {
|
|
|
|
|
|
t.Errorf("OPPO system notify_level = %v, want 2", payload.TPC.OPPO["notify_level"])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestOppoNotifyLevelRequiresCategory 验证:notify_level 配置但 category 未配置时,跳过 notify_level(避免 OPPO 校验失败)
|
|
|
|
|
|
func TestOppoNotifyLevelRequiresCategory(t *testing.T) {
|
|
|
|
|
|
factory := NewFactory()
|
|
|
|
|
|
cfg := config.ChannelConfig{
|
|
|
|
|
|
Vendor: config.VendorChannel{
|
|
|
|
|
|
OPPO: config.VendorChannelID{
|
|
|
|
|
|
Chat: "oppo_chat",
|
|
|
|
|
|
OppoChatNotifyLevel: 16, // 只配 notify_level,不配 category
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
payload := factory.Build(MessageParams{Scene: SceneChat, Title: "T", Body: "B"}, cfg)
|
|
|
|
|
|
if payload.TPC == nil || payload.TPC.OPPO == nil {
|
|
|
|
|
|
t.Fatal("OPPO TPC should exist (has channel_id)")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, ok := payload.TPC.OPPO["notify_level"]; ok {
|
|
|
|
|
|
t.Error("OPPO should NOT contain notify_level when category not configured")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, ok := payload.TPC.OPPO["category"]; ok {
|
|
|
|
|
|
t.Error("OPPO should NOT contain category when not configured")
|
|
|
|
|
|
}
|
|
|
|
|
|
// channel_id 仍正常下发
|
|
|
|
|
|
if payload.TPC.OPPO["channel_id"] != "oppo_chat" {
|
|
|
|
|
|
t.Errorf("OPPO channel_id = %v, want oppo_chat", payload.TPC.OPPO["channel_id"])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestHonorImportanceByScene 验证:荣耀 importance 按场景区分(chat=NORMAL, system=LOW)
|
|
|
|
|
|
func TestHonorImportanceByScene(t *testing.T) {
|
|
|
|
|
|
factory := NewFactory()
|
|
|
|
|
|
cfg := config.ChannelConfig{
|
|
|
|
|
|
Vendor: config.VendorChannel{
|
|
|
|
|
|
Honor: config.VendorChannelID{
|
|
|
|
|
|
Chat: "honor_chat",
|
|
|
|
|
|
System: "honor_sys",
|
|
|
|
|
|
HonorChatImportance: "NORMAL",
|
|
|
|
|
|
HonorSystemImportance: "LOW",
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// chat 场景 -> NORMAL
|
|
|
|
|
|
payload := factory.Build(MessageParams{Scene: SceneChat, Title: "T", Body: "B"}, cfg)
|
|
|
|
|
|
if payload.TPC == nil || payload.TPC.Honor == nil {
|
|
|
|
|
|
t.Fatal("Honor TPC should exist for chat scene")
|
|
|
|
|
|
}
|
|
|
|
|
|
if payload.TPC.Honor["importance"] != "NORMAL" {
|
|
|
|
|
|
t.Errorf("Honor chat importance = %v, want NORMAL", payload.TPC.Honor["importance"])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// system 场景 -> LOW
|
|
|
|
|
|
payload = factory.Build(MessageParams{Scene: SceneSystem, Title: "T", Body: "B"}, cfg)
|
|
|
|
|
|
if payload.TPC.Honor["importance"] != "LOW" {
|
|
|
|
|
|
t.Errorf("Honor system importance = %v, want LOW", payload.TPC.Honor["importance"])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestHonorImportanceOptional 验证:未配置 importance 时不下发,仅 channel_id
|
|
|
|
|
|
func TestHonorImportanceOptional(t *testing.T) {
|
|
|
|
|
|
factory := NewFactory()
|
|
|
|
|
|
cfg := config.ChannelConfig{
|
|
|
|
|
|
Vendor: config.VendorChannel{
|
|
|
|
|
|
Honor: config.VendorChannelID{Chat: "honor_chat"},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
payload := factory.Build(MessageParams{Scene: SceneChat, Title: "T", Body: "B"}, cfg)
|
|
|
|
|
|
if payload.TPC == nil || payload.TPC.Honor == nil {
|
|
|
|
|
|
t.Fatal("Honor TPC should exist (has channel_id)")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, ok := payload.TPC.Honor["importance"]; ok {
|
|
|
|
|
|
t.Error("Honor should NOT contain importance when not configured")
|
|
|
|
|
|
}
|
|
|
|
|
|
if payload.TPC.Honor["channel_id"] != "honor_chat" {
|
|
|
|
|
|
t.Errorf("Honor channel_id = %v, want honor_chat", payload.TPC.Honor["channel_id"])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestVivoCategoryByScene 验证:vivo category 按场景区分
|
|
|
|
|
|
func TestVivoCategoryByScene(t *testing.T) {
|
|
|
|
|
|
factory := NewFactory()
|
|
|
|
|
|
cfg := config.ChannelConfig{
|
|
|
|
|
|
Vendor: config.VendorChannel{
|
|
|
|
|
|
VIVO: config.VendorChannelID{
|
|
|
|
|
|
Chat: "vivo_chat",
|
|
|
|
|
|
System: "vivo_sys",
|
|
|
|
|
|
VivoChatCategory: "IM",
|
|
|
|
|
|
VivoSystemCategory: "ACCOUNT",
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// chat 场景 -> IM
|
|
|
|
|
|
payload := factory.Build(MessageParams{Scene: SceneChat, Title: "T", Body: "B"}, cfg)
|
|
|
|
|
|
if payload.TPC == nil || payload.TPC.VIVO == nil {
|
|
|
|
|
|
t.Fatal("VIVO TPC should exist for chat scene")
|
|
|
|
|
|
}
|
|
|
|
|
|
if payload.TPC.VIVO["category"] != "IM" {
|
|
|
|
|
|
t.Errorf("VIVO chat category = %v, want IM", payload.TPC.VIVO["category"])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// system 场景 -> ACCOUNT
|
|
|
|
|
|
payload = factory.Build(MessageParams{Scene: SceneSystem, Title: "T", Body: "B"}, cfg)
|
|
|
|
|
|
if payload.TPC.VIVO["category"] != "ACCOUNT" {
|
|
|
|
|
|
t.Errorf("VIVO system category = %v, want ACCOUNT", payload.TPC.VIVO["category"])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestVivoCategoryOptional 验证:未配置 category 时不下发,仅 channel_id
|
|
|
|
|
|
func TestVivoCategoryOptional(t *testing.T) {
|
|
|
|
|
|
factory := NewFactory()
|
|
|
|
|
|
cfg := config.ChannelConfig{
|
|
|
|
|
|
Vendor: config.VendorChannel{
|
|
|
|
|
|
VIVO: config.VendorChannelID{Chat: "vivo_chat"},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
payload := factory.Build(MessageParams{Scene: SceneChat, Title: "T", Body: "B"}, cfg)
|
|
|
|
|
|
if payload.TPC == nil || payload.TPC.VIVO == nil {
|
|
|
|
|
|
t.Fatal("VIVO TPC should exist (has channel_id)")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, ok := payload.TPC.VIVO["category"]; ok {
|
|
|
|
|
|
t.Error("VIVO should NOT contain category when not configured")
|
|
|
|
|
|
}
|
|
|
|
|
|
if payload.TPC.VIVO["channel_id"] != "vivo_chat" {
|
|
|
|
|
|
t.Errorf("VIVO channel_id = %v, want vivo_chat", payload.TPC.VIVO["channel_id"])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-17 20:41:55 +08:00
|
|
|
|
func mustJSON(v any) string {
|
|
|
|
|
|
b, _ := json.Marshal(v)
|
|
|
|
|
|
return string(b)
|
|
|
|
|
|
}
|