agent-116 KOOK token即用+自动获取:
- kook/identity.go: Connect 调 GET /user/me 拿 bot 身份(bot_id/bot_username/bot_nickname), 失败容错
- 渠道列表 enrichAccountsWithBot 附 bot 身份; base_url空用默认/receive_mode默认ws(只填token可跑)
agent-117 action/narration 剥出气泡(主人需求: 动作/旁白不进气泡,按位置上/下):
- parseContent.splitBubble(content)->{body,head,tail}: body=纯文本(去标签), head=开头/中间action/narr(气泡上), tail=结尾(气泡下)
- renderContent 只渲染text(去内联灰斜体); chat.tsx BubbleNotes 泡外 *动作/旁白* 灰斜体小字(气泡外依附), SplitChatBubble 先剥splitBubble再splitParagraphs(body), <p>/\n\n/毛玻璃/SourceLabel不破坏
- 注: 非chat mode renderContent 现在丢弃action/narr(可接受)
Channels 配置表单回填(修save后输入框空):
- openConfig 从 data.accounts[0].config 回填 token(掩码提示)/base_url/平台字段/黑白名单/限流, 不再硬清空
全仓 build/vet/test绿+前端build绿
98 lines
3.7 KiB
Go
98 lines
3.7 KiB
Go
package kook
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
)
|
|
|
|
// TestFetchBotIdentity exercises the user/me lookup directly: a valid payload
|
|
// decodes into the bot account, and the adapter stores it.
|
|
func TestFetchBotIdentity(t *testing.T) {
|
|
t.Parallel()
|
|
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != userMePath {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
if got := r.Header.Get("Authorization"); got != "Bearer testtoken" {
|
|
t.Errorf("Authorization = %q, want Bearer testtoken", got)
|
|
}
|
|
kookOK(w, map[string]any{"id": "bot-1", "username": "bot", "nickname": "小K"})
|
|
}))
|
|
bi, err := a.fetchBotIdentity(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("fetchBotIdentity: %v", err)
|
|
}
|
|
if bi.ID != "bot-1" || bi.Username != "bot" || bi.Nickname != "小K" {
|
|
t.Fatalf("bot identity = %+v, want bot-1/bot/小K", bi)
|
|
}
|
|
a.setBotIdentity(bi)
|
|
if id, username, nickname := a.BotIdentity(); id != "bot-1" || username != "bot" || nickname != "小K" {
|
|
t.Errorf("BotIdentity = %q/%q/%q, want bot-1/bot/小K", id, username, nickname)
|
|
}
|
|
}
|
|
|
|
// TestFetchBotIdentity_EmptyID confirms that a user/me payload with a blank id is
|
|
// a bad response (a caller can distinguish "no account" from "not yet fetched").
|
|
func TestFetchBotIdentity_EmptyID(t *testing.T) {
|
|
t.Parallel()
|
|
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
kookOK(w, map[string]any{"username": "bot"})
|
|
}))
|
|
if _, err := a.fetchBotIdentity(context.Background()); err == nil {
|
|
t.Fatal("expected an error for a user/me payload with no id")
|
|
}
|
|
}
|
|
|
|
// TestRefreshBotIdentity_FailureTolerant verifies the best-effort behavior: a
|
|
// failed user/me lookup (bad token / network) logs and leaves the identity empty,
|
|
// but does not abort the connect.
|
|
func TestRefreshBotIdentity_FailureTolerant(t *testing.T) {
|
|
t.Parallel()
|
|
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
kookFail(w, 401, "invalid token")
|
|
}))
|
|
// Must not panic; identity stays empty.
|
|
a.refreshBotIdentity(context.Background())
|
|
if id, username, nickname := a.BotIdentity(); id != "" || username != "" || nickname != "" {
|
|
t.Errorf("BotIdentity after failure = %q/%q/%q, want empty", id, username, nickname)
|
|
}
|
|
}
|
|
|
|
// TestBotIdentityConfigSnapshot confirms the resolved bot identity is surfaced
|
|
// through Config() (non-sensitive keys) so the admin read path can display it.
|
|
func TestBotIdentityConfigSnapshot(t *testing.T) {
|
|
t.Parallel()
|
|
a := New(Config{BotToken: "x"})
|
|
a.setBotIdentity(botIdentity{ID: "bot-5", Username: "bot5", Nickname: "Five"})
|
|
cfg := a.Config()
|
|
if cfg["bot_id"] != "bot-5" || cfg["bot_username"] != "bot5" || cfg["bot_nickname"] != "Five" {
|
|
t.Errorf("config bot identity = %v/%v/%v, want bot-5/bot5/Five",
|
|
cfg["bot_id"], cfg["bot_username"], cfg["bot_nickname"])
|
|
}
|
|
}
|
|
|
|
// TestConfigDefaults_NoRequirement asserts the "token 即用" contract: with only a
|
|
// token, base_url/empty receive_mode/webhook_path resolve to sensible defaults and
|
|
// no additional field is required to connect.
|
|
func TestConfigDefaults_NoRequirement(t *testing.T) {
|
|
t.Parallel()
|
|
a := New(Config{BotToken: "token"})
|
|
if err := a.cfg.Validate(); err != nil {
|
|
t.Fatalf("Validate with token only = %v, want nil", err)
|
|
}
|
|
got := a.Config()
|
|
if got["base_url"] != defaultBaseURL {
|
|
t.Errorf("base_url = %v, want default %s", got["base_url"], defaultBaseURL)
|
|
}
|
|
if got["receive_mode"] != modeWS {
|
|
t.Errorf("receive_mode = %v, want %q", got["receive_mode"], modeWS)
|
|
}
|
|
// Webhook mode with an empty WebhookPath uses the default route.
|
|
wh := New(Config{BotToken: "t", ReceiveMode: modeWebhook})
|
|
if p := wh.WebhookPath(); p != defaultWebhookPath {
|
|
t.Errorf("WebhookPath = %q, want %s", p, defaultWebhookPath)
|
|
}
|
|
}
|