test_server.rs

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