File: Commands\InitCommandTests.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.Commands;
using Aspire.Cli.Interaction;
using Aspire.Cli.NuGet;
using Aspire.Cli.Packaging;
using Aspire.Cli.Tests.TestServices;
using Aspire.Cli.Tests.Utils;
using Microsoft.Extensions.DependencyInjection;
 
namespace Aspire.Cli.Tests.Commands;
 
public class InitCommandTests(ITestOutputHelper outputHelper)
{
    [Fact]
    public async Task InitCommand_WithSingleFileAppHost_DoesNotPromptForProjectNameOrOutputPath()
    {
        // Arrange
        var promptedForProjectName = false;
        var promptedForOutputPath = false;
        
        using var workspace = TemporaryWorkspace.Create(outputHelper);
        
        var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
        {
            // Enable single-file AppHost feature
            options.EnabledFeatures = [KnownFeatures.SingleFileAppHostEnabled];
            
            // Set up prompter to track if prompts are called
            options.NewCommandPrompterFactory = (sp) =>
            {
                var interactionService = sp.GetRequiredService<IInteractionService>();
                var prompter = new TestNewCommandPrompter(interactionService);
 
                prompter.PromptForProjectNameCallback = (defaultName) =>
                {
                    promptedForProjectName = true;
                    throw new InvalidOperationException("PromptForProjectName should not be called for init command with single-file AppHost");
                };
 
                prompter.PromptForOutputPathCallback = (path) =>
                {
                    promptedForOutputPath = true;
                    throw new InvalidOperationException("PromptForOutputPath should not be called for init command with single-file AppHost");
                };
 
                // PromptForTemplatesVersion is expected to be called
                prompter.PromptForTemplatesVersionCallback = (packages) => packages.First();
 
                return prompter;
            };
            
            // Mock the runner to avoid actual template installation and project creation
            options.DotNetCliRunnerFactory = (sp) =>
            {
                var runner = new TestDotNetCliRunner();
                
                // Mock template installation
                runner.InstallTemplateAsyncCallback = (packageName, version, nugetSource, force, invocationOptions, cancellationToken) =>
                {
                    return (ExitCode: 0, TemplateVersion: "10.0.0");
                };
                
                // Mock project creation
                runner.NewProjectAsyncCallback = (templateName, projectName, outputPath, invocationOptions, cancellationToken) =>
                {
                    // Verify the expected values are being used
                    Assert.Equal(workspace.WorkspaceRoot.Name, projectName);
                    Assert.Equal(workspace.WorkspaceRoot.FullName, Path.GetFullPath(outputPath));
                    
                    // Create a minimal file to simulate successful template creation
                    var appHostFile = Path.Combine(outputPath, "apphost.cs");
                    File.WriteAllText(appHostFile, "// Test apphost file");
                    
                    return 0;
                };
                
                // Mock package search for template version selection
                runner.SearchPackagesAsyncCallback = (dir, query, prerelease, take, skip, nugetConfigFile, useCache, invocationOptions, cancellationToken) =>
                {
                    var package = new Aspire.Shared.NuGetPackageCli
                    {
                        Id = "Aspire.ProjectTemplates",
                        Source = "nuget",
                        Version = "10.0.0"
                    };
                    
                    return (0, new[] { package });
                };
                
                return runner;
            };
            
            // Mock packaging service to return fake channels
            options.PackagingServiceFactory = (sp) =>
            {
                return new TestPackagingService();
            };
        });
 
        var serviceProvider = services.BuildServiceProvider();
        var initCommand = serviceProvider.GetRequiredService<InitCommand>();
        
        // Act - Invoke init command
        var parseResult = initCommand.Parse("init");
        var exitCode = await parseResult.InvokeAsync().WaitAsync(CliTestConstants.DefaultTimeout);
        
        // Assert
        Assert.Equal(0, exitCode);
        Assert.False(promptedForProjectName, "Should not have prompted for project name");
        Assert.False(promptedForOutputPath, "Should not have prompted for output path");
    }
    
    // Test implementation of INewCommandPrompter
    private sealed class TestNewCommandPrompter(IInteractionService interactionService) : NewCommandPrompter(interactionService)
    {
        public Func<IEnumerable<(Aspire.Shared.NuGetPackageCli Package, PackageChannel Channel)>, (Aspire.Shared.NuGetPackageCli Package, PackageChannel Channel)>? PromptForTemplatesVersionCallback { get; set; }
        public Func<string, string>? PromptForProjectNameCallback { get; set; }
        public Func<string, string>? PromptForOutputPathCallback { get; set; }
 
        public override Task<(Aspire.Shared.NuGetPackageCli Package, PackageChannel Channel)> PromptForTemplatesVersionAsync(IEnumerable<(Aspire.Shared.NuGetPackageCli Package, PackageChannel Channel)> candidatePackages, CancellationToken cancellationToken)
        {
            return PromptForTemplatesVersionCallback switch
            {
                { } callback => Task.FromResult(callback(candidatePackages)),
                _ => Task.FromResult(candidatePackages.First())
            };
        }
 
        public override Task<string> PromptForProjectNameAsync(string defaultName, CancellationToken cancellationToken)
        {
            return PromptForProjectNameCallback switch
            {
                { } callback => Task.FromResult(callback(defaultName)),
                _ => Task.FromResult(defaultName)
            };
        }
 
        public override Task<string> PromptForOutputPath(string defaultPath, CancellationToken cancellationToken)
        {
            return PromptForOutputPathCallback switch
            {
                { } callback => Task.FromResult(callback(defaultPath)),
                _ => Task.FromResult(defaultPath)
            };
        }
    }
    
    // Test implementation of IPackagingService
    private sealed class TestPackagingService : IPackagingService
    {
        public Task<IEnumerable<PackageChannel>> GetChannelsAsync(CancellationToken cancellationToken = default)
        {
            // Return a fake channel with the implicit type (meaning use default NuGet sources)
            var testChannel = PackageChannel.CreateImplicitChannel(new FakeNuGetPackageCache());
            return Task.FromResult<IEnumerable<PackageChannel>>(new[] { testChannel });
        }
    }
    
    private sealed class FakeNuGetPackageCache : INuGetPackageCache
    {
        public Task<IEnumerable<Aspire.Shared.NuGetPackageCli>> GetTemplatePackagesAsync(DirectoryInfo workingDirectory, bool prerelease, FileInfo? nugetConfigFile, CancellationToken cancellationToken)
        {
            var package = new Aspire.Shared.NuGetPackageCli
            {
                Id = "Aspire.ProjectTemplates",
                Source = "nuget",
                Version = "10.0.0"
            };
            return Task.FromResult<IEnumerable<Aspire.Shared.NuGetPackageCli>>(new[] { package });
        }
        
        public Task<IEnumerable<Aspire.Shared.NuGetPackageCli>> GetIntegrationPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, FileInfo? nugetConfigFile, CancellationToken cancellationToken)
        {
            return Task.FromResult<IEnumerable<Aspire.Shared.NuGetPackageCli>>(Array.Empty<Aspire.Shared.NuGetPackageCli>());
        }
        
        public Task<IEnumerable<Aspire.Shared.NuGetPackageCli>> GetCliPackagesAsync(DirectoryInfo workingDirectory, bool prerelease, FileInfo? nugetConfigFile, CancellationToken cancellationToken)
        {
            return Task.FromResult<IEnumerable<Aspire.Shared.NuGetPackageCli>>(Array.Empty<Aspire.Shared.NuGetPackageCli>());
        }
        
        public Task<IEnumerable<Aspire.Shared.NuGetPackageCli>> GetPackagesAsync(DirectoryInfo workingDirectory, string packageId, Func<string, bool>? filter, bool prerelease, FileInfo? nugetConfigFile, bool useCache, CancellationToken cancellationToken)
        {
            return Task.FromResult<IEnumerable<Aspire.Shared.NuGetPackageCli>>(Array.Empty<Aspire.Shared.NuGetPackageCli>());
        }
    }
}