Files
Cyrene_v2/plugins/channels/telegram/config_test.go
T

218 lines
6.0 KiB
Go

package telegram
import (
"context"
"errors"
"net/http"
"sync"
"testing"
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
)
func TestName(t *testing.T) {
t.Parallel()
a := New(Config{BotToken: "x"})
if got := a.Name(); got != "telegram" {
t.Fatalf("Name() = %q, want telegram", got)
}
}
func TestConfig(t *testing.T) {
t.Parallel()
a := New(Config{BotToken: "abc123", WebhookURL: "https://example.com/hook"})
got := a.Config()
if got["bot_token"] != "abc123" {
t.Fatalf("bot_token = %v, want abc123", got["bot_token"])
}
if got["webhook_url"] != "https://example.com/hook" {
t.Fatalf("webhook_url = %v, want https://example.com/hook", got["webhook_url"])
}
// Round-trip through the shared decoder to prove the map is decodable.
var round Config
if err := adapter.DecodeConfig(got, &round); err != nil {
t.Fatalf("DecodeConfig: %v", err)
}
if round.BotToken != "abc123" || round.WebhookURL != "https://example.com/hook" {
t.Fatalf("round trip mismatch: %+v", round)
}
}
func TestCapabilities(t *testing.T) {
t.Parallel()
a := New(Config{BotToken: "x"})
caps := a.Capabilities()
if !caps.Proactive {
t.Error("Proactive should be true for telegram")
}
if !caps.Markdown || !caps.Image || !caps.Voice {
t.Errorf("expected Markdown/Image/Voice supported, got %+v", caps)
}
if caps.Reaction || caps.TypingHint {
t.Errorf("expected Reaction/TypingHint unsupported, got %+v", caps)
}
if caps.MaxMessageLength != 4096 {
t.Errorf("MaxMessageLength = %d, want 4096", caps.MaxMessageLength)
}
if caps.RecommendBurstMax <= 0 {
t.Errorf("RecommendBurstMax should be positive, got %d", caps.RecommendBurstMax)
}
}
func TestListChannels_Empty(t *testing.T) {
t.Parallel()
a := New(Config{BotToken: "x"})
got, err := a.ListChannels(context.Background())
if err != nil {
t.Fatalf("ListChannels: %v", err)
}
if got == nil || len(got) != 0 {
t.Fatalf("expected empty non-nil slice, got %#v", got)
}
}
func TestConnect_MissingToken(t *testing.T) {
t.Parallel()
a := New(Config{})
err := a.Connect(context.Background())
if !errors.Is(err, ErrMissingToken) {
t.Fatalf("err = %v, want ErrMissingToken", err)
}
if a.IsConnected() {
t.Fatal("should not be connected")
}
}
func TestConnect_WebhookIdempotent(t *testing.T) {
t.Parallel()
var mu sync.Mutex
calls := 0
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if methodOf(r) != "setWebhook" {
http.Error(w, "unexpected method", http.StatusNotFound)
return
}
mu.Lock()
calls++
mu.Unlock()
okResponse(w, map[string]any{"webhook_url": "https://example.com/hook"})
}))
a.cfg.WebhookURL = "https://example.com/hook"
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
if !a.IsConnected() {
t.Fatal("expected connected")
}
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("second Connect should be idempotent: %v", err)
}
mu.Lock()
if calls != 1 {
t.Fatalf("expected 1 setWebhook call, got %d", calls)
}
mu.Unlock()
}
func TestConnect_PollingMode(t *testing.T) {
t.Parallel()
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "no api call expected in poll mode", http.StatusNotFound)
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
if !a.IsConnected() {
t.Fatal("expected connected")
}
if a.isWebhookMode() {
t.Fatal("expected polling mode")
}
}
func TestDisconnect_Idempotent(t *testing.T) {
t.Parallel()
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "no api call expected", http.StatusNotFound)
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
if err := a.Disconnect(context.Background()); err != nil {
t.Fatalf("Disconnect: %v", err)
}
if a.IsConnected() {
t.Fatal("expected disconnected")
}
if err := a.Disconnect(context.Background()); err != nil {
t.Fatalf("second Disconnect should be idempotent: %v", err)
}
}
func TestClose_Terminal(t *testing.T) {
t.Parallel()
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "no api call expected", http.StatusNotFound)
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
if err := a.Close(); err != nil {
t.Fatalf("Close: %v", err)
}
if a.IsConnected() {
t.Fatal("expected disconnected after close")
}
if err := a.Connect(context.Background()); !errors.Is(err, adapter.ErrClosed) {
t.Fatalf("Connect after close err = %v, want ErrClosed", err)
}
if err := a.HealthCheck(context.Background()); !errors.Is(err, adapter.ErrClosed) {
t.Fatalf("HealthCheck after close err = %v, want ErrClosed", err)
}
}
func TestHealthCheck_OK(t *testing.T) {
t.Parallel()
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if methodOf(r) != "getMe" {
http.Error(w, "unexpected method", http.StatusNotFound)
return
}
okResponse(w, map[string]any{"id": 123, "username": "cyrene_bot"})
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
if err := a.HealthCheck(context.Background()); err != nil {
t.Fatalf("HealthCheck: %v", err)
}
}
func TestHealthCheck_NotConnected(t *testing.T) {
t.Parallel()
a := New(Config{BotToken: "x"})
err := a.HealthCheck(context.Background())
if !errors.Is(err, adapter.ErrNotConnected) {
t.Fatalf("err = %v, want ErrNotConnected", err)
}
}
func TestHealthCheck_APIError(t *testing.T) {
t.Parallel()
a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if methodOf(r) != "getMe" {
http.Error(w, "unexpected method", http.StatusNotFound)
return
}
failResponse(w, "Unauthorized", 401)
}))
if err := a.Connect(context.Background()); err != nil {
t.Fatalf("Connect: %v", err)
}
err := a.HealthCheck(context.Background())
if !errors.Is(err, ErrBadResponse) {
t.Fatalf("err = %v, want ErrBadResponse", err)
}
}