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 != "Bot testtoken" { t.Errorf("Authorization = %q, want Bot 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) } }