File: NuGet\NuGetConfigurationQueryTests.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.DotNet;
using Aspire.Cli.Packaging;
using Aspire.Cli.Projects;
using Aspire.Cli.Tests.TestServices;
using Aspire.Cli.Tests.Utils;
using Microsoft.Extensions.Logging.Abstractions;
 
namespace Aspire.Cli.Tests.NuGet;
 
public class NuGetConfigurationQueryTests(ITestOutputHelper outputHelper)
{
    [Theory]
    [InlineData(false, "Contoso.*")]
    [InlineData(true, "Contoso.*")]
    [InlineData(false, "Aspire.Hosting.Private")]
    public async Task StableChannelPolicy_RejectsAmbiguousPrivateDependencyRouting(bool separateAlias, string narrowPattern)
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var appHostDirectory = workspace.CreateDirectory("AppHost");
        const string privateFeed = "https://example.test/team/v3/index.json";
        var configPath = Path.Combine(workspace.Path, "nuget.config");
        var original = $$"""
            <configuration>
              <packageSources>
                <clear />
                <add key="aspire-daily" value="{{privateFeed}}" />
                {{(separateAlias ? $"""<add key="company" value="{privateFeed}" />""" : "")}}
                <add key="public" value="{{PackageSources.NuGetOrg}}" />
              </packageSources>
              <packageSourceMapping>
                <clear />
                <packageSource key="aspire-daily">
                  <package pattern="Aspire*" />
                  {{(separateAlias ? "" : $"""<package pattern="{narrowPattern}" />""")}}
                </packageSource>
                {{(separateAlias ? $"""<packageSource key="company"><package pattern="{narrowPattern}" /></packageSource>""" : "")}}
                <packageSource key="public"><package pattern="*" /></packageSource>
              </packageSourceMapping>
            </configuration>
            """;
        await File.WriteAllTextAsync(configPath, original);
        await File.WriteAllTextAsync(Path.Combine(appHostDirectory.FullName, Aspire.Cli.Configuration.AspireConfigFile.FileName), $$"""
            {
              "integrationSources": {
                "default": {"enabled":false},
                "team": {"source":"{{privateFeed}}","patterns":["Contoso.*"]}
              }
            }
            """);
        var service = new BundleNuGetService(NullLogger<BundleNuGetService>.Instance, NuGetTestHelper.CreateClient(),
            new TestConfigurationService { SettingsFilePath = Path.Combine(workspace.Path, "global.json") });
        var settings = service.GetNuGetSettings(appHostDirectory.FullName, TestContext.Current.CancellationToken);
        var channel = PackageChannel.CreateExplicitChannel(
            "stable", PackageChannelQuality.Stable, [new("*", PackageSources.NuGetOrg)],
            new FakeNuGetPackageCache(), new TestFeatures(), NullLogger.Instance);
 
        Assert.Equal([privateFeed], NuGetTestHelper.GetEligiblePackageSources(appHostDirectory.FullName, "Aspire.Private.Dependency"));
        Assert.Throws<ProjectUpdaterException>(() =>
            DotNetAppHostNuGetConfigBuilder.BuildStable(settings, "test", channel));
        Assert.Equal(original, await File.ReadAllTextAsync(configPath));
        Assert.False(File.Exists(Path.Combine(appHostDirectory.FullName, "nuget.config")));
        Assert.Equal([privateFeed], NuGetTestHelper.GetEligiblePackageSources(appHostDirectory.FullName, "Aspire.Private.Dependency"));
    }
 
    [Theory]
    [InlineData("Aspire.Hosting.Private", false, true)]
    [InlineData("Aspire.Hosting.Private*", false, true)]
    [InlineData("Aspire.Hosting.Private", true, true)]
    [InlineData("Aspire.Hosting.Private", false, false)]
    [InlineData("*", false, true)]
    public async Task StableChannelPolicy_PreservesIntegrationFeedAndRouting(
        string privatePattern, bool separateIntegrationAlias, bool isEnabled)
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var appHostDirectory = workspace.CreateDirectory("AppHost");
        const string privateFeed = "https://example.test/team/v3/index.json";
        const string sdkSourceKey = "aspire-daily";
        var integrationKey = separateIntegrationAlias ? "company" : sdkSourceKey;
        var configPath = Path.Combine(workspace.Path, "nuget.config");
        var original = $$"""
            <configuration>
              <packageSources>
                <clear />
                <add key="{{sdkSourceKey}}" value="{{privateFeed}}" />
                <add key="aspire-staging" value="https://example.test/sdk/v3/index.json" />
                {{(separateIntegrationAlias ? $"""<add key="{integrationKey}" value="{privateFeed}" />""" : "")}}
                <add key="public" value="{{PackageSources.NuGetOrg}}" />
              </packageSources>
              <packageSourceCredentials>
                <aspire-daily><add key="Username" value="test-user" /><add key="ClearTextPassword" value="test-password" /></aspire-daily>
              </packageSourceCredentials>
              {{(isEnabled ? "" : """<disabledPackageSources><add key="aspire-daily" value="true" /></disabledPackageSources>""")}}
              <packageSourceMapping>
                <clear />
                <packageSource key="aspire-staging"><package pattern="Aspire*" /></packageSource>
                {{(separateIntegrationAlias ? "" : $"""<packageSource key="{sdkSourceKey}"><package pattern="{privatePattern}" /></packageSource>""")}}
                {{(separateIntegrationAlias ? $"""<packageSource key="{integrationKey}"><package pattern="{privatePattern}" /></packageSource>""" : "")}}
                <packageSource key="public"><package pattern="*" /></packageSource>
              </packageSourceMapping>
            </configuration>
            """;
        await File.WriteAllTextAsync(configPath, original);
        await File.WriteAllTextAsync(Path.Combine(appHostDirectory.FullName, Aspire.Cli.Configuration.AspireConfigFile.FileName), $$"""
            {
              "integrationSources": {
                "default": {"enabled":false},
                "team": {"source":"{{privateFeed}}","patterns":["Aspire.Hosting.Private"]}
              }
            }
            """);
        var service = new BundleNuGetService(NullLogger<BundleNuGetService>.Instance, NuGetTestHelper.CreateClient(),
            new TestConfigurationService { SettingsFilePath = Path.Combine(workspace.Path, "global.json") });
        var settings = service.GetNuGetSettings(appHostDirectory.FullName, TestContext.Current.CancellationToken);
        Assert.True(Assert.Single(settings.Sources, source => source.Name == sdkSourceKey).IsIntegrationSource);
        var channel = PackageChannel.CreateExplicitChannel(
            "stable", PackageChannelQuality.Stable, [new("*", PackageSources.NuGetOrg)],
            new FakeNuGetPackageCache(), new TestFeatures(), NullLogger.Instance);
        if (privatePattern == PackageMapping.AllPackages)
        {
            Assert.Throws<ProjectUpdaterException>(() =>
                DotNetAppHostNuGetConfigBuilder.BuildStable(settings, "test", channel));
            Assert.Equal(original, await File.ReadAllTextAsync(configPath));
            Assert.False(File.Exists(Path.Combine(appHostDirectory.FullName, "nuget.config")));
            return;
        }
 
        var configuration = DotNetAppHostNuGetConfigBuilder.BuildStable(settings, "test", channel);
        using var preview = await service.CreateConfigurationPreviewAsync(
            appHostDirectory, configuration, globalPackagesFolder: null, TestContext.Current.CancellationToken);
        var previewDirectory = preview.EffectiveWorkingDirectory.FullName;
        string[] privateSources = isEnabled ? [privateFeed] : [];
 
        Assert.Equal([PackageSources.NuGetOrg], NuGetTestHelper.GetEligiblePackageSources(previewDirectory, "Aspire.AppHost.Sdk"));
        Assert.Equal([PackageSources.NuGetOrg], NuGetTestHelper.GetEligiblePackageSources(previewDirectory, "Aspire.Hosting.AppHost"));
        Assert.Equal(privateSources, NuGetTestHelper.GetEligiblePackageSources(previewDirectory, "Aspire.Hosting.Private"));
        Assert.Equal(original, await File.ReadAllTextAsync(configPath));
        var merger = new DotNetAppHostNuGetConfigMerger(service);
        var candidate = await merger.PrepareAsync(
            workspace.WorkspaceRoot, configuration, createIfMissing: false, globalPackagesFolder: null, TestContext.Current.CancellationToken);
        Assert.NotNull(candidate);
        await DotNetAppHostNuGetConfigMerger.ApplyAsync(candidate, TestContext.Current.CancellationToken);
 
        Assert.Equal([PackageSources.NuGetOrg], NuGetTestHelper.GetEligiblePackageSources(appHostDirectory.FullName, "Aspire.AppHost.Sdk"));
        Assert.Equal([PackageSources.NuGetOrg], NuGetTestHelper.GetEligiblePackageSources(appHostDirectory.FullName, "Aspire.Hosting.AppHost"));
        Assert.Equal(privateSources, NuGetTestHelper.GetEligiblePackageSources(appHostDirectory.FullName, "Aspire.Hosting.Private"));
        var retained = Assert.Single(service.GetNuGetSettings(appHostDirectory.FullName, TestContext.Current.CancellationToken).Sources,
            source => source.Name == sdkSourceKey);
        Assert.Equal(isEnabled, retained.IsEnabled);
        Assert.True(retained.HasCredentials);
        var persistedSource = Assert.Single(new global::NuGet.Configuration.PackageSourceProvider(
            NuGetTestHelper.LoadSettings(appHostDirectory.FullName)).LoadPackageSources(), source => source.Name == sdkSourceKey);
        Assert.NotNull(persistedSource.Credentials);
        Assert.Equal("test-user", persistedSource.Credentials.Username);
        Assert.Equal("test-password", persistedSource.Credentials.Password);
    }
 
    [Theory]
    [InlineData(null)]
    [InlineData("*")]
    [InlineData("Aspire*")]
    public async Task ExactIntegrationSelection_PreservesSdkFeedAfterPersistence(string? sdkPattern)
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var sdkFeed = workspace.CreateDirectory(Path.Combine(".aspire", "hives", "pr-21366", "packages")).FullName;
        var integrationFeed = workspace.CreateDirectory("integration-feed").FullName;
        const string sdkSourceKey = "aspire-pr-21366";
        const string packageId = "Contoso.Hosting.One";
        await File.WriteAllTextAsync(Path.Combine(workspace.Path, "nuget.config"), $$"""
            <configuration>
              <packageSources>
                <clear />
                <add key="{{sdkSourceKey}}" value="{{sdkFeed}}" />
                <add key="public" value="{{PackageSources.NuGetOrg}}" />
              </packageSources>
              {{(sdkPattern is null ? "" : $"""
              <packageSourceMapping>
                <packageSource key="{sdkSourceKey}"><package pattern="{sdkPattern}" /></packageSource>
                <packageSource key="public"><package pattern="*" /></packageSource>
              </packageSourceMapping>
              """)}}
            </configuration>
            """);
        var service = NuGetTestHelper.CreateService();
        Assert.True(Assert.Single(service.GetNuGetSettings(workspace.Path, TestContext.Current.CancellationToken).Sources,
            source => source.Name == sdkSourceKey).IsCliManaged);
        var configuration = service.BuildAppHostConfiguration(
            workspace.WorkspaceRoot, "integration-add",
            [new(packageId, integrationFeed) { SourceKey = "team" }],
            overrideIntegrationSources: false, TestContext.Current.CancellationToken);
        var candidate = await new DotNetAppHostNuGetConfigMerger(service).PrepareAsync(
            workspace.WorkspaceRoot, configuration, createIfMissing: false,
            globalPackagesFolder: null, TestContext.Current.CancellationToken);
        Assert.NotNull(candidate);
 
        await DotNetAppHostNuGetConfigMerger.ApplyAsync(candidate, TestContext.Current.CancellationToken);
 
        Assert.True(Assert.Single(service.GetNuGetSettings(workspace.Path, TestContext.Current.CancellationToken).Sources,
            source => source.Name == sdkSourceKey).IsEnabled);
        Assert.Equal(sdkPattern is not null,
            service.IsPackageSourceMappingEnabled(workspace.WorkspaceRoot, TestContext.Current.CancellationToken));
        string[] allSources = [sdkFeed, PackageSources.NuGetOrg, integrationFeed];
        string[] sdkSources = sdkPattern switch
        {
            null => allSources,
            "*" => [sdkFeed, PackageSources.NuGetOrg],
            _ => [sdkFeed]
        };
        Assert.Equal(sdkSources.Order(StringComparer.Ordinal),
            NuGetTestHelper.GetEligiblePackageSources(workspace.Path, "Aspire.Hosting.AppHost"));
        Assert.Equal((sdkPattern is null ? allSources : [integrationFeed]).Order(StringComparer.Ordinal),
            NuGetTestHelper.GetEligiblePackageSources(workspace.Path, packageId));
    }
 
    [Theory]
    [InlineData(PackageChannelNames.Daily, false)]
    [InlineData(PackageChannelNames.Daily, true)]
    [InlineData(PackageChannelNames.Staging, false)]
    [InlineData(PackageChannelNames.Staging, true)]
    [InlineData(PackageChannelNames.Stable, false)]
    [InlineData(PackageChannelNames.Stable, true)]
    public async Task ChannelPolicy_PreservesAmbientFeedsAndRetiresOwnedSources(string channelName, bool hasMappings)
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var appHostDirectory = workspace.CreateDirectory("AppHost");
        var oldHive = workspace.CreateDirectory(".aspire/hives/pr-old/packages").FullName;
        const string customerSource = "https://pkgs.dev.azure.com/contoso/team/_packaging/aspire-company/nuget/v3/index.json";
        const string privateSource = "https://packages.example.com/v3/index.json";
        const string sharedSource = "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json";
        const string oldStagingSource = "https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-microsoft-aspire-abcdef12/nuget/v3/index.json";
        const string newStagingSource = "https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-microsoft-aspire-12345678/nuget/v3/index.json";
        var configPath = Path.Combine(workspace.WorkspaceRoot.FullName, "NuGet.Config");
        var original = $$"""
            <configuration>
              <packageSources>
                <clear />
                <add key="customer" value="{{customerSource}}" />
                <add key="private" value="{{privateSource}}" />
                <add key="shared" value="{{sharedSource}}" />
                <add key="public" value="{{PackageSources.NuGetOrg}}" />
                <add key="old-staging" value="{{oldStagingSource}}" />
                <add key="old-hive" value="{{oldHive}}" />
              </packageSources>
              {{(hasMappings ? """
              <packageSourceMapping>
                <packageSource key="customer"><package pattern="*" /></packageSource>
                <packageSource key="private"><package pattern="*" /></packageSource>
                <packageSource key="shared"><package pattern="*" /></packageSource>
                <packageSource key="public"><package pattern="*" /></packageSource>
                <packageSource key="old-staging"><package pattern="*" /></packageSource>
                <packageSource key="old-hive"><package pattern="*" /></packageSource>
              </packageSourceMapping>
              """ : "")}}
            </configuration>
            """;
        await File.WriteAllTextAsync(configPath, original);
        var service = NuGetTestHelper.CreateService();
        var selectedSource = channelName switch
        {
            PackageChannelNames.Daily => sharedSource,
            PackageChannelNames.Staging => newStagingSource,
            _ => PackageSources.NuGetOrg
        };
        var channel = PackageChannel.CreateExplicitChannel(
            channelName, channelName == PackageChannelNames.Stable ? PackageChannelQuality.Stable : PackageChannelQuality.Both,
            channelName == PackageChannelNames.Stable
                ? [new PackageMapping("*", PackageSources.NuGetOrg)]
                : [new PackageMapping("Aspire*", selectedSource), new PackageMapping("*", PackageSources.NuGetOrg)],
            new FakeNuGetPackageCache(), new TestFeatures(), NullLogger.Instance);
        var configuration = channelName == PackageChannelNames.Stable
            ? DotNetAppHostNuGetConfigBuilder.BuildStable(
                service.GetNuGetSettings(appHostDirectory.FullName, TestContext.Current.CancellationToken),
                "test", channel)
            : service.BuildChannelConfiguration(
                appHostDirectory, "test", channel, packageSourceOverride: null,
                nugetServiceIndexOverride: null, TestContext.Current.CancellationToken);
 
        var overlay = Assert.IsType<NuGetConfigOverlay>(configuration.Overlay);
        Assert.Equal(["old-hive", "old-staging"], overlay.RetiredSourceKeys.Order(StringComparer.Ordinal));
        using var preview = await service.CreateConfigurationPreviewAsync(
            appHostDirectory, configuration, globalPackagesFolder: null, TestContext.Current.CancellationToken);
        var ambientSources = new[] { customerSource, privateSource, sharedSource, PackageSources.NuGetOrg }
            .Order(StringComparer.Ordinal).ToArray();
        string[] aspireSources = channelName == PackageChannelNames.Stable ? ambientSources : [selectedSource];
        var persistedSources = ambientSources.Append(selectedSource).Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray();
        var previewDirectory = preview.EffectiveWorkingDirectory.FullName;
 
        Assert.Equal(ambientSources, NuGetTestHelper.GetEligiblePackageSources(previewDirectory, "Contoso.Package"));
        Assert.Equal(aspireSources, NuGetTestHelper.GetEligiblePackageSources(previewDirectory, "Aspire.Hosting"));
        Assert.Equal(original, await File.ReadAllTextAsync(configPath));
 
        var merger = new DotNetAppHostNuGetConfigMerger(service);
        if (channelName == PackageChannelNames.Daily && hasMappings)
        {
            await Assert.ThrowsAsync<Aspire.Cli.Projects.ProjectUpdaterException>(() =>
                merger.PrepareAsync(workspace.WorkspaceRoot, configuration, createIfMissing: false,
                    globalPackagesFolder: null, TestContext.Current.CancellationToken));
            Assert.Equal(original, await File.ReadAllTextAsync(configPath));
            return;
        }
 
        var candidate = await merger.PrepareAsync(
            workspace.WorkspaceRoot, configuration, createIfMissing: false,
            globalPackagesFolder: null, TestContext.Current.CancellationToken);
        Assert.NotNull(candidate);
        await DotNetAppHostNuGetConfigMerger.ApplyAsync(candidate, TestContext.Current.CancellationToken);
 
        Assert.Equal(hasMappings ? ambientSources : persistedSources,
            NuGetTestHelper.GetEligiblePackageSources(appHostDirectory.FullName, "Contoso.Package"));
        Assert.Equal(hasMappings ? aspireSources : persistedSources,
            NuGetTestHelper.GetEligiblePackageSources(appHostDirectory.FullName, "Aspire.Hosting"));
    }
 
    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public async Task ChannelPolicy_RetiresUnselectedHivesBeforePersistence(bool hasMappings)
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var appHostDirectory = workspace.CreateDirectory("AppHost");
        var oldHive = workspace.CreateDirectory(".aspire/hives/pr-old/packages").FullName;
        var newHive = workspace.CreateDirectory(".aspire/hives/pr-new/packages").FullName;
        var configPath = Path.Combine(workspace.WorkspaceRoot.FullName, "NuGet.Config");
        var original = $$"""
            <configuration>
              <packageSources>
                <clear />
                <add key="old-hive" value="{{oldHive}}" />
                <add key="private" value="https://private.example/v3/index.json" />
              </packageSources>
              {{(hasMappings ? """
              <packageSourceMapping>
                <packageSource key="old-hive"><package pattern="Aspire*" /></packageSource>
                <packageSource key="private"><package pattern="Contoso.*" /></packageSource>
              </packageSourceMapping>
              """ : "")}}
            </configuration>
            """;
        await File.WriteAllTextAsync(configPath, original);
        var service = CreateService();
        var configuration = service.BuildAppHostConfiguration(
            appHostDirectory, "test", [new PackageMapping("Aspire*", newHive)],
            overrideIntegrationSources: false,
            cancellationToken: TestContext.Current.CancellationToken);
        using var preview = await service.CreateConfigurationPreviewAsync(
            appHostDirectory, configuration, globalPackagesFolder: null, TestContext.Current.CancellationToken);
        var previewDirectory = preview.EffectiveWorkingDirectory.FullName;
 
        Assert.Equal([newHive], NuGetTestHelper.GetEligiblePackageSources(previewDirectory, "Aspire.Hosting"));
        Assert.Equal(hasMappings ? [] : ["https://private.example/v3/index.json"],
            NuGetTestHelper.GetEligiblePackageSources(previewDirectory, "Example.Dependency"));
        Assert.Equal(["https://private.example/v3/index.json"],
            NuGetTestHelper.GetEligiblePackageSources(previewDirectory, "Contoso.Package"));
        Assert.Equal(original, await File.ReadAllTextAsync(configPath));
 
        var candidate = await new DotNetAppHostNuGetConfigMerger(service).PrepareAsync(
            workspace.WorkspaceRoot, configuration, createIfMissing: false,
            globalPackagesFolder: null, TestContext.Current.CancellationToken);
        Assert.NotNull(candidate);
        await DotNetAppHostNuGetConfigMerger.ApplyAsync(candidate, TestContext.Current.CancellationToken);
        var persistedLocations = new[] { newHive, "https://private.example/v3/index.json" }.Order(StringComparer.Ordinal).ToArray();
        Assert.Equal(hasMappings ? [newHive] : persistedLocations,
            NuGetTestHelper.GetEligiblePackageSources(appHostDirectory.FullName, "Aspire.Hosting"));
        Assert.Equal(hasMappings ? [] : persistedLocations,
            NuGetTestHelper.GetEligiblePackageSources(appHostDirectory.FullName, "Example.Dependency"));
        var persistedSources = new global::NuGet.Configuration.PackageSourceProvider(
            global::NuGet.Configuration.Settings.LoadDefaultSettings(appHostDirectory.FullName)).LoadPackageSources();
        Assert.Equal(
            new[] { "https://private.example/v3/index.json", newHive }.Order(StringComparer.Ordinal),
            persistedSources.Select(static source => source.Source).Order(StringComparer.Ordinal));
    }
 
    [Fact]
    public async Task ChannelPolicy_CanChangeGeneratedSourceWithoutReusingItsCredentials()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var directory = workspace.WorkspaceRoot;
        await File.WriteAllTextAsync(
            Path.Combine(directory.FullName, "NuGet.Config"),
            """
            <configuration>
              <packageSources>
                <clear />
                <add key="aspire-test" value="https://old.example/v3/index.json" />
              </packageSources>
              <packageSourceCredentials>
                <aspire-test>
                  <add key="Username" value="test-user" />
                  <add key="ClearTextPassword" value="test-password" />
                </aspire-test>
              </packageSourceCredentials>
              <packageSourceMapping>
                <packageSource key="aspire-test"><package pattern="Aspire*" /></packageSource>
              </packageSourceMapping>
            </configuration>
            """);
        var service = CreateService();
        var configuration = service.BuildAppHostConfiguration(
            directory, "test", [new PackageMapping("Aspire*", "https://new.example/v3/index.json")],
            overrideIntegrationSources: false,
            cancellationToken: TestContext.Current.CancellationToken);
        using var preview = await service.CreateConfigurationPreviewAsync(
            directory, configuration, globalPackagesFolder: null, TestContext.Current.CancellationToken);
        var previewSettings = global::NuGet.Configuration.Settings.LoadDefaultSettings(preview.EffectiveWorkingDirectory.FullName);
        var selectedSource = new global::NuGet.Configuration.PackageSourceProvider(previewSettings).LoadPackageSources()
            .Single(static source => source.IsEnabled);
 
        Assert.Equal("aspire-test-0", selectedSource.Name);
        Assert.Null(selectedSource.Credentials);
        Assert.Equal(["https://new.example/v3/index.json"], NuGetTestHelper.GetEligiblePackageSources(preview.EffectiveWorkingDirectory.FullName, "Aspire.Hosting"));
        await new DotNetAppHostNuGetConfigMerger(service).CreateOrUpdateAsync(
            directory, configuration, createIfMissing: false, globalPackagesFolder: null,
            confirmationCallback: null, TestContext.Current.CancellationToken);
        Assert.Equal(["https://new.example/v3/index.json"], NuGetTestHelper.GetEligiblePackageSources(directory.FullName, "Aspire.Hosting"));
    }
 
    [Fact]
    public void IsPackageSourceMappingEnabled_UsesNuGetConfigHierarchy()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var projectDirectory = workspace.CreateDirectory("AppHost");
        var configPath = Path.Combine(workspace.WorkspaceRoot.FullName, "NuGet.Config");
        File.WriteAllText(
            configPath,
            """
            <configuration>
              <packageSourceMapping>
                <packageSource key="private">
                  <package pattern="Aspire*" />
                </packageSource>
              </packageSourceMapping>
            </configuration>
            """);
        var service = CreateService();
 
        var enabled = service.IsPackageSourceMappingEnabled(
            projectDirectory,
            TestContext.Current.CancellationToken);
 
        Assert.True(enabled);
    }
 
    [Fact]
    public void IsPackageSourceMappingEnabled_MatchesNuGetSectionAndItemSemantics()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var nonCanonicalDirectory = workspace.CreateDirectory("noncanonical");
        var mappingThenClearDirectory = workspace.CreateDirectory("mapping-then-clear");
        var clearThenMappingDirectory = workspace.CreateDirectory("clear-then-mapping");
 
        File.WriteAllText(
            Path.Combine(nonCanonicalDirectory.FullName, "NuGet.Config"),
            """
            <configuration>
              <PackageSourceMapping>
                <packageSource key="private">
                  <package pattern="*" />
                </packageSource>
              </PackageSourceMapping>
              <packageSourceMapping>
                <clear />
              </packageSourceMapping>
            </configuration>
            """);
        File.WriteAllText(
            Path.Combine(mappingThenClearDirectory.FullName, "NuGet.Config"),
            """
            <configuration>
              <packageSourceMapping>
                <packageSource key="private">
                  <package pattern="*" />
                </packageSource>
                <clear />
              </packageSourceMapping>
            </configuration>
            """);
        File.WriteAllText(
            Path.Combine(clearThenMappingDirectory.FullName, "NuGet.Config"),
            """
            <configuration>
              <packageSourceMapping>
                <clear />
                <PackageSource key="private">
                  <package pattern="*" />
                </PackageSource>
              </packageSourceMapping>
            </configuration>
            """);
        var service = CreateService();
 
        Assert.False(service.IsPackageSourceMappingEnabled(
            nonCanonicalDirectory,
            TestContext.Current.CancellationToken));
        Assert.False(service.IsPackageSourceMappingEnabled(
            mappingThenClearDirectory,
            TestContext.Current.CancellationToken));
        Assert.True(service.IsPackageSourceMappingEnabled(
            clearThenMappingDirectory,
            TestContext.Current.CancellationToken));
    }
 
    private static BundleNuGetService CreateService()
    {
        var nuGetClient = new NuGetClient(
            new TestFeatures(),
            new TestEnvironment(),
            NullLogger<NuGetClient>.Instance);
        var bundleNuGetService = new BundleNuGetService(
            NullLogger<BundleNuGetService>.Instance,
            nuGetClient,
            new TestConfigurationService());
        return bundleNuGetService;
    }
}