X-Git-Url: http://git.giacomolli.uk/onechat/blobdiff_plain/fb242538c1b012b0faf3cc4045a9b1c370f1ef42:/botapi_test.go..09cde69fdffe2721434392036f98dc241177f5a1:/telegram_api_test.go diff --git a/botapi_test.go b/telegram_api_test.go similarity index 68% rename from botapi_test.go rename to telegram_api_test.go index fb57b45..bbfd93e 100644 --- a/botapi_test.go +++ b/telegram_api_test.go @@ -16,22 +16,22 @@ import ( ) const ( - // testBotToken is the bot token of the tests. It has the form of a real - // token. - testBotToken = "123456:TEST-secret_token" + // telegramTestToken is the bot token of the tests. It has the form of a + // real token. + telegramTestToken = "123456:TEST-secret_token" - // testOwner is the user ID of the owner of the test bot. - testOwner = 4242 + // telegramTestOwner is the user ID of the owner of the test bot. + telegramTestOwner = 4242 ) func TestGetUpdatesAsksForMessagesFromTheOffset(t *testing.T) { - server := newFakeBot(t) - server.Add(botUpdate{ + server := newTelegramFake(t) + server.Add(TelegramUpdate{ UpdateID: 7, - Message: ownerMessage("hello"), + Message: telegramOwnerMessage("hello"), }) - updates, err := newBotAPI(server.URL, testBotToken).GetUpdates(t.Context(), 7) + updates, err := newTelegramAPI(server.URL, telegramTestToken).GetUpdates(t.Context(), 7) require.NoError(t, err) require.Len(t, updates, 1) require.Equal(t, int64(7), updates[0].UpdateID) @@ -41,25 +41,25 @@ func TestGetUpdatesAsksForMessagesFromTheOffset(t *testing.T) { require.Equal(t, "getUpdates", call.method) require.Equal(t, map[string]any{ "offset": float64(7), - "timeout": float64(pollSeconds), + "timeout": float64(telegramPollSeconds), "allowed_updates": []any{"message"}, }, call.params) } func TestSendMessageSendsPlainText(t *testing.T) { - server := newFakeBot(t) - err := newBotAPI(server.URL, testBotToken).SendMessage(t.Context(), testOwner, "*not bold*", nil) + server := newTelegramFake(t) + err := newTelegramAPI(server.URL, telegramTestToken).SendMessage(t.Context(), telegramTestOwner, "*not bold*", nil) require.NoError(t, err) require.Equal(t, map[string]any{ - "chat_id": float64(testOwner), + "chat_id": float64(telegramTestOwner), "text": "*not bold*", }, server.Calls()[0].params) } func TestSendMessageSendsTheEntities(t *testing.T) { - server := newFakeBot(t) - err := newBotAPI(server.URL, testBotToken).SendMessage(t.Context(), testOwner, "a link", []botEntity{ + server := newTelegramFake(t) + err := newTelegramAPI(server.URL, telegramTestToken).SendMessage(t.Context(), telegramTestOwner, "a link", []TelegramEntity{ { Type: "text_link", Offset: 2, @@ -80,15 +80,15 @@ func TestSendMessageSendsTheEntities(t *testing.T) { } func TestSendRichMessageSendsTheMarkdownUnchanged(t *testing.T) { - server := newFakeBot(t) + server := newTelegramFake(t) markdown := "| a | b |\n|---|---|\n| **1** | `2` |" - err := newBotAPI(server.URL, testBotToken).SendRichMessage(t.Context(), testOwner, markdown) + err := newTelegramAPI(server.URL, telegramTestToken).SendRichMessage(t.Context(), telegramTestOwner, markdown) require.NoError(t, err) call := server.Calls()[0] require.Equal(t, "sendRichMessage", call.method) require.Equal(t, map[string]any{ - "chat_id": float64(testOwner), + "chat_id": float64(telegramTestOwner), "rich_message": map[string]any{ "markdown": markdown, }, @@ -98,39 +98,39 @@ func TestSendRichMessageSendsTheMarkdownUnchanged(t *testing.T) { func TestSendCallsTimeOutOnAStalledConnection(t *testing.T) { testCases := []struct { method string - send func(ctx context.Context, api *botAPI) error + send func(ctx context.Context, api *TelegramAPI) error }{ { method: "sendMessage", - send: func(ctx context.Context, api *botAPI) error { - return api.SendMessage(ctx, testOwner, "hi", nil) + send: func(ctx context.Context, api *TelegramAPI) error { + return api.SendMessage(ctx, telegramTestOwner, "hi", nil) }, }, { method: "sendRichMessage", - send: func(ctx context.Context, api *botAPI) error { - return api.SendRichMessage(ctx, testOwner, "hi") + send: func(ctx context.Context, api *TelegramAPI) error { + return api.SendRichMessage(ctx, telegramTestOwner, "hi") }, }, } for _, tc := range testCases { t.Run(tc.method, func(t *testing.T) { - server := newFakeBot(t) - server.stall = func(call fakeBotCall) bool { + server := newTelegramFake(t) + server.stall = func(call TelegramFakeCall) bool { return call.method == tc.method } - api := newBotAPI(server.URL, testBotToken) + api := newTelegramAPI(server.URL, telegramTestToken) api.sendTimeout = 50 * time.Millisecond start := time.Now() err := tc.send(t.Context(), api) - require.ErrorIs(t, err, errBotNetwork) + require.ErrorIs(t, err, errTelegramNetwork) require.Less(t, time.Since(start), 5*time.Second) - require.NotContains(t, err.Error(), testBotToken) + require.NotContains(t, err.Error(), telegramTestToken) require.NotContains(t, err.Error(), server.URL) - retry, _ := retryable(err) + retry, _ := telegramRetryable(err) require.True(t, retry) api.sendTimeout = time.Hour @@ -187,7 +187,7 @@ func TestBotErrorsDoNotHoldTheToken(t *testing.T) { name: "token echoed in a description is hidden", reply: func(w http.ResponseWriter) { w.WriteHeader(http.StatusUnauthorized) - _, _ = io.WriteString(w, `{"ok":false,"error_code":401,"description":"Unauthorized: bot`+testBotToken+`"}`) + _, _ = io.WriteString(w, `{"ok":false,"error_code":401,"description":"Unauthorized: bot`+telegramTestToken+`"}`) }, retry: false, errorText: "Unauthorized: botTOKEN", @@ -238,72 +238,54 @@ func TestBotErrorsDoNotHoldTheToken(t *testing.T) { })) t.Cleanup(server.Close) - err := newBotAPI(server.URL, testBotToken).SendMessage(t.Context(), testOwner, "hi", nil) + err := newTelegramAPI(server.URL, telegramTestToken).SendMessage(t.Context(), telegramTestOwner, "hi", nil) require.ErrorContains(t, err, tc.errorText) - require.NotContains(t, err.Error(), testBotToken) + require.NotContains(t, err.Error(), telegramTestToken) require.NotContains(t, err.Error(), server.URL) - retry, wait := retryable(err) + retry, wait := telegramRetryable(err) require.Equal(t, tc.retry, retry) require.Equal(t, tc.wait, wait) - require.Equal(t, tc.rejected, rejected(err)) + require.Equal(t, tc.rejected, telegramRejected(err)) }) } server := httptest.NewServer(http.NotFoundHandler()) server.Close() - _, err := newBotAPI(server.URL, testBotToken).GetUpdates(t.Context(), 0) - require.ErrorIs(t, err, errBotNetwork) - require.NotContains(t, err.Error(), testBotToken) + _, err := newTelegramAPI(server.URL, telegramTestToken).GetUpdates(t.Context(), 0) + require.ErrorIs(t, err, errTelegramNetwork) + require.NotContains(t, err.Error(), telegramTestToken) } -// 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. 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 - - mutex sync.Mutex - 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 +// TelegramFakeCall is one call of the fake Bot API. OK tells if the call got a // success answer. -type fakeBotCall struct { +type TelegramFakeCall struct { method string params map[string]any ok bool } -// newFakeBot starts a fake Bot API server that stops at the end of the test. -func newFakeBot(t *testing.T) *fakeBot { - t.Helper() - - f := &fakeBot{ - 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) - }) +// TelegramFake is a fake Bot API server for telegramTestToken. 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. 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 TelegramFake struct { + *httptest.Server - return f + mutex sync.Mutex + updates []TelegramUpdate + calls []TelegramFakeCall + fail func(call TelegramFakeCall) (int, string) + stall func(call TelegramFakeCall) bool + added chan struct{} + stopped chan struct{} } // Add makes update available to getUpdates. -func (f *fakeBot) Add(update botUpdate) { +func (f *TelegramFake) Add(update TelegramUpdate) { f.mutex.Lock() f.updates = append(f.updates, update) f.mutex.Unlock() @@ -315,16 +297,16 @@ func (f *fakeBot) Add(update botUpdate) { } // Calls returns the calls so far. -func (f *fakeBot) Calls() []fakeBotCall { +func (f *TelegramFake) Calls() []TelegramFakeCall { f.mutex.Lock() defer f.mutex.Unlock() - return append([]fakeBotCall(nil), f.calls...) + return append([]TelegramFakeCall(nil), f.calls...) } // Sent returns the texts of the sendMessage calls and the Markdown of the // sendRichMessage calls that succeeded, in order. -func (f *fakeBot) Sent() []string { +func (f *TelegramFake) Sent() []string { var texts []string for _, call := range f.Calls() { switch { @@ -339,8 +321,8 @@ func (f *fakeBot) Sent() []string { return texts } -func (f *fakeBot) serve(w http.ResponseWriter, r *http.Request) { - method, found := strings.CutPrefix(r.URL.Path, "/bot"+testBotToken+"/") +func (f *TelegramFake) serve(w http.ResponseWriter, r *http.Request) { + method, found := strings.CutPrefix(r.URL.Path, "/bot"+telegramTestToken+"/") if !found { http.Error(w, `{"ok":false,"error_code":404,"description":"Not Found"}`, http.StatusNotFound) return @@ -353,7 +335,7 @@ func (f *fakeBot) serve(w http.ResponseWriter, r *http.Request) { return } - call := fakeBotCall{ + call := TelegramFakeCall{ method: method, params: params, } @@ -416,7 +398,7 @@ func (f *fakeBot) serve(w http.ResponseWriter, r *http.Request) { } // record keeps call. -func (f *fakeBot) record(call fakeBotCall) { +func (f *TelegramFake) record(call TelegramFakeCall) { f.mutex.Lock() defer f.mutex.Unlock() @@ -425,11 +407,11 @@ func (f *fakeBot) record(call fakeBotCall) { // from returns the updates from offset on, and forgets the ones before it, as // Telegram does. -func (f *fakeBot) from(offset int64) []botUpdate { +func (f *TelegramFake) from(offset int64) []TelegramUpdate { f.mutex.Lock() defer f.mutex.Unlock() - var updates []botUpdate + var updates []TelegramUpdate kept := f.updates[:0] for _, update := range f.updates { if update.UpdateID >= offset { @@ -443,15 +425,34 @@ func (f *fakeBot) from(offset int64) []botUpdate { return updates } -// ownerMessage returns a text message of the owner in the private chat. -func ownerMessage(text string) *botMessage { - return &botMessage{ - Chat: botChat{ - ID: testOwner, +// newTelegramFake starts a fake Bot API server that stops at the end of the +// test. +func newTelegramFake(t *testing.T) *TelegramFake { + t.Helper() + + f := &TelegramFake{ + 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 +} + +// telegramOwnerMessage returns a text message of the owner in the private chat. +func telegramOwnerMessage(text string) *TelegramMessage { + return &TelegramMessage{ + Chat: TelegramChat{ + ID: telegramTestOwner, Type: "private", }, - From: &botUser{ - ID: testOwner, + From: &TelegramUser{ + ID: telegramTestOwner, }, Text: text, }