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