diff --git a/internal/commands/root.go b/internal/commands/root.go index 681ddff..e5fa7d0 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "sort" - "strings" "github.com/devbytes-cloud/freight/internal/blueprint" "github.com/devbytes-cloud/freight/internal/config" @@ -18,14 +17,6 @@ import ( "gopkg.in/yaml.v3" ) -var allowHooks = map[string]struct{}{ - "pre-commit": {}, - "prepare-commit-msg": {}, - "commit-msg": {}, - "post-commit": {}, - "post-checkout": {}, -} - // Execute runs the root command and handles any errors that occur during execution. func Execute() { if err := NewRootCmd().Execute(); err != nil { @@ -84,7 +75,7 @@ func NewRootCmd() *cobra.Command { } else if len(freightConfig.Allow) == 0 { // If no user allow and no existing allow, use default all hooks var allHooks []string - for h := range allowHooks { + for h := range githooks.AllowedGitHooks() { allHooks = append(allHooks, h) } sort.Strings(allHooks) @@ -98,7 +89,7 @@ func NewRootCmd() *cobra.Command { pterm.Debug.Printfln("Effective allow: %v", freightConfig.Allow) pterm.Debug.Printfln("Hooks to setup: %v", hooksToSetup) - validatedAllow, err := validateAllowHooks(hooksToSetup) + validatedAllow, err := validate.GitHooks(hooksToSetup) if err != nil { cmd.PrintErrln(err) os.Exit(1) @@ -224,26 +215,3 @@ func installBinary() error { pterm.Success.Println("✔ Installed conductor successfully") return nil } - -// validateAllowHooks validates the provided allow hooks and returns a map of valid hooks. -func validateAllowHooks(allow []string) (map[string]struct{}, error) { - if len(allow) == 0 { - pterm.Debug.Println("No hooks provided, using default allowed hooks") - return allowHooks, nil - } - - inputHooks := map[string]struct{}{} - var invalidHooks []string - for _, v := range allow { - if _, ok := allowHooks[v]; !ok { - invalidHooks = append(invalidHooks, v) - } - inputHooks[v] = struct{}{} - } - - if len(invalidHooks) > 0 { - return nil, fmt.Errorf("invalid hook types: %s", strings.Join(invalidHooks, ", ")) - } - - return inputHooks, nil -} diff --git a/internal/commands/root_test.go b/internal/commands/root_test.go index 800282b..cdff10d 100644 --- a/internal/commands/root_test.go +++ b/internal/commands/root_test.go @@ -1,97 +1 @@ package commands - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -type hookStructure struct { - inputHooks []string - expectedHooks map[string]struct{} - expectedErr bool - expectedErrMgs error -} - -func TestValidateAllowHooks(t *testing.T) { - testData := map[string]hookStructure{ - "no input hooks": { - expectedErr: false, - inputHooks: []string{}, - expectedHooks: map[string]struct{}{ - "pre-commit": {}, - "prepare-commit-msg": {}, - "commit-msg": {}, - "post-commit": {}, - "post-checkout": {}, - }, - }, - "only pre-commit hook": { - expectedErr: false, - inputHooks: []string{"pre-commit"}, - expectedHooks: map[string]struct{}{ - "pre-commit": {}, - }, - }, - "invalid hook name hook": { - expectedErr: true, - inputHooks: []string{"invalid hook name"}, - expectedErrMgs: fmt.Errorf("invalid hook types: invalid hook name"), - expectedHooks: nil, - }, - "multiple valid hooks": { - expectedErr: false, - inputHooks: []string{"pre-commit", "commit-msg", "post-checkout"}, - expectedHooks: map[string]struct{}{ - "pre-commit": {}, - "commit-msg": {}, - "post-checkout": {}, - }, - }, - "multiple invalid hooks": { - expectedErr: true, - inputHooks: []string{"invalid1", "invalid2"}, - expectedErrMgs: fmt.Errorf("invalid hook types: invalid1, invalid2"), - expectedHooks: nil, - }, - "mix of valid and invalid hooks": { - expectedErr: true, - inputHooks: []string{"pre-commit", "invalid-hook"}, - expectedErrMgs: fmt.Errorf("invalid hook types: invalid-hook"), - expectedHooks: nil, - }, - "all valid hooks explicitly provided": { - expectedErr: false, - inputHooks: []string{"pre-commit", "prepare-commit-msg", "commit-msg", "post-commit", "post-checkout"}, - expectedHooks: map[string]struct{}{ - "pre-commit": {}, - "prepare-commit-msg": {}, - "commit-msg": {}, - "post-commit": {}, - "post-checkout": {}, - }, - }, - "duplicate valid hooks": { - expectedErr: false, - inputHooks: []string{"pre-commit", "pre-commit"}, - expectedHooks: map[string]struct{}{ - "pre-commit": {}, - }, - }, - } - - for name, test := range testData { - t.Run(name, func(t *testing.T) { - resp, err := validateAllowHooks(test.inputHooks) - - if test.expectedErr { - assert.Error(t, err) - assert.EqualError(t, err, test.expectedErrMgs.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, test.expectedHooks, resp) - } - }) - } -} diff --git a/internal/githooks/githooks.go b/internal/githooks/githooks.go index 6a67dbc..ffae293 100644 --- a/internal/githooks/githooks.go +++ b/internal/githooks/githooks.go @@ -53,3 +53,21 @@ func generateHooks(hook []string) []GitHook { func hookPath(hookType string) string { return filepath.Join(hooksBaseDir, hookType) } + +// AllowedGitHooks returns a slice of allowed Git hook types. +func AllowedGitHooks() map[string]struct{} { + checkout := getCheckoutHooks() + commit := getCommitHook() + + allowedHooks := make(map[string]struct{}, len(checkout)+len(commit)) + + for _, hook := range checkout { + allowedHooks[hook] = struct{}{} + } + + for _, hook := range commit { + allowedHooks[hook] = struct{}{} + } + + return allowedHooks +} diff --git a/internal/githooks/githooks_test.go b/internal/githooks/githooks_test.go index f746109..53a3326 100644 --- a/internal/githooks/githooks_test.go +++ b/internal/githooks/githooks_test.go @@ -55,3 +55,23 @@ func TestNewGitHooks(t *testing.T) { assert.Equal(t, gitHookTemplate, hook.Template, "checkout hook Template should match gitHookTemplate") } } + +// TestAllowedGitHooks verifies that AllowedGitHooks returns the expected map of allowed Git hooks. +func TestAllowedGitHooks(t *testing.T) { + allowedHooks := AllowedGitHooks() + + expectedHooks := []string{ + PreCommit, + PrepareCommitMsg, + CommitMsg, + PostCommit, + PostCheckout, + } + + assert.Len(t, allowedHooks, len(expectedHooks), "AllowedGitHooks should return the correct number of hooks") + + for _, hook := range expectedHooks { + _, exists := allowedHooks[hook] + assert.True(t, exists, "Hook %s should be in the allowed hooks map", hook) + } +} diff --git a/internal/validate/validate.go b/internal/validate/validate.go index e3711db..2577e03 100644 --- a/internal/validate/validate.go +++ b/internal/validate/validate.go @@ -4,6 +4,10 @@ import ( "fmt" "os" "path/filepath" + "strings" + + "github.com/devbytes-cloud/freight/internal/githooks" + "github.com/pterm/pterm" ) var ( @@ -35,3 +39,28 @@ func CurrentWD() (string, error) { return dir, nil } + +// GitHooks validates the provided allow hooks and returns a map of valid hooks. +func GitHooks(allow []string) (map[string]struct{}, error) { + allowedGitHooks := githooks.AllowedGitHooks() + if len(allow) == 0 { + pterm.Debug.Println("No hooks provided, using default allowed hooks") + return allowedGitHooks, nil + } + + inputHooks := map[string]struct{}{} + var invalidHooks []string + for _, v := range allow { + if _, ok := allowedGitHooks[v]; !ok { + invalidHooks = append(invalidHooks, v) + continue + } + inputHooks[v] = struct{}{} + } + + if len(invalidHooks) > 0 { + return nil, fmt.Errorf("invalid hook types: %s", strings.Join(invalidHooks, ", ")) + } + + return inputHooks, nil +} diff --git a/internal/validate/validate_test.go b/internal/validate/validate_test.go index 9696095..5be80db 100644 --- a/internal/validate/validate_test.go +++ b/internal/validate/validate_test.go @@ -1,6 +1,7 @@ package validate_test import ( + "fmt" "os" "path/filepath" "testing" @@ -10,6 +11,13 @@ import ( "github.com/stretchr/testify/require" ) +type hookStructure struct { + inputHooks []string + expectedHooks map[string]struct{} + expectedErr bool + expectedErrMgs error +} + func TestGitDirs(t *testing.T) { tt := []struct { name string @@ -60,3 +68,85 @@ func TestCurrentWD(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, wd) } + +func TestValidateAllowHooks(t *testing.T) { + testData := map[string]hookStructure{ + "no input hooks": { + expectedErr: false, + inputHooks: []string{}, + expectedHooks: map[string]struct{}{ + "pre-commit": {}, + "prepare-commit-msg": {}, + "commit-msg": {}, + "post-commit": {}, + "post-checkout": {}, + }, + }, + "only pre-commit hook": { + expectedErr: false, + inputHooks: []string{"pre-commit"}, + expectedHooks: map[string]struct{}{ + "pre-commit": {}, + }, + }, + "invalid hook name hook": { + expectedErr: true, + inputHooks: []string{"invalid hook name"}, + expectedErrMgs: fmt.Errorf("invalid hook types: invalid hook name"), + expectedHooks: nil, + }, + "multiple valid hooks": { + expectedErr: false, + inputHooks: []string{"pre-commit", "commit-msg", "post-checkout"}, + expectedHooks: map[string]struct{}{ + "pre-commit": {}, + "commit-msg": {}, + "post-checkout": {}, + }, + }, + "multiple invalid hooks": { + expectedErr: true, + inputHooks: []string{"invalid1", "invalid2"}, + expectedErrMgs: fmt.Errorf("invalid hook types: invalid1, invalid2"), + expectedHooks: nil, + }, + "mix of valid and invalid hooks": { + expectedErr: true, + inputHooks: []string{"pre-commit", "invalid-hook"}, + expectedErrMgs: fmt.Errorf("invalid hook types: invalid-hook"), + expectedHooks: nil, + }, + "all valid hooks explicitly provided": { + expectedErr: false, + inputHooks: []string{"pre-commit", "prepare-commit-msg", "commit-msg", "post-commit", "post-checkout"}, + expectedHooks: map[string]struct{}{ + "pre-commit": {}, + "prepare-commit-msg": {}, + "commit-msg": {}, + "post-commit": {}, + "post-checkout": {}, + }, + }, + "duplicate valid hooks": { + expectedErr: false, + inputHooks: []string{"pre-commit", "pre-commit"}, + expectedHooks: map[string]struct{}{ + "pre-commit": {}, + }, + }, + } + + for name, test := range testData { + t.Run(name, func(t *testing.T) { + resp, err := validate.GitHooks(test.inputHooks) + + if test.expectedErr { + assert.Error(t, err) + assert.EqualError(t, err, test.expectedErrMgs.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, test.expectedHooks, resp) + } + }) + } +}