test_server.rs

  1use crate::{
  2    auth::split_dev_server_token,
  3    db::{tests::TestDb, NewUserParams, UserId},
  4    executor::Executor,
  5    rpc::{Principal, Server, ZedVersion, CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
  6    AppState, Config, RateLimiter,
  7};
  8use anyhow::anyhow;
  9use call::ActiveCall;
 10use channel::{ChannelBuffer, ChannelStore};
 11use client::{
 12    self, proto::PeerId, ChannelId, Client, Connection, Credentials, EstablishConnectionError,
 13    UserStore,
 14};
 15use clock::FakeSystemClock;
 16use collab_ui::channel_view::ChannelView;
 17use collections::{HashMap, HashSet};
 18use fs::FakeFs;
 19use futures::{channel::oneshot, StreamExt as _};
 20use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, View, VisualTestContext};
 21use language::LanguageRegistry;
 22use node_runtime::FakeNodeRuntime;
 23use notifications::NotificationStore;
 24use parking_lot::Mutex;
 25use project::{Project, WorktreeId};
 26use rpc::{
 27    proto::{self, ChannelRole},
 28    RECEIVE_TIMEOUT,
 29};
 30use semantic_version::SemanticVersion;
 31use serde_json::json;
 32use settings::SettingsStore;
 33use std::{
 34    cell::{Ref, RefCell, RefMut},
 35    env,
 36    ops::{Deref, DerefMut},
 37    path::Path,
 38    sync::{
 39        atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
 40        Arc,
 41    },
 42};
 43use util::http::FakeHttpClient;
 44use workspace::{Workspace, WorkspaceId, WorkspaceStore};
 45
 46pub struct TestServer {
 47    pub app_state: Arc<AppState>,
 48    pub test_live_kit_server: Arc<live_kit_client::TestServer>,
 49    server: Arc<Server>,
 50    next_github_user_id: i32,
 51    connection_killers: Arc<Mutex<HashMap<PeerId, Arc<AtomicBool>>>>,
 52    forbid_connections: Arc<AtomicBool>,
 53    _test_db: TestDb,
 54}
 55
 56pub struct TestClient {
 57    pub username: String,
 58    pub app_state: Arc<workspace::AppState>,
 59    channel_store: Model<ChannelStore>,
 60    notification_store: Model<NotificationStore>,
 61    state: RefCell<TestClientState>,
 62}
 63
 64#[derive(Default)]
 65struct TestClientState {
 66    local_projects: Vec<Model<Project>>,
 67    remote_projects: Vec<Model<Project>>,
 68    buffers: HashMap<Model<Project>, HashSet<Model<language::Buffer>>>,
 69    channel_buffers: HashSet<Model<ChannelBuffer>>,
 70}
 71
 72pub struct ContactsSummary {
 73    pub current: Vec<String>,
 74    pub outgoing_requests: Vec<String>,
 75    pub incoming_requests: Vec<String>,
 76}
 77
 78impl TestServer {
 79    pub async fn start(deterministic: BackgroundExecutor) -> Self {
 80        static NEXT_LIVE_KIT_SERVER_ID: AtomicUsize = AtomicUsize::new(0);
 81
 82        let use_postgres = env::var("USE_POSTGRES").ok();
 83        let use_postgres = use_postgres.as_deref();
 84        let test_db = if use_postgres == Some("true") || use_postgres == Some("1") {
 85            TestDb::postgres(deterministic.clone())
 86        } else {
 87            TestDb::sqlite(deterministic.clone())
 88        };
 89        let live_kit_server_id = NEXT_LIVE_KIT_SERVER_ID.fetch_add(1, SeqCst);
 90        let live_kit_server = live_kit_client::TestServer::create(
 91            format!("http://livekit.{}.test", live_kit_server_id),
 92            format!("devkey-{}", live_kit_server_id),
 93            format!("secret-{}", live_kit_server_id),
 94            deterministic.clone(),
 95        )
 96        .unwrap();
 97        let executor = Executor::Deterministic(deterministic.clone());
 98        let app_state = Self::build_app_state(&test_db, &live_kit_server, executor.clone()).await;
 99        let epoch = app_state
100            .db
101            .create_server(&app_state.config.zed_environment)
102            .await
103            .unwrap();
104        let server = Server::new(epoch, app_state.clone());
105        server.start().await.unwrap();
106        // Advance clock to ensure the server's cleanup task is finished.
107        deterministic.advance_clock(CLEANUP_TIMEOUT);
108        Self {
109            app_state,
110            server,
111            connection_killers: Default::default(),
112            forbid_connections: Default::default(),
113            next_github_user_id: 0,
114            _test_db: test_db,
115            test_live_kit_server: live_kit_server,
116        }
117    }
118
119    pub async fn start2(
120        cx_a: &mut TestAppContext,
121        cx_b: &mut TestAppContext,
122    ) -> (TestServer, TestClient, TestClient, ChannelId) {
123        let mut server = Self::start(cx_a.executor()).await;
124        let client_a = server.create_client(cx_a, "user_a").await;
125        let client_b = server.create_client(cx_b, "user_b").await;
126        let channel_id = server
127            .make_channel(
128                "test-channel",
129                None,
130                (&client_a, cx_a),
131                &mut [(&client_b, cx_b)],
132            )
133            .await;
134        cx_a.run_until_parked();
135
136        (server, client_a, client_b, channel_id)
137    }
138
139    pub async fn start1(cx: &mut TestAppContext) -> (TestServer, TestClient) {
140        let mut server = Self::start(cx.executor().clone()).await;
141        let client = server.create_client(cx, "user_a").await;
142        (server, client)
143    }
144
145    pub async fn reset(&self) {
146        self.app_state.db.reset();
147        let epoch = self
148            .app_state
149            .db
150            .create_server(&self.app_state.config.zed_environment)
151            .await
152            .unwrap();
153        self.server.reset(epoch);
154    }
155
156    pub async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
157        cx.update(|cx| {
158            if cx.has_global::<SettingsStore>() {
159                panic!("Same cx used to create two test clients")
160            }
161            let settings = SettingsStore::test(cx);
162            cx.set_global(settings);
163            release_channel::init("0.0.0", cx);
164            client::init_settings(cx);
165        });
166
167        let clock = Arc::new(FakeSystemClock::default());
168        let http = FakeHttpClient::with_404_response();
169        let user_id = if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await
170        {
171            user.id
172        } else {
173            let github_user_id = self.next_github_user_id;
174            self.next_github_user_id += 1;
175            self.app_state
176                .db
177                .create_user(
178                    &format!("{name}@example.com"),
179                    false,
180                    NewUserParams {
181                        github_login: name.into(),
182                        github_user_id,
183                    },
184                )
185                .await
186                .expect("creating user failed")
187                .user_id
188        };
189        let client_name = name.to_string();
190        let mut client = cx.update(|cx| Client::new(clock, http.clone(), cx));
191        let server = self.server.clone();
192        let db = self.app_state.db.clone();
193        let connection_killers = self.connection_killers.clone();
194        let forbid_connections = self.forbid_connections.clone();
195
196        Arc::get_mut(&mut client)
197            .unwrap()
198            .set_id(user_id.to_proto())
199            .override_authenticate(move |cx| {
200                cx.spawn(|_| async move {
201                    let access_token = "the-token".to_string();
202                    Ok(Credentials::User {
203                        user_id: user_id.to_proto(),
204                        access_token,
205                    })
206                })
207            })
208            .override_establish_connection(move |credentials, cx| {
209                assert_eq!(
210                    credentials,
211                    &Credentials::User {
212                        user_id: user_id.0 as u64,
213                        access_token: "the-token".into()
214                    }
215                );
216
217                let server = server.clone();
218                let db = db.clone();
219                let connection_killers = connection_killers.clone();
220                let forbid_connections = forbid_connections.clone();
221                let client_name = client_name.clone();
222                cx.spawn(move |cx| async move {
223                    if forbid_connections.load(SeqCst) {
224                        Err(EstablishConnectionError::other(anyhow!(
225                            "server is forbidding connections"
226                        )))
227                    } else {
228                        let (client_conn, server_conn, killed) =
229                            Connection::in_memory(cx.background_executor().clone());
230                        let (connection_id_tx, connection_id_rx) = oneshot::channel();
231                        let user = db
232                            .get_user_by_id(user_id)
233                            .await
234                            .expect("retrieving user failed")
235                            .unwrap();
236                        cx.background_executor()
237                            .spawn(server.handle_connection(
238                                server_conn,
239                                client_name,
240                                Principal::User(user),
241                                ZedVersion(SemanticVersion::new(1, 0, 0)),
242                                Some(connection_id_tx),
243                                Executor::Deterministic(cx.background_executor().clone()),
244                            ))
245                            .detach();
246                        let connection_id = connection_id_rx.await.map_err(|e| {
247                            EstablishConnectionError::Other(anyhow!(
248                                "{} (is server shutting down?)",
249                                e
250                            ))
251                        })?;
252                        connection_killers
253                            .lock()
254                            .insert(connection_id.into(), killed);
255                        Ok(client_conn)
256                    }
257                })
258            });
259
260        let fs = FakeFs::new(cx.executor());
261        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
262        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
263        let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
264        let app_state = Arc::new(workspace::AppState {
265            client: client.clone(),
266            user_store: user_store.clone(),
267            workspace_store,
268            languages: language_registry,
269            fs: fs.clone(),
270            build_window_options: |_, _| Default::default(),
271            node_runtime: FakeNodeRuntime::new(),
272        });
273
274        let os_keymap = if cfg!(target_os = "linux") {
275            "keymaps/default-linux.json"
276        } else {
277            "keymaps/default-macos.json"
278        };
279
280        cx.update(|cx| {
281            theme::init(theme::LoadThemes::JustBase, cx);
282            Project::init(&client, cx);
283            client::init(&client, cx);
284            language::init(cx);
285            editor::init(cx);
286            workspace::init(app_state.clone(), cx);
287            call::init(client.clone(), user_store.clone(), cx);
288            channel::init(&client, user_store.clone(), cx);
289            notifications::init(client.clone(), user_store, cx);
290            collab_ui::init(&app_state, cx);
291            file_finder::init(cx);
292            menu::init();
293            remote_projects::init(client.clone(), cx);
294            settings::KeymapFile::load_asset(os_keymap, cx).unwrap();
295        });
296
297        client
298            .authenticate_and_connect(false, &cx.to_async())
299            .await
300            .unwrap();
301
302        let client = TestClient {
303            app_state,
304            username: name.to_string(),
305            channel_store: cx.read(ChannelStore::global).clone(),
306            notification_store: cx.read(NotificationStore::global).clone(),
307            state: Default::default(),
308        };
309        client.wait_for_current_user(cx).await;
310        client
311    }
312
313    pub async fn create_dev_server(
314        &self,
315        access_token: String,
316        cx: &mut TestAppContext,
317    ) -> TestClient {
318        cx.update(|cx| {
319            if cx.has_global::<SettingsStore>() {
320                panic!("Same cx used to create two test clients")
321            }
322            let settings = SettingsStore::test(cx);
323            cx.set_global(settings);
324            release_channel::init("0.0.0", cx);
325            client::init_settings(cx);
326        });
327        let (dev_server_id, _) = split_dev_server_token(&access_token).unwrap();
328
329        let clock = Arc::new(FakeSystemClock::default());
330        let http = FakeHttpClient::with_404_response();
331        let mut client = cx.update(|cx| Client::new(clock, http.clone(), cx));
332        let server = self.server.clone();
333        let db = self.app_state.db.clone();
334        let connection_killers = self.connection_killers.clone();
335        let forbid_connections = self.forbid_connections.clone();
336        Arc::get_mut(&mut client)
337            .unwrap()
338            .set_id(1)
339            .set_dev_server_token(client::DevServerToken(access_token.clone()))
340            .override_establish_connection(move |credentials, cx| {
341                assert_eq!(
342                    credentials,
343                    &Credentials::DevServer {
344                        token: client::DevServerToken(access_token.to_string())
345                    }
346                );
347
348                let server = server.clone();
349                let db = db.clone();
350                let connection_killers = connection_killers.clone();
351                let forbid_connections = forbid_connections.clone();
352                cx.spawn(move |cx| async move {
353                    if forbid_connections.load(SeqCst) {
354                        Err(EstablishConnectionError::other(anyhow!(
355                            "server is forbidding connections"
356                        )))
357                    } else {
358                        let (client_conn, server_conn, killed) =
359                            Connection::in_memory(cx.background_executor().clone());
360                        let (connection_id_tx, connection_id_rx) = oneshot::channel();
361                        let dev_server = db
362                            .get_dev_server(dev_server_id)
363                            .await
364                            .expect("retrieving dev_server failed");
365                        cx.background_executor()
366                            .spawn(server.handle_connection(
367                                server_conn,
368                                "dev-server".to_string(),
369                                Principal::DevServer(dev_server),
370                                ZedVersion(SemanticVersion::new(1, 0, 0)),
371                                Some(connection_id_tx),
372                                Executor::Deterministic(cx.background_executor().clone()),
373                            ))
374                            .detach();
375                        let connection_id = connection_id_rx.await.map_err(|e| {
376                            EstablishConnectionError::Other(anyhow!(
377                                "{} (is server shutting down?)",
378                                e
379                            ))
380                        })?;
381                        connection_killers
382                            .lock()
383                            .insert(connection_id.into(), killed);
384                        Ok(client_conn)
385                    }
386                })
387            });
388
389        let fs = FakeFs::new(cx.executor());
390        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
391        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
392        let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
393        let app_state = Arc::new(workspace::AppState {
394            client: client.clone(),
395            user_store: user_store.clone(),
396            workspace_store,
397            languages: language_registry,
398            fs: fs.clone(),
399            build_window_options: |_, _| Default::default(),
400            node_runtime: FakeNodeRuntime::new(),
401        });
402
403        cx.update(|cx| {
404            theme::init(theme::LoadThemes::JustBase, cx);
405            Project::init(&client, cx);
406            client::init(&client, cx);
407            language::init(cx);
408            editor::init(cx);
409            workspace::init(app_state.clone(), cx);
410            call::init(client.clone(), user_store.clone(), cx);
411            channel::init(&client, user_store.clone(), cx);
412            notifications::init(client.clone(), user_store, cx);
413            collab_ui::init(&app_state, cx);
414            file_finder::init(cx);
415            menu::init();
416            headless::init(
417                client.clone(),
418                headless::AppState {
419                    languages: app_state.languages.clone(),
420                    user_store: app_state.user_store.clone(),
421                    fs: fs.clone(),
422                    node_runtime: app_state.node_runtime.clone(),
423                },
424                cx,
425            );
426        });
427
428        TestClient {
429            app_state,
430            username: "dev-server".to_string(),
431            channel_store: cx.read(ChannelStore::global).clone(),
432            notification_store: cx.read(NotificationStore::global).clone(),
433            state: Default::default(),
434        }
435    }
436
437    pub fn disconnect_client(&self, peer_id: PeerId) {
438        self.connection_killers
439            .lock()
440            .remove(&peer_id)
441            .unwrap()
442            .store(true, SeqCst);
443    }
444
445    pub fn simulate_long_connection_interruption(
446        &self,
447        peer_id: PeerId,
448        deterministic: BackgroundExecutor,
449    ) {
450        self.forbid_connections();
451        self.disconnect_client(peer_id);
452        deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
453        self.allow_connections();
454        deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
455        deterministic.run_until_parked();
456    }
457
458    pub fn forbid_connections(&self) {
459        self.forbid_connections.store(true, SeqCst);
460    }
461
462    pub fn allow_connections(&self) {
463        self.forbid_connections.store(false, SeqCst);
464    }
465
466    pub async fn make_contacts(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) {
467        for ix in 1..clients.len() {
468            let (left, right) = clients.split_at_mut(ix);
469            let (client_a, cx_a) = left.last_mut().unwrap();
470            for (client_b, cx_b) in right {
471                client_a
472                    .app_state
473                    .user_store
474                    .update(*cx_a, |store, cx| {
475                        store.request_contact(client_b.user_id().unwrap(), cx)
476                    })
477                    .await
478                    .unwrap();
479                cx_a.executor().run_until_parked();
480                client_b
481                    .app_state
482                    .user_store
483                    .update(*cx_b, |store, cx| {
484                        store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
485                    })
486                    .await
487                    .unwrap();
488            }
489        }
490    }
491
492    pub async fn make_channel(
493        &self,
494        channel: &str,
495        parent: Option<ChannelId>,
496        admin: (&TestClient, &mut TestAppContext),
497        members: &mut [(&TestClient, &mut TestAppContext)],
498    ) -> ChannelId {
499        let (_, admin_cx) = admin;
500        let channel_id = admin_cx
501            .read(ChannelStore::global)
502            .update(admin_cx, |channel_store, cx| {
503                channel_store.create_channel(channel, parent, cx)
504            })
505            .await
506            .unwrap();
507
508        for (member_client, member_cx) in members {
509            admin_cx
510                .read(ChannelStore::global)
511                .update(admin_cx, |channel_store, cx| {
512                    channel_store.invite_member(
513                        channel_id,
514                        member_client.user_id().unwrap(),
515                        ChannelRole::Member,
516                        cx,
517                    )
518                })
519                .await
520                .unwrap();
521
522            admin_cx.executor().run_until_parked();
523
524            member_cx
525                .read(ChannelStore::global)
526                .update(*member_cx, |channels, cx| {
527                    channels.respond_to_channel_invite(channel_id, true, cx)
528                })
529                .await
530                .unwrap();
531        }
532
533        channel_id
534    }
535
536    pub async fn make_public_channel(
537        &self,
538        channel: &str,
539        client: &TestClient,
540        cx: &mut TestAppContext,
541    ) -> ChannelId {
542        let channel_id = self
543            .make_channel(channel, None, (client, cx), &mut [])
544            .await;
545
546        client
547            .channel_store()
548            .update(cx, |channel_store, cx| {
549                channel_store.set_channel_visibility(
550                    channel_id,
551                    proto::ChannelVisibility::Public,
552                    cx,
553                )
554            })
555            .await
556            .unwrap();
557
558        channel_id
559    }
560
561    pub async fn make_channel_tree(
562        &self,
563        channels: &[(&str, Option<&str>)],
564        creator: (&TestClient, &mut TestAppContext),
565    ) -> Vec<ChannelId> {
566        let mut observed_channels = HashMap::default();
567        let mut result = Vec::new();
568        for (channel, parent) in channels {
569            let id;
570            if let Some(parent) = parent {
571                if let Some(parent_id) = observed_channels.get(parent) {
572                    id = self
573                        .make_channel(channel, Some(*parent_id), (creator.0, creator.1), &mut [])
574                        .await;
575                } else {
576                    panic!(
577                        "Edge {}->{} referenced before {} was created",
578                        parent, channel, parent
579                    )
580                }
581            } else {
582                id = self
583                    .make_channel(channel, None, (creator.0, creator.1), &mut [])
584                    .await;
585            }
586
587            observed_channels.insert(channel, id);
588            result.push(id);
589        }
590
591        result
592    }
593
594    pub async fn create_room(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) {
595        self.make_contacts(clients).await;
596
597        let (left, right) = clients.split_at_mut(1);
598        let (_client_a, cx_a) = &mut left[0];
599        let active_call_a = cx_a.read(ActiveCall::global);
600
601        for (client_b, cx_b) in right {
602            let user_id_b = client_b.current_user_id(cx_b).to_proto();
603            active_call_a
604                .update(*cx_a, |call, cx| call.invite(user_id_b, None, cx))
605                .await
606                .unwrap();
607
608            cx_b.executor().run_until_parked();
609            let active_call_b = cx_b.read(ActiveCall::global);
610            active_call_b
611                .update(*cx_b, |call, cx| call.accept_incoming(cx))
612                .await
613                .unwrap();
614        }
615    }
616
617    pub async fn build_app_state(
618        test_db: &TestDb,
619        live_kit_test_server: &live_kit_client::TestServer,
620        executor: Executor,
621    ) -> Arc<AppState> {
622        Arc::new(AppState {
623            db: test_db.db().clone(),
624            live_kit_client: Some(Arc::new(live_kit_test_server.create_api_client())),
625            blob_store_client: None,
626            rate_limiter: Arc::new(RateLimiter::new(test_db.db().clone())),
627            executor,
628            clickhouse_client: None,
629            config: Config {
630                http_port: 0,
631                database_url: "".into(),
632                database_max_connections: 0,
633                api_token: "".into(),
634                invite_link_prefix: "".into(),
635                live_kit_server: None,
636                live_kit_key: None,
637                live_kit_secret: None,
638                rust_log: None,
639                log_json: None,
640                zed_environment: "test".into(),
641                blob_store_url: None,
642                blob_store_region: None,
643                blob_store_access_key: None,
644                blob_store_secret_key: None,
645                blob_store_bucket: None,
646                openai_api_key: None,
647                google_ai_api_key: None,
648                anthropic_api_key: None,
649                clickhouse_url: None,
650                clickhouse_user: None,
651                clickhouse_password: None,
652                clickhouse_database: None,
653                zed_client_checksum_seed: None,
654                slack_panics_webhook: None,
655                auto_join_channel_id: None,
656                migrations_path: None,
657                seed_path: None,
658            },
659        })
660    }
661}
662
663impl Deref for TestServer {
664    type Target = Server;
665
666    fn deref(&self) -> &Self::Target {
667        &self.server
668    }
669}
670
671impl Drop for TestServer {
672    fn drop(&mut self) {
673        self.server.teardown();
674        self.test_live_kit_server.teardown().unwrap();
675    }
676}
677
678impl Deref for TestClient {
679    type Target = Arc<Client>;
680
681    fn deref(&self) -> &Self::Target {
682        &self.app_state.client
683    }
684}
685
686impl TestClient {
687    pub fn fs(&self) -> &FakeFs {
688        self.app_state.fs.as_fake()
689    }
690
691    pub fn channel_store(&self) -> &Model<ChannelStore> {
692        &self.channel_store
693    }
694
695    pub fn notification_store(&self) -> &Model<NotificationStore> {
696        &self.notification_store
697    }
698
699    pub fn user_store(&self) -> &Model<UserStore> {
700        &self.app_state.user_store
701    }
702
703    pub fn language_registry(&self) -> &Arc<LanguageRegistry> {
704        &self.app_state.languages
705    }
706
707    pub fn client(&self) -> &Arc<Client> {
708        &self.app_state.client
709    }
710
711    pub fn current_user_id(&self, cx: &TestAppContext) -> UserId {
712        UserId::from_proto(
713            self.app_state
714                .user_store
715                .read_with(cx, |user_store, _| user_store.current_user().unwrap().id),
716        )
717    }
718
719    pub async fn wait_for_current_user(&self, cx: &TestAppContext) {
720        let mut authed_user = self
721            .app_state
722            .user_store
723            .read_with(cx, |user_store, _| user_store.watch_current_user());
724        while authed_user.next().await.unwrap().is_none() {}
725    }
726
727    pub async fn clear_contacts(&self, cx: &mut TestAppContext) {
728        self.app_state
729            .user_store
730            .update(cx, |store, _| store.clear_contacts())
731            .await;
732    }
733
734    pub fn local_projects(&self) -> impl Deref<Target = Vec<Model<Project>>> + '_ {
735        Ref::map(self.state.borrow(), |state| &state.local_projects)
736    }
737
738    pub fn remote_projects(&self) -> impl Deref<Target = Vec<Model<Project>>> + '_ {
739        Ref::map(self.state.borrow(), |state| &state.remote_projects)
740    }
741
742    pub fn local_projects_mut(&self) -> impl DerefMut<Target = Vec<Model<Project>>> + '_ {
743        RefMut::map(self.state.borrow_mut(), |state| &mut state.local_projects)
744    }
745
746    pub fn remote_projects_mut(&self) -> impl DerefMut<Target = Vec<Model<Project>>> + '_ {
747        RefMut::map(self.state.borrow_mut(), |state| &mut state.remote_projects)
748    }
749
750    pub fn buffers_for_project<'a>(
751        &'a self,
752        project: &Model<Project>,
753    ) -> impl DerefMut<Target = HashSet<Model<language::Buffer>>> + 'a {
754        RefMut::map(self.state.borrow_mut(), |state| {
755            state.buffers.entry(project.clone()).or_default()
756        })
757    }
758
759    pub fn buffers(
760        &self,
761    ) -> impl DerefMut<Target = HashMap<Model<Project>, HashSet<Model<language::Buffer>>>> + '_
762    {
763        RefMut::map(self.state.borrow_mut(), |state| &mut state.buffers)
764    }
765
766    pub fn channel_buffers(&self) -> impl DerefMut<Target = HashSet<Model<ChannelBuffer>>> + '_ {
767        RefMut::map(self.state.borrow_mut(), |state| &mut state.channel_buffers)
768    }
769
770    pub fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
771        self.app_state
772            .user_store
773            .read_with(cx, |store, _| ContactsSummary {
774                current: store
775                    .contacts()
776                    .iter()
777                    .map(|contact| contact.user.github_login.clone())
778                    .collect(),
779                outgoing_requests: store
780                    .outgoing_contact_requests()
781                    .iter()
782                    .map(|user| user.github_login.clone())
783                    .collect(),
784                incoming_requests: store
785                    .incoming_contact_requests()
786                    .iter()
787                    .map(|user| user.github_login.clone())
788                    .collect(),
789            })
790    }
791
792    pub async fn build_local_project(
793        &self,
794        root_path: impl AsRef<Path>,
795        cx: &mut TestAppContext,
796    ) -> (Model<Project>, WorktreeId) {
797        let project = self.build_empty_local_project(cx);
798        let (worktree, _) = project
799            .update(cx, |p, cx| {
800                p.find_or_create_local_worktree(root_path, true, cx)
801            })
802            .await
803            .unwrap();
804        worktree
805            .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
806            .await;
807        (project, worktree.read_with(cx, |tree, _| tree.id()))
808    }
809
810    pub async fn build_test_project(&self, cx: &mut TestAppContext) -> Model<Project> {
811        self.fs()
812            .insert_tree(
813                "/a",
814                json!({
815                    "1.txt": "one\none\none",
816                    "2.js": "function two() { return 2; }",
817                    "3.rs": "mod test",
818                }),
819            )
820            .await;
821        self.build_local_project("/a", cx).await.0
822    }
823
824    pub async fn host_workspace(
825        &self,
826        workspace: &View<Workspace>,
827        channel_id: ChannelId,
828        cx: &mut VisualTestContext,
829    ) {
830        cx.update(|cx| {
831            let active_call = ActiveCall::global(cx);
832            active_call.update(cx, |call, cx| call.join_channel(channel_id, cx))
833        })
834        .await
835        .unwrap();
836        cx.update(|cx| {
837            let active_call = ActiveCall::global(cx);
838            let project = workspace.read(cx).project().clone();
839            active_call.update(cx, |call, cx| call.share_project(project, cx))
840        })
841        .await
842        .unwrap();
843        cx.executor().run_until_parked();
844    }
845
846    pub async fn join_workspace<'a>(
847        &'a self,
848        channel_id: ChannelId,
849        cx: &'a mut TestAppContext,
850    ) -> (View<Workspace>, &'a mut VisualTestContext) {
851        cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
852            .await
853            .unwrap();
854        cx.run_until_parked();
855
856        self.active_workspace(cx)
857    }
858
859    pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model<Project> {
860        cx.update(|cx| {
861            Project::local(
862                self.client().clone(),
863                self.app_state.node_runtime.clone(),
864                self.app_state.user_store.clone(),
865                self.app_state.languages.clone(),
866                self.app_state.fs.clone(),
867                cx,
868            )
869        })
870    }
871
872    pub async fn build_remote_project(
873        &self,
874        host_project_id: u64,
875        guest_cx: &mut TestAppContext,
876    ) -> Model<Project> {
877        let active_call = guest_cx.read(ActiveCall::global);
878        let room = active_call.read_with(guest_cx, |call, _| call.room().unwrap().clone());
879        room.update(guest_cx, |room, cx| {
880            room.join_project(
881                host_project_id,
882                self.app_state.languages.clone(),
883                self.app_state.fs.clone(),
884                cx,
885            )
886        })
887        .await
888        .unwrap()
889    }
890
891    pub fn build_workspace<'a>(
892        &'a self,
893        project: &Model<Project>,
894        cx: &'a mut TestAppContext,
895    ) -> (View<Workspace>, &'a mut VisualTestContext) {
896        cx.add_window_view(|cx| {
897            cx.activate_window();
898            Workspace::new(
899                WorkspaceId::default(),
900                project.clone(),
901                self.app_state.clone(),
902                cx,
903            )
904        })
905    }
906
907    pub async fn build_test_workspace<'a>(
908        &'a self,
909        cx: &'a mut TestAppContext,
910    ) -> (View<Workspace>, &'a mut VisualTestContext) {
911        let project = self.build_test_project(cx).await;
912        cx.add_window_view(|cx| {
913            cx.activate_window();
914            Workspace::new(
915                WorkspaceId::default(),
916                project.clone(),
917                self.app_state.clone(),
918                cx,
919            )
920        })
921    }
922
923    pub fn active_workspace<'a>(
924        &'a self,
925        cx: &'a mut TestAppContext,
926    ) -> (View<Workspace>, &'a mut VisualTestContext) {
927        let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
928
929        let view = window.root_view(cx).unwrap();
930        let cx = VisualTestContext::from_window(*window.deref(), cx).as_mut();
931        // it might be nice to try and cleanup these at the end of each test.
932        (view, cx)
933    }
934}
935
936pub fn open_channel_notes(
937    channel_id: ChannelId,
938    cx: &mut VisualTestContext,
939) -> Task<anyhow::Result<View<ChannelView>>> {
940    let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
941    let view = window.root_view(cx).unwrap();
942
943    cx.update(|cx| ChannelView::open(channel_id, None, view.clone(), cx))
944}
945
946impl Drop for TestClient {
947    fn drop(&mut self) {
948        self.app_state.client.teardown();
949    }
950}