mirror of
https://github.com/tiennm99/openai-status-bot.git
synced 2026-10-15 06:16:18 +00:00
fix(telegram): tolerate empty webhook delete response
This commit is contained in:
1 parent
259b2dfdbd
commit
76d7495b4b
2 files changed
+142
-2
No files matched your search
@@ -2,7 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
tgbot "github.com/go-telegram/bot"
|
||||
tgmodels "github.com/go-telegram/bot/models"
|
||||
"github.com/tiennm99/openai-status-bot/internal/bot"
|
||||
"github.com/tiennm99/openai-status-bot/internal/config"
|
||||
"github.com/tiennm99/openai-status-bot/internal/health"
|
||||
@@ -24,6 +27,11 @@ import (
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
type telegramWebhookManager interface {
|
||||
DeleteWebhook(context.Context, *tgbot.DeleteWebhookParams) (bool, error)
|
||||
GetWebhookInfo(context.Context) (*tgmodels.WebhookInfo, error)
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg, err := config.LoadFromEnv()
|
||||
if err != nil {
|
||||
@@ -103,7 +111,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
requestCtx, cancelRequest := contextWithOptionalTimeout(ctx, cfg.HTTPTimeout)
|
||||
_, err = telegramBot.DeleteWebhook(requestCtx, &tgbot.DeleteWebhookParams{DropPendingUpdates: false})
|
||||
err = clearTelegramWebhook(requestCtx, telegramBot)
|
||||
cancelRequest()
|
||||
if err != nil {
|
||||
logger.Error("delete telegram webhook", "error", redactTelegramRuntimeError(cfg.TelegramBotToken, err))
|
||||
@@ -163,3 +171,33 @@ func frameworkInitialOffset(nextOffset int64) (int64, bool) {
|
||||
}
|
||||
return nextOffset - 1, true
|
||||
}
|
||||
|
||||
func clearTelegramWebhook(ctx context.Context, telegramBot telegramWebhookManager) error {
|
||||
_, err := telegramBot.DeleteWebhook(ctx, &tgbot.DeleteWebhookParams{DropPendingUpdates: false})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isDeleteWebhookEmptyResponseError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
info, infoErr := telegramBot.GetWebhookInfo(ctx)
|
||||
if infoErr != nil {
|
||||
return fmt.Errorf("deleteWebhook returned an empty response and getWebhookInfo failed: %w", errors.Join(err, infoErr))
|
||||
}
|
||||
if info == nil {
|
||||
return fmt.Errorf("deleteWebhook returned an empty response and getWebhookInfo returned no webhook info: %w", err)
|
||||
}
|
||||
if info.URL != "" {
|
||||
return fmt.Errorf("deleteWebhook returned an empty response and webhook is still configured: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isDeleteWebhookEmptyResponseError(err error) bool {
|
||||
var syntaxErr *json.SyntaxError
|
||||
if !errors.As(err, &syntaxErr) {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(err.Error(), "error decode response body for method deleteWebhook, ,")
|
||||
}
|
||||
@@ -1,6 +1,16 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
tgbot "github.com/go-telegram/bot"
|
||||
tgmodels "github.com/go-telegram/bot/models"
|
||||
)
|
||||
|
||||
func TestFrameworkInitialOffsetSeedsLastSeenUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
@@ -23,3 +33,95 @@ func TestFrameworkInitialOffsetSeedsLastSeenUpdate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearTelegramWebhookReturnsNilOnSuccessfulDelete(t *testing.T) {
|
||||
manager := &fakeWebhookManager{}
|
||||
|
||||
if err := clearTelegramWebhook(context.Background(), manager); err != nil {
|
||||
t.Fatalf("clearTelegramWebhook returned error: %v", err)
|
||||
}
|
||||
if manager.deleteCalls != 1 || manager.infoCalls != 0 {
|
||||
t.Fatalf("calls = delete %d info %d, want delete 1 info 0", manager.deleteCalls, manager.infoCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearTelegramWebhookAcceptsEmptyDeleteResponseWhenWebhookGone(t *testing.T) {
|
||||
manager := &fakeWebhookManager{
|
||||
deleteErr: emptyDeleteWebhookResponseError(t),
|
||||
webhookInfo: &tgmodels.WebhookInfo{},
|
||||
}
|
||||
|
||||
if err := clearTelegramWebhook(context.Background(), manager); err != nil {
|
||||
t.Fatalf("clearTelegramWebhook returned error: %v", err)
|
||||
}
|
||||
if manager.deleteCalls != 1 || manager.infoCalls != 1 {
|
||||
t.Fatalf("calls = delete %d info %d, want delete 1 info 1", manager.deleteCalls, manager.infoCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearTelegramWebhookFailsWhenWebhookStillConfiguredAfterEmptyDeleteResponse(t *testing.T) {
|
||||
manager := &fakeWebhookManager{
|
||||
deleteErr: emptyDeleteWebhookResponseError(t),
|
||||
webhookInfo: &tgmodels.WebhookInfo{URL: "https://example.com/webhook"},
|
||||
}
|
||||
|
||||
err := clearTelegramWebhook(context.Background(), manager)
|
||||
if err == nil || !strings.Contains(err.Error(), "webhook is still configured") {
|
||||
t.Fatalf("error = %v, want webhook still configured", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearTelegramWebhookFailsWhenWebhookInfoCheckFails(t *testing.T) {
|
||||
infoErr := errors.New("getWebhookInfo unavailable")
|
||||
manager := &fakeWebhookManager{
|
||||
deleteErr: emptyDeleteWebhookResponseError(t),
|
||||
webhookInfoErr: infoErr,
|
||||
}
|
||||
|
||||
err := clearTelegramWebhook(context.Background(), manager)
|
||||
if !errors.Is(err, infoErr) {
|
||||
t.Fatalf("error = %v, want wrapping info error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearTelegramWebhookReturnsNonEmptyDeleteError(t *testing.T) {
|
||||
deleteErr := errors.New("telegram unauthorized")
|
||||
manager := &fakeWebhookManager{deleteErr: deleteErr}
|
||||
|
||||
err := clearTelegramWebhook(context.Background(), manager)
|
||||
if !errors.Is(err, deleteErr) {
|
||||
t.Fatalf("error = %v, want delete error", err)
|
||||
}
|
||||
if manager.infoCalls != 0 {
|
||||
t.Fatalf("infoCalls = %d, want 0", manager.infoCalls)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeWebhookManager struct {
|
||||
deleteErr error
|
||||
webhookInfo *tgmodels.WebhookInfo
|
||||
webhookInfoErr error
|
||||
deleteCalls int
|
||||
infoCalls int
|
||||
}
|
||||
|
||||
func (f *fakeWebhookManager) DeleteWebhook(context.Context, *tgbot.DeleteWebhookParams) (bool, error) {
|
||||
f.deleteCalls++
|
||||
return f.deleteErr == nil, f.deleteErr
|
||||
}
|
||||
|
||||
func (f *fakeWebhookManager) GetWebhookInfo(context.Context) (*tgmodels.WebhookInfo, error) {
|
||||
f.infoCalls++
|
||||
return f.webhookInfo, f.webhookInfoErr
|
||||
}
|
||||
|
||||
func emptyDeleteWebhookResponseError(t *testing.T) error {
|
||||
t.Helper()
|
||||
|
||||
var response struct{}
|
||||
err := json.Unmarshal([]byte{}, &response)
|
||||
if err == nil {
|
||||
t.Fatal("expected json decode error")
|
||||
}
|
||||
return fmt.Errorf("error decode response body for method deleteWebhook, , %w", err)
|
||||
}
|
||||
Reference in new issue
Block a user