channel_tests.rs

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