package cabana import ( "strings" "testing" "time" "unicode/utf8" "git.golem15.com/golem15/summercms/modules/bouncer" "git.golem15.com/golem15/summercms/modules/pact" "git.golem15.com/golem15/summercms/modules/party" ) func TestAPITokenMint(t *testing.T) { seen := map[string]bool{} for i := 0; i < 1000; i++ { plain, hash, hint, err := mintAPIToken() if err != nil { t.Fatal(err) } if !wellFormedAPIToken(plain) { t.Fatalf("plain %q", plain) } if len(hash) != 64 { t.Fatalf("hash len %d", len(hash)) } for _, c := range hash { if (c < '0' || c > '9') && (c < 'a' || c > 'f') { t.Fatalf("hash non-hex %q", hash) } } if hint != plain[len(plain)-4:] { t.Fatalf("hint %q", hint) } if seen[plain] { t.Fatalf("duplicate plain at %d", i) } seen[plain] = true if hashAPIToken(plain) != hash { t.Fatal("hashAPIToken mismatch") } } } func TestParseTokenExpiry(t *testing.T) { now := time.Date(2026, 6, 15, 12, 0, 0, 0, time.UTC) cases := []struct { name string expires string cli bool wantKey string check func(*testing.T, *time.Time) }{ {"30d", "30d", false, "", func(t *testing.T, got *time.Time) { if got == nil || !got.Equal(now.Add(30*24*time.Hour)) { t.Fatalf("%v", got) } }}, {"90d", "90d", false, "", func(t *testing.T, got *time.Time) { if got == nil || !got.Equal(now.Add(90*24*time.Hour)) { t.Fatalf("%v", got) } }}, {"365d", "365d", false, "", func(t *testing.T, got *time.Time) { if got == nil || !got.Equal(now.Add(365*24*time.Hour)) { t.Fatalf("%v", got) } }}, {"never", "never", false, "", func(t *testing.T, got *time.Time) { if got != nil { t.Fatalf("%v", got) } }}, {"absent defaults 90d", "", false, "", func(t *testing.T, got *time.Time) { if got == nil || !got.Equal(now.Add(90*24*time.Hour)) { t.Fatalf("%v", got) } }}, {"cli future date", "2026-07-01", true, "", func(t *testing.T, got *time.Time) { want := time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC) if got == nil || !got.Equal(want) { t.Fatalf("%v", got) } }}, {"cli today refused", "2026-06-15", true, "api_tokens.expires_invalid", nil}, {"cli past refused", "2026-01-01", true, "api_tokens.expires_invalid", nil}, {"garbage api", "tomorrow", false, "api_tokens.expires_invalid", nil}, {"date in api mode refused", "2026-07-01", false, "api_tokens.expires_invalid", nil}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got, key := parseTokenExpiry(tc.expires, now, tc.cli) if key != tc.wantKey { t.Fatalf("key = %q, want %q", key, tc.wantKey) } if tc.check != nil { tc.check(t, got) } }) } } func TestValidateTokenRequest(t *testing.T) { reg, err := compileTokenCatalog([]party.Plugin{scopePlugin{id: "acme.demo", scopes: []pact.TokenScope{{ Name: "demo", Label: "Demo", Description: "D", Permissions: []string{"acme.demo.*"}, Methods: []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"}, }}}}) if err != nil { t.Fatal(err) } owner := &bouncer.Principal{ ID: 1, Backend: true, PermissionGrants: map[string]bool{"acme.demo.access": true}, } now := time.Date(2026, 6, 15, 12, 0, 0, 0, time.UTC) _, details := validateTokenRequest(reg, owner, "", "90d", []string{"demo"}, now, false) if details["name"] == nil { t.Fatal("blank name") } long := strings.Repeat("x", 101) if utf8.RuneCountInString(long) != 101 { t.Fatal("fixture") } _, details = validateTokenRequest(reg, owner, long, "90d", []string{"demo"}, now, false) if details["name"] == nil { t.Fatal("over-100 name") } _, details = validateTokenRequest(reg, owner, "ok", "nope", []string{"demo"}, now, false) if details["expires"] == nil { t.Fatal("invalid expiry") } _, details = validateTokenRequest(reg, owner, "ok", "90d", nil, now, false) if details["scopes"] == nil { t.Fatal("empty scopes") } _, details = validateTokenRequest(reg, owner, "ok", "90d", []string{"missing"}, now, false) if details["scopes"] == nil { t.Fatal("unknown scope") } noOverlap := &bouncer.Principal{ID: 2, Backend: true, PermissionGrants: map[string]bool{"acme.other.x": true}} _, details = validateTokenRequest(reg, noOverlap, "ok", "90d", []string{"demo"}, now, false) if details["scopes"] == nil { t.Fatal("non-overlapping scope") } cleaned, details := validateTokenRequest(reg, owner, "ok", "90d", []string{"demo", "*", "demo"}, now, false) if details != nil { t.Fatalf("star normalize details=%v", details) } if len(cleaned.Scopes) != 1 || cleaned.Scopes[0] != "*" { t.Fatalf("star normalize = %#v", cleaned.Scopes) } cleaned, details = validateTokenRequest(reg, owner, "ok", "90d", []string{"demo", "demo"}, now, false) if details != nil || len(cleaned.Scopes) != 1 || cleaned.Scopes[0] != "demo" { t.Fatalf("dup removal = %#v details=%v", cleaned.Scopes, details) } }