package bot import ( "context" "testing" "time" storagemodel "github.com/tech/sendico/gateway/tgsettle/storage/model" mloggerfactory "github.com/tech/sendico/pkg/mlogger/factory" "github.com/tech/sendico/pkg/model" ) type fakeService struct{} type fakeUserBindingResolver struct { bindings map[string]*UserBinding err error } func (f fakeUserBindingResolver) ResolveUserBinding(_ context.Context, telegramUserID string) (*UserBinding, error) { if f.err != nil { return nil, f.err } if f.bindings == nil { return nil, nil } return f.bindings[telegramUserID], nil } func (fakeService) ExecutionDelay() time.Duration { return 30 * time.Second } func (fakeService) MaxPerOperationLimit() string { return "1000000" } func (fakeService) GetActiveRequestForAccount(context.Context, string) (*storagemodel.TreasuryRequest, error) { return nil, nil } func (fakeService) GetAccountProfile(_ context.Context, ledgerAccountID string) (*AccountProfile, error) { return &AccountProfile{ AccountID: ledgerAccountID, AccountCode: ledgerAccountID, Currency: "USD", }, nil } func (fakeService) CreateRequest(context.Context, CreateRequestInput) (*storagemodel.TreasuryRequest, error) { return nil, nil } func (fakeService) ConfirmRequest(context.Context, string, string) (*storagemodel.TreasuryRequest, error) { return nil, nil } func (fakeService) CancelRequest(context.Context, string, string) (*storagemodel.TreasuryRequest, error) { return nil, nil } func TestRouterUnauthorizedInAllowedChatSendsAccessDenied(t *testing.T) { var sent []string router := NewRouter( mloggerfactory.NewLogger(false), fakeService{}, func(_ context.Context, _ string, text string) error { sent = append(sent, text) return nil }, nil, fakeUserBindingResolver{ bindings: map[string]*UserBinding{ "123": { TelegramUserID: "123", LedgerAccountID: "acct-1", AllowedChatIDs: []string{"100"}, }, }, }, ) handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{ Message: &model.TelegramMessage{ ChatID: "100", FromUserID: "999", Text: "/fund", }, }) if !handled { t.Fatalf("expected update to be handled") } if len(sent) != 1 { t.Fatalf("expected one message, got %d", len(sent)) } if sent[0] != unauthorizedMessage { t.Fatalf("unexpected message: %q", sent[0]) } } func TestRouterUnknownChatGetsDenied(t *testing.T) { var sent []string router := NewRouter( mloggerfactory.NewLogger(false), fakeService{}, func(_ context.Context, _ string, text string) error { sent = append(sent, text) return nil }, nil, fakeUserBindingResolver{ bindings: map[string]*UserBinding{ "123": { TelegramUserID: "123", LedgerAccountID: "acct-1", AllowedChatIDs: []string{"100"}, }, }, }, ) handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{ Message: &model.TelegramMessage{ ChatID: "999", FromUserID: "123", Text: "/fund", }, }) if !handled { t.Fatalf("expected update to be handled") } if len(sent) != 1 { t.Fatalf("expected one message, got %d", len(sent)) } if sent[0] != unauthorizedChatMessage { t.Fatalf("unexpected message: %q", sent[0]) } } func TestRouterEmptyAllowedChats_AllowsAnyChatForAuthorizedUser(t *testing.T) { var sent []string router := NewRouter( mloggerfactory.NewLogger(false), fakeService{}, func(_ context.Context, _ string, text string) error { sent = append(sent, text) return nil }, nil, fakeUserBindingResolver{ bindings: map[string]*UserBinding{ "123": { TelegramUserID: "123", LedgerAccountID: "acct-1", }, }, }, ) handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{ Message: &model.TelegramMessage{ ChatID: "999", FromUserID: "123", Text: "/fund", }, }) if !handled { t.Fatalf("expected update to be handled") } if len(sent) != 1 { t.Fatalf("expected one message, got %d", len(sent)) } if sent[0] != amountPromptMessage( storagemodel.TreasuryOperationFund, &AccountProfile{AccountID: "acct-1", AccountCode: "acct-1", Currency: "USD"}, "acct-1", ) { t.Fatalf("unexpected message: %q", sent[0]) } } func TestRouterEmptyAllowedChats_UnauthorizedUserGetsDenied(t *testing.T) { var sent []string router := NewRouter( mloggerfactory.NewLogger(false), fakeService{}, func(_ context.Context, _ string, text string) error { sent = append(sent, text) return nil }, nil, fakeUserBindingResolver{ bindings: map[string]*UserBinding{ "123": { TelegramUserID: "123", LedgerAccountID: "acct-1", }, }, }, ) handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{ Message: &model.TelegramMessage{ ChatID: "777", FromUserID: "999", Text: "/fund", }, }) if !handled { t.Fatalf("expected update to be handled") } if len(sent) != 1 { t.Fatalf("expected one message, got %d", len(sent)) } if sent[0] != unauthorizedMessage { t.Fatalf("unexpected message: %q", sent[0]) } } func TestRouterStartAuthorizedShowsWelcome(t *testing.T) { var sent []string router := NewRouter( mloggerfactory.NewLogger(false), fakeService{}, func(_ context.Context, _ string, text string) error { sent = append(sent, text) return nil }, nil, fakeUserBindingResolver{ bindings: map[string]*UserBinding{ "123": { TelegramUserID: "123", LedgerAccountID: "acct-1", }, }, }, ) handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{ Message: &model.TelegramMessage{ ChatID: "777", FromUserID: "123", Text: "/start", }, }) if !handled { t.Fatalf("expected update to be handled") } if len(sent) != 1 { t.Fatalf("expected one message, got %d", len(sent)) } if sent[0] != welcomeMessage(&AccountProfile{AccountID: "acct-1", AccountCode: "acct-1", Currency: "USD"}) { t.Fatalf("unexpected message: %q", sent[0]) } } func TestRouterHelpAuthorizedShowsHelp(t *testing.T) { var sent []string router := NewRouter( mloggerfactory.NewLogger(false), fakeService{}, func(_ context.Context, _ string, text string) error { sent = append(sent, text) return nil }, nil, fakeUserBindingResolver{ bindings: map[string]*UserBinding{ "123": { TelegramUserID: "123", LedgerAccountID: "acct-1", }, }, }, ) handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{ Message: &model.TelegramMessage{ ChatID: "777", FromUserID: "123", Text: "/help", }, }) if !handled { t.Fatalf("expected update to be handled") } if len(sent) != 1 { t.Fatalf("expected one message, got %d", len(sent)) } if sent[0] != helpMessage("acct-1", "USD") { t.Fatalf("unexpected message: %q", sent[0]) } } func TestRouterStartUnauthorizedGetsDenied(t *testing.T) { var sent []string router := NewRouter( mloggerfactory.NewLogger(false), fakeService{}, func(_ context.Context, _ string, text string) error { sent = append(sent, text) return nil }, nil, fakeUserBindingResolver{ bindings: map[string]*UserBinding{ "123": { TelegramUserID: "123", LedgerAccountID: "acct-1", }, }, }, ) handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{ Message: &model.TelegramMessage{ ChatID: "777", FromUserID: "999", Text: "/start", }, }) if !handled { t.Fatalf("expected update to be handled") } if len(sent) != 1 { t.Fatalf("expected one message, got %d", len(sent)) } if sent[0] != unauthorizedMessage { t.Fatalf("unexpected message: %q", sent[0]) } } func TestRouterPlainTextWithoutSession_ShowsSupportedCommands(t *testing.T) { var sent []string router := NewRouter( mloggerfactory.NewLogger(false), fakeService{}, func(_ context.Context, _ string, text string) error { sent = append(sent, text) return nil }, nil, fakeUserBindingResolver{ bindings: map[string]*UserBinding{ "123": { TelegramUserID: "123", LedgerAccountID: "acct-1", }, }, }, ) handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{ Message: &model.TelegramMessage{ ChatID: "777", FromUserID: "123", Text: "hello", }, }) if !handled { t.Fatalf("expected update to be handled") } if len(sent) != 1 { t.Fatalf("expected one message, got %d", len(sent)) } if sent[0] != supportedCommandsMessage() { t.Fatalf("unexpected message: %q", sent[0]) } }