159 lines
5.0 KiB
Go
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)
|
|
}
|
|
}
|