feat(kol): add KOL profile field validators
This commit is contained in:
@@ -0,0 +1,53 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/geo-platform/tenant-api/internal/shared/response"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
kolDisplayNameMaxLen = 40
|
||||||
|
kolBioMaxLen = 500
|
||||||
|
kolAvatarURLPrefix = "/api/public/assets/"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateKolDisplayName(raw string) (string, error) {
|
||||||
|
v := strings.TrimSpace(raw)
|
||||||
|
if v == "" {
|
||||||
|
return "", response.ErrBadRequest(40001, "kol_profile_invalid_display_name", "display_name is required")
|
||||||
|
}
|
||||||
|
if utf8.RuneCountInString(v) > kolDisplayNameMaxLen {
|
||||||
|
return "", response.ErrBadRequest(40001, "kol_profile_invalid_display_name", "display_name must be at most 40 characters")
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateKolBio(raw *string) (*string, error) {
|
||||||
|
if raw == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
v := strings.TrimSpace(*raw)
|
||||||
|
if v == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if utf8.RuneCountInString(v) > kolBioMaxLen {
|
||||||
|
return nil, response.ErrBadRequest(40002, "kol_profile_invalid_bio", "bio must be at most 500 characters")
|
||||||
|
}
|
||||||
|
return &v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateKolAvatarURL(raw *string) (*string, error) {
|
||||||
|
if raw == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
v := strings.TrimSpace(*raw)
|
||||||
|
if v == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(v, kolAvatarURLPrefix) {
|
||||||
|
return nil, response.ErrBadRequest(40003, "kol_profile_invalid_avatar_url", "avatar_url must be an asset path served by this service")
|
||||||
|
}
|
||||||
|
return &v, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateKolDisplayName(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
wantOK bool
|
||||||
|
wantVal string
|
||||||
|
}{
|
||||||
|
{"trim spaces", " alice ", true, "alice"},
|
||||||
|
{"keep unicode", "张小明", true, "张小明"},
|
||||||
|
{"empty rejected", " ", false, ""},
|
||||||
|
{"40 chars ok", strings.Repeat("a", 40), true, strings.Repeat("a", 40)},
|
||||||
|
{"41 chars rejected", strings.Repeat("a", 41), false, ""},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got, err := validateKolDisplayName(tc.input)
|
||||||
|
if tc.wantOK && err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !tc.wantOK && err == nil {
|
||||||
|
t.Fatalf("expected error, got nil (value=%q)", got)
|
||||||
|
}
|
||||||
|
if tc.wantOK && got != tc.wantVal {
|
||||||
|
t.Fatalf("got %q want %q", got, tc.wantVal)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateKolBio(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input *string
|
||||||
|
wantOK bool
|
||||||
|
}{
|
||||||
|
{"nil ok", nil, true},
|
||||||
|
{"empty trimmed to nil", strPtr(" "), true},
|
||||||
|
{"500 chars ok", strPtr(strings.Repeat("b", 500)), true},
|
||||||
|
{"501 chars rejected", strPtr(strings.Repeat("b", 501)), false},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err := validateKolBio(tc.input)
|
||||||
|
if tc.wantOK && err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !tc.wantOK && err == nil {
|
||||||
|
t.Fatalf("expected error, got nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateKolAvatarURL(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input *string
|
||||||
|
wantOK bool
|
||||||
|
}{
|
||||||
|
{"nil ok", nil, true},
|
||||||
|
{"empty trimmed to nil", strPtr(" "), true},
|
||||||
|
{"asset path ok", strPtr("/api/public/assets/abc.def"), true},
|
||||||
|
{"external url rejected", strPtr("https://evil.example.com/a.jpg"), false},
|
||||||
|
{"javascript scheme rejected", strPtr("javascript:alert(1)"), false},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
_, err := validateKolAvatarURL(tc.input)
|
||||||
|
if tc.wantOK && err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !tc.wantOK && err == nil {
|
||||||
|
t.Fatalf("expected error, got nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func strPtr(s string) *string { return &s }
|
||||||
Reference in New Issue
Block a user