1use crate::{
2 db::{self, UserId},
3 rpc::RECONNECT_TIMEOUT,
4 tests::{room_participants, test_server::join_channel_call, RoomParticipants, TestServer},
5};
6use call::ActiveCall;
7use channel::{ChannelId, ChannelMembership, ChannelStore};
8use client::User;
9use futures::future::try_join_all;
10use gpui::{BackgroundExecutor, Model, SharedString, 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".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.get_channel_member_details(channel_a_id, 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),
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!(active_call_b
352 .update(cx_b, |active_call, cx| active_call.join_channel(sub_id, cx))
353 .await
354 .is_ok());
355}
356
357#[gpui::test]
358async fn test_channel_room(
359 executor: BackgroundExecutor,
360 cx_a: &mut TestAppContext,
361 cx_b: &mut TestAppContext,
362 cx_c: &mut TestAppContext,
363) {
364 let mut server = TestServer::start(executor.clone()).await;
365 let client_a = server.create_client(cx_a, "user_a").await;
366 let client_b = server.create_client(cx_b, "user_b").await;
367 let client_c = server.create_client(cx_c, "user_c").await;
368
369 let zed_id = server
370 .make_channel(
371 "zed",
372 None,
373 (&client_a, cx_a),
374 &mut [(&client_b, cx_b), (&client_c, cx_c)],
375 )
376 .await;
377
378 let active_call_a = cx_a.read(ActiveCall::global);
379 let active_call_b = cx_b.read(ActiveCall::global);
380
381 active_call_a
382 .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
383 .await
384 .unwrap();
385 join_channel_call(cx_a).await.unwrap();
386
387 // Give everyone a chance to observe user A joining
388 executor.run_until_parked();
389 let room_a =
390 cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
391 cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
392
393 cx_a.read(|cx| {
394 client_a.channel_store().read_with(cx, |channels, _| {
395 assert_participants_eq(
396 channels.channel_participants(zed_id),
397 &[client_a.user_id().unwrap()],
398 );
399 })
400 });
401
402 assert_channels(
403 client_b.channel_store(),
404 cx_b,
405 &[ExpectedChannel {
406 id: zed_id,
407 name: "zed".into(),
408 depth: 0,
409 }],
410 );
411 cx_b.read(|cx| {
412 client_b.channel_store().read_with(cx, |channels, _| {
413 assert_participants_eq(
414 channels.channel_participants(zed_id),
415 &[client_a.user_id().unwrap()],
416 );
417 })
418 });
419
420 cx_c.read(|cx| {
421 client_c.channel_store().read_with(cx, |channels, _| {
422 assert_participants_eq(
423 channels.channel_participants(zed_id),
424 &[client_a.user_id().unwrap()],
425 );
426 })
427 });
428
429 active_call_b
430 .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
431 .await
432 .unwrap();
433 join_channel_call(cx_b).await.unwrap();
434 executor.run_until_parked();
435
436 cx_a.read(|cx| {
437 client_a.channel_store().read_with(cx, |channels, _| {
438 assert_participants_eq(
439 channels.channel_participants(zed_id),
440 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
441 );
442 })
443 });
444
445 cx_b.read(|cx| {
446 client_b.channel_store().read_with(cx, |channels, _| {
447 assert_participants_eq(
448 channels.channel_participants(zed_id),
449 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
450 );
451 })
452 });
453
454 cx_c.read(|cx| {
455 client_c.channel_store().read_with(cx, |channels, _| {
456 assert_participants_eq(
457 channels.channel_participants(zed_id),
458 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
459 );
460 })
461 });
462
463 let room_a =
464 cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
465 cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
466 assert_eq!(
467 room_participants(&room_a, cx_a),
468 RoomParticipants {
469 remote: vec!["user_b".to_string()],
470 pending: vec![]
471 }
472 );
473
474 let room_b =
475 cx_b.read(|cx| active_call_b.read_with(cx, |call, _| call.room().unwrap().clone()));
476 cx_b.read(|cx| room_b.read_with(cx, |room, _| assert!(room.is_connected())));
477 assert_eq!(
478 room_participants(&room_b, cx_b),
479 RoomParticipants {
480 remote: vec!["user_a".to_string()],
481 pending: vec![]
482 }
483 );
484
485 // Make sure that leaving and rejoining works
486
487 active_call_a
488 .update(cx_a, |active_call, cx| active_call.hang_up(cx))
489 .await
490 .unwrap();
491
492 executor.run_until_parked();
493
494 cx_a.read(|cx| {
495 client_a.channel_store().read_with(cx, |channels, _| {
496 assert_participants_eq(
497 channels.channel_participants(zed_id),
498 &[client_b.user_id().unwrap()],
499 );
500 })
501 });
502
503 cx_b.read(|cx| {
504 client_b.channel_store().read_with(cx, |channels, _| {
505 assert_participants_eq(
506 channels.channel_participants(zed_id),
507 &[client_b.user_id().unwrap()],
508 );
509 })
510 });
511
512 cx_c.read(|cx| {
513 client_c.channel_store().read_with(cx, |channels, _| {
514 assert_participants_eq(
515 channels.channel_participants(zed_id),
516 &[client_b.user_id().unwrap()],
517 );
518 })
519 });
520
521 active_call_b
522 .update(cx_b, |active_call, cx| active_call.hang_up(cx))
523 .await
524 .unwrap();
525
526 executor.run_until_parked();
527
528 cx_a.read(|cx| {
529 client_a.channel_store().read_with(cx, |channels, _| {
530 assert_participants_eq(channels.channel_participants(zed_id), &[]);
531 })
532 });
533
534 cx_b.read(|cx| {
535 client_b.channel_store().read_with(cx, |channels, _| {
536 assert_participants_eq(channels.channel_participants(zed_id), &[]);
537 })
538 });
539
540 cx_c.read(|cx| {
541 client_c.channel_store().read_with(cx, |channels, _| {
542 assert_participants_eq(channels.channel_participants(zed_id), &[]);
543 })
544 });
545
546 active_call_a
547 .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
548 .await
549 .unwrap();
550
551 active_call_b
552 .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
553 .await
554 .unwrap();
555
556 join_channel_call(cx_a).await.unwrap();
557 join_channel_call(cx_b).await.unwrap();
558
559 executor.run_until_parked();
560
561 let room_a =
562 cx_a.read(|cx| active_call_a.read_with(cx, |call, _| call.room().unwrap().clone()));
563 cx_a.read(|cx| room_a.read_with(cx, |room, _| assert!(room.is_connected())));
564 assert_eq!(
565 room_participants(&room_a, cx_a),
566 RoomParticipants {
567 remote: vec!["user_b".to_string()],
568 pending: vec![]
569 }
570 );
571
572 let room_b =
573 cx_b.read(|cx| active_call_b.read_with(cx, |call, _| call.room().unwrap().clone()));
574 cx_b.read(|cx| room_b.read_with(cx, |room, _| assert!(room.is_connected())));
575 assert_eq!(
576 room_participants(&room_b, cx_b),
577 RoomParticipants {
578 remote: vec!["user_a".to_string()],
579 pending: vec![]
580 }
581 );
582}
583
584#[gpui::test]
585async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppContext) {
586 let mut server = TestServer::start(executor.clone()).await;
587 let client_a = server.create_client(cx_a, "user_a").await;
588
589 let zed_id = server
590 .make_channel("zed", None, (&client_a, cx_a), &mut [])
591 .await;
592 let rust_id = server
593 .make_channel("rust", None, (&client_a, cx_a), &mut [])
594 .await;
595
596 let active_call_a = cx_a.read(ActiveCall::global);
597
598 active_call_a
599 .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
600 .await
601 .unwrap();
602
603 // Give everything a chance to observe user A joining
604 executor.run_until_parked();
605
606 cx_a.read(|cx| {
607 client_a.channel_store().read_with(cx, |channels, _| {
608 assert_participants_eq(
609 channels.channel_participants(zed_id),
610 &[client_a.user_id().unwrap()],
611 );
612 assert_participants_eq(channels.channel_participants(rust_id), &[]);
613 })
614 });
615
616 active_call_a
617 .update(cx_a, |active_call, cx| {
618 active_call.join_channel(rust_id, cx)
619 })
620 .await
621 .unwrap();
622
623 executor.run_until_parked();
624
625 cx_a.read(|cx| {
626 client_a.channel_store().read_with(cx, |channels, _| {
627 assert_participants_eq(channels.channel_participants(zed_id), &[]);
628 assert_participants_eq(
629 channels.channel_participants(rust_id),
630 &[client_a.user_id().unwrap()],
631 );
632 })
633 });
634}
635
636#[gpui::test]
637async fn test_permissions_update_while_invited(
638 executor: BackgroundExecutor,
639 cx_a: &mut TestAppContext,
640 cx_b: &mut TestAppContext,
641) {
642 let mut server = TestServer::start(executor.clone()).await;
643 let client_a = server.create_client(cx_a, "user_a").await;
644 let client_b = server.create_client(cx_b, "user_b").await;
645
646 let rust_id = server
647 .make_channel("rust", None, (&client_a, cx_a), &mut [])
648 .await;
649
650 client_a
651 .channel_store()
652 .update(cx_a, |channel_store, cx| {
653 channel_store.invite_member(
654 rust_id,
655 client_b.user_id().unwrap(),
656 proto::ChannelRole::Member,
657 cx,
658 )
659 })
660 .await
661 .unwrap();
662
663 executor.run_until_parked();
664
665 assert_channel_invitations(
666 client_b.channel_store(),
667 cx_b,
668 &[ExpectedChannel {
669 depth: 0,
670 id: rust_id,
671 name: "rust".into(),
672 }],
673 );
674 assert_channels(client_b.channel_store(), cx_b, &[]);
675
676 // Update B's invite before they've accepted it
677 client_a
678 .channel_store()
679 .update(cx_a, |channel_store, cx| {
680 channel_store.set_member_role(
681 rust_id,
682 client_b.user_id().unwrap(),
683 proto::ChannelRole::Admin,
684 cx,
685 )
686 })
687 .await
688 .unwrap();
689
690 executor.run_until_parked();
691
692 assert_channel_invitations(
693 client_b.channel_store(),
694 cx_b,
695 &[ExpectedChannel {
696 depth: 0,
697 id: rust_id,
698 name: "rust".into(),
699 }],
700 );
701 assert_channels(client_b.channel_store(), cx_b, &[]);
702}
703
704#[gpui::test]
705async fn test_channel_rename(
706 executor: BackgroundExecutor,
707 cx_a: &mut TestAppContext,
708 cx_b: &mut TestAppContext,
709) {
710 let mut server = TestServer::start(executor.clone()).await;
711 let client_a = server.create_client(cx_a, "user_a").await;
712 let client_b = server.create_client(cx_b, "user_b").await;
713
714 let rust_id = server
715 .make_channel("rust", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
716 .await;
717
718 // Rename the channel
719 client_a
720 .channel_store()
721 .update(cx_a, |channel_store, cx| {
722 channel_store.rename(rust_id, "#rust-archive", cx)
723 })
724 .await
725 .unwrap();
726
727 executor.run_until_parked();
728
729 // Client A sees the channel with its new name.
730 assert_channels(
731 client_a.channel_store(),
732 cx_a,
733 &[ExpectedChannel {
734 depth: 0,
735 id: rust_id,
736 name: "rust-archive".into(),
737 }],
738 );
739
740 // Client B sees the channel with its new name.
741 assert_channels(
742 client_b.channel_store(),
743 cx_b,
744 &[ExpectedChannel {
745 depth: 0,
746 id: rust_id,
747 name: "rust-archive".into(),
748 }],
749 );
750}
751
752#[gpui::test]
753async fn test_call_from_channel(
754 executor: BackgroundExecutor,
755 cx_a: &mut TestAppContext,
756 cx_b: &mut TestAppContext,
757 cx_c: &mut TestAppContext,
758) {
759 let mut server = TestServer::start(executor.clone()).await;
760 let client_a = server.create_client(cx_a, "user_a").await;
761 let client_b = server.create_client(cx_b, "user_b").await;
762 let client_c = server.create_client(cx_c, "user_c").await;
763 server
764 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
765 .await;
766
767 let channel_id = server
768 .make_channel(
769 "x",
770 None,
771 (&client_a, cx_a),
772 &mut [(&client_b, cx_b), (&client_c, cx_c)],
773 )
774 .await;
775
776 let active_call_a = cx_a.read(ActiveCall::global);
777 let active_call_b = cx_b.read(ActiveCall::global);
778
779 active_call_a
780 .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
781 .await
782 .unwrap();
783
784 // Client A calls client B while in the channel.
785 active_call_a
786 .update(cx_a, |call, cx| {
787 call.invite(client_b.user_id().unwrap(), None, cx)
788 })
789 .await
790 .unwrap();
791
792 // Client B accepts the call.
793 executor.run_until_parked();
794 active_call_b
795 .update(cx_b, |call, cx| call.accept_incoming(cx))
796 .await
797 .unwrap();
798
799 // Client B sees that they are now in the channel
800 executor.run_until_parked();
801 cx_b.read(|cx| {
802 active_call_b.read_with(cx, |call, cx| {
803 assert_eq!(call.channel_id(cx), Some(channel_id));
804 })
805 });
806 cx_b.read(|cx| {
807 client_b.channel_store().read_with(cx, |channels, _| {
808 assert_participants_eq(
809 channels.channel_participants(channel_id),
810 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
811 );
812 })
813 });
814
815 // Clients A and C also see that client B is in the channel.
816 cx_a.read(|cx| {
817 client_a.channel_store().read_with(cx, |channels, _| {
818 assert_participants_eq(
819 channels.channel_participants(channel_id),
820 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
821 );
822 })
823 });
824 cx_c.read(|cx| {
825 client_c.channel_store().read_with(cx, |channels, _| {
826 assert_participants_eq(
827 channels.channel_participants(channel_id),
828 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
829 );
830 })
831 });
832}
833
834#[gpui::test]
835async fn test_lost_channel_creation(
836 executor: BackgroundExecutor,
837 cx_a: &mut TestAppContext,
838 cx_b: &mut TestAppContext,
839) {
840 let mut server = TestServer::start(executor.clone()).await;
841 let client_a = server.create_client(cx_a, "user_a").await;
842 let client_b = server.create_client(cx_b, "user_b").await;
843
844 server
845 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
846 .await;
847
848 let channel_id = server
849 .make_channel("x", None, (&client_a, cx_a), &mut [])
850 .await;
851
852 // Invite a member
853 client_a
854 .channel_store()
855 .update(cx_a, |channel_store, cx| {
856 channel_store.invite_member(
857 channel_id,
858 client_b.user_id().unwrap(),
859 proto::ChannelRole::Member,
860 cx,
861 )
862 })
863 .await
864 .unwrap();
865
866 executor.run_until_parked();
867
868 // Sanity check, B has the invitation
869 assert_channel_invitations(
870 client_b.channel_store(),
871 cx_b,
872 &[ExpectedChannel {
873 depth: 0,
874 id: channel_id,
875 name: "x".into(),
876 }],
877 );
878
879 // A creates a subchannel while the invite is still pending.
880 let subchannel_id = client_a
881 .channel_store()
882 .update(cx_a, |channel_store, cx| {
883 channel_store.create_channel("subchannel", Some(channel_id), cx)
884 })
885 .await
886 .unwrap();
887
888 executor.run_until_parked();
889
890 // Make sure A sees their new channel
891 assert_channels(
892 client_a.channel_store(),
893 cx_a,
894 &[
895 ExpectedChannel {
896 depth: 0,
897 id: channel_id,
898 name: "x".into(),
899 },
900 ExpectedChannel {
901 depth: 1,
902 id: subchannel_id,
903 name: "subchannel".into(),
904 },
905 ],
906 );
907
908 // Client B accepts the invite
909 client_b
910 .channel_store()
911 .update(cx_b, |channel_store, cx| {
912 channel_store.respond_to_channel_invite(channel_id, true, cx)
913 })
914 .await
915 .unwrap();
916
917 executor.run_until_parked();
918
919 // Client B should now see the channel
920 assert_channels(
921 client_b.channel_store(),
922 cx_b,
923 &[
924 ExpectedChannel {
925 depth: 0,
926 id: channel_id,
927 name: "x".into(),
928 },
929 ExpectedChannel {
930 depth: 1,
931 id: subchannel_id,
932 name: "subchannel".into(),
933 },
934 ],
935 );
936}
937
938#[gpui::test]
939async fn test_channel_link_notifications(
940 executor: BackgroundExecutor,
941 cx_a: &mut TestAppContext,
942 cx_b: &mut TestAppContext,
943 cx_c: &mut TestAppContext,
944) {
945 let mut server = TestServer::start(executor.clone()).await;
946 let client_a = server.create_client(cx_a, "user_a").await;
947 let client_b = server.create_client(cx_b, "user_b").await;
948 let client_c = server.create_client(cx_c, "user_c").await;
949
950 let user_b = client_b.user_id().unwrap();
951 let user_c = client_c.user_id().unwrap();
952
953 let channels = server
954 .make_channel_tree(&[("zed", None)], (&client_a, cx_a))
955 .await;
956 let zed_channel = channels[0];
957
958 try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
959 [
960 channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
961 channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Member, cx),
962 channel_store.invite_member(zed_channel, user_c, proto::ChannelRole::Guest, cx),
963 ]
964 }))
965 .await
966 .unwrap();
967
968 executor.run_until_parked();
969
970 client_b
971 .channel_store()
972 .update(cx_b, |channel_store, cx| {
973 channel_store.respond_to_channel_invite(zed_channel, true, cx)
974 })
975 .await
976 .unwrap();
977
978 client_c
979 .channel_store()
980 .update(cx_c, |channel_store, cx| {
981 channel_store.respond_to_channel_invite(zed_channel, true, cx)
982 })
983 .await
984 .unwrap();
985
986 executor.run_until_parked();
987
988 // we have an admin (a), member (b) and guest (c) all part of the zed channel.
989
990 // create a new private channel, make it public, and move it under the previous one, and verify it shows for b and not c
991 let active_channel = client_a
992 .channel_store()
993 .update(cx_a, |channel_store, cx| {
994 channel_store.create_channel("active", Some(zed_channel), cx)
995 })
996 .await
997 .unwrap();
998
999 executor.run_until_parked();
1000
1001 // the new channel shows for b and not c
1002 assert_channels_list_shape(
1003 client_a.channel_store(),
1004 cx_a,
1005 &[(zed_channel, 0), (active_channel, 1)],
1006 );
1007 assert_channels_list_shape(
1008 client_b.channel_store(),
1009 cx_b,
1010 &[(zed_channel, 0), (active_channel, 1)],
1011 );
1012 assert_channels_list_shape(client_c.channel_store(), cx_c, &[(zed_channel, 0)]);
1013
1014 let vim_channel = client_a
1015 .channel_store()
1016 .update(cx_a, |channel_store, cx| {
1017 channel_store.create_channel("vim", Some(zed_channel), cx)
1018 })
1019 .await
1020 .unwrap();
1021
1022 client_a
1023 .channel_store()
1024 .update(cx_a, |channel_store, cx| {
1025 channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx)
1026 })
1027 .await
1028 .unwrap();
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!(active_call_b
1209 .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
1210 .await
1211 .is_err());
1212
1213 // Make channels A and B public
1214 client_a
1215 .channel_store()
1216 .update(cx_a, |channel_store, cx| {
1217 channel_store.set_channel_visibility(channel_a, proto::ChannelVisibility::Public, cx)
1218 })
1219 .await
1220 .unwrap();
1221 client_a
1222 .channel_store()
1223 .update(cx_a, |channel_store, cx| {
1224 channel_store.set_channel_visibility(channel_b, proto::ChannelVisibility::Public, cx)
1225 })
1226 .await
1227 .unwrap();
1228
1229 // Client B joins channel A as a guest
1230 active_call_b
1231 .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
1232 .await
1233 .unwrap();
1234
1235 executor.run_until_parked();
1236 assert_channels_list_shape(
1237 client_a.channel_store(),
1238 cx_a,
1239 &[(channel_a, 0), (channel_b, 1)],
1240 );
1241 assert_channels_list_shape(
1242 client_b.channel_store(),
1243 cx_b,
1244 &[(channel_a, 0), (channel_b, 1)],
1245 );
1246
1247 client_a.channel_store().update(cx_a, |channel_store, _| {
1248 let participants = channel_store.channel_participants(channel_a);
1249 assert_eq!(participants.len(), 1);
1250 assert_eq!(participants[0].id, client_b.user_id().unwrap());
1251 });
1252}
1253
1254#[gpui::test]
1255async fn test_invite_access(
1256 executor: BackgroundExecutor,
1257 cx_a: &mut TestAppContext,
1258 cx_b: &mut TestAppContext,
1259) {
1260 let mut server = TestServer::start(executor.clone()).await;
1261 let client_a = server.create_client(cx_a, "user_a").await;
1262 let client_b = server.create_client(cx_b, "user_b").await;
1263
1264 let channels = server
1265 .make_channel_tree(
1266 &[("channel-a", None), ("channel-b", Some("channel-a"))],
1267 (&client_a, cx_a),
1268 )
1269 .await;
1270 let channel_a_id = channels[0];
1271 let channel_b_id = channels[0];
1272
1273 let active_call_b = cx_b.read(ActiveCall::global);
1274
1275 // should not be allowed to join
1276 assert!(active_call_b
1277 .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
1278 .await
1279 .is_err());
1280
1281 client_a
1282 .channel_store()
1283 .update(cx_a, |channel_store, cx| {
1284 channel_store.invite_member(
1285 channel_a_id,
1286 client_b.user_id().unwrap(),
1287 ChannelRole::Member,
1288 cx,
1289 )
1290 })
1291 .await
1292 .unwrap();
1293
1294 active_call_b
1295 .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
1296 .await
1297 .unwrap();
1298
1299 executor.run_until_parked();
1300
1301 client_b.channel_store().update(cx_b, |channel_store, _| {
1302 assert!(channel_store.channel_for_id(channel_b_id).is_some());
1303 assert!(channel_store.channel_for_id(channel_a_id).is_some());
1304 });
1305
1306 client_a.channel_store().update(cx_a, |channel_store, _| {
1307 let participants = channel_store.channel_participants(channel_b_id);
1308 assert_eq!(participants.len(), 1);
1309 assert_eq!(participants[0].id, client_b.user_id().unwrap());
1310 })
1311}
1312
1313#[gpui::test]
1314async fn test_leave_channel(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1315 let (_server, _client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await;
1316
1317 client_b
1318 .channel_store()
1319 .update(cx_b, |channel_store, cx| {
1320 channel_store.remove_member(channel_id, client_b.user_id().unwrap(), cx)
1321 })
1322 .await
1323 .unwrap();
1324
1325 cx_a.run_until_parked();
1326
1327 assert_eq!(
1328 client_b
1329 .channel_store()
1330 .read_with(cx_b, |store, _| store.channels().count()),
1331 0
1332 );
1333}
1334
1335#[gpui::test]
1336async fn test_channel_moving(
1337 executor: BackgroundExecutor,
1338 cx_a: &mut TestAppContext,
1339 _cx_b: &mut TestAppContext,
1340 _cx_c: &mut TestAppContext,
1341) {
1342 let mut server = TestServer::start(executor.clone()).await;
1343 let client_a = server.create_client(cx_a, "user_a").await;
1344
1345 let channels = server
1346 .make_channel_tree(
1347 &[
1348 ("channel-a", None),
1349 ("channel-b", Some("channel-a")),
1350 ("channel-c", Some("channel-b")),
1351 ("channel-d", Some("channel-c")),
1352 ],
1353 (&client_a, cx_a),
1354 )
1355 .await;
1356 let channel_a_id = channels[0];
1357 let channel_b_id = channels[1];
1358 let channel_c_id = channels[2];
1359 let channel_d_id = channels[3];
1360
1361 // Current shape:
1362 // a - b - c - d
1363 assert_channels_list_shape(
1364 client_a.channel_store(),
1365 cx_a,
1366 &[
1367 (channel_a_id, 0),
1368 (channel_b_id, 1),
1369 (channel_c_id, 2),
1370 (channel_d_id, 3),
1371 ],
1372 );
1373
1374 client_a
1375 .channel_store()
1376 .update(cx_a, |channel_store, cx| {
1377 channel_store.move_channel(channel_d_id, channel_b_id, cx)
1378 })
1379 .await
1380 .unwrap();
1381
1382 // Current shape:
1383 // /- d
1384 // a - b -- c
1385 assert_channels_list_shape(
1386 client_a.channel_store(),
1387 cx_a,
1388 &[
1389 (channel_a_id, 0),
1390 (channel_b_id, 1),
1391 (channel_c_id, 2),
1392 (channel_d_id, 2),
1393 ],
1394 );
1395}
1396
1397#[derive(Debug, PartialEq)]
1398struct ExpectedChannel {
1399 depth: usize,
1400 id: ChannelId,
1401 name: SharedString,
1402}
1403
1404#[track_caller]
1405fn assert_channel_invitations(
1406 channel_store: &Model<ChannelStore>,
1407 cx: &TestAppContext,
1408 expected_channels: &[ExpectedChannel],
1409) {
1410 let actual = cx.read(|cx| {
1411 channel_store.read_with(cx, |store, _| {
1412 store
1413 .channel_invitations()
1414 .iter()
1415 .map(|channel| ExpectedChannel {
1416 depth: 0,
1417 name: channel.name.clone(),
1418 id: channel.id,
1419 })
1420 .collect::<Vec<_>>()
1421 })
1422 });
1423 assert_eq!(actual, expected_channels);
1424}
1425
1426#[track_caller]
1427fn assert_channels(
1428 channel_store: &Model<ChannelStore>,
1429 cx: &TestAppContext,
1430 expected_channels: &[ExpectedChannel],
1431) {
1432 let actual = cx.read(|cx| {
1433 channel_store.read_with(cx, |store, _| {
1434 store
1435 .ordered_channels()
1436 .map(|(depth, channel)| ExpectedChannel {
1437 depth,
1438 name: channel.name.clone().into(),
1439 id: channel.id,
1440 })
1441 .collect::<Vec<_>>()
1442 })
1443 });
1444 pretty_assertions::assert_eq!(actual, expected_channels);
1445}
1446
1447#[track_caller]
1448fn assert_channels_list_shape(
1449 channel_store: &Model<ChannelStore>,
1450 cx: &TestAppContext,
1451 expected_channels: &[(u64, usize)],
1452) {
1453 let actual = cx.read(|cx| {
1454 channel_store.read_with(cx, |store, _| {
1455 store
1456 .ordered_channels()
1457 .map(|(depth, channel)| (channel.id, depth))
1458 .collect::<Vec<_>>()
1459 })
1460 });
1461 pretty_assertions::assert_eq!(actual, expected_channels);
1462}