Files
Cyrene_v2/internal/services/gateway/admin_channels_platform_test.go
T
AskaEth 074ffc4ebf
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(gateway): gateway 剩余文件注释改中文(逐行 1:1,代码零字节改动)
主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批:
已推库 16 个文件 + 本批剩余文件)。
校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0);
全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
2026-10-03 22:28:25 +08:00

158 lines
5.5 KiB
Go

package gateway
// 演练按平台的 KOOK 适配器面板端点(design/17 §2)。适配器
// 经由一个内嵌 adapter.PlatformAdapter 的包装器注册 —— 仿照
// cmd/cyrene 的 namedAdapter —— 因此这些测试也覆盖网关用来
// 抵达具体 *kook.Adapter 的反射拆包。
import (
"encoding/json"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"testing"
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry"
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
"git.yeij.top/AskaEth/Cyrene_v2/plugins/channels/kook"
)
func gatewayKookLogger() *slog.Logger {
return slog.New(slog.NewTextHandler(io.Discard, nil))
}
// gatewayKookWrap 按 cmd/cyrene 的 namedAdapter 的方式内嵌
// adapter.PlatformAdapter,从而演练网关基于反射的拆包(asKookAdmin)。
type gatewayKookWrap struct {
adapter.PlatformAdapter
name string
}
func (w *gatewayKookWrap) Name() string { return w.name }
// gatewayKookServer 应答面板视图会触及的 KOOK REST 表面。
func gatewayKookServer(t *testing.T) *httptest.Server {
t.Helper()
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.URL.Path {
case "/friend":
_ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "data": map[string]any{
"friend": []any{map[string]any{"id": 1, "friend_info": map[string]any{"id": "u1", "username": "alice"}}},
}})
case "/user-chat/list":
_ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "data": map[string]any{
"items": []any{map[string]any{"code": "c1", "target_info": map[string]any{"id": "u9", "username": "zed"}}},
}})
case "/guild/list":
_ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "data": map[string]any{
"items": []any{map[string]any{"id": "g1", "name": "server"}},
}})
case "/channel/list":
_ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "data": map[string]any{
"items": []any{map[string]any{"id": "c1", "guild_id": "g1", "name": "general", "type": 1}},
}})
case "/guild/user-list":
_ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "data": map[string]any{
"items": []any{map[string]any{"id": "u1", "username": "alice", "online": true}},
}})
case "/friend/handle-request":
_ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "data": map[string]any{}})
case "/message/create":
_ = json.NewEncoder(w).Encode(map[string]any{"code": 0, "data": map[string]any{"msg_id": "m1"}})
default:
http.NotFound(w, r)
}
})
srv := httptest.NewServer(h)
t.Cleanup(srv.Close)
return srv
}
// newKookGateway 接线一个注册表,其 kook 渠道包装一个指向 httptest
// KOOK 服务器的真实适配器,并返回 owner JWT。
func newKookGateway(t *testing.T, name string) (string, string) {
t.Helper()
kookSrv := gatewayKookServer(t)
ka := kook.New(
kook.Config{BotToken: "testtoken"},
kook.WithBaseURL(kookSrv.URL),
kook.WithHTTPClient(kookSrv.Client()),
kook.WithLogger(gatewayKookLogger()),
)
reg := registry.New()
if err := reg.RegisterChannel(&gatewayKookWrap{PlatformAdapter: ka, name: name}); err != nil {
t.Fatalf("register channel: %v", err)
}
_, ts, jwt := buildEnv(t, cfgDefault(), Deps{Registry: reg})
tok := signAny(t, jwt, "acct", auth.RoleOwner)
return ts.URL, tok
}
func TestAdminChannelPlatformView_Kook(t *testing.T) {
t.Parallel()
base, tok := newKookGateway(t, "kook:conf0")
resp, body := doReq(t, http.MethodGet, base+"/api/v1/admin/channels/kook/view?accountId=conf0", nil, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("view status = %d, body=%s", resp.StatusCode, body)
}
var out struct {
Data struct {
Platform string `json:"platform"`
Friends *struct {
Friends []struct{ UserID string `json:"user_id"` } `json:"friends"`
} `json:"friends"`
Guilds []struct{ ID string `json:"id"` } `json:"guilds"`
} `json:"data"`
}
if err := json.Unmarshal(body, &out); err != nil {
t.Fatalf("decode view: %v", err)
}
if out.Data.Platform != "kook" {
t.Errorf("platform = %q, want kook", out.Data.Platform)
}
if out.Data.Friends == nil || len(out.Data.Friends.Friends) != 1 || out.Data.Friends.Friends[0].UserID != "u1" {
t.Errorf("friends = %+v, want [u1]", out.Data.Friends)
}
if len(out.Data.Guilds) != 1 || out.Data.Guilds[0].ID != "g1" {
t.Errorf("guilds = %+v, want [g1]", out.Data.Guilds)
}
}
func TestAdminChannelPlatformView_NonKookNotImplemented(t *testing.T) {
t.Parallel()
_, ts, jwt := buildEnv(t, cfgDefault(), Deps{})
tok := signAny(t, jwt, "acct", auth.RoleOwner)
resp, body := doReq(t, http.MethodGet, ts.URL+"/api/v1/admin/channels/telegram/view", nil, tok)
if resp.StatusCode != http.StatusNotImplemented {
t.Fatalf("status = %d, want 501 (body=%s)", resp.StatusCode, body)
}
}
func TestAdminChannelFriendHandle_Kook(t *testing.T) {
t.Parallel()
base, tok := newKookGateway(t, "kook:conf0")
resp, body := doReq(t, http.MethodPost, base+"/api/v1/admin/channels/kook/friend/handle?accountId=conf0",
map[string]any{"id": 77, "accept": true}, tok)
if resp.StatusCode != http.StatusOK {
t.Fatalf("handle status = %d, body=%s", resp.StatusCode, body)
}
var out struct {
Data struct {
Handled bool `json:"handled"`
ID int64 `json:"id"`
} `json:"data"`
}
if err := json.Unmarshal(body, &out); err != nil {
t.Fatalf("decode handle: %v", err)
}
if !out.Data.Handled || out.Data.ID != 77 {
t.Errorf("handle = %+v, want handled id 77", out.Data)
}
}