File: Utils\CliUpdateNotificationServiceTests.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.Interaction;
using Aspire.Cli.NuGet;
using Aspire.Cli.Tests.TestServices;
using Aspire.Cli.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Semver;
using Xunit;
 
namespace Aspire.Cli.Tests.Utils;
 
public class CliUpdateNotificationServiceTests(ITestOutputHelper outputHelper)
{
    [Fact]
    public async Task PrereleaseWillRecommendUpgradeToPrereleaseOnSameVersionFamily()
    {
        var currentVersion = VersionHelper.GetDefaultTemplateVersion();
        TaskCompletionSource<string> suggestedVersionTcs = new();
 
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.NuGetPackageCacheFactory = (sp) =>
            {
                var cache = new TestNuGetPackageCache();
                cache.SetMockCliPackages([
                    // Should be ignored because its lower that current prerelease version.
                    new NuGetPackage { Id = "Aspire.Cli", Version = "9.3.1", Source = "nuget.org" },
 
                    // Should be selected because it is higher than 9.4.0-dev (dev and preview sort using alphabetical sort).
                    new NuGetPackage { Id = "Aspire.Cli", Version = "9.4.0-preview", Source = "nuget.org" }, 
 
                    // Should be ignored because it is lower than 9.4.0-dev (dev and preview sort using alpha).
                    new NuGetPackage { Id = "Aspire.Cli", Version = "9.4.0-beta", Source = "nuget.org" }
                ]);
 
                return cache;
            };
 
            configure.InteractionServiceFactory = (sp) =>
            {
                var interactionService = new TestConsoleInteractionService();
                interactionService.DisplayVersionUpdateNotificationCallback = (newerVersion) =>
                {
                    suggestedVersionTcs.SetResult(newerVersion);
                };
 
                return interactionService;
            };
 
            configure.CliUpdateNotifierFactory = (sp) =>
            {
                var logger = sp.GetRequiredService<ILogger<CliUpdateNotifier>>();
                var nuGetPackageCache = sp.GetRequiredService<INuGetPackageCache>();
                var interactionService = sp.GetRequiredService<IInteractionService>();
 
                // Use a custom notifier that overrides the current version
                return new CliUpdateNotifierWithPackageVersionOverride("9.4.0-dev", logger, nuGetPackageCache, interactionService);
            };
        });
 
        var provider = services.BuildServiceProvider();
        var notifier = provider.GetRequiredService<ICliUpdateNotifier>();
 
        await notifier.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot).WaitAsync(CliTestConstants.DefaultTimeout);
        var suggestedVersion = await suggestedVersionTcs.Task.WaitAsync(CliTestConstants.DefaultTimeout);
 
        Assert.Equal("9.4.0-preview", suggestedVersion);
    }
 
    [Fact]
    public async Task PrereleaseWillRecommendUpgradeToStableInCurrentVersionFamily()
    {
        var currentVersion = VersionHelper.GetDefaultTemplateVersion();
        TaskCompletionSource<string> suggestedVersionTcs = new();
 
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.NuGetPackageCacheFactory = (sp) =>
            {
                var cache = new TestNuGetPackageCache();
                cache.SetMockCliPackages([
                    // Should be selected because stable sorts higher than preview.
                    new NuGetPackage { Id = "Aspire.Cli", Version = "9.4.0", Source = "nuget.org" },
 
                    // Should be ignored because its prerelease but in a higher version family.
                    new NuGetPackage { Id = "Aspire.Cli", Version = "9.5.0-preview", Source = "nuget.org" }, 
                ]);
 
                return cache;
            };
 
            configure.InteractionServiceFactory = (sp) =>
            {
                var interactionService = new TestConsoleInteractionService();
                interactionService.DisplayVersionUpdateNotificationCallback = (newerVersion) =>
                {
                    suggestedVersionTcs.SetResult(newerVersion);
                };
 
                return interactionService;
            };
 
            configure.CliUpdateNotifierFactory = (sp) =>
            {
                var logger = sp.GetRequiredService<ILogger<CliUpdateNotifier>>();
                var nuGetPackageCache = sp.GetRequiredService<INuGetPackageCache>();
                var interactionService = sp.GetRequiredService<IInteractionService>();
 
                // Use a custom notifier that overrides the current version
                return new CliUpdateNotifierWithPackageVersionOverride("9.4.0-dev", logger, nuGetPackageCache, interactionService);
            };
        });
 
        var provider = services.BuildServiceProvider();
        var notifier = provider.GetRequiredService<ICliUpdateNotifier>();
 
        await notifier.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot).WaitAsync(CliTestConstants.DefaultTimeout);
        var suggestedVersion = await suggestedVersionTcs.Task.WaitAsync(CliTestConstants.DefaultTimeout);
 
        Assert.Equal("9.4.0", suggestedVersion);
    }
 
    [Fact]
    public async Task StableWillOnlyRecommendGoingToNewerStable()
    {
        var currentVersion = VersionHelper.GetDefaultTemplateVersion();
        TaskCompletionSource<string> suggestedVersionTcs = new();
 
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.NuGetPackageCacheFactory = (sp) =>
            {
                var cache = new TestNuGetPackageCache();
                cache.SetMockCliPackages([
                    // Should be ignored because its stable in a higher version family.
                    new NuGetPackage { Id = "Aspire.Cli", Version = "9.5.0", Source = "nuget.org" }, 
 
                    // Should be ignored because its prerelease but in a (even) higher version family.
                    new NuGetPackage { Id = "Aspire.Cli", Version = "9.6.0-preview", Source = "nuget.org" }, 
                ]);
 
                return cache;
            };
 
            configure.InteractionServiceFactory = (sp) =>
            {
                var interactionService = new TestConsoleInteractionService();
                interactionService.DisplayVersionUpdateNotificationCallback = (newerVersion) =>
                {
                    suggestedVersionTcs.SetResult(newerVersion);
                };
 
                return interactionService;
            };
 
            configure.CliUpdateNotifierFactory = (sp) =>
            {
                var logger = sp.GetRequiredService<ILogger<CliUpdateNotifier>>();
                var nuGetPackageCache = sp.GetRequiredService<INuGetPackageCache>();
                var interactionService = sp.GetRequiredService<IInteractionService>();
 
                // Use a custom notifier that overrides the current version
                return new CliUpdateNotifierWithPackageVersionOverride("9.4.0", logger, nuGetPackageCache, interactionService);
            };
        });
 
        var provider = services.BuildServiceProvider();
        var notifier = provider.GetRequiredService<ICliUpdateNotifier>();
 
        await notifier.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot).WaitAsync(CliTestConstants.DefaultTimeout);
        var suggestedVersion = await suggestedVersionTcs.Task.WaitAsync(CliTestConstants.DefaultTimeout);
 
        Assert.Equal("9.5.0", suggestedVersion);
    }
 
    [Fact]
    public async Task StableWillNotRecommendUpdatingToPreview()
    {
        var currentVersion = VersionHelper.GetDefaultTemplateVersion();
 
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, configure =>
        {
            configure.NuGetPackageCacheFactory = (sp) =>
            {
                var cache = new TestNuGetPackageCache();
                cache.SetMockCliPackages([
                    new NuGetPackage { Id = "Aspire.Cli", Version = "9.4.0-preview", Source = "nuget.org" }, 
                    new NuGetPackage { Id = "Aspire.Cli", Version = "9.5.0-preview", Source = "nuget.org" }, 
                ]);
 
                return cache;
            };
 
            configure.InteractionServiceFactory = (sp) =>
            {
                var interactionService = new TestConsoleInteractionService();
                interactionService.DisplayVersionUpdateNotificationCallback = (newerVersion) =>
                {
                    Assert.Fail("Should not suggest a preview version when current version is stable.");
                };
 
                return interactionService;
            };
 
            configure.CliUpdateNotifierFactory = (sp) =>
            {
                var logger = sp.GetRequiredService<ILogger<CliUpdateNotifier>>();
                var nuGetPackageCache = sp.GetRequiredService<INuGetPackageCache>();
                var interactionService = sp.GetRequiredService<IInteractionService>();
 
                // Use a custom notifier that overrides the current version
                return new CliUpdateNotifierWithPackageVersionOverride("9.4.0", logger, nuGetPackageCache, interactionService);
            };
        });
 
        var provider = services.BuildServiceProvider();
        var notifier = provider.GetRequiredService<ICliUpdateNotifier>();
 
        await notifier.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot).WaitAsync(CliTestConstants.DefaultTimeout);
    }
 
    [Fact]
    public async Task NotifyIfUpdateAvailableAsync_WithNewerStableVersion_DoesNotThrow()
    {
        // Arrange
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper);
        
        // Replace the NuGetPackageCache with our test implementation
        services.AddSingleton<INuGetPackageCache, TestNuGetPackageCache>();
        services.AddSingleton<ICliUpdateNotifier, CliUpdateNotifier>();
        
        var provider = services.BuildServiceProvider();
        var service = provider.GetRequiredService<ICliUpdateNotifier>();
        
        // Mock packages with a newer stable version
        var nugetCache = provider.GetRequiredService<INuGetPackageCache>() as TestNuGetPackageCache;
        nugetCache?.SetMockCliPackages([
            new NuGetPackage { Id = "Aspire.Cli", Version = "9.0.0", Source = "nuget.org" }
        ]);
 
        // Act & Assert (should not throw)
        await service.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot);
    }
 
    [Fact]
    public async Task NotifyIfUpdateAvailableAsync_WithEmptyPackages_DoesNotThrow()
    {
        // Arrange
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper);
        
        // Replace the NuGetPackageCache with our test implementation
        services.AddSingleton<INuGetPackageCache, TestNuGetPackageCache>();
        services.AddSingleton<ICliUpdateNotifier, CliUpdateNotifier>();
        
        var provider = services.BuildServiceProvider();
        var service = provider.GetRequiredService<ICliUpdateNotifier>();
 
        // Act & Assert (should not throw)
        await service.NotifyIfUpdateAvailableAsync(workspace.WorkspaceRoot);
    }
}
 
internal sealed class CliUpdateNotifierWithPackageVersionOverride(string currentVersion, ILogger<CliUpdateNotifier> logger, INuGetPackageCache nuGetPackageCache, IInteractionService interactionService) : CliUpdateNotifier(logger, nuGetPackageCache, interactionService)
{
    protected override SemVersion? GetCurrentVersion()
    {
        return SemVersion.Parse(currentVersion, SemVersionStyles.Strict);
    }
}
 
internal sealed class TestNuGetPackageCache : INuGetPackageCache
{
    private IEnumerable<NuGetPackage> _cliPackages = [];
 
    public void SetMockCliPackages(IEnumerable<NuGetPackage> packages)
    {
        _cliPackages = packages;
    }
 
    public Task<IEnumerable<NuGetPackage>> GetTemplatePackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken)
    {
        return Task.FromResult(Enumerable.Empty<NuGetPackage>());
    }
 
    public Task<IEnumerable<NuGetPackage>> GetIntegrationPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken)
    {
        return Task.FromResult(Enumerable.Empty<NuGetPackage>());
    }
 
    public Task<IEnumerable<NuGetPackage>> GetCliPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, string? source, CancellationToken cancellationToken)
    {
        return Task.FromResult(_cliPackages);
    }
}