package gateway // Exercises the per-platform KOOK adapter panel endpoints (design/17 §2). The // adapter is registered through a wrapper that embeds adapter.PlatformAdapter — // mirroring cmd/cyrene's namedAdapter — so these tests also cover the reflection // unwrap the gateway uses to reach the concrete *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 embeds adapter.PlatformAdapter the way cmd/cyrene's namedAdapter // does, so the gateway's reflection-based unwrap (asKookAdmin) is exercised. type gatewayKookWrap struct { adapter.PlatformAdapter name string } func (w *gatewayKookWrap) Name() string { return w.name } // gatewayKookServer answers the KOOK REST surface a panel view touches. 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 wires a registry whose kook channel wraps a real adapter pointed // at an httptest KOOK server, and returns the 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) } }