195 lines
5.5 KiB
Go
195 lines
5.5 KiB
Go
package weixin
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"testing"
|
|
|
|
"github.com/chenhg5/cc-connect/core"
|
|
)
|
|
|
|
func TestFormatAesKeyForAPI(t *testing.T) {
|
|
// Verify our encode matches the Python SDK's format:
|
|
// base64(hex_string_bytes), not base64(raw_bytes).
|
|
key := []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
|
|
got := formatAesKeyForAPI(key)
|
|
|
|
// Expected: base64("00112233445566778899aabbccddeeff")
|
|
hexStr := hex.EncodeToString(key)
|
|
want := base64.StdEncoding.EncodeToString([]byte(hexStr))
|
|
if got != want {
|
|
t.Fatalf("formatAesKeyForAPI: got %q, want %q", got, want)
|
|
}
|
|
|
|
// Verify round-trip with parseAesKey (decode direction)
|
|
decoded, err := parseAesKey(got, "test")
|
|
if err != nil {
|
|
t.Fatalf("parseAesKey failed on formatAesKeyForAPI output: %v", err)
|
|
}
|
|
for i := range key {
|
|
if decoded[i] != key[i] {
|
|
t.Fatalf("round-trip mismatch at byte %d: got %02x, want %02x", i, decoded[i], key[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatAesKeyForAPI_NotRawBase64(t *testing.T) {
|
|
// Ensure the output is NOT just base64(raw_bytes) — that was the old bug.
|
|
key := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}
|
|
got := formatAesKeyForAPI(key)
|
|
wrongFormat := base64.StdEncoding.EncodeToString(key) // base64(raw) — the old bug
|
|
if got == wrongFormat {
|
|
t.Fatalf("formatAesKeyForAPI should NOT produce base64(raw_bytes), but got %q which equals the wrong format", got)
|
|
}
|
|
}
|
|
|
|
func TestIsWeixinCDNHost(t *testing.T) {
|
|
tests := []struct {
|
|
url string
|
|
want bool
|
|
}{
|
|
{"https://novac2c.cdn.weixin.qq.com/c2c/upload?param=abc", true},
|
|
{"https://anything.weixin.qq.com/path", true},
|
|
{"https://cdn.wechat.com/upload", true},
|
|
{"https://sub.domain.wechat.com/path", true},
|
|
{"https://example.com/upload", false},
|
|
{"https://weixin.qq.com.evil.com/fake", false},
|
|
{"https://notwechat.com/path", false},
|
|
{"", false},
|
|
{"not-a-url", false},
|
|
}
|
|
for _, tt := range tests {
|
|
got := isWeixinCDNHost(tt.url)
|
|
if got != tt.want {
|
|
t.Errorf("isWeixinCDNHost(%q) = %v, want %v", tt.url, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetUploadURLResponse_Validation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
resp getUploadURLResponse
|
|
wantError bool
|
|
}{
|
|
{
|
|
name: "upload_param only (legacy)",
|
|
resp: getUploadURLResponse{UploadParam: "some_param"},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "upload_full_url only (new API)",
|
|
resp: getUploadURLResponse{UploadFullURL: "https://novac2c.cdn.weixin.qq.com/c2c/upload?encrypted_query_param=abc"},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "both present",
|
|
resp: getUploadURLResponse{UploadParam: "param", UploadFullURL: "https://cdn.example.com/upload"},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "both empty",
|
|
resp: getUploadURLResponse{},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "whitespace only",
|
|
resp: getUploadURLResponse{UploadParam: " ", UploadFullURL: " "},
|
|
wantError: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Replicate the validation logic from client.go:
|
|
// both fields empty/whitespace-only → error
|
|
trim := func(s string) string {
|
|
for len(s) > 0 && s[0] == ' ' {
|
|
s = s[1:]
|
|
}
|
|
for len(s) > 0 && s[len(s)-1] == ' ' {
|
|
s = s[:len(s)-1]
|
|
}
|
|
return s
|
|
}
|
|
hasError := trim(tt.resp.UploadParam) == "" && trim(tt.resp.UploadFullURL) == ""
|
|
if hasError != tt.wantError {
|
|
t.Errorf("validation error = %v, wantError %v", hasError, tt.wantError)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsVideoFile(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
file core.FileAttachment
|
|
want bool
|
|
}{
|
|
{
|
|
name: "video mime",
|
|
file: core.FileAttachment{MimeType: "video/mp4", FileName: "reply.bin"},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "mp4 extension",
|
|
file: core.FileAttachment{MimeType: "application/octet-stream", FileName: "reply.mp4"},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "uppercase mov extension",
|
|
file: core.FileAttachment{FileName: "reply.MOV"},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "plain file",
|
|
file: core.FileAttachment{MimeType: "application/pdf", FileName: "reply.pdf"},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "audio is not video",
|
|
file: core.FileAttachment{MimeType: "audio/mpeg", FileName: "reply.mp3"},
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := isVideoFile(tt.file); got != tt.want {
|
|
t.Fatalf("isVideoFile() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildVideoMessageItemUsesVideoShape(t *testing.T) {
|
|
ref := &cdnUploadedRef{
|
|
downloadParam: "encrypted-query",
|
|
aesKey: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
|
cipherSize: 4096,
|
|
rawSize: 4000,
|
|
}
|
|
|
|
item := buildVideoMessageItem(ref)
|
|
if item.Type != messageItemVideo {
|
|
t.Fatalf("item.Type = %d, want %d", item.Type, messageItemVideo)
|
|
}
|
|
if item.VideoItem == nil {
|
|
t.Fatal("VideoItem is nil")
|
|
}
|
|
if item.VideoItem.VideoSize != ref.cipherSize {
|
|
t.Fatalf("VideoSize = %d, want %d", item.VideoItem.VideoSize, ref.cipherSize)
|
|
}
|
|
if item.VideoItem.Media == nil {
|
|
t.Fatal("VideoItem.Media is nil")
|
|
}
|
|
if item.VideoItem.Media.EncryptQueryParam != ref.downloadParam {
|
|
t.Fatalf("EncryptQueryParam = %q, want %q", item.VideoItem.Media.EncryptQueryParam, ref.downloadParam)
|
|
}
|
|
if item.VideoItem.Media.AESKey != formatAesKeyForAPI(ref.aesKey) {
|
|
t.Fatal("VideoItem.Media.AESKey does not match API format")
|
|
}
|
|
if item.VideoItem.Media.EncryptType != 1 {
|
|
t.Fatalf("EncryptType = %d, want 1", item.VideoItem.Media.EncryptType)
|
|
}
|
|
}
|