File: Packaging\PackageSourceOverrideMappingsTests.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.Packaging;
using Aspire.Cli.Projects;
using Aspire.Cli.Tests.TestServices;
using Microsoft.Extensions.Logging.Abstractions;
using NuGet.Configuration;
 
namespace Aspire.Cli.Tests.Packaging;
 
public class PackageSourceOverrideMappingsTests(ITestOutputHelper outputHelper)
{
    [Theory]
    [InlineData("Aspire*", "Aspire.Hosting.Redis", true)]
    [InlineData("aspire*", "Aspire.Hosting.Redis", true)]
    [InlineData("Aspire.Hosting.Redis", "aspire.hosting.redis", true)]
    [InlineData("Aspire.Hosting.Redis", "Aspire.Hosting.PostgreSQL", false)]
    [InlineData("Aspire*", "CommunityToolkit.Aspire.Hosting.Redis", false)]
    [InlineData("*", "CommunityToolkit.Aspire.Hosting.Redis", true)]
    public void MatchesPackage_UsesNuGetMappingPatternSemantics(
        string packagePattern,
        string packageName,
        bool expected)
    {
        var nativeMapping = new PackageSourceMapping(new Dictionary<string, IReadOnlyList<string>>
        {
            ["selected"] = [packagePattern]
        });
 
        Assert.Equal(expected ? ["selected"] : [], nativeMapping.GetConfiguredPackageSources(packageName));
        Assert.Equal(expected, PackageSourceOverrideMappings.MatchesPackage(packagePattern, packageName));
    }
 
    [Fact]
    public void CredentialBearingSourceOverride_IsRejected()
    {
        const string source = "https://user:p#word@host/";
 
        Assert.True(PackageSourceOverrideMappings.HasCredentialMaterial(source));
        Assert.Throws<ArgumentException>(() =>
            PackageSourceOverrideMappings.Create(source, requestedChannel: null, nugetServiceIndexOverride: source));
        Assert.Throws<ArgumentException>(() =>
            PackageSourceOverrideMappings.CreateForSourceOnlyOperations(source));
    }
 
    [Fact]
    public void Create_ExactPackagePatternKeepsSourceEligibleForAspireClosure()
    {
        const string source = "https://example.com/integration";
        const string packageId = "CommunityToolkit.Aspire.Hosting.Redis";
 
        var mappings = PackageSourceOverrideMappings.Create(
            source,
            requestedChannel: null,
            nugetServiceIndexOverride: null,
            packagePattern: packageId);
 
        Assert.Equal(
            [packageId, PackageSourceOverrideMappings.DefaultPackagePattern, PackageMapping.AllPackages],
            mappings
                .Where(mapping => PackageSourceIdentity.Comparer.Equals(mapping.Source, source))
                .Select(static mapping => mapping.PackageFilter));
    }
 
