159 lines
5.6 KiB
Go
159 lines
5.6 KiB
Go
|
|
package notify
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"testing"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"building-mgmt/internal/telegram"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Тесты реестра ботов (миграция 0012): личный токен приоритетнее
|
|||
|
|
// глобального, на каждый личный бот — свой поллер, остановка гасит его.
|
|||
|
|
|
|||
|
|
// newRegistryBot — бот с каналами, чтобы дождаться старта поллера и его
|
|||
|
|
// выхода (idle — поллинг начался, stopped — поллер остановился).
|
|||
|
|
func newRegistryBot() *fakeBot {
|
|||
|
|
return &fakeBot{
|
|||
|
|
enabled: true,
|
|||
|
|
username: "test_bot",
|
|||
|
|
idle: make(chan struct{}),
|
|||
|
|
stopped: make(chan struct{}),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// waitStopped — ждёт остановки поллера, но не вечно.
|
|||
|
|
func waitStopped(t *testing.T, bot *fakeBot) {
|
|||
|
|
t.Helper()
|
|||
|
|
select {
|
|||
|
|
case <-bot.stopped:
|
|||
|
|
case <-time.After(3 * time.Second):
|
|||
|
|
t.Fatal("поллер не остановился")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestRegistryBotFor — без своего токена отдаётся глобальный бот;
|
|||
|
|
// со своим — он, глобальный не задействуется.
|
|||
|
|
func TestRegistryBotFor(t *testing.T) {
|
|||
|
|
global := &fakeBot{enabled: false, username: "global_bot"}
|
|||
|
|
r := NewRegistry(context.Background(), newFakeStore(), global, testLog())
|
|||
|
|
|
|||
|
|
if got := r.BotFor("user-1"); got != telegram.API(global) {
|
|||
|
|
t.Errorf("BotFor = %v, want global fallback", got)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
mine := newRegistryBot()
|
|||
|
|
r.Register("user-1", mine)
|
|||
|
|
if got := r.BotFor("user-1"); got != telegram.API(mine) {
|
|||
|
|
t.Errorf("BotFor = %v, want personal bot", got)
|
|||
|
|
}
|
|||
|
|
// Чужой пользователь по-прежнему получает глобального.
|
|||
|
|
if got := r.BotFor("user-2"); got != telegram.API(global) {
|
|||
|
|
t.Errorf("BotFor другого = %v, want global", got)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
r.Unregister("user-1")
|
|||
|
|
if got := r.BotFor("user-1"); got != telegram.API(global) {
|
|||
|
|
t.Errorf("BotFor после удаления = %v, want global", got)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestRegistryPollerLifecycle — Register поднимает поллер, Unregister
|
|||
|
|
// останавливает: без этого процесс накапливал бы горутины на каждый
|
|||
|
|
// ввод и удаление токена.
|
|||
|
|
func TestRegistryPollerLifecycle(t *testing.T) {
|
|||
|
|
r := NewRegistry(context.Background(), newFakeStore(), &fakeBot{enabled: false}, testLog())
|
|||
|
|
|
|||
|
|
bot := newRegistryBot()
|
|||
|
|
r.Register("user-1", bot)
|
|||
|
|
if r.Users() != 1 {
|
|||
|
|
t.Fatalf("Users = %d, want 1", r.Users())
|
|||
|
|
}
|
|||
|
|
select {
|
|||
|
|
case <-bot.idle: // поллинг пошёл
|
|||
|
|
case <-time.After(3 * time.Second):
|
|||
|
|
t.Fatal("поллер не стартовал")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
r.Unregister("user-1")
|
|||
|
|
if r.Users() != 0 {
|
|||
|
|
t.Errorf("Users = %d, want 0", r.Users())
|
|||
|
|
}
|
|||
|
|
waitStopped(t, bot)
|
|||
|
|
|
|||
|
|
// Повторное удаление безопасно (идемпотентно).
|
|||
|
|
r.Unregister("user-1")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestRegistryReRegister — смена токена гасит прежний поллер: две
|
|||
|
|
// горутины не должны драть одну очередь getUpdates.
|
|||
|
|
func TestRegistryReRegister(t *testing.T) {
|
|||
|
|
r := NewRegistry(context.Background(), newFakeStore(), &fakeBot{enabled: false}, testLog())
|
|||
|
|
|
|||
|
|
first := newRegistryBot()
|
|||
|
|
r.Register("user-1", first)
|
|||
|
|
select {
|
|||
|
|
case <-first.idle:
|
|||
|
|
case <-time.After(3 * time.Second):
|
|||
|
|
t.Fatal("первый поллер не стартовал")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
second := newRegistryBot()
|
|||
|
|
r.Register("user-1", second)
|
|||
|
|
waitStopped(t, first) // прежний остановлен
|
|||
|
|
|
|||
|
|
if got := r.BotFor("user-1"); got != telegram.API(second) {
|
|||
|
|
t.Errorf("BotFor = %v, want второй бот", got)
|
|||
|
|
}
|
|||
|
|
if r.Users() != 1 {
|
|||
|
|
t.Errorf("Users = %d, want 1", r.Users())
|
|||
|
|
}
|
|||
|
|
// Дожидаемся, пока новый поллер повиснет в getUpdates, и гасим:
|
|||
|
|
// до этого момента его отмена — штатный выход без вызова API.
|
|||
|
|
select {
|
|||
|
|
case <-second.idle:
|
|||
|
|
case <-time.After(3 * time.Second):
|
|||
|
|
t.Fatal("второй поллер не стартовал")
|
|||
|
|
}
|
|||
|
|
r.Unregister("user-1")
|
|||
|
|
waitStopped(t, second)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestRegistryParentContext — остановка процесса (отмена родительского
|
|||
|
|
// контекста) гасит все личные поллеры разом.
|
|||
|
|
func TestRegistryParentContext(t *testing.T) {
|
|||
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|||
|
|
r := NewRegistry(ctx, newFakeStore(), &fakeBot{enabled: false}, testLog())
|
|||
|
|
|
|||
|
|
first, second := newRegistryBot(), newRegistryBot()
|
|||
|
|
r.Register("user-1", first)
|
|||
|
|
r.Register("user-2", second)
|
|||
|
|
// Оба должны повиснуть в getUpdates — иначе отмена пройдёт мимо и
|
|||
|
|
// поллер выйдет, не вызв API (тест не увидит stopped).
|
|||
|
|
for _, bot := range []*fakeBot{first, second} {
|
|||
|
|
select {
|
|||
|
|
case <-bot.idle:
|
|||
|
|
case <-time.After(3 * time.Second):
|
|||
|
|
t.Fatal("поллер не стартовал")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
cancel()
|
|||
|
|
waitStopped(t, first)
|
|||
|
|
waitStopped(t, second)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestRegistryIgnoresDisabled — выключенный бот (пустой токен) поллер
|
|||
|
|
// не поднимает: держать горутину, которая сразу вернёт ErrDisabled,
|
|||
|
|
// незачем.
|
|||
|
|
func TestRegistryIgnoresDisabled(t *testing.T) {
|
|||
|
|
r := NewRegistry(context.Background(), newFakeStore(), &fakeBot{enabled: false}, testLog())
|
|||
|
|
|
|||
|
|
r.Register("user-1", &fakeBot{enabled: false, idle: make(chan struct{})})
|
|||
|
|
if r.Users() != 0 {
|
|||
|
|
t.Errorf("Users = %d, want 0", r.Users())
|
|||
|
|
}
|
|||
|
|
if got := r.BotFor("user-1"); got.Enabled() {
|
|||
|
|
t.Error("BotFor = включённый бот, want выключенный fallback")
|
|||
|
|
}
|
|||
|
|
}
|