| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- // telegrambots_test.go — pure-Go tests for the input validators
- // and the format helpers on the telegram_bots store. The
- // DB-backed paths (Create, Update, SetStatus, List, RotateToken)
- // are exercised by scripts/m13b_w3_smoke.sh against a real
- // Postgres.
- package authd
- import (
- "strings"
- "testing"
- )
- func TestValidTelegramBotID(t *testing.T) {
- cases := []struct {
- in string
- want bool
- }{
- // valid (same shape as auth.tenants.slug / source id)
- {"primary", true},
- {"ops-foo", true},
- {"a-b-c", true},
- {strings.Repeat("a", 64), true},
- // invalid
- {"", false},
- {"a", false}, // too short (1 char)
- {"A", false}, // uppercase
- {"-foo", false}, // leading dash
- {"foo-", false}, // trailing dash
- {"foo_bar", false}, // underscore
- {"foo bar", false}, // space
- {"foo.bar", false}, // dot
- {strings.Repeat("a", 65), false},
- }
- for _, c := range cases {
- if got := validTelegramBotID(c.in); got != c.want {
- t.Errorf("validTelegramBotID(%q) = %v, want %v", c.in, got, c.want)
- }
- }
- }
- func TestValidBotTokenFormat(t *testing.T) {
- goodSecret := strings.Repeat("a", 35)
- goodSecretWith := "abc-DEF_123" + strings.Repeat("a", 26)
- cases := []struct {
- in string
- want bool
- }{
- // valid
- {"12345678:" + goodSecret, true},
- {"1:" + goodSecret, true},
- {"1234567890:" + goodSecretWith, true},
- // invalid
- {"", false},
- {":", false},
- {":" + goodSecret, false}, // empty bot id
- {"12345678", false}, // no colon
- {"12345678:" + strings.Repeat("a", 34), false}, // secret too short
- {"12345678:" + strings.Repeat("a", 36), true}, // secret one over (still ok per spec)
- {"12345678:short", false}, // secret too short
- {"abc:" + goodSecret, false}, // non-digit bot id
- {"12345678:" + strings.Repeat("a", 35) + ":extra", false}, // extra colon
- {"12345678:" + goodSecret + "!", false}, // bad char in secret
- {"12345678:" + goodSecret + " with space", false},
- }
- for _, c := range cases {
- if got := validBotTokenFormat(c.in); got != c.want {
- t.Errorf("validBotTokenFormat(%q) = %v, want %v", c.in, got, c.want)
- }
- }
- }
- func TestCreateTelegramBotInput_Validate(t *testing.T) {
- goodToken := "12345678:" + strings.Repeat("a", 35)
- tooLongName := strings.Repeat("a", 201)
- tooLongWelcome := strings.Repeat("a", 4097)
- tooLongDesc := strings.Repeat("a", 501)
- cases := []struct {
- name string
- in CreateTelegramBotInput
- wantErr bool
- errSub string
- }{
- {
- name: "ok",
- in: CreateTelegramBotInput{
- ID: "primary", Name: "Primary", BotToken: goodToken,
- },
- wantErr: false,
- },
- {
- name: "ok with optional fields",
- in: CreateTelegramBotInput{
- ID: "primary", Name: "Primary", BotToken: goodToken,
- WelcomeMessage: "Welcome to Acme alerts!",
- DefaultSourceID: "primary",
- Description: "Main bot for ops",
- },
- wantErr: false,
- },
- {name: "bad id", in: CreateTelegramBotInput{ID: "Bad ID!", Name: "x", BotToken: goodToken}, wantErr: true, errSub: "id must match"},
- {name: "empty name", in: CreateTelegramBotInput{ID: "primary", Name: " ", BotToken: goodToken}, wantErr: true, errSub: "name is required"},
- {name: "name too long", in: CreateTelegramBotInput{ID: "primary", Name: tooLongName, BotToken: goodToken}, wantErr: true, errSub: "name must be"},
- {name: "bad token", in: CreateTelegramBotInput{ID: "primary", Name: "x", BotToken: "short"}, wantErr: true, errSub: "bot_token must match"},
- {name: "welcome too long", in: CreateTelegramBotInput{ID: "primary", Name: "x", BotToken: goodToken, WelcomeMessage: tooLongWelcome}, wantErr: true, errSub: "welcome_message must be"},
- {name: "default source id bad", in: CreateTelegramBotInput{ID: "primary", Name: "x", BotToken: goodToken, DefaultSourceID: "Bad ID!"}, wantErr: true, errSub: "default_source_id must match"},
- {name: "default source id empty ok", in: CreateTelegramBotInput{ID: "primary", Name: "x", BotToken: goodToken, DefaultSourceID: ""}, wantErr: false},
- {name: "description too long", in: CreateTelegramBotInput{ID: "primary", Name: "x", BotToken: goodToken, Description: tooLongDesc}, wantErr: true, errSub: "description must be"},
- }
- for _, c := range cases {
- t.Run(c.name, func(t *testing.T) {
- err := c.in.Validate()
- if c.wantErr {
- if err == nil {
- t.Fatalf("expected error containing %q, got nil", c.errSub)
- }
- if !strings.Contains(err.Error(), c.errSub) {
- t.Fatalf("expected error containing %q, got %q", c.errSub, err.Error())
- }
- } else if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- })
- }
- }
- func TestUpdateTelegramBotInput_Validate(t *testing.T) {
- name := "Renamed"
- emptyName := " "
- welcome := "Hello there"
- tooLongWelcome := strings.Repeat("a", 4097)
- defaultSrc := "primary"
- badDefault := "Bad ID!"
- desc := "Some description"
- cases := []struct {
- name string
- in UpdateTelegramBotInput
- wantErr bool
- errSub string
- }{
- {name: "empty (no-op)", in: UpdateTelegramBotInput{}, wantErr: false},
- {name: "name change", in: UpdateTelegramBotInput{Name: &name}, wantErr: false},
- {name: "name empty", in: UpdateTelegramBotInput{Name: &emptyName}, wantErr: true, errSub: "name cannot be empty"},
- {name: "welcome ok", in: UpdateTelegramBotInput{WelcomeMessage: &welcome}, wantErr: false},
- {name: "welcome empty ok (clear)", in: UpdateTelegramBotInput{WelcomeMessage: ptr("")}, wantErr: false},
- {name: "welcome too long", in: UpdateTelegramBotInput{WelcomeMessage: &tooLongWelcome}, wantErr: true, errSub: "welcome_message must be"},
- {name: "default source ok", in: UpdateTelegramBotInput{DefaultSourceID: &defaultSrc}, wantErr: false},
- {name: "default source bad", in: UpdateTelegramBotInput{DefaultSourceID: &badDefault}, wantErr: true, errSub: "default_source_id must match"},
- {name: "description ok", in: UpdateTelegramBotInput{Description: &desc}, wantErr: false},
- }
- for _, c := range cases {
- t.Run(c.name, func(t *testing.T) {
- err := c.in.Validate()
- if c.wantErr {
- if err == nil || !strings.Contains(err.Error(), c.errSub) {
- t.Fatalf("expected error containing %q, got %v", c.errSub, err)
- }
- } else if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- })
- }
- }
- func TestHashBotToken(t *testing.T) {
- plain := "12345678:" + strings.Repeat("a", 35)
- h, err := hashBotToken(plain)
- if err != nil {
- t.Fatalf("hashBotToken: %v", err)
- }
- if h == "" {
- t.Fatal("expected non-empty hash")
- }
- if h == plain {
- t.Fatal("hash equals plaintext (bcrypt not applied)")
- }
- if !strings.HasPrefix(h, "$2a$") && !strings.HasPrefix(h, "$2b$") {
- t.Errorf("expected bcrypt prefix ($2a$/$2b$), got %q", h[:10])
- }
- }
|