@@ -538,6 +538,170 @@ public void GenerateFriendlyName_ProducesExpectedResults(string packageId, strin
538538 Assert . Equal ( expectedFriendlyName , result . FriendlyName ) ;
539539 Assert . Equal ( package , result . Package ) ;
540540 }
541+
542+ [ Fact ]
543+ public async Task AddCommandPrompter_FiltersToHighestVersionPerPackageId ( )
544+ {
545+ // Arrange
546+ List < ( string FriendlyName , NuGetPackage Package , PackageChannel Channel ) > ? displayedPackages = null ;
547+
548+ using var workspace = TemporaryWorkspace . Create ( outputHelper ) ;
549+ var services = CliTestHelper . CreateServiceCollection ( workspace , outputHelper , options =>
550+ {
551+ options . InteractionServiceFactory = ( sp ) =>
552+ {
553+ var mockInteraction = new TestConsoleInteractionService ( ) ;
554+ mockInteraction . PromptForSelectionCallback = ( message , choices , formatter , ct ) =>
555+ {
556+ // Capture what the prompter passes to the interaction service
557+ var choicesList = choices . Cast < ( string FriendlyName , NuGetPackage Package , PackageChannel Channel ) > ( ) . ToList ( ) ;
558+ displayedPackages = choicesList ;
559+ return choicesList . First ( ) ;
560+ } ;
561+ return mockInteraction ;
562+ } ;
563+ } ) ;
564+ var provider = services . BuildServiceProvider ( ) ;
565+ var interactionService = provider . GetRequiredService < IInteractionService > ( ) ;
566+
567+ var prompter = new AddCommandPrompter ( interactionService ) ;
568+
569+ // Create a fake channel
570+ var fakeCache = new FakeNuGetPackageCache ( ) ;
571+ var channel = PackageChannel . CreateImplicitChannel ( fakeCache ) ;
572+
573+ // Create multiple versions of the same package
574+ var packages = new [ ]
575+ {
576+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "9.0.0" , Source = "nuget" } , channel ) ,
577+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "9.2.0" , Source = "nuget" } , channel ) ,
578+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "9.1.0" , Source = "nuget" } , channel ) ,
579+ } ;
580+
581+ // Act
582+ await prompter . PromptForIntegrationAsync ( packages , CancellationToken . None ) ;
583+
584+ // Assert - should only show highest version (9.2.0) for the package ID
585+ Assert . NotNull ( displayedPackages ) ;
586+ Assert . Single ( displayedPackages ! ) ;
587+ Assert . Equal ( "9.2.0" , displayedPackages ! . First ( ) . Package . Version ) ;
588+ }
589+
590+ [ Fact ]
591+ public async Task AddCommandPrompter_FiltersToHighestVersionPerChannel ( )
592+ {
593+ // Arrange
594+ List < object > ? displayedChoices = null ;
595+
596+ using var workspace = TemporaryWorkspace . Create ( outputHelper ) ;
597+ var services = CliTestHelper . CreateServiceCollection ( workspace , outputHelper , options =>
598+ {
599+ options . InteractionServiceFactory = ( sp ) =>
600+ {
601+ var mockInteraction = new TestConsoleInteractionService ( ) ;
602+ mockInteraction . PromptForSelectionCallback = ( message , choices , formatter , ct ) =>
603+ {
604+ // Capture what the prompter passes to the interaction service
605+ var choicesList = choices . Cast < object > ( ) . ToList ( ) ;
606+ displayedChoices = choicesList ;
607+ return choicesList . First ( ) ;
608+ } ;
609+ return mockInteraction ;
610+ } ;
611+ } ) ;
612+ var provider = services . BuildServiceProvider ( ) ;
613+ var interactionService = provider . GetRequiredService < IInteractionService > ( ) ;
614+
615+ var prompter = new AddCommandPrompter ( interactionService ) ;
616+
617+ // Create a fake channel
618+ var fakeCache = new FakeNuGetPackageCache ( ) ;
619+ var channel = PackageChannel . CreateImplicitChannel ( fakeCache ) ;
620+
621+ // Create multiple versions of the same package from same channel
622+ var packages = new [ ]
623+ {
624+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "9.0.0" , Source = "nuget" } , channel ) ,
625+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "9.2.0" , Source = "nuget" } , channel ) ,
626+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "9.1.0" , Source = "nuget" } , channel ) ,
627+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "9.0.1-preview.1" , Source = "nuget" } , channel ) ,
628+ } ;
629+
630+ // Act
631+ await prompter . PromptForIntegrationVersionAsync ( packages , CancellationToken . None ) ;
632+
633+ // Assert - For implicit channel, should only show highest version (9.2.0) directly
634+ // The root menu shows: (string Label, Func<...> Action) tuples
635+ Assert . NotNull ( displayedChoices ) ;
636+ Assert . Single ( displayedChoices ! ) ; // Only one choice for implicit channel
637+ }
638+
639+ [ Fact ]
640+ public async Task AddCommandPrompter_ShowsHighestVersionPerChannelWhenMultipleChannels ( )
641+ {
642+ // Arrange
643+ List < object > ? displayedChoices = null ;
644+
645+ using var workspace = TemporaryWorkspace . Create ( outputHelper ) ;
646+ var services = CliTestHelper . CreateServiceCollection ( workspace , outputHelper , options =>
647+ {
648+ options . InteractionServiceFactory = ( sp ) =>
649+ {
650+ var mockInteraction = new TestConsoleInteractionService ( ) ;
651+ mockInteraction . PromptForSelectionCallback = ( message , choices , formatter , ct ) =>
652+ {
653+ // Capture what the prompter passes to the interaction service
654+ var choicesList = choices . Cast < object > ( ) . ToList ( ) ;
655+ displayedChoices = choicesList ;
656+ return choicesList . First ( ) ;
657+ } ;
658+ return mockInteraction ;
659+ } ;
660+ } ) ;
661+ var provider = services . BuildServiceProvider ( ) ;
662+ var interactionService = provider . GetRequiredService < IInteractionService > ( ) ;
663+
664+ var prompter = new AddCommandPrompter ( interactionService ) ;
665+
666+ // Create two different channels
667+ var fakeCache = new FakeNuGetPackageCache ( ) ;
668+ var implicitChannel = PackageChannel . CreateImplicitChannel ( fakeCache ) ;
669+
670+ var mappings = new [ ] { new PackageMapping ( "Aspire*" , "https://preview-feed" ) } ;
671+ var explicitChannel = PackageChannel . CreateExplicitChannel ( "preview" , PackageChannelQuality . Prerelease , mappings , fakeCache ) ;
672+
673+ // Create packages from different channels with different versions
674+ var packages = new [ ]
675+ {
676+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "9.0.0" , Source = "nuget" } , implicitChannel ) ,
677+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "9.1.0" , Source = "nuget" } , implicitChannel ) ,
678+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "9.2.0" , Source = "nuget" } , implicitChannel ) ,
679+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "10.0.0-preview.1" , Source = "preview-feed" } , explicitChannel ) ,
680+ ( "redis" , new NuGetPackage { Id = "Aspire.Hosting.Redis" , Version = "10.0.0-preview.2" , Source = "preview-feed" } , explicitChannel ) ,
681+ } ;
682+
683+ // Act
684+ await prompter . PromptForIntegrationVersionAsync ( packages , CancellationToken . None ) ;
685+
686+ // Assert - should show 2 root choices: one for implicit channel, one submenu for explicit channel
687+ Assert . NotNull ( displayedChoices ) ;
688+ Assert . Equal ( 2 , displayedChoices ! . Count ) ;
689+ }
690+
691+ private sealed class FakeNuGetPackageCache : Aspire . Cli . NuGet . INuGetPackageCache
692+ {
693+ public Task < IEnumerable < NuGetPackage > > GetTemplatePackagesAsync ( DirectoryInfo workingDirectory , bool prerelease , FileInfo ? nugetConfigFile , CancellationToken cancellationToken )
694+ => Task . FromResult < IEnumerable < NuGetPackage > > ( [ ] ) ;
695+
696+ public Task < IEnumerable < NuGetPackage > > GetIntegrationPackagesAsync ( DirectoryInfo workingDirectory , bool prerelease , FileInfo ? nugetConfigFile , CancellationToken cancellationToken )
697+ => Task . FromResult < IEnumerable < NuGetPackage > > ( [ ] ) ;
698+
699+ public Task < IEnumerable < NuGetPackage > > GetCliPackagesAsync ( DirectoryInfo workingDirectory , bool prerelease , FileInfo ? nugetConfigFile , CancellationToken cancellationToken )
700+ => Task . FromResult < IEnumerable < NuGetPackage > > ( [ ] ) ;
701+
702+ public Task < IEnumerable < NuGetPackage > > GetPackagesAsync ( DirectoryInfo workingDirectory , string packageId , Func < string , bool > ? filter , bool prerelease , FileInfo ? nugetConfigFile , bool useCache , CancellationToken cancellationToken )
703+ => Task . FromResult < IEnumerable < NuGetPackage > > ( [ ] ) ;
704+ }
541705}
542706
543707internal sealed class TestAddCommandPrompter ( IInteractionService interactionService ) : AddCommandPrompter ( interactionService )
0 commit comments