diff --git a/src/Weikio.NugetDownloader.sln b/src/Weikio.NugetDownloader.sln index 3d8f298..f023899 100644 --- a/src/Weikio.NugetDownloader.sln +++ b/src/Weikio.NugetDownloader.sln @@ -5,6 +5,10 @@ VisualStudioVersion = 16.0.30413.136 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Weikio.NugetDownloader", "Weikio.NugetDownloader\Weikio.NugetDownloader.csproj", "{4BCDAB59-F231-4241-B6BE-93BDC047C86F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D712A662-C87A-433F-B3BC-2E5DB5EAB0A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Weikio.NugetDownloader.Tests", "..\tests\integration\Weikio.NugetDownloader.Tests\Weikio.NugetDownloader.Tests.csproj", "{09606C73-4CAE-4069-9B74-A623F7957E4F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,10 +19,17 @@ Global {4BCDAB59-F231-4241-B6BE-93BDC047C86F}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BCDAB59-F231-4241-B6BE-93BDC047C86F}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BCDAB59-F231-4241-B6BE-93BDC047C86F}.Release|Any CPU.Build.0 = Release|Any CPU + {09606C73-4CAE-4069-9B74-A623F7957E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09606C73-4CAE-4069-9B74-A623F7957E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09606C73-4CAE-4069-9B74-A623F7957E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09606C73-4CAE-4069-9B74-A623F7957E4F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {09606C73-4CAE-4069-9B74-A623F7957E4F} = {D712A662-C87A-433F-B3BC-2E5DB5EAB0A5} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {69466E23-050E-41B5-945E-367A476EF6CB} EndGlobalSection diff --git a/src/Weikio.NugetDownloader/NugetDownloader.cs b/src/Weikio.NugetDownloader/NugetDownloader.cs index 56024cf..6d0e0d1 100644 --- a/src/Weikio.NugetDownloader/NugetDownloader.cs +++ b/src/Weikio.NugetDownloader/NugetDownloader.cs @@ -33,30 +33,14 @@ public NuGetDownloader(ILogger logger = null) public async Task DownloadAsync(string packageFolder, string packageName, string packageVersion = null, bool includePrerelease = false, NuGetFeed packageFeed = null, bool onlyDownload = false) { - if (!Directory.Exists(packageFolder)) - { - Directory.CreateDirectory(packageFolder); - } + IPackageSearchMetadata package = null; + SourceRepository sourceRepo = null; var providers = GetNugetResourceProviders(); var settings = Settings.LoadDefaultSettings(packageFolder, null, new MachineWideSettings()); var packageSourceProvider = new PackageSourceProvider(settings); var sourceRepositoryProvider = new SourceRepositoryProvider(packageSourceProvider, providers); - var dotNetFramework = Assembly - .GetEntryAssembly() - .GetCustomAttribute()? - .FrameworkName; - - var frameworkNameProvider = new FrameworkNameProvider( - new[] { DefaultFrameworkMappings.Instance }, - new[] { DefaultPortableFrameworkMappings.Instance }); - - var nuGetFramework = NuGetFramework.ParseFrameworkName(dotNetFramework, frameworkNameProvider); - - IPackageSearchMetadata package = null; - SourceRepository sourceRepo = null; - if (!string.IsNullOrWhiteSpace(packageFeed?.Feed)) { sourceRepo = GetSourceRepo(packageFeed, providers); @@ -88,50 +72,7 @@ public async Task DownloadAsync(string packageFolder, string packageNa throw new PackageNotFoundException($"Couldn't find package '{packageVersion}'.{packageVersion}."); } - var project = new PluginFolderNugetProject(packageFolder, package, nuGetFramework, onlyDownload); - var packageManager = new NuGetPackageManager(sourceRepositoryProvider, settings, packageFolder) { PackagesFolderNuGetProject = project }; - - var clientPolicyContext = ClientPolicyContext.GetClientPolicy(settings, _logger); - - var projectContext = new FolderProjectContext(_logger) - { - PackageExtractionContext = new PackageExtractionContext( - PackageSaveMode.Defaultv2, - PackageExtractionBehavior.XmlDocFileSaveMode, - clientPolicyContext, - _logger) - }; - - var resolutionContext = new ResolutionContext( - DependencyBehavior.Lowest, - includePrerelease, - includeUnlisted: false, - VersionConstraints.None); - - var downloadContext = new PackageDownloadContext( - resolutionContext.SourceCacheContext, - packageFolder, - resolutionContext.SourceCacheContext.DirectDownload); - - // We are waiting here instead of await as await actually doesn't seem to work correctly. - packageManager.InstallPackageAsync( - project, - package.Identity, - resolutionContext, - projectContext, - downloadContext, - sourceRepo, - new List(), - CancellationToken.None).Wait(); - - if (onlyDownload) - { - var versionFolder = Path.Combine(packageFolder, package.Identity.ToString()); - - return Directory.GetFiles(versionFolder, "*.*", SearchOption.AllDirectories); - } - - return await project.GetPluginAssemblyFilesAsync(); + return await DownloadAsync(package, sourceRepo, packageFolder, onlyDownload); } public async Task DownloadAsync(IPackageSearchMetadata packageIdentity, SourceRepository repository, @@ -194,9 +135,14 @@ await packageManager.InstallPackageAsync( new List(), CancellationToken.None); - var versionFolder = Path.Combine(downloadFolder, packageIdentity.Identity.ToString()); + if (onlyDownload) + { + var versionFolder = Path.Combine(downloadFolder, packageIdentity.Identity.ToString()); - return Directory.GetFiles(versionFolder, "*.*", SearchOption.AllDirectories); + return Directory.GetFiles(versionFolder, "*.*", SearchOption.AllDirectories); + } + + return await project.GetPluginAssemblyFilesAsync(); } private static List> GetNugetResourceProviders() @@ -252,19 +198,40 @@ private static SourceRepository GetSourceRepo(NuGetFeed packageFeed, List GetPackages(string searchTerm, + int maxResults, bool includePrerelease, IEnumerable repositories) + { foreach (var repository in repositories) { - var packageSearchResource = await repository.GetResourceAsync(); + PackageSearchResource packageSearchResource; + + try + { + packageSearchResource = await repository.GetResourceAsync(); + } + catch (FatalProtocolException ex) + { + _logger.LogError($"Failed to download package search resource: {ex}"); + continue; + } SearchFilter searchFilter; if (includePrerelease) { - searchFilter = new SearchFilter(includePrerelease, SearchFilterType.IsAbsoluteLatestVersion); + searchFilter = new SearchFilter(true, SearchFilterType.IsAbsoluteLatestVersion); } else { - searchFilter = new SearchFilter(includePrerelease); + searchFilter = new SearchFilter(false); } var items = await packageSearchResource.SearchAsync(searchTerm, searchFilter, 0, maxResults, _logger, CancellationToken.None); @@ -276,28 +243,19 @@ private static SourceRepository GetSourceRepo(NuGetFeed packageFeed, List> SearchPackagesAsync(NuGetFeed packageFeed, + public async IAsyncEnumerable<(SourceRepository Repository, IPackageSearchMetadata Package)> SearchPackagesAsync(NuGetFeed packageFeed, string searchTerm, int maxResults = 128, bool includePrerelease = false) { var providers = GetNugetResourceProviders(); var sourceRepo = GetSourceRepo(packageFeed, providers); - var packageSearchResource = await sourceRepo.GetResourceAsync(); - SearchFilter searchFilter; + var packages = GetPackages(searchTerm, maxResults, includePrerelease, new List { sourceRepo }); - if (includePrerelease) + await foreach (var package in packages) { - searchFilter = new SearchFilter(includePrerelease, SearchFilterType.IsAbsoluteLatestVersion); + yield return package; } - else - { - searchFilter = new SearchFilter(includePrerelease); - } - - var packages = await packageSearchResource.SearchAsync(searchTerm, searchFilter, 0, maxResults, _logger, CancellationToken.None); - - return packages.Select(x => (sourceRepo, x)); } private async Task SearchPackageAsync(string packageName, string version, bool includePrerelease, diff --git a/tests/integration/Weikio.NugetDownloader.Tests/NugetDownloaderTests.cs b/tests/integration/Weikio.NugetDownloader.Tests/NugetDownloaderTests.cs new file mode 100644 index 0000000..713e94a --- /dev/null +++ b/tests/integration/Weikio.NugetDownloader.Tests/NugetDownloaderTests.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Versioning; +using System.Threading.Tasks; +using FluentAssertions; +using NuGet.Protocol.Core.Types; +using Xunit; + +namespace Weikio.NugetDownloader.Tests +{ + /* + * Note that these test depend on public NuGet feed and expect certain packages to be available. + * This tests can fail without notice if feed is down or package is removed. + */ + + public class NugetDownloaderTests : IDisposable + { + private readonly string _packagesFolderInTestsBin; + private const string _packageFromNugetOrgName = "Newtonsoft.Json"; + private const string _packageFromThirdPartyFeedName = "DummyProject"; + private const string _thirdPartyFeedName = "AdafyPublic"; + private const string _thirdPartyFeed = "https://pkgs.dev.azure.com/adafy/df962856-ce0c-4e96-8999-bee7c8b0582c/_packaging/AdafyPublic/nuget/v3/index.json"; + + public NugetDownloaderTests() + { + var executingAssemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + _packagesFolderInTestsBin = Path.Combine(executingAssemblyDir!, "FeedTestPackages"); + } + + public void Dispose() + { + if (Directory.Exists(_packagesFolderInTestsBin)) + { + Directory.Delete(_packagesFolderInTestsBin, true); + } + } + + [Fact] + public async Task DownloadsPackageFromNugetOrg() + { + // Arrange + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsPackageFromNugetOrg)); + var downloader = new NuGetDownloader(); + + // Act + var assemblies = await downloader.DownloadAsync(packageFolder, _packageFromNugetOrgName); + + // Assert + assemblies.Should().NotBeEmpty("because return value should contain assembly name."); + assemblies.Should().ContainMatch($"*{_packageFromNugetOrgName}*", "because package contains assembly with package name."); + + var directories = Directory.GetDirectories(packageFolder); + directories.Should().ContainMatch($"*{_packageFromNugetOrgName}*", "because package is installed to the directory"); + + var files = Directory.GetFiles(packageFolder, "*.*", SearchOption.TopDirectoryOnly); + files.Should().NotBeEmpty("because NuGet package has been downloaded"); + files.Should().ContainMatch("*.dll", "because NuGet packages has dll file."); + } + + [Fact] + public async Task DownloadsOnlyPackageFromNugetOrg() + { + // Arrange + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsOnlyPackageFromNugetOrg)); + var downloader = new NuGetDownloader(); + + // Act + var packageFiles = await downloader.DownloadAsync(packageFolder, _packageFromNugetOrgName, null, false, null, true); + + // Assert + packageFiles.Should().NotBeEmpty("because return value should contain listing of files in package."); + + var directories = Directory.GetDirectories(packageFolder); + directories.Should().ContainMatch($"*{_packageFromNugetOrgName}*", "because package is installed to the directory"); + + var files = Directory.GetFiles(packageFolder, "*.*", SearchOption.TopDirectoryOnly); + files.Should().BeEmpty("because package is only download but not extracted"); + } + + [Fact] + public async Task DownloadsCorrectVersionFromNugetOrg() + { + // Arrange + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsCorrectVersionFromNugetOrg)); + var version = "12.0.1"; + var downloader = new NuGetDownloader(); + + // Act + var assemblies = await downloader.DownloadAsync(packageFolder, _packageFromNugetOrgName, version, false, null, false); + + // Assert + assemblies.Should().NotBeEmpty("because return value should contain assembly name."); + assemblies.Should().ContainMatch($"*{_packageFromNugetOrgName}*", "because package contains assembly with package name."); + + var directories = Directory.GetDirectories(packageFolder); + directories.Should().ContainMatch($"*{_packageFromNugetOrgName}.{version}*", "because package is installed to the directory"); + } + + [Fact] + public async Task ExtractsCorrectFrameworkVersionFromNugetOrg() + { + // Arrange + var dotnetFramework = GetDotnetFrameworkName(); + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsCorrectVersionFromNugetOrg)); + var downloader = new NuGetDownloader(); + + // Act + await downloader.DownloadAsync(packageFolder, _packageFromNugetOrgName, null, false, null, true); + + // Assert + var dllFiles = Directory.GetFiles(packageFolder, "*.dll", SearchOption.TopDirectoryOnly); + var assembiles = dllFiles.Select(Assembly.Load); + + foreach (var assembly in assembiles) + { + AssertAssemblyFrameWork(dotnetFramework, assembly); + } + } + + [Fact] + public async Task DownloadsPackageFromThirdPartyFeed() + { + // Arrange + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsPackageFromThirdPartyFeed)); + var downloader = new NuGetDownloader(); + + // Act + var assemblies = await downloader.DownloadAsync(packageFolder, _packageFromThirdPartyFeedName, null, false, + new NuGetFeed(_thirdPartyFeedName, _thirdPartyFeed)); + + // Assert + assemblies.Should().NotBeEmpty("because return value should contain assembly name."); + assemblies.Should().ContainMatch($"*{_packageFromThirdPartyFeedName}*", "because package contains assembly with package name."); + + var directories = Directory.GetDirectories(packageFolder); + directories.Should().ContainMatch($"*{_packageFromThirdPartyFeedName}*", "because package is installed to the directory"); + + var files = Directory.GetFiles(packageFolder, "*.*", SearchOption.TopDirectoryOnly); + files.Should().NotBeEmpty("because NuGet package has been downloaded"); + files.Should().ContainMatch("*.dll", "because NuGet packages has dll file."); + } + + + [Fact] + public async Task DownloadsPreleasePackageFromThirdpartyFeed() + { + // Arrange + var version = "1.2.3-beta"; + var packageFolder = Path.Combine(_packagesFolderInTestsBin, nameof(DownloadsPreleasePackageFromThirdpartyFeed)); + var downloader = new NuGetDownloader(); + + // Act + var assemblies = await downloader.DownloadAsync(packageFolder, _packageFromThirdPartyFeedName, version, true, + new NuGetFeed(_thirdPartyFeedName, _thirdPartyFeed)); + + // Assert + assemblies.Should().NotBeEmpty("because return value should contain assembly name."); + assemblies.Should().ContainMatch($"*{_packageFromThirdPartyFeedName}*", "because package contains assembly with package name."); + + var directories = Directory.GetDirectories(packageFolder); + directories.Should().ContainMatch($"*{_packageFromThirdPartyFeedName}.{version}*", "because package is installed to the directory"); + } + + [Fact] + public async Task SearchesPackageFromNugetOrg() + { + // Arrange + var downloader = new NuGetDownloader(); + + // Act + var packages = new List(); + var results = downloader.SearchPackagesAsync(_packageFromNugetOrgName); + await foreach (var result in results) + { + packages.Add(result.Package); + } + + // Assert + packages.Should().NotBeEmpty("because search should find some packages"); + packages.Select(p => p.Title).Should().ContainMatch($"*{_packageFromNugetOrgName}*", "because feed should contain the package."); + } + + [Fact] + public async Task SearchesPackageFromThirdPartyFeed() + { + // Arrange + var downloader = new NuGetDownloader(); + + // Act + var packages = new List(); + var results = downloader.SearchPackagesAsync(new NuGetFeed(_thirdPartyFeedName, _thirdPartyFeed), _packageFromThirdPartyFeedName); + await foreach (var result in results) + { + packages.Add(result.Package); + } + + // Assert + packages.Should().NotBeEmpty("because search should find some packages"); + packages.Select(p => p.Title).Should().ContainMatch($"*{_packageFromThirdPartyFeedName}*", "because feed should contain the package."); + } + + [Fact] + public async Task SearchesPrereleasePackageFromThirdPartyFeed() + { + // Arrange + var version = "1.2.3-beta"; + var downloader = new NuGetDownloader(); + + // Act + var packages = new List(); + var results = downloader.SearchPackagesAsync( + new NuGetFeed(_thirdPartyFeedName, _thirdPartyFeed), _packageFromThirdPartyFeedName, 128, true); + await foreach (var result in results) + { + packages.Add(result.Package); + } + + // Assert + packages.Should().NotBeEmpty("because search should find some packages"); + packages.Select(p => p.Identity.Version.ToString()).Should().ContainMatch($"*{version}*", + "because feed should contain the package."); + } + + private string GetDotnetFrameworkName() + { + var dotNetFramework = Assembly + .GetEntryAssembly() + .GetCustomAttribute()? + .FrameworkName; + + return dotNetFramework; + } + + private void AssertAssemblyFrameWork(string targetFramework, Assembly assembly) + { + var assemblyFramework = assembly + .GetCustomAttribute()? + .FrameworkName; + + assemblyFramework.Should().Be(targetFramework, "because only target framework version should be extracted."); + } + } +} diff --git a/tests/integration/Weikio.NugetDownloader.Tests/Weikio.NugetDownloader.Tests.csproj b/tests/integration/Weikio.NugetDownloader.Tests/Weikio.NugetDownloader.Tests.csproj new file mode 100644 index 0000000..9bc0d5a --- /dev/null +++ b/tests/integration/Weikio.NugetDownloader.Tests/Weikio.NugetDownloader.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + +