diff --git a/Makefile b/Makefile index 69ac1e8b3b..a1fecb3821 100644 --- a/Makefile +++ b/Makefile @@ -132,7 +132,7 @@ codequality: $(GO) get -u github.com/fzipp/gocyclo; \ fi @$(foreach gofile, $(GOFILES_NOVENDOR),\ - gocyclo -over 15 $(gofile);) + gocyclo -over 15 $(gofile) || exit 1;) @$(call ok) @echo -n " LINT " diff --git a/action/action_test.go b/action/action_test.go index 2f1c20be00..9f8379ab1b 100644 --- a/action/action_test.go +++ b/action/action_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/blang/semver" + "github.com/fatih/color" "github.com/google/go-cmp/cmp" gpgmock "github.com/justwatchcom/gopass/backend/gpg/mock" "github.com/justwatchcom/gopass/config" @@ -53,6 +54,10 @@ func newMock(ctx context.Context, dir string) (*Action, error) { func capture(t *testing.T, fn func() error) string { t.Helper() old := os.Stdout + + oldcol := color.NoColor + color.NoColor = true + r, w, _ := os.Pipe() os.Stdout = w @@ -67,6 +72,7 @@ func capture(t *testing.T, fn func() error) string { // back to normal _ = w.Close() os.Stdout = old + color.NoColor = oldcol if err != nil { t.Errorf("Error: %s", err) } diff --git a/action/binary_test.go b/action/binary_test.go index 9ee8fb899d..3bc87fd743 100644 --- a/action/binary_test.go +++ b/action/binary_test.go @@ -62,25 +62,93 @@ func TestBinary(t *testing.T) { if err := act.BinarySum(ctx, c); err == nil { t.Errorf("Should fail") } +} + +func TestBinaryCat(t *testing.T) { + td, err := ioutil.TempDir("", "gopass-") + if err != nil { + t.Fatalf("Error: %s", err) + } + defer func() { + _ = os.RemoveAll(td) + }() + + ctx := context.Background() + ctx = ctxutil.WithAlwaysYes(ctx, true) + ctx = out.WithHidden(ctx, true) + act, err := newMock(ctx, td) + if err != nil { + t.Fatalf("Error: %s", err) + } + + app := cli.NewApp() + + buf := &bytes.Buffer{} + out.Stdout = buf + defer func() { + out.Stdout = os.Stdout + }() + + infile := filepath.Join(td, "input.txt") + if err := ioutil.WriteFile(infile, []byte("0xDEADBEEF"), 0644); err != nil { + t.Fatalf("Failed to write input file: %s", err) + } + if err := act.binaryCopy(ctx, infile, "bar", true); err != nil { + t.Fatalf("Failed to move file to store: %s", err) + } // binary cat bar - fs = flag.NewFlagSet("default", flag.ContinueOnError) + fs := flag.NewFlagSet("default", flag.ContinueOnError) if err := fs.Parse([]string{"bar"}); err != nil { t.Fatalf("Error: %s", err) } - c = cli.NewContext(app, fs, nil) + c := cli.NewContext(app, fs, nil) if err := act.BinaryCat(ctx, c); err != nil { t.Errorf("Should not fail") } +} + +func TestBinaryCopy(t *testing.T) { + td, err := ioutil.TempDir("", "gopass-") + if err != nil { + t.Fatalf("Error: %s", err) + } + defer func() { + _ = os.RemoveAll(td) + }() + + ctx := context.Background() + ctx = ctxutil.WithAlwaysYes(ctx, true) + ctx = out.WithHidden(ctx, true) + act, err := newMock(ctx, td) + if err != nil { + t.Fatalf("Error: %s", err) + } + + app := cli.NewApp() + + buf := &bytes.Buffer{} + out.Stdout = buf + defer func() { + out.Stdout = os.Stdout + }() + + infile := filepath.Join(td, "input.txt") + if err := ioutil.WriteFile(infile, []byte("0xDEADBEEF"), 0644); err != nil { + t.Fatalf("Failed to write input file: %s", err) + } + if err := act.binaryCopy(ctx, infile, "bar", true); err != nil { + t.Fatalf("Failed to move file to store: %s", err) + } outfile := filepath.Join(td, "output.txt") // binary copy bar tempdir/bar - fs = flag.NewFlagSet("default", flag.ContinueOnError) + fs := flag.NewFlagSet("default", flag.ContinueOnError) if err := fs.Parse([]string{"bar", outfile}); err != nil { t.Fatalf("Error: %s", err) } - c = cli.NewContext(app, fs, nil) + c := cli.NewContext(app, fs, nil) if err := act.BinaryCopy(ctx, c); err != nil { t.Errorf("Should not fail") } @@ -94,13 +162,47 @@ func TestBinary(t *testing.T) { if err := act.BinaryMove(ctx, c); err != nil { t.Errorf("Should not fail") } +} + +func TestBinarySum(t *testing.T) { + td, err := ioutil.TempDir("", "gopass-") + if err != nil { + t.Fatalf("Error: %s", err) + } + defer func() { + _ = os.RemoveAll(td) + }() + + ctx := context.Background() + ctx = ctxutil.WithAlwaysYes(ctx, true) + ctx = out.WithHidden(ctx, true) + act, err := newMock(ctx, td) + if err != nil { + t.Fatalf("Error: %s", err) + } + + app := cli.NewApp() + + buf := &bytes.Buffer{} + out.Stdout = buf + defer func() { + out.Stdout = os.Stdout + }() + + infile := filepath.Join(td, "input.txt") + if err := ioutil.WriteFile(infile, []byte("0xDEADBEEF"), 0644); err != nil { + t.Fatalf("Failed to write input file: %s", err) + } + if err := act.binaryCopy(ctx, infile, "bar", true); err != nil { + t.Fatalf("Failed to move file to store: %s", err) + } // binary sum bar - fs = flag.NewFlagSet("default", flag.ContinueOnError) + fs := flag.NewFlagSet("default", flag.ContinueOnError) if err := fs.Parse([]string{"bar"}); err != nil { t.Fatalf("Error: %s", err) } - c = cli.NewContext(app, fs, nil) + c := cli.NewContext(app, fs, nil) if err := act.BinarySum(ctx, c); err != nil { t.Errorf("Should not fail") } diff --git a/action/config_test.go b/action/config_test.go index 7d60981d69..c5e56aedf7 100644 --- a/action/config_test.go +++ b/action/config_test.go @@ -72,9 +72,8 @@ func TestConfig(t *testing.T) { } buf.Reset() - act.cfg.Mounts["foo"] = &config.StoreConfig{} - // action.printConfigValues + act.cfg.Mounts["foo"] = &config.StoreConfig{} if err := act.printConfigValues(ctx, "", "nopager"); err != nil { t.Errorf("Error: %s", err) } @@ -84,5 +83,40 @@ foo/nopager: false` if sv != want { t.Errorf("Wrong config result: '%s' != '%s'", sv, want) } + + delete(act.cfg.Mounts, "foo") buf.Reset() + + // config autoimport + fs := flag.NewFlagSet("default", flag.ContinueOnError) + if err := fs.Parse([]string{"autoimport"}); err != nil { + t.Fatalf("Error: %s", err) + } + c = cli.NewContext(app, fs, nil) + if err := act.Config(ctx, c); err != nil { + t.Errorf("Error: %s", err) + } + want = `autoimport: true` + sv = strings.TrimSpace(buf.String()) + if sv != want { + t.Errorf("Wrong config result: '%s' != '%s'", sv, want) + } + buf.Reset() + + // config autoimport false + fs = flag.NewFlagSet("default", flag.ContinueOnError) + if err := fs.Parse([]string{"autoimport", "false"}); err != nil { + t.Fatalf("Error: %s", err) + } + c = cli.NewContext(app, fs, nil) + if err := act.Config(ctx, c); err != nil { + t.Errorf("Error: %s", err) + } + want = `autoimport: false` + sv = strings.TrimSpace(buf.String()) + if sv != want { + t.Errorf("Wrong config result: '%s' != '%s'", sv, want) + } + buf.Reset() + } diff --git a/action/copy_test.go b/action/copy_test.go index f2418909d1..10d6a5bb10 100644 --- a/action/copy_test.go +++ b/action/copy_test.go @@ -27,20 +27,43 @@ func TestCopy(t *testing.T) { t.Fatalf("Error: %s", err) } + buf := &bytes.Buffer{} + out.Stdout = buf + defer func() { + out.Stdout = os.Stdout + }() + app := cli.NewApp() + // copy foo bar fs := flag.NewFlagSet("default", flag.ContinueOnError) if err := fs.Parse([]string{"foo", "bar"}); err != nil { t.Fatalf("Error: %s", err) } c := cli.NewContext(app, fs, nil) - buf := &bytes.Buffer{} - out.Stdout = buf - defer func() { - out.Stdout = os.Stdout - }() - if err := act.Copy(ctx, c); err != nil { t.Errorf("Error: %s", err) } + buf.Reset() + + // copy not-found still-not-there + fs = flag.NewFlagSet("default", flag.ContinueOnError) + if err := fs.Parse([]string{"not-found", "still-not-there"}); err != nil { + t.Fatalf("Error: %s", err) + } + c = cli.NewContext(app, fs, nil) + + if err := act.Copy(ctx, c); err == nil { + t.Errorf("Should fail") + } + buf.Reset() + + // copy + fs = flag.NewFlagSet("default", flag.ContinueOnError) + c = cli.NewContext(app, fs, nil) + + if err := act.Copy(ctx, c); err == nil { + t.Errorf("Should fail") + } + buf.Reset() } diff --git a/action/create_test.go b/action/create_test.go index 18c191ea74..e82e766c66 100644 --- a/action/create_test.go +++ b/action/create_test.go @@ -1,9 +1,21 @@ package action -import "testing" +import ( + "bytes" + "context" + "flag" + "io/ioutil" + "os" + "testing" + + "github.com/justwatchcom/gopass/utils/ctxutil" + "github.com/justwatchcom/gopass/utils/out" + "github.com/urfave/cli" +) func TestExtractHostname(t *testing.T) { for in, out := range map[string]string{ + "": "", "http://www.example.org/": "www.example.org", "++#+++#jhlkadsrezu 33 553q ++++##$§&": "jhlkadsrezu_33_553q", "www.example.org/?foo=bar#abc": "www.example.org", @@ -13,3 +25,36 @@ func TestExtractHostname(t *testing.T) { } } } + +func TestCreate(t *testing.T) { + td, err := ioutil.TempDir("", "gopass-") + if err != nil { + t.Fatalf("Error: %s", err) + } + defer func() { + _ = os.RemoveAll(td) + }() + + ctx := context.Background() + ctx = ctxutil.WithAlwaysYes(ctx, true) + act, err := newMock(ctx, td) + if err != nil { + t.Fatalf("Error: %s", err) + } + + buf := &bytes.Buffer{} + out.Stdout = buf + defer func() { + out.Stdout = os.Stdout + }() + + app := cli.NewApp() + // create + fs := flag.NewFlagSet("default", flag.ContinueOnError) + c := cli.NewContext(app, fs, nil) + + if err := act.Create(ctx, c); err == nil { + t.Errorf("Should fail") + } + buf.Reset() +} diff --git a/action/delete_test.go b/action/delete_test.go index a9e24c9c2a..72c285d215 100644 --- a/action/delete_test.go +++ b/action/delete_test.go @@ -8,6 +8,8 @@ import ( "os" "testing" + "github.com/justwatchcom/gopass/store/secret" + "github.com/justwatchcom/gopass/utils/ctxutil" "github.com/justwatchcom/gopass/utils/out" "github.com/urfave/cli" ) @@ -22,21 +24,52 @@ func TestDelete(t *testing.T) { }() ctx := context.Background() + ctx = ctxutil.WithAlwaysYes(ctx, true) act, err := newMock(ctx, td) if err != nil { t.Fatalf("Error: %s", err) } - app := cli.NewApp() - c := cli.NewContext(app, flag.NewFlagSet("default", flag.ContinueOnError), nil) - buf := &bytes.Buffer{} out.Stdout = buf defer func() { out.Stdout = os.Stdout }() + app := cli.NewApp() + + // delete + c := cli.NewContext(app, flag.NewFlagSet("default", flag.ContinueOnError), nil) + if err := act.Delete(ctx, c); err == nil || err.Error() != "Usage: action.test rm name" { t.Errorf("Should fail") } + buf.Reset() + + // delete foo + fs := flag.NewFlagSet("default", flag.ContinueOnError) + if err := fs.Parse([]string{"foo"}); err != nil { + t.Fatalf("Error: %s", err) + } + c = cli.NewContext(app, fs, nil) + + if err := act.Delete(ctx, c); err != nil { + t.Errorf("Error: %s", err) + } + buf.Reset() + + // delete foo bar + if err := act.Store.Set(ctx, "foo", secret.New("123", "---\nbar: zab")); err != nil { + t.Errorf("Failed to add secret: %s", err) + } + fs = flag.NewFlagSet("default", flag.ContinueOnError) + if err := fs.Parse([]string{"foo", "bar"}); err != nil { + t.Fatalf("Error: %s", err) + } + c = cli.NewContext(app, fs, nil) + + if err := act.Delete(ctx, c); err != nil { + t.Errorf("Error: %s", err) + } + buf.Reset() } diff --git a/action/edit.go b/action/edit.go index b49c3fa8bf..106bf48e96 100644 --- a/action/edit.go +++ b/action/edit.go @@ -11,6 +11,7 @@ import ( "github.com/fatih/color" "github.com/justwatchcom/gopass/store/secret" "github.com/justwatchcom/gopass/store/sub" + "github.com/justwatchcom/gopass/utils/ctxutil" "github.com/justwatchcom/gopass/utils/fsutil" "github.com/justwatchcom/gopass/utils/out" "github.com/justwatchcom/gopass/utils/pwgen" @@ -77,6 +78,10 @@ func (s *Action) Edit(ctx context.Context, c *cli.Context) error { } func (s *Action) editor(ctx context.Context, editor string, content []byte) ([]byte, error) { + if !ctxutil.IsTerminal(ctx) { + return nil, errors.New("need terminal") + } + tmpfile, err := fsutil.TempFile(ctx, "gopass-edit") if err != nil { return []byte{}, errors.Errorf("failed to create tmpfile %s: %s", editor, err) diff --git a/action/edit_test.go b/action/edit_test.go index 09f3f21900..62149bc720 100644 --- a/action/edit_test.go +++ b/action/edit_test.go @@ -9,6 +9,7 @@ import ( "os/exec" "testing" + "github.com/justwatchcom/gopass/utils/ctxutil" "github.com/justwatchcom/gopass/utils/out" "github.com/urfave/cli" ) @@ -23,23 +24,38 @@ func TestEdit(t *testing.T) { }() ctx := context.Background() + ctx = ctxutil.WithAlwaysYes(ctx, true) + ctx = ctxutil.WithTerminal(ctx, false) act, err := newMock(ctx, td) if err != nil { t.Fatalf("Error: %s", err) } - app := cli.NewApp() - c := cli.NewContext(app, flag.NewFlagSet("default", flag.ContinueOnError), nil) - buf := &bytes.Buffer{} out.Stdout = buf defer func() { out.Stdout = os.Stdout }() + app := cli.NewApp() + + // edit + c := cli.NewContext(app, flag.NewFlagSet("default", flag.ContinueOnError), nil) if err := act.Edit(ctx, c); err == nil || err.Error() != "Usage: action.test edit secret" { t.Errorf("Should fail") } + + // edit foo + fs := flag.NewFlagSet("default", flag.ContinueOnError) + if err := fs.Parse([]string{"foo"}); err != nil { + t.Fatalf("Error: %s", err) + } + c = cli.NewContext(app, fs, nil) + + if err := act.Edit(ctx, c); err == nil { + t.Errorf("Should fail") + } + buf.Reset() } func TestEditor(t *testing.T) { diff --git a/action/find_test.go b/action/find_test.go index 55d2490393..f4fb82a89b 100644 --- a/action/find_test.go +++ b/action/find_test.go @@ -6,6 +6,7 @@ import ( "flag" "io/ioutil" "os" + "strings" "testing" "github.com/justwatchcom/gopass/utils/out" @@ -27,16 +28,46 @@ func TestFind(t *testing.T) { t.Fatalf("Error: %s", err) } - app := cli.NewApp() - c := cli.NewContext(app, flag.NewFlagSet("default", flag.ContinueOnError), nil) - buf := &bytes.Buffer{} out.Stdout = buf defer func() { out.Stdout = os.Stdout }() + app := cli.NewApp() + + // find + c := cli.NewContext(app, flag.NewFlagSet("default", flag.ContinueOnError), nil) if err := act.Find(ctx, c); err == nil || err.Error() != "Usage: action.test find arg" { t.Errorf("Should fail") } + + // find fo + fs := flag.NewFlagSet("default", flag.ContinueOnError) + if err := fs.Parse([]string{"fo"}); err != nil { + t.Fatalf("Error: %s", err) + } + c = cli.NewContext(app, fs, nil) + + out := capture(t, func() error { + return act.Find(ctx, c) + }) + out = strings.TrimSpace(out) + want := "0xDEADBEEF" + if out != want { + t.Errorf("'%s' != '%s'", out, want) + } + buf.Reset() + + // find yo + fs = flag.NewFlagSet("default", flag.ContinueOnError) + if err := fs.Parse([]string{"yo"}); err != nil { + t.Fatalf("Error: %s", err) + } + c = cli.NewContext(app, fs, nil) + + if err := act.Find(ctx, c); err == nil { + t.Errorf("Should fail") + } + buf.Reset() } diff --git a/utils/cui/cui.go b/utils/cui/cui.go index 814008031d..b5db9f993c 100644 --- a/utils/cui/cui.go +++ b/utils/cui/cui.go @@ -7,6 +7,7 @@ import ( "github.com/fatih/color" "github.com/jroimartin/gocui" + "github.com/justwatchcom/gopass/utils/ctxutil" ) type selection struct { @@ -161,6 +162,9 @@ func (s *selection) sync(g *gocui.Gui, v *gocui.View) error { // GetSelection show a navigateable multiple-choice list to the user // and returns the selected entry along with the action func GetSelection(ctx context.Context, prompt, usage string, choices []string) (string, int) { + if ctxutil.IsAlwaysYes(ctx) || !ctxutil.IsInteractive(ctx) { + return "impossible", 0 + } g, err := gocui.NewGui(gocui.OutputNormal) if err != nil { panic(err) diff --git a/utils/termwiz/selection.go b/utils/termwiz/selection.go index 3cfdbfb922..e9a9a7cd00 100644 --- a/utils/termwiz/selection.go +++ b/utils/termwiz/selection.go @@ -19,6 +19,10 @@ func tbprint(x, y int, fg, bg termbox.Attribute, msg string) { // GetSelection show a navigateable multiple-choice list to the user // and returns the selected entry along with the action func GetSelection(ctx context.Context, prompt, usage string, choices []string) (string, int) { + if ctxutil.IsAlwaysYes(ctx) || !ctxutil.IsInteractive(ctx) { + return "impossible", 0 + } + if prompt != "" { prompt += " " } @@ -54,14 +58,7 @@ func GetSelection(ctx context.Context, prompt, usage string, choices []string) ( } tbprint(0, 1+i-offset, coldef, coldef, fmt.Sprintf("%s %s", mark, c)) } - usageLine := usage - if usageLine == "" { - usageLine = "<↑/↓> to change the selection, <→> to show, <←> to copy, to sync, to quit" - } - if ctxutil.IsDebug(ctx) { - usageLine += " - DEBUG: " + fmt.Sprintf("Offset: %d - Cur: %d - Choices: %d", offset, cur, len(choices)) - } - tbprint(0, h-1, coldef, coldef, usageLine) + tbprint(0, h-1, coldef, coldef, formatUsageLine(ctx, usage, offset, cur, choices)) _ = termbox.Flush() var act string if act, cur = tbpoll(cur, len(choices)); act != "" { @@ -70,6 +67,17 @@ func GetSelection(ctx context.Context, prompt, usage string, choices []string) ( } } +func formatUsageLine(ctx context.Context, usage string, offset, cur int, choices []string) string { + usageLine := usage + if usageLine == "" { + usageLine = "<↑/↓> to change the selection, <→> to show, <←> to copy, to sync, to quit" + } + if ctxutil.IsDebug(ctx) { + usageLine += " - DEBUG: " + fmt.Sprintf("Offset: %d - Cur: %d - Choices: %d", offset, cur, len(choices)) + } + return usageLine +} + func tbpoll(cur, max int) (string, int) { switch ev := termbox.PollEvent(); ev.Type { case termbox.EventKey: