github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/service/usersearch_test.go (about) 1 // Copyright 2019 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package service 5 6 import ( 7 "errors" 8 "fmt" 9 "sort" 10 "strings" 11 "testing" 12 13 "github.com/keybase/client/go/contacts" 14 "github.com/keybase/client/go/protocol/keybase1" 15 "golang.org/x/net/context" 16 17 "github.com/keybase/client/go/kbtest" 18 "github.com/keybase/client/go/libkb" 19 "github.com/stretchr/testify/require" 20 ) 21 22 type emptyUserSearchProvider struct{} 23 24 func (*emptyUserSearchProvider) MakeSearchRequest(mctx libkb.MetaContext, arg keybase1.UserSearchArg) ([]keybase1.APIUserSearchResult, error) { 25 return nil, nil 26 } 27 28 type searchResultForTest struct { 29 id string 30 displayName string 31 displayLabel string 32 keybaseUsername string 33 } 34 35 // pluckSearchResultForTest normalizes APIUserSearchResult data for making test 36 // assertion code simpler. 37 func pluckSearchResultForTest(apiRes keybase1.APIUserSearchResult) searchResultForTest { 38 // TODO: For Y2K-310, usersearch handler will just return data like this, 39 // so this kind of normalization will not be needed. 40 switch { 41 case apiRes.Service != nil: 42 // Logic from shared/constants/team-building.tsx 43 var keybaseUsername string 44 var id string 45 label := apiRes.Service.FullName 46 if apiRes.Keybase != nil { 47 keybaseUsername = apiRes.Keybase.Username 48 id = keybaseUsername 49 if label == "" { 50 if apiRes.Keybase.FullName != nil { 51 label = *apiRes.Keybase.FullName 52 } else { 53 label = keybaseUsername 54 } 55 } 56 } else { 57 id = fmt.Sprintf("%s@%s", apiRes.Service.Username, apiRes.Service.ServiceName) 58 } 59 return searchResultForTest{ 60 id: id, 61 displayName: apiRes.Service.Username, 62 displayLabel: label, 63 keybaseUsername: keybaseUsername, 64 } 65 case apiRes.Contact != nil: 66 return searchResultForTest{ 67 id: apiRes.Contact.Assertion, 68 displayName: apiRes.Contact.DisplayName, 69 displayLabel: apiRes.Contact.DisplayLabel, 70 keybaseUsername: apiRes.Contact.Username, 71 } 72 case apiRes.Imptofu != nil: 73 return searchResultForTest{ 74 id: apiRes.Imptofu.Assertion, 75 displayName: apiRes.Imptofu.PrettyName, 76 displayLabel: apiRes.Imptofu.Label, 77 keybaseUsername: apiRes.Imptofu.KeybaseUsername, 78 } 79 case apiRes.Keybase != nil: 80 var fullName string 81 if apiRes.Keybase.FullName != nil { 82 fullName = *apiRes.Keybase.FullName 83 } 84 return searchResultForTest{ 85 id: apiRes.Keybase.Username, 86 displayName: apiRes.Keybase.Username, 87 displayLabel: fullName, 88 keybaseUsername: apiRes.Keybase.Username, 89 } 90 default: 91 panic("unexpected APIUserSearchResult for pluckSearchResultForTest") 92 } 93 } 94 95 func pluckAllSearchResultForTest(apiRes []keybase1.APIUserSearchResult) (res []searchResultForTest) { 96 res = make([]searchResultForTest, len(apiRes)) 97 for i, v := range apiRes { 98 res[i] = pluckSearchResultForTest(v) 99 } 100 return res 101 } 102 103 type makeContactArg struct { 104 index int 105 106 name string 107 label string 108 phone string 109 email string 110 111 // if resolved 112 username string 113 fullname string 114 following bool 115 } 116 117 func makeContact(arg makeContactArg) (res keybase1.ProcessedContact) { 118 res.ContactIndex = arg.index 119 res.ContactName = arg.name 120 res.Component.Label = arg.label 121 var componentValue string 122 if arg.phone != "" { 123 componentValue = arg.phone 124 phone := keybase1.RawPhoneNumber(arg.phone) 125 res.Component.PhoneNumber = &phone 126 res.Assertion = fmt.Sprintf("%s@phone", strings.TrimLeft(componentValue, "+")) 127 } else if arg.email != "" { 128 componentValue = arg.email 129 email := keybase1.EmailAddress(arg.email) 130 res.Component.Email = &email 131 res.Assertion = fmt.Sprintf("[%s]@email", componentValue) 132 } 133 if arg.username != "" { 134 res.Username = arg.username 135 res.Uid = libkb.UsernameToUID(arg.username) 136 res.Resolved = true 137 res.Following = arg.following 138 } 139 // Emulate contact sync display name/label generation. Will not be needed 140 // once Y2K-310 is done where we move all that logic to usersearch. 141 if arg.username != "" { 142 res.DisplayName = arg.username 143 if arg.following && arg.fullname != "" { 144 res.DisplayLabel = arg.fullname 145 } else if arg.name != "" { 146 res.DisplayLabel = arg.name 147 } else { 148 res.DisplayLabel = componentValue 149 } 150 } else { 151 res.DisplayName = arg.name 152 if arg.label != "" { 153 res.DisplayLabel = fmt.Sprintf("%s (%s)", componentValue, arg.label) 154 } else { 155 res.DisplayLabel = componentValue 156 } 157 } 158 return res 159 } 160 161 func stringifyAPIResult(list []keybase1.APIUserSearchResult) (res []string) { 162 res = make([]string, 0, len(list)) 163 for _, v := range list { 164 if v.Contact != nil { 165 res = append(res, fmt.Sprintf("%s,%s", v.Contact.DisplayName, v.Contact.DisplayLabel)) 166 } 167 } 168 return res 169 } 170 171 type testKeybaseUserSearchData struct { 172 username string 173 fullName string 174 serviceMap map[string]string 175 followee bool 176 } 177 178 type testUserSearchProvider struct { 179 T *testing.T 180 users []testKeybaseUserSearchData 181 } 182 183 type testAddUserArg struct { 184 username string 185 fullName string 186 } 187 188 func (p *testUserSearchProvider) addUser(args ...testAddUserArg) { 189 for _, arg := range args { 190 user := testKeybaseUserSearchData{ 191 username: arg.username, 192 fullName: arg.fullName, 193 } 194 p.users = append(p.users, user) 195 } 196 } 197 198 func (p *testUserSearchProvider) MakeSearchRequest(mctx libkb.MetaContext, arg keybase1.UserSearchArg) (res []keybase1.APIUserSearchResult, err error) { 199 if arg.Service != "keybase" && arg.Service != "" { 200 p.T.Errorf("unexpected service to MakeSearchRequest: %q", arg.Service) 201 return nil, errors.New("unexpected service") 202 } 203 204 // Use functions for contacts searching to emulate server behavior here. 205 query, err := compileQuery(arg.Query) 206 if err != nil { 207 return nil, err 208 } 209 210 for _, user := range p.users { 211 var found bool 212 var score float64 213 if found, score = query.scoreString(user.username); found { 214 // noop, query matched username 215 } else if found, score = query.scoreString(user.fullName); found { 216 // noop, query matched full name 217 } else if user.serviceMap != nil { 218 for _, serviceUser := range user.serviceMap { 219 if found, score = query.scoreString(serviceUser); found { 220 // query matched one of the services, break out of 221 // serviceMap loop 222 break 223 } 224 } 225 } 226 if found { 227 var fullname *string 228 if user.fullName != "" { 229 fn := user.fullName 230 fullname = &fn 231 } 232 keybase := keybase1.APIUserKeybaseResult{ 233 Username: user.username, 234 Uid: libkb.UsernameToUID(user.username), 235 FullName: fullname, 236 RawScore: score, 237 IsFollowee: user.followee, 238 } 239 res = append(res, keybase1.APIUserSearchResult{Keybase: &keybase}) 240 } 241 } 242 243 sort.Slice(res, func(i, j int) bool { 244 return res[i].Keybase.RawScore > res[j].Keybase.RawScore 245 }) 246 for i := range res { 247 res[i].Score = 1.0 / float64(1+i) 248 } 249 return res, nil 250 } 251 252 func setupUserSearchTest(t *testing.T) (tc libkb.TestContext, handler *UserSearchHandler, searchProv *testUserSearchProvider) { 253 tc = libkb.SetupTest(t, "contacts", 3) 254 tc.G.SyncedContactList = contacts.NewSavedContactsStore(tc.G) 255 256 _, err := kbtest.CreateAndSignupFakeUser("lmu", tc.G) 257 require.NoError(t, err) 258 259 contactsProv := &contacts.CachedContactsProvider{ 260 // Usersearch tests will call this provider for imp tofu searches, we 261 // want it to return errors but not fail entire test. 262 Provider: &contacts.ErrorContactsProvider{T: t, NoFail: true}, 263 Store: contacts.NewContactCacheStore(tc.G), 264 } 265 handler = NewUserSearchHandler(nil, tc.G, contactsProv) 266 searchProv = &testUserSearchProvider{T: t} 267 handler.searchProvider = searchProv 268 return tc, handler, searchProv 269 } 270 271 func TestContactSearch(t *testing.T) { 272 tc := libkb.SetupTest(t, "contacts", 3) 273 defer tc.Cleanup() 274 275 _, err := kbtest.CreateAndSignupFakeUser("lmu", tc.G) 276 require.NoError(t, err) 277 278 contactlist := []keybase1.ProcessedContact{ 279 makeContact(makeContactArg{index: 0, name: "Test Contact 1", username: "tuser1"}), 280 makeContact(makeContactArg{index: 1, name: "Office Building", phone: "+1123"}), 281 makeContact(makeContactArg{index: 2, name: "Michal", username: "michal"}), 282 makeContact(makeContactArg{index: 3, name: "TEST", phone: "+1555123456"}), 283 } 284 285 contactsProv := NewCachedContactsProvider(tc.G) 286 savedStore := contacts.NewSavedContactsStore(tc.G) 287 err = savedStore.SaveProcessedContacts(tc.MetaContext(), contactlist) 288 require.NoError(t, err) 289 tc.G.SyncedContactList = savedStore 290 291 searchHandler := NewUserSearchHandler(nil, tc.G, contactsProv) 292 searchHandler.searchProvider = &emptyUserSearchProvider{} 293 294 // Invalid: `IncludeContacts` can only be passed with service="keybase" 295 // (even if query is empty). 296 _, err = searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 297 IncludeContacts: true, 298 Service: "", 299 Query: "", 300 }) 301 require.Error(t, err) 302 303 res, err := searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 304 IncludeContacts: true, 305 Service: "keybase", 306 Query: "test", 307 MaxResults: 50, 308 }) 309 require.NoError(t, err) 310 require.Len(t, res, 2) 311 strList := stringifyAPIResult(res) 312 require.Contains(t, strList, "TEST,+1555123456") 313 require.Contains(t, strList, "tuser1,Test Contact 1") 314 315 res, err = searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 316 IncludeContacts: true, 317 Service: "keybase", 318 Query: "building", 319 }) 320 require.NoError(t, err) 321 require.Len(t, res, 1) 322 strList = stringifyAPIResult(res) 323 require.Contains(t, strList, "Office Building,+1123") 324 } 325 326 func TestContactSearchWide(t *testing.T) { 327 tc := libkb.SetupTest(t, "contacts", 3) 328 defer tc.Cleanup() 329 330 _, err := kbtest.CreateAndSignupFakeUser("lmu", tc.G) 331 require.NoError(t, err) 332 333 contactlist := []keybase1.ProcessedContact{ 334 makeContact(makeContactArg{name: "Test Contact 1", username: "tuser1"}), 335 makeContact(makeContactArg{name: "🍱🍜🍪 Lunch", phone: "+48123"}), 336 makeContact(makeContactArg{name: "Michal", username: "michal"}), 337 makeContact(makeContactArg{name: "高橋幸治", phone: "+81123456555"}), 338 } 339 340 contactsProv := NewCachedContactsProvider(tc.G) 341 savedStore := contacts.NewSavedContactsStore(tc.G) 342 err = savedStore.SaveProcessedContacts(tc.MetaContext(), contactlist) 343 require.NoError(t, err) 344 tc.G.SyncedContactList = savedStore 345 346 searchHandler := NewUserSearchHandler(nil, tc.G, contactsProv) 347 searchHandler.searchProvider = &emptyUserSearchProvider{} 348 349 res, err := searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 350 IncludeContacts: true, 351 Service: "keybase", 352 Query: "高橋", 353 }) 354 require.NoError(t, err) 355 require.Len(t, res, 1) 356 require.Equal(t, "高橋幸治", res[0].Contact.DisplayName) 357 358 for _, v := range []string{"🍜", "🍱", "lunch"} { 359 res, err = searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 360 IncludeContacts: true, 361 Service: "keybase", 362 Query: v, 363 }) 364 require.NoError(t, err) 365 require.Len(t, res, 1) 366 require.Equal(t, "🍱🍜🍪 Lunch", res[0].Contact.DisplayName) 367 } 368 } 369 370 func TestUserSearchResolvedUsersShouldGoFirst(t *testing.T) { 371 tc, searchHandler, searchProv := setupUserSearchTest(t) 372 defer tc.Cleanup() 373 374 contactlist := []keybase1.ProcessedContact{ 375 makeContact(makeContactArg{index: 0, name: "TEST", phone: "+1555123456"}), 376 makeContact(makeContactArg{index: 1, name: "Michal", email: "michal@example.com", username: "michal"}), 377 makeContact(makeContactArg{index: 2, name: "Test Contact 1", phone: "+1555165432", username: "tuser1", fullname: "Test User 123"}), 378 } 379 380 searchProv.addUser(testAddUserArg{"tuser1", "Test User 123"}) 381 382 err := tc.G.SyncedContactList.SaveProcessedContacts(tc.MetaContext(), contactlist) 383 require.NoError(t, err) 384 385 // "1555" query will match two users: name: "TEST", and name: "Test Contact 386 // 1". We should see the resolved one appear first, with the matched string 387 // (the phone number) being the label. 388 res, err := searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 389 IncludeContacts: true, 390 Service: "keybase", 391 Query: "1555", 392 MaxResults: 50, 393 }) 394 require.NoError(t, err) 395 require.Len(t, res, 2) 396 require.Equal(t, searchResultForTest{ 397 id: "1555165432@phone", 398 displayName: "tuser1", 399 displayLabel: "+1555165432", 400 keybaseUsername: "tuser1", 401 }, pluckSearchResultForTest(res[0])) 402 403 require.Equal(t, searchResultForTest{ 404 id: "1555123456@phone", 405 displayName: "TEST", 406 displayLabel: "+1555123456", 407 keybaseUsername: "", 408 }, pluckSearchResultForTest(res[1])) 409 410 // If we have a username match coming from the service, prefer it instead 411 // of contact result for the same user but with SBS assertion in it. 412 res, err = searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 413 IncludeContacts: true, 414 Service: "keybase", 415 Query: "tuser", 416 MaxResults: 50, 417 }) 418 require.NoError(t, err) 419 require.Equal(t, searchResultForTest{ 420 id: "tuser1", 421 displayName: "tuser1", 422 displayLabel: "Test User 123", 423 keybaseUsername: "tuser1", 424 }, pluckSearchResultForTest(res[0])) 425 } 426 427 func TestSearchContactDeduplicateNameAndLabel(t *testing.T) { 428 tc, searchHandler, _ := setupUserSearchTest(t) 429 defer tc.Cleanup() 430 431 contactlist := []keybase1.ProcessedContact{ 432 makeContact(makeContactArg{index: 0, name: "Alice", email: "a@example.org", username: "alice"}), 433 makeContact(makeContactArg{index: 1, name: "Mary Elizabeth Smith", email: "smith@example.com", username: "keybasetester"}), 434 makeContact(makeContactArg{index: 2, name: "Mary Elizabeth Smith", email: "mary.smith@example.com", username: "keybasetester"}), 435 makeContact(makeContactArg{index: 3, name: "Mary Elizabeth Smith", phone: "+1555123456", username: "keybasetester"}), 436 } 437 438 err := tc.G.SyncedContactList.SaveProcessedContacts(tc.MetaContext(), contactlist) 439 require.NoError(t, err) 440 441 { 442 // Best match here is `smith@example.com` and we expect to see only that 443 // because they all resolve to same user. 444 res, err := searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 445 IncludeContacts: true, 446 Service: "keybase", 447 Query: "smith", 448 MaxResults: 50, 449 }) 450 require.NoError(t, err) 451 require.Len(t, res, 1) 452 require.NotNil(t, res[0].Contact) 453 require.Equal(t, "[smith@example.com]@email", res[0].Contact.Assertion) 454 } 455 456 { 457 // But others are still findable 458 res, err := searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 459 IncludeContacts: true, 460 Service: "keybase", 461 Query: "555123456", 462 MaxResults: 50, 463 }) 464 require.NoError(t, err) 465 require.Len(t, res, 1) 466 require.NotNil(t, res[0].Contact) 467 require.Equal(t, "1555123456@phone", res[0].Contact.Assertion) 468 } 469 } 470 471 func TestContactSearchMixing(t *testing.T) { 472 tc, searchHandler, searchProv := setupUserSearchTest(t) 473 defer tc.Cleanup() 474 475 contactlist := []keybase1.ProcessedContact{ 476 makeContact(makeContactArg{index: 0, name: "Isaac Newton", phone: "+1555123456"}), 477 makeContact(makeContactArg{index: 1, name: "Pierre de Fermat", email: "fermatp@keyba.se", username: "pierre"}), 478 makeContact(makeContactArg{index: 2, name: "Gottfried Wilhelm Leibniz", phone: "+1555165432"}), 479 } 480 481 searchProv.addUser(testAddUserArg{username: "pierre"}) // the one we have in contacts 482 for i := 0; i < 5; i++ { 483 searchProv.addUser(testAddUserArg{fmt.Sprintf("isaac%d", i), fmt.Sprintf("The Isaac %d", i)}) 484 // Longer names score lower 485 searchProv.addUser(testAddUserArg{fmt.Sprintf("isaac_____%d", i), fmt.Sprintf("The Isaac %d", i)}) 486 searchProv.addUser(testAddUserArg{fmt.Sprintf("isaacsxzzz%d", i), fmt.Sprintf("The Isaac %d", i)}) 487 } 488 489 err := tc.G.SyncedContactList.SaveProcessedContacts(tc.MetaContext(), contactlist) 490 require.NoError(t, err) 491 492 { 493 // Expecting to see our contact within the results. All the `isaacX` 494 // users will score higher than our contact, so it will come 6th on the 495 // list. 496 res, err := searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 497 IncludeContacts: true, 498 Service: "keybase", 499 Query: "isaac", 500 MaxResults: 10, 501 }) 502 require.NoError(t, err) 503 require.Len(t, res, 10) 504 require.NotNil(t, res[5].Contact) 505 require.Equal(t, "1555123456@phone", res[5].Contact.Assertion) 506 require.Equal(t, "Isaac Newton", res[5].Contact.DisplayName) 507 } 508 } 509 510 func TestContactSearchMobilePhonesGoFirst(t *testing.T) { 511 tc, searchHandler, _ := setupUserSearchTest(t) 512 defer tc.Cleanup() 513 514 contactList := []keybase1.ProcessedContact{ 515 makeContact(makeContactArg{index: 0, name: "Isaac Newton", phone: "+1555123456", label: "home"}), 516 makeContact(makeContactArg{index: 1, name: "Isaac Newton", email: "isaac@newt.on"}), 517 makeContact(makeContactArg{index: 2, name: "Isaac Newton", phone: "+1555012345", label: "mobile"}), 518 } 519 520 err := tc.G.SyncedContactList.SaveProcessedContacts(tc.MetaContext(), contactList) 521 require.NoError(t, err) 522 523 { 524 // We expect to see the "mobile" phone number ranked above the "home" phone 525 // number, and both should rank above email. 526 res, err := searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 527 IncludeContacts: true, 528 Service: "keybase", 529 Query: "isaac", 530 MaxResults: 10, 531 }) 532 require.NoError(t, err) 533 require.Len(t, res, 3) 534 require.Equal(t, "1555012345@phone", res[0].Contact.Assertion) 535 require.Equal(t, "1555123456@phone", res[1].Contact.Assertion) 536 require.Equal(t, "[isaac@newt.on]@email", res[2].Contact.Assertion) 537 } 538 } 539 540 func TestUserSearchPhoneEmail(t *testing.T) { 541 tc, searchHandler, _ := setupUserSearchTest(t) 542 defer tc.Cleanup() 543 544 contactlist := []keybase1.ProcessedContact{ 545 makeContact(makeContactArg{index: 1, name: "Pierre de Fermat", email: "fermatp@keyba.se", username: "pierre"}), 546 makeContact(makeContactArg{index: 2, name: "Gottfried Wilhelm Leibniz", phone: "+1555165432", username: "lwg"}), 547 } 548 549 err := tc.G.SyncedContactList.SaveProcessedContacts(tc.MetaContext(), contactlist) 550 require.NoError(t, err) 551 552 doSearch := func(service, query string) []keybase1.APIUserSearchResult { 553 res, err := searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 554 IncludeContacts: false, 555 Service: service, 556 Query: query, 557 MaxResults: 10, 558 }) 559 require.NoError(t, err) 560 return res 561 } 562 563 { 564 // Imptofu searches (service "phone" or "email") do not search in 565 // synced contacts anymore. 566 query := "+1555165432" 567 res := doSearch("phone", query) 568 require.Len(t, res, 1) 569 require.NotNil(t, res[0].Imptofu) 570 require.Empty(t, res[0].Imptofu.KeybaseUsername) 571 require.Equal(t, "1555165432@phone", res[0].Imptofu.Assertion) 572 } 573 574 { 575 // Same with e-mail. 576 query := "fermatp@keyba.se" 577 res := doSearch("email", query) 578 require.Len(t, res, 1) 579 require.NotNil(t, res[0].Imptofu) 580 require.Empty(t, res[0].Imptofu.KeybaseUsername) 581 require.Equal(t, "[fermatp@keyba.se]@email", res[0].Imptofu.Assertion) 582 } 583 584 { 585 // Ask for a different number and get an imptofu result. 586 query := "+1201555201" 587 res := doSearch("phone", query) 588 require.Len(t, res, 1) 589 require.Nil(t, res[0].Contact) 590 require.NotNil(t, res[0].Imptofu) 591 require.Empty(t, res[0].Imptofu.KeybaseUsername) 592 require.Equal(t, "1201555201@phone", res[0].Imptofu.Assertion) 593 require.Empty(t, res[0].Imptofu.PrettyName) 594 require.Empty(t, res[0].Imptofu.Label) 595 require.Equal(t, "phone", res[0].Imptofu.AssertionKey) 596 require.Equal(t, "1201555201", res[0].Imptofu.AssertionValue) 597 } 598 599 { 600 // Imp tofu email. 601 query := "test@keyba.se" 602 res := doSearch("email", query) 603 require.Len(t, res, 1) 604 require.Nil(t, res[0].Contact) 605 require.NotNil(t, res[0].Imptofu) 606 require.Empty(t, res[0].Imptofu.KeybaseUsername) 607 require.Equal(t, "[test@keyba.se]@email", res[0].Imptofu.Assertion) 608 require.Empty(t, res[0].Imptofu.PrettyName) 609 require.Empty(t, res[0].Imptofu.Label) 610 require.Equal(t, "email", res[0].Imptofu.AssertionKey) 611 require.Equal(t, "test@keyba.se", res[0].Imptofu.AssertionValue) 612 } 613 614 { 615 // Email should be lowercased when returning search result. 616 query := "TEST@keyba.se" 617 res := doSearch("email", query) 618 require.Len(t, res, 1) 619 require.Nil(t, res[0].Contact) 620 require.NotNil(t, res[0].Imptofu) 621 require.Empty(t, res[0].Imptofu.KeybaseUsername) 622 // Assertion should be lowercased for display names. 623 require.Equal(t, "[test@keyba.se]@email", res[0].Imptofu.Assertion) 624 require.Empty(t, res[0].Imptofu.PrettyName) 625 require.Empty(t, res[0].Imptofu.Label) 626 require.Equal(t, "email", res[0].Imptofu.AssertionKey) 627 require.Equal(t, "test@keyba.se", res[0].Imptofu.AssertionValue) 628 } 629 } 630 631 func TestUserSearchBadArgs(t *testing.T) { 632 tc, searchHandler, _ := setupUserSearchTest(t) 633 defer tc.Cleanup() 634 635 // Invalid empty service name 636 _, err := searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 637 IncludeContacts: false, 638 Service: "", 639 Query: "test", 640 MaxResults: 10, 641 }) 642 require.Error(t, err) 643 644 // IncludeContacts=true with invalid `Service` (only "keybase" is allowed 645 // for IncludeContacts). 646 _, err = searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{ 647 IncludeContacts: true, 648 Service: "twitter", 649 Query: "test", 650 MaxResults: 10, 651 }) 652 require.Error(t, err) 653 } 654 655 func TestImptofuSearch(t *testing.T) { 656 tc := libkb.SetupTest(t, "usersearch", 1) 657 defer tc.Cleanup() 658 659 mockContactsProv := contacts.MakeMockProvider(t) 660 contactsProv := &contacts.CachedContactsProvider{ 661 Provider: mockContactsProv, 662 Store: contacts.NewContactCacheStore(tc.G), 663 } 664 665 searchHandler := NewUserSearchHandler(nil, tc.G, contactsProv) 666 667 mockContactsProv.PhoneNumbers["+48111222332"] = contacts.MakeMockLookupUser("alice", "Alice A") 668 mockContactsProv.Emails["bob@keyba.se"] = contacts.MakeMockLookupUser("bob", "Bobby") 669 670 ret, err := searchHandler.searchEmailsOrPhoneNumbers(tc.MetaContext(), 671 []keybase1.EmailAddress{}, []keybase1.RawPhoneNumber{"+48111222332"}, 672 true, true) 673 674 require.NoError(t, err) 675 require.Len(t, ret.emails, 0, "0 emails in results (we didn't ask)") 676 require.Len(t, ret.phoneNumbers, 1, "1 phone number in results") 677 phoneRet := ret.phoneNumbers[0] 678 require.Equal(t, phoneRet.input, "+48111222332") 679 require.Equal(t, phoneRet.assertion.String(), "48111222332@phone") 680 require.True(t, phoneRet.found) 681 require.True(t, phoneRet.UID.Exists()) 682 require.Equal(t, phoneRet.username, "alice") 683 require.Equal(t, phoneRet.fullName, "Alice A") 684 685 ret, err = searchHandler.searchEmailsOrPhoneNumbers(tc.MetaContext(), 686 []keybase1.EmailAddress{}, []keybase1.RawPhoneNumber{"+1555123456"}, 687 true, true) 688 689 require.NoError(t, err) 690 require.Len(t, ret.emails, 0, "0 emails in results (we didn't ask)") 691 require.Len(t, ret.phoneNumbers, 1, "1 phone number in results (even if it wasn't found)") 692 phoneRet = ret.phoneNumbers[0] 693 require.Equal(t, phoneRet.input, "+1555123456") 694 require.Equal(t, phoneRet.assertion.String(), "1555123456@phone") 695 require.False(t, phoneRet.found) 696 require.True(t, phoneRet.UID.IsNil()) 697 require.Empty(t, phoneRet.username) 698 require.Empty(t, phoneRet.fullName) 699 700 ret, err = searchHandler.searchEmailsOrPhoneNumbers(tc.MetaContext(), 701 []keybase1.EmailAddress{"bob@keyba.se"}, []keybase1.RawPhoneNumber{}, 702 true, true) 703 704 require.NoError(t, err) 705 require.Len(t, ret.emails, 1, "1 email in results") 706 require.Len(t, ret.phoneNumbers, 0, "0 phone numbers in results (we didn't ask)") 707 emailRet := ret.emails[0] 708 require.Equal(t, emailRet.input, "bob@keyba.se") 709 require.Equal(t, emailRet.assertion.String(), "[bob@keyba.se]@email") 710 require.True(t, emailRet.found) 711 require.True(t, emailRet.UID.Exists()) 712 require.Equal(t, emailRet.username, "bob") 713 require.Equal(t, emailRet.fullName, "Bobby") 714 715 ret, err = searchHandler.searchEmailsOrPhoneNumbers(tc.MetaContext(), 716 []keybase1.EmailAddress{"alice@keyba.se"}, []keybase1.RawPhoneNumber{}, 717 true, true) 718 719 require.NoError(t, err) 720 require.Len(t, ret.emails, 1, "1 email in results") 721 require.Len(t, ret.phoneNumbers, 0, "0 phone numbers in results (we didn't ask)") 722 emailRet = ret.emails[0] 723 require.Equal(t, emailRet.input, "alice@keyba.se") 724 require.Equal(t, emailRet.assertion.String(), "[alice@keyba.se]@email") 725 require.False(t, emailRet.found) 726 require.True(t, emailRet.UID.IsNil()) 727 require.Empty(t, emailRet.username) 728 require.Empty(t, emailRet.fullName) 729 } 730 731 func TestImptofuSearchMulti(t *testing.T) { 732 tc := libkb.SetupTest(t, "usersearch", 1) 733 defer tc.Cleanup() 734 735 mockContactsProv := contacts.MakeMockProvider(t) 736 contactsProv := &contacts.CachedContactsProvider{ 737 Provider: mockContactsProv, 738 Store: contacts.NewContactCacheStore(tc.G), 739 } 740 741 searchHandler := NewUserSearchHandler(nil, tc.G, contactsProv) 742 743 mockContactsProv.PhoneNumbers["+48111222332"] = contacts.MakeMockLookupUser("alice", "Alice A") 744 mockContactsProv.PhoneNumbers["+1123456789"] = contacts.MakeMockLookupUser("lily", "") 745 mockContactsProv.Emails["bobby6@example.com"] = contacts.MakeMockLookupUser("bob", "Bobby") 746 mockContactsProv.Emails["bob@keyba.se"] = contacts.MakeMockLookupUser("bob", "Bobby") 747 748 ret, err := searchHandler.searchEmailsOrPhoneNumbers(tc.MetaContext(), 749 []keybase1.EmailAddress{"bobby6@example.com", "bob@keyba.se", "alice@keyba.se", "alice"}, 750 []keybase1.RawPhoneNumber{"+48111222332", "+1123456789", "+44123123", "011"}, 751 true, true) 752 753 require.NoError(t, err) 754 755 // Number of results is always equal to the number of inputs. 756 require.Len(t, ret.emails, 4) 757 require.Len(t, ret.phoneNumbers, 4) 758 759 for i, v := range ret.emails { 760 if i < 2 { 761 // "bobby6@example.com", "bob@keyba.se" 762 require.True(t, v.validInput) 763 require.NotNil(t, v.assertion) 764 require.True(t, v.found) 765 require.True(t, v.UID.Exists()) 766 require.Equal(t, v.username, "bob") 767 require.Equal(t, v.fullName, "Bobby") 768 } else if i == 2 { 769 // "alice@keyba.se" 770 require.True(t, v.validInput) 771 require.NotNil(t, v.assertion) 772 require.False(t, v.found) 773 require.True(t, v.UID.IsNil()) 774 } else if i == 3 { 775 // "alice" 776 require.False(t, v.validInput) 777 require.Nil(t, v.assertion) 778 require.False(t, v.found) 779 require.True(t, v.UID.IsNil()) 780 } 781 } 782 783 for i, v := range ret.phoneNumbers { 784 if i < 2 { 785 require.True(t, v.validInput) 786 require.NotNil(t, v.assertion) 787 require.True(t, v.found) 788 require.True(t, v.UID.Exists()) 789 switch i { 790 case 0: 791 // "+48111222332", "+1123456789" 792 require.Equal(t, v.username, "alice") 793 require.Equal(t, v.fullName, "Alice A") 794 case 1: 795 require.Equal(t, v.username, "lily") 796 require.Equal(t, v.fullName, "") 797 } 798 } else if i == 2 { 799 // "+44123123" 800 require.True(t, v.validInput) 801 require.NotNil(t, v.assertion) 802 require.False(t, v.found) 803 require.True(t, v.UID.IsNil()) 804 } else if i == 3 { 805 // "011" 806 require.False(t, v.validInput) 807 require.Nil(t, v.assertion) 808 require.False(t, v.found) 809 require.True(t, v.UID.IsNil()) 810 } 811 } 812 } 813 814 func TestImptofuBadInput(t *testing.T) { 815 tc := libkb.SetupTest(t, "usersearch", 1) 816 defer tc.Cleanup() 817 818 mockContactsProv := contacts.MakeMockProvider(t) 819 contactsProv := &contacts.CachedContactsProvider{ 820 Provider: mockContactsProv, 821 Store: contacts.NewContactCacheStore(tc.G), 822 } 823 824 searchHandler := NewUserSearchHandler(nil, tc.G, contactsProv) 825 826 ret, err := searchHandler.searchEmailsOrPhoneNumbers(tc.MetaContext(), 827 []keybase1.EmailAddress{"alice"}, []keybase1.RawPhoneNumber{"test", "01234", "+1"}, 828 true, true) 829 830 require.NoError(t, err) 831 832 require.Len(t, ret.emails, 1) 833 require.Len(t, ret.phoneNumbers, 3) 834 require.Equal(t, ret.emails[0].input, "alice") 835 require.Equal(t, ret.phoneNumbers[0].input, "test") 836 require.Equal(t, ret.phoneNumbers[1].input, "01234") 837 require.Equal(t, ret.phoneNumbers[2].input, "+1") 838 839 all := ret.emails 840 all = append(all, ret.phoneNumbers...) 841 for _, v := range all { 842 require.Equal(t, v.validInput, false) 843 require.Nil(t, v.assertion) 844 require.False(t, v.found) 845 require.True(t, v.UID.IsNil()) 846 require.Empty(t, v.username) 847 require.Empty(t, v.fullName) 848 } 849 } 850 851 func TestBulkEmailSearch(t *testing.T) { 852 tc := libkb.SetupTest(t, "usersearch", 1) 853 defer tc.Cleanup() 854 855 mockContactsProv := contacts.MakeMockProvider(t) 856 contactsProv := &contacts.CachedContactsProvider{ 857 Provider: mockContactsProv, 858 Store: contacts.NewContactCacheStore(tc.G), 859 } 860 861 searchHandler := NewUserSearchHandler(nil, tc.G, contactsProv) 862 863 emails := []string{ 864 "alice@example.org", 865 "bob@example.com", 866 "no-reply@keybase.example.com", 867 "test@example.edu", 868 "hello@keybase.example.com", 869 } 870 separators := []string{ 871 ",", "\n", ", ", "\r\n", 872 } 873 874 query := "" 875 for i, v := range emails { 876 query += v 877 if i < len(emails)-1 { 878 query += separators[i%len(separators)] 879 } 880 } 881 882 ret, err := searchHandler.BulkEmailOrPhoneSearch(context.Background(), keybase1.BulkEmailOrPhoneSearchArg{ 883 Emails: query, 884 }) 885 886 require.NoError(t, err) 887 require.Len(t, ret, len(emails)) 888 for i, v := range ret { 889 require.Equal(t, v.Assertion, fmt.Sprintf("[%s]@email", emails[i])) 890 require.Equal(t, v.AssertionKey, "email") 891 require.Equal(t, v.AssertionValue, emails[i]) 892 893 require.False(t, v.FoundUser) 894 require.Empty(t, v.Username) 895 require.Empty(t, v.FullName) 896 } 897 898 ret, err = searchHandler.BulkEmailOrPhoneSearch(context.Background(), keybase1.BulkEmailOrPhoneSearchArg{ 899 Emails: "Alice <alice@example.com>,Bob <bob@example.com>", 900 }) 901 902 require.NoError(t, err) 903 require.Len(t, ret, 2) 904 require.Equal(t, ret[0].Input, "alice@example.com") 905 require.Equal(t, ret[0].Assertion, "[alice@example.com]@email") 906 require.Equal(t, ret[1].Input, "bob@example.com") 907 require.Equal(t, ret[1].Assertion, "[bob@example.com]@email") 908 } 909 910 func TestBulkEmailSearchBadInput(t *testing.T) { 911 tc := libkb.SetupTest(t, "usersearch", 1) 912 defer tc.Cleanup() 913 914 mockContactsProv := contacts.MakeMockProvider(t) 915 contactsProv := &contacts.CachedContactsProvider{ 916 Provider: mockContactsProv, 917 Store: contacts.NewContactCacheStore(tc.G), 918 } 919 920 searchHandler := NewUserSearchHandler(nil, tc.G, contactsProv) 921 922 emails := "\nalice:,alice@example.org, alice, x\n, ,\n" 923 ret, err := searchHandler.BulkEmailOrPhoneSearch(context.Background(), keybase1.BulkEmailOrPhoneSearchArg{ 924 Emails: emails, 925 }) 926 927 require.NoError(t, err) 928 require.Len(t, ret, 1) // there was only one valid email in there 929 require.Equal(t, ret[0].Input, "alice@example.org") 930 require.Equal(t, ret[0].Assertion, "[alice@example.org]@email") 931 require.Equal(t, ret[0].AssertionValue, "alice@example.org") 932 require.Equal(t, ret[0].AssertionKey, "email") 933 require.Equal(t, ret[0].FoundUser, false) 934 require.Equal(t, ret[0].Username, "") 935 require.Equal(t, ret[0].FullName, "") 936 937 ret, err = searchHandler.BulkEmailOrPhoneSearch(context.Background(), keybase1.BulkEmailOrPhoneSearchArg{ 938 PhoneNumbers: []keybase1.PhoneNumber{"+1", "00"}, 939 }) 940 941 require.NoError(t, err) 942 require.Len(t, ret, 0) 943 }