1use crate::{
2 db::{self, UserId},
3 rpc::RECONNECT_TIMEOUT,
4 tests::{room_participants, RoomParticipants, TestServer},
5};
6use call::ActiveCall;
7use channel::{ChannelId, ChannelMembership, ChannelStore};
8use client::User;
9use futures::future::try_join_all;
10use gpui::{BackgroundExecutor, Model, TestAppContext};
11use rpc::{
12 proto::{self, ChannelRole},
13 RECEIVE_TIMEOUT,
14};
15use std::sync::Arc;
16
17#[gpui::test]
18async fn test_core_channels(
19 executor: BackgroundExecutor,
20 cx_a: &mut TestAppContext,
21 cx_b: &mut TestAppContext,
22) {
23 let mut server = TestServer::start(executor.clone()).await;
24 let client_a = server.create_client(cx_a, "user_a").await;
25 let client_b = server.create_client(cx_b, "user_b").await;
26
27 let channel_a_id = client_a
28 .channel_store()
29 .update(cx_a, |channel_store, cx| {
30 channel_store.create_channel("channel-a", None, cx)
31 })
32 .await
33 .unwrap();
34 let channel_b_id = client_a
35 .channel_store()
36 .update(cx_a, |channel_store, cx| {
37 channel_store.create_channel("channel-b", Some(channel_a_id), cx)
38 })
39 .await
40 .unwrap();
41
42 executor.run_until_parked();
43 assert_channels(
44 client_a.channel_store(),
45 cx_a,
46 &[
47 ExpectedChannel {
48 id: channel_a_id,
49 name: "channel-a".to_string(),
50 depth: 0,
51 role: ChannelRole::Admin,
52 },
53 ExpectedChannel {
54 id: channel_b_id,
55 name: "channel-b".to_string(),
56 depth: 1,
57 role: ChannelRole::Admin,
58 },
59 ],
60 );
61
62 cx_b.read(|cx| {
63 client_b.channel_store().read_with(cx, |channels, _| {
64 assert!(channels.ordered_channels().collect::<Vec<_>>().is_empty())
65 })
66 });
67
68 // Invite client B to channel A as client A.
69 client_a
70 .channel_store()
71 .update(cx_a, |store, cx| {
72 assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
73
74 let invite = store.invite_member(
75 channel_a_id,
76 client_b.user_id().unwrap(),
77 proto::ChannelRole::Member,
78 cx,
79 );
80
81 // Make sure we're synchronously storing the pending invite
82 assert!(store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
83 invite
84 })
85 .await
86 .unwrap();
87
88 // Client A sees that B has been invited.
89 executor.run_until_parked();
90 assert_channel_invitations(
91 client_b.channel_store(),
92 cx_b,
93 &[ExpectedChannel {
94 id: channel_a_id,
95 name: "channel-a".to_string(),
96 depth: 0,
97 role: ChannelRole::Member,
98 }],
99 );
100
101 let members = client_a
102 .channel_store()
103 .update(cx_a, |store, cx| {
104 assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
105 store.get_channel_member_details(channel_a_id, cx)
106 })
107 .await
108 .unwrap();
109 assert_members_eq(
110 &members,
111 &[
112 (
113 client_a.user_id().unwrap(),
114 proto::ChannelRole::Admin,
115 proto::channel_member::Kind::Member,
116 ),
117 (
118 client_b.user_id().unwrap(),
119 proto::ChannelRole::Member,
120 proto::channel_member::Kind::Invitee,
121 ),
122 ],
123 );
124
125 // Client B accepts the invitation.
126 client_b
127 .channel_store()
128 .update(cx_b, |channels, cx| {
129 channels.respond_to_channel_invite(channel_a_id, true, cx)
130 })
131 .await
132 .unwrap();
133 executor.run_until_parked();
134
135 // Client B now sees that they are a member of channel A and its existing subchannels.
136 assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
137 assert_channels(
138 client_b.channel_store(),
139 cx_b,
140 &[
141 ExpectedChannel {
142 id: channel_a_id,
143 name: "channel-a".to_string(),
144 role: ChannelRole::Member,
145 depth: 0,
146 },
147 ExpectedChannel {
148 id: channel_b_id,
149 name: "channel-b".to_string(),
150 role: ChannelRole::Member,
151 depth: 1,
152 },
153 ],
154 );
155
156 let channel_c_id = client_a
157 .channel_store()
158 .update(cx_a, |channel_store, cx| {
159 channel_store.create_channel("channel-c", Some(channel_b_id), cx)
160 })
161 .await
162 .unwrap();
163
164 executor.run_until_parked();
165 assert_channels(
166 client_b.channel_store(),
167 cx_b,
168 &[
169 ExpectedChannel {
170 id: channel_a_id,
171 name: "channel-a".to_string(),
172 role: ChannelRole::Member,
173 depth: 0,
174 },
175 ExpectedChannel {
176 id: channel_b_id,
177 name: "channel-b".to_string(),
178 role: ChannelRole::Member,
179 depth: 1,
180 },
181 ExpectedChannel {
182 id: channel_c_id,
183 name: "channel-c".to_string(),
184 role: ChannelRole::Member,
185 depth: 2,
186 },
187 ],
188 );
189
190 // Update client B's membership to channel A to be an admin.
191 client_a
192 .channel_store()
193 .update(cx_a, |store, cx| {
194 store.set_member_role(
195 channel_a_id,
196 client_b.user_id().unwrap(),
197 proto::ChannelRole::Admin,
198 cx,
199 )
200 })
201 .await
202 .unwrap();
203 executor.run_until_parked();
204
205 // Observe that client B is now an admin of channel A, and that
206 // their admin priveleges extend to subchannels of channel A.
207 assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
208 assert_channels(
209 client_b.channel_store(),
210 cx_b,
211 &[
212 ExpectedChannel {
213 id: channel_a_id,
214 name: "channel-a".to_string(),
215 depth: 0,
216 role: ChannelRole::Admin,
217 },
218 ExpectedChannel {
219 id: channel_b_id,
220 name: "channel-b".to_string(),
221 depth: 1,
222 role: ChannelRole::Admin,
223 },
224 ExpectedChannel {
225 id: channel_c_id,
226 name: "channel-c".to_string(),
227 depth: 2,
228 role: ChannelRole::Admin,
229 },
230 ],
231 );
232
233 // Client A deletes the channel, deletion also deletes subchannels.
234 client_a
235 .channel_store()
236 .update(cx_a, |channel_store, _| {
237 channel_store.remove_channel(channel_b_id)
238 })
239 .await
240 .unwrap();
241
242 executor.run_until_parked();
243 assert_channels(
244 client_a.channel_store(),
245 cx_a,
246 &[ExpectedChannel {
247 id: channel_a_id,
248 name: "channel-a".to_string(),
249 depth: 0,
250 role: ChannelRole::Admin,
251 }],
252 );
253 assert_channels(
254 client_b.channel_store(),
255 cx_b,
256 &[ExpectedChannel {
257 id: channel_a_id,
258 name: "channel-a".to_string(),
259 depth: 0,
260 role: ChannelRole::Admin,
261 }],
262 );
263
264 // Remove client B
265 client_a
266 .channel_store()
267 .update(cx_a, |channel_store, cx| {
268 channel_store.remove_member(channel_a_id, client_b.user_id().unwrap(), cx)
269 })
270 .await
271 .unwrap();
272
273 executor.run_until_parked();
274
275 // Client A still has their channel
276 assert_channels(
277 client_a.channel_store(),
278 cx_a,
279 &[ExpectedChannel {
280 id: channel_a_id,
281 name: "channel-a".to_string(),
282 depth: 0,
283 role: ChannelRole::Admin,
284 }],
285 );
286
287 // Client B no longer has access to the channel
288 assert_channels(client_b.channel_store(), cx_b, &[]);
289
290 server.forbid_connections();
291 server.disconnect_client(client_a.peer_id().unwrap());
292 executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
293
294 server
295 .app_state
296 .db
297 .rename_channel(
298 db::ChannelId::from_proto(channel_a_id),
299 UserId::from_proto(client_a.id()),
300 "channel-a-renamed",
301 )
302 .await
303 .unwrap();
304
305 server.allow_connections();
306 executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
307 assert_channels(
308 client_a.channel_store(),
309 cx_a,
310 &[ExpectedChannel {
311 id: channel_a_id,
312 name: "channel-a-renamed".to_string(),
313 depth: 0,
314 role: ChannelRole::Admin,
315 }],
316 );
317}
318
319#[track_caller]
320fn assert_participants_eq(participants: &[Arc<User>], expected_partitipants: &[u64]) {
321 assert_eq!(
322 participants.iter().map(|p| p.id).collect::<Vec<_>>(),
323 expected_partitipants
324 );
325}
326
327#[track_caller]
328fn assert_members_eq(
329 members: &[ChannelMembership],
330 expected_members: &[(u64, proto::ChannelRole, proto::channel_member::Kind)],
331) {
332 assert_eq!(
333 members
334 .iter()
335 .map(|member| (member.user.id, member.role, member.kind))
336 .collect::<Vec<_>>(),
337 expected_members
338 );
339}
340
341#[gpui::test]
342async fn test_joining_channel_ancestor_member(
343 executor: BackgroundExecutor,
344 cx_a: &mut TestAppContext,
345 cx_b: &mut TestAppContext,
346) {
347 let mut server = TestServer::start(executor.clone()).await;
348
349 let client_a = server.create_client(cx_a, "user_a").await;
350 let client_b = server.create_client(cx_b, "user_b").await;
351
352 let parent_id = server
353 .make_channel("parent", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
354 .await;
355
356 let sub_id = client_a
357 .channel_store()
358 .update(cx_a, |channel_store, cx| {
359 channel_store.create_channel("sub_channel", Some(parent_id), cx)
360 })
361 .await
362 .unwrap();
363
364 let active_call_b = cx_b.read(ActiveCall::global);
365
366 assert!(active_call_b
367 .update(cx_b, |active_call, cx| active_call.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".to_string(),
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".to_string(),
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".to_string(),
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".to_string(),
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".to_string(),
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".to_string(),
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".to_string(),
916 role: ChannelRole::Admin,
917 },
918 ExpectedChannel {
919 depth: 1,
920 id: subchannel_id,
921 name: "subchannel".to_string(),
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".to_string(),
947 role: ChannelRole::Member,
948 },
949 ExpectedChannel {
950 depth: 1,
951 id: subchannel_id,
952 name: "subchannel".to_string(),
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 // the members-only channel is still shown for c, but hidden for b
1132 assert_channels_list_shape(
1133 client_b.channel_store(),
1134 cx_b,
1135 &[
1136 (zed_channel, 0),
1137 (active_channel, 1),
1138 (vim_channel, 2),
1139 (helix_channel, 3),
1140 ],
1141 );
1142 cx_b.read(|cx| {
1143 client_b.channel_store().read_with(cx, |channel_store, _| {
1144 assert_eq!(
1145 channel_store
1146 .channel_for_id(vim_channel)
1147 .unwrap()
1148 .visibility,
1149 proto::ChannelVisibility::Members
1150 )
1151 })
1152 });
1153
1154 assert_channels_list_shape(client_c.channel_store(), cx_c, &[(zed_channel, 0)]);
1155}
1156
1157#[gpui::test]
1158async fn test_channel_membership_notifications(
1159 executor: BackgroundExecutor,
1160 cx_a: &mut TestAppContext,
1161 cx_b: &mut TestAppContext,
1162) {
1163 let mut server = TestServer::start(executor.clone()).await;
1164 let client_a = server.create_client(cx_a, "user_a").await;
1165 let client_b = server.create_client(cx_b, "user_c").await;
1166
1167 let user_b = client_b.user_id().unwrap();
1168
1169 let channels = server
1170 .make_channel_tree(
1171 &[
1172 ("zed", None),
1173 ("active", Some("zed")),
1174 ("vim", Some("active")),
1175 ],
1176 (&client_a, cx_a),
1177 )
1178 .await;
1179 let zed_channel = channels[0];
1180 let _active_channel = channels[1];
1181 let vim_channel = channels[2];
1182
1183 try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
1184 [
1185 channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
1186 channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx),
1187 channel_store.invite_member(vim_channel, user_b, proto::ChannelRole::Member, cx),
1188 channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Guest, cx),
1189 ]
1190 }))
1191 .await
1192 .unwrap();
1193
1194 executor.run_until_parked();
1195
1196 client_b
1197 .channel_store()
1198 .update(cx_b, |channel_store, cx| {
1199 channel_store.respond_to_channel_invite(zed_channel, true, cx)
1200 })
1201 .await
1202 .unwrap();
1203
1204 client_b
1205 .channel_store()
1206 .update(cx_b, |channel_store, cx| {
1207 channel_store.respond_to_channel_invite(vim_channel, true, cx)
1208 })
1209 .await
1210 .unwrap();
1211
1212 executor.run_until_parked();
1213
1214 // we have an admin (a), and a guest (b) with access to all of zed, and membership in vim.
1215 assert_channels(
1216 client_b.channel_store(),
1217 cx_b,
1218 &[
1219 ExpectedChannel {
1220 depth: 0,
1221 id: zed_channel,
1222 name: "zed".to_string(),
1223 role: ChannelRole::Guest,
1224 },
1225 ExpectedChannel {
1226 depth: 1,
1227 id: vim_channel,
1228 name: "vim".to_string(),
1229 role: ChannelRole::Member,
1230 },
1231 ],
1232 );
1233
1234 client_a
1235 .channel_store()
1236 .update(cx_a, |channel_store, cx| {
1237 channel_store.remove_member(vim_channel, user_b, cx)
1238 })
1239 .await
1240 .unwrap();
1241
1242 executor.run_until_parked();
1243
1244 assert_channels(
1245 client_b.channel_store(),
1246 cx_b,
1247 &[
1248 ExpectedChannel {
1249 depth: 0,
1250 id: zed_channel,
1251 name: "zed".to_string(),
1252 role: ChannelRole::Guest,
1253 },
1254 ExpectedChannel {
1255 depth: 1,
1256 id: vim_channel,
1257 name: "vim".to_string(),
1258 role: ChannelRole::Guest,
1259 },
1260 ],
1261 )
1262}
1263
1264#[gpui::test]
1265async fn test_guest_access(
1266 executor: BackgroundExecutor,
1267 cx_a: &mut TestAppContext,
1268 cx_b: &mut TestAppContext,
1269) {
1270 let mut server = TestServer::start(executor.clone()).await;
1271 let client_a = server.create_client(cx_a, "user_a").await;
1272 let client_b = server.create_client(cx_b, "user_b").await;
1273
1274 let channels = server
1275 .make_channel_tree(
1276 &[("channel-a", None), ("channel-b", Some("channel-a"))],
1277 (&client_a, cx_a),
1278 )
1279 .await;
1280 let channel_a = channels[0];
1281 let channel_b = channels[1];
1282
1283 let active_call_b = cx_b.read(ActiveCall::global);
1284
1285 // Non-members should not be allowed to join
1286 assert!(active_call_b
1287 .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
1288 .await
1289 .is_err());
1290
1291 // Make channels A and B public
1292 client_a
1293 .channel_store()
1294 .update(cx_a, |channel_store, cx| {
1295 channel_store.set_channel_visibility(channel_a, proto::ChannelVisibility::Public, cx)
1296 })
1297 .await
1298 .unwrap();
1299 client_a
1300 .channel_store()
1301 .update(cx_a, |channel_store, cx| {
1302 channel_store.set_channel_visibility(channel_b, proto::ChannelVisibility::Public, cx)
1303 })
1304 .await
1305 .unwrap();
1306
1307 // Client B joins channel A as a guest
1308 active_call_b
1309 .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
1310 .await
1311 .unwrap();
1312
1313 executor.run_until_parked();
1314 assert_channels_list_shape(
1315 client_a.channel_store(),
1316 cx_a,
1317 &[(channel_a, 0), (channel_b, 1)],
1318 );
1319 assert_channels_list_shape(
1320 client_b.channel_store(),
1321 cx_b,
1322 &[(channel_a, 0), (channel_b, 1)],
1323 );
1324
1325 client_a.channel_store().update(cx_a, |channel_store, _| {
1326 let participants = channel_store.channel_participants(channel_a);
1327 assert_eq!(participants.len(), 1);
1328 assert_eq!(participants[0].id, client_b.user_id().unwrap());
1329 });
1330
1331 client_a
1332 .channel_store()
1333 .update(cx_a, |channel_store, cx| {
1334 channel_store.set_channel_visibility(channel_a, proto::ChannelVisibility::Members, cx)
1335 })
1336 .await
1337 .unwrap();
1338
1339 assert_channels_list_shape(client_b.channel_store(), cx_b, &[]);
1340
1341 active_call_b
1342 .update(cx_b, |call, cx| call.join_channel(channel_b, cx))
1343 .await
1344 .unwrap();
1345
1346 executor.run_until_parked();
1347 assert_channels_list_shape(client_b.channel_store(), cx_b, &[(channel_b, 0)]);
1348}
1349
1350#[gpui::test]
1351async fn test_invite_access(
1352 executor: BackgroundExecutor,
1353 cx_a: &mut TestAppContext,
1354 cx_b: &mut TestAppContext,
1355) {
1356 let mut server = TestServer::start(executor.clone()).await;
1357 let client_a = server.create_client(cx_a, "user_a").await;
1358 let client_b = server.create_client(cx_b, "user_b").await;
1359
1360 let channels = server
1361 .make_channel_tree(
1362 &[("channel-a", None), ("channel-b", Some("channel-a"))],
1363 (&client_a, cx_a),
1364 )
1365 .await;
1366 let channel_a_id = channels[0];
1367 let channel_b_id = channels[0];
1368
1369 let active_call_b = cx_b.read(ActiveCall::global);
1370
1371 // should not be allowed to join
1372 assert!(active_call_b
1373 .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
1374 .await
1375 .is_err());
1376
1377 client_a
1378 .channel_store()
1379 .update(cx_a, |channel_store, cx| {
1380 channel_store.invite_member(
1381 channel_a_id,
1382 client_b.user_id().unwrap(),
1383 ChannelRole::Member,
1384 cx,
1385 )
1386 })
1387 .await
1388 .unwrap();
1389
1390 active_call_b
1391 .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
1392 .await
1393 .unwrap();
1394
1395 executor.run_until_parked();
1396
1397 client_b.channel_store().update(cx_b, |channel_store, _| {
1398 assert!(channel_store.channel_for_id(channel_b_id).is_some());
1399 assert!(channel_store.channel_for_id(channel_a_id).is_some());
1400 });
1401
1402 client_a.channel_store().update(cx_a, |channel_store, _| {
1403 let participants = channel_store.channel_participants(channel_b_id);
1404 assert_eq!(participants.len(), 1);
1405 assert_eq!(participants[0].id, client_b.user_id().unwrap());
1406 })
1407}
1408
1409#[gpui::test]
1410async fn test_channel_moving(
1411 executor: BackgroundExecutor,
1412 cx_a: &mut TestAppContext,
1413 _cx_b: &mut TestAppContext,
1414 _cx_c: &mut TestAppContext,
1415) {
1416 let mut server = TestServer::start(executor.clone()).await;
1417 let client_a = server.create_client(cx_a, "user_a").await;
1418 // let client_b = server.create_client(cx_b, "user_b").await;
1419 // let client_c = server.create_client(cx_c, "user_c").await;
1420
1421 let channels = server
1422 .make_channel_tree(
1423 &[
1424 ("channel-a", None),
1425 ("channel-b", Some("channel-a")),
1426 ("channel-c", Some("channel-b")),
1427 ("channel-d", Some("channel-c")),
1428 ],
1429 (&client_a, cx_a),
1430 )
1431 .await;
1432 let channel_a_id = channels[0];
1433 let channel_b_id = channels[1];
1434 let channel_c_id = channels[2];
1435 let channel_d_id = channels[3];
1436
1437 // Current shape:
1438 // a - b - c - d
1439 assert_channels_list_shape(
1440 client_a.channel_store(),
1441 cx_a,
1442 &[
1443 (channel_a_id, 0),
1444 (channel_b_id, 1),
1445 (channel_c_id, 2),
1446 (channel_d_id, 3),
1447 ],
1448 );
1449
1450 client_a
1451 .channel_store()
1452 .update(cx_a, |channel_store, cx| {
1453 channel_store.move_channel(channel_d_id, Some(channel_b_id), cx)
1454 })
1455 .await
1456 .unwrap();
1457
1458 // Current shape:
1459 // /- d
1460 // a - b -- c
1461 assert_channels_list_shape(
1462 client_a.channel_store(),
1463 cx_a,
1464 &[
1465 (channel_a_id, 0),
1466 (channel_b_id, 1),
1467 (channel_c_id, 2),
1468 (channel_d_id, 2),
1469 ],
1470 );
1471}
1472
1473#[derive(Debug, PartialEq)]
1474struct ExpectedChannel {
1475 depth: usize,
1476 id: ChannelId,
1477 name: String,
1478 role: ChannelRole,
1479}
1480
1481#[track_caller]
1482fn assert_channel_invitations(
1483 channel_store: &Model<ChannelStore>,
1484 cx: &TestAppContext,
1485 expected_channels: &[ExpectedChannel],
1486) {
1487 let actual = cx.read(|cx| {
1488 channel_store.read_with(cx, |store, _| {
1489 store
1490 .channel_invitations()
1491 .iter()
1492 .map(|channel| ExpectedChannel {
1493 depth: 0,
1494 name: channel.name.clone(),
1495 id: channel.id,
1496 role: channel.role,
1497 })
1498 .collect::<Vec<_>>()
1499 })
1500 });
1501 assert_eq!(actual, expected_channels);
1502}
1503
1504#[track_caller]
1505fn assert_channels(
1506 channel_store: &Model<ChannelStore>,
1507 cx: &TestAppContext,
1508 expected_channels: &[ExpectedChannel],
1509) {
1510 let actual = cx.read(|cx| {
1511 channel_store.read_with(cx, |store, _| {
1512 store
1513 .ordered_channels()
1514 .map(|(depth, channel)| ExpectedChannel {
1515 depth,
1516 name: channel.name.clone(),
1517 id: channel.id,
1518 role: channel.role,
1519 })
1520 .collect::<Vec<_>>()
1521 })
1522 });
1523 pretty_assertions::assert_eq!(actual, expected_channels);
1524}
1525
1526#[track_caller]
1527fn assert_channels_list_shape(
1528 channel_store: &Model<ChannelStore>,
1529 cx: &TestAppContext,
1530 expected_channels: &[(u64, usize)],
1531) {
1532 let actual = cx.read(|cx| {
1533 channel_store.read_with(cx, |store, _| {
1534 store
1535 .ordered_channels()
1536 .map(|(depth, channel)| (channel.id, depth))
1537 .collect::<Vec<_>>()
1538 })
1539 });
1540 pretty_assertions::assert_eq!(actual, expected_channels);
1541}