@@ -2632,6 +2632,121 @@ public async Task UpdateCommand_NonInteractive_WithYesAndChannel_SucceedsWithout
26322632 Assert . NotNull ( capturedContext ) ;
26332633 }
26342634
2635+ [ Theory ]
2636+ [ InlineData ( "daily" , "daily" ) ]
2637+ [ InlineData ( "stable" , "stable" ) ]
2638+ [ InlineData ( "DAILY" , "daily" ) ] // case-insensitive match; canonical name from channels
2639+ public async Task UpdateCommand_SelfUpdate_NonInteractive_WhenIdentityChannelMatchesKnownChannel_UsesItWithoutPrompting ( string identityChannel , string expectedChannel )
2640+ {
2641+ using var workspace = TemporaryWorkspace . Create ( outputHelper ) ;
2642+
2643+ var ( _, capturedChannel , promptInvoked , _) = await RunNonInteractiveSelfUpdateAsync (
2644+ workspace , identityChannel : identityChannel ) ;
2645+
2646+ Assert . False ( promptInvoked , "Identity-channel match should bypass the channel prompt." ) ;
2647+ Assert . Equal ( expectedChannel , capturedChannel ) ;
2648+ }
2649+
2650+ [ Fact ]
2651+ public async Task UpdateCommand_SelfUpdate_NonInteractive_WhenIdentityChannelIsLocal_DefaultsToStable ( )
2652+ {
2653+ using var workspace = TemporaryWorkspace . Create ( outputHelper ) ;
2654+
2655+ var ( _, capturedChannel , promptInvoked , _) = await RunNonInteractiveSelfUpdateAsync (
2656+ workspace , identityChannel : PackageChannelNames . Local ) ;
2657+
2658+ Assert . False ( promptInvoked , "Non-interactive mode should not prompt; should default to stable." ) ;
2659+ Assert . Equal ( PackageChannelNames . Stable , capturedChannel ) ;
2660+ }
2661+
2662+ [ Fact ]
2663+ public async Task UpdateCommand_SelfUpdate_NonInteractive_WhenIdentityChannelIsStalePr_RequiresExplicitChannel ( )
2664+ {
2665+ using var workspace = TemporaryWorkspace . Create ( outputHelper ) ;
2666+
2667+ var ( exitCode , capturedChannel , promptInvoked , interactionService ) = await RunNonInteractiveSelfUpdateAsync (
2668+ workspace , identityChannel : "pr-99999" ) ;
2669+
2670+ Assert . Equal ( CliExitCodes . MissingRequiredArgument , exitCode ) ;
2671+ Assert . False ( promptInvoked , "Non-interactive mode should not prompt when channel cannot be resolved." ) ;
2672+ Assert . Null ( capturedChannel ) ;
2673+ Assert . Contains (
2674+ interactionService . DisplayedErrors ,
2675+ e => e . Contains ( "--channel" , StringComparison . Ordinal ) && e . Contains ( "non-interactive" , StringComparison . OrdinalIgnoreCase ) ) ;
2676+ }
2677+
2678+ [ Fact ]
2679+ public async Task UpdateCommand_SelfUpdate_ExplicitChannelOverridesIdentityChannel ( )
2680+ {
2681+ using var workspace = TemporaryWorkspace . Create ( outputHelper ) ;
2682+
2683+ var ( _, capturedChannel , promptInvoked , _) = await RunNonInteractiveSelfUpdateAsync (
2684+ workspace , identityChannel : PackageChannelNames . Daily , updateArgs : "update --self --non-interactive --channel stable -y" ) ;
2685+
2686+ Assert . False ( promptInvoked , "Explicit --channel should bypass the prompt." ) ;
2687+ Assert . Equal ( PackageChannelNames . Stable , capturedChannel ) ;
2688+ }
2689+
2690+ [ Fact ]
2691+ public async Task UpdateCommand_SelfUpdate_NonInteractive_WhenIdentityChannelIsStalePr_ExplicitChannelSucceeds ( )
2692+ {
2693+ using var workspace = TemporaryWorkspace . Create ( outputHelper ) ;
2694+
2695+ var ( _, capturedChannel , promptInvoked , _) = await RunNonInteractiveSelfUpdateAsync (
2696+ workspace , identityChannel : "pr-99999" , updateArgs : "update --self --non-interactive --channel daily -y" ) ;
2697+
2698+ Assert . False ( promptInvoked , "Explicit --channel should bypass the prompt even with stale identity." ) ;
2699+ Assert . Equal ( PackageChannelNames . Daily , capturedChannel ) ;
2700+ }
2701+
2702+ private async Task < ( int ExitCode , string ? CapturedChannel , bool PromptInvoked , TestInteractionService InteractionService ) > RunNonInteractiveSelfUpdateAsync (
2703+ TemporaryWorkspace workspace ,
2704+ string identityChannel ,
2705+ string updateArgs = "update --self --non-interactive -y" )
2706+ {
2707+ var promptForSelectionInvoked = false ;
2708+ string ? capturedChannel = null ;
2709+ TestInteractionService ? interactionService = null ;
2710+
2711+ var services = CliTestHelper . CreateServiceCollection ( workspace , outputHelper , options =>
2712+ {
2713+ options . CliExecutionContextFactory = _ => workspace . CreateExecutionContext ( identityChannel : identityChannel ) ;
2714+
2715+ options . InteractionServiceFactory = _ =>
2716+ {
2717+ interactionService = new TestInteractionService ( )
2718+ {
2719+ PromptForSelectionCallback = ( prompt , choices , formatter , ct ) =>
2720+ {
2721+ promptForSelectionInvoked = true ;
2722+ return PackageChannelNames . Stable ;
2723+ }
2724+ } ;
2725+ return interactionService ;
2726+ } ;
2727+
2728+ options . CliDownloaderFactory = _ => new TestCliDownloader ( workspace . WorkspaceRoot )
2729+ {
2730+ DownloadLatestCliAsyncCallback = ( channel , ct ) =>
2731+ {
2732+ capturedChannel = channel ;
2733+ var archivePath = Path . Combine ( workspace . WorkspaceRoot . FullName , "test-cli.tar.gz" ) ;
2734+ File . WriteAllText ( archivePath , "fake archive" ) ;
2735+ return Task . FromResult ( archivePath ) ;
2736+ }
2737+ } ;
2738+ } ) ;
2739+
2740+ using var provider = services . BuildServiceProvider ( ) ;
2741+
2742+ var command = provider . GetRequiredService < RootCommand > ( ) ;
2743+ var result = command . Parse ( updateArgs ) ;
2744+
2745+ var exitCode = await result . InvokeAsync ( ) . DefaultTimeout ( ) ;
2746+
2747+ return ( exitCode , capturedChannel , promptForSelectionInvoked , interactionService ! ) ;
2748+ }
2749+
26352750 private static string CreateCustomToolPathInstall ( string toolPath )
26362751 {
26372752 var processPath = Path . Combine ( toolPath , GetAspireExecutableName ( ) ) ;
0 commit comments