axe/internal/notify/registry_test.go

159 lines
5.6 KiB
Go
Raw Permalink Normal View History

2026-10-18 19:07:25 +00:00
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")
}
}