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    dev_server_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            dev_server_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                supermaven_admin_api_key: None,
659            },
660        })
661    }
662}
663
664impl Deref for TestServer {
665    type Target = Server;
666
667    fn deref(&self) -> &Self::Target {
668        &self.server
669    }
670}
671
672impl Drop for TestServer {
673    fn drop(&mut self) {
674        self.server.teardown();
675        self.test_live_kit_server.teardown().unwrap();
676    }
677}
678
679impl Deref for TestClient {
680    type Target = Arc<Client>;
681
682    fn deref(&self) -> &Self::Target {
683        &self.app_state.client
684    }
685}
686
687impl TestClient {
688    pub fn fs(&self) -> &FakeFs {
689        self.app_state.fs.as_fake()
690    }
691
692    pub fn channel_store(&self) -> &Model<ChannelStore> {
693        &self.channel_store
694    }
695
696    pub fn notification_store(&self) -> &Model<NotificationStore> {
697        &self.notification_store
698    }
699
700    pub fn user_store(&self) -> &Model<UserStore> {
701        &self.app_state.user_store
702    }
703
704    pub fn language_registry(&self) -> &Arc<LanguageRegistry> {
705        &self.app_state.languages
706    }
707
708    pub fn client(&self) -> &Arc<Client> {
709        &self.app_state.client
710    }
711
712    pub fn current_user_id(&self, cx: &TestAppContext) -> UserId {
713        UserId::from_proto(
714            self.app_state
715                .user_store
716                .read_with(cx, |user_store, _| user_store.current_user().unwrap().id),
717        )
718    }
719
720    pub async fn wait_for_current_user(&self, cx: &TestAppContext) {
721        let mut authed_user = self
722            .app_state
723            .user_store
724            .read_with(cx, |user_store, _| user_store.watch_current_user());
725        while authed_user.next().await.unwrap().is_none() {}
726    }
727
728    pub async fn clear_contacts(&self, cx: &mut TestAppContext) {
729        self.app_state
730            .user_store
731            .update(cx, |store, _| store.clear_contacts())
732            .await;
733    }
734
735    pub fn local_projects(&self) -> impl Deref<Target = Vec<Model<Project>>> + '_ {
736        Ref::map(self.state.borrow(), |state| &state.local_projects)
737    }
738
739    pub fn dev_server_projects(&self) -> impl Deref<Target = Vec<Model<Project>>> + '_ {
740        Ref::map(self.state.borrow(), |state| &state.dev_server_projects)
741    }
742
743    pub fn local_projects_mut(&self) -> impl DerefMut<Target = Vec<Model<Project>>> + '_ {
744        RefMut::map(self.state.borrow_mut(), |state| &mut state.local_projects)
745    }
746
747    pub fn dev_server_projects_mut(&self) -> impl DerefMut<Target = Vec<Model<Project>>> + '_ {
748        RefMut::map(self.state.borrow_mut(), |state| {
749            &mut state.dev_server_projects
750        })
751    }
752
753    pub fn buffers_for_project<'a>(
754        &'a self,
755        project: &Model<Project>,
756    ) -> impl DerefMut<Target = HashSet<Model<language::Buffer>>> + 'a {
757        RefMut::map(self.state.borrow_mut(), |state| {
758            state.buffers.entry(project.clone()).or_default()
759        })
760    }
761
762    pub fn buffers(
763        &self,
764    ) -> impl DerefMut<Target = HashMap<Model<Project>, HashSet<Model<language::Buffer>>>> + '_
765    {
766        RefMut::map(self.state.borrow_mut(), |state| &mut state.buffers)
767    }
768
769    pub fn channel_buffers(&self) -> impl DerefMut<Target = HashSet<Model<ChannelBuffer>>> + '_ {
770        RefMut::map(self.state.borrow_mut(), |state| &mut state.channel_buffers)
771    }
772
773    pub fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
774        self.app_state
775            .user_store
776            .read_with(cx, |store, _| ContactsSummary {
777                current: store
778                    .contacts()
779                    .iter()
780                    .map(|contact| contact.user.github_login.clone())
781                    .collect(),
782                outgoing_requests: store
783                    .outgoing_contact_requests()
784                    .iter()
785                    .map(|user| user.github_login.clone())
786                    .collect(),
787                incoming_requests: store
788                    .incoming_contact_requests()
789                    .iter()
790                    .map(|user| user.github_login.clone())
791                    .collect(),
792            })
793    }
794
795    pub async fn build_local_project(
796        &self,
797        root_path: impl AsRef<Path>,
798        cx: &mut TestAppContext,
799    ) -> (Model<Project>, WorktreeId) {
800        let project = self.build_empty_local_project(cx);
801        let (worktree, _) = project
802            .update(cx, |p, cx| {
803                p.find_or_create_local_worktree(root_path, true, cx)
804            })
805            .await
806            .unwrap();
807        worktree
808            .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
809            .await;
810        (project, worktree.read_with(cx, |tree, _| tree.id()))
811    }
812
813    pub async fn build_test_project(&self, cx: &mut TestAppContext) -> Model<Project> {
814        self.fs()
815            .insert_tree(
816                "/a",
817                json!({
818                    "1.txt": "one\none\none",
819                    "2.js": "function two() { return 2; }",
820                    "3.rs": "mod test",
821                }),
822            )
823            .await;
824        self.build_local_project("/a", cx).await.0
825    }
826
827    pub async fn host_workspace(
828        &self,
829        workspace: &View<Workspace>,
830        channel_id: ChannelId,
831        cx: &mut VisualTestContext,
832    ) {
833        cx.update(|cx| {
834            let active_call = ActiveCall::global(cx);
835            active_call.update(cx, |call, cx| call.join_channel(channel_id, cx))
836        })
837        .await
838        .unwrap();
839        cx.update(|cx| {
840            let active_call = ActiveCall::global(cx);
841            let project = workspace.read(cx).project().clone();
842            active_call.update(cx, |call, cx| call.share_project(project, cx))
843        })
844        .await
845        .unwrap();
846        cx.executor().run_until_parked();
847    }
848
849    pub async fn join_workspace<'a>(
850        &'a self,
851        channel_id: ChannelId,
852        cx: &'a mut TestAppContext,
853    ) -> (View<Workspace>, &'a mut VisualTestContext) {
854        cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
855            .await
856            .unwrap();
857        cx.run_until_parked();
858
859        self.active_workspace(cx)
860    }
861
862    pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model<Project> {
863        cx.update(|cx| {
864            Project::local(
865                self.client().clone(),
866                self.app_state.node_runtime.clone(),
867                self.app_state.user_store.clone(),
868                self.app_state.languages.clone(),
869                self.app_state.fs.clone(),
870                cx,
871            )
872        })
873    }
874
875    pub async fn build_dev_server_project(
876        &self,
877        host_project_id: u64,
878        guest_cx: &mut TestAppContext,
879    ) -> Model<Project> {
880        let active_call = guest_cx.read(ActiveCall::global);
881        let room = active_call.read_with(guest_cx, |call, _| call.room().unwrap().clone());
882        room.update(guest_cx, |room, cx| {
883            room.join_project(
884                host_project_id,
885                self.app_state.languages.clone(),
886                self.app_state.fs.clone(),
887                cx,
888            )
889        })
890        .await
891        .unwrap()
892    }
893
894    pub fn build_workspace<'a>(
895        &'a self,
896        project: &Model<Project>,
897        cx: &'a mut TestAppContext,
898    ) -> (View<Workspace>, &'a mut VisualTestContext) {
899        cx.add_window_view(|cx| {
900            cx.activate_window();
901            Workspace::new(
902                WorkspaceId::default(),
903                project.clone(),
904                self.app_state.clone(),
905                cx,
906            )
907        })
908    }
909
910    pub async fn build_test_workspace<'a>(
911        &'a self,
912        cx: &'a mut TestAppContext,
913    ) -> (View<Workspace>, &'a mut VisualTestContext) {
914        let project = self.build_test_project(cx).await;
915        cx.add_window_view(|cx| {
916            cx.activate_window();
917            Workspace::new(
918                WorkspaceId::default(),
919                project.clone(),
920                self.app_state.clone(),
921                cx,
922            )
923        })
924    }
925
926    pub fn active_workspace<'a>(
927        &'a self,
928        cx: &'a mut TestAppContext,
929    ) -> (View<Workspace>, &'a mut VisualTestContext) {
930        let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
931
932        let view = window.root_view(cx).unwrap();
933        let cx = VisualTestContext::from_window(*window.deref(), cx).as_mut();
934        // it might be nice to try and cleanup these at the end of each test.
935        (view, cx)
936    }
937}
938
939pub fn open_channel_notes(
940    channel_id: ChannelId,
941    cx: &mut VisualTestContext,
942) -> Task<anyhow::Result<View<ChannelView>>> {
943    let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
944    let view = window.root_view(cx).unwrap();
945
946    cx.update(|cx| ChannelView::open(channel_id, None, view.clone(), cx))
947}
948
949impl Drop for TestClient {
950    fn drop(&mut self) {
951        self.app_state.client.teardown();
952    }
953}