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