package wechatmini import ( "crypto/sha1" "encoding/hex" "encoding/json" "sort" "strings" "testing" "server/services/wechatmp" ) // sign 复刻微信签名算法:sha1(sort(token, timestamp, nonce)) func sign(token, timestamp, nonce string) string { arr := []string{token, timestamp, nonce} sort.Strings(arr) sum := sha1.Sum([]byte(strings.Join(arr, ""))) return hex.EncodeToString(sum[:]) } func TestGeneratePushSecrets(t *testing.T) { token := GeneratePushToken() if len(token) < 3 || len(token) > 32 { t.Fatalf("Token 长度非法: %d", len(token)) } for _, c := range token { if !strings.ContainsRune(aesKeyAlphabet, c) { t.Fatalf("Token 含非法字符: %q", c) } } key := GeneratePushAESKey() if len(key) != 43 { t.Fatalf("EncodingAESKey 应为 43 位,实际 %d", len(key)) } for _, c := range key { if !strings.ContainsRune(aesKeyAlphabet, c) { t.Fatalf("EncodingAESKey 含非法字符: %q", c) } } } // 生成的 EncodingAESKey 必须能被微信 AES-256-CBC 直接使用 func TestGeneratedAESKeyUsable(t *testing.T) { key := GeneratePushAESKey() plain := `wx123` cipherText, err := wechatmp.EncryptMessage(key, "wx123", plain) if err != nil { t.Fatalf("加密失败: %v", err) } got, err := wechatmp.DecryptMessage(key, "wx123", cipherText) if err != nil { t.Fatalf("解密失败: %v", err) } if got != plain { t.Fatalf("解密内容不一致: %s", got) } } func TestMatchPushConfigBySignature(t *testing.T) { cfgs := []PushConfig{ {AppID: "wxA", Token: "tokenA", EncryptMode: wechatmp.EncryptModePlain, DataFormat: DataFormatXML}, {AppID: "wxB", Token: "tokenB", EncryptMode: wechatmp.EncryptModeSafe, DataFormat: DataFormatJSON}, } ts, nonce := "1700000000", "abc123" cfg, ok := MatchPushConfigBySignature(cfgs, ts, nonce, sign("tokenB", ts, nonce)) if !ok || cfg.AppID != "wxB" { t.Fatalf("应匹配到 wxB,实际 ok=%v cfg=%v", ok, cfg) } if _, ok := MatchPushConfigBySignature(cfgs, ts, nonce, "bad-signature"); ok { t.Fatal("错误签名不应匹配") } } func TestParsePushXML(t *testing.T) { raw := `wxAppoOpenId1700000000text你好123456` msg, err := ParsePushXML(raw) if err != nil { t.Fatalf("解析失败: %v", err) } if msg.ToUserName != "wxApp" || msg.FromUserName != "oOpenId" || msg.Content != "你好" || msg.MsgID != 123456 { t.Fatalf("解析结果不符: %+v", msg) } } func TestParsePushJSON(t *testing.T) { raw := `{"ToUserName":"wxApp","FromUserName":"oOpenId","CreateTime":1700000000,"MsgType":"text","Content":"hello","MsgId":987654}` msg, err := ParsePushJSON(raw) if err != nil { t.Fatalf("解析失败: %v", err) } if msg.ToUserName != "wxApp" || msg.Content != "hello" || msg.MsgID != 987654 { t.Fatalf("解析结果不符: %+v", msg) } } func TestDetectPushFormat(t *testing.T) { if got := DetectPushFormat(`{"a":1}`); got != DataFormatJSON { t.Fatalf("应为 JSON,实际 %s", got) } if got := DetectPushFormat(""); got != DataFormatXML { t.Fatalf("应为 XML,实际 %s", got) } } func TestBuildTextReply(t *testing.T) { xmlReply, err := BuildTextReply(DataFormatXML, "oOpenId", "wxApp", "收到", 1700000000) if err != nil { t.Fatalf("构造 XML 回复失败: %v", err) } if !strings.Contains(xmlReply, "") { t.Fatalf("XML 回复内容不符: %s", xmlReply) } jsonReply, err := BuildTextReply(DataFormatJSON, "oOpenId", "wxApp", "收到", 1700000000) if err != nil { t.Fatalf("构造 JSON 回复失败: %v", err) } var payload map[string]interface{} if err := json.Unmarshal([]byte(jsonReply), &payload); err != nil { t.Fatalf("JSON 回复不是合法 JSON: %v", err) } if payload["Content"] != "收到" || payload["MsgType"] != "text" { t.Fatalf("JSON 回复内容不符: %s", jsonReply) } } func TestBuildEncryptedReply(t *testing.T) { cfg := &PushConfig{AppID: "wxApp", Token: "token", AESKey: GeneratePushAESKey(), EncryptMode: wechatmp.EncryptModeSafe, DataFormat: DataFormatJSON} plain, err := BuildTextReply(DataFormatJSON, "oOpenId", "wxApp", "收到", 1700000000) if err != nil { t.Fatalf("构造明文回复失败: %v", err) } reply, err := BuildEncryptedReply(cfg, plain, "1700000000", "nonce1", DataFormatJSON) if err != nil { t.Fatalf("构造加密回复失败: %v", err) } var payload map[string]interface{} if err := json.Unmarshal([]byte(reply), &payload); err != nil { t.Fatalf("加密回复不是合法 JSON: %v", err) } enc, _ := payload["Encrypt"].(string) if enc == "" { t.Fatal("加密回复缺少 Encrypt") } if payload["MsgSignature"] != wechatmp.MsgSignature(cfg.Token, "1700000000", "nonce1", enc) { t.Fatal("加密回复签名不正确") } decrypted, err := wechatmp.DecryptMessage(cfg.AESKey, cfg.AppID, enc) if err != nil { t.Fatalf("解密回复失败: %v", err) } if decrypted != plain { t.Fatalf("解密回复内容不符: %s", decrypted) } }