package telegram_test import ( "context" "io" "log/slog" "testing" "backend/internal/modules/telegram" ) // fakeRepository is an in-memory telegram.Repository (the single settings // row) used to unit-test telegram.Service without a real database. type fakeRepository struct { settings telegram.Settings } func newFakeRepository() *fakeRepository { return &fakeRepository{settings: telegram.Settings{ID: 1}} } func (r *fakeRepository) Get(_ context.Context) (*telegram.Settings, error) { cp := r.settings return &cp, nil } func (r *fakeRepository) Update(_ context.Context, s *telegram.Settings) error { r.settings.Enabled = s.Enabled r.settings.ChatID = s.ChatID r.settings.NotifyNewOrder = s.NotifyNewOrder if s.BotToken != "" { r.settings.BotToken = s.BotToken } return nil } // fakeSender records every message it was asked to send, instead of making // a real network call to the Telegram Bot API. type fakeSender struct { sent []string err error } func (s *fakeSender) Send(_ context.Context, botToken, chatID, text string) error { if s.err != nil { return s.err } s.sent = append(s.sent, text) return nil } func discardLogger() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) } func TestService_Update_KeepsExistingTokenWhenBlank(t *testing.T) { repo := newFakeRepository() svc := telegram.NewService(repo, &fakeSender{}, discardLogger()) ctx := context.Background() if _, err := svc.Update(ctx, true, "secret-token", "12345", true); err != nil { t.Fatalf("first Update() error = %v", err) } // Flip a flag without resending the token. settings, err := svc.Update(ctx, true, "", "12345", false) if err != nil { t.Fatalf("second Update() error = %v", err) } if settings.NotifyNewOrder { t.Fatalf("Update() flags = %+v, want NotifyStatusChange=true NotifyNewOrder=false", settings) } if repo.settings.BotToken != "secret-token" { t.Fatalf("Update() with blank bot_token overwrote the stored token: got %q", repo.settings.BotToken) } } func TestService_Get_NeverExposesRawToken(t *testing.T) { // This mirrors the handler's toResponse() contract: the service layer // itself returns the raw Settings (needed internally to call the // Telegram API), but the HTTP handler must never marshal BotToken back // to the client. This test guards the service-level data so a future // change to the handler can't accidentally start doing so without // resetting the flag on read. repo := newFakeRepository() svc := telegram.NewService(repo, &fakeSender{}, discardLogger()) ctx := context.Background() if _, err := svc.Update(ctx, true, "super-secret", "12345", true); err != nil { t.Fatalf("Update() error = %v", err) } settings, err := svc.Get(ctx) if err != nil { t.Fatalf("Get() error = %v", err) } if settings.BotToken != "super-secret" { t.Fatalf("Get() lost the stored bot token: got %q", settings.BotToken) } } func TestService_NotifyNewOrder_SkipsWhenDisabled(t *testing.T) { repo := newFakeRepository() sender := &fakeSender{} svc := telegram.NewService(repo, sender, discardLogger()) ctx := context.Background() if _, err := svc.Update(ctx, false, "token", "12345", true); err != nil { t.Fatalf("Update() error = %v", err) } svc.NotifyNewOrder(ctx, "new order!") if len(sender.sent) != 0 { t.Fatalf("NotifyNewOrder() sent a message while Telegram is disabled: %v", sender.sent) } } func TestService_NotifyNewOrder_SkipsWhenEventNotEnabled(t *testing.T) { repo := newFakeRepository() sender := &fakeSender{} svc := telegram.NewService(repo, sender, discardLogger()) ctx := context.Background() // Enabled overall, but the "new order" event specifically is off. if _, err := svc.Update(ctx, true, "token", "12345", false); err != nil { t.Fatalf("Update() error = %v", err) } svc.NotifyNewOrder(ctx, "new order!") if len(sender.sent) != 0 { t.Fatalf("NotifyNewOrder() sent a message while notify_new_order=false: %v", sender.sent) } } func TestService_NotifyNewOrder_SendsWhenEnabled(t *testing.T) { repo := newFakeRepository() sender := &fakeSender{} svc := telegram.NewService(repo, sender, discardLogger()) ctx := context.Background() if _, err := svc.Update(ctx, true, "token", "12345", true); err != nil { t.Fatalf("Update() error = %v", err) } svc.NotifyNewOrder(ctx, "new order!") if len(sender.sent) != 1 || sender.sent[0] != "new order!" { t.Fatalf("NotifyNewOrder() sent = %v, want exactly one message \"new order!\"", sender.sent) } } func TestService_SendTestMessage_RequiresConfiguredCredentials(t *testing.T) { svc := telegram.NewService(newFakeRepository(), &fakeSender{}, discardLogger()) if err := svc.SendTestMessage(context.Background()); err == nil { t.Fatal("SendTestMessage() error = nil, want error when bot token/chat id are not configured") } }