context_server_store.rs

  1use anyhow::Result;
  2use context_server::test::create_fake_transport;
  3use context_server::{ContextServer, ContextServerId};
  4use gpui::{AppContext, AsyncApp, Entity, Subscription, Task, TestAppContext, UpdateGlobal as _};
  5use http_client::{FakeHttpClient, Response};
  6use project::context_server_store::registry::ContextServerDescriptorRegistry;
  7use project::context_server_store::*;
  8use project::project_settings::ContextServerSettings;
  9use project::worktree_store::WorktreeStore;
 10use project::{
 11    FakeFs, Project, context_server_store::registry::ContextServerDescriptor,
 12    project_settings::ProjectSettings,
 13};
 14use serde_json::json;
 15use settings::{ContextServerCommand, Settings, SettingsStore};
 16use std::sync::Arc;
 17use std::{cell::RefCell, path::PathBuf, rc::Rc};
 18use util::path;
 19
 20#[gpui::test]
 21async fn test_context_server_status(cx: &mut TestAppContext) {
 22    const SERVER_1_ID: &str = "mcp-1";
 23    const SERVER_2_ID: &str = "mcp-2";
 24
 25    let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
 26
 27    let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
 28    let store = cx.new(|cx| {
 29        ContextServerStore::test(
 30            registry.clone(),
 31            project.read(cx).worktree_store(),
 32            Some(project.downgrade()),
 33            cx,
 34        )
 35    });
 36
 37    let server_1_id = ContextServerId(SERVER_1_ID.into());
 38    let server_2_id = ContextServerId(SERVER_2_ID.into());
 39
 40    let server_1 = Arc::new(ContextServer::new(
 41        server_1_id.clone(),
 42        Arc::new(create_fake_transport(SERVER_1_ID, cx.executor())),
 43    ));
 44    let server_2 = Arc::new(ContextServer::new(
 45        server_2_id.clone(),
 46        Arc::new(create_fake_transport(SERVER_2_ID, cx.executor())),
 47    ));
 48
 49    store.update(cx, |store, cx| store.test_start_server(server_1, cx));
 50
 51    cx.run_until_parked();
 52
 53    cx.update(|cx| {
 54        assert_eq!(
 55            store.read(cx).status_for_server(&server_1_id),
 56            Some(ContextServerStatus::Running)
 57        );
 58        assert_eq!(store.read(cx).status_for_server(&server_2_id), None);
 59    });
 60
 61    store.update(cx, |store, cx| {
 62        store.test_start_server(server_2.clone(), cx)
 63    });
 64
 65    cx.run_until_parked();
 66
 67    cx.update(|cx| {
 68        assert_eq!(
 69            store.read(cx).status_for_server(&server_1_id),
 70            Some(ContextServerStatus::Running)
 71        );
 72        assert_eq!(
 73            store.read(cx).status_for_server(&server_2_id),
 74            Some(ContextServerStatus::Running)
 75        );
 76    });
 77
 78    store
 79        .update(cx, |store, cx| store.stop_server(&server_2_id, cx))
 80        .unwrap();
 81
 82    cx.update(|cx| {
 83        assert_eq!(
 84            store.read(cx).status_for_server(&server_1_id),
 85            Some(ContextServerStatus::Running)
 86        );
 87        assert_eq!(
 88            store.read(cx).status_for_server(&server_2_id),
 89            Some(ContextServerStatus::Stopped)
 90        );
 91    });
 92}
 93
 94#[gpui::test]
 95async fn test_context_server_status_events(cx: &mut TestAppContext) {
 96    const SERVER_1_ID: &str = "mcp-1";
 97    const SERVER_2_ID: &str = "mcp-2";
 98
 99    let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
100
101    let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
102    let store = cx.new(|cx| {
103        ContextServerStore::test(
104            registry.clone(),
105            project.read(cx).worktree_store(),
106            Some(project.downgrade()),
107            cx,
108        )
109    });
110
111    let server_1_id = ContextServerId(SERVER_1_ID.into());
112    let server_2_id = ContextServerId(SERVER_2_ID.into());
113
114    let server_1 = Arc::new(ContextServer::new(
115        server_1_id.clone(),
116        Arc::new(create_fake_transport(SERVER_1_ID, cx.executor())),
117    ));
118    let server_2 = Arc::new(ContextServer::new(
119        server_2_id.clone(),
120        Arc::new(create_fake_transport(SERVER_2_ID, cx.executor())),
121    ));
122
123    let _server_events = assert_server_events(
124        &store,
125        vec![
126            (server_1_id.clone(), ContextServerStatus::Starting),
127            (server_1_id, ContextServerStatus::Running),
128            (server_2_id.clone(), ContextServerStatus::Starting),
129            (server_2_id.clone(), ContextServerStatus::Running),
130            (server_2_id.clone(), ContextServerStatus::Stopped),
131        ],
132        cx,
133    );
134
135    store.update(cx, |store, cx| store.test_start_server(server_1, cx));
136
137    cx.run_until_parked();
138
139    store.update(cx, |store, cx| {
140        store.test_start_server(server_2.clone(), cx)
141    });
142
143    cx.run_until_parked();
144
145    store
146        .update(cx, |store, cx| store.stop_server(&server_2_id, cx))
147        .unwrap();
148}
149
150#[gpui::test(iterations = 25)]
151async fn test_context_server_concurrent_starts(cx: &mut TestAppContext) {
152    const SERVER_1_ID: &str = "mcp-1";
153
154    let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
155
156    let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
157    let store = cx.new(|cx| {
158        ContextServerStore::test(
159            registry.clone(),
160            project.read(cx).worktree_store(),
161            Some(project.downgrade()),
162            cx,
163        )
164    });
165
166    let server_id = ContextServerId(SERVER_1_ID.into());
167
168    let server_with_same_id_1 = Arc::new(ContextServer::new(
169        server_id.clone(),
170        Arc::new(create_fake_transport(SERVER_1_ID, cx.executor())),
171    ));
172    let server_with_same_id_2 = Arc::new(ContextServer::new(
173        server_id.clone(),
174        Arc::new(create_fake_transport(SERVER_1_ID, cx.executor())),
175    ));
176
177    // If we start another server with the same id, we should report that we stopped the previous one
178    let _server_events = assert_server_events(
179        &store,
180        vec![
181            (server_id.clone(), ContextServerStatus::Starting),
182            (server_id.clone(), ContextServerStatus::Stopped),
183            (server_id.clone(), ContextServerStatus::Starting),
184            (server_id.clone(), ContextServerStatus::Running),
185        ],
186        cx,
187    );
188
189    store.update(cx, |store, cx| {
190        store.test_start_server(server_with_same_id_1.clone(), cx)
191    });
192    store.update(cx, |store, cx| {
193        store.test_start_server(server_with_same_id_2.clone(), cx)
194    });
195
196    cx.run_until_parked();
197
198    cx.update(|cx| {
199        assert_eq!(
200            store.read(cx).status_for_server(&server_id),
201            Some(ContextServerStatus::Running)
202        );
203    });
204}
205
206#[gpui::test]
207async fn test_context_server_maintain_servers_loop(cx: &mut TestAppContext) {
208    const SERVER_1_ID: &str = "mcp-1";
209    const SERVER_2_ID: &str = "mcp-2";
210
211    let server_1_id = ContextServerId(SERVER_1_ID.into());
212    let server_2_id = ContextServerId(SERVER_2_ID.into());
213
214    let fake_descriptor_1 = Arc::new(FakeContextServerDescriptor::new(SERVER_1_ID));
215
216    let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
217
218    let executor = cx.executor();
219    let store = project.read_with(cx, |project, _| project.context_server_store());
220    store.update(cx, |store, cx| {
221        store.set_context_server_factory(Box::new(move |id, _| {
222            Arc::new(ContextServer::new(
223                id.clone(),
224                Arc::new(create_fake_transport(id.0.to_string(), executor.clone())),
225            ))
226        }));
227        store.registry().update(cx, |registry, cx| {
228            registry.register_context_server_descriptor(SERVER_1_ID.into(), fake_descriptor_1, cx);
229        });
230    });
231
232    set_context_server_configuration(
233        vec![(
234            server_1_id.0.clone(),
235            settings::ContextServerSettingsContent::Extension {
236                enabled: true,
237                remote: false,
238                settings: json!({
239                    "somevalue": true
240                }),
241            },
242        )],
243        cx,
244    );
245
246    // Ensure that mcp-1 starts up
247    {
248        let _server_events = assert_server_events(
249            &store,
250            vec![
251                (server_1_id.clone(), ContextServerStatus::Starting),
252                (server_1_id.clone(), ContextServerStatus::Running),
253            ],
254            cx,
255        );
256        cx.run_until_parked();
257    }
258
259    // Ensure that mcp-1 is restarted when the configuration was changed
260    {
261        let _server_events = assert_server_events(
262            &store,
263            vec![
264                (server_1_id.clone(), ContextServerStatus::Stopped),
265                (server_1_id.clone(), ContextServerStatus::Starting),
266                (server_1_id.clone(), ContextServerStatus::Running),
267            ],
268            cx,
269        );
270        set_context_server_configuration(
271            vec![(
272                server_1_id.0.clone(),
273                settings::ContextServerSettingsContent::Extension {
274                    enabled: true,
275                    remote: false,
276                    settings: json!({
277                        "somevalue": false
278                    }),
279                },
280            )],
281            cx,
282        );
283
284        cx.run_until_parked();
285    }
286
287    // Ensure that mcp-1 is not restarted when the configuration was not changed
288    {
289        let _server_events = assert_server_events(&store, vec![], cx);
290        set_context_server_configuration(
291            vec![(
292                server_1_id.0.clone(),
293                settings::ContextServerSettingsContent::Extension {
294                    enabled: true,
295                    remote: false,
296                    settings: json!({
297                        "somevalue": false
298                    }),
299                },
300            )],
301            cx,
302        );
303
304        cx.run_until_parked();
305    }
306
307    // Ensure that mcp-2 is started once it is added to the settings
308    {
309        let _server_events = assert_server_events(
310            &store,
311            vec![
312                (server_2_id.clone(), ContextServerStatus::Starting),
313                (server_2_id.clone(), ContextServerStatus::Running),
314            ],
315            cx,
316        );
317        set_context_server_configuration(
318            vec![
319                (
320                    server_1_id.0.clone(),
321                    settings::ContextServerSettingsContent::Extension {
322                        enabled: true,
323                        remote: false,
324                        settings: json!({
325                            "somevalue": false
326                        }),
327                    },
328                ),
329                (
330                    server_2_id.0.clone(),
331                    settings::ContextServerSettingsContent::Stdio {
332                        enabled: true,
333                        remote: false,
334                        command: ContextServerCommand {
335                            path: "somebinary".into(),
336                            args: vec!["arg".to_string()],
337                            env: None,
338                            timeout: None,
339                        },
340                    },
341                ),
342            ],
343            cx,
344        );
345
346        cx.run_until_parked();
347    }
348
349    // Ensure that mcp-2 is restarted once the args have changed
350    {
351        let _server_events = assert_server_events(
352            &store,
353            vec![
354                (server_2_id.clone(), ContextServerStatus::Stopped),
355                (server_2_id.clone(), ContextServerStatus::Starting),
356                (server_2_id.clone(), ContextServerStatus::Running),
357            ],
358            cx,
359        );
360        set_context_server_configuration(
361            vec![
362                (
363                    server_1_id.0.clone(),
364                    settings::ContextServerSettingsContent::Extension {
365                        enabled: true,
366                        remote: false,
367                        settings: json!({
368                            "somevalue": false
369                        }),
370                    },
371                ),
372                (
373                    server_2_id.0.clone(),
374                    settings::ContextServerSettingsContent::Stdio {
375                        enabled: true,
376                        remote: false,
377                        command: ContextServerCommand {
378                            path: "somebinary".into(),
379                            args: vec!["anotherArg".to_string()],
380                            env: None,
381                            timeout: None,
382                        },
383                    },
384                ),
385            ],
386            cx,
387        );
388
389        cx.run_until_parked();
390    }
391
392    // Ensure that mcp-2 is removed once it is removed from the settings
393    {
394        let _server_events = assert_server_events(
395            &store,
396            vec![(server_2_id.clone(), ContextServerStatus::Stopped)],
397            cx,
398        );
399        set_context_server_configuration(
400            vec![(
401                server_1_id.0.clone(),
402                settings::ContextServerSettingsContent::Extension {
403                    enabled: true,
404                    remote: false,
405                    settings: json!({
406                        "somevalue": false
407                    }),
408                },
409            )],
410            cx,
411        );
412
413        cx.run_until_parked();
414
415        cx.update(|cx| {
416            assert_eq!(store.read(cx).status_for_server(&server_2_id), None);
417        });
418    }
419
420    // Ensure that nothing happens if the settings do not change
421    {
422        let _server_events = assert_server_events(&store, vec![], cx);
423        set_context_server_configuration(
424            vec![(
425                server_1_id.0.clone(),
426                settings::ContextServerSettingsContent::Extension {
427                    enabled: true,
428                    remote: false,
429                    settings: json!({
430                        "somevalue": false
431                    }),
432                },
433            )],
434            cx,
435        );
436
437        cx.run_until_parked();
438
439        cx.update(|cx| {
440            assert_eq!(
441                store.read(cx).status_for_server(&server_1_id),
442                Some(ContextServerStatus::Running)
443            );
444            assert_eq!(store.read(cx).status_for_server(&server_2_id), None);
445        });
446    }
447}
448
449#[gpui::test]
450async fn test_context_server_enabled_disabled(cx: &mut TestAppContext) {
451    const SERVER_1_ID: &str = "mcp-1";
452
453    let server_1_id = ContextServerId(SERVER_1_ID.into());
454
455    let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
456
457    let executor = cx.executor();
458    let store = project.read_with(cx, |project, _| project.context_server_store());
459    store.update(cx, |store, _| {
460        store.set_context_server_factory(Box::new(move |id, _| {
461            Arc::new(ContextServer::new(
462                id.clone(),
463                Arc::new(create_fake_transport(id.0.to_string(), executor.clone())),
464            ))
465        }));
466    });
467
468    set_context_server_configuration(
469        vec![(
470            server_1_id.0.clone(),
471            settings::ContextServerSettingsContent::Stdio {
472                enabled: true,
473                remote: false,
474                command: ContextServerCommand {
475                    path: "somebinary".into(),
476                    args: vec!["arg".to_string()],
477                    env: None,
478                    timeout: None,
479                },
480            },
481        )],
482        cx,
483    );
484
485    // Ensure that mcp-1 starts up
486    {
487        let _server_events = assert_server_events(
488            &store,
489            vec![
490                (server_1_id.clone(), ContextServerStatus::Starting),
491                (server_1_id.clone(), ContextServerStatus::Running),
492            ],
493            cx,
494        );
495        cx.run_until_parked();
496    }
497
498    // Ensure that mcp-1 is stopped once it is disabled.
499    {
500        let _server_events = assert_server_events(
501            &store,
502            vec![(server_1_id.clone(), ContextServerStatus::Stopped)],
503            cx,
504        );
505        set_context_server_configuration(
506            vec![(
507                server_1_id.0.clone(),
508                settings::ContextServerSettingsContent::Stdio {
509                    enabled: false,
510                    remote: false,
511                    command: ContextServerCommand {
512                        path: "somebinary".into(),
513                        args: vec!["arg".to_string()],
514                        env: None,
515                        timeout: None,
516                    },
517                },
518            )],
519            cx,
520        );
521
522        cx.run_until_parked();
523    }
524
525    // Ensure that mcp-1 is started once it is enabled again.
526    {
527        let _server_events = assert_server_events(
528            &store,
529            vec![
530                (server_1_id.clone(), ContextServerStatus::Starting),
531                (server_1_id.clone(), ContextServerStatus::Running),
532            ],
533            cx,
534        );
535        set_context_server_configuration(
536            vec![(
537                server_1_id.0.clone(),
538                settings::ContextServerSettingsContent::Stdio {
539                    enabled: true,
540                    remote: false,
541                    command: ContextServerCommand {
542                        path: "somebinary".into(),
543                        args: vec!["arg".to_string()],
544                        timeout: None,
545                        env: None,
546                    },
547                },
548            )],
549            cx,
550        );
551
552        cx.run_until_parked();
553    }
554}
555
556fn set_context_server_configuration(
557    context_servers: Vec<(Arc<str>, settings::ContextServerSettingsContent)>,
558    cx: &mut TestAppContext,
559) {
560    cx.update(|cx| {
561        SettingsStore::update_global(cx, |store, cx| {
562            store.update_user_settings(cx, |content| {
563                content.project.context_servers.clear();
564                for (id, config) in context_servers {
565                    content.project.context_servers.insert(id, config);
566                }
567            });
568        })
569    });
570}
571
572#[gpui::test]
573async fn test_remote_context_server(cx: &mut TestAppContext) {
574    const SERVER_ID: &str = "remote-server";
575    let server_id = ContextServerId(SERVER_ID.into());
576    let server_url = "http://example.com/api";
577
578    let client = FakeHttpClient::create(|_| async move {
579        use http_client::AsyncBody;
580
581        let response = Response::builder()
582            .status(200)
583            .header("Content-Type", "application/json")
584            .body(AsyncBody::from(
585                serde_json::to_string(&json!({
586                    "jsonrpc": "2.0",
587                    "id": 0,
588                    "result": {
589                        "protocolVersion": "2024-11-05",
590                        "capabilities": {},
591                        "serverInfo": {
592                            "name": "test-server",
593                            "version": "1.0.0"
594                        }
595                    }
596                }))
597                .unwrap(),
598            ))
599            .unwrap();
600        Ok(response)
601    });
602    cx.update(|cx| cx.set_http_client(client));
603
604    let (_fs, project) = setup_context_server_test(cx, json!({ "code.rs": "" }), vec![]).await;
605
606    let store = project.read_with(cx, |project, _| project.context_server_store());
607
608    set_context_server_configuration(
609        vec![(
610            server_id.0.clone(),
611            settings::ContextServerSettingsContent::Http {
612                enabled: true,
613                url: server_url.to_string(),
614                headers: Default::default(),
615                timeout: None,
616            },
617        )],
618        cx,
619    );
620
621    let _server_events = assert_server_events(
622        &store,
623        vec![
624            (server_id.clone(), ContextServerStatus::Starting),
625            (server_id.clone(), ContextServerStatus::Running),
626        ],
627        cx,
628    );
629    cx.run_until_parked();
630}
631
632struct ServerEvents {
633    received_event_count: Rc<RefCell<usize>>,
634    expected_event_count: usize,
635    _subscription: Subscription,
636}
637
638impl Drop for ServerEvents {
639    fn drop(&mut self) {
640        let actual_event_count = *self.received_event_count.borrow();
641        assert_eq!(
642            actual_event_count, self.expected_event_count,
643            "
644               Expected to receive {} context server store events, but received {} events",
645            self.expected_event_count, actual_event_count
646        );
647    }
648}
649
650#[gpui::test]
651async fn test_context_server_global_timeout(cx: &mut TestAppContext) {
652    cx.update(|cx| {
653        let settings_store = SettingsStore::test(cx);
654        cx.set_global(settings_store);
655        SettingsStore::update_global(cx, |store, cx| {
656            store
657                .set_user_settings(r#"{"context_server_timeout": 90}"#, cx)
658                .expect("Failed to set test user settings");
659        });
660    });
661
662    let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
663
664    let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
665    let store = cx.new(|cx| {
666        ContextServerStore::test(
667            registry.clone(),
668            project.read(cx).worktree_store(),
669            Some(project.downgrade()),
670            cx,
671        )
672    });
673
674    let mut async_cx = cx.to_async();
675    let result = ContextServerStore::create_context_server(
676        store.downgrade(),
677        ContextServerId("test-server".into()),
678        Arc::new(ContextServerConfiguration::Http {
679            url: url::Url::parse("http://localhost:8080").expect("Failed to parse test URL"),
680            headers: Default::default(),
681            timeout: None,
682        }),
683        &mut async_cx,
684    )
685    .await;
686
687    assert!(
688        result.is_ok(),
689        "Server should be created successfully with global timeout"
690    );
691}
692
693#[gpui::test]
694async fn test_context_server_per_server_timeout_override(cx: &mut TestAppContext) {
695    const SERVER_ID: &str = "test-server";
696
697    cx.update(|cx| {
698        let settings_store = SettingsStore::test(cx);
699        cx.set_global(settings_store);
700        SettingsStore::update_global(cx, |store, cx| {
701            store
702                .set_user_settings(r#"{"context_server_timeout": 60}"#, cx)
703                .expect("Failed to set test user settings");
704        });
705    });
706
707    let (_fs, project) = setup_context_server_test(
708        cx,
709        json!({"code.rs": ""}),
710        vec![(
711            SERVER_ID.into(),
712            ContextServerSettings::Http {
713                enabled: true,
714                url: "http://localhost:8080".to_string(),
715                headers: Default::default(),
716                timeout: Some(120),
717            },
718        )],
719    )
720    .await;
721
722    let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
723    let store = cx.new(|cx| {
724        ContextServerStore::test(
725            registry.clone(),
726            project.read(cx).worktree_store(),
727            Some(project.downgrade()),
728            cx,
729        )
730    });
731
732    let mut async_cx = cx.to_async();
733    let result = ContextServerStore::create_context_server(
734        store.downgrade(),
735        ContextServerId("test-server".into()),
736        Arc::new(ContextServerConfiguration::Http {
737            url: url::Url::parse("http://localhost:8080").expect("Failed to parse test URL"),
738            headers: Default::default(),
739            timeout: Some(120),
740        }),
741        &mut async_cx,
742    )
743    .await;
744
745    assert!(
746        result.is_ok(),
747        "Server should be created successfully with per-server timeout override"
748    );
749}
750
751#[gpui::test]
752async fn test_context_server_stdio_timeout(cx: &mut TestAppContext) {
753    let (_fs, project) = setup_context_server_test(cx, json!({"code.rs": ""}), vec![]).await;
754
755    let registry = cx.new(|_| ContextServerDescriptorRegistry::new());
756    let store = cx.new(|cx| {
757        ContextServerStore::test(
758            registry.clone(),
759            project.read(cx).worktree_store(),
760            Some(project.downgrade()),
761            cx,
762        )
763    });
764
765    let mut async_cx = cx.to_async();
766    let result = ContextServerStore::create_context_server(
767        store.downgrade(),
768        ContextServerId("stdio-server".into()),
769        Arc::new(ContextServerConfiguration::Custom {
770            command: ContextServerCommand {
771                path: "/usr/bin/node".into(),
772                args: vec!["server.js".into()],
773                env: None,
774                timeout: Some(180000),
775            },
776            remote: false,
777        }),
778        &mut async_cx,
779    )
780    .await;
781
782    assert!(
783        result.is_ok(),
784        "Stdio server should be created successfully with timeout"
785    );
786}
787
788fn assert_server_events(
789    store: &Entity<ContextServerStore>,
790    expected_events: Vec<(ContextServerId, ContextServerStatus)>,
791    cx: &mut TestAppContext,
792) -> ServerEvents {
793    cx.update(|cx| {
794        let mut ix = 0;
795        let received_event_count = Rc::new(RefCell::new(0));
796        let expected_event_count = expected_events.len();
797        let subscription = cx.subscribe(store, {
798            let received_event_count = received_event_count.clone();
799            move |_, event, _| match event {
800                Event::ServerStatusChanged {
801                    server_id: actual_server_id,
802                    status: actual_status,
803                } => {
804                    let (expected_server_id, expected_status) = &expected_events[ix];
805
806                    assert_eq!(
807                        actual_server_id, expected_server_id,
808                        "Expected different server id at index {}",
809                        ix
810                    );
811                    assert_eq!(
812                        actual_status, expected_status,
813                        "Expected different status at index {}",
814                        ix
815                    );
816                    ix += 1;
817                    *received_event_count.borrow_mut() += 1;
818                }
819            }
820        });
821        ServerEvents {
822            expected_event_count,
823            received_event_count,
824            _subscription: subscription,
825        }
826    })
827}
828
829async fn setup_context_server_test(
830    cx: &mut TestAppContext,
831    files: serde_json::Value,
832    context_server_configurations: Vec<(Arc<str>, ContextServerSettings)>,
833) -> (Arc<FakeFs>, Entity<Project>) {
834    cx.update(|cx| {
835        let settings_store = SettingsStore::test(cx);
836        cx.set_global(settings_store);
837        let mut settings = ProjectSettings::get_global(cx).clone();
838        for (id, config) in context_server_configurations {
839            settings.context_servers.insert(id, config);
840        }
841        ProjectSettings::override_global(settings, cx);
842    });
843
844    let fs = FakeFs::new(cx.executor());
845    fs.insert_tree(path!("/test"), files).await;
846    let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
847
848    (fs, project)
849}
850
851struct FakeContextServerDescriptor {
852    path: PathBuf,
853}
854
855impl FakeContextServerDescriptor {
856    fn new(path: impl Into<PathBuf>) -> Self {
857        Self { path: path.into() }
858    }
859}
860
861impl ContextServerDescriptor for FakeContextServerDescriptor {
862    fn command(
863        &self,
864        _worktree_store: Entity<WorktreeStore>,
865        _cx: &AsyncApp,
866    ) -> Task<Result<ContextServerCommand>> {
867        Task::ready(Ok(ContextServerCommand {
868            path: self.path.clone(),
869            args: vec!["arg1".to_string(), "arg2".to_string()],
870            env: None,
871            timeout: None,
872        }))
873    }
874
875    fn configuration(
876        &self,
877        _worktree_store: Entity<WorktreeStore>,
878        _cx: &AsyncApp,
879    ) -> Task<Result<Option<::extension::ContextServerConfiguration>>> {
880        Task::ready(Ok(None))
881    }
882}