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