    [Theory]
    [InlineData("HTTPS://EXAMPLE.TEST/feed/v3/index.json", "Aspire*")]
    [InlineData("HTTPS://EXAMPLE.TEST/feed/v3/index.json", "aspire*")]
    [InlineData("https://example.test:443/feed/v3/index.json", "Aspire*")]
    [InlineData("https://example.test/feed/./v3/index.json", "Aspire*")]
    [InlineData("https://example.test/feed/v3/%69ndex.json", "Aspire*")]
    [InlineData("https://example.test/feed/v3/index.json", "aspire*")]
    public async Task Create_EquivalentMappingsPreserveNativeEligibilityAndCacheIdentity(string source, string channelPattern)
    {
        const string canonicalSource = "https://example.test/feed/v3/index.json";
        const string packageId = "CommunityToolkit.Aspire.Hosting.Redis";
        var channel = CreateSourceIsolatedChannel(canonicalSource, channelPattern);
 
        var mappings = PackageSourceOverrideMappings.Create(source, channel, nugetServiceIndexOverride: null, packageId);
        var canonicalMappings = PackageSourceOverrideMappings.Create(canonicalSource, channel, nugetServiceIndexOverride: null, packageId);
 
        Assert.Equal(
        [
            (packageId, source),
            ("Aspire*", source),
            (PackageMapping.AllPackages, source),
            (PackageMapping.AllPackages, PackageSources.NuGetOrg)
        ],
            mappings.Select(mapping => (mapping.PackageFilter, mapping.Source)));
        Assert.Equal(
            IntegrationRestorePlanResolver.CreateGlobalPackagesFolderIdentity([canonicalSource], canonicalMappings),
            IntegrationRestorePlanResolver.CreateGlobalPackagesFolderIdentity([source], mappings));
 
        using var config = await NuGetTestHelper.CreateStandaloneConfigurationAsync(mappings);
        var settings = Settings.LoadSpecificSettings(config.ConfigFile.DirectoryName!, config.ConfigFile.Name);
        var nativeMapping = PackageSourceMapping.GetPackageSourceMapping(settings);
 
        Assert.Equal(["aspire-standalone"], nativeMapping.GetConfiguredPackageSources("Aspire.Hosting.Redis"));
        Assert.Equal(["aspire-standalone"], nativeMapping.GetConfiguredPackageSources("aspire.hosting.redis"));
        Assert.Equal(["aspire-standalone"], nativeMapping.GetConfiguredPackageSources(packageId));
        Assert.Equal(["aspire-standalone", "aspire-standalone-0"], nativeMapping.GetConfiguredPackageSources("Example.Dependency"));
    }
 
    [Theory]
    [InlineData("https://example.test/Feed/v3/index.json")]
    [InlineData("https://example.test/feed/v3/index.json/")]
    [InlineData("http://example.test/feed/v3/index.json")]
    public async Task Create_DistinctUriLocationsRemainEligible(string channelSource)
    {
        const string source = "https://example.test/feed/v3/index.json";
        const string packageId = "CommunityToolkit.Aspire.Hosting.Redis";
        var channel = CreateSourceIsolatedChannel(channelSource, "Aspire*");
 
        var mappings = PackageSourceOverrideMappings.Create(source, channel, nugetServiceIndexOverride: null, packageId);
 
        Assert.Equal(
        [
            (packageId, source),
            ("Aspire*", source),
            (PackageMapping.AllPackages, source),
            ("Aspire*", channelSource),
            (PackageMapping.AllPackages, PackageSources.NuGetOrg)
        ],
            mappings.Select(mapping => (mapping.PackageFilter, mapping.Source)));
 
        using var config = await NuGetTestHelper.CreateStandaloneConfigurationAsync(mappings);
        var settings = Settings.LoadSpecificSettings(config.ConfigFile.DirectoryName!, config.ConfigFile.Name);
        var nativeMapping = PackageSourceMapping.GetPackageSourceMapping(settings);
 
        Assert.Equal(["aspire-standalone", "aspire-standalone-0"], nativeMapping.GetConfiguredPackageSources("Aspire.Hosting.Redis"));
        Assert.Equal(["aspire-standalone"], nativeMapping.GetConfiguredPackageSources(packageId));
    }
 
    [Fact]
    public async Task Create_LocalPathAndFileUriPreserveNativeEligibilityAndCacheIdentity()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var source = workspace.CreateDirectory("Feed With Spaces").FullName;
        var fileUri = new Uri(source).AbsoluteUri;
        const string packageId = "CommunityToolkit.Aspire.Hosting.Redis";
        var channel = CreateSourceIsolatedChannel(fileUri, "Aspire*");
 
        var mappings = PackageSourceOverrideMappings.Create(source, channel, nugetServiceIndexOverride: null, packageId);
        var fileUriMappings = PackageSourceOverrideMappings.Create(fileUri, channel, nugetServiceIndexOverride: null, packageId);
 
