mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-06-05 22:43:59 +00:00
ac7cae8fa7
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
138 lines
3.5 KiB
Go
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()
|
|
}
|
|
}
|