Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Aspire.Cli/Commands/AddCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,14 @@ bool MatchesIntegrationName((string FriendlyName, NuGetPackage Package, PackageC
? ProfilingTelemetry.Values.AddPackageMatchKindExact
: ProfilingTelemetry.Values.AddPackageMatchKindNone;

// Without an integration name there is nothing to match, so non-interactive mode would
// otherwise auto-select the first package in the full list and add it
// (https://github.com/microsoft/aspire/issues/16357).
if (integrationName is null && !_hostEnvironment.SupportsInteractiveInput)
{
return AddCommandFailure(CliExitCodes.FailedToAddPackage, AddCommandStrings.NonInteractiveRequiresIntegrationName);
}

// Non-interactive mode never falls back to fuzzy search: in interactive mode the user picks
// from the fuzzy candidates, but a script/CI invocation would otherwise silently auto-select
// distinctPackages.First() in GetPackageByInteractiveFlow and install the wrong package
Expand Down
9 changes: 9 additions & 0 deletions src/Aspire.Cli/Resources/AddCommandStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Aspire.Cli/Resources/AddCommandStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@
<value>When --version is specified in non-interactive mode, the integration name must exactly match a package or friendly name. No exact match was found for '{0}'.</value>
<comment>{0} is the integration name provided by the user.</comment>
</data>
<data name="NonInteractiveRequiresIntegrationName" xml:space="preserve">
<value>An integration name is required in non-interactive mode. Specify the package id or friendly name, for example 'aspire add redis'.</value>
</data>
<data name="NonInteractiveRequiresExactPackageMatch" xml:space="preserve">
<value>No exact match was found for '{0}'. In non-interactive mode the integration name must exactly match a package id or friendly name; fuzzy fallback is disabled to prevent silently selecting the wrong package.</value>
<comment>{0} is the integration name provided by the user.</comment>
Expand Down
5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Aspire.Cli/Resources/xlf/AddCommandStrings.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 52 additions & 0 deletions tests/Aspire.Cli.Tests/Commands/AddCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4101,6 +4101,58 @@ public async Task AddCommand_NonInteractive_NoExactMatchWithoutVersion_FailsInst
Assert.Contains(string.Format(AddCommandStrings.NonInteractiveRequiresExactPackageMatch, "kube"), testInteractionService.DisplayedErrors);
}

[Theory]
[InlineData("add")]
[InlineData("add --version 9.2.0")]
public async Task AddCommand_NonInteractive_NoIntegrationName_FailsInsteadOfAddingFirstPackage_Regression16357(string commandLine)
{
// Regression for https://github.com/microsoft/aspire/issues/16357: `aspire add --non-interactive`
// with no integration argument used to auto-select the first package in the full list and add it.
var addPackageWasCalled = false;
var testInteractionService = new TestInteractionService();

using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
{
options.CliHostEnvironmentFactory = _ => TestHelpers.CreateNonInteractiveHostEnvironment();
options.InteractionServiceFactory = _ => testInteractionService;
options.ProjectLocatorFactory = _ => new TestProjectLocator();

options.DotNetCliRunnerFactory = (sp) =>
{
var runner = new TestDotNetCliRunner();
runner.SearchPackagesAsyncCallback = (dir, query, exactMatch, prerelease, take, skip, nugetSource, useCache, options, cancellationToken) =>
{
return (
0,
new NuGetPackage[]
{
new() { Id = "Aspire.Hosting.Azure", Source = "nuget", Version = "9.2.0" },
new() { Id = "Aspire.Hosting.Redis", Source = "nuget", Version = "9.2.0" }
});
};

runner.AddPackageAsyncCallback = (projectFilePath, packageName, packageVersion, nugetSource, noRestore, options, cancellationToken) =>
{
addPackageWasCalled = true;
return 0;
};

return runner;
};
});
using var provider = services.BuildServiceProvider();

var command = provider.GetRequiredService<AddCommand>();
var result = command.Parse(commandLine);

var exitCode = await result.InvokeAsync().DefaultTimeout();

Assert.Equal(CliExitCodes.FailedToAddPackage, exitCode);
Assert.False(addPackageWasCalled, "AddPackageAsync must not be called when no integration name is given in non-interactive mode.");
Assert.Contains(AddCommandStrings.NonInteractiveRequiresIntegrationName, testInteractionService.DisplayedErrors);
}

[Fact]
public async Task AddCommand_NonInteractive_ExactMatchWithoutVersion_StillSucceeds()
{
Expand Down
Loading