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