Files
go-telegram/transport/webhook_test.go
T
lukaszraczylo ac7cae8fa7 Initial release of go-telegram
A fully-generated, strongly-typed Go client for the Telegram Bot API.

* 176 methods + 301 types generated from Bot API v10.0
* 1408 auto-generated tests (8 scenarios per method)
* Typed unions throughout — no 'any' in the public surface
* Pluggable HTTP transport and JSON codec (default goccy/go-json)
* Built-in retry middleware honouring Telegram's retry_after
* Generic dispatcher with filters and conversation handlers
* Self-verifying codegen pipeline (regen → audit → emit → run tests)
* 14 example bots covering common patterns
2026-05-09 13:09:27 +01:00

138 lines
3.5 KiB
Go

package transport
import (
"bytes"
"context"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"
"github.com/lukaszraczylo/go-telegram/client"
"github.com/stretchr/testify/require"
)
func TestWebhook_DeliversUpdate(t *testing.T) {
b := client.New("t")
w := NewWebhookServer(b)
w.SecretToken = "secret"
srv := httptest.NewServer(w)
t.Cleanup(srv.Close)
body := `{"update_id":1,"message":{"message_id":1,"date":0,"chat":{"id":1,"type":"private"},"text":"hi"}}`
req, _ := http.NewRequest(http.MethodPost, srv.URL, strings.NewReader(body))
req.Header.Set("X-Telegram-Bot-Api-Secret-Token", "secret")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
_ = resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode)
select {
case u := <-w.Updates():
require.Equal(t, int64(1), u.UpdateID)
case <-time.After(time.Second):
t.Fatal("update not delivered")
}
}
func TestWebhook_RejectsBadSecret(t *testing.T) {
b := client.New("t")
w := NewWebhookServer(b)
w.SecretToken = "secret"
srv := httptest.NewServer(w)
t.Cleanup(srv.Close)
req, _ := http.NewRequest(http.MethodPost, srv.URL, strings.NewReader(`{}`))
req.Header.Set("X-Telegram-Bot-Api-Secret-Token", "wrong")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
_ = resp.Body.Close()
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
}
func TestWebhook_RejectsNonPOST(t *testing.T) {
w := NewWebhookServer(client.New("t"))
srv := httptest.NewServer(w)
t.Cleanup(srv.Close)
resp, err := http.Get(srv.URL)
require.NoError(t, err)
_ = resp.Body.Close()
require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
}
func TestWebhook_RejectsBadJSON(t *testing.T) {
w := NewWebhookServer(client.New("t"))
srv := httptest.NewServer(w)
t.Cleanup(srv.Close)
resp, err := http.Post(srv.URL, "application/json", bytes.NewBufferString("not json"))
require.NoError(t, err)
_ = resp.Body.Close()
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
}
func TestWebhook_StopExitsRun(t *testing.T) {
w := NewWebhookServer(client.New("t"))
done := make(chan struct{})
go func() { _ = w.Run(context.Background()); close(done) }()
require.NoError(t, w.Stop(context.Background()))
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("Run did not exit after Stop")
}
}
// TestWebhook_ConcurrentStopNoPanic fires many concurrent requests while
// simultaneously calling Stop, and asserts no panic (send on closed channel).
// Run under -race to verify mutex and WaitGroup correctness.
func TestWebhook_ConcurrentStopNoPanic(t *testing.T) {
body := `{"update_id":1,"message":{"message_id":1,"date":0,"chat":{"id":1,"type":"private"},"text":"hi"}}`
for range 20 {
w := NewWebhookServer(client.New("t"), WithBufferSize(256))
srv := httptest.NewServer(w)
// Drain updates so ServeHTTP doesn't block on a full channel.
go func() {
for range w.Updates() {
}
}()
// Run in background.
go func() { _ = w.Run(context.Background()) }()
// Fire concurrent requests.
const goroutines = 20
ready := make(chan struct{})
var wg sync.WaitGroup
for i := 0; i < goroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
<-ready
for j := 0; j < 5; j++ {
req, _ := http.NewRequest(http.MethodPost, srv.URL, strings.NewReader(body))
resp, err := http.DefaultClient.Do(req)
if err == nil {
_ = resp.Body.Close()
}
}
}()
}
close(ready)
time.Sleep(5 * time.Millisecond) // let some requests land before Stop
srv.Close()
_ = w.Stop(context.Background())
wg.Wait()
}
}