package main
import (
+ "context"
"encoding/json"
"fmt"
"io"
}, server.Calls()[0].params)
}
+func TestSendMessageTimesOutOnAStalledConnection(t *testing.T) {
+ server := newFakeBot(t)
+ server.stall = func(call fakeBotCall) bool {
+ return call.method == "sendMessage"
+ }
+
+ api := newBotAPI(server.URL, testBotToken)
+ api.sendTimeout = 50 * time.Millisecond
+ start := time.Now()
+ err := api.SendMessage(t.Context(), testOwner, "hi")
+ require.ErrorIs(t, err, errBotNetwork)
+ require.Less(t, time.Since(start), 5*time.Second)
+ require.NotContains(t, err.Error(), testBotToken)
+ require.NotContains(t, err.Error(), server.URL)
+
+ retry, _ := retryable(err)
+ require.True(t, retry)
+
+ api.sendTimeout = time.Hour
+ ctx, cancel := context.WithCancel(t.Context())
+ time.AfterFunc(50*time.Millisecond, cancel)
+ start = time.Now()
+ err = api.SendMessage(ctx, testOwner, "hi")
+ require.Error(t, err)
+ require.Less(t, time.Since(start), 5*time.Second)
+}
+
func TestBotErrorsDoNotHoldTheToken(t *testing.T) {
testCases := []struct {
name string
// fakeBot is a fake Bot API server for testBotToken. getUpdates returns the
// added updates from the offset on, or waits a short time when there are none.
// The other methods succeed, unless fail gives an error answer for a call. An
-// answer with HTTP 429 asks for a wait of one second. Fail runs with the mutex
-// held.
+// answer with HTTP 429 asks for a wait of one second. When stall is true for a
+// call, the call gets no answer until the client gives it up or the test ends.
+// Fail and stall run with the mutex held.
type fakeBot struct {
*httptest.Server
updates []botUpdate
calls []fakeBotCall
fail func(call fakeBotCall) (int, string)
+ stall func(call fakeBotCall) bool
added chan struct{}
+ stopped chan struct{}
}
// fakeBotCall is one call of the fake Bot API. OK tells if the call got a
t.Helper()
f := &fakeBot{
- added: make(chan struct{}, 1),
+ added: make(chan struct{}, 1),
+ stopped: make(chan struct{}),
}
f.Server = httptest.NewServer(http.HandlerFunc(f.serve))
t.Cleanup(f.Close)
+ t.Cleanup(func() {
+ close(f.stopped)
+ })
return f
}
status, description = f.fail(call)
}
+ stall := f.stall != nil && f.stall(call)
f.mutex.Unlock()
+ if stall {
+ f.record(call)
+ select {
+ case <-r.Context().Done():
+ case <-f.stopped:
+ }
+
+ return
+ }
+
if status != 0 {
f.record(call)
w.WriteHeader(status)