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