        Assert.Equal(
        [
            (packageId, source),
            ("Aspire*", source),
            (PackageMapping.AllPackages, source),
            (PackageMapping.AllPackages, PackageSources.NuGetOrg)
        ],
            mappings.Select(mapping => (mapping.PackageFilter, mapping.Source)));
        Assert.Equal(
            IntegrationRestorePlanResolver.CreateGlobalPackagesFolderIdentity([fileUri], fileUriMappings),
            IntegrationRestorePlanResolver.CreateGlobalPackagesFolderIdentity([source], mappings));
 
        using var config = await NuGetTestHelper.CreateStandaloneConfigurationAsync(mappings);
        var settings = Settings.LoadSpecificSettings(config.ConfigFile.DirectoryName!, config.ConfigFile.Name);
        var nativeMapping = PackageSourceMapping.GetPackageSourceMapping(settings);
 
        Assert.Equal(["aspire-standalone"], nativeMapping.GetConfiguredPackageSources("Aspire.Hosting.Redis"));
        Assert.Equal(["aspire-standalone"], nativeMapping.GetConfiguredPackageSources(packageId));
    }
 
    [Fact]
    [PlatformSpecific(TestPlatforms.AnyUnix)]
    public void ResolveForWorkingDirectory_RelativePathContainingColon_ResolvesAgainstWorkingDirectory()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
 
        var result = PackageSourceOverrideMappings.ResolveForWorkingDirectory("relative:feed", workspace.WorkspaceRoot);
 
        Assert.Equal(Path.Combine(workspace.WorkspaceRoot.FullName, "relative:feed"), result);
    }
 
    [Theory]
    [InlineData("C:/feed")]
    [InlineData("a:/feed")]
    [PlatformSpecific(TestPlatforms.AnyUnix)]
    public void ResolveForWorkingDirectory_DosShapedRelativePath_ResolvesAgainstWorkingDirectory(string source)
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
 
        var result = PackageSourceOverrideMappings.ResolveForWorkingDirectory(source, workspace.WorkspaceRoot);
 
        Assert.Equal(Path.Combine(workspace.WorkspaceRoot.FullName, source), result);
    }
 
    [Fact]
    public void ResolveForWorkingDirectory_FileUri_ReturnsUnchanged()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        const string source = "file:///tmp/feed";
 
        var result = PackageSourceOverrideMappings.ResolveForWorkingDirectory(source, workspace.WorkspaceRoot);
 
        Assert.Equal(source, result);
    }
 
    [Fact]
    public void ResolveForWorkingDirectory_MalformedHttpSource_ReturnsUnchanged()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        const string source = "https://user:p#word@packages.example.com/v3/index.json";
 
        var result = PackageSourceOverrideMappings.ResolveForWorkingDirectory(source, workspace.WorkspaceRoot);
 
        Assert.Equal(source, result);
        Assert.True(PackageSourceOverrideMappings.HasCredentialMaterial(result));
        Assert.Null(PackageSourceOverrideMappings.GetMissingLocalDirectory(result));
    }
 
    [Fact]
    [PlatformSpecific(TestPlatforms.Windows)]
    public void ResolveForWorkingDirectory_WindowsFullyQualifiedPath_ReturnsUnchanged()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        const string source = @"C:\feed";
 
        var result = PackageSourceOverrideMappings.ResolveForWorkingDirectory(source, workspace.WorkspaceRoot);
 
        Assert.Equal(source, result);
    }
 
    private static PackageChannel CreateSourceIsolatedChannel(string source, string pattern)
        => PackageChannel.CreateExplicitChannel(
            "staging",
            PackageChannelQuality.Stable,
            [new PackageMapping(pattern, source), new PackageMapping(PackageMapping.AllPackages, PackageSources.NuGetOrg)],
            new FakeNuGetPackageCache(),
            new TestFeatures(),
            NullLogger.Instance,
            configureGlobalPackagesFolder: true);
}