Skip to content

Commit

Permalink
🚧 Add capability to find hook location
Browse files Browse the repository at this point in the history
Find location of existing hook directory. This is necessary to
determine where to install new hooks.

By adding the runner to the struct, this caused some tests to fail as
the memory address was in a field. Refactored the New function tests to
handle this change.
  • Loading branch information
mikelorant committed Feb 16, 2023
1 parent 0627636 commit 6fe8477
Show file tree
Hide file tree
Showing 4 changed files with 274 additions and 15 deletions.
9 changes: 9 additions & 0 deletions internal/hook/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package hook

import (
"errors"
"io"

"github.com/mikelorant/committed/internal/shell"
)

type Hook struct {
Action Action
Runner Runner
}

type Options struct {
Expand All @@ -14,6 +18,10 @@ type Options struct {
Commit bool
}

type (
Runner func(io.Writer, string, []string) error
)

type (
Action int
)
Expand All @@ -30,6 +38,7 @@ const (
func New(opts Options) Hook {
return Hook{
Action: action(opts),
Runner: shell.Run,
}
}

Expand Down
33 changes: 18 additions & 15 deletions internal/hook/hook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,26 @@ func TestNew(t *testing.T) {
tests := []struct {
name string
input hook.Options
output hook.Hook
output hook.Action
}{
{
name: "empty",
output: hook.Hook{},
output: hook.ActionUnset,
},
{
name: "install",
input: hook.Options{Install: true},
output: hook.Hook{Action: hook.ActionInstall},
output: hook.ActionInstall,
},
{
name: "uninstall",
input: hook.Options{Uninstall: true},
output: hook.Hook{Action: hook.ActionUninstall},
output: hook.ActionUninstall,
},
{
name: "commit",
input: hook.Options{Commit: true},
output: hook.Hook{Action: hook.ActionCommit},
output: hook.ActionCommit,
},
}

Expand All @@ -44,7 +44,7 @@ func TestNew(t *testing.T) {

got := hook.New(tt.input)

assert.Equal(t, tt.output, got)
assert.Equal(t, tt.output, got.Action)
})
}
}
Expand All @@ -54,31 +54,30 @@ func TestDo(t *testing.T) {

tests := []struct {
name string
input hook.Hook
input hook.Action
err error
}{
{
name: "empty",
input: hook.Hook{},
input: hook.ActionUnset,
err: hook.ErrAction,
},
{
name: "install",
input: hook.Hook{Action: hook.ActionInstall},
input: hook.ActionInstall,
},
{
name: "uninstall",
input: hook.Hook{Action: hook.ActionUninstall},
input: hook.ActionUninstall,
},
{
name: "commit",
input: hook.Hook{Action: hook.ActionCommit},
input: hook.ActionCommit,
err: hook.ErrAction,
},
{
name: "nil",
input: hook.Hook{},
err: hook.ErrAction,
name: "nil",
err: hook.ErrAction,
},
}

Expand All @@ -88,7 +87,11 @@ func TestDo(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

err := tt.input.Do()
hook := hook.Hook{
Action: tt.input,
}

err := hook.Do()

assert.ErrorIs(t, err, tt.err)
})
Expand Down
56 changes: 56 additions & 0 deletions internal/hook/locate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package hook

import (
"bytes"
"errors"
"fmt"
"io"
"strings"
)

var (
gitCommand = "git"
gitGlobalArgs = []string{"config", "--get", "core.hooksPath"}
gitRepositoryArgs = []string{"rev-parse", "--absolute-git-dir"}
)

var ErrLocation = errors.New("no hook location")

func Locate(run Runner) (string, error) {
var err error

glob, err := runCmd(run, gitCommand, gitGlobalArgs)
if err != nil {
return "", fmt.Errorf("unable to check global hook: %w", err)
}

if glob != "" {
return glob, nil
}

repo, err := runCmd(run, gitCommand, gitRepositoryArgs)
if err != nil {
return "", fmt.Errorf("unable to check repository hook: %w", err)
}

if repo != "" {
return repo, nil
}

return "", ErrLocation
}

func runCmd(run Runner, cmd string, args []string) (string, error) {
var buf bytes.Buffer

if err := run(&buf, cmd, args); err != nil {
return "", fmt.Errorf("unable to run command: %w", err)
}

out, err := io.ReadAll(&buf)
if err != nil {
return "", fmt.Errorf("unable to read buffer: %w", err)
}

return strings.TrimSpace(string(out)), nil
}
191 changes: 191 additions & 0 deletions internal/hook/locate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package hook_test

import (
"errors"
"io"
"testing"

"github.com/mikelorant/committed/internal/hook"
"github.com/stretchr/testify/assert"
)

type MockRun struct {
idx int

glob string
globErr error
repo string
repoErr error

cmd string
args []string
}

var errMock = errors.New("error")

func (r *MockRun) Run() func(io.Writer, string, []string) error {
return func(w io.Writer, cmd string, args []string) error {
r.cmd = cmd
r.args = args

if r.idx == 0 && r.glob != "" {
if r.globErr != nil {
return r.globErr
}

io.WriteString(w, r.glob)

return nil
}

if r.idx == 1 && r.repo != "" {
if r.repoErr != nil {
return r.repoErr
}

io.WriteString(w, r.repo)

return nil
}

r.idx++

return nil
}
}

func TestLocate(t *testing.T) {
t.Parallel()

type args struct {
glob string
globErr error

repo string
repoErr error

output string
}

type want struct {
cmd string
args []string
err string
output string
}

tests := []struct {
name string
args args
want want
}{
{
name: "default",
want: want{
err: "no hook location",
},
},
{
name: "global",
args: args{
glob: "test",
},
want: want{
cmd: "git",
args: []string{"config", "--get", "core.hooksPath"},
output: "test",
},
},
{
name: "repo",
args: args{
repo: "test",
},
want: want{
cmd: "git",
args: []string{"rev-parse", "--absolute-git-dir"},
output: "test",
},
},
{
name: "global_error",
args: args{
glob: "test",
globErr: errMock,
},
want: want{
err: "unable to check global hook: unable to run command: error",
},
},
{
name: "repo_error",
args: args{
repo: "test",
repoErr: errMock,
},
want: want{
err: "unable to check repository hook: unable to run command: error",
},
},
{
name: "spaces_at_end",
args: args{
glob: "test ",
},
want: want{
cmd: "git",
args: []string{"config", "--get", "core.hooksPath"},
output: "test",
},
},
{
name: "spaces_at_beginning",
args: args{
glob: " test",
},
want: want{
cmd: "git",
args: []string{"config", "--get", "core.hooksPath"},
output: "test",
},
},
{
name: "spaces_at_beginning_end",
args: args{
glob: " test ",
},
want: want{
cmd: "git",
args: []string{"config", "--get", "core.hooksPath"},
output: "test",
},
},
}

for _, tt := range tests {
tt := tt

t.Run(tt.name, func(t *testing.T) {
t.Parallel()

r := MockRun{
glob: tt.args.glob,
globErr: tt.args.globErr,
repo: tt.args.repo,
repoErr: tt.args.repoErr,
}

got, err := hook.Locate(r.Run())
if tt.want.err != "" {
assert.Error(t, err)
assert.ErrorContains(t, err, tt.want.err)
return
}

assert.NoError(t, err)
assert.Equal(t, tt.want.cmd, r.cmd)
assert.Equal(t, tt.want.args, r.args)
assert.Equal(t, tt.want.output, got)
})
}
}

0 comments on commit 6fe8477

Please sign in to comment.