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            },
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 dev_server_projects(&self) -> impl Deref<Target = Vec<Model<Project>>> + '_ {
739        Ref::map(self.state.borrow(), |state| &state.dev_server_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 dev_server_projects_mut(&self) -> impl DerefMut<Target = Vec<Model<Project>>> + '_ {
747        RefMut::map(self.state.borrow_mut(), |state| {
748            &mut state.dev_server_projects
749        })
750    }
751
752    pub fn buffers_for_project<'a>(
753        &'a self,
754        project: &Model<Project>,
755    ) -> impl DerefMut<Target = HashSet<Model<language::Buffer>>> + 'a {
756        RefMut::map(self.state.borrow_mut(), |state| {
757            state.buffers.entry(project.clone()).or_default()
758        })
759    }
760
761    pub fn buffers(
762        &self,
763    ) -> impl DerefMut<Target = HashMap<Model<Project>, HashSet<Model<language::Buffer>>>> + '_
764    {
765        RefMut::map(self.state.borrow_mut(), |state| &mut state.buffers)
766    }
767
768    pub fn channel_buffers(&self) -> impl DerefMut<Target = HashSet<Model<ChannelBuffer>>> + '_ {
769        RefMut::map(self.state.borrow_mut(), |state| &mut state.channel_buffers)
770    }
771
772    pub fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
773        self.app_state
774            .user_store
775            .read_with(cx, |store, _| ContactsSummary {
776                current: store
777                    .contacts()
778                    .iter()
779                    .map(|contact| contact.user.github_login.clone())
780                    .collect(),
781                outgoing_requests: store
782                    .outgoing_contact_requests()
783                    .iter()
784                    .map(|user| user.github_login.clone())
785                    .collect(),
786                incoming_requests: store
787                    .incoming_contact_requests()
788                    .iter()
789                    .map(|user| user.github_login.clone())
790                    .collect(),
791            })
792    }
793
794    pub async fn build_local_project(
795        &self,
796        root_path: impl AsRef<Path>,
797        cx: &mut TestAppContext,
798    ) -> (Model<Project>, WorktreeId) {
799        let project = self.build_empty_local_project(cx);
800        let (worktree, _) = project
801            .update(cx, |p, cx| {
802                p.find_or_create_local_worktree(root_path, true, cx)
803            })
804            .await
805            .unwrap();
806        worktree
807            .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
808            .await;
809        (project, worktree.read_with(cx, |tree, _| tree.id()))
810    }
811
812    pub async fn build_test_project(&self, cx: &mut TestAppContext) -> Model<Project> {
813        self.fs()
814            .insert_tree(
815                "/a",
816                json!({
817                    "1.txt": "one\none\none",
818                    "2.js": "function two() { return 2; }",
819                    "3.rs": "mod test",
820                }),
821            )
822            .await;
823        self.build_local_project("/a", cx).await.0
824    }
825
826    pub async fn host_workspace(
827        &self,
828        workspace: &View<Workspace>,
829        channel_id: ChannelId,
830        cx: &mut VisualTestContext,
831    ) {
832        cx.update(|cx| {
833            let active_call = ActiveCall::global(cx);
834            active_call.update(cx, |call, cx| call.join_channel(channel_id, cx))
835        })
836        .await
837        .unwrap();
838        cx.update(|cx| {
839            let active_call = ActiveCall::global(cx);
840            let project = workspace.read(cx).project().clone();
841            active_call.update(cx, |call, cx| call.share_project(project, cx))
842        })
843        .await
844        .unwrap();
845        cx.executor().run_until_parked();
846    }
847
848    pub async fn join_workspace<'a>(
849        &'a self,
850        channel_id: ChannelId,
851        cx: &'a mut TestAppContext,
852    ) -> (View<Workspace>, &'a mut VisualTestContext) {
853        cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
854            .await
855            .unwrap();
856        cx.run_until_parked();
857
858        self.active_workspace(cx)
859    }
860
861    pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model<Project> {
862        cx.update(|cx| {
863            Project::local(
864                self.client().clone(),
865                self.app_state.node_runtime.clone(),
866                self.app_state.user_store.clone(),
867                self.app_state.languages.clone(),
868                self.app_state.fs.clone(),
869                cx,
870            )
871        })
872    }
873
874    pub async fn build_dev_server_project(
875        &self,
876        host_project_id: u64,
877        guest_cx: &mut TestAppContext,
878    ) -> Model<Project> {
879        let active_call = guest_cx.read(ActiveCall::global);
880        let room = active_call.read_with(guest_cx, |call, _| call.room().unwrap().clone());
881        room.update(guest_cx, |room, cx| {
882            room.join_project(
883                host_project_id,
884                self.app_state.languages.clone(),
885                self.app_state.fs.clone(),
886                cx,
887            )
888        })
889        .await
890        .unwrap()
891    }
892
893    pub fn build_workspace<'a>(
894        &'a self,
895        project: &Model<Project>,
896        cx: &'a mut TestAppContext,
897    ) -> (View<Workspace>, &'a mut VisualTestContext) {
898        cx.add_window_view(|cx| {
899            cx.activate_window();
900            Workspace::new(
901                WorkspaceId::default(),
902                project.clone(),
903                self.app_state.clone(),
904                cx,
905            )
906        })
907    }
908
909    pub async fn build_test_workspace<'a>(
910        &'a self,
911        cx: &'a mut TestAppContext,
912    ) -> (View<Workspace>, &'a mut VisualTestContext) {
913        let project = self.build_test_project(cx).await;
914        cx.add_window_view(|cx| {
915            cx.activate_window();
916            Workspace::new(
917                WorkspaceId::default(),
918                project.clone(),
919                self.app_state.clone(),
920                cx,
921            )
922        })
923    }
924
925    pub fn active_workspace<'a>(
926        &'a self,
927        cx: &'a mut TestAppContext,
928    ) -> (View<Workspace>, &'a mut VisualTestContext) {
929        let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
930
931        let view = window.root_view(cx).unwrap();
932        let cx = VisualTestContext::from_window(*window.deref(), cx).as_mut();
933        // it might be nice to try and cleanup these at the end of each test.
934        (view, cx)
935    }
936}
937
938pub fn open_channel_notes(
939    channel_id: ChannelId,
940    cx: &mut VisualTestContext,
941) -> Task<anyhow::Result<View<ChannelView>>> {
942    let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
943    let view = window.root_view(cx).unwrap();
944
945    cx.update(|cx| ChannelView::open(channel_id, None, view.clone(), cx))
946}
947
948impl Drop for TestClient {
949    fn drop(&mut self) {
950        self.app_state.client.teardown();
951    }
952}