Files
2026-06-02 23:14:41 +08:00

201 lines
5.5 KiB
Go

package weixin
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
)
func TestBodyFromItemList_Text(t *testing.T) {
got := bodyFromItemList([]messageItem{
{Type: messageItemText, TextItem: &textItem{Text: " hello "}},
})
if got != "hello" {
t.Fatalf("got %q", got)
}
}
func TestBodyFromItemList_VoiceText(t *testing.T) {
got := bodyFromItemList([]messageItem{
{Type: messageItemVoice, VoiceItem: &voiceItem{Text: "transcribed"}},
})
if got != "transcribed" {
t.Fatalf("got %q", got)
}
}
func TestBodyFromItemList_Quote(t *testing.T) {
ref := &refMessage{
Title: "t",
MessageItem: &messageItem{
Type: messageItemText,
TextItem: &textItem{Text: "inner"},
},
}
got := bodyFromItemList([]messageItem{
{Type: messageItemText, TextItem: &textItem{Text: "reply"}, RefMsg: ref},
})
want := "[引用: t | inner]\nreply"
if got != want {
t.Fatalf("got %q want %q", got, want)
}
}
func TestSplitUTF8(t *testing.T) {
s := string([]rune{'a', '啊', 'b', '吧', 'c'})
parts := splitUTF8(s, 2)
if len(parts) != 3 || parts[0] != "a啊" || parts[1] != "b吧" || parts[2] != "c" {
t.Fatalf("parts=%#v", parts)
}
}
func TestSplitUTF8Empty(t *testing.T) {
parts := splitUTF8("", maxWeixinChunk)
if len(parts) != 1 || parts[0] != "" {
t.Fatalf("parts=%#v", parts)
}
}
func TestMediaOnlyItems(t *testing.T) {
if !mediaOnlyItems([]messageItem{{Type: messageItemImage}}) {
t.Fatal("image should be media-only")
}
if mediaOnlyItems([]messageItem{{Type: messageItemVoice, VoiceItem: &voiceItem{Text: "x"}}}) {
t.Fatal("voice with text is not media-only")
}
}
func TestCollectInboundMediaUsesCDNHTTPClient(t *testing.T) {
png := []byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/download" {
t.Fatalf("path = %q, want /download", r.URL.Path)
}
if r.URL.Query().Get("encrypted_query_param") != "image-ref" {
t.Fatalf("encrypted_query_param = %q, want image-ref", r.URL.Query().Get("encrypted_query_param"))
}
_, _ = w.Write(png)
}))
defer server.Close()
p := &Platform{
cdnBaseURL: server.URL,
httpClient: &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
return nil, errors.New("api client should not download media")
})},
cdnHttpClient: server.Client(),
}
images, files, audio := p.collectInboundMedia(context.Background(), []messageItem{{
Type: messageItemImage,
ImageItem: &imageItem{
Media: &cdnMedia{EncryptQueryParam: "image-ref"},
},
}})
if len(images) != 1 {
t.Fatalf("images len = %d, want 1", len(images))
}
if images[0].MimeType != "image/png" {
t.Fatalf("mime = %q, want image/png", images[0].MimeType)
}
if string(images[0].Data) != string(png) {
t.Fatalf("image data = %v, want %v", images[0].Data, png)
}
if len(files) != 0 {
t.Fatalf("files len = %d, want 0", len(files))
}
if audio != nil {
t.Fatalf("audio = %#v, want nil", audio)
}
}
func TestSendMessageResp_JSON(t *testing.T) {
var r sendMessageResp
if err := json.Unmarshal([]byte(`{"ret":-1,"errcode":100,"errmsg":"rate limited"}`), &r); err != nil {
t.Fatal(err)
}
if r.Ret != -1 || r.Errcode != 100 || r.Errmsg != "rate limited" {
t.Fatalf("got %+v", r)
}
}
func TestSendAudioRejectsEmptyAudio(t *testing.T) {
p := &Platform{}
// resolveReplyContext checks context_token first, so provide one
rc := &replyContext{peerUserID: "test", contextToken: "valid-token"}
err := p.SendAudio(context.Background(), rc, []byte{}, "wav")
if err == nil {
t.Fatal("expected error for empty audio")
}
if !containsStr(err.Error(), "empty audio") {
t.Fatalf("expected 'empty audio' error, got: %v", err)
}
}
func TestSendAudioRejectsInvalidReplyContext(t *testing.T) {
p := &Platform{}
err := p.SendAudio(context.Background(), "invalid-context", []byte("audio-data"), "wav")
if err == nil {
t.Fatal("expected error for invalid reply context")
}
if !containsStr(err.Error(), "invalid reply context") {
t.Fatalf("expected 'invalid reply context' error, got: %v", err)
}
}
func TestSendAudioRejectsNilReplyContext(t *testing.T) {
p := &Platform{}
err := p.SendAudio(context.Background(), nil, []byte("audio-data"), "wav")
if err == nil {
t.Fatal("expected error for nil reply context")
}
if !containsStr(err.Error(), "invalid reply context") {
t.Fatalf("expected 'invalid reply context' error, got: %v", err)
}
}
func TestGetConfig_RejectsNonZeroErrcode(t *testing.T) {
raw := `{"ret":0,"errcode":40001,"errmsg":"invalid token","typing_ticket":""}`
var out getConfigResp
if err := json.Unmarshal([]byte(raw), &out); err != nil {
t.Fatal(err)
}
if out.Errcode != 40001 {
t.Fatalf("expected errcode 40001, got %d", out.Errcode)
}
}
func TestGetConfig_RejectsNonZeroRet(t *testing.T) {
raw := `{"ret":-1,"errcode":0,"errmsg":"internal error","typing_ticket":"tk"}`
var out getConfigResp
if err := json.Unmarshal([]byte(raw), &out); err != nil {
t.Fatal(err)
}
if out.Ret != -1 {
t.Fatalf("expected ret -1, got %d", out.Ret)
}
}
func containsStr(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsStrHelper(s, substr))
}
func containsStrHelper(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
type roundTripFunc func(*http.Request) (*http.Response, error)
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
return f(r)
}