diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Package/Download/PackageDownloadRunner.cs b/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Package/Download/PackageDownloadRunner.cs index 940821c4ad3..3714a1ed29a 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Package/Download/PackageDownloadRunner.cs +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Package/Download/PackageDownloadRunner.cs @@ -91,15 +91,31 @@ public static async Task RunAsync(PackageDownloadArgs args, ILoggerWithColo } else { - if (!TryGetRepositoriesForPackage( - package.Id, - args, - packageSourceMapping!, - allRepositories, - logger, - out sourceRepositories)) + var mappedNames = packageSourceMapping!.GetConfiguredPackageSources(package.Id); + + if (mappedNames.Count == 0) + { + // fail, no sources mapped for this package + var notConsideredSources = string.Join( + ", ", + allRepositories.Select(repository => repository.PackageSource)); + + logger.LogError(string.Format( + CultureInfo.CurrentCulture, + Strings.PackageDownloadCommand_PackageSourceMapping_NoSourcesMapped, + package.Id, + notConsideredSources)); + + downloadedAllSuccessfully &= false; + continue; + } + + sourceRepositories = GetMappedRepositories(mappedNames, allRepositories, package.Id, logger); + + if (DetectAndReportInsecureSources(args.AllowInsecureConnections, sourceRepositories.Select(r => r.PackageSource), logger)) { - return ExitCodeError; + downloadedAllSuccessfully &= false; + continue; } } @@ -224,67 +240,49 @@ await ResolvePackageDownloadVersion( return (versionToDownload, downloadSourceRepository); } - /// - /// Builds the set of SourceRepository objects to use for a given package, - /// applying package source mapping - /// validating HTTP usage only on the *effective* sources. - /// - private static bool TryGetRepositoriesForPackage( - string packageId, - PackageDownloadArgs args, - PackageSourceMapping packageSourceMapping, + internal static IReadOnlyList GetMappedRepositories( + IReadOnlyList mappedNames, IReadOnlyList allRepos, - ILoggerWithColor logger, - out IReadOnlyList repositories) + string packageId, + ILoggerWithColor logger) { - var mappedNames = packageSourceMapping.GetConfiguredPackageSources(packageId); + var mappedRepos = new List(mappedNames.Count); - // Only validate insecure sources when mapping produced something - if (mappedNames.Count > 0) + foreach (var mappedName in mappedNames) { - var mappedRepos = new List(mappedNames.Count); - foreach (var mappedName in mappedNames) - { - SourceRepository? repo = null; - for (int i = 0; i < allRepos.Count; i++) - { - if (string.Equals(allRepos[i].PackageSource.Name, mappedName, StringComparison.OrdinalIgnoreCase)) - { - repo = allRepos[i]; - break; - } - } + SourceRepository? repo = FindRepositoryByName(mappedName, allRepos); - if (repo != null) - { - mappedRepos.Add(repo); - } - else - { - logger.LogVerbose( - string.Format( - CultureInfo.CurrentCulture, - Strings.PackageDownloadCommand_PackageSourceMapping_NoSuchSource, - mappedName, - packageId)); - } + if (repo != null) + { + mappedRepos.Add(repo); } - - if (DetectAndReportInsecureSources(args.AllowInsecureConnections, mappedRepos.Select(repo => repo.PackageSource), logger)) + else { - repositories = []; - return false; + logger.LogVerbose( + string.Format( + CultureInfo.CurrentCulture, + Strings.PackageDownloadCommand_PackageSourceMapping_NoSuchSource, + mappedName, + packageId)); } - - repositories = mappedRepos; - return true; } - else + + return mappedRepos; + } + + private static SourceRepository? FindRepositoryByName( + string mappedName, + IReadOnlyList allRepos) + { + for (int i = 0; i < allRepos.Count; i++) { - // No mapping for this package: fall back to all sources - repositories = allRepos; - return true; + if (string.Equals(allRepos[i].PackageSource.Name, mappedName, StringComparison.OrdinalIgnoreCase)) + { + return allRepos[i]; + } } + + return null; } private static async Task DownloadPackageAsync( diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.Designer.cs b/src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.Designer.cs index a34243fa494..a0a4eb8bfae 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.Designer.cs +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -1661,6 +1661,15 @@ internal static string PackageDownloadCommand_PackageIdDescription { } } + /// + /// Looks up a localized string similar to Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}.. + /// + internal static string PackageDownloadCommand_PackageSourceMapping_NoSourcesMapped { + get { + return ResourceManager.GetString("PackageDownloadCommand_PackageSourceMapping_NoSourcesMapped", resourceCulture); + } + } + /// /// Looks up a localized string similar to The mapped source '{0}' for package '{1}' was not found among the configured sources.. /// diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.resx b/src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.resx index 3a9b3a4c269..3d5ef962d17 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.resx +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.resx @@ -1162,4 +1162,9 @@ Do not translate "PackageVersion" 0 - package source name 1 - package name + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + \ No newline at end of file diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.cs.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.cs.xlf index f88a09768ef..30a22234ca0 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.cs.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.cs.xlf @@ -929,6 +929,12 @@ Další informace najdete tady: https://docs.nuget.org/docs/reference/command-li Identifikátor balíčku (např. Newtonsoft.Json) + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. Mapovaný zdroj {0} pro balíček {1} nebyl nalezen mezi nakonfigurovanými zdroji. diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.de.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.de.xlf index ef421b620f5..3e1714a7a6a 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.de.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.de.xlf @@ -929,6 +929,12 @@ Weitere Informationen finden Sie unter: https://docs.nuget.org/docs/reference/co Paketbezeichner (z. B. „Newtonsoft.Json“). + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. Die zugeordnete Quelle {0} für das Paket {1} wurde unter den konfigurierten Quellen nicht gefunden. diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.es.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.es.xlf index 00f17f02fe2..d2d3f4c00b0 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.es.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.es.xlf @@ -929,6 +929,12 @@ Para obtener más información, visite https://docs.nuget.org/docs/reference/com Identificador del paquete (por ejemplo, 'Newtonsoft.Json'). + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. No se encontró el origen asignado '{0}' para el paquete '{1}' entre los orígenes configurados. diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.fr.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.fr.xlf index 65fb46259d1..1ccb9edd8f7 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.fr.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.fr.xlf @@ -929,6 +929,12 @@ Pour plus d'informations, visitez https://docs.nuget.org/docs/reference/command- Identificateur de package (par exemple, « Newtonsoft.Json »). + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. La source mappée « {0} » pour le package « {1} » est introuvable parmi les sources configurées. diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.it.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.it.xlf index fadaec784f7..c784b60e6f5 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.it.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.it.xlf @@ -929,6 +929,12 @@ Per altre informazioni, vedere https://docs.nuget.org/docs/reference/command-lin Identificatore del pacchetto ,ad esempio 'Newtonsoft.Json'. + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. L'origine mappata '{0}' per il pacchetto '{1}' non è stata trovata tra le origini configurate. diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ja.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ja.xlf index b613c85a669..93ed0d07a40 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ja.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ja.xlf @@ -929,6 +929,12 @@ For more information, visit https://docs.nuget.org/docs/reference/command-line-r パッケージ識別子 (例: 'Newtonsoft.Json')。 + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. パッケージ '{1}' のマップされたソース '{0}' が、構成済みのソースの中に見つかりませんでした。 diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ko.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ko.xlf index 9b24629c526..8bce96c1054 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ko.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ko.xlf @@ -929,6 +929,12 @@ For more information, visit https://docs.nuget.org/docs/reference/command-line-r 패키지 식별자(예: 'Newtonsoft.Json'). + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. 구성된 원본 중 '{1}' 패키지에 대해 매핑된 원본 '{0}'을(를) 찾을 수 없습니다. diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.pl.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.pl.xlf index 6b9b4b48b69..540b8bb33e0 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.pl.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.pl.xlf @@ -929,6 +929,12 @@ Aby uzyskać więcej informacji, odwiedź stronę https://docs.nuget.org/docs/re Identyfikator pakietu (np. „Newtonsoft.Json”). + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. Zmapowane źródło „{0}” dla pakietu „{1}” nie zostało znalezione wśród skonfigurowanych źródeł. diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.pt-BR.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.pt-BR.xlf index 6bf07f8ac8a..19d526ab67b 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.pt-BR.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.pt-BR.xlf @@ -929,6 +929,12 @@ Para obter mais informações, acesse https://docs.nuget.org/docs/reference/comm Identificador de pacote (por exemplo, "Newtonsoft.Json"). + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. A origem mapeada "{0}" para o pacote "{1}" não foi encontrada entre as fontes configuradas. diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ru.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ru.xlf index 5e89925d95f..104a684865d 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ru.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ru.xlf @@ -929,6 +929,12 @@ For more information, visit https://docs.nuget.org/docs/reference/command-line-r Идентификатор пакета (например, "Newtonsoft.Json"). + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. Сопоставленный источник "{0}" для пакета "{1}" не найден среди настроенных источников. diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.tr.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.tr.xlf index 11899374363..8345a2db6f0 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.tr.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.tr.xlf @@ -930,6 +930,12 @@ Daha fazla bilgi için bkz. https://docs.nuget.org/docs/reference/command-line-r Paket tanımlayıcısı (örneğin ‘Newtonsoft.Json’). + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. '{1}' paketi için eşlenen '{0}' kaynağı yapılandırılmış kaynaklar arasında bulunamadı. diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.zh-Hans.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.zh-Hans.xlf index f3750eef157..e6bf8af7abd 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.zh-Hans.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.zh-Hans.xlf @@ -929,6 +929,12 @@ For more information, visit https://docs.nuget.org/docs/reference/command-line-r 包标识符(例如 "Newtonsoft.Json")。 + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. 在配置的源中找不到包 "{1}" 的映射源 "{0}"。 diff --git a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.zh-Hant.xlf b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.zh-Hant.xlf index 56a5581cb3c..226ef4ed6cd 100644 --- a/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.zh-Hant.xlf +++ b/src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.zh-Hant.xlf @@ -929,6 +929,12 @@ For more information, visit https://docs.nuget.org/docs/reference/command-line-r 套件識別碼 (e.g. 'Newtonsoft.Json')。 + + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + Unable to download package '{0}'. PackageSourceMapping is enabled, but no mapped sources were found for this package. The following source(s) were not considered: {1}. + {0} = package ID +{1} = comma-separated list of all configured sources + The mapped source '{0}' for package '{1}' was not found among the configured sources. 在已設定的來源中找不到套件 '{1}' 的對應來源 '{0}'。 diff --git a/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/Package/Download/PackageDownloadRunnerTests.cs b/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/Package/Download/PackageDownloadRunnerTests.cs index d96dcc6559f..970967ce217 100644 --- a/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/Package/Download/PackageDownloadRunnerTests.cs +++ b/test/NuGet.Core.FuncTests/NuGet.XPlat.FuncTest/Package/Download/PackageDownloadRunnerTests.cs @@ -472,6 +472,30 @@ public static IEnumerable Cases() false, null! }; + + // no --source, mapping -> A&B, package in both A and B. Latest version from A installed + yield return new object[] + { + new List<(string,string)> { ("Contoso.Mapped", "3.0.0") }, // A + new List<(string,string)> { ("Contoso.Mapped", "2.0.0") }, // B + new List<(string,string)> { ("A", "Contoso.*"), ("B", "Contoso.*") }, // mapped to A&B + null, + "Contoso.Mapped", null, + true, + ("Contoso.Mapped", "3.0.0") + }; + + // no --source, mapping -> A&B, package in both A and B. Latest version from B installed + yield return new object[] + { + new List<(string,string)> { ("Contoso.Mapped", "2.0.0") }, // A + new List<(string,string)> { ("Contoso.Mapped", "3.0.0") }, // B + new List<(string,string)> { ("A", "Contoso.*"), ("B", "Contoso.*") }, // mapped to A&B + null, + "Contoso.Mapped", null, + true, + ("Contoso.Mapped", "3.0.0") + }; } [Theory] @@ -570,10 +594,9 @@ public async Task RunAsync_WhenSourceMappingMapsToInsecureSource_AndAllowInsecur { // Arrange using var context = new SimpleTestPathContext(); - var insecureSourceUrl = "http://contoso.test/v3/index.json"; - - context.Settings.AddSource("InsecureMapped", insecureSourceUrl); - context.Settings.AddPackageSourceMapping("InsecureMapped", "Contoso.*"); + PackageSource source = new("http://contoso.test/v3/index.json", "InsecureMapped"); + context.Settings.AddSource(source.Name, source.SourceUri.OriginalString); + context.Settings.AddPackageSourceMapping(source.Name, "Contoso.*"); var settings = Settings.LoadSettingsGivenConfigPaths([context.Settings.ConfigPath]); @@ -594,9 +617,15 @@ public async Task RunAsync_WhenSourceMappingMapsToInsecureSource_AndAllowInsecur var logger = new Mock(MockBehavior.Loose); var packageSources = new List { - new(insecureSourceUrl, "InsecureMapped") + source }; + string expectedError = string.Format( + CultureInfo.CurrentCulture, + Strings.Error_HttpServerUsage, + "package download", + source); + // Act var exit = await PackageDownloadRunner.RunAsync( args, @@ -607,6 +636,10 @@ public async Task RunAsync_WhenSourceMappingMapsToInsecureSource_AndAllowInsecur // Assert exit.Should().Be(PackageDownloadRunner.ExitCodeError); + logger.Verify( + l => l.LogError(expectedError), + Times.Once); + } [Fact] @@ -663,6 +696,60 @@ public async Task RunAsync_WhenSourceMappingMapsToSecureLocalSource_DoesNotLogEr File.Exists(Path.Combine(installDir, $"{id.ToLowerInvariant()}.{version}.nupkg")).Should().BeTrue(); } + [Fact] + public async Task RunAsync_WhenSourceMappingEnabled_PackageMapsToNoSource_Fails() + { + // Arrange + using var context = new SimpleTestPathContext(); + PackageSource source = new(context.PackageSource, "source"); + context.Settings.AddSource(source.Name, source.SourceUri.OriginalString); + context.Settings.AddPackageSourceMapping(source.Name, "Contoso.Not.*"); + var settings = Settings.LoadSettingsGivenConfigPaths([context.Settings.ConfigPath]); + + // package + var id = "Contoso.Package"; + var version = "1.0.0"; + + string expectedError = string.Format( + CultureInfo.CurrentCulture, + Strings.PackageDownloadCommand_PackageSourceMapping_NoSourcesMapped, + id, + source); + + // arguments + var logger = new Mock(MockBehavior.Loose); + var packageSources = new List + { + source + }; + var args = new PackageDownloadArgs + { + Packages = + [ + new PackageWithNuGetVersion + { + Id = id, + NuGetVersion = NuGetVersion.Parse(version) + } + ], + OutputDirectory = context.WorkingDirectory, + }; + + // Act + var exit = await PackageDownloadRunner.RunAsync( + args, + logger.Object, + packageSources, + settings, + CancellationToken.None); + + // Assert + exit.Should().Be(PackageDownloadRunner.ExitCodeError); + logger.Verify( + l => l.LogError(expectedError), + Times.Once); + } + [Fact] public async Task RunAsync_WhenSourceMappingMapsToInsecureSource_AndAllowInsecureConnectionsTrue_DownloadsWithoutError() { diff --git a/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/Commands/Package/Download/PackageDownloadRunnerTests.cs b/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/Commands/Package/Download/PackageDownloadRunnerTests.cs index 7779eb52aeb..e150b03e25d 100644 --- a/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/Commands/Package/Download/PackageDownloadRunnerTests.cs +++ b/test/NuGet.Core.Tests/NuGet.CommandLine.Xplat.Tests/Commands/Package/Download/PackageDownloadRunnerTests.cs @@ -3,7 +3,9 @@ #nullable disable +using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -202,4 +204,97 @@ public async Task ResolvePackageDownloadVersion_UnlistedPackage_BehavesAsExpecte foundRepo.Should().BeNull(); } } + + [Theory] + [InlineData("A", "a")] + [InlineData("a", "A")] + [InlineData("SourceA", "sourcea")] + [InlineData("SOURCEA", "sourcea")] + public void GetMappedRepositories_WithVariousSourceCasing_ReturnsOnlyApplicableSources(string sourceNameConfig, string sourceNameMapped) + { + // Arrange + var packageId = "Contoso.Package"; + var packageSource = new PackageSource(sourceNameConfig, sourceNameConfig); + var repository = new SourceRepository(packageSource, Array.Empty()); + var allRepos = new List { repository }; + var mappedNames = new List { sourceNameMapped }; + var logger = new Mock(MockBehavior.Loose); + + // Act + var result = PackageDownloadRunner.GetMappedRepositories( + mappedNames, + allRepos, + packageId, + logger.Object); + + // Assert + result.Should().HaveCount(1); + result[0].PackageSource.Name.Should().Be(sourceNameConfig); + } + + [Fact] + public void GetMappedRepositories_WhenSourceMissing_LogsVerbose() + { + // Arrange + var packageId = "Contoso.Package"; + + var existingRepo = new SourceRepository(new PackageSource("A", "A"), Array.Empty()); + + var allRepos = new List { existingRepo }; + string mappedName = "MissingSource"; + var mappedNames = new List { mappedName }; + + string captured = ""; + var logger = new Mock(MockBehavior.Loose); + logger.Setup(l => l.LogVerbose(It.IsAny())) + .Callback(msg => captured += msg); + + string expectedLog = string.Format( + CultureInfo.CurrentCulture, + XPlat.Strings.PackageDownloadCommand_PackageSourceMapping_NoSuchSource, + mappedName, + packageId); + + // Act + var result = PackageDownloadRunner.GetMappedRepositories( + mappedNames, + allRepos, + packageId, + logger.Object); + + // Assert + result.Should().BeEmpty(); + captured.Should().Contain(expectedLog); + } + + [Fact] + public void GetMappedRepositories_WhenAllMappedSourcesExist_ReturnsAllMatchedRepositories() + { + // Arrange + var packageId = "Contoso.Package"; + + var packageSourceA = new PackageSource("Source1", "Source1"); + var packageSourceB = new PackageSource("Source2", "Source2"); + + var repoA = new SourceRepository(packageSourceA, Array.Empty()); + var repoB = new SourceRepository(packageSourceB, Array.Empty()); + + var allRepos = new List { repoA, repoB }; + var mappedNames = new List { "Source1", "source2" }; + + var logger = new Mock(MockBehavior.Loose); + + // Act + var result = PackageDownloadRunner.GetMappedRepositories( + mappedNames, + allRepos, + packageId, + logger.Object); + + // Assert + Assert.Equal(2, result.Count); + Assert.Equal(packageSourceA.Name, result[0].PackageSource.Name); + Assert.Equal(packageSourceB.Name, result[1].PackageSource.Name); + logger.Verify(l => l.LogVerbose(It.IsAny()), Times.Never); + } } diff --git a/test/NuGet.Core.Tests/NuGet.DependencyResolver.Core.Tests/RemoteWalkContextTests.cs b/test/NuGet.Core.Tests/NuGet.DependencyResolver.Core.Tests/RemoteWalkContextTests.cs index 26b1e97bde4..73b591c5271 100644 --- a/test/NuGet.Core.Tests/NuGet.DependencyResolver.Core.Tests/RemoteWalkContextTests.cs +++ b/test/NuGet.Core.Tests/NuGet.DependencyResolver.Core.Tests/RemoteWalkContextTests.cs @@ -109,6 +109,80 @@ public void FilterDependencyProvidersForLibrary_WhenPackagePatternToSourceMappin Assert.Empty(providers); } + [Fact] + public void FilterDependencyProvidersForLibrary_WhenPackagePatternMappedToMultipleSources_ReturnsAllApplicableProviders() + { + // Arrange + var logger = new TestLogger(); + + // package source mapping configuration: same pattern "x" mapped to multiple sources + Dictionary> patterns = new() + { + { "Source1", new List { "x" } }, + { "Source2", new List { "x" } }, + { "Source3", new List { "y" } } + }; + PackageSourceMapping sourceMappingConfiguration = new(patterns); + + var context = new TestRemoteWalkContext(sourceMappingConfiguration, logger); + + // Source1 + var remoteProvider1 = CreateRemoteDependencyProvider("Source1"); + context.RemoteLibraryProviders.Add(remoteProvider1.Object); + + // Source2 + var remoteProvider2 = CreateRemoteDependencyProvider("Source2"); + context.RemoteLibraryProviders.Add(remoteProvider2.Object); + + // Source3 + var remoteProvider3 = CreateRemoteDependencyProvider("Source3"); + context.RemoteLibraryProviders.Add(remoteProvider3.Object); + + var libraryRange = new LibraryRange("x", Versioning.VersionRange.None, LibraryDependencyTarget.Package); + + // Act + IList providers = context.FilterDependencyProvidersForLibrary(libraryRange); + + // Assert + Assert.Equal(2, providers.Count); + Assert.Equal("Source1", providers[0].Source.Name); + Assert.Equal("Source2", providers[1].Source.Name); + } + + [Fact] + public void FilterDependencyProvidersForLibrary_WhenMappingIncludesMissingSource_IgnoresMissingSource() + { + // Arrange + var logger = new TestLogger(); + + // "x" is mapped to an existing source and a non-existent one + Dictionary> patterns = new() + { + { "Source1", new List { "x" } }, + { "MissingSource", new List { "x" } } + }; + PackageSourceMapping sourceMappingConfiguration = new(patterns); + + var context = new TestRemoteWalkContext(sourceMappingConfiguration, logger); + + // Only Source1 exists as a provider + var remoteProvider1 = CreateRemoteDependencyProvider("Source1"); + context.RemoteLibraryProviders.Add(remoteProvider1.Object); + + // Another unrelated provider + var remoteProvider2 = CreateRemoteDependencyProvider("OtherSource"); + context.RemoteLibraryProviders.Add(remoteProvider2.Object); + + var libraryRange = new LibraryRange("x", Versioning.VersionRange.None, LibraryDependencyTarget.Package); + + // Act + IList providers = context.FilterDependencyProvidersForLibrary(libraryRange); + + // Assert + Assert.Single(providers); + Assert.Equal("Source1", providers[0].Source.Name); + } + private Mock CreateRemoteDependencyProvider(string source) { var remoteProvider = new Mock();