| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- // sources_test.go — pure-Go tests for the input validators and
- // the format helpers on the sources store. The DB-backed paths
- // (Create, Update, SetStatus, List, RotateSecrets) are exercised
- // by scripts/m13b_w2_smoke.sh against a real Postgres.
- package authd
- import (
- "strings"
- "testing"
- )
- func TestValidSourceID(t *testing.T) {
- cases := []struct {
- in string
- want bool
- }{
- // valid (same shape as auth.tenants.slug)
- {"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 := validSourceID(c.in); got != c.want {
- t.Errorf("validSourceID(%q) = %v, want %v", c.in, got, c.want)
- }
- }
- }
- func TestValidSecretFormat(t *testing.T) {
- cases := []struct {
- in string
- want bool
- }{
- {strings.Repeat("a", 32), true},
- {strings.Repeat("a", 64), true},
- {strings.Repeat("a", 128), true},
- {"abc-DEF_123" + strings.Repeat("a", 22), true},
- // invalid
- {"", false},
- {strings.Repeat("a", 31), false}, // too short
- {strings.Repeat("a", 129), false}, // too long
- {"with spaces inside", false},
- {"with!special", false},
- {"with.dot", false},
- {"with/slash", false},
- }
- for _, c := range cases {
- if got := validSecretFormat(c.in); got != c.want {
- t.Errorf("validSecretFormat(%q) = %v, want %v", c.in, got, c.want)
- }
- }
- }
- func TestValidAPIKeyFormat(t *testing.T) {
- cases := []struct {
- in string
- want bool
- }{
- {strings.Repeat("a", 16), true},
- {strings.Repeat("a", 48), true},
- {strings.Repeat("a", 128), true},
- // invalid
- {strings.Repeat("a", 15), false},
- {strings.Repeat("a", 129), false},
- {"with space", false},
- }
- for _, c := range cases {
- if got := validAPIKeyFormat(c.in); got != c.want {
- t.Errorf("validAPIKeyFormat(%q) = %v, want %v", c.in, got, c.want)
- }
- }
- }
- func TestCreateSourceInput_Validate(t *testing.T) {
- tooLongName := strings.Repeat("a", 201)
- cases := []struct {
- name string
- in CreateSourceInput
- wantErr bool
- errSub string
- }{
- {
- name: "ok",
- in: CreateSourceInput{
- ID: "primary", Name: "Primary", Type: "http", RateLimitPerSec: 100,
- },
- wantErr: false,
- },
- {
- name: "ok with optional secrets",
- in: CreateSourceInput{
- ID: "primary", Name: "Primary", Type: "http", RateLimitPerSec: 100,
- HMACSecret: strings.Repeat("a", 32),
- APIKey: strings.Repeat("a", 16),
- },
- wantErr: false,
- },
- {name: "bad id", in: CreateSourceInput{ID: "Bad ID!", Name: "x", Type: "http", RateLimitPerSec: 1}, wantErr: true, errSub: "id must match"},
- {name: "empty name", in: CreateSourceInput{ID: "primary", Name: " ", Type: "http", RateLimitPerSec: 1}, wantErr: true, errSub: "name is required"},
- {name: "name too long", in: CreateSourceInput{ID: "primary", Name: tooLongName, Type: "http", RateLimitPerSec: 1}, wantErr: true, errSub: "name must be"},
- {name: "bad type", in: CreateSourceInput{ID: "primary", Name: "x", Type: "smtp", RateLimitPerSec: 1}, wantErr: true, errSub: "type must be"},
- {name: "rate 0", in: CreateSourceInput{ID: "primary", Name: "x", Type: "http", RateLimitPerSec: 0}, wantErr: true, errSub: "rate_limit_per_sec"},
- {name: "rate too high", in: CreateSourceInput{ID: "primary", Name: "x", Type: "http", RateLimitPerSec: 2_000_000}, wantErr: true, errSub: "rate_limit_per_sec"},
- {name: "bad hmac", in: CreateSourceInput{ID: "primary", Name: "x", Type: "http", RateLimitPerSec: 1, HMACSecret: "short"}, wantErr: true, errSub: "hmac_secret"},
- {name: "bad api_key", in: CreateSourceInput{ID: "primary", Name: "x", Type: "http", RateLimitPerSec: 1, APIKey: "short"}, wantErr: true, errSub: "api_key"},
- {name: "bad allowed_targets json", in: CreateSourceInput{ID: "primary", Name: "x", Type: "http", RateLimitPerSec: 1, AllowedTargets: []byte("{not-json")}, wantErr: true, errSub: "allowed_targets"},
- {name: "bad match_expr json", in: CreateSourceInput{ID: "primary", Name: "x", Type: "http", RateLimitPerSec: 1, MatchExpr: []byte("{not-json")}, wantErr: true, errSub: "match_expr"},
- }
- 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 TestUpdateSourceInput_Validate(t *testing.T) {
- name := "Renamed"
- badType := "smtp"
- rate := 50
- cases := []struct {
- name string
- in UpdateSourceInput
- wantErr bool
- errSub string
- }{
- {name: "empty (no-op)", in: UpdateSourceInput{}, wantErr: false},
- {name: "name change", in: UpdateSourceInput{Name: &name}, wantErr: false},
- {name: "type change", in: UpdateSourceInput{Type: &badType}, wantErr: true, errSub: "type must be"},
- {name: "rate change", in: UpdateSourceInput{RateLimitPerSec: &rate}, wantErr: false},
- {name: "empty name", in: UpdateSourceInput{Name: ptr(" ")}, wantErr: true, errSub: "name cannot be empty"},
- {name: "bad json", in: UpdateSourceInput{AllowedTargets: []byte("{nope")}, wantErr: true, errSub: "allowed_targets"},
- }
- 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 TestGenerateSecret(t *testing.T) {
- a, err := generateSecret(16)
- if err != nil {
- t.Fatalf("generateSecret(16): %v", err)
- }
- if len(a) != 32 {
- // 16 bytes -> 32 hex chars
- t.Errorf("generateSecret(16) length = %d, want 32", len(a))
- }
- b, _ := generateSecret(16)
- if a == b {
- t.Errorf("generateSecret returned same value twice: %q", a)
- }
- }
- func ptr(s string) *string { return &s }
|