1use crate::{
2 db::{self, UserId},
3 rpc::RECONNECT_TIMEOUT,
4 tests::{room_participants, RoomParticipants, TestServer},
5};
6use call::ActiveCall;
7use channel::{ChannelId, ChannelMembership, ChannelStore};
8use client::User;
9use futures::future::try_join_all;
10use gpui::{BackgroundExecutor, Model, TestAppContext};
11use rpc::{
12 proto::{self, ChannelRole},
13 RECEIVE_TIMEOUT,
14};
15use std::sync::Arc;
16
17#[gpui::test]
18async fn test_core_channels(
19 executor: BackgroundExecutor,
20 cx_a: &mut TestAppContext,
21 cx_b: &mut TestAppContext,
22) {
23 let mut server = TestServer::start(executor.clone()).await;
24 let client_a = server.create_client(cx_a, "user_a").await;
25 let client_b = server.create_client(cx_b, "user_b").await;
26
27 let channel_a_id = client_a
28 .channel_store()
29 .update(cx_a, |channel_store, cx| {
30 channel_store.create_channel("channel-a", None, cx)
31 })
32 .await
33 .unwrap();
34 let channel_b_id = client_a
35 .channel_store()
36 .update(cx_a, |channel_store, cx| {
37 channel_store.create_channel("channel-b", Some(channel_a_id), cx)
38 })
39 .await
40 .unwrap();
41
42 executor.run_until_parked();
43 assert_channels(
44 client_a.channel_store(),
45 cx_a,
46 &[
47 ExpectedChannel {
48 id: channel_a_id,
49 name: "channel-a".to_string(),
50 depth: 0,
51 role: ChannelRole::Admin,
52 },
53 ExpectedChannel {
54 id: channel_b_id,
55 name: "channel-b".to_string(),
56 depth: 1,
57 role: ChannelRole::Admin,
58 },
59 ],
60 );
61
62 cx_b.read(|cx| {
63 client_b.channel_store().read_with(cx, |channels, _| {
64 assert!(channels.ordered_channels().collect::<Vec<_>>().is_empty())
65 })
66 });
67
68 // Invite client B to channel A as client A.
69 client_a
70 .channel_store()
71 .update(cx_a, |store, cx| {
72 assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
73
74 let invite = store.invite_member(
75 channel_a_id,
76 client_b.user_id().unwrap(),
77 proto::ChannelRole::Member,
78 cx,
79 );
80
81 // Make sure we're synchronously storing the pending invite
82 assert!(store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
83 invite
84 })
85 .await
86 .unwrap();
87
88 // Client A sees that B has been invited.
89 executor.run_until_parked();
90 assert_channel_invitations(
91 client_b.channel_store(),
92 cx_b,
93 &[ExpectedChannel {
94 id: channel_a_id,
95 name: "channel-a".to_string(),
96 depth: 0,
97 role: ChannelRole::Member,
98 }],
99 );
100
101 let members = client_a
102 .channel_store()
103 .update(cx_a, |store, cx| {
104 assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
105 store.get_channel_member_details(channel_a_id, cx)
106 })
107 .await
108 .unwrap();
109 assert_members_eq(
110 &members,
111 &[
112 (
113 client_a.user_id().unwrap(),
114 proto::ChannelRole::Admin,
115 proto::channel_member::Kind::Member,
116 ),
117 (
118 client_b.user_id().unwrap(),
119 proto::ChannelRole::Member,
120 proto::channel_member::Kind::Invitee,
121 ),
122 ],
123 );
124
125 // Client B accepts the invitation.
126 client_b
127 .channel_store()
128 .update(cx_b, |channels, cx| {
129 channels.respond_to_channel_invite(channel_a_id, true, cx)
130 })
131 .await
132 .unwrap();
133 executor.run_until_parked();
134
135 // Client B now sees that they are a member of channel A and its existing subchannels.
136 assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
137 assert_channels(
138 client_b.channel_store(),
139 cx_b,
140 &[
141 ExpectedChannel {
142 id: channel_a_id,
143 name: "channel-a".to_string(),
144 role: ChannelRole::Member,
145 depth: 0,
146 },
147 ExpectedChannel {
148 id: channel_b_id,
149 name: "channel-b".to_string(),
150 role: ChannelRole::Member,
151 depth: 1,
152 },
153 ],
154 );
155
156 let channel_c_id = client_a
157 .channel_store()
158 .update(cx_a, |channel_store, cx| {
159 channel_store.create_channel("channel-c", Some(channel_b_id), cx)
160 })
161 .await
162 .unwrap();
163
164 executor.run_until_parked();
165 assert_channels(
166 client_b.channel_store(),
167 cx_b,
168 &[
169 ExpectedChannel {
170 id: channel_a_id,
171 name: "channel-a".to_string(),
172 role: ChannelRole::Member,
173 depth: 0,
174 },
175 ExpectedChannel {
176 id: channel_b_id,
177 name: "channel-b".to_string(),
178 role: ChannelRole::Member,
179 depth: 1,
180 },
181 ExpectedChannel {
182 id: channel_c_id,
183 name: "channel-c".to_string(),
184 role: ChannelRole::Member,
185 depth: 2,
186 },
187 ],
188 );
189
190 // Update client B's membership to channel A to be an admin.
191 client_a
192 .channel_store()
193 .update(cx_a, |store, cx| {
194 store.set_member_role(
195 channel_a_id,
196 client_b.user_id().unwrap(),
197 proto::ChannelRole::Admin,
198 cx,
199 )
200 })
201 .await
202 .unwrap();
203 executor.run_until_parked();
204
205 // Observe that client B is now an admin of channel A, and that
206 // their admin priveleges extend to subchannels of channel A.
207 assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
208 assert_channels(
209 client_b.channel_store(),
210 cx_b,
211 &[
212 ExpectedChannel {
213 id: channel_a_id,
214 name: "channel-a".to_string(),
215 depth: 0,
216 role: ChannelRole::Admin,
217 },
218 ExpectedChannel {
219 id: channel_b_id,
220 name: "channel-b".to_string(),
221 depth: 1,
222 role: ChannelRole::Admin,
223 },
224 ExpectedChannel {
225 id: channel_c_id,
226 name: "channel-c".to_string(),
227 depth: 2,
228 role: ChannelRole::Admin,
229 },
230 ],
231 );
232
233 // Client A deletes the channel, deletion also deletes subchannels.
234 client_a
235 .channel_store()
236 .update(cx_a, |channel_store, _| {
237 channel_store.remove_channel(channel_b_id)
238 })
239 .await
240 .unwrap();
241
242 executor.run_until_parked();
243 assert_channels(
244 client_a.channel_store(),
245 cx_a,
246 &[ExpectedChannel {
247 id: channel_a_id,
248 name: "channel-a".to_string(),
249 depth: 0,
250 role: ChannelRole::Admin,
251 }],
252 );
253 assert_channels(
254 client_b.channel_store(),
255 cx_b,
256 &[ExpectedChannel {
257 id: channel_a_id,
258 name: "channel-a".to_string(),
259 depth: 0,
260 role: ChannelRole::Admin,
261 }],
262 );
263
264 // Remove client B
265 client_a
266 .channel_store()
267 .update(cx_a, |channel_store, cx| {
268 channel_store.remove_member(channel_a_id, client_b.user_id().unwrap(), cx)
269 })
270 .await
271 .unwrap();
272
273 executor.run_until_parked();
274
275 // Client A still has their channel
276 assert_channels(
277 client_a.channel_store(),
278 cx_a,
279 &[ExpectedChannel {
280 id: channel_a_id,
281 name: "channel-a".to_string(),
282 depth: 0,
283 role: ChannelRole::Admin,
284 }],
285 );
286
287 // Client B no longer has access to the channel
288 assert_channels(client_b.channel_store(), cx_b, &[]);
289
290 server.forbid_connections();
291 server.disconnect_client(client_a.peer_id().unwrap());
292 executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
293
294 server
295 .app_state
296 .db
297 .rename_channel(
298 db::ChannelId::from_proto(channel_a_id),
299 UserId::from_proto(client_a.id()),
300 "channel-a-renamed",
301 )
302 .await
303 .unwrap();
304
305 server.allow_connections();
306 executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
307 assert_channels(
308 client_a.channel_store(),
309 cx_a,
310 &[ExpectedChannel {
311 id: channel_a_id,
312 name: "channel-a-renamed".to_string(),
313 depth: 0,
314 role: ChannelRole::Admin,
315 }],
316 );
317}
318
319#[track_caller]
320fn assert_participants_eq(participants: &[Arc<User>], expected_partitipants: &[u64]) {
321 assert_eq!(
322 participants.iter().map(|p| p.id).collect::<Vec<_>>(),
323 expected_partitipants
324 );
325}
326
327#[track_caller]
328fn assert_members_eq(
329 members: &[ChannelMembership],
330 expected_members: &[(u64, proto::ChannelRole, proto::channel_member::Kind)],
331) {
332 assert_eq!(
333 members
334 .iter()
335 .map(|member| (member.user.id, member.role, member.kind))
336 .collect::<Vec<_>>(),
337 expected_members
338 );
339}
340
341#[gpui::test]
342async fn test_joining_channel_ancestor_member(
343 executor: BackgroundExecutor,
344 cx_a: &mut TestAppContext,
345 cx_b: &mut TestAppContext,
346) {
347 let mut server = TestServer::start(executor.clone()).await;
348
349 let client_a = server.create_client(cx_a, "user_a").await;
350 let client_b = server.create_client(cx_b, "user_b").await;
351
352 let parent_id = server
353 .make_channel("parent", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
354 .await;
355
356 let sub_id = client_a
357 .channel_store()
358 .update(cx_a, |channel_store, cx| {
359 channel_store.create_channel("sub_channel", Some(parent_id), cx)
360 })
361 .await
362 .unwrap();
363
364 let active_call_b = cx_b.read(ActiveCall::global);
365
366 assert!(active_call_b
367 .update(cx_b, |active_call, cx| active_call
368 .join_channel(sub_id, None, cx))
369 .await
370 .is_ok());
371}
372
373#[gpui::test]
374async fn test_channel_room(
375 executor: BackgroundExecutor,
376 cx_a: &mut TestAppContext,
377 cx_b: &mut TestAppContext,
378 cx_c: &mut TestAppContext,
379) {
380 let mut server = TestServer::start(executor.clone()).await;
381 let client_a = server.create_client(cx_a, "user_a").await;
382 let client_b = server.create_client(cx_b, "user_b").await;
383 let client_c = server.create_client(cx_c, "user_c").await;
384
385 let zed_id = server
386 .make_channel(
387 "zed",
388 None,
389 (&client_a, cx_a),
390 &mut [(&client_b, cx_b), (&client_c, cx_c)],
391 )
392 .await;
393
394 let active_call_a = cx_a.read(ActiveCall::global);
395 let active_call_b = cx_b.read(ActiveCall::global);
396
397 active_call_a
398 .update(cx_a, |active_call, cx| {
399 active_call.join_channel(zed_id, None, cx)
400 })
401 .await
402 .unwrap();
403
404 // Give everyone a chance to observe user A joining
405 executor.run_until_parked();
406 let room_a =
407 cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
408 cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
409
410 cx_a.read(|cx| {
411 client_a.channel_store().read_with(cx, |channels, _| {
412 assert_participants_eq(
413 channels.channel_participants(zed_id),
414 &[client_a.user_id().unwrap()],
415 );
416 })
417 });
418
419 assert_channels(
420 client_b.channel_store(),
421 cx_b,
422 &[ExpectedChannel {
423 id: zed_id,
424 name: "zed".to_string(),
425 depth: 0,
426 role: ChannelRole::Member,
427 }],
428 );
429 cx_b.read(|cx| {
430 client_b.channel_store().read_with(cx, |channels, _| {
431 assert_participants_eq(
432 channels.channel_participants(zed_id),
433 &[client_a.user_id().unwrap()],
434 );
435 })
436 });
437
438 cx_c.read(|cx| {
439 client_c.channel_store().read_with(cx, |channels, _| {
440 assert_participants_eq(
441 channels.channel_participants(zed_id),
442 &[client_a.user_id().unwrap()],
443 );
444 })
445 });
446
447 active_call_b
448 .update(cx_b, |active_call, cx| {
449 active_call.join_channel(zed_id, None, cx)
450 })
451 .await
452 .unwrap();
453
454 executor.run_until_parked();
455
456 cx_a.read(|cx| {
457 client_a.channel_store().read_with(cx, |channels, _| {
458 assert_participants_eq(
459 channels.channel_participants(zed_id),
460 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
461 );
462 })
463 });
464
465 cx_b.read(|cx| {
466 client_b.channel_store().read_with(cx, |channels, _| {
467 assert_participants_eq(
468 channels.channel_participants(zed_id),
469 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
470 );
471 })
472 });
473
474 cx_c.read(|cx| {
475 client_c.channel_store().read_with(cx, |channels, _| {
476 assert_participants_eq(
477 channels.channel_participants(zed_id),
478 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
479 );
480 })
481 });
482
483 let room_a =
484 cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
485 cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
486 assert_eq!(
487 room_participants(&room_a, cx_a),
488 RoomParticipants {
489 remote: vec!["user_b".to_string()],
490 pending: vec![]
491 }
492 );
493
494 let room_b =
495 cx_b.read(|cx| active_call_b.read_with(cx, |call, _| call.room().unwrap().clone()));
496 cx_b.read(|cx| room_b.read_with(cx, |room, _| assert!(room.is_connected())));
497 assert_eq!(
498 room_participants(&room_b, cx_b),
499 RoomParticipants {
500 remote: vec!["user_a".to_string()],
501 pending: vec![]
502 }
503 );
504
505 // Make sure that leaving and rejoining works
506
507 active_call_a
508 .update(cx_a, |active_call, cx| active_call.hang_up(cx))
509 .await
510 .unwrap();
511
512 executor.run_until_parked();
513
514 cx_a.read(|cx| {
515 client_a.channel_store().read_with(cx, |channels, _| {
516 assert_participants_eq(
517 channels.channel_participants(zed_id),
518 &[client_b.user_id().unwrap()],
519 );
520 })
521 });
522
523 cx_b.read(|cx| {
524 client_b.channel_store().read_with(cx, |channels, _| {
525 assert_participants_eq(
526 channels.channel_participants(zed_id),
527 &[client_b.user_id().unwrap()],
528 );
529 })
530 });
531
532 cx_c.read(|cx| {
533 client_c.channel_store().read_with(cx, |channels, _| {
534 assert_participants_eq(
535 channels.channel_participants(zed_id),
536 &[client_b.user_id().unwrap()],
537 );
538 })
539 });
540
541 active_call_b
542 .update(cx_b, |active_call, cx| active_call.hang_up(cx))
543 .await
544 .unwrap();
545
546 executor.run_until_parked();
547
548 cx_a.read(|cx| {
549 client_a.channel_store().read_with(cx, |channels, _| {
550 assert_participants_eq(channels.channel_participants(zed_id), &[]);
551 })
552 });
553
554 cx_b.read(|cx| {
555 client_b.channel_store().read_with(cx, |channels, _| {
556 assert_participants_eq(channels.channel_participants(zed_id), &[]);
557 })
558 });
559
560 cx_c.read(|cx| {
561 client_c.channel_store().read_with(cx, |channels, _| {
562 assert_participants_eq(channels.channel_participants(zed_id), &[]);
563 })
564 });
565
566 active_call_a
567 .update(cx_a, |active_call, cx| {
568 active_call.join_channel(zed_id, None, cx)
569 })
570 .await
571 .unwrap();
572
573 active_call_b
574 .update(cx_b, |active_call, cx| {
575 active_call.join_channel(zed_id, None, cx)
576 })
577 .await
578 .unwrap();
579
580 executor.run_until_parked();
581
582 let room_a =
583 cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
584 cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
585 assert_eq!(
586 room_participants(&room_a, cx_a),
587 RoomParticipants {
588 remote: vec!["user_b".to_string()],
589 pending: vec![]
590 }
591 );
592
593 let room_b =
594 cx_b.read(|cx| active_call_b.read_with(cx, |call, _| call.room().unwrap().clone()));
595 cx_b.read(|cx| room_b.read_with(cx, |room, _| assert!(room.is_connected())));
596 assert_eq!(
597 room_participants(&room_b, cx_b),
598 RoomParticipants {
599 remote: vec!["user_a".to_string()],
600 pending: vec![]
601 }
602 );
603}
604
605#[gpui::test]
606async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppContext) {
607 let mut server = TestServer::start(executor.clone()).await;
608 let client_a = server.create_client(cx_a, "user_a").await;
609
610 let zed_id = server
611 .make_channel("zed", None, (&client_a, cx_a), &mut [])
612 .await;
613 let rust_id = server
614 .make_channel("rust", None, (&client_a, cx_a), &mut [])
615 .await;
616
617 let active_call_a = cx_a.read(ActiveCall::global);
618
619 active_call_a
620 .update(cx_a, |active_call, cx| {
621 active_call.join_channel(zed_id, None, cx)
622 })
623 .await
624 .unwrap();
625
626 // Give everything a chance to observe user A joining
627 executor.run_until_parked();
628
629 cx_a.read(|cx| {
630 client_a.channel_store().read_with(cx, |channels, _| {
631 assert_participants_eq(
632 channels.channel_participants(zed_id),
633 &[client_a.user_id().unwrap()],
634 );
635 assert_participants_eq(channels.channel_participants(rust_id), &[]);
636 })
637 });
638
639 active_call_a
640 .update(cx_a, |active_call, cx| {
641 active_call.join_channel(rust_id, None, cx)
642 })
643 .await
644 .unwrap();
645
646 executor.run_until_parked();
647
648 cx_a.read(|cx| {
649 client_a.channel_store().read_with(cx, |channels, _| {
650 assert_participants_eq(channels.channel_participants(zed_id), &[]);
651 assert_participants_eq(
652 channels.channel_participants(rust_id),
653 &[client_a.user_id().unwrap()],
654 );
655 })
656 });
657}
658
659#[gpui::test]
660async fn test_permissions_update_while_invited(
661 executor: BackgroundExecutor,
662 cx_a: &mut TestAppContext,
663 cx_b: &mut TestAppContext,
664) {
665 let mut server = TestServer::start(executor.clone()).await;
666 let client_a = server.create_client(cx_a, "user_a").await;
667 let client_b = server.create_client(cx_b, "user_b").await;
668
669 let rust_id = server
670 .make_channel("rust", None, (&client_a, cx_a), &mut [])
671 .await;
672
673 client_a
674 .channel_store()
675 .update(cx_a, |channel_store, cx| {
676 channel_store.invite_member(
677 rust_id,
678 client_b.user_id().unwrap(),
679 proto::ChannelRole::Member,
680 cx,
681 )
682 })
683 .await
684 .unwrap();
685
686 executor.run_until_parked();
687
688 assert_channel_invitations(
689 client_b.channel_store(),
690 cx_b,
691 &[ExpectedChannel {
692 depth: 0,
693 id: rust_id,
694 name: "rust".to_string(),
695 role: ChannelRole::Member,
696 }],
697 );
698 assert_channels(client_b.channel_store(), cx_b, &[]);
699
700 // Update B's invite before they've accepted it
701 client_a
702 .channel_store()
703 .update(cx_a, |channel_store, cx| {
704 channel_store.set_member_role(
705 rust_id,
706 client_b.user_id().unwrap(),
707 proto::ChannelRole::Admin,
708 cx,
709 )
710 })
711 .await
712 .unwrap();
713
714 executor.run_until_parked();
715
716 assert_channel_invitations(
717 client_b.channel_store(),
718 cx_b,
719 &[ExpectedChannel {
720 depth: 0,
721 id: rust_id,
722 name: "rust".to_string(),
723 role: ChannelRole::Member,
724 }],
725 );
726 assert_channels(client_b.channel_store(), cx_b, &[]);
727}
728
729#[gpui::test]
730async fn test_channel_rename(
731 executor: BackgroundExecutor,
732 cx_a: &mut TestAppContext,
733 cx_b: &mut TestAppContext,
734) {
735 let mut server = TestServer::start(executor.clone()).await;
736 let client_a = server.create_client(cx_a, "user_a").await;
737 let client_b = server.create_client(cx_b, "user_b").await;
738
739 let rust_id = server
740 .make_channel("rust", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
741 .await;
742
743 // Rename the channel
744 client_a
745 .channel_store()
746 .update(cx_a, |channel_store, cx| {
747 channel_store.rename(rust_id, "#rust-archive", cx)
748 })
749 .await
750 .unwrap();
751
752 executor.run_until_parked();
753
754 // Client A sees the channel with its new name.
755 assert_channels(
756 client_a.channel_store(),
757 cx_a,
758 &[ExpectedChannel {
759 depth: 0,
760 id: rust_id,
761 name: "rust-archive".to_string(),
762 role: ChannelRole::Admin,
763 }],
764 );
765
766 // Client B sees the channel with its new name.
767 assert_channels(
768 client_b.channel_store(),
769 cx_b,
770 &[ExpectedChannel {
771 depth: 0,
772 id: rust_id,
773 name: "rust-archive".to_string(),
774 role: ChannelRole::Member,
775 }],
776 );
777}
778
779#[gpui::test]
780async fn test_call_from_channel(
781 executor: BackgroundExecutor,
782 cx_a: &mut TestAppContext,
783 cx_b: &mut TestAppContext,
784 cx_c: &mut TestAppContext,
785) {
786 let mut server = TestServer::start(executor.clone()).await;
787 let client_a = server.create_client(cx_a, "user_a").await;
788 let client_b = server.create_client(cx_b, "user_b").await;
789 let client_c = server.create_client(cx_c, "user_c").await;
790 server
791 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
792 .await;
793
794 let channel_id = server
795 .make_channel(
796 "x",
797 None,
798 (&client_a, cx_a),
799 &mut [(&client_b, cx_b), (&client_c, cx_c)],
800 )
801 .await;
802
803 let active_call_a = cx_a.read(ActiveCall::global);
804 let active_call_b = cx_b.read(ActiveCall::global);
805
806 active_call_a
807 .update(cx_a, |call, cx| call.join_channel(channel_id, None, cx))
808 .await
809 .unwrap();
810
811 // Client A calls client B while in the channel.
812 active_call_a
813 .update(cx_a, |call, cx| {
814 call.invite(client_b.user_id().unwrap(), None, cx)
815 })
816 .await
817 .unwrap();
818
819 // Client B accepts the call.
820 executor.run_until_parked();
821 active_call_b
822 .update(cx_b, |call, cx| call.accept_incoming(cx))
823 .await
824 .unwrap();
825
826 // Client B sees that they are now in the channel
827 executor.run_until_parked();
828 cx_b.read(|cx| {
829 active_call_b.read_with(cx, |call, cx| {
830 assert_eq!(call.channel_id(cx), Some(channel_id));
831 })
832 });
833 cx_b.read(|cx| {
834 client_b.channel_store().read_with(cx, |channels, _| {
835 assert_participants_eq(
836 channels.channel_participants(channel_id),
837 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
838 );
839 })
840 });
841
842 // Clients A and C also see that client B is in the channel.
843 cx_a.read(|cx| {
844 client_a.channel_store().read_with(cx, |channels, _| {
845 assert_participants_eq(
846 channels.channel_participants(channel_id),
847 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
848 );
849 })
850 });
851 cx_c.read(|cx| {
852 client_c.channel_store().read_with(cx, |channels, _| {
853 assert_participants_eq(
854 channels.channel_participants(channel_id),
855 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
856 );
857 })
858 });
859}
860
861#[gpui::test]
862async fn test_lost_channel_creation(
863 executor: BackgroundExecutor,
864 cx_a: &mut TestAppContext,
865 cx_b: &mut TestAppContext,
866) {
867 let mut server = TestServer::start(executor.clone()).await;
868 let client_a = server.create_client(cx_a, "user_a").await;
869 let client_b = server.create_client(cx_b, "user_b").await;
870
871 server
872 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
873 .await;
874
875 let channel_id = server
876 .make_channel("x", None, (&client_a, cx_a), &mut [])
877 .await;
878
879 // Invite a member
880 client_a
881 .channel_store()
882 .update(cx_a, |channel_store, cx| {
883 channel_store.invite_member(
884 channel_id,
885 client_b.user_id().unwrap(),
886 proto::ChannelRole::Member,
887 cx,
888 )
889 })
890 .await
891 .unwrap();
892
893 executor.run_until_parked();
894
895 // Sanity check, B has the invitation
896 assert_channel_invitations(
897 client_b.channel_store(),
898 cx_b,
899 &[ExpectedChannel {
900 depth: 0,
901 id: channel_id,
902 name: "x".to_string(),
903 role: ChannelRole::Member,
904 }],
905 );
906
907 // A creates a subchannel while the invite is still pending.
908 let subchannel_id = client_a
909 .channel_store()
910 .update(cx_a, |channel_store, cx| {
911 channel_store.create_channel("subchannel", Some(channel_id), cx)
912 })
913 .await
914 .unwrap();
915
916 executor.run_until_parked();
917
918 // Make sure A sees their new channel
919 assert_channels(
920 client_a.channel_store(),
921 cx_a,
922 &[
923 ExpectedChannel {
924 depth: 0,
925 id: channel_id,
926 name: "x".to_string(),
927 role: ChannelRole::Admin,
928 },
929 ExpectedChannel {
930 depth: 1,
931 id: subchannel_id,
932 name: "subchannel".to_string(),
933 role: ChannelRole::Admin,
934 },
935 ],
936 );
937
938 // Client B accepts the invite
939 client_b
940 .channel_store()
941 .update(cx_b, |channel_store, cx| {
942 channel_store.respond_to_channel_invite(channel_id, true, cx)
943 })
944 .await
945 .unwrap();
946
947 executor.run_until_parked();
948
949 // Client B should now see the channel
950 assert_channels(
951 client_b.channel_store(),
952 cx_b,
953 &[
954 ExpectedChannel {
955 depth: 0,
956 id: channel_id,
957 name: "x".to_string(),
958 role: ChannelRole::Member,
959 },
960 ExpectedChannel {
961 depth: 1,
962 id: subchannel_id,
963 name: "subchannel".to_string(),
964 role: ChannelRole::Member,
965 },
966 ],
967 );
968}
969
970#[gpui::test]
971async fn test_channel_link_notifications(
972 executor: BackgroundExecutor,
973 cx_a: &mut TestAppContext,
974 cx_b: &mut TestAppContext,
975 cx_c: &mut TestAppContext,
976) {
977 let mut server = TestServer::start(executor.clone()).await;
978 let client_a = server.create_client(cx_a, "user_a").await;
979 let client_b = server.create_client(cx_b, "user_b").await;
980 let client_c = server.create_client(cx_c, "user_c").await;
981
982 let user_b = client_b.user_id().unwrap();
983 let user_c = client_c.user_id().unwrap();
984
985 let channels = server
986 .make_channel_tree(&[("zed", None)], (&client_a, cx_a))
987 .await;
988 let zed_channel = channels[0];
989
990 try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
991 [
992 channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
993 channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Member, cx),
994 channel_store.invite_member(zed_channel, user_c, proto::ChannelRole::Guest, cx),
995 ]
996 }))
997 .await
998 .unwrap();
999
1000 executor.run_until_parked();
1001
1002 client_b
1003 .channel_store()
1004 .update(cx_b, |channel_store, cx| {
1005 channel_store.respond_to_channel_invite(zed_channel, true, cx)
1006 })
1007 .await
1008 .unwrap();
1009
1010 client_c
1011 .channel_store()
1012 .update(cx_c, |channel_store, cx| {
1013 channel_store.respond_to_channel_invite(zed_channel, true, cx)
1014 })
1015 .await
1016 .unwrap();
1017
1018 executor.run_until_parked();
1019
1020 // we have an admin (a), member (b) and guest (c) all part of the zed channel.
1021
1022 // create a new private channel, make it public, and move it under the previous one, and verify it shows for b and not c
1023 let active_channel = client_a
1024 .channel_store()
1025 .update(cx_a, |channel_store, cx| {
1026 channel_store.create_channel("active", Some(zed_channel), cx)
1027 })
1028 .await
1029 .unwrap();
1030
1031 executor.run_until_parked();
1032
1033 // the new channel shows for b and not c
1034 assert_channels_list_shape(
1035 client_a.channel_store(),
1036 cx_a,
1037 &[(zed_channel, 0), (active_channel, 1)],
1038 );
1039 assert_channels_list_shape(
1040 client_b.channel_store(),
1041 cx_b,
1042 &[(zed_channel, 0), (active_channel, 1)],
1043 );
1044 assert_channels_list_shape(client_c.channel_store(), cx_c, &[(zed_channel, 0)]);
1045
1046 let vim_channel = client_a
1047 .channel_store()
1048 .update(cx_a, |channel_store, cx| {
1049 channel_store.create_channel("vim", None, cx)
1050 })
1051 .await
1052 .unwrap();
1053
1054 client_a
1055 .channel_store()
1056 .update(cx_a, |channel_store, cx| {
1057 channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx)
1058 })
1059 .await
1060 .unwrap();
1061
1062 client_a
1063 .channel_store()
1064 .update(cx_a, |channel_store, cx| {
1065 channel_store.move_channel(vim_channel, Some(active_channel), cx)
1066 })
1067 .await
1068 .unwrap();
1069
1070 executor.run_until_parked();
1071
1072 // the new channel shows for b and c
1073 assert_channels_list_shape(
1074 client_a.channel_store(),
1075 cx_a,
1076 &[(zed_channel, 0), (active_channel, 1), (vim_channel, 2)],
1077 );
1078 assert_channels_list_shape(
1079 client_b.channel_store(),
1080 cx_b,
1081 &[(zed_channel, 0), (active_channel, 1), (vim_channel, 2)],
1082 );
1083 assert_channels_list_shape(
1084 client_c.channel_store(),
1085 cx_c,
1086 &[(zed_channel, 0), (vim_channel, 1)],
1087 );
1088
1089 let helix_channel = client_a
1090 .channel_store()
1091 .update(cx_a, |channel_store, cx| {
1092 channel_store.create_channel("helix", None, cx)
1093 })
1094 .await
1095 .unwrap();
1096
1097 client_a
1098 .channel_store()
1099 .update(cx_a, |channel_store, cx| {
1100 channel_store.move_channel(helix_channel, Some(vim_channel), cx)
1101 })
1102 .await
1103 .unwrap();
1104
1105 client_a
1106 .channel_store()
1107 .update(cx_a, |channel_store, cx| {
1108 channel_store.set_channel_visibility(
1109 helix_channel,
1110 proto::ChannelVisibility::Public,
1111 cx,
1112 )
1113 })
1114 .await
1115 .unwrap();
1116
1117 // the new channel shows for b and c
1118 assert_channels_list_shape(
1119 client_b.channel_store(),
1120 cx_b,
1121 &[
1122 (zed_channel, 0),
1123 (active_channel, 1),
1124 (vim_channel, 2),
1125 (helix_channel, 3),
1126 ],
1127 );
1128 assert_channels_list_shape(
1129 client_c.channel_store(),
1130 cx_c,
1131 &[(zed_channel, 0), (vim_channel, 1), (helix_channel, 2)],
1132 );
1133
1134 client_a
1135 .channel_store()
1136 .update(cx_a, |channel_store, cx| {
1137 channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Members, cx)
1138 })
1139 .await
1140 .unwrap();
1141
1142 executor.run_until_parked();
1143
1144 // the members-only channel is still shown for c, but hidden for b
1145 assert_channels_list_shape(
1146 client_b.channel_store(),
1147 cx_b,
1148 &[
1149 (zed_channel, 0),
1150 (active_channel, 1),
1151 (vim_channel, 2),
1152 (helix_channel, 3),
1153 ],
1154 );
1155 cx_b.read(|cx| {
1156 client_b.channel_store().read_with(cx, |channel_store, _| {
1157 assert_eq!(
1158 channel_store
1159 .channel_for_id(vim_channel)
1160 .unwrap()
1161 .visibility,
1162 proto::ChannelVisibility::Members
1163 )
1164 })
1165 });
1166
1167 assert_channels_list_shape(client_c.channel_store(), cx_c, &[(zed_channel, 0)]);
1168}
1169
1170#[gpui::test]
1171async fn test_channel_membership_notifications(
1172 executor: BackgroundExecutor,
1173 cx_a: &mut TestAppContext,
1174 cx_b: &mut TestAppContext,
1175) {
1176 let mut server = TestServer::start(executor.clone()).await;
1177 let client_a = server.create_client(cx_a, "user_a").await;
1178 let client_b = server.create_client(cx_b, "user_c").await;
1179
1180 let user_b = client_b.user_id().unwrap();
1181
1182 let channels = server
1183 .make_channel_tree(
1184 &[
1185 ("zed", None),
1186 ("active", Some("zed")),
1187 ("vim", Some("active")),
1188 ],
1189 (&client_a, cx_a),
1190 )
1191 .await;
1192 let zed_channel = channels[0];
1193 let _active_channel = channels[1];
1194 let vim_channel = channels[2];
1195
1196 try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
1197 [
1198 channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
1199 channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx),
1200 channel_store.invite_member(vim_channel, user_b, proto::ChannelRole::Member, cx),
1201 channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Guest, cx),
1202 ]
1203 }))
1204 .await
1205 .unwrap();
1206
1207 executor.run_until_parked();
1208
1209 client_b
1210 .channel_store()
1211 .update(cx_b, |channel_store, cx| {
1212 channel_store.respond_to_channel_invite(zed_channel, true, cx)
1213 })
1214 .await
1215 .unwrap();
1216
1217 client_b
1218 .channel_store()
1219 .update(cx_b, |channel_store, cx| {
1220 channel_store.respond_to_channel_invite(vim_channel, true, cx)
1221 })
1222 .await
1223 .unwrap();
1224
1225 executor.run_until_parked();
1226
1227 // we have an admin (a), and a guest (b) with access to all of zed, and membership in vim.
1228 assert_channels(
1229 client_b.channel_store(),
1230 cx_b,
1231 &[
1232 ExpectedChannel {
1233 depth: 0,
1234 id: zed_channel,
1235 name: "zed".to_string(),
1236 role: ChannelRole::Guest,
1237 },
1238 ExpectedChannel {
1239 depth: 1,
1240 id: vim_channel,
1241 name: "vim".to_string(),
1242 role: ChannelRole::Member,
1243 },
1244 ],
1245 );
1246
1247 client_a
1248 .channel_store()
1249 .update(cx_a, |channel_store, cx| {
1250 channel_store.remove_member(vim_channel, user_b, cx)
1251 })
1252 .await
1253 .unwrap();
1254
1255 executor.run_until_parked();
1256
1257 assert_channels(
1258 client_b.channel_store(),
1259 cx_b,
1260 &[
1261 ExpectedChannel {
1262 depth: 0,
1263 id: zed_channel,
1264 name: "zed".to_string(),
1265 role: ChannelRole::Guest,
1266 },
1267 ExpectedChannel {
1268 depth: 1,
1269 id: vim_channel,
1270 name: "vim".to_string(),
1271 role: ChannelRole::Guest,
1272 },
1273 ],
1274 )
1275}
1276
1277#[gpui::test]
1278async fn test_guest_access(
1279 executor: BackgroundExecutor,
1280 cx_a: &mut TestAppContext,
1281 cx_b: &mut TestAppContext,
1282) {
1283 let mut server = TestServer::start(executor.clone()).await;
1284 let client_a = server.create_client(cx_a, "user_a").await;
1285 let client_b = server.create_client(cx_b, "user_b").await;
1286
1287 let channels = server
1288 .make_channel_tree(
1289 &[("channel-a", None), ("channel-b", Some("channel-a"))],
1290 (&client_a, cx_a),
1291 )
1292 .await;
1293 let channel_a = channels[0];
1294 let channel_b = channels[1];
1295
1296 let active_call_b = cx_b.read(ActiveCall::global);
1297
1298 // Non-members should not be allowed to join
1299 assert!(active_call_b
1300 .update(cx_b, |call, cx| call.join_channel(channel_a, None, cx))
1301 .await
1302 .is_err());
1303
1304 // Make channels A and B public
1305 client_a
1306 .channel_store()
1307 .update(cx_a, |channel_store, cx| {
1308 channel_store.set_channel_visibility(channel_a, proto::ChannelVisibility::Public, cx)
1309 })
1310 .await
1311 .unwrap();
1312 client_a
1313 .channel_store()
1314 .update(cx_a, |channel_store, cx| {
1315 channel_store.set_channel_visibility(channel_b, proto::ChannelVisibility::Public, cx)
1316 })
1317 .await
1318 .unwrap();
1319
1320 // Client B joins channel A as a guest
1321 active_call_b
1322 .update(cx_b, |call, cx| call.join_channel(channel_a, None, cx))
1323 .await
1324 .unwrap();
1325
1326 executor.run_until_parked();
1327 assert_channels_list_shape(
1328 client_a.channel_store(),
1329 cx_a,
1330 &[(channel_a, 0), (channel_b, 1)],
1331 );
1332 assert_channels_list_shape(
1333 client_b.channel_store(),
1334 cx_b,
1335 &[(channel_a, 0), (channel_b, 1)],
1336 );
1337
1338 client_a.channel_store().update(cx_a, |channel_store, _| {
1339 let participants = channel_store.channel_participants(channel_a);
1340 assert_eq!(participants.len(), 1);
1341 assert_eq!(participants[0].id, client_b.user_id().unwrap());
1342 });
1343
1344 client_a
1345 .channel_store()
1346 .update(cx_a, |channel_store, cx| {
1347 channel_store.set_channel_visibility(channel_a, proto::ChannelVisibility::Members, cx)
1348 })
1349 .await
1350 .unwrap();
1351
1352 assert_channels_list_shape(client_b.channel_store(), cx_b, &[]);
1353
1354 active_call_b
1355 .update(cx_b, |call, cx| call.join_channel(channel_b, None, cx))
1356 .await
1357 .unwrap();
1358
1359 executor.run_until_parked();
1360 assert_channels_list_shape(client_b.channel_store(), cx_b, &[(channel_b, 0)]);
1361}
1362
1363#[gpui::test]
1364async fn test_invite_access(
1365 executor: BackgroundExecutor,
1366 cx_a: &mut TestAppContext,
1367 cx_b: &mut TestAppContext,
1368) {
1369 let mut server = TestServer::start(executor.clone()).await;
1370 let client_a = server.create_client(cx_a, "user_a").await;
1371 let client_b = server.create_client(cx_b, "user_b").await;
1372
1373 let channels = server
1374 .make_channel_tree(
1375 &[("channel-a", None), ("channel-b", Some("channel-a"))],
1376 (&client_a, cx_a),
1377 )
1378 .await;
1379 let channel_a_id = channels[0];
1380 let channel_b_id = channels[0];
1381
1382 let active_call_b = cx_b.read(ActiveCall::global);
1383
1384 // should not be allowed to join
1385 assert!(active_call_b
1386 .update(cx_b, |call, cx| call.join_channel(channel_b_id, None, cx))
1387 .await
1388 .is_err());
1389
1390 client_a
1391 .channel_store()
1392 .update(cx_a, |channel_store, cx| {
1393 channel_store.invite_member(
1394 channel_a_id,
1395 client_b.user_id().unwrap(),
1396 ChannelRole::Member,
1397 cx,
1398 )
1399 })
1400 .await
1401 .unwrap();
1402
1403 active_call_b
1404 .update(cx_b, |call, cx| call.join_channel(channel_b_id, None, cx))
1405 .await
1406 .unwrap();
1407
1408 executor.run_until_parked();
1409
1410 client_b.channel_store().update(cx_b, |channel_store, _| {
1411 assert!(channel_store.channel_for_id(channel_b_id).is_some());
1412 assert!(channel_store.channel_for_id(channel_a_id).is_some());
1413 });
1414
1415 client_a.channel_store().update(cx_a, |channel_store, _| {
1416 let participants = channel_store.channel_participants(channel_b_id);
1417 assert_eq!(participants.len(), 1);
1418 assert_eq!(participants[0].id, client_b.user_id().unwrap());
1419 })
1420}
1421
1422#[gpui::test]
1423async fn test_channel_moving(
1424 executor: BackgroundExecutor,
1425 cx_a: &mut TestAppContext,
1426 _cx_b: &mut TestAppContext,
1427 _cx_c: &mut TestAppContext,
1428) {
1429 let mut server = TestServer::start(executor.clone()).await;
1430 let client_a = server.create_client(cx_a, "user_a").await;
1431 // let client_b = server.create_client(cx_b, "user_b").await;
1432 // let client_c = server.create_client(cx_c, "user_c").await;
1433
1434 let channels = server
1435 .make_channel_tree(
1436 &[
1437 ("channel-a", None),
1438 ("channel-b", Some("channel-a")),
1439 ("channel-c", Some("channel-b")),
1440 ("channel-d", Some("channel-c")),
1441 ],
1442 (&client_a, cx_a),
1443 )
1444 .await;
1445 let channel_a_id = channels[0];
1446 let channel_b_id = channels[1];
1447 let channel_c_id = channels[2];
1448 let channel_d_id = channels[3];
1449
1450 // Current shape:
1451 // a - b - c - d
1452 assert_channels_list_shape(
1453 client_a.channel_store(),
1454 cx_a,
1455 &[
1456 (channel_a_id, 0),
1457 (channel_b_id, 1),
1458 (channel_c_id, 2),
1459 (channel_d_id, 3),
1460 ],
1461 );
1462
1463 client_a
1464 .channel_store()
1465 .update(cx_a, |channel_store, cx| {
1466 channel_store.move_channel(channel_d_id, Some(channel_b_id), cx)
1467 })
1468 .await
1469 .unwrap();
1470
1471 // Current shape:
1472 // /- d
1473 // a - b -- c
1474 assert_channels_list_shape(
1475 client_a.channel_store(),
1476 cx_a,
1477 &[
1478 (channel_a_id, 0),
1479 (channel_b_id, 1),
1480 (channel_c_id, 2),
1481 (channel_d_id, 2),
1482 ],
1483 );
1484}
1485
1486#[derive(Debug, PartialEq)]
1487struct ExpectedChannel {
1488 depth: usize,
1489 id: ChannelId,
1490 name: String,
1491 role: ChannelRole,
1492}
1493
1494#[track_caller]
1495fn assert_channel_invitations(
1496 channel_store: &Model<ChannelStore>,
1497 cx: &TestAppContext,
1498 expected_channels: &[ExpectedChannel],
1499) {
1500 let actual = cx.read(|cx| {
1501 channel_store.read_with(cx, |store, _| {
1502 store
1503 .channel_invitations()
1504 .iter()
1505 .map(|channel| ExpectedChannel {
1506 depth: 0,
1507 name: channel.name.clone(),
1508 id: channel.id,
1509 role: channel.role,
1510 })
1511 .collect::<Vec<_>>()
1512 })
1513 });
1514 assert_eq!(actual, expected_channels);
1515}
1516
1517#[track_caller]
1518fn assert_channels(
1519 channel_store: &Model<ChannelStore>,
1520 cx: &TestAppContext,
1521 expected_channels: &[ExpectedChannel],
1522) {
1523 let actual = cx.read(|cx| {
1524 channel_store.read_with(cx, |store, _| {
1525 store
1526 .ordered_channels()
1527 .map(|(depth, channel)| ExpectedChannel {
1528 depth,
1529 name: channel.name.clone(),
1530 id: channel.id,
1531 role: channel.role,
1532 })
1533 .collect::<Vec<_>>()
1534 })
1535 });
1536 pretty_assertions::assert_eq!(actual, expected_channels);
1537}
1538
1539#[track_caller]
1540fn assert_channels_list_shape(
1541 channel_store: &Model<ChannelStore>,
1542 cx: &TestAppContext,
1543 expected_channels: &[(u64, usize)],
1544) {
1545 let actual = cx.read(|cx| {
1546 channel_store.read_with(cx, |store, _| {
1547 store
1548 .ordered_channels()
1549 .map(|(depth, channel)| (channel.id, depth))
1550 .collect::<Vec<_>>()
1551 })
1552 });
1553 pretty_assertions::assert_eq!(actual, expected_channels);
1554}