fix(usernames): sync index active collectible aliases
This commit is contained in:
parent
2f995f607a
commit
5156b17c1b
29 changed files with 657 additions and 112 deletions
|
|
@ -597,7 +597,10 @@ func (s *Service) ResolvePublicUsername(ctx context.Context, userID int64, usern
|
|||
return domain.Channel{}, false, domain.ErrChannelInvalid
|
||||
}
|
||||
username = normalizeChannelUsername(username)
|
||||
if !validChannelUsername(username) {
|
||||
// Public resolution also covers Fragment-style collectible usernames,
|
||||
// whose protocol minimum is four characters. Channel username mutation
|
||||
// remains on the ordinary 5..32 validation path above.
|
||||
if !domain.ValidCollectibleUsername(username) {
|
||||
return domain.Channel{}, false, domain.ErrUsernameInvalid
|
||||
}
|
||||
return s.channels.ResolvePublicChannelUsername(ctx, userID, username)
|
||||
|
|
|
|||
|
|
@ -2904,7 +2904,10 @@ func TestListSendAsChannelsFiltersPostMessageRights(t *testing.T) {
|
|||
|
||||
func TestPublicChannelSearchAndResolveUsername(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
service := NewService(memory.NewChannelStore())
|
||||
channelStore := memory.NewChannelStore()
|
||||
registry := memory.NewCollectibleUsernameStore()
|
||||
channelStore.AttachUsernameRegistry(registry)
|
||||
service := NewService(channelStore)
|
||||
created, err := service.CreateMegagroupFromCreateChat(ctx, 1001, domain.CreateChannelRequest{
|
||||
Title: "CU Public Lab",
|
||||
MemberUserIDs: []int64{1002},
|
||||
|
|
@ -2945,6 +2948,53 @@ func TestPublicChannelSearchAndResolveUsername(t *testing.T) {
|
|||
if err != nil || !found || resolved.ID != public.ID {
|
||||
t.Fatalf("ResolvePublicUsername = %+v found %v err %v, want public channel", resolved, found, err)
|
||||
}
|
||||
peer := domain.Peer{Type: domain.PeerTypeChannel, ID: public.ID}
|
||||
if _, created, err := registry.MintCollectibleUsername(ctx, domain.MintCollectibleUsernameRequest{
|
||||
Username: "nfc4",
|
||||
Owner: peer,
|
||||
Currency: domain.CollectibleCurrencyStars,
|
||||
Amount: 1,
|
||||
Actor: "test",
|
||||
}); err != nil || !created {
|
||||
t.Fatalf("mint channel collectible: created=%v err=%v", created, err)
|
||||
}
|
||||
resolved, found, err = service.ResolvePublicUsername(ctx, 1003, "@NFC4")
|
||||
if err != nil || !found || resolved.ID != public.ID {
|
||||
t.Fatalf("ResolvePublicUsername collectible = %+v found %v err %v, want public channel", resolved, found, err)
|
||||
}
|
||||
collectibleSearch, err := service.SearchPublicChannels(ctx, 1003, "nfc", 10)
|
||||
if err != nil || len(collectibleSearch.Results) != 1 || collectibleSearch.Results[0].ID != public.ID {
|
||||
t.Fatalf("collectible channel search = %+v err=%v, want public channel", collectibleSearch, err)
|
||||
}
|
||||
if _, err := service.UpdateUsername(ctx, 1001, domain.UpdateChannelUsernameRequest{
|
||||
ChannelID: public.ID,
|
||||
Username: "",
|
||||
}); err != nil {
|
||||
t.Fatalf("clear editable username: %v", err)
|
||||
}
|
||||
resolved, found, err = service.ResolvePublicUsername(ctx, 1003, "nfc4")
|
||||
if err != nil || !found || resolved.ID != public.ID {
|
||||
t.Fatalf("NFT-only ResolvePublicUsername = %+v found=%v err=%v", resolved, found, err)
|
||||
}
|
||||
if view, err := service.GetChannel(ctx, 1003, public.ID); err != nil || view.Channel.ID != public.ID {
|
||||
t.Fatalf("NFT-only public preview = %+v err=%v", view, err)
|
||||
}
|
||||
if _, err := service.UpdateUsername(ctx, 1001, domain.UpdateChannelUsernameRequest{
|
||||
ChannelID: public.ID,
|
||||
Username: "cu_public_lab",
|
||||
}); err != nil {
|
||||
t.Fatalf("restore editable username: %v", err)
|
||||
}
|
||||
if changed, err := registry.SetUsernameActive(ctx, peer, "nfc4", false); err != nil || !changed {
|
||||
t.Fatalf("deactivate channel collectible: changed=%v err=%v", changed, err)
|
||||
}
|
||||
if _, found, err := service.ResolvePublicUsername(ctx, 1003, "nfc4"); err != nil || found {
|
||||
t.Fatalf("inactive collectible resolve found=%v err=%v, want hidden", found, err)
|
||||
}
|
||||
hiddenSearch, err := service.SearchPublicChannels(ctx, 1003, "nfc4", 10)
|
||||
if err != nil || len(hiddenSearch.Results) != 0 {
|
||||
t.Fatalf("inactive collectible search = %+v err=%v, want empty", hiddenSearch, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicChannelPreviewAllowsNonMemberHistory(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -644,6 +644,46 @@ func TestAcceptContactRequiresExistingContactRequest(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSearchFindsOnlyActiveCollectibleUsernames(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
users := memory.NewUserStore()
|
||||
registry := memory.NewCollectibleUsernameStore()
|
||||
users.AttachUsernameRegistry(registry)
|
||||
viewer, err := users.Create(ctx, domain.User{Phone: "15550000101", FirstName: "Viewer"})
|
||||
if err != nil {
|
||||
t.Fatalf("create viewer: %v", err)
|
||||
}
|
||||
target, err := users.Create(ctx, domain.User{Phone: "15550000102", FirstName: "Unrelated", Username: "target_slot"})
|
||||
if err != nil {
|
||||
t.Fatalf("create target: %v", err)
|
||||
}
|
||||
peer := domain.Peer{Type: domain.PeerTypeUser, ID: target.ID}
|
||||
if _, err := registry.SetEditableUsername(ctx, peer, target.Username); err != nil {
|
||||
t.Fatalf("seed editable username: %v", err)
|
||||
}
|
||||
if _, created, err := registry.MintCollectibleUsername(ctx, domain.MintCollectibleUsernameRequest{
|
||||
Username: "nft4",
|
||||
Owner: peer,
|
||||
Currency: domain.CollectibleCurrencyStars,
|
||||
Amount: 1,
|
||||
Actor: "test",
|
||||
}); err != nil || !created {
|
||||
t.Fatalf("mint collectible: created=%v err=%v", created, err)
|
||||
}
|
||||
svc := NewService(memory.NewContactStore(), users)
|
||||
found, err := svc.Search(ctx, viewer.ID, "@NFT4", 10)
|
||||
if err != nil || len(found.Results) != 1 || found.Results[0].ID != target.ID {
|
||||
t.Fatalf("search active collectible = %+v err=%v, want target", found, err)
|
||||
}
|
||||
if changed, err := registry.SetUsernameActive(ctx, peer, "nft4", false); err != nil || !changed {
|
||||
t.Fatalf("deactivate collectible: changed=%v err=%v", changed, err)
|
||||
}
|
||||
hidden, err := svc.Search(ctx, viewer.ID, "nft4", 10)
|
||||
if err != nil || len(hidden.Results) != 0 || len(hidden.MyResults) != 0 {
|
||||
t.Fatalf("search inactive collectible = %+v err=%v, want empty", hidden, err)
|
||||
}
|
||||
}
|
||||
|
||||
func contactByID(t *testing.T, list domain.ContactList, id int64) domain.Contact {
|
||||
t.Helper()
|
||||
for _, contact := range list.Contacts {
|
||||
|
|
|
|||
|
|
@ -577,7 +577,11 @@ func (s *Service) ResolveUsername(ctx context.Context, currentUserID int64, user
|
|||
return domain.User{}, false, err
|
||||
}
|
||||
username = normalizeUsername(username)
|
||||
if !validUsername(username) {
|
||||
// Resolution covers both the editable username slot (5..32) and
|
||||
// Fragment-style collectible usernames (4..32). Keep the stricter
|
||||
// validUsername check on create/update paths; only lookup accepts the
|
||||
// collectible lower bound.
|
||||
if !domain.ValidCollectibleUsername(username) {
|
||||
return domain.User{}, false, domain.ErrUsernameInvalid
|
||||
}
|
||||
u, found, err := s.users.ByUsername(ctx, username)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,34 @@ func TestServiceUsernameLifecycle(t *testing.T) {
|
|||
if err != nil || !found || resolved.ID != owner.ID {
|
||||
t.Fatalf("ResolveUsername = user %+v found %v err %v, want owner", resolved, found, err)
|
||||
}
|
||||
registry := memory.NewCollectibleUsernameStore()
|
||||
store.AttachUsernameRegistry(registry)
|
||||
peer := domain.Peer{Type: domain.PeerTypeUser, ID: owner.ID}
|
||||
if _, err := registry.SetEditableUsername(ctx, peer, updated.Username); err != nil {
|
||||
t.Fatalf("seed editable username registry: %v", err)
|
||||
}
|
||||
if _, created, err := registry.MintCollectibleUsername(ctx, domain.MintCollectibleUsernameRequest{
|
||||
Username: "nft4",
|
||||
Owner: peer,
|
||||
Currency: domain.CollectibleCurrencyStars,
|
||||
Amount: 1,
|
||||
Actor: "test",
|
||||
}); err != nil || !created {
|
||||
t.Fatalf("mint four-character collectible: created=%v err=%v", created, err)
|
||||
}
|
||||
resolved, found, err = svc.ResolveUsername(ctx, other.ID, "@NFT4")
|
||||
if err != nil || !found || resolved.ID != owner.ID {
|
||||
t.Fatalf("ResolveUsername collectible = user %+v found %v err %v, want owner", resolved, found, err)
|
||||
}
|
||||
if _, err := svc.UpdateUsername(ctx, owner.ID, "nft4"); !errors.Is(err, domain.ErrUsernameInvalid) {
|
||||
t.Fatalf("four-character editable username err = %v, want username invalid", err)
|
||||
}
|
||||
if changed, err := registry.SetUsernameActive(ctx, peer, "nft4", false); err != nil || !changed {
|
||||
t.Fatalf("deactivate collectible: changed=%v err=%v", changed, err)
|
||||
}
|
||||
if _, found, err := svc.ResolveUsername(ctx, other.ID, "nft4"); err != nil || found {
|
||||
t.Fatalf("inactive collectible found=%v err=%v, want hidden", found, err)
|
||||
}
|
||||
if _, err := svc.UpdateUsername(ctx, owner.ID, "TAKEN_NAME"); !errors.Is(err, domain.ErrUsernameOccupied) {
|
||||
t.Fatalf("UpdateUsername duplicate err = %v, want username occupied", err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue