1use super::*;
2use crate::test_both_dbs;
3use chrono::Utc;
4use pretty_assertions::{assert_eq, assert_ne};
5use std::sync::Arc;
6
7test_both_dbs!(
8 test_get_users,
9 test_get_users_by_ids_postgres,
10 test_get_users_by_ids_sqlite
11);
12
13async fn test_get_users(db: &Arc<Database>) {
14 let mut user_ids = Vec::new();
15 let mut user_metric_ids = Vec::new();
16 for i in 1..=4 {
17 let user = db
18 .create_user(
19 &format!("user{i}@example.com"),
20 None,
21 false,
22 NewUserParams {
23 github_login: format!("user{i}"),
24 github_user_id: i,
25 },
26 )
27 .await
28 .unwrap();
29 user_ids.push(user.user_id);
30 user_metric_ids.push(user.metrics_id);
31 }
32
33 assert_eq!(
34 db.get_users_by_ids(user_ids.clone())
35 .await
36 .unwrap()
37 .into_iter()
38 .map(|user| (
39 user.id,
40 user.github_login,
41 user.github_user_id,
42 user.email_address
43 ))
44 .collect::<Vec<_>>(),
45 vec![
46 (
47 user_ids[0],
48 "user1".to_string(),
49 1,
50 Some("user1@example.com".to_string()),
51 ),
52 (
53 user_ids[1],
54 "user2".to_string(),
55 2,
56 Some("user2@example.com".to_string()),
57 ),
58 (
59 user_ids[2],
60 "user3".to_string(),
61 3,
62 Some("user3@example.com".to_string()),
63 ),
64 (
65 user_ids[3],
66 "user4".to_string(),
67 4,
68 Some("user4@example.com".to_string()),
69 )
70 ]
71 );
72}
73
74test_both_dbs!(
75 test_get_or_create_user_by_github_account,
76 test_get_or_create_user_by_github_account_postgres,
77 test_get_or_create_user_by_github_account_sqlite
78);
79
80async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
81 db.create_user(
82 "user1@example.com",
83 None,
84 false,
85 NewUserParams {
86 github_login: "login1".into(),
87 github_user_id: 101,
88 },
89 )
90 .await
91 .unwrap();
92 let user_id2 = db
93 .create_user(
94 "user2@example.com",
95 None,
96 false,
97 NewUserParams {
98 github_login: "login2".into(),
99 github_user_id: 102,
100 },
101 )
102 .await
103 .unwrap()
104 .user_id;
105
106 let user = db
107 .get_or_create_user_by_github_account("the-new-login2", 102, None, None, Utc::now(), None)
108 .await
109 .unwrap();
110 assert_eq!(user.id, user_id2);
111 assert_eq!(&user.github_login, "the-new-login2");
112 assert_eq!(user.github_user_id, 102);
113
114 let user = db
115 .get_or_create_user_by_github_account(
116 "login3",
117 103,
118 Some("user3@example.com"),
119 None,
120 Utc::now(),
121 None,
122 )
123 .await
124 .unwrap();
125 assert_eq!(&user.github_login, "login3");
126 assert_eq!(user.github_user_id, 103);
127 assert_eq!(user.email_address, Some("user3@example.com".into()));
128}
129
130test_both_dbs!(
131 test_create_access_tokens,
132 test_create_access_tokens_postgres,
133 test_create_access_tokens_sqlite
134);
135
136async fn test_create_access_tokens(db: &Arc<Database>) {
137 let user_1 = db
138 .create_user(
139 "u1@example.com",
140 None,
141 false,
142 NewUserParams {
143 github_login: "u1".into(),
144 github_user_id: 1,
145 },
146 )
147 .await
148 .unwrap()
149 .user_id;
150 let user_2 = db
151 .create_user(
152 "u2@example.com",
153 None,
154 false,
155 NewUserParams {
156 github_login: "u2".into(),
157 github_user_id: 2,
158 },
159 )
160 .await
161 .unwrap()
162 .user_id;
163
164 let token_1 = db.create_access_token(user_1, None, "h1", 2).await.unwrap();
165 let token_2 = db.create_access_token(user_1, None, "h2", 2).await.unwrap();
166 assert_eq!(
167 db.get_access_token(token_1).await.unwrap(),
168 access_token::Model {
169 id: token_1,
170 user_id: user_1,
171 impersonated_user_id: None,
172 hash: "h1".into(),
173 }
174 );
175 assert_eq!(
176 db.get_access_token(token_2).await.unwrap(),
177 access_token::Model {
178 id: token_2,
179 user_id: user_1,
180 impersonated_user_id: None,
181 hash: "h2".into()
182 }
183 );
184
185 let token_3 = db.create_access_token(user_1, None, "h3", 2).await.unwrap();
186 assert_eq!(
187 db.get_access_token(token_3).await.unwrap(),
188 access_token::Model {
189 id: token_3,
190 user_id: user_1,
191 impersonated_user_id: None,
192 hash: "h3".into()
193 }
194 );
195 assert_eq!(
196 db.get_access_token(token_2).await.unwrap(),
197 access_token::Model {
198 id: token_2,
199 user_id: user_1,
200 impersonated_user_id: None,
201 hash: "h2".into()
202 }
203 );
204 assert!(db.get_access_token(token_1).await.is_err());
205
206 let token_4 = db.create_access_token(user_1, None, "h4", 2).await.unwrap();
207 assert_eq!(
208 db.get_access_token(token_4).await.unwrap(),
209 access_token::Model {
210 id: token_4,
211 user_id: user_1,
212 impersonated_user_id: None,
213 hash: "h4".into()
214 }
215 );
216 assert_eq!(
217 db.get_access_token(token_3).await.unwrap(),
218 access_token::Model {
219 id: token_3,
220 user_id: user_1,
221 impersonated_user_id: None,
222 hash: "h3".into()
223 }
224 );
225 assert!(db.get_access_token(token_2).await.is_err());
226 assert!(db.get_access_token(token_1).await.is_err());
227
228 // An access token for user 2 impersonating user 1 does not
229 // count against user 1's access token limit (of 2).
230 let token_5 = db
231 .create_access_token(user_2, Some(user_1), "h5", 2)
232 .await
233 .unwrap();
234 assert_eq!(
235 db.get_access_token(token_5).await.unwrap(),
236 access_token::Model {
237 id: token_5,
238 user_id: user_2,
239 impersonated_user_id: Some(user_1),
240 hash: "h5".into()
241 }
242 );
243 assert_eq!(
244 db.get_access_token(token_3).await.unwrap(),
245 access_token::Model {
246 id: token_3,
247 user_id: user_1,
248 impersonated_user_id: None,
249 hash: "h3".into()
250 }
251 );
252
253 // Only a limited number (2) of access tokens are stored for user 2
254 // impersonating other users.
255 let token_6 = db
256 .create_access_token(user_2, Some(user_1), "h6", 2)
257 .await
258 .unwrap();
259 let token_7 = db
260 .create_access_token(user_2, Some(user_1), "h7", 2)
261 .await
262 .unwrap();
263 assert_eq!(
264 db.get_access_token(token_6).await.unwrap(),
265 access_token::Model {
266 id: token_6,
267 user_id: user_2,
268 impersonated_user_id: Some(user_1),
269 hash: "h6".into()
270 }
271 );
272 assert_eq!(
273 db.get_access_token(token_7).await.unwrap(),
274 access_token::Model {
275 id: token_7,
276 user_id: user_2,
277 impersonated_user_id: Some(user_1),
278 hash: "h7".into()
279 }
280 );
281 assert!(db.get_access_token(token_5).await.is_err());
282 assert_eq!(
283 db.get_access_token(token_3).await.unwrap(),
284 access_token::Model {
285 id: token_3,
286 user_id: user_1,
287 impersonated_user_id: None,
288 hash: "h3".into()
289 }
290 );
291}
292
293test_both_dbs!(
294 test_add_contacts,
295 test_add_contacts_postgres,
296 test_add_contacts_sqlite
297);
298
299async fn test_add_contacts(db: &Arc<Database>) {
300 let mut user_ids = Vec::new();
301 for i in 0..3 {
302 user_ids.push(
303 db.create_user(
304 &format!("user{i}@example.com"),
305 None,
306 false,
307 NewUserParams {
308 github_login: format!("user{i}"),
309 github_user_id: i,
310 },
311 )
312 .await
313 .unwrap()
314 .user_id,
315 );
316 }
317
318 let user_1 = user_ids[0];
319 let user_2 = user_ids[1];
320 let user_3 = user_ids[2];
321
322 // User starts with no contacts
323 assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
324
325 // User requests a contact. Both users see the pending request.
326 db.send_contact_request(user_1, user_2).await.unwrap();
327 assert!(!db.has_contact(user_1, user_2).await.unwrap());
328 assert!(!db.has_contact(user_2, user_1).await.unwrap());
329 assert_eq!(
330 db.get_contacts(user_1).await.unwrap(),
331 &[Contact::Outgoing { user_id: user_2 }],
332 );
333 assert_eq!(
334 db.get_contacts(user_2).await.unwrap(),
335 &[Contact::Incoming { user_id: user_1 }]
336 );
337
338 // User 2 dismisses the contact request notification without accepting or rejecting.
339 // We shouldn't notify them again.
340 db.dismiss_contact_notification(user_1, user_2)
341 .await
342 .unwrap_err();
343 db.dismiss_contact_notification(user_2, user_1)
344 .await
345 .unwrap();
346 assert_eq!(
347 db.get_contacts(user_2).await.unwrap(),
348 &[Contact::Incoming { user_id: user_1 }]
349 );
350
351 // User can't accept their own contact request
352 db.respond_to_contact_request(user_1, user_2, true)
353 .await
354 .unwrap_err();
355
356 // User accepts a contact request. Both users see the contact.
357 db.respond_to_contact_request(user_2, user_1, true)
358 .await
359 .unwrap();
360 assert_eq!(
361 db.get_contacts(user_1).await.unwrap(),
362 &[Contact::Accepted {
363 user_id: user_2,
364 busy: false,
365 }],
366 );
367 assert!(db.has_contact(user_1, user_2).await.unwrap());
368 assert!(db.has_contact(user_2, user_1).await.unwrap());
369 assert_eq!(
370 db.get_contacts(user_2).await.unwrap(),
371 &[Contact::Accepted {
372 user_id: user_1,
373 busy: false,
374 }]
375 );
376
377 // Users cannot re-request existing contacts.
378 db.send_contact_request(user_1, user_2).await.unwrap_err();
379 db.send_contact_request(user_2, user_1).await.unwrap_err();
380
381 // Users can't dismiss notifications of them accepting other users' requests.
382 db.dismiss_contact_notification(user_2, user_1)
383 .await
384 .unwrap_err();
385 assert_eq!(
386 db.get_contacts(user_1).await.unwrap(),
387 &[Contact::Accepted {
388 user_id: user_2,
389 busy: false,
390 }]
391 );
392
393 // Users can dismiss notifications of other users accepting their requests.
394 db.dismiss_contact_notification(user_1, user_2)
395 .await
396 .unwrap();
397 assert_eq!(
398 db.get_contacts(user_1).await.unwrap(),
399 &[Contact::Accepted {
400 user_id: user_2,
401 busy: false,
402 }]
403 );
404
405 // Users send each other concurrent contact requests and
406 // see that they are immediately accepted.
407 db.send_contact_request(user_1, user_3).await.unwrap();
408 db.send_contact_request(user_3, user_1).await.unwrap();
409 assert_eq!(
410 db.get_contacts(user_1).await.unwrap(),
411 &[
412 Contact::Accepted {
413 user_id: user_2,
414 busy: false,
415 },
416 Contact::Accepted {
417 user_id: user_3,
418 busy: false,
419 }
420 ]
421 );
422 assert_eq!(
423 db.get_contacts(user_3).await.unwrap(),
424 &[Contact::Accepted {
425 user_id: user_1,
426 busy: false,
427 }],
428 );
429
430 // User declines a contact request. Both users see that it is gone.
431 db.send_contact_request(user_2, user_3).await.unwrap();
432 db.respond_to_contact_request(user_3, user_2, false)
433 .await
434 .unwrap();
435 assert!(!db.has_contact(user_2, user_3).await.unwrap());
436 assert!(!db.has_contact(user_3, user_2).await.unwrap());
437 assert_eq!(
438 db.get_contacts(user_2).await.unwrap(),
439 &[Contact::Accepted {
440 user_id: user_1,
441 busy: false,
442 }]
443 );
444 assert_eq!(
445 db.get_contacts(user_3).await.unwrap(),
446 &[Contact::Accepted {
447 user_id: user_1,
448 busy: false,
449 }],
450 );
451}
452
453test_both_dbs!(
454 test_metrics_id,
455 test_metrics_id_postgres,
456 test_metrics_id_sqlite
457);
458
459async fn test_metrics_id(db: &Arc<Database>) {
460 let NewUserResult {
461 user_id: user1,
462 metrics_id: metrics_id1,
463 ..
464 } = db
465 .create_user(
466 "person1@example.com",
467 None,
468 false,
469 NewUserParams {
470 github_login: "person1".into(),
471 github_user_id: 101,
472 },
473 )
474 .await
475 .unwrap();
476 let NewUserResult {
477 user_id: user2,
478 metrics_id: metrics_id2,
479 ..
480 } = db
481 .create_user(
482 "person2@example.com",
483 None,
484 false,
485 NewUserParams {
486 github_login: "person2".into(),
487 github_user_id: 102,
488 },
489 )
490 .await
491 .unwrap();
492
493 assert_eq!(db.get_user_metrics_id(user1).await.unwrap(), metrics_id1);
494 assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id2);
495 assert_eq!(metrics_id1.len(), 36);
496 assert_eq!(metrics_id2.len(), 36);
497 assert_ne!(metrics_id1, metrics_id2);
498}
499
500test_both_dbs!(
501 test_project_count,
502 test_project_count_postgres,
503 test_project_count_sqlite
504);
505
506async fn test_project_count(db: &Arc<Database>) {
507 let owner_id = db.create_server("test").await.unwrap().0 as u32;
508
509 let user1 = db
510 .create_user(
511 "admin@example.com",
512 None,
513 true,
514 NewUserParams {
515 github_login: "admin".into(),
516 github_user_id: 0,
517 },
518 )
519 .await
520 .unwrap();
521 let user2 = db
522 .create_user(
523 "user@example.com",
524 None,
525 false,
526 NewUserParams {
527 github_login: "user".into(),
528 github_user_id: 1,
529 },
530 )
531 .await
532 .unwrap();
533
534 let room_id = RoomId::from_proto(
535 db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "")
536 .await
537 .unwrap()
538 .id,
539 );
540 db.call(
541 room_id,
542 user1.user_id,
543 ConnectionId { owner_id, id: 0 },
544 user2.user_id,
545 None,
546 )
547 .await
548 .unwrap();
549 db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 })
550 .await
551 .unwrap();
552 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
553
554 db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false)
555 .await
556 .unwrap();
557 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
558
559 db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false)
560 .await
561 .unwrap();
562 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
563
564 // Projects shared by admins aren't counted.
565 db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false)
566 .await
567 .unwrap();
568 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
569
570 db.leave_room(ConnectionId { owner_id, id: 1 })
571 .await
572 .unwrap();
573 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
574}
575
576#[test]
577fn test_fuzzy_like_string() {
578 assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
579 assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
580 assert_eq!(Database::fuzzy_like_string(" z "), "%z%");
581}
582
583#[cfg(target_os = "macos")]
584#[gpui::test]
585async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
586 let test_db = tests::TestDb::postgres(cx.executor());
587 let db = test_db.db();
588 for (i, github_login) in [
589 "California",
590 "colorado",
591 "oregon",
592 "washington",
593 "florida",
594 "delaware",
595 "rhode-island",
596 ]
597 .into_iter()
598 .enumerate()
599 {
600 db.create_user(
601 &format!("{github_login}@example.com"),
602 None,
603 false,
604 NewUserParams {
605 github_login: github_login.into(),
606 github_user_id: i as i32,
607 },
608 )
609 .await
610 .unwrap();
611 }
612
613 assert_eq!(
614 fuzzy_search_user_names(db, "clr").await,
615 &["colorado", "California"]
616 );
617 assert_eq!(
618 fuzzy_search_user_names(db, "ro").await,
619 &["rhode-island", "colorado", "oregon"],
620 );
621
622 async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
623 db.fuzzy_search_users(query, 10)
624 .await
625 .unwrap()
626 .into_iter()
627 .map(|user| user.github_login)
628 .collect::<Vec<_>>()
629 }
630}