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