feat(19-04): add users.handle, HandleGenerator and user.Created
Generate a unique mention handle on API register and admin create, and fire user.Created from both paths so listeners run in the same write. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
78
classes/handle_generator_test.go
Normal file
78
classes/handle_generator_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package classes
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestHandle: generator charset is a subset of MentionParser, uniqueness
|
||||
// uses the smallest free integer suffix, and short names fall back to user{id}.
|
||||
func TestHandle(t *testing.T) {
|
||||
t.Parallel()
|
||||
g := HandleGenerator{}
|
||||
|
||||
if got := g.Slug("Paweł"); got != "pawel" {
|
||||
t.Fatalf("slug Paweł = %q, want pawel", got)
|
||||
}
|
||||
if got := g.Slug("John Doe"); got != "johndoe" {
|
||||
t.Fatalf("slug John Doe = %q, want johndoe", got)
|
||||
}
|
||||
if got := g.Slug("Zoë"); got != "zoe" {
|
||||
t.Fatalf("slug Zoë = %q, want zoe", got)
|
||||
}
|
||||
|
||||
handle := g.Generate("James", func(string) bool { return false }, nil)
|
||||
if handle != "james" {
|
||||
t.Fatalf("generate James = %q, want james", handle)
|
||||
}
|
||||
if !validHandle(handle) {
|
||||
t.Fatalf("generated %q is outside [a-z0-9._-]{2,255}", handle)
|
||||
}
|
||||
|
||||
taken := map[string]struct{}{}
|
||||
isTaken := func(c string) bool {
|
||||
_, ok := taken[c]
|
||||
return ok
|
||||
}
|
||||
first := g.Generate("James", isTaken, nil)
|
||||
taken[first] = struct{}{}
|
||||
second := g.Generate("James", isTaken, nil)
|
||||
taken[second] = struct{}{}
|
||||
third := g.Generate("James", isTaken, nil)
|
||||
if first != "james" || second != "james1" || third != "james2" {
|
||||
t.Fatalf("suffixes = %q %q %q, want james james1 james2", first, second, third)
|
||||
}
|
||||
|
||||
id := uint(42)
|
||||
if got := g.Generate("", func(string) bool { return false }, &id); got != "user42" {
|
||||
t.Fatalf("empty name = %q, want user42", got)
|
||||
}
|
||||
if got := g.Generate("x", func(string) bool { return false }, &id); got != "user42" {
|
||||
t.Fatalf("short name = %q, want user42", got)
|
||||
}
|
||||
if got := g.Generate("???", func(string) bool { return false }, &id); got != "user42" {
|
||||
t.Fatalf("punctuation name = %q, want user42", got)
|
||||
}
|
||||
if got := g.Generate("", func(string) bool { return false }, nil); got != "user" {
|
||||
t.Fatalf("no id = %q, want user", got)
|
||||
}
|
||||
|
||||
names := []string{"James", "Paweł", "John Doe", "???", "a", "Zoë", " ", "....", "O'Brien"}
|
||||
for _, name := range names {
|
||||
got := g.Generate(name, func(string) bool { return false }, &id)
|
||||
if !validHandle(got) {
|
||||
t.Errorf("generated %q for %q is outside the MentionParser subset", got, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validHandle(s string) bool {
|
||||
if len(s) < 2 || len(s) > 255 {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case r >= 'a' && r <= 'z', r >= '0' && r <= '9', r == '.', r == '_', r == '-':
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user