Files
yunzerwebsiteallinone/go/services/wechatmini/push_test.go
T
2026-10-31 19:17:37 +08:00

159 lines
5.0 KiB
Go

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 := `<xml><ToUserName>wx123</ToUserName></xml>`
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 := `<xml><ToUserName>wxApp</ToUserName><FromUserName>oOpenId</FromUserName><CreateTime>1700000000</CreateTime><MsgType>text</MsgType><Content>你好</Content><MsgId>123456</MsgId></xml>`
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("<xml></xml>"); 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, "<Content><![CDATA[收到]]></Content>") {
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)
}
}