// 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]) } }