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