Files
ratatoskr-go/internal/chat/router_test.go
2026-08-14 20:33:04 +05:00

157 lines
4.4 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 chat
import (
"context"
"errors"
"testing"
)
const (
uidA UserID = "u-a"
uidB UserID = "u-b"
tg Address = "tg://123"
tui Address = "tui://local"
)
// fakeOnMsg — тест-колбэк, копящий входящие.
type fakeOnMsg struct{ got []Incoming }
func (f *fakeOnMsg) h(inc Incoming) { f.got = append(f.got, inc) }
func TestRouter_AttachAndIncoming(t *testing.T) {
cb := &fakeOnMsg{}
r := NewRouter(cb.h)
tgCh := newFakeChannel(tg)
if err := r.Attach(tgCh); err != nil {
t.Fatalf("Attach: %v", err)
}
tgCh.emit(uidA, tg, "привет")
if len(cb.got) != 1 {
t.Fatalf("handler got %d, want 1", len(cb.got))
}
got := cb.got[0]
if got.UserID != uidA || got.Address != tg || got.Msg.Text != "привет" {
t.Errorf("incoming = %+v", got)
}
}
func TestRouter_AttachNil(t *testing.T) {
r := NewRouter(nil)
if err := r.Attach(nil); err == nil {
t.Fatal("Attach(nil) должен вернуть ошибку")
}
}
func TestRouter_Send_NoRoute(t *testing.T) {
r := NewRouter(nil)
// M1: нет маршрута → Send no-op, nil
if err := r.Send(context.Background(), uidA, Message{Text: "x"}); err != nil {
t.Fatalf("Send без маршрута: %v", err)
}
}
func TestRouter_Send_UsesCurrentRoute(t *testing.T) {
cb := &fakeOnMsg{}
r := NewRouter(cb.h)
tgCh := newFakeChannel(tg)
_ = r.Attach(tgCh)
tgCh.emit(uidA, tg, "hi") // устанавливает маршрут
if err := r.Send(context.Background(), uidA, Message{Text: "отв"}); err != nil {
t.Fatalf("Send: %v", err)
}
if tgCh.sentCount() != 1 {
t.Fatalf("sent = %d, want 1", tgCh.sentCount())
}
if tgCh.sent[0].Msg.Text != "отв" {
t.Errorf("msg = %q", tgCh.sent[0].Msg.Text)
}
}
func TestRouter_SwitchChannel_Continues(t *testing.T) {
cb := &fakeOnMsg{}
r := NewRouter(cb.h)
tgCh := newFakeChannel(tg)
tuiCh := newFakeChannel(tui)
_ = r.Attach(tgCh)
_ = r.Attach(tuiCh)
// начал в TG
tgCh.emit(uidA, tg, "hi")
// продолжил в GUI
tuiCh.emit(uidA, tui, "продолжаю тут")
if tgCh.sentCount() != 0 || tuiCh.sentCount() != 0 {
t.Fatal("до Send ничего не шлём")
}
// ответ должен уйти в последний канал (GUI)
_ = r.Send(context.Background(), uidA, Message{Text: "отв"})
if tuiCh.sentCount() != 1 {
t.Errorf("tui sent = %d, want 1 (последний маршрут)", tuiCh.sentCount())
}
if tgCh.sentCount() != 0 {
t.Errorf("tg sent = %d, want 0", tgCh.sentCount())
}
}
func TestRouter_Ask_PendingThenAnswer(t *testing.T) {
cb := &fakeOnMsg{}
r := NewRouter(cb.h)
tgCh := newFakeChannel(tg)
_ = r.Attach(tgCh)
tgCh.emit(uidA, tg, "hi")
prompt := Message{Text: "Как зовут?", Options: []Option{{ID: "a", Label: "Анна"}}}
if err := r.Ask(context.Background(), uidA, prompt); err != nil {
t.Fatalf("Ask: %v", err)
}
if _, ok := r.Pending(uidA); !ok {
t.Fatal("pending не открыт")
}
// повторный Ask → M3
if err := r.Ask(context.Background(), uidA, prompt); !errors.Is(err, ErrWaitingAnswer) {
t.Fatalf("второй Ask err = %v, want ErrWaitingAnswer", err)
}
// ответ с того же адреса потребляет pending
tgCh.emit(uidA, tg, "Анна")
if _, ok := r.Pending(uidA); ok {
t.Fatal("pending должен быть закрыт после ответа")
}
if len(cb.got) != 2 {
t.Fatalf("handler got %d, want 2 (hi + ответ)", len(cb.got))
}
if cb.got[1].Msg.QuestionID == "" {
t.Error("ответ должен нести QuestionID вопроса")
}
}
func TestRouter_Ask_NoRoute(t *testing.T) {
r := NewRouter(nil)
if err := r.Ask(context.Background(), uidB, Message{Text: "q"}); !errors.Is(err, ErrRouteNotFound) {
t.Fatalf("Ask без маршрута err = %v, want ErrRouteNotFound", err)
}
}
func TestRouter_Ask_PendingNotConsumedFromOtherAddr(t *testing.T) {
cb := &fakeOnMsg{}
r := NewRouter(cb.h)
tgCh := newFakeChannel(tg)
tuiCh := newFakeChannel(tui)
_ = r.Attach(tgCh)
_ = r.Attach(tuiCh)
tgCh.emit(uidA, tg, "hi")
if err := r.Ask(context.Background(), uidA, Message{Text: "q"}); err != nil {
t.Fatalf("Ask: %v", err)
}
// ответ из ДРУГОГО канала → это новый message, pending НЕ потребляется
tuiCh.emit(uidA, tui, "ответ из gui")
if _, ok := r.Pending(uidA); !ok {
t.Fatal("pending должен остаться (ответ из другого адреса)")
}
}