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