File: NuGet\NuGetPackageCacheTests.cs
Web Access
Project: src\tests\Aspire.Cli.Tests\Aspire.Cli.Tests.csproj (Aspire.Cli.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Cli.NuGet;
using Aspire.Cli.Packaging;
using Aspire.Cli.Tests.TestServices;
using Aspire.Cli.Tests.Utils;
using Microsoft.Extensions.DependencyInjection;
using NuGetPackage = Aspire.Shared.NuGetPackageCli;
using Microsoft.AspNetCore.InternalTesting;
 
namespace Aspire.Cli.Tests.NuGet;
 
public class NuGetPackageCacheTests(ITestOutputHelper outputHelper)
{
    [Theory]
    [InlineData("Aspire.Hosting.Redis", true)]
    [InlineData("aspire.hosting.redis", true)]
    [InlineData("Aspire.Hosting.CommunityToolkit.Redis", true)]
    [InlineData("CommunityToolkit.Aspire.Hosting.Redis", false)]
    [InlineData("Acme.Aspire.Hosting.Redis", false)]
    [InlineData("Aspire.HostingExtra.Redis", false)]
    [InlineData("Aspire.Hosting.Sdk", false)]
    [InlineData("Aspire.Hosting.AppHost", false)]
    public void IsOfficialIntegrationPackageIdRecognizesOfficialIntegrations(string packageId, bool expected)
    {
        Assert.Equal(expected, PackageIdFilters.IsOfficialIntegrationPackageId(packageId));
    }
 
    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public async Task SearchAppliesMappingsToLatestAndExactVersionResults(bool useChannelOverlay)
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        const string selectedFeed = "https://example.test/selected/v3/index.json";
        const string ambientFeed = "https://example.test/ambient/v3/index.json";
        File.WriteAllText(
            Path.Combine(workspace.WorkspaceRoot.FullName, "NuGet.Config"),
            $"""
            <configuration>
              <packageSources>
                <clear />
                <add key="selected" value="{selectedFeed}" />
                <add key="ambient" value="{ambientFeed}" />
              </packageSources>
              <packageSourceMapping>
                <packageSource key="selected">
                  <package pattern="Aspire.Hosting.Redis" />
                </packageSource>
                <packageSource key="ambient">
                  <package pattern="*" />
                </packageSource>
              </packageSourceMapping>
            </configuration>
            """);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.NuGetClientFactory = _ => NuGetTestHelper.CreateClient();
            configure.DotNetCliRunnerFactory = _ => new TestDotNetCliRunner
            {
                SearchPackagesAsyncCallback = (_, _, exactMatch, _, _, _, _, _, _, _) => exactMatch
                    ? (0, [
                        new NuGetPackage { Id = "Aspire.Hosting.Redis", Version = "9.0.0", Source = ambientFeed },
                        new NuGetPackage { Id = "Aspire.Hosting.Redis", Version = "1.0.0", Source = selectedFeed }
                    ])
                    : (0, [
                        new NuGetPackage { Id = "Aspire.Hosting.Redis", Version = "9.0.0", Source = "ambient" },
                        new NuGetPackage { Id = "Aspire.Hosting.Redis", Version = "1.0.0", Source = "selected" },
                        new NuGetPackage { Id = "CommunityToolkit.Aspire.Hosting.Custom", Version = "2.0.0", Source = "ambient" }
                    ])
            };
        });
        using var provider = services.BuildServiceProvider();
        var cache = provider.GetRequiredService<INuGetPackageCache>();
        using var configuration = await cache.CreateChannelConfigurationAsync(
            workspace.WorkspaceRoot,
            useChannelOverlay ? [new PackageMapping("Aspire*", selectedFeed)] : null,
            TestContext.Current.CancellationToken);
 
        var packages = await cache.GetIntegrationPackagesAsync(configuration, prerelease: true, TestContext.Current.CancellationToken);
        Assert.Collection(
            packages,
            package =>
            {
                Assert.Equal("Aspire.Hosting.Redis", package.Id);
                Assert.Equal("1.0.0", package.Version);
            },
            package => Assert.Equal("CommunityToolkit.Aspire.Hosting.Custom", package.Id));
 
        var versions = await cache.GetPackageVersionsAsync(
            configuration,
            "Aspire.Hosting.Redis",
            prerelease: true,
            useCache: false,
            TestContext.Current.CancellationToken);
        var selected = Assert.Single(versions);
        Assert.Equal("1.0.0", selected.Version);
        Assert.Equal(selectedFeed, selected.Source);
    }
 
    [Fact]
    public async Task NonAspireCliPackagesWillNotBeConsidered()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.DotNetCliRunnerFactory = (sp) =>
            {
                var runner = new TestDotNetCliRunner();
                runner.SearchPackagesAsyncCallback = (_, _, _, _, _, _, _, _, _, _) =>
                {
                    // Simulate a search that returns packages that do not match Aspire.Cli
                    return (0, [
                        new NuGetPackage { Id = "CommunityToolkit.Aspire.Hosting.Foo", Version = "9.4.0-xyz", Source = "nuget.org" },
                        new NuGetPackage { Id = "Aspire.Cli", Version = "9.4.0-preview", Source = "nuget.org" }
                    ]);
                };
 
                return runner;
            };
        });
 
        using var provider = services.BuildServiceProvider();
 
        var nuGetPackageCache = provider.GetRequiredService<INuGetPackageCache>();
        var packages = await nuGetPackageCache.GetCliPackagesAsync(workspace.WorkspaceRoot, prerelease: true, nugetConfigFile: null, CancellationToken.None).DefaultTimeout();
 
        Assert.Collection(
            packages,
            package => Assert.Equal("Aspire.Cli", package.Id)
        );
    }
 
    [Fact]
    public async Task DeprecatedPackagesAreFilteredByDefault()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.DotNetCliRunnerFactory = (sp) =>
            {
                var runner = new TestDotNetCliRunner();
                runner.SearchPackagesAsyncCallback = (_, _, _, _, _, _, _, _, _, _) =>
                {
                    // Simulate a search that returns both regular and deprecated packages
                    return (0, [
                        new NuGetPackage { Id = "Aspire.Hosting.Redis", Version = "9.4.0", Source = "nuget.org" },
                        new NuGetPackage { Id = "Aspire.Hosting.Dapr", Version = "9.4.0", Source = "nuget.org" }, // Deprecated
                        new NuGetPackage { Id = "Aspire.Hosting.GitHub.Models", Version = "9.4.0", Source = "nuget.org" }, // Deprecated
                        new NuGetPackage { Id = "Aspire.Hosting.NodeJs", Version = "9.4.0", Source = "nuget.org" }, // Deprecated
                        new NuGetPackage { Id = "Aspire.Hosting.PostgreSQL", Version = "9.4.0", Source = "nuget.org" }
                    ]);
                };
 
                return runner;
            };
        });
 
        using var provider = services.BuildServiceProvider();
 
        var nuGetPackageCache = provider.GetRequiredService<INuGetPackageCache>();
        var packages = await nuGetPackageCache.GetPackagesAsync(workspace.WorkspaceRoot, "Aspire.Hosting", null, prerelease: false, nugetConfigFile: null, useCache: true, CancellationToken.None).DefaultTimeout();
 
        // Should include regular packages but exclude deprecated Dapr package
        var packageIds = packages.Select(p => p.Id).ToList();
        Assert.Contains("Aspire.Hosting.Redis", packageIds);
        Assert.Contains("Aspire.Hosting.PostgreSQL", packageIds);
        Assert.DoesNotContain("Aspire.Hosting.Dapr", packageIds);
        Assert.DoesNotContain("Aspire.Hosting.GitHub.Models", packageIds);
        Assert.DoesNotContain("Aspire.Hosting.NodeJs", packageIds);
    }
 
    [Fact]
    public async Task DeprecatedPackagesAreIncludedWhenShowDeprecatedPackagesEnabled()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            // Enable showing deprecated packages
            configure.EnabledFeatures = [Aspire.Cli.KnownFeatures.ShowDeprecatedPackages];
 
            configure.DotNetCliRunnerFactory = (sp) =>
            {
                var runner = new TestDotNetCliRunner();
                runner.SearchPackagesAsyncCallback = (_, _, _, _, _, _, _, _, _, _) =>
                {
                    // Simulate a search that returns both regular and deprecated packages
                    return (0, [
                        new NuGetPackage { Id = "Aspire.Hosting.Redis", Version = "9.4.0", Source = "nuget.org" },
                        new NuGetPackage { Id = "Aspire.Hosting.Dapr", Version = "9.4.0", Source = "nuget.org" }, // Deprecated
                        new NuGetPackage { Id = "Aspire.Hosting.GitHub.Models", Version = "9.4.0", Source = "nuget.org" }, // Deprecated
                        new NuGetPackage { Id = "Aspire.Hosting.NodeJs", Version = "9.4.0", Source = "nuget.org" }, // Deprecated
                        new NuGetPackage { Id = "Aspire.Hosting.PostgreSQL", Version = "9.4.0", Source = "nuget.org" }
                    ]);
                };
 
                return runner;
            };
        });
 
        using var provider = services.BuildServiceProvider();
 
        var nuGetPackageCache = provider.GetRequiredService<INuGetPackageCache>();
        var packages = await nuGetPackageCache.GetPackagesAsync(workspace.WorkspaceRoot, "Aspire.Hosting", null, prerelease: false, nugetConfigFile: null, useCache: true, CancellationToken.None).DefaultTimeout();
 
        // Should include all packages including deprecated Dapr package when showing deprecated is enabled
        var packageIds = packages.Select(p => p.Id).ToList();
        Assert.Contains("Aspire.Hosting.Redis", packageIds);
        Assert.Contains("Aspire.Hosting.PostgreSQL", packageIds);
        Assert.Contains("Aspire.Hosting.Dapr", packageIds);
        Assert.Contains("Aspire.Hosting.GitHub.Models", packageIds);
        Assert.Contains("Aspire.Hosting.NodeJs", packageIds);
    }
 
    [Theory]
    [InlineData("Aspire.Azure.AI.Inference")]
    [InlineData("Aspire.Azure.AI.OpenAI")]
    [InlineData("aspire.azure.ai.inference")]
    [InlineData("aspire.azure.ai.openai")]
    [InlineData("Aspire.Azure.AI.Projects")]
    [InlineData("Aspire.Azure.AI.Extensions.OpenAI")]
    [InlineData("Aspire.OpenAI")]
    public void ClientPackagesAreExcludedFromHostingDiscovery(string packageId)
    {
        Assert.False(PackageIdFilters.IsOfficialOrCommunityToolkitPackage(packageId));
        Assert.False(PackageIdFilters.IsIntegrationPackageId(packageId));
    }
 
    [Theory]
    [InlineData("Aspire.Azure.AI.Inference", true, false)]
    [InlineData("Aspire.Azure.AI.Inference", true, true)]
    [InlineData("Aspire.Azure.AI.OpenAI", true, false)]
    [InlineData("Aspire.Azure.AI.OpenAI", true, true)]
    [InlineData("aspire.azure.ai.inference", true, false)]
    [InlineData("aspire.azure.ai.inference", true, true)]
    [InlineData("aspire.azure.ai.openai", true, false)]
    [InlineData("aspire.azure.ai.openai", true, true)]
    [InlineData("Aspire.Azure.AI.Projects", false, false)]
    [InlineData("Aspire.Azure.AI.Projects", false, true)]
    [InlineData("Aspire.Azure.AI.Extensions.OpenAI", false, false)]
    [InlineData("Aspire.Azure.AI.Extensions.OpenAI", false, true)]
    [InlineData("Aspire.OpenAI", false, false)]
    [InlineData("Aspire.OpenAI", false, true)]
    public async Task GetPackageVersionsAsync_RespectsClientPackageRetirement(string packageId, bool deprecated, bool showDeprecatedPackages)
    {
        Assert.Equal(deprecated, DeprecatedPackages.IsDeprecated(packageId));
 
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.EnabledFeatures = showDeprecatedPackages ? [KnownFeatures.ShowDeprecatedPackages] : [];
            configure.DotNetCliRunnerFactory = (sp) =>
            {
                var runner = new TestDotNetCliRunner();
                runner.SearchPackagesAsyncCallback = (_, query, exactMatch, _, _, _, _, _, _, _) =>
                {
                    Assert.Equal(packageId, query);
                    Assert.True(exactMatch);
 
                    return (0, [
                        new NuGetPackage { Id = packageId, Version = "13.3.0", Source = "nuget.org" },
                        new NuGetPackage { Id = packageId, Version = "13.2.0", Source = "nuget.org" }
                    ]);
                };
 
                return runner;
            };
        });
 
        using var provider = services.BuildServiceProvider();
        var nuGetPackageCache = provider.GetRequiredService<INuGetPackageCache>();
        var packages = (await nuGetPackageCache.GetPackageVersionsAsync(
            workspace.WorkspaceRoot,
            packageId,
            prerelease: false,
            nugetConfigFile: null,
            useCache: true,
            CancellationToken.None).DefaultTimeout()).ToArray();
 
        string[] expectedVersions = showDeprecatedPackages || !deprecated ? ["13.3.0", "13.2.0"] : [];
        Assert.Equal(expectedVersions, packages.Select(package => package.Version));
        Assert.All(packages, package => Assert.Equal(packageId, package.Id));
    }
 
    [Fact]
    public async Task CustomFilterBypassesDeprecatedPackageFiltering()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.DotNetCliRunnerFactory = (sp) =>
            {
                var runner = new TestDotNetCliRunner();
                runner.SearchPackagesAsyncCallback = (_, _, _, _, _, _, _, _, _, _) =>
                {
                    // Simulate a search that returns both regular and deprecated packages
                    return (0, [
                        new NuGetPackage { Id = "Aspire.Hosting.Redis", Version = "9.4.0", Source = "nuget.org" },
                        new NuGetPackage { Id = "Aspire.Hosting.Dapr", Version = "9.4.0", Source = "nuget.org" }, // Deprecated
                        new NuGetPackage { Id = "Other.Package", Version = "9.4.0", Source = "nuget.org" }
                    ]);
                };
 
                return runner;
            };
        });
 
        using var provider = services.BuildServiceProvider();
 
        var nuGetPackageCache = provider.GetRequiredService<INuGetPackageCache>();
 
        // Use a custom filter that includes all packages containing "Dapr"
        var packages = await nuGetPackageCache.GetPackagesAsync(
            workspace.WorkspaceRoot,
            "Aspire.Hosting",
            filter: id => id.Contains("Dapr", StringComparison.OrdinalIgnoreCase),
            prerelease: false,
            nugetConfigFile: null,
            useCache: true,
            CancellationToken.None).DefaultTimeout();
 
        // Custom filter should bypass deprecated package filtering
        var packageIds = packages.Select(p => p.Id).ToList();
        Assert.Contains("Aspire.Hosting.Dapr", packageIds);
        Assert.DoesNotContain("Aspire.Hosting.Redis", packageIds);
        Assert.DoesNotContain("Other.Package", packageIds);
    }
 
    [Fact]
    public async Task DeprecatedPackageFilteringIsCaseInsensitive()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.DotNetCliRunnerFactory = (sp) =>
            {
                var runner = new TestDotNetCliRunner();
                runner.SearchPackagesAsyncCallback = (_, _, _, _, _, _, _, _, _, _) =>
                {
                    // Test different casing of deprecated package name
                    return (0, [
                        new NuGetPackage { Id = "aspire.hosting.dapr", Version = "9.4.0", Source = "nuget.org" }, // lowercase
                        new NuGetPackage { Id = "ASPIRE.HOSTING.DAPR", Version = "9.4.0", Source = "nuget.org" }, // uppercase
                        new NuGetPackage { Id = "Aspire.Hosting.Redis", Version = "9.4.0", Source = "nuget.org" }
                    ]);
                };
 
                return runner;
            };
        });
 
        using var provider = services.BuildServiceProvider();
 
        var nuGetPackageCache = provider.GetRequiredService<INuGetPackageCache>();
        var packages = await nuGetPackageCache.GetPackagesAsync(workspace.WorkspaceRoot, "Aspire.Hosting", null, prerelease: false, nugetConfigFile: null, useCache: true, CancellationToken.None).DefaultTimeout();
 
        // Should filter out all case variations of deprecated package
        var packageIds = packages.Select(p => p.Id).ToList();
        Assert.Contains("Aspire.Hosting.Redis", packageIds);
        Assert.DoesNotContain("aspire.hosting.dapr", packageIds);
        Assert.DoesNotContain("ASPIRE.HOSTING.DAPR", packageIds);
    }
 
    [Fact]
    public async Task AnalyzerPackageIsFilteredFromDefaultPackageSearch()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.DotNetCliRunnerFactory = (sp) =>
            {
                var runner = new TestDotNetCliRunner();
                runner.SearchPackagesAsyncCallback = (_, _, _, _, _, _, _, _, _, _) =>
                {
                    return (0, [
                        new NuGetPackage { Id = "Aspire.Hosting.Redis", Version = "13.4.0", Source = "nuget.org" },
                        new NuGetPackage { Id = "Aspire.Hosting.Integration.Analyzers", Version = "13.4.0", Source = "nuget.org" },
                        new NuGetPackage { Id = "Aspire.Hosting.PostgreSQL", Version = "13.4.0", Source = "nuget.org" }
                    ]);
                };
 
                return runner;
            };
        });
 
        using var provider = services.BuildServiceProvider();
 
        var nuGetPackageCache = provider.GetRequiredService<INuGetPackageCache>();
        var packages = await nuGetPackageCache.GetPackagesAsync(workspace.WorkspaceRoot, "Aspire.Hosting", filter: null, prerelease: false, nugetConfigFile: null, useCache: true, CancellationToken.None).DefaultTimeout();
 
        Assert.Collection(
            packages.Select(p => p.Id),
            id => Assert.Equal("Aspire.Hosting.Redis", id),
            id => Assert.Equal("Aspire.Hosting.PostgreSQL", id));
    }
 
    [Fact]
    public async Task GetPackageVersionsAsync_UsesExactMatchSearch()
    {
        bool? observedExactMatch = null;
        int observedTake = -1;
        int observedSkip = -1;
        bool? observedUseCache = null;
 
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.DotNetCliRunnerFactory = (sp) =>
            {
                var runner = new TestDotNetCliRunner();
                runner.SearchPackagesAsyncCallback = (_, query, exactMatch, prerelease, take, skip, nugetConfigFile, useCache, options, cancellationToken) =>
                {
                    observedExactMatch = exactMatch;
                    observedTake = take;
                    observedSkip = skip;
                    observedUseCache = useCache;
 
                    return query switch
                    {
                        "Aspire.Hosting.Redis" => (0, [
                            new NuGetPackage { Id = "Aspire.Hosting.Redis", Version = "13.3.0", Source = "nuget.org" },
                            new NuGetPackage { Id = "Aspire.Hosting.Redis", Version = "13.2.0", Source = "nuget.org" }
                        ]),
                        _ => (0, Array.Empty<NuGetPackage>())
                    };
                };
 
                return runner;
            };
        });
 
        using var provider = services.BuildServiceProvider();
 
        var nuGetPackageCache = provider.GetRequiredService<INuGetPackageCache>();
        var packages = (await nuGetPackageCache.GetPackageVersionsAsync(
            workspace.WorkspaceRoot,
            "Aspire.Hosting.Redis",
            prerelease: false,
            nugetConfigFile: null,
            useCache: true,
            CancellationToken.None)).OrderBy(package => package.Version).ToArray();
 
        Assert.True(observedExactMatch);
        Assert.Equal(0, observedTake);
        Assert.Equal(0, observedSkip);
        Assert.True(observedUseCache);
        Assert.Collection(
            packages,
            package => Assert.Equal("13.2.0", package.Version),
            package => Assert.Equal("13.3.0", package.Version));
    }
}