File: TemplateManifest\TemplateManifestCacheTests.cs
Web Access
Project: src\tests\Infrastructure.Tests\Infrastructure.Tests.csproj (Infrastructure.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.Json;
using System.Text.Json.Nodes;
using System.Xml.Linq;
using Aspire.TestUtilities;
using VerifyXunit;
using Xunit;
 
namespace Infrastructure.Tests.TemplateManifest;
 
public sealed class TemplateManifestCacheTests(ITestOutputHelper output)
{
    [Fact]
    [RequiresTools(["pwsh"])]
    public async Task RebuiltPackageRefreshesEveryGraphWithoutChangingItsVersion()
    {
        using var workspace = TemporaryWorkspace.Create(output);
        var environment = new TemplateManifestTestEnvironment(workspace, output);
        // Distinct projects share the same package, just like independently restored template
        // options. Check each assets file: unioning them can conceal a stale sibling graph.
        const int graphCount = 2;
        for (var i = 0; i < graphCount; i++)
        {
            File.WriteAllText(Path.Combine(environment.TemplateDirectory, $"Project{i}.csproj"), $$"""
                <Project Sdk="Microsoft.NET.Sdk">
                  <PropertyGroup>
                    <TargetFramework>net11.0</TargetFramework>
                    <AssemblyName>Project{{i}}</AssemblyName>
                  </PropertyGroup>
                  <ItemGroup><PackageReference Include="Local.CacheFixture" Version="1.0.0-dev" /></ItemGroup>
                </Project>
                """);
        }
        TemplateManifestTestEnvironment.CreatePackage(environment.ExternalFeed, "External.CacheFixture", "1.0.0");
        TemplateManifestTestEnvironment.CreatePackage(environment.ExternalFeed, "External.CacheFixture", "2.0.0");
        var staleSharedPackage = TemplateManifestTestEnvironment.CreatePackage(environment.SharedCache, "Local.CacheFixture", "1.0.0-dev", ("External.CacheFixture", "[1.0.0]"));
        var sharedPackageBytes = File.ReadAllBytes(staleSharedPackage);
        var configBefore = File.ReadAllText(environment.ConfigPath);
 
        using var command = environment.CreateCommand()
            // Restore one graph before checking its siblings for no-op eligibility, so
            // cache repopulation cannot depend on thread scheduling in this regression test.
            .WithEnvironmentVariable("RestoreDisableParallel", "true");
 
        TemplateManifestTestEnvironment.CreatePackage(environment.BuiltFeed, "Local.CacheFixture", "1.0.0-dev", ("External.CacheFixture", "[1.0.0]"));
        (await command.ExecuteAsync()).EnsureSuccessful();
        AssertGraphs(environment.RestoreDirectory, graphCount, "1.0.0");
        var firstManifest = JsonNode.Parse(File.ReadAllText(environment.ManifestPath));
 
        // Keep the private cache and all assets files. Only replace the same-version nupkg.
        TemplateManifestTestEnvironment.CreatePackage(environment.BuiltFeed, "Local.CacheFixture", "1.0.0-dev", ("External.CacheFixture", "[2.0.0]"));
        (await command.ExecuteAsync()).EnsureSuccessful();
        AssertGraphs(environment.RestoreDirectory, graphCount, "2.0.0");
        var secondManifest = JsonNode.Parse(File.ReadAllText(environment.ManifestPath));
 
        var manifests = new JsonObject
        {
            ["First"] = firstManifest,
            ["Second"] = secondManifest
        };
        await Verifier.Verify(manifests.ToJsonString(new JsonSerializerOptions { WriteIndented = true }), "json").UseDirectory("Snapshots");
        Assert.Equal(sharedPackageBytes, File.ReadAllBytes(staleSharedPackage));
        Assert.Equal(configBefore, File.ReadAllText(environment.ConfigPath));
        Assert.Equal([Path.GetFileName(staleSharedPackage)], Directory.GetFiles(environment.SharedCache).Select(Path.GetFileName));
        Assert.Empty(Directory.GetDirectories(environment.SharedCache));
    }
 
    private static void AssertGraphs(string restoreDirectory, int expectedCount, string dependencyVersion)
    {
        var solution = XDocument.Load(Path.Combine(restoreDirectory, "TemplateDependencies.slnx"));
        var projects = solution.Root!.Elements("Project").ToArray();
        Assert.Equal(expectedCount, projects.Length);
        foreach (var project in projects)
        {
            var path = project.Attribute("Path")!.Value.Replace('/', Path.DirectorySeparatorChar);
            var directory = Path.GetDirectoryName(Path.Combine(restoreDirectory, path))!;
            using var assets = JsonDocument.Parse(File.ReadAllText(Path.Combine(directory, "obj", "project.assets.json")));
            Assert.Equal(
                [$"External.CacheFixture/{dependencyVersion}", "Local.CacheFixture/1.0.0-dev"],
                assets.RootElement.GetProperty("libraries").EnumerateObject().Select(p => p.Name).Order());
            var targets = assets.RootElement.GetProperty("targets").EnumerateObject().ToArray();
            var target = Assert.Single(targets);
            Assert.Equal($"[{dependencyVersion}]", target.Value.GetProperty("Local.CacheFixture/1.0.0-dev")
                .GetProperty("dependencies").GetProperty("External.CacheFixture").GetString());
        }
    }
}