|
// 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>());
}
}
}
|