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