| File: Packaging\TemporaryNuGetConfigTests.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.Xml; using Aspire.Cli.Packaging; namespace Aspire.Cli.Tests.Packaging; public class TemporaryNuGetConfigTests { [Fact] public async Task CreateAsync_IncludesAllPackageSourceMappings() { // Arrange var mappings = new PackageMapping[] { new("Aspire.*", "https://example.com/feed1"), new(PackageMapping.AllPackages, "https://example.com/feed2"), // "*" filter new("Microsoft.*", "https://example.com/feed1") }; // Act using var tempConfig = await TemporaryNuGetConfig.CreateAsync(mappings); // Assert var configContent = await File.ReadAllTextAsync(tempConfig.ConfigFile.FullName); var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(configContent); // Verify that package source mappings section exists var packageSourceMappingNode = xmlDoc.SelectSingleNode("//packageSourceMapping"); Assert.NotNull(packageSourceMappingNode); // Verify all package sources are present var packageSourceNodes = xmlDoc.SelectNodes("//packageSourceMapping/packageSource"); Assert.NotNull(packageSourceNodes); Assert.Equal(2, packageSourceNodes.Count); // Two distinct sources // Verify that the AllPackages mapping is included var allPackagesMapping = xmlDoc.SelectSingleNode("//packageSourceMapping/packageSource[@key='https://example.com/feed2']/package[@pattern='*']"); Assert.NotNull(allPackagesMapping); // Verify other specific mappings are also included var aspireMapping = xmlDoc.SelectSingleNode("//packageSourceMapping/packageSource[@key='https://example.com/feed1']/package[@pattern='Aspire.*']"); Assert.NotNull(aspireMapping); var microsoftMapping = xmlDoc.SelectSingleNode("//packageSourceMapping/packageSource[@key='https://example.com/feed1']/package[@pattern='Microsoft.*']"); Assert.NotNull(microsoftMapping); } [Fact] public async Task CreateAsync_WithOnlyAllPackagesMappings_IncludesAllMappings() { // Arrange var mappings = new PackageMapping[] { new(PackageMapping.AllPackages, "https://feed1.example.com"), new(PackageMapping.AllPackages, "https://feed2.example.com") }; // Act using var tempConfig = await TemporaryNuGetConfig.CreateAsync(mappings); // Assert var configContent = await File.ReadAllTextAsync(tempConfig.ConfigFile.FullName); var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(configContent); // Verify that package source mappings section exists var packageSourceMappingNode = xmlDoc.SelectSingleNode("//packageSourceMapping"); Assert.NotNull(packageSourceMappingNode); // Verify all package sources are present var packageSourceNodes = xmlDoc.SelectNodes("//packageSourceMapping/packageSource"); Assert.NotNull(packageSourceNodes); Assert.Equal(2, packageSourceNodes.Count); // Two distinct sources // Verify that both AllPackages mappings are included var feed1Mapping = xmlDoc.SelectSingleNode("//packageSourceMapping/packageSource[@key='https://feed1.example.com']/package[@pattern='*']"); Assert.NotNull(feed1Mapping); var feed2Mapping = xmlDoc.SelectSingleNode("//packageSourceMapping/packageSource[@key='https://feed2.example.com']/package[@pattern='*']"); Assert.NotNull(feed2Mapping); } [Fact] public async Task CreateAsync_WithNoMappings_CreatesValidConfig() { // Arrange var mappings = Array.Empty<PackageMapping>(); // Act using var tempConfig = await TemporaryNuGetConfig.CreateAsync(mappings); // Assert var configContent = await File.ReadAllTextAsync(tempConfig.ConfigFile.FullName); var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(configContent); // Verify basic structure exists var configNode = xmlDoc.SelectSingleNode("//configuration"); Assert.NotNull(configNode); var packageSourcesNode = xmlDoc.SelectSingleNode("//packageSources"); Assert.NotNull(packageSourcesNode); // No package source mappings should exist when no mappings provided var packageSourceMappingNode = xmlDoc.SelectSingleNode("//packageSourceMapping"); Assert.Null(packageSourceMappingNode); } [Fact] public async Task CreateAsync_WithConfiguredGlobalPackagesFolder_AddsConfigEntry() { using var tempConfig = await TemporaryNuGetConfig.CreateAsync( [new PackageMapping("Aspire.*", "https://example.com/feed")], configureGlobalPackagesFolder: true); var configContent = await File.ReadAllTextAsync(tempConfig.ConfigFile.FullName); var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(configContent); var globalPackagesFolder = xmlDoc.SelectSingleNode("//config/add[@key='globalPackagesFolder']"); Assert.NotNull(globalPackagesFolder); Assert.Equal(".nugetpackages", globalPackagesFolder!.Attributes!["value"]!.Value); } [Fact] public async Task CreateAsync_WithExplicitGlobalPackagesFolderOverride_UsesOverrideValue() { // Callers that need the cache to outlive the temp config (e.g. PrebuiltAppHostServer's // staging path) supply an absolute, persistent path so BundleNuGetService manifest paths // remain valid after TemporaryNuGetConfig.Dispose deletes the temp directory. var overrideValue = Path.Combine(Path.GetTempPath(), "aspire-tests", "stable-cache", "deadbeef"); using var tempConfig = await TemporaryNuGetConfig.CreateAsync( [new PackageMapping("Aspire.*", "https://example.com/feed")], configureGlobalPackagesFolder: true, globalPackagesFolderValue: overrideValue); var configContent = await File.ReadAllTextAsync(tempConfig.ConfigFile.FullName); var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(configContent); var globalPackagesFolder = xmlDoc.SelectSingleNode("//config/add[@key='globalPackagesFolder']"); Assert.NotNull(globalPackagesFolder); Assert.Equal(overrideValue, globalPackagesFolder!.Attributes!["value"]!.Value); } [Fact] public async Task CreateAsync_WithoutConfiguredGlobalPackagesFolder_IgnoresOverride() { // When configureGlobalPackagesFolder is false the override is irrelevant — no // <config><add key="globalPackagesFolder"/> element should be emitted at all. using var tempConfig = await TemporaryNuGetConfig.CreateAsync( [new PackageMapping("Aspire.*", "https://example.com/feed")], configureGlobalPackagesFolder: false, globalPackagesFolderValue: "/should/not/appear"); var configContent = await File.ReadAllTextAsync(tempConfig.ConfigFile.FullName); var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(configContent); Assert.Null(xmlDoc.SelectSingleNode("//config/add[@key='globalPackagesFolder']")); } [Theory] [InlineData("https://example.com/feed")] [InlineData("/var/folders/X/hives/pr-17105/packages")] [InlineData(@"C:\Users\X\.aspire\hives\pr-17105\packages")] public async Task CreateAsync_PackageSourceAddKeyMatchesPackageSourceMappingKey(string source) { // Bug B defense: NuGet's packageSourceMapping lookup matches the // <packageSource key="..."> attribute against the source name registered // from <packageSources><add key="..." />. A future refactor that splits // those keys (or canonicalizes one side and not the other) would silently // drop the mapping. This invariant lives at the writer; pin it. // // Note that we ALSO need the source written here to be in the form NuGet // will accept after its own internal canonicalization (e.g. on macOS the // upstream caller must strip /private/var → /var before constructing the // PackageMapping — see CliPathHelper.StripMacOSFirmlinkPrefix and the // GetAspireHomeDirectory_OnMacOS_PrRouteWithFirmlinkedProcessPath test). // This test only pins the writer's symmetry contract. var mappings = new PackageMapping[] { new("Aspire*", source), new(PackageMapping.AllPackages, "https://api.nuget.org/v3/index.json"), }; using var tempConfig = await TemporaryNuGetConfig.CreateAsync(mappings); var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(await File.ReadAllTextAsync(tempConfig.ConfigFile.FullName)); // Collect <packageSources><add key="X" value="Y" /> entries (filter out <clear/>). var addNodes = xmlDoc.SelectNodes("//packageSources/add")!; var addKeys = new List<string>(); foreach (XmlNode add in addNodes) { addKeys.Add(add.Attributes!["key"]!.Value); Assert.Equal(add.Attributes!["key"]!.Value, add.Attributes!["value"]!.Value); } // Collect <packageSourceMapping><packageSource key="X"> entries. var mappingNodes = xmlDoc.SelectNodes("//packageSourceMapping/packageSource")!; var mappingKeys = new List<string>(); foreach (XmlNode m in mappingNodes) { mappingKeys.Add(m.Attributes!["key"]!.Value); } // Every mapping key must have a matching <add key>, byte-for-byte. foreach (var mappingKey in mappingKeys) { Assert.Contains(mappingKey, addKeys); } // The mapping for our source must be present and exactly equal the input source. Assert.Contains(source, mappingKeys); } }