@@ -31,6 +31,19 @@ func setupInstallMock(t *testing.T) *[]installCall {
3131 return & calls
3232}
3333
34+ func setupScopeMock (t * testing.T , scope string ) * bool {
35+ t .Helper ()
36+ orig := promptScopeSelection
37+ t .Cleanup (func () { promptScopeSelection = orig })
38+
39+ called := false
40+ promptScopeSelection = func (_ context.Context ) (string , error ) {
41+ called = true
42+ return scope , nil
43+ }
44+ return & called
45+ }
46+
3447type installCall struct {
3548 agents []string
3649 opts installer.InstallOptions
@@ -146,6 +159,7 @@ func TestInstallIncludeExperimental(t *testing.T) {
146159func TestInstallInteractivePrompt (t * testing.T ) {
147160 setupTestAgents (t )
148161 calls := setupInstallMock (t )
162+ setupScopeMock (t , installer .ScopeGlobal )
149163
150164 origPrompt := promptAgentSelection
151165 t .Cleanup (func () { promptAgentSelection = origPrompt })
@@ -347,3 +361,134 @@ func TestResolveAgentNamesEmpty(t *testing.T) {
347361 require .Error (t , err )
348362 assert .Contains (t , err .Error (), "no agents specified" )
349363}
364+
365+ // --- Scope flag tests ---
366+
367+ func TestInstallProjectFlag (t * testing.T ) {
368+ setupTestAgents (t )
369+ calls := setupInstallMock (t )
370+
371+ ctx := cmdio .MockDiscard (t .Context ())
372+ cmd := newInstallCmd ()
373+ cmd .SetContext (ctx )
374+ cmd .SetArgs ([]string {"--project" })
375+
376+ err := cmd .Execute ()
377+ require .NoError (t , err )
378+
379+ require .Len (t , * calls , 1 )
380+ assert .Equal (t , installer .ScopeProject , (* calls )[0 ].opts .Scope )
381+ }
382+
383+ func TestInstallGlobalFlag (t * testing.T ) {
384+ setupTestAgents (t )
385+ calls := setupInstallMock (t )
386+
387+ ctx := cmdio .MockDiscard (t .Context ())
388+ cmd := newInstallCmd ()
389+ cmd .SetContext (ctx )
390+ cmd .SetArgs ([]string {"--global" })
391+
392+ err := cmd .Execute ()
393+ require .NoError (t , err )
394+
395+ require .Len (t , * calls , 1 )
396+ assert .Equal (t , installer .ScopeGlobal , (* calls )[0 ].opts .Scope )
397+ }
398+
399+ func TestInstallGlobalAndProjectErrors (t * testing.T ) {
400+ setupTestAgents (t )
401+ setupInstallMock (t )
402+
403+ ctx := cmdio .MockDiscard (t .Context ())
404+ cmd := newInstallCmd ()
405+ cmd .SetContext (ctx )
406+ cmd .SetArgs ([]string {"--global" , "--project" })
407+ cmd .SilenceErrors = true
408+ cmd .SilenceUsage = true
409+
410+ err := cmd .Execute ()
411+ require .Error (t , err )
412+ assert .Contains (t , err .Error (), "cannot use --global and --project together" )
413+ }
414+
415+ func TestInstallNoFlagNonInteractiveUsesGlobal (t * testing.T ) {
416+ setupTestAgents (t )
417+ calls := setupInstallMock (t )
418+
419+ ctx := cmdio .MockDiscard (t .Context ())
420+ cmd := newInstallCmd ()
421+ cmd .SetContext (ctx )
422+
423+ err := cmd .RunE (cmd , nil )
424+ require .NoError (t , err )
425+
426+ require .Len (t , * calls , 1 )
427+ assert .Equal (t , installer .ScopeGlobal , (* calls )[0 ].opts .Scope )
428+ }
429+
430+ func TestInstallNoFlagInteractiveShowsScopePrompt (t * testing.T ) {
431+ setupTestAgents (t )
432+ calls := setupInstallMock (t )
433+ scopePromptCalled := setupScopeMock (t , installer .ScopeProject )
434+
435+ // Also mock agent prompt since interactive mode triggers it.
436+ origPrompt := promptAgentSelection
437+ t .Cleanup (func () { promptAgentSelection = origPrompt })
438+ promptAgentSelection = func (_ context.Context , detected []* agents.Agent ) ([]* agents.Agent , error ) {
439+ return detected , nil
440+ }
441+
442+ ctx , test := cmdio .SetupTest (t .Context (), cmdio.TestOptions {PromptSupported : true })
443+ defer test .Done ()
444+
445+ drain := func (r * bufio.Reader ) {
446+ buf := make ([]byte , 4096 )
447+ for {
448+ _ , err := r .Read (buf )
449+ if err != nil {
450+ return
451+ }
452+ }
453+ }
454+ go drain (test .Stdout )
455+ go drain (test .Stderr )
456+
457+ cmd := newInstallCmd ()
458+ cmd .SetContext (ctx )
459+
460+ err := cmd .RunE (cmd , nil )
461+ require .NoError (t , err )
462+
463+ assert .True (t , * scopePromptCalled , "scope prompt should be called in interactive mode" )
464+ require .Len (t , * calls , 1 )
465+ assert .Equal (t , installer .ScopeProject , (* calls )[0 ].opts .Scope )
466+ }
467+
468+ func TestResolveScopeValidation (t * testing.T ) {
469+ tests := []struct {
470+ name string
471+ project bool
472+ global bool
473+ want string
474+ wantErr string
475+ }{
476+ {name : "neither" , want : installer .ScopeGlobal },
477+ {name : "global only" , global : true , want : installer .ScopeGlobal },
478+ {name : "project only" , project : true , want : installer .ScopeProject },
479+ {name : "both" , project : true , global : true , wantErr : "cannot use --global and --project together" },
480+ }
481+
482+ for _ , tc := range tests {
483+ t .Run (tc .name , func (t * testing.T ) {
484+ got , err := resolveScope (tc .project , tc .global )
485+ if tc .wantErr != "" {
486+ require .Error (t , err )
487+ assert .Contains (t , err .Error (), tc .wantErr )
488+ } else {
489+ require .NoError (t , err )
490+ assert .Equal (t , tc .want , got )
491+ }
492+ })
493+ }
494+ }
0 commit comments