- 只翻注释/文档字符串:剥掉行尾注释后,每条「-」都有逐字节相同的「+」对应行(8 组,代码零改动) - 保留协议/线上标识符原文:errcode、bot_token、context_token、getupdates、binded_redirect、PKCS#7、ECB 等 - 术语对齐既有译法:owner→主人、peer→对端、long poll→长轮询、backoff→退避、sentinel error→哨兵错误 - 已知既有欠债(未顺手修):login_reuse_test.go 在 HEAD 上就未 gofmt 干净;login_test.go 的 TestStartLogin_ScanStatusTransition 91 行 >80(HEAD 同为 91 行)
146 lines
4.3 KiB
Go
146 lines
4.3 KiB
Go
package weixin_ilink
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/aes"
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestDeriveInboundKey(t *testing.T) {
|
|
t.Parallel()
|
|
key := bytes.Repeat([]byte{0x7F}, aes.BlockSize)
|
|
token := encodeToken(key)
|
|
|
|
got, err := deriveInboundKey(token)
|
|
if err != nil {
|
|
t.Fatalf("deriveInboundKey: %v", err)
|
|
}
|
|
if !bytes.Equal(got, key) {
|
|
t.Fatalf("key = %x, want %x", got, key)
|
|
}
|
|
}
|
|
|
|
func TestDeriveInboundKey_Errors(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("empty token", func(t *testing.T) {
|
|
t.Parallel()
|
|
if _, err := deriveInboundKey(""); !errors.Is(err, ErrInvalidConfig) {
|
|
t.Fatalf("err = %v, want ErrInvalidConfig", err)
|
|
}
|
|
})
|
|
t.Run("not base64", func(t *testing.T) {
|
|
t.Parallel()
|
|
if _, err := deriveInboundKey("@not-base64@"); err == nil {
|
|
t.Fatal("err = nil, want decode error")
|
|
}
|
|
})
|
|
t.Run("token too short", func(t *testing.T) {
|
|
t.Parallel()
|
|
if _, err := deriveInboundKey(encodeToken([]byte("short"))); !errors.Is(err, ErrKeyTooShort) {
|
|
t.Fatalf("err = %v, want ErrKeyTooShort", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestDownloadMedia_DecryptsFromTokenDerivedKey(t *testing.T) {
|
|
t.Parallel()
|
|
key := bytes.Repeat([]byte{0x11}, aes.BlockSize)
|
|
plain := []byte("the quick brown fox jumps over the lazy dog")
|
|
enc, err := aesECBEncrypt(key, plain)
|
|
if err != nil {
|
|
t.Fatalf("aesECBEncrypt: %v", err)
|
|
}
|
|
token := encodeToken(key)
|
|
|
|
srv := newJSONServer(t, nil, func(req recordedReq) handlerResult {
|
|
if req.path == "/media/1" {
|
|
return handlerResult{ctype: "application/octet-stream", body: enc}
|
|
}
|
|
return handlerResult{status: 404}
|
|
})
|
|
|
|
ad := newAdapter(t, Config{BaseURL: srv.URL, CDNBaseURL: srv.URL, BotToken: token}, srv)
|
|
got, err := ad.DownloadMedia(context.Background(), srv.URL+"/media/1")
|
|
if err != nil {
|
|
t.Fatalf("DownloadMedia: %v", err)
|
|
}
|
|
if !bytes.Equal(got, plain) {
|
|
t.Fatalf("decrypted = %q, want %q", got, plain)
|
|
}
|
|
}
|
|
|
|
func TestDownloadMediaWithKey_ExplicitKey(t *testing.T) {
|
|
t.Parallel()
|
|
key := bytes.Repeat([]byte{0x22}, aes.BlockSize)
|
|
plain := []byte("explicit key path")
|
|
enc, err := aesECBEncrypt(key, plain)
|
|
if err != nil {
|
|
t.Fatalf("aesECBEncrypt: %v", err)
|
|
}
|
|
srv := newJSONServer(t, nil, func(req recordedReq) handlerResult {
|
|
if req.path == "/media/2" {
|
|
return handlerResult{ctype: "application/octet-stream", body: enc}
|
|
}
|
|
return handlerResult{status: 404}
|
|
})
|
|
ad := newAdapter(t, Config{BaseURL: srv.URL, CDNBaseURL: srv.URL, BotToken: "unused"}, srv)
|
|
got, err := ad.DownloadMediaWithKey(context.Background(), srv.URL+"/media/2", key)
|
|
if err != nil {
|
|
t.Fatalf("DownloadMediaWithKey: %v", err)
|
|
}
|
|
if !bytes.Equal(got, plain) {
|
|
t.Fatalf("decrypted = %q, want %q", got, plain)
|
|
}
|
|
}
|
|
|
|
func TestDownloadMedia_RejectsOversize(t *testing.T) {
|
|
t.Parallel()
|
|
big := bytes.Repeat([]byte{0x01}, 256)
|
|
srv := newJSONServer(t, nil, func(req recordedReq) handlerResult {
|
|
if req.path == "/media/big" {
|
|
return handlerResult{ctype: "application/octet-stream", body: big}
|
|
}
|
|
return handlerResult{status: 404}
|
|
})
|
|
ad := newAdapter(t, Config{
|
|
BaseURL: srv.URL, CDNBaseURL: srv.URL,
|
|
BotToken: encodeToken(bytes.Repeat([]byte{1}, aes.BlockSize)),
|
|
MaxMediaSize: 64,
|
|
}, srv)
|
|
_, err := ad.DownloadMedia(context.Background(), srv.URL+"/media/big")
|
|
if !errors.Is(err, ErrMediaTooLarge) {
|
|
t.Fatalf("err = %v, want ErrMediaTooLarge", err)
|
|
}
|
|
}
|
|
|
|
func TestDownloadMedia_RejectsDecryptFailure(t *testing.T) {
|
|
t.Parallel()
|
|
// 返回不构成合法 AES 分组序列的字节。
|
|
srv := newJSONServer(t, nil, func(req recordedReq) handlerResult {
|
|
if req.path == "/media/bad" {
|
|
return handlerResult{ctype: "application/octet-stream", body: []byte("not-a-block")}
|
|
}
|
|
return handlerResult{status: 404}
|
|
})
|
|
ad := newAdapter(t, Config{
|
|
BaseURL: srv.URL, CDNBaseURL: srv.URL,
|
|
BotToken: encodeToken(bytes.Repeat([]byte{1}, aes.BlockSize)),
|
|
}, srv)
|
|
if _, err := ad.DownloadMedia(context.Background(), srv.URL+"/media/bad"); err == nil {
|
|
t.Fatal("err = nil, want decrypt failure")
|
|
}
|
|
}
|
|
|
|
func TestCDNURL_JoinsRelativeAndPassesAbsolute(t *testing.T) {
|
|
t.Parallel()
|
|
ad, _ := New(Config{BaseURL: "https://api.example.com", CDNBaseURL: "https://cdn.example.com/c2c"}, nil, nil)
|
|
if got := ad.cdnURL("media/1.jpg"); got != "https://cdn.example.com/c2c/media/1.jpg" {
|
|
t.Fatalf("cdnURL(relative) = %q", got)
|
|
}
|
|
if got := ad.cdnURL("https://other.example.com/x"); got != "https://other.example.com/x" {
|
|
t.Fatalf("cdnURL(absolute) = %q", got)
|
|
}
|
|
}
|