Files
Cyrene_v2/plugins/channels/kook/send_test.go
T
Aska c1bd740f7c
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
fix(kook): KOOK 机器人鉴权用 Authorization: Bot <token> 而非 Bearer
httpclient 硬编码 Bearer,导致 KOOK bot token 被当 OAuth2 用户令牌发,
/ gateway/index 返回 401『登录环境异常』。按 KOOK reference 鉴权规范,
给共享 httpclient 增加可配置 authScheme(默认 Bearer),KOOK 用 Bot。

- httpclient: 新增 WithAuthScheme 选项 + authScheme 字段,do() 按 scheme 组装
- kook/adapter: 装配时传 WithAuthScheme("Bot")
- 同步修正 config.go 注释与 send/identity 测试断言
2026-09-07 16:59:53 +08:00

179 lines
5.4 KiB
Go

package kook
import (
"context"
"net/http"
"sync"
"testing"
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
)
func TestSendMessage_GroupText(t *testing.T) {
t.Parallel()
var mu sync.Mutex
var body map[string]any
var auth string
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != channelMessagePath {
http.Error(w, "unexpected path: "+r.URL.Path, http.StatusNotFound)
return
}
mu.Lock()
auth = r.Header.Get("Authorization")
body = decodeBody(r)
mu.Unlock()
kookOK(w, map[string]any{"msg_id": "x-1", "msg_timestamp": 1})
})
a, _ := newTestAdapter(t, h)
a.connected = true
resp := adapter.UnifiedResponse{
ReplyTo: "reply-99",
Messages: []adapter.UnifiedMessage{
{ChannelID: "chan-1", ChannelType: adapter.ChannelGroup, Content: "hello", ContentType: adapter.ContentText},
},
}
if err := a.SendMessage(context.Background(), resp); err != nil {
t.Fatalf("SendMessage: %v", err)
}
mu.Lock()
defer mu.Unlock()
if auth != "Bot testtoken" {
t.Errorf("Authorization = %q, want Bot testtoken", auth)
}
if body["target_id"] != "chan-1" {
t.Errorf("target_id = %v, want chan-1", body["target_id"])
}
if body["type"] != float64(9) {
t.Errorf("type = %v, want 9 (kmarkdown)", body["type"])
}
if body["content"] != "hello" {
t.Errorf("content = %v, want hello", body["content"])
}
if body["quote"] != "reply-99" {
t.Errorf("quote = %v, want reply-99", body["quote"])
}
}
func TestSendMessage_PrivateText(t *testing.T) {
t.Parallel()
var mu sync.Mutex
var body map[string]any
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != privateMessagePath {
http.Error(w, "unexpected path: "+r.URL.Path, http.StatusNotFound)
return
}
mu.Lock()
body = decodeBody(r)
mu.Unlock()
kookOK(w, map[string]any{"msg_id": "x-2", "msg_timestamp": 1})
})
a, _ := newTestAdapter(t, h)
a.connected = true
resp := adapter.UnifiedResponse{
Messages: []adapter.UnifiedMessage{
{ChannelID: "user-9", ChannelType: adapter.ChannelDirect, Content: "private hi", ContentType: adapter.ContentText},
},
}
if err := a.SendMessage(context.Background(), resp); err != nil {
t.Fatalf("SendMessage: %v", err)
}
mu.Lock()
defer mu.Unlock()
if body["target_id"] != "user-9" || body["content"] != "private hi" {
t.Errorf("body = %v, want target_id user-9 content private hi", body)
}
}
func TestSendMessage_MediaTypeMapping(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
ct adapter.ContentType
media adapter.Media
wantType int
}{
{name: "image", ct: adapter.ContentImage, media: adapter.Media{Kind: adapter.MediaImage, URL: "https://img/a.jpg"}, wantType: 2},
{name: "voice", ct: adapter.ContentVoice, media: adapter.Media{Kind: adapter.MediaVoice, URL: "https://img/a.mp3"}, wantType: 8},
{name: "video", ct: adapter.ContentFile, media: adapter.Media{Kind: adapter.MediaVideo, URL: "https://img/a.mp4"}, wantType: 3},
{name: "file", ct: adapter.ContentFile, media: adapter.Media{Kind: adapter.MediaFile, URL: "https://img/a.txt"}, wantType: 4},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var mu sync.Mutex
var body map[string]any
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != channelMessagePath {
http.Error(w, "unexpected path", http.StatusNotFound)
return
}
mu.Lock()
body = decodeBody(r)
mu.Unlock()
kookOK(w, map[string]any{"msg_id": "x", "msg_timestamp": 1})
})
a, _ := newTestAdapter(t, h)
a.connected = true
resp := adapter.UnifiedResponse{
Messages: []adapter.UnifiedMessage{
{ChannelID: "chan-1", ChannelType: adapter.ChannelGroup, ContentType: tc.ct, Media: []adapter.Media{tc.media}},
},
}
if err := a.SendMessage(context.Background(), resp); err != nil {
t.Fatalf("SendMessage: %v", err)
}
mu.Lock()
defer mu.Unlock()
if body["type"] != float64(tc.wantType) {
t.Errorf("type = %v, want %d", body["type"], tc.wantType)
}
if body["content"] != tc.media.URL {
t.Errorf("content = %v, want %s", body["content"], tc.media.URL)
}
})
}
}
func TestSendProactive_Private(t *testing.T) {
t.Parallel()
var mu sync.Mutex
var body map[string]any
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != privateMessagePath {
http.Error(w, "unexpected path", http.StatusNotFound)
return
}
mu.Lock()
body = decodeBody(r)
mu.Unlock()
kookOK(w, map[string]any{"msg_id": "x-3", "msg_timestamp": 1})
})
a, _ := newTestAdapter(t, h)
a.connected = true
if err := a.SendProactive(context.Background(), adapter.UnifiedMessage{
ChannelID: "user-9", ChannelType: adapter.ChannelDirect, Content: "reminder", ContentType: adapter.ContentText,
}); err != nil {
t.Fatalf("SendProactive: %v", err)
}
mu.Lock()
defer mu.Unlock()
if body["target_id"] != "user-9" || body["content"] != "reminder" {
t.Errorf("body = %v, want target_id user-9 content reminder", body)
}
}
func TestSendMessage_NoTarget(t *testing.T) {
t.Parallel()
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "unexpected", http.StatusNotFound)
}))
a.connected = true
err := a.SendMessage(context.Background(), adapter.UnifiedResponse{
Messages: []adapter.UnifiedMessage{{ContentType: adapter.ContentText, Content: "hi"}},
})
if err == nil {
t.Fatal("expected error for missing target")
}
}