axe/internal/notify/registry_test.go
2026-10-18 22:07:25 +03:00

159 lines
5.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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")
}
}