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) } }