File: NuGet\TemporaryNuGetConfigFileTests.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 System.Text;
using System.Xml.Linq;
using Aspire.Cli.NuGet;
using Aspire.Cli.Packaging;
using Aspire.Cli.Tests.TestServices;
using NuGet.Configuration;
 
namespace Aspire.Cli.Tests.NuGet;
 
public class TemporaryNuGetConfigFileTests(ITestOutputHelper outputHelper)
{
    [Fact]
    public async Task CreatePreviewAsync_DisposeOwnsOnlySiblingDraft()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var target = new FileInfo(Path.Combine(workspace.WorkspaceRoot.FullName, "NuGet.Config"));
        const string original = "<configuration />";
        await File.WriteAllTextAsync(target.FullName, original);
        var content = Encoding.UTF8.GetBytes("<configuration><candidate /></configuration>");
        using var draft = await TemporaryNuGetConfigFile.CreatePreviewAsync(target, content, TestContext.Current.CancellationToken);
 
        Assert.Equal(target.DirectoryName, draft.ConfigFile.DirectoryName);
        Assert.Equal(content, await File.ReadAllBytesAsync(draft.ConfigFile.FullName));
        if (!OperatingSystem.IsWindows())
        {
            Assert.Equal(UnixFileMode.UserRead | UnixFileMode.UserWrite, File.GetUnixFileMode(draft.ConfigFile.FullName));
        }
        draft.Dispose();
 
        Assert.Equal(original, await File.ReadAllTextAsync(target.FullName));
        Assert.True(Directory.Exists(target.DirectoryName));
        Assert.False(File.Exists(draft.ConfigFile.FullName));
    }
 
    [Fact]
    public async Task CreatePreviewAsync_InvalidContentCleansUpOnlyDraft()
    {
        using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
        var target = new FileInfo(Path.Combine(workspace.WorkspaceRoot.FullName, "NuGet.Config"));
        await File.WriteAllTextAsync(target.FullName, "<configuration />");
 
        await Assert.ThrowsAsync<System.Xml.XmlException>(() => TemporaryNuGetConfigFile.CreatePreviewAsync(
            target, Encoding.UTF8.GetBytes("<invalid"), TestContext.Current.CancellationToken));
 
        Assert.Equal([target.Name], workspace.WorkspaceRoot.EnumerateFiles().Select(static file => file.Name));
    }
 
    [Fact]
    public async Task CreateAsync_IncludesSourcesAndMappings()
    {
        using var config = await NuGetTestHelper.CreateStandaloneConfigurationAsync(
        [
            new PackageMapping("Aspire.*", "https://example.com/feed1"),
            new PackageMapping(PackageMapping.AllPackages, "https://example.com/feed2"),
            new PackageMapping("Microsoft.*", "https://example.com/feed1")
        ]);
 
        var document = XDocument.Load(config.ConfigFile.FullName);
        var sourceKeys = document.Descendants("packageSources")
            .Elements("add")
            .ToDictionary(
                static element => element.Attribute("value")!.Value,
                static element => element.Attribute("key")!.Value,
                PackageSourceIdentity.Comparer);
 
        Assert.Equal(2, sourceKeys.Count);
        Assert.Contains(
            document.Descendants("packageSourceMapping").Elements("packageSource"),
            element => element.Attribute("key")?.Value == sourceKeys["https://example.com/feed1"] &&
                element.Elements("package").Select(static package => package.Attribute("pattern")?.Value)
                    .SequenceEqual(["Aspire.*", "Microsoft.*"]));
        Assert.Contains(
            document.Descendants("packageSourceMapping").Elements("packageSource"),
            element => element.Attribute("key")?.Value == sourceKeys["https://example.com/feed2"] &&
                element.Elements("package").Single().Attribute("pattern")?.Value == PackageMapping.AllPackages);
    }
 
    [Fact]
    public async Task CreateAsync_WithConfiguredGlobalPackagesFolder_AddsConfigEntry()
    {
        using var config = await NuGetTestHelper.CreateStandaloneConfigurationAsync(
            [new PackageMapping("Aspire.*", "https://example.com/feed")],
            configureGlobalPackagesFolder: true,
            globalPackagesFolderValue: "/packages");
 
        var document = XDocument.Load(config.ConfigFile.FullName);
 
        Assert.Equal(
            "/packages",
            document.Descendants("config")
                .Elements("add")
                .Single(element => element.Attribute("key")?.Value == "globalPackagesFolder")
                .Attribute("value")?.Value);
    }
 
    [Fact]
    public async Task CreateAsync_PreservesCaseDistinctSourcePaths()
    {
        using var config = await NuGetTestHelper.CreateStandaloneConfigurationAsync(
        [
            new PackageMapping("Upper.*", "https://example.com/Feed/index.json"),
            new PackageMapping("Lower.*", "https://example.com/feed/index.json")
        ]);
 
        var document = XDocument.Load(config.ConfigFile.FullName);
        var sources = document.Descendants("packageSources")
            .Elements("add")
            .Select(static element => element.Attribute("value")!.Value)
            .ToArray();
 
        Assert.Equal(
        [
            "https://example.com/Feed/index.json",
            "https://example.com/feed/index.json"
        ],
            sources);
 
        var settings = Settings.LoadSpecificSettings(config.ConfigFile.DirectoryName!, config.ConfigFile.Name);
        var nativeMapping = PackageSourceMapping.GetPackageSourceMapping(settings);
 
        Assert.Equal(
            sources,
            new PackageSourceProvider(settings).LoadPackageSources().Select(source => source.Source));
        Assert.Equal(["aspire-standalone"], nativeMapping.GetConfiguredPackageSources("Upper.Example"));
        Assert.Equal(["aspire-standalone-0"], nativeMapping.GetConfiguredPackageSources("Lower.Example"));
    }
 
    [Fact]
    public async Task CreateRestoreOverlayAsync_UsesProvidedWriter()
    {
        using var config = await TemporaryNuGetConfigFile.CreateAsync(
            path => File.WriteAllText(
                path,
                """
                <configuration>
                  <packageSourceMapping>
                    <clear />
                    <packageSource key="private">
                      <package pattern="Aspire*" />
                    </packageSource>
                  </packageSourceMapping>
                </configuration>
                """));
 
        var document = XDocument.Load(config.ConfigFile.FullName);
 
        var mapping = Assert.Single(document.Descendants("packageSourceMapping"));
        Assert.NotNull(mapping.Element("clear"));
        Assert.Equal("private", mapping.Element("packageSource")?.Attribute("key")?.Value);
        Assert.Equal("Aspire*", mapping.Descendants("package").Single().Attribute("pattern")?.Value);
    }
 
    [Fact]
    public async Task RegenerateAsync_RewritesConfigAndCacheIdentity()
    {
        using var config = await TemporaryNuGetConfigFile.CreateAsync(
            path => File.WriteAllText(
                path,
                "<configuration><packageSourceMapping><clear /></packageSourceMapping></configuration>"));
        var originalIdentity = config.CacheIdentity;
 
        await config.RegenerateAsync(
            path => File.WriteAllText(
                path,
                "<configuration><packageSourceMapping><clear /><packageSource key=\"private\"><package pattern=\"Aspire*\" /></packageSource></packageSourceMapping></configuration>"));
 
        Assert.NotEqual(originalIdentity, config.CacheIdentity);
        Assert.Equal(
            "Aspire*",
            XDocument.Load(config.ConfigFile.FullName)
                .Descendants("package")
                .Single()
                .Attribute("pattern")?
                .Value);
    }
 
    [Fact]
    public async Task Dispose_RemovesDirectoryWhenFailedRegenerationDeletedConfig()
    {
        using var config = await TemporaryNuGetConfigFile.CreateAsync(
            path => File.WriteAllText(path, "<configuration />"));
        var directory = config.ConfigFile.Directory!.FullName;
 
        await Assert.ThrowsAsync<InvalidOperationException>(() => config.RegenerateAsync(path =>
        {
            File.Delete(path);
            throw new InvalidOperationException("Failed to regenerate the configuration.");
        }));
 
        Assert.False(config.ConfigFile.Exists);
        Assert.True(Directory.Exists(directory));
 
        config.Dispose();
 
        Assert.False(Directory.Exists(directory));
    }
 
    [Fact]
    public async Task CacheIdentity_DoesNotDependOnGlobalPackagesFolderLocation()
    {
        using var first = await TemporaryNuGetConfigFile.CreateAsync(
            path => File.WriteAllText(
                path,
                "<configuration><config><add key=\"globalPackagesFolder\" value=\"/packages/first\" /></config></configuration>"));
        using var second = await TemporaryNuGetConfigFile.CreateAsync(
            path => File.WriteAllText(
                path,
                "<configuration><config><add key=\"globalPackagesFolder\" value=\"/packages/second\" /></config></configuration>"));
 
        Assert.Equal(first.CacheIdentity, second.CacheIdentity);
    }
 
    [Fact]
    public async Task CacheIdentity_DoesNotChangeWhenGlobalPackagesFolderIsAdded()
    {
        using var config = await TemporaryNuGetConfigFile.CreateAsync(
            path => File.WriteAllText(path, "<configuration />"));
        var originalIdentity = config.CacheIdentity;
 
        await config.RegenerateAsync(
            path => File.WriteAllText(
                path,
                "<configuration><config><add key=\"globalPackagesFolder\" value=\"/packages\" /></config></configuration>"));
 
        Assert.Equal(originalIdentity, config.CacheIdentity);
    }
}