package main import ( "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "sync/atomic" "testing" "time" ) func TestHTTPHandler_ForwardAndReply(t *testing.T) { ft := NewFakeTelegram(t) var receivedBody []byte var receivedSlug string upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { receivedBody, _ = io.ReadAll(r.Body) receivedSlug = r.Header.Get("X-Bot-Slug") w.Header().Set("Content-Type", "application/json") _, _ = fmt.Fprint(w, `{"text":"pong"}`) })) defer upstream.Close() h, err := NewHTTPHandler(ft.Client(), HTTPConfig{URL: upstream.URL, Timeout: 2 * time.Second}) if err != nil { t.Fatalf("NewHTTPHandler: %v", err) } bot := Bot{Slug: "ping", Token: "t1", Secret: "s"} upd := MakeUpdate(1, 100, 42, 1, "ping") if err := h.Handle(bgCtx(), upd, bot); err != nil { t.Fatalf("handle: %v", err) } // Upstream got the Update verbatim var got Update if err := json.Unmarshal(receivedBody, &got); err != nil { t.Fatalf("upstream body decode: %v", err) } if got.UpdateID != 1 || got.Message.Text != "ping" { t.Fatalf("upstream body wrong: %+v", got) } if receivedSlug != "ping" { t.Fatalf("X-Bot-Slug = %q, want ping", receivedSlug) } // Telegram sendMessage was called with the upstream's text sent := ft.Sent() if len(sent) != 1 || sent[0].Text != "pong" || sent[0].ChatID != 42 { t.Fatalf("sendMessage wrong: %+v", sent) } } func TestHTTPHandler_EmptyBody_NoSend(t *testing.T) { ft := NewFakeTelegram(t) upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) defer upstream.Close() h, _ := NewHTTPHandler(ft.Client(), HTTPConfig{URL: upstream.URL, Timeout: time.Second}) bot := Bot{Slug: "x", Token: "t", Secret: "s"} if err := h.Handle(bgCtx(), MakeUpdate(1, 1, 1, 1, "hi"), bot); err != nil { t.Fatalf("handle: %v", err) } if len(ft.Sent()) != 0 { t.Fatalf("empty upstream body should not trigger sendMessage") } } func TestHTTPHandler_Non2xx_ReturnsError(t *testing.T) { ft := NewFakeTelegram(t) upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = fmt.Fprint(w, "boom") })) defer upstream.Close() h, _ := NewHTTPHandler(ft.Client(), HTTPConfig{URL: upstream.URL, Timeout: time.Second}) bot := Bot{Slug: "x", Token: "t", Secret: "s"} err := h.Handle(bgCtx(), MakeUpdate(1, 1, 1, 1, "hi"), bot) if err == nil { t.Fatal("expected error on non-2xx upstream") } } func TestHTTPHandler_Timeout(t *testing.T) { ft := NewFakeTelegram(t) var calls int32 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt32(&calls, 1) time.Sleep(200 * time.Millisecond) w.Header().Set("Content-Type", "application/json") _, _ = fmt.Fprint(w, `{"text":"too late"}`) })) defer upstream.Close() h, _ := NewHTTPHandler(ft.Client(), HTTPConfig{URL: upstream.URL, Timeout: 50 * time.Millisecond}) bot := Bot{Slug: "x", Token: "t", Secret: "s"} err := h.Handle(bgCtx(), MakeUpdate(1, 1, 1, 1, "hi"), bot) if err == nil { t.Fatal("expected timeout error") } if atomic.LoadInt32(&calls) == 0 { t.Fatal("upstream should have been called once") } } func TestHTTPHandler_RequiresURL(t *testing.T) { ft := NewFakeTelegram(t) if _, err := NewHTTPHandler(ft.Client(), HTTPConfig{URL: ""}); err == nil { t.Fatal("empty URL should fail config validation") } }