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            },
  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.get_channel_member_details(channel_a_id, 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),
 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    // the new channel shows for b and c
1027    assert_channels_list_shape(
1028        client_a.channel_store(),
1029        cx_a,
1030        &[(zed_channel, 0), (active_channel, 1), (vim_channel, 1)],
1031    );
1032    assert_channels_list_shape(
1033        client_b.channel_store(),
1034        cx_b,
1035        &[(zed_channel, 0), (active_channel, 1), (vim_channel, 1)],
1036    );
1037    assert_channels_list_shape(
1038        client_c.channel_store(),
1039        cx_c,
1040        &[(zed_channel, 0), (vim_channel, 1)],
1041    );
1042
1043    let helix_channel = client_a
1044        .channel_store()
1045        .update(cx_a, |channel_store, cx| {
1046            channel_store.create_channel("helix", Some(zed_channel), cx)
1047        })
1048        .await
1049        .unwrap();
1050
1051    client_a
1052        .channel_store()
1053        .update(cx_a, |channel_store, cx| {
1054            channel_store.move_channel(helix_channel, vim_channel, cx)
1055        })
1056        .await
1057        .unwrap();
1058
1059    client_a
1060        .channel_store()
1061        .update(cx_a, |channel_store, cx| {
1062            channel_store.set_channel_visibility(
1063                helix_channel,
1064                proto::ChannelVisibility::Public,
1065                cx,
1066            )
1067        })
1068        .await
1069        .unwrap();
1070    cx_a.run_until_parked();
1071
1072    // the new channel shows for b and c
1073    assert_channels_list_shape(
1074        client_b.channel_store(),
1075        cx_b,
1076        &[
1077            (zed_channel, 0),
1078            (active_channel, 1),
1079            (vim_channel, 1),
1080            (helix_channel, 2),
1081        ],
1082    );
1083    assert_channels_list_shape(
1084        client_c.channel_store(),
1085        cx_c,
1086        &[(zed_channel, 0), (vim_channel, 1), (helix_channel, 2)],
1087    );
1088}
1089
1090#[gpui::test]
1091async fn test_channel_membership_notifications(
1092    executor: BackgroundExecutor,
1093    cx_a: &mut TestAppContext,
1094    cx_b: &mut TestAppContext,
1095) {
1096    let mut server = TestServer::start(executor.clone()).await;
1097    let client_a = server.create_client(cx_a, "user_a").await;
1098    let client_b = server.create_client(cx_b, "user_c").await;
1099
1100    let user_b = client_b.user_id().unwrap();
1101
1102    let channels = server
1103        .make_channel_tree(
1104            &[("zed", None), ("vim", Some("zed")), ("opensource", None)],
1105            (&client_a, cx_a),
1106        )
1107        .await;
1108    let zed_channel = channels[0];
1109    let vim_channel = channels[1];
1110    let opensource_channel = channels[2];
1111
1112    try_join_all(client_a.channel_store().update(cx_a, |channel_store, cx| {
1113        [
1114            channel_store.set_channel_visibility(zed_channel, proto::ChannelVisibility::Public, cx),
1115            channel_store.set_channel_visibility(vim_channel, proto::ChannelVisibility::Public, cx),
1116            channel_store.invite_member(zed_channel, user_b, proto::ChannelRole::Admin, cx),
1117            channel_store.invite_member(opensource_channel, user_b, proto::ChannelRole::Member, cx),
1118        ]
1119    }))
1120    .await
1121    .unwrap();
1122
1123    executor.run_until_parked();
1124
1125    client_b
1126        .channel_store()
1127        .update(cx_b, |channel_store, cx| {
1128            channel_store.respond_to_channel_invite(zed_channel, true, cx)
1129        })
1130        .await
1131        .unwrap();
1132
1133    executor.run_until_parked();
1134
1135    // we have an admin (a), and a guest (b) with access to all of zed, and membership in vim.
1136    assert_channels(
1137        client_b.channel_store(),
1138        cx_b,
1139        &[
1140            ExpectedChannel {
1141                depth: 0,
1142                id: zed_channel,
1143                name: "zed".into(),
1144            },
1145            ExpectedChannel {
1146                depth: 1,
1147                id: vim_channel,
1148                name: "vim".into(),
1149            },
1150        ],
1151    );
1152
1153    client_b.channel_store().update(cx_b, |channel_store, _| {
1154        channel_store.is_channel_admin(zed_channel)
1155    });
1156
1157    client_b
1158        .channel_store()
1159        .update(cx_b, |channel_store, cx| {
1160            channel_store.respond_to_channel_invite(opensource_channel, true, cx)
1161        })
1162        .await
1163        .unwrap();
1164
1165    cx_a.run_until_parked();
1166
1167    client_a
1168        .channel_store()
1169        .update(cx_a, |channel_store, cx| {
1170            channel_store.set_member_role(opensource_channel, user_b, ChannelRole::Admin, cx)
1171        })
1172        .await
1173        .unwrap();
1174
1175    cx_a.run_until_parked();
1176
1177    client_b.channel_store().update(cx_b, |channel_store, _| {
1178        channel_store.is_channel_admin(opensource_channel)
1179    });
1180}
1181
1182#[gpui::test]
1183async fn test_guest_access(
1184    executor: BackgroundExecutor,
1185    cx_a: &mut TestAppContext,
1186    cx_b: &mut TestAppContext,
1187) {
1188    let mut server = TestServer::start(executor.clone()).await;
1189    let client_a = server.create_client(cx_a, "user_a").await;
1190    let client_b = server.create_client(cx_b, "user_b").await;
1191
1192    let channels = server
1193        .make_channel_tree(
1194            &[("channel-a", None), ("channel-b", Some("channel-a"))],
1195            (&client_a, cx_a),
1196        )
1197        .await;
1198    let channel_a = channels[0];
1199    let channel_b = channels[1];
1200
1201    let active_call_b = cx_b.read(ActiveCall::global);
1202
1203    // Non-members should not be allowed to join
1204    assert!(active_call_b
1205        .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
1206        .await
1207        .is_err());
1208
1209    // Make channels A and B public
1210    client_a
1211        .channel_store()
1212        .update(cx_a, |channel_store, cx| {
1213            channel_store.set_channel_visibility(channel_a, proto::ChannelVisibility::Public, cx)
1214        })
1215        .await
1216        .unwrap();
1217    client_a
1218        .channel_store()
1219        .update(cx_a, |channel_store, cx| {
1220            channel_store.set_channel_visibility(channel_b, proto::ChannelVisibility::Public, cx)
1221        })
1222        .await
1223        .unwrap();
1224
1225    // Client B joins channel A as a guest
1226    active_call_b
1227        .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
1228        .await
1229        .unwrap();
1230
1231    executor.run_until_parked();
1232    assert_channels_list_shape(
1233        client_a.channel_store(),
1234        cx_a,
1235        &[(channel_a, 0), (channel_b, 1)],
1236    );
1237    assert_channels_list_shape(
1238        client_b.channel_store(),
1239        cx_b,
1240        &[(channel_a, 0), (channel_b, 1)],
1241    );
1242
1243    client_a.channel_store().update(cx_a, |channel_store, _| {
1244        let participants = channel_store.channel_participants(channel_a);
1245        assert_eq!(participants.len(), 1);
1246        assert_eq!(participants[0].id, client_b.user_id().unwrap());
1247    });
1248}
1249
1250#[gpui::test]
1251async fn test_invite_access(
1252    executor: BackgroundExecutor,
1253    cx_a: &mut TestAppContext,
1254    cx_b: &mut TestAppContext,
1255) {
1256    let mut server = TestServer::start(executor.clone()).await;
1257    let client_a = server.create_client(cx_a, "user_a").await;
1258    let client_b = server.create_client(cx_b, "user_b").await;
1259
1260    let channels = server
1261        .make_channel_tree(
1262            &[("channel-a", None), ("channel-b", Some("channel-a"))],
1263            (&client_a, cx_a),
1264        )
1265        .await;
1266    let channel_a_id = channels[0];
1267    let channel_b_id = channels[0];
1268
1269    let active_call_b = cx_b.read(ActiveCall::global);
1270
1271    // should not be allowed to join
1272    assert!(active_call_b
1273        .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
1274        .await
1275        .is_err());
1276
1277    client_a
1278        .channel_store()
1279        .update(cx_a, |channel_store, cx| {
1280            channel_store.invite_member(
1281                channel_a_id,
1282                client_b.user_id().unwrap(),
1283                ChannelRole::Member,
1284                cx,
1285            )
1286        })
1287        .await
1288        .unwrap();
1289
1290    active_call_b
1291        .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
1292        .await
1293        .unwrap();
1294
1295    executor.run_until_parked();
1296
1297    client_b.channel_store().update(cx_b, |channel_store, _| {
1298        assert!(channel_store.channel_for_id(channel_b_id).is_some());
1299        assert!(channel_store.channel_for_id(channel_a_id).is_some());
1300    });
1301
1302    client_a.channel_store().update(cx_a, |channel_store, _| {
1303        let participants = channel_store.channel_participants(channel_b_id);
1304        assert_eq!(participants.len(), 1);
1305        assert_eq!(participants[0].id, client_b.user_id().unwrap());
1306    })
1307}
1308
1309#[gpui::test]
1310async fn test_leave_channel(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1311    let (_server, _client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await;
1312
1313    client_b
1314        .channel_store()
1315        .update(cx_b, |channel_store, cx| {
1316            channel_store.remove_member(channel_id, client_b.user_id().unwrap(), cx)
1317        })
1318        .await
1319        .unwrap();
1320
1321    cx_a.run_until_parked();
1322
1323    assert_eq!(
1324        client_b
1325            .channel_store()
1326            .read_with(cx_b, |store, _| store.channels().count()),
1327        0
1328    );
1329}
1330
1331#[gpui::test]
1332async fn test_channel_moving(
1333    executor: BackgroundExecutor,
1334    cx_a: &mut TestAppContext,
1335    _cx_b: &mut TestAppContext,
1336    _cx_c: &mut TestAppContext,
1337) {
1338    let mut server = TestServer::start(executor.clone()).await;
1339    let client_a = server.create_client(cx_a, "user_a").await;
1340
1341    let channels = server
1342        .make_channel_tree(
1343            &[
1344                ("channel-a", None),
1345                ("channel-b", Some("channel-a")),
1346                ("channel-c", Some("channel-b")),
1347                ("channel-d", Some("channel-c")),
1348            ],
1349            (&client_a, cx_a),
1350        )
1351        .await;
1352    let channel_a_id = channels[0];
1353    let channel_b_id = channels[1];
1354    let channel_c_id = channels[2];
1355    let channel_d_id = channels[3];
1356
1357    // Current shape:
1358    // a - b - c - d
1359    assert_channels_list_shape(
1360        client_a.channel_store(),
1361        cx_a,
1362        &[
1363            (channel_a_id, 0),
1364            (channel_b_id, 1),
1365            (channel_c_id, 2),
1366            (channel_d_id, 3),
1367        ],
1368    );
1369
1370    client_a
1371        .channel_store()
1372        .update(cx_a, |channel_store, cx| {
1373            channel_store.move_channel(channel_d_id, channel_b_id, cx)
1374        })
1375        .await
1376        .unwrap();
1377
1378    // Current shape:
1379    //       /- d
1380    // a - b -- c
1381    assert_channels_list_shape(
1382        client_a.channel_store(),
1383        cx_a,
1384        &[
1385            (channel_a_id, 0),
1386            (channel_b_id, 1),
1387            (channel_c_id, 2),
1388            (channel_d_id, 2),
1389        ],
1390    );
1391}
1392
1393#[derive(Debug, PartialEq)]
1394struct ExpectedChannel {
1395    depth: usize,
1396    id: ChannelId,
1397    name: SharedString,
1398}
1399
1400#[track_caller]
1401fn assert_channel_invitations(
1402    channel_store: &Model<ChannelStore>,
1403    cx: &TestAppContext,
1404    expected_channels: &[ExpectedChannel],
1405) {
1406    let actual = cx.read(|cx| {
1407        channel_store.read_with(cx, |store, _| {
1408            store
1409                .channel_invitations()
1410                .iter()
1411                .map(|channel| ExpectedChannel {
1412                    depth: 0,
1413                    name: channel.name.clone(),
1414                    id: channel.id,
1415                })
1416                .collect::<Vec<_>>()
1417        })
1418    });
1419    assert_eq!(actual, expected_channels);
1420}
1421
1422#[track_caller]
1423fn assert_channels(
1424    channel_store: &Model<ChannelStore>,
1425    cx: &TestAppContext,
1426    expected_channels: &[ExpectedChannel],
1427) {
1428    let actual = cx.read(|cx| {
1429        channel_store.read_with(cx, |store, _| {
1430            store
1431                .ordered_channels()
1432                .map(|(depth, channel)| ExpectedChannel {
1433                    depth,
1434                    name: channel.name.clone().into(),
1435                    id: channel.id,
1436                })
1437                .collect::<Vec<_>>()
1438        })
1439    });
1440    pretty_assertions::assert_eq!(actual, expected_channels);
1441}
1442
1443#[track_caller]
1444fn assert_channels_list_shape(
1445    channel_store: &Model<ChannelStore>,
1446    cx: &TestAppContext,
1447    expected_channels: &[(u64, usize)],
1448) {
1449    let actual = cx.read(|cx| {
1450        channel_store.read_with(cx, |store, _| {
1451            store
1452                .ordered_channels()
1453                .map(|(depth, channel)| (channel.id, depth))
1454                .collect::<Vec<_>>()
1455        })
1456    });
1457    pretty_assertions::assert_eq!(actual, expected_channels);
1458}