1use std::any::Any;
7use std::future::Future;
8use std::sync::Arc;
9use tower_lsp_server::ls_types::{CompletionItem, Position, Uri};
10
11use deps_core::{
12 Ecosystem, ParseResult as ParseResultTrait, Registry, Result, completion::Completions,
13 lsp_helpers::EcosystemFormatter,
14};
15
16use crate::config::GoParseContext;
17use crate::formatter::GoFormatter;
18use crate::registry::GoRegistry;
19
20pub struct GoEcosystem {
30 registry: Arc<GoRegistry>,
31 formatter: GoFormatter,
32 context: GoParseContext,
39}
40
41impl GoEcosystem {
42 pub fn new(cache: Arc<deps_core::HttpCache>) -> Self {
46 Self::with_context(Arc::new(GoRegistry::new(cache)), GoParseContext::default())
47 }
48
49 #[must_use]
55 pub fn with_context(registry: Arc<GoRegistry>, ctx: GoParseContext) -> Self {
56 Self {
57 registry,
58 formatter: GoFormatter,
59 context: ctx,
60 }
61 }
62
63 fn complete_package_names(&self, _prefix: &str) -> impl Future<Output = Vec<CompletionItem>> {
74 std::future::ready(vec![])
77 }
78
79 async fn complete_versions(
95 &self,
96 parse_result: &dyn ParseResultTrait,
97 position: Position,
98 prefix: &str,
99 freshness: deps_core::FreshnessSettings,
100 ) -> Vec<CompletionItem> {
101 deps_core::completion::complete_versions_at_position(
102 self.registry.as_ref(),
103 &self.formatter,
104 parse_result,
105 position,
106 prefix,
107 &[],
108 freshness,
109 )
110 .await
111 }
112
113 fn complete_features(
118 &self,
119 _package_name: &deps_core::PackageName,
120 _prefix: &str,
121 ) -> impl Future<Output = Vec<CompletionItem>> {
122 std::future::ready(vec![])
124 }
125}
126
127impl deps_core::ecosystem::private::Sealed for GoEcosystem {}
128
129impl Ecosystem for GoEcosystem {
130 fn id(&self) -> &'static str {
131 "go"
132 }
133
134 fn display_name(&self) -> &'static str {
135 "Go Modules"
136 }
137
138 fn manifest_filenames(&self) -> &[&'static str] {
139 &["go.mod"]
140 }
141
142 fn lockfile_filenames(&self) -> &[&'static str] {
143 &["go.sum"]
144 }
145
146 fn parse_manifest<'a>(
147 &'a self,
148 content: &'a str,
149 uri: &'a Uri,
150 ) -> deps_core::ecosystem::BoxFuture<'a, Result<Box<dyn ParseResultTrait>>> {
151 Box::pin(async move {
152 let result = crate::parser::parse_go_mod_with_context(content, uri, &self.context)?;
153 for chain in &result.resolved_chains {
158 GoRegistry::register_chain(&self.registry, chain);
159 }
160 Ok(Box::new(result) as Box<dyn ParseResultTrait>)
161 })
162 }
163
164 fn registry(&self) -> Arc<dyn Registry> {
165 self.registry.clone() as Arc<dyn Registry>
166 }
167
168 fn lockfile_provider(&self) -> Option<Arc<dyn deps_core::lockfile::LockFileProvider>> {
169 Some(Arc::new(crate::lockfile::GoSumParser))
170 }
171
172 fn formatter(&self) -> &dyn EcosystemFormatter {
173 &self.formatter
174 }
175
176 fn generate_completions<'a>(
177 &'a self,
178 parse_result: &'a dyn ParseResultTrait,
179 position: Position,
180 content: &'a str,
181 freshness: deps_core::FreshnessSettings,
182 ) -> deps_core::ecosystem::BoxFuture<'a, Completions> {
183 Box::pin(async move {
184 use deps_core::completion::{CompletionContext, detect_completion_context};
185
186 let context = detect_completion_context(parse_result, position, content);
187
188 match context {
189 CompletionContext::PackageName { prefix, .. } => {
190 self.complete_package_names(&prefix).await
191 }
192 CompletionContext::Version { prefix, .. } => {
193 self.complete_versions(parse_result, position, &prefix, freshness)
194 .await
195 }
196 CompletionContext::Feature {
197 package_name,
198 prefix,
199 } => self.complete_features(&package_name, &prefix).await,
200 CompletionContext::None => vec![],
201 }
202 .into()
203 })
204 }
205
206 fn as_any(&self) -> &dyn Any {
207 self
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::types::{GoDependency, GoDirective};
215 use deps_core::{
216 Dependency, EcosystemConfig, PackageVersions, VersionData, parser::DependencySource,
217 };
218 use std::collections::HashMap;
219 use tower_lsp_server::ls_types::{InlayHintLabel, Position, Range};
220
221 fn pkg(s: &str) -> deps_core::PackageName {
222 deps_core::PackageName::new(s)
223 }
224
225 fn mock_dependency(name: &str, version: Option<&str>, line: u32) -> GoDependency {
227 GoDependency {
228 module_path: name.into(),
229 module_path_range: Range::new(
230 Position::new(line, 0),
231 Position::new(line, name.len() as u32),
232 ),
233 version: version.map(Into::into),
234 version_range: version
235 .map(|_| Range::new(Position::new(line, 0), Position::new(line, 10))),
236 directive: GoDirective::Require,
237 indirect: false,
238 source: deps_core::parser::DependencySource::Registry,
239 }
240 }
241
242 struct MockParseResult {
244 dependencies: Vec<GoDependency>,
245 uri: Uri,
246 }
247
248 fn dep_with_source(name: &str, source: DependencySource, line: u32) -> GoDependency {
251 GoDependency {
252 module_path: pkg(name),
253 module_path_range: Range::new(Position::new(line, 0), Position::new(line, 0)),
254 version: None,
255 version_range: Some(Range::new(Position::new(line, 0), Position::new(line, 10))),
256 directive: GoDirective::Require,
257 indirect: false,
258 source,
259 }
260 }
261
262 impl deps_core::ParseResult for MockParseResult {
263 fn dependencies(&self) -> Vec<&dyn deps_core::Dependency> {
264 self.dependencies
265 .iter()
266 .map(|d| d as &dyn deps_core::Dependency)
267 .collect()
268 }
269
270 fn workspace_root(&self) -> Option<&std::path::Path> {
271 None
272 }
273
274 fn uri(&self) -> &Uri {
275 &self.uri
276 }
277
278 fn as_any(&self) -> &dyn Any {
279 self
280 }
281 }
282
283 #[test]
284 fn test_ecosystem_id() {
285 let cache = Arc::new(deps_core::HttpCache::new());
286 let ecosystem = GoEcosystem::new(cache);
287 assert_eq!(ecosystem.id(), "go");
288 }
289
290 #[test]
291 fn test_ecosystem_display_name() {
292 let cache = Arc::new(deps_core::HttpCache::new());
293 let ecosystem = GoEcosystem::new(cache);
294 assert_eq!(ecosystem.display_name(), "Go Modules");
295 }
296
297 #[test]
298 fn test_ecosystem_manifest_filenames() {
299 let cache = Arc::new(deps_core::HttpCache::new());
300 let ecosystem = GoEcosystem::new(cache);
301 assert_eq!(ecosystem.manifest_filenames(), &["go.mod"]);
302 }
303
304 #[test]
305 fn test_ecosystem_lockfile_filenames() {
306 let cache = Arc::new(deps_core::HttpCache::new());
307 let ecosystem = GoEcosystem::new(cache);
308 assert_eq!(ecosystem.lockfile_filenames(), &["go.sum"]);
309 }
310
311 #[test]
312 fn test_generate_inlay_hints_up_to_date() {
313 let cache = Arc::new(deps_core::HttpCache::new());
314 let ecosystem = GoEcosystem::new(cache);
315
316 let uri = deps_core::test_util::test_uri("/test/go.mod");
317 let parse_result = MockParseResult {
318 dependencies: vec![mock_dependency(
319 "github.com/gin-gonic/gin",
320 Some("v1.9.1"),
321 5,
322 )],
323 uri,
324 };
325
326 let mut cached_versions = HashMap::new();
327 cached_versions.insert(
328 "github.com/gin-gonic/gin".into(),
329 PackageVersions::latest_only("v1.9.1"),
330 );
331
332 let config = EcosystemConfig {
333 loading_text: "⏳".to_string(),
334 show_loading_hints: true,
335 offline: false,
336 show_up_to_date_hints: true,
337 up_to_date_text: "✅".to_string(),
338 needs_update_text: "❌ {}".to_string(),
339 };
340
341 let mut resolved_versions = HashMap::new();
343 resolved_versions.insert("github.com/gin-gonic/gin".into(), "v1.9.1".into());
344 let hints = tokio_test::block_on(ecosystem.generate_inlay_hints(
345 &parse_result,
346 VersionData::new(&cached_versions, &resolved_versions),
347 deps_core::LoadingState::Loaded,
348 &config,
349 ));
350
351 assert_eq!(hints.len(), 1);
352 match &hints[0].label {
353 InlayHintLabel::String(s) => assert_eq!(s, "✅ v1.9.1"),
354 _ => panic!("Expected String label"),
355 }
356 }
357
358 #[test]
359 fn test_generate_inlay_hints_needs_update() {
360 let cache = Arc::new(deps_core::HttpCache::new());
361 let ecosystem = GoEcosystem::new(cache);
362
363 let uri = deps_core::test_util::test_uri("/test/go.mod");
364 let parse_result = MockParseResult {
365 dependencies: vec![mock_dependency(
366 "github.com/gin-gonic/gin",
367 Some("v1.9.0"),
368 5,
369 )],
370 uri,
371 };
372
373 let mut cached_versions = HashMap::new();
374 cached_versions.insert(
375 "github.com/gin-gonic/gin".into(),
376 PackageVersions::latest_only("v1.9.1"),
377 );
378
379 let config = EcosystemConfig {
380 loading_text: "⏳".to_string(),
381 show_loading_hints: true,
382 offline: false,
383 show_up_to_date_hints: true,
384 up_to_date_text: "✅".to_string(),
385 needs_update_text: "❌ {}".to_string(),
386 };
387
388 let resolved_versions = HashMap::new();
389 let hints = tokio_test::block_on(ecosystem.generate_inlay_hints(
390 &parse_result,
391 VersionData::new(&cached_versions, &resolved_versions),
392 deps_core::LoadingState::Loaded,
393 &config,
394 ));
395
396 assert_eq!(hints.len(), 1);
397 match &hints[0].label {
398 InlayHintLabel::String(s) => assert_eq!(s, "❌ v1.9.1"),
399 _ => panic!("Expected String label"),
400 }
401 }
402
403 #[test]
404 fn test_generate_inlay_hints_hide_up_to_date() {
405 let cache = Arc::new(deps_core::HttpCache::new());
406 let ecosystem = GoEcosystem::new(cache);
407
408 let uri = deps_core::test_util::test_uri("/test/go.mod");
409 let parse_result = MockParseResult {
410 dependencies: vec![mock_dependency(
411 "github.com/gin-gonic/gin",
412 Some("v1.9.1"),
413 5,
414 )],
415 uri,
416 };
417
418 let mut cached_versions = HashMap::new();
419 cached_versions.insert(
420 "github.com/gin-gonic/gin".into(),
421 PackageVersions::latest_only("v1.9.1"),
422 );
423
424 let config = EcosystemConfig {
425 loading_text: "⏳".to_string(),
426 show_loading_hints: true,
427 offline: false,
428 show_up_to_date_hints: false,
429 up_to_date_text: "✅".to_string(),
430 needs_update_text: "❌ {}".to_string(),
431 };
432
433 let mut resolved_versions = HashMap::new();
435 resolved_versions.insert("github.com/gin-gonic/gin".into(), "v1.9.1".into());
436 let hints = tokio_test::block_on(ecosystem.generate_inlay_hints(
437 &parse_result,
438 VersionData::new(&cached_versions, &resolved_versions),
439 deps_core::LoadingState::Loaded,
440 &config,
441 ));
442
443 assert_eq!(hints.len(), 0);
444 }
445
446 #[test]
447 fn test_generate_inlay_hints_no_version_range() {
448 let cache = Arc::new(deps_core::HttpCache::new());
449 let ecosystem = GoEcosystem::new(cache);
450
451 let mut dep = mock_dependency("github.com/gin-gonic/gin", Some("v1.9.1"), 5);
452 dep.version_range = None;
453
454 let uri = deps_core::test_util::test_uri("/test/go.mod");
455 let parse_result = MockParseResult {
456 dependencies: vec![dep],
457 uri,
458 };
459
460 let mut cached_versions = HashMap::new();
461 cached_versions.insert(
462 "github.com/gin-gonic/gin".into(),
463 PackageVersions::latest_only("v1.9.1"),
464 );
465
466 let config = EcosystemConfig {
467 loading_text: "⏳".to_string(),
468 show_loading_hints: true,
469 offline: false,
470 show_up_to_date_hints: true,
471 up_to_date_text: "✅".to_string(),
472 needs_update_text: "❌ {}".to_string(),
473 };
474
475 let resolved_versions = HashMap::new();
476 let hints = tokio_test::block_on(ecosystem.generate_inlay_hints(
477 &parse_result,
478 VersionData::new(&cached_versions, &resolved_versions),
479 deps_core::LoadingState::Loaded,
480 &config,
481 ));
482
483 assert_eq!(hints.len(), 0);
484 }
485
486 #[test]
487 fn test_as_any() {
488 let cache = Arc::new(deps_core::HttpCache::new());
489 let ecosystem = GoEcosystem::new(cache);
490
491 let any = ecosystem.as_any();
493 assert!(any.is::<GoEcosystem>());
494 }
495
496 #[tokio::test]
497 async fn test_complete_package_names_empty() {
498 let cache = Arc::new(deps_core::HttpCache::new());
499 let ecosystem = GoEcosystem::new(cache);
500
501 let results = ecosystem.complete_package_names("github").await;
503 assert!(results.is_empty());
504 }
505
506 #[tokio::test]
507 #[ignore] async fn test_complete_versions_real() {
509 let cache = Arc::new(deps_core::HttpCache::new());
510 let ecosystem = GoEcosystem::new(cache);
511 let dep = mock_dependency("github.com/gin-gonic/gin", Some("v1.9"), 0);
512 let position = dep.version_range.unwrap().start;
513 let parse_result = MockParseResult {
514 dependencies: vec![dep],
515 uri: deps_core::test_util::test_uri("/test/go.mod"),
516 };
517
518 let results = ecosystem
519 .complete_versions(
520 &parse_result,
521 position,
522 "v1.9",
523 deps_core::FreshnessSettings::default(),
524 )
525 .await;
526 assert!(!results.is_empty());
527 assert!(results.iter().all(|r| r.label.starts_with("v1.9")));
528 }
529
530 #[tokio::test]
531 async fn test_complete_versions_unknown_package() {
532 let cache = Arc::new(deps_core::HttpCache::new());
533 let ecosystem = GoEcosystem::new(cache);
534 let dep = mock_dependency("github.com/nonexistent/package12345", Some("v1.0"), 0);
535 let position = dep.version_range.unwrap().start;
536 let parse_result = MockParseResult {
537 dependencies: vec![dep],
538 uri: deps_core::test_util::test_uri("/test/go.mod"),
539 };
540
541 let results = ecosystem
543 .complete_versions(
544 &parse_result,
545 position,
546 "v1.0",
547 deps_core::FreshnessSettings::default(),
548 )
549 .await;
550 assert!(results.is_empty());
551 }
552
553 #[tokio::test]
554 async fn test_complete_features_always_empty() {
555 let cache = Arc::new(deps_core::HttpCache::new());
556 let ecosystem = GoEcosystem::new(cache);
557
558 let results = ecosystem
560 .complete_features(&pkg("github.com/gin-gonic/gin"), "")
561 .await;
562 assert!(results.is_empty());
563 }
564
565 #[tokio::test]
566 #[ignore] async fn test_complete_versions_limit_20() {
568 let cache = Arc::new(deps_core::HttpCache::new());
569 let ecosystem = GoEcosystem::new(cache);
570
571 let dep = mock_dependency("github.com/gin-gonic/gin", Some("v1.0"), 0);
573 let position = dep.version_range.unwrap().start;
574 let parse_result = MockParseResult {
575 dependencies: vec![dep],
576 uri: deps_core::test_util::test_uri("/test/go.mod"),
577 };
578 let results = ecosystem
579 .complete_versions(
580 &parse_result,
581 position,
582 "v",
583 deps_core::FreshnessSettings::default(),
584 )
585 .await;
586 assert!(results.len() <= 20);
587 }
588
589 #[tokio::test]
590 async fn test_generate_hover_on_module_path() {
591 let cache = Arc::new(deps_core::HttpCache::new());
592 let ecosystem = GoEcosystem::new(cache);
593
594 let uri = deps_core::test_util::test_uri("/test/go.mod");
595 let parse_result = MockParseResult {
596 dependencies: vec![mock_dependency(
597 "github.com/gin-gonic/gin",
598 Some("v1.9.1"),
599 5,
600 )],
601 uri,
602 };
603
604 let position = Position::new(5, 5);
605 let cached_versions = HashMap::new();
606 let resolved_versions = HashMap::new();
607
608 let hover = ecosystem
609 .generate_hover(
610 &parse_result,
611 position,
612 VersionData::new(&cached_versions, &resolved_versions),
613 deps_core::FreshnessSettings::default(),
614 )
615 .await;
616
617 assert!(hover.is_some());
619 let hover_content = hover.unwrap();
620 let markdown = format!("{:?}", hover_content.contents);
621 assert!(markdown.contains("pkg.go.dev"));
622 }
623
624 #[tokio::test]
625 async fn test_generate_hover_outside_dependency() {
626 let cache = Arc::new(deps_core::HttpCache::new());
627 let ecosystem = GoEcosystem::new(cache);
628
629 let uri = deps_core::test_util::test_uri("/test/go.mod");
630 let parse_result = MockParseResult {
631 dependencies: vec![mock_dependency(
632 "github.com/gin-gonic/gin",
633 Some("v1.9.1"),
634 5,
635 )],
636 uri,
637 };
638
639 let position = Position::new(0, 0);
640 let cached_versions = HashMap::new();
641 let resolved_versions = HashMap::new();
642
643 let hover = ecosystem
644 .generate_hover(
645 &parse_result,
646 position,
647 VersionData::new(&cached_versions, &resolved_versions),
648 deps_core::FreshnessSettings::default(),
649 )
650 .await;
651
652 assert!(hover.is_none());
653 }
654
655 #[tokio::test]
656 async fn test_generate_code_actions_on_module() {
657 let cache = Arc::new(deps_core::HttpCache::new());
658 let ecosystem = GoEcosystem::new(cache);
659
660 let uri = deps_core::test_util::test_uri("/test/go.mod");
661 let parse_result = MockParseResult {
662 dependencies: vec![mock_dependency(
663 "github.com/gin-gonic/gin",
664 Some("v1.9.0"),
665 5,
666 )],
667 uri: uri.clone(),
668 };
669
670 let position = Position::new(5, 5);
671 let cached_versions = HashMap::new();
672 let resolved_versions = HashMap::new();
673
674 let content = "\n\n\n\n\nv1.9.0 \n";
678
679 let actions = ecosystem
680 .generate_code_actions(
681 &parse_result,
682 position,
683 &uri,
684 VersionData::new(&cached_versions, &resolved_versions),
685 content,
686 )
687 .await;
688
689 assert!(!actions.is_empty());
691 }
692
693 #[tokio::test]
694 #[ignore = "Requires network access to proxy.golang.org"]
695 async fn test_generate_diagnostics_basic() {
696 let cache = Arc::new(deps_core::HttpCache::new());
697 let ecosystem = GoEcosystem::new(cache);
698
699 let uri = deps_core::test_util::test_uri("/test/go.mod");
700 let parse_result = MockParseResult {
701 dependencies: vec![mock_dependency(
702 "github.com/gin-gonic/gin",
703 Some("v1.9.1"),
704 5,
705 )],
706 uri,
707 };
708
709 let cached_versions = HashMap::new();
710 let resolved_versions = HashMap::new();
711
712 let result = tokio::time::timeout(
714 std::time::Duration::from_secs(5),
715 ecosystem.generate_diagnostics(
716 &parse_result,
717 VersionData::new(&cached_versions, &resolved_versions),
718 parse_result.uri(),
719 deps_core::FreshnessSettings::default(),
720 deps_core::DiagnosticSeverities::default(),
721 ),
722 )
723 .await;
724
725 assert!(result.is_ok(), "Diagnostic generation timed out");
727 }
728
729 #[tokio::test]
730 async fn test_generate_completions_package_name() {
731 let cache = Arc::new(deps_core::HttpCache::new());
732 let ecosystem = GoEcosystem::new(cache);
733
734 let content = r"module example.com/myapp
735
736go 1.21
737
738require github.com/
739";
740
741 let uri = deps_core::test_util::test_uri("/test/go.mod");
742 let parse_result = MockParseResult {
743 dependencies: vec![],
744 uri,
745 };
746
747 let position = Position::new(4, 19);
748
749 let completions = ecosystem
750 .generate_completions(
751 &parse_result,
752 position,
753 content,
754 deps_core::FreshnessSettings::default(),
755 )
756 .await;
757
758 assert!(completions.items.is_empty());
760 }
761
762 #[tokio::test]
763 async fn test_generate_completions_outside_context() {
764 let cache = Arc::new(deps_core::HttpCache::new());
765 let ecosystem = GoEcosystem::new(cache);
766
767 let content = r"module example.com/myapp
768
769go 1.21
770";
771
772 let uri = deps_core::test_util::test_uri("/test/go.mod");
773 let parse_result = MockParseResult {
774 dependencies: vec![],
775 uri,
776 };
777
778 let position = Position::new(0, 0);
779
780 let completions = ecosystem
781 .generate_completions(
782 &parse_result,
783 position,
784 content,
785 deps_core::FreshnessSettings::default(),
786 )
787 .await;
788
789 assert!(completions.items.is_empty());
790 }
791
792 #[tokio::test]
793 async fn test_parse_manifest_valid() {
794 let cache = Arc::new(deps_core::HttpCache::new());
795 let ecosystem = GoEcosystem::new(cache);
796
797 let content = r"module example.com/myapp
798
799go 1.21
800
801require github.com/gin-gonic/gin v1.9.1
802";
803
804 let uri = deps_core::test_util::test_uri("/test/go.mod");
805
806 let result = ecosystem.parse_manifest(content, &uri).await;
807 assert!(result.is_ok());
808
809 let parse_result = result.unwrap();
810 assert_eq!(parse_result.dependencies().len(), 1);
811 assert_eq!(
812 parse_result.dependencies()[0].name(),
813 "github.com/gin-gonic/gin"
814 );
815 }
816
817 #[tokio::test]
818 async fn test_parse_manifest_empty() {
819 let cache = Arc::new(deps_core::HttpCache::new());
820 let ecosystem = GoEcosystem::new(cache);
821
822 let content = "";
823 let uri = deps_core::test_util::test_uri("/test/go.mod");
824
825 let result = ecosystem.parse_manifest(content, &uri).await;
826 assert!(result.is_ok());
827
828 let parse_result = result.unwrap();
829 assert_eq!(parse_result.dependencies().len(), 0);
830 }
831
832 #[test]
833 fn test_registry_returns_trait_object() {
834 let cache = Arc::new(deps_core::HttpCache::new());
835 let ecosystem = GoEcosystem::new(cache);
836
837 let registry = ecosystem.registry();
838 assert!(registry.as_any().is::<GoRegistry>());
839 }
840
841 #[test]
842 fn test_lockfile_provider_exists() {
843 let cache = Arc::new(deps_core::HttpCache::new());
844 let ecosystem = GoEcosystem::new(cache);
845
846 assert!(ecosystem.lockfile_provider().is_some());
847 }
848
849 #[test]
850 fn test_mock_dependency_indirect() {
851 let mut dep = mock_dependency("github.com/example/pkg", Some("v1.0.0"), 10);
852 dep.indirect = true;
853
854 assert!(dep.indirect);
855 assert_eq!(dep.name(), "github.com/example/pkg");
856 }
857
858 #[tokio::test]
865 async fn test_complete_versions_same_name_different_sources_routes_by_position() {
866 let cache = Arc::new(deps_core::HttpCache::new());
867 let ecosystem = GoEcosystem::new(cache);
868 let registry_dep = dep_with_source("git.mycorp.example/pkg", DependencySource::Registry, 0);
869 let alternate_dep = dep_with_source(
870 "git.mycorp.example/pkg",
871 DependencySource::AlternateRegistry {
872 index: "go-private:never-registered".to_string(),
873 mirrors_crates_io: false,
874 },
875 1,
876 );
877 let alternate_position = alternate_dep.version_range.unwrap().start;
878 let parse_result = MockParseResult {
879 dependencies: vec![registry_dep, alternate_dep],
880 uri: deps_core::test_util::test_uri("/test/go.mod"),
881 };
882
883 let results = ecosystem
888 .complete_versions(
889 &parse_result,
890 alternate_position,
891 "v1.",
892 deps_core::FreshnessSettings::default(),
893 )
894 .await;
895 assert!(
896 results.is_empty(),
897 "unregistered alternate index must offer no completions"
898 );
899 }
900
901 #[tokio::test]
904 async fn test_complete_versions_unregistered_alternate_offers_nothing() {
905 let cache = Arc::new(deps_core::HttpCache::new());
906 let ecosystem = GoEcosystem::new(cache);
907 let dep = dep_with_source(
908 "git.mycorp.example/internal/auth",
909 DependencySource::AlternateRegistry {
910 index: "never-registered".to_string(),
911 mirrors_crates_io: false,
912 },
913 0,
914 );
915 let position = dep.version_range.unwrap().start;
916 let parse_result = MockParseResult {
917 dependencies: vec![dep],
918 uri: deps_core::test_util::test_uri("/test/go.mod"),
919 };
920 let results = ecosystem
921 .complete_versions(
922 &parse_result,
923 position,
924 "v1.",
925 deps_core::FreshnessSettings::default(),
926 )
927 .await;
928 assert!(results.is_empty());
929 }
930
931 #[tokio::test]
935 async fn test_complete_versions_routes_to_registered_alternate_client() {
936 use crate::config::{GoProxyChain, GoProxyHop, GoProxyUrl};
937 use deps_core::net_policy::{RegistryAccessPolicy, WorkspaceRegistryAccess};
938
939 let mut alt_server = mockito::Server::new_async().await;
940 alt_server
941 .mock("GET", "/git.mycorp.example/internal/auth/@v/list")
942 .with_status(200)
943 .with_body("v1.0.0\nv1.5.0\n")
944 .create_async()
945 .await;
946
947 let cache = Arc::new(deps_core::HttpCache::new());
948 cache.set_registry_policy(WorkspaceRegistryAccess::All);
949 let registry = Arc::new(GoRegistry::new(Arc::clone(&cache)));
950 let ecosystem = GoEcosystem::with_context(Arc::clone(®istry), GoParseContext::default());
951
952 let policy = RegistryAccessPolicy::new(WorkspaceRegistryAccess::All);
953 let chain = GoProxyChain {
954 key: "go-proxy:test".to_string(),
955 hops: vec![GoProxyHop::Url(
956 GoProxyUrl::new(&alt_server.url(), &policy).unwrap(),
957 )],
958 ..Default::default()
959 };
960 GoRegistry::register_chain(®istry, &chain);
961
962 let dep = dep_with_source(
963 "git.mycorp.example/internal/auth",
964 DependencySource::AlternateRegistry {
965 index: "go-proxy:test".to_string(),
966 mirrors_crates_io: false,
967 },
968 0,
969 );
970 let position = dep.version_range.unwrap().start;
971 let parse_result = MockParseResult {
972 dependencies: vec![dep],
973 uri: deps_core::test_util::test_uri("/test/go.mod"),
974 };
975 let results = ecosystem
976 .complete_versions(
977 &parse_result,
978 position,
979 "v1.",
980 deps_core::FreshnessSettings::default(),
981 )
982 .await;
983 assert!(!results.is_empty());
984 }
985}