@@ -10,6 +10,7 @@ import (
1010 "strings"
1111 "testing"
1212
13+ "github.com/github/gh-aw/pkg/gitutil"
1314 "github.com/github/gh-aw/pkg/testutil"
1415 "github.com/github/gh-aw/pkg/workflow"
1516 "github.com/spf13/cobra"
@@ -367,6 +368,198 @@ func TestAddCommandArgs(t *testing.T) {
367368 require .NoError (t , err , "Should not error with multiple arguments" )
368369}
369370
371+ func TestRejectBootstrapProfileForRegularAdd (t * testing.T ) {
372+ profileWithConfig := & resolvedBootstrapProfile {
373+ PackageID : "githubnext/central-agentic-ops" ,
374+ Profile : & repositoryPackageBootstrap {
375+ Config : []repositoryPackageBootstrapAction {
376+ {Type : "repo-variable" , Name : "EXAMPLE" , Prompt : "Enter value" },
377+ },
378+ },
379+ }
380+
381+ t .Run ("rejects regular add for packages with manifest config" , func (t * testing.T ) {
382+ err := rejectBootstrapProfileForRegularAdd ([]string {"githubnext/central-agentic-ops" }, profileWithConfig )
383+ require .Error (t , err )
384+ assert .Contains (t , err .Error (), "package githubnext/central-agentic-ops declares aw.yml config" )
385+ assert .Contains (t , err .Error (), "gh aw add-wizard githubnext/central-agentic-ops" )
386+ })
387+
388+ t .Run ("uses requested sources in the add-wizard guidance" , func (t * testing.T ) {
389+ err := rejectBootstrapProfileForRegularAdd ([]string {"githubnext/central-agentic-ops" , "./local-workflow.md" }, profileWithConfig )
390+ require .Error (t , err )
391+ assert .Contains (t , err .Error (), "gh aw add-wizard githubnext/central-agentic-ops ./local-workflow.md" )
392+ })
393+
394+ t .Run ("allows packages without manifest config" , func (t * testing.T ) {
395+ err := rejectBootstrapProfileForRegularAdd ([]string {"owner/pkg" }, nil )
396+ require .NoError (t , err )
397+
398+ err = rejectBootstrapProfileForRegularAdd ([]string {"owner/pkg" }, & resolvedBootstrapProfile {
399+ PackageID : "owner/pkg" ,
400+ Profile : & repositoryPackageBootstrap {Config : nil },
401+ })
402+ require .NoError (t , err )
403+ })
404+ }
405+
406+ func TestEnsureAddRepositoryInitialized (t * testing.T ) {
407+ originalFindGitRoot := addFindGitRoot
408+ originalInitRepository := addInitRepository
409+ originalMissingInitMarkers := addMissingInitMarkers
410+ t .Cleanup (func () {
411+ addFindGitRoot = originalFindGitRoot
412+ addInitRepository = originalInitRepository
413+ addMissingInitMarkers = originalMissingInitMarkers
414+ })
415+
416+ t .Run ("skips initialization outside a git checkout" , func (t * testing.T ) {
417+ addFindGitRoot = func () (string , error ) { return "" , gitutil .ErrNotGitRepository }
418+ addMissingInitMarkers = func (string , string ) ([]string , error ) {
419+ t .Fatal ("missing init markers check should be skipped outside a git checkout" )
420+ return nil , nil
421+ }
422+ addInitRepository = func (InitOptions ) error {
423+ t .Fatal ("InitRepository should not run outside a git checkout" )
424+ return nil
425+ }
426+
427+ err := ensureAddRepositoryInitialized ("" , false )
428+ require .NoError (t , err )
429+ })
430+
431+ t .Run ("runs init when required markers are missing" , func (t * testing.T ) {
432+ repoDir := t .TempDir ()
433+ addFindGitRoot = func () (string , error ) { return repoDir , nil }
434+ addMissingInitMarkers = func (baseDir string , engineOverride string ) ([]string , error ) {
435+ assert .Equal (t , "." , baseDir )
436+ assert .Equal (t , "claude" , engineOverride )
437+ return []string {".gitattributes" }, nil
438+ }
439+
440+ called := false
441+ addInitRepository = func (opts InitOptions ) error {
442+ called = true
443+ assert .True (t , opts .Verbose )
444+ assert .Equal (t , "claude" , opts .Engine )
445+ assert .True (t , opts .Skill )
446+ assert .True (t , opts .Agent )
447+ assert .True (t , opts .MCP )
448+ assert .False (t , opts .CodespaceEnabled )
449+ assert .False (t , opts .Completions )
450+ assert .False (t , opts .CreatePR )
451+ return nil
452+ }
453+
454+ err := ensureAddRepositoryInitialized ("claude" , true )
455+ require .NoError (t , err )
456+ assert .True (t , called )
457+ })
458+
459+ t .Run ("does nothing when markers are already present" , func (t * testing.T ) {
460+ repoDir := t .TempDir ()
461+ addFindGitRoot = func () (string , error ) { return repoDir , nil }
462+ addMissingInitMarkers = func (string , string ) ([]string , error ) { return nil , nil }
463+ addInitRepository = func (InitOptions ) error {
464+ t .Fatal ("InitRepository should not run when markers are already present" )
465+ return nil
466+ }
467+
468+ err := ensureAddRepositoryInitialized ("" , false )
469+ require .NoError (t , err )
470+ })
471+ }
472+
473+ func TestAddResolvedWorkflows_IgnoresBootstrapRequireOwnerTypeDuringInstall (t * testing.T ) {
474+ originalCheckOwnerType := bootstrapCheckOwnerType
475+ t .Cleanup (func () {
476+ bootstrapCheckOwnerType = originalCheckOwnerType
477+ })
478+
479+ bootstrapCheckOwnerType = func (context.Context , string ) (string , error ) { return "User" , nil }
480+
481+ resolved := & ResolvedWorkflows {
482+ Workflows : nil ,
483+ BootstrapProfile : & resolvedBootstrapProfile {
484+ PackageID : "githubnext/central-agentic-ops" ,
485+ Profile : & repositoryPackageBootstrap {
486+ Config : []repositoryPackageBootstrapAction {{Type : "require-owner-type" , Value : "org" }},
487+ },
488+ },
489+ }
490+
491+ _ , err := AddResolvedWorkflows (context .Background (), []string {"githubnext/central-agentic-ops" }, resolved , AddOptions {NoGitattributes : true })
492+ require .NoError (t , err )
493+ }
494+
495+ func TestCompileDispatchWorkflowDependencies_FallsBackToRawFrontmatter (t * testing.T ) {
496+ tmpDir := testutil .TempDir (t , "dispatch-workflow-fallback-*" )
497+ workflowsDir := setupMinimalGitRepo (t , tmpDir )
498+
499+ mainPath := filepath .Join (workflowsDir , "dispatcher.md" )
500+ workerPath := filepath .Join (workflowsDir , "worker.md" )
501+
502+ require .NoError (t , os .WriteFile (mainPath , []byte (`---
503+ name: Dispatcher
504+ on:
505+ workflow_dispatch:
506+ safe-outputs:
507+ dispatch-workflow:
508+ workflows: [worker]
509+ imports:
510+ - uses: shared/missing.md
511+ ---
512+
513+ # Dispatcher
514+ ` ), 0o644 ))
515+ require .NoError (t , os .WriteFile (workerPath , []byte (`---
516+ name: Worker
517+ on:
518+ workflow_dispatch:
519+ ---
520+
521+ # Worker
522+ ` ), 0o644 ))
523+
524+ compileDispatchWorkflowDependencies (context .Background (), mainPath , false , true , "" , nil )
525+
526+ lockPath := filepath .Join (workflowsDir , "worker.lock.yml" )
527+ _ , err := os .Stat (lockPath )
528+ require .NoError (t , err , "dispatch dependency should still be compiled when merged parse fails" )
529+ lockContent , err := os .ReadFile (lockPath )
530+ require .NoError (t , err )
531+ assert .Contains (t , string (lockContent ), "name: \" Worker\" " , "compiled dispatch dependency should preserve its workflow name" )
532+ }
533+
534+ func TestValidateWorkflowDestination_SkipsExistingWorkflowFromSameSource (t * testing.T ) {
535+ workflowsDir := t .TempDir ()
536+ existingPath := filepath .Join (workflowsDir , "dependabot.md" )
537+ require .NoError (t , os .WriteFile (existingPath , []byte (`---
538+ source: githubnext/central-agentic-ops/.github/workflows/dependabot.md@main
539+ ---
540+ # Dependabot
541+ ` ), 0o644 ))
542+
543+ skip , err := validateWorkflowDestination (workflowsDir , "dependabot" , "githubnext/central-agentic-ops" , AddOptions {})
544+ require .NoError (t , err )
545+ assert .True (t , skip )
546+ }
547+
548+ func TestValidateWorkflowDestination_ErrorsForExistingWorkflowFromDifferentSource (t * testing.T ) {
549+ workflowsDir := t .TempDir ()
550+ existingPath := filepath .Join (workflowsDir , "dependabot.md" )
551+ require .NoError (t , os .WriteFile (existingPath , []byte (`---
552+ source: octo/other/.github/workflows/dependabot.md@main
553+ ---
554+ # Dependabot
555+ ` ), 0o644 ))
556+
557+ skip , err := validateWorkflowDestination (workflowsDir , "dependabot" , "githubnext/central-agentic-ops" , AddOptions {})
558+ require .Error (t , err )
559+ assert .False (t , skip )
560+ assert .Contains (t , err .Error (), "workflow 'dependabot' already exists" )
561+ }
562+
370563// TestAddMultipleWorkflowsNameFlag verifies that --name is not allowed when multiple workflows are specified.
371564func TestAddMultipleWorkflowsNameFlag (t * testing.T ) {
372565 cmd := NewAddCommand (validateEngineStub )
0 commit comments