telegram: сервис поднимается без бота и работает одним входом
- Клиент бота собирается один раз и достаётся отправителю и транспорту; разрез прошёл по «ответил ли Telegram»: ответ «такого бота нет» роняет старт, недоступность даёт подъём без Telegram (ADR-2026-08-13). Ожидание при сборке ограничено сроком — иначе молчащий Telegram вешал подъём. - Недоставленный ответ не роняет шаг: пишется с job_id и считается метрикой, уровень по причине — WARN для неподнятого входа, ERROR для неназванного адресата. Заведены transcriber_intake_up и transcriber_undelivered_reply_count. - Закрыта утечка токена в журнал: отказ разбора адреса рождается раньше обращения к клиенту, то есть мимо чистки на его границе.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
|
||||
"git.vakhrushev.me/av/transcriber/internal/adapter/telegram"
|
||||
"git.vakhrushev.me/av/transcriber/internal/contract"
|
||||
)
|
||||
|
||||
// Разрез сборки — то, ради чего написано изменение, — до этих проверок не
|
||||
// держался ничем: инвертируй его, и весь набор оставался зелёным.
|
||||
//
|
||||
// Судится решение, а не обращение к Telegram: обращение ходит в сеть, а
|
||||
// боевым токеном запускаться запрещено.
|
||||
|
||||
func journalLogger() (*slog.Logger, *bytes.Buffer) {
|
||||
journal := &bytes.Buffer{}
|
||||
return slog.New(slog.NewTextHandler(journal, &slog.HandlerOptions{Level: slog.LevelDebug})), journal
|
||||
}
|
||||
|
||||
// Пустой токен — законный отказ от входа: старт продолжается, ядро получает
|
||||
// заглушку, а владелец узнаёт об этом одной записью.
|
||||
func TestTelegramFromBotOnEmptyTokenGivesAbsentSender(t *testing.T) {
|
||||
logger, journal := journalLogger()
|
||||
|
||||
bot, sender, err := telegramFromBot(nil, telegram.ErrEmptyToken, logger)
|
||||
|
||||
require.NoError(t, err, "пустой токен старт не роняет")
|
||||
assert.Nil(t, bot, "клиента нет — транспорт не поднимется")
|
||||
require.NotNil(t, sender, "ядро получает отправителя всегда, а не ноль")
|
||||
require.ErrorIs(t, sender.Send("любой ответ", 1, nil), contract.ErrDeliveryChannelDown,
|
||||
"заглушка говорит, что канал не поднят")
|
||||
|
||||
written := journal.String()
|
||||
assert.Contains(t, written, "Telegram bot is not started", "о неподнятом боте сказано")
|
||||
assert.Contains(t, written, "level=WARN", "уровень — «может стать проблемой»")
|
||||
assert.Equal(t, 1, strings.Count(written, "Telegram bot is not started"),
|
||||
"запись ровно одна: вторая была бы записью о том же факте")
|
||||
}
|
||||
|
||||
// Telegram ответил, что такого бота нет, — ошибка настройки, а не режим: бот по
|
||||
// этому токену не появится ни от ожидания, ни от повтора, и старт роняется.
|
||||
func TestTelegramFromBotOnRejectedTokenReturnsError(t *testing.T) {
|
||||
logger, journal := journalLogger()
|
||||
rejected := &tgbotapi.Error{Code: 401, Message: "Unauthorized"}
|
||||
|
||||
bot, sender, err := telegramFromBot(nil, rejected, logger)
|
||||
|
||||
require.ErrorIs(t, err, rejected, "отказ поднят вызывающему нетронутым")
|
||||
assert.Nil(t, bot)
|
||||
assert.Nil(t, sender, "заглушка тут не подставляется: это ошибка, а не режим")
|
||||
|
||||
assert.NotContains(t, journal.String(), "Telegram bot is not started",
|
||||
"о законном отсутствии входа речи нет: вход заявлен и отвергнут")
|
||||
}
|
||||
|
||||
// До Telegram не дошли — недоступность: на подъём сервиса она не влияет.
|
||||
// Решение владельца 2026-08-13; иначе чужая авария кладёт и основной вход, и
|
||||
// панель, и конвейер, которому Telegram не нужен вовсе.
|
||||
func TestTelegramFromBotOnUnreachableTelegramKeepsStarting(t *testing.T) {
|
||||
logger, journal := journalLogger()
|
||||
unreachable := errors.New("dial tcp: connection refused")
|
||||
|
||||
bot, sender, err := telegramFromBot(nil, unreachable, logger)
|
||||
|
||||
require.NoError(t, err, "недоступность Telegram старт не роняет")
|
||||
assert.Nil(t, bot, "клиента нет — транспорт не поднимется")
|
||||
require.NotNil(t, sender, "ядро получает отправителя всегда, а не ноль")
|
||||
require.ErrorIs(t, sender.Send("любой ответ", 1, nil), contract.ErrDeliveryChannelDown)
|
||||
|
||||
written := journal.String()
|
||||
assert.Contains(t, written, "Telegram bot is not started", "о неподнятом входе сказано")
|
||||
assert.Contains(t, written, "unreachable", "причина названа")
|
||||
assert.Contains(t, written, "level=WARN")
|
||||
}
|
||||
|
||||
// Годный токен даёт настоящего отправителя и клиента для транспорта: прежний
|
||||
// путь сохранён, и о неподнятом боте не говорится ничего.
|
||||
func TestTelegramFromBotOnLiveBotGivesRealSender(t *testing.T) {
|
||||
logger, journal := journalLogger()
|
||||
live := &tgbotapi.BotAPI{}
|
||||
|
||||
bot, sender, err := telegramFromBot(live, nil, logger)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Same(t, live, bot, "транспорт получит того же клиента, что и отправитель")
|
||||
require.NotNil(t, sender)
|
||||
_, stub := sender.(*telegram.AbsentMessageSender)
|
||||
assert.False(t, stub, "это настоящий отправитель, а не заглушка")
|
||||
|
||||
assert.NotContains(t, journal.String(), "Telegram bot is not started")
|
||||
}
|
||||
Reference in New Issue
Block a user