lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 33011264f014e14fe379a3b752ec51e97ed85cf6
parent 0e5b1bb919eb2c1d67b35078e964440d4cd61f6b
Author: Sean Enck <sean@ttypty.com>
Date:   Sat,  4 Mar 2023 08:47:03 -0500

refactor all to use an interface for app command inputs

Diffstat:
Mcmd/main.go | 42+++++++++++++-----------------------------
Minternal/app/core.go | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Minternal/app/hash.go | 5+++--
Minternal/app/hash_test.go | 14++++++--------
Minternal/app/insert.go | 37+++++++++++++++++--------------------
Minternal/app/insert_test.go | 145++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Minternal/app/listfind.go | 7++++---
Minternal/app/listfind_test.go | 27++++++++++-----------------
Minternal/app/move.go | 6++++--
Minternal/app/move_test.go | 52++++++++++++++++++++++++++++++++++++++--------------
Minternal/app/remove.go | 11+++++------
Minternal/app/remove_test.go | 28+++++++++++++++++-----------
Minternal/app/showclip.go | 8++++----
Minternal/app/showclip_test.go | 24+++++++++++-------------
Minternal/app/stats.go | 8++++----
Minternal/app/stats_test.go | 21+++++++++------------
Mscripts/testing/expected.log | 6+++---
17 files changed, 318 insertions(+), 200 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go @@ -11,7 +11,6 @@ import ( "time" "github.com/enckse/lockbox/internal/app" - "github.com/enckse/lockbox/internal/backend" "github.com/enckse/lockbox/internal/cli" "github.com/enckse/lockbox/internal/inputs" "github.com/enckse/lockbox/internal/platform" @@ -42,8 +41,6 @@ func handleEarly(command string, args []string) (bool, error) { return true, nil case cli.TOTPCommand: return true, totp.Call(args) - case cli.HashCommand: - return true, app.Hash(os.Stdout, args) case cli.ClearCommand: return true, clearClipboard(args) } @@ -64,38 +61,33 @@ func run() error { if ok { return nil } - t, err := backend.NewTransaction() + p, err := app.NewDefaultCommand(sub) if err != nil { - return fmt.Errorf("unable to build transaction model: %w", err) + return err } switch command { case cli.ReKeyCommand: - if confirm("proceed with rekey") { - return t.ReKey() + if p.Confirm("proceed with rekey") { + return p.Transaction().ReKey() } case cli.ListCommand, cli.FindCommand: - return app.ListFind(t, os.Stdout, command == cli.FindCommand, sub) + return app.ListFind(p, command == cli.FindCommand) case cli.MoveCommand: - return app.Move(t, sub, confirm) + return app.Move(p) case cli.InsertCommand: - insert := app.InsertOptions{} - parser := app.InsertArgsOptions{} - parser.IsNoTOTP = inputs.IsNoTOTP - parser.TOTPToken = inputs.TOTPToken - insert.Confirm = confirm - insert.IsPipe = inputs.IsInputFromPipe - insert.Input = inputs.GetUserInputPassword - insertArgs, err := parser.ReadArgs(insert, sub) + insertArgs, err := app.ReadArgs(p) if err != nil { return err } - return insertArgs.Do(os.Stdout, t) + return insertArgs.Do(p) case cli.RemoveCommand: - return app.Remove(os.Stdout, t, sub, confirm) + return app.Remove(p) case cli.StatsCommand: - return app.Stats(os.Stdout, t, sub) + return app.Stats(p) case cli.ShowCommand, cli.ClipCommand: - return app.ShowClip(os.Stdout, t, command == cli.ShowCommand, sub) + return app.ShowClip(p, command == cli.ShowCommand) + case cli.HashCommand: + return app.Hash(p) default: return fmt.Errorf("unknown command: %s", command) } @@ -130,11 +122,3 @@ func clearClipboard(args []string) error { } return clipboard.CopyTo("") } - -func confirm(prompt string) bool { - yesNo, err := inputs.ConfirmYesNoPrompt(prompt) - if err != nil { - util.Dief("failed to read stdin for confirmation: %v", err) - } - return yesNo -} diff --git a/internal/app/core.go b/internal/app/core.go @@ -1,7 +1,80 @@ // Package app common objects package app +import ( + "io" + "os" + + "github.com/enckse/lockbox/internal/backend" + "github.com/enckse/lockbox/internal/inputs" + "github.com/enckse/lockbox/internal/util" +) + type ( - // Confirm user inputs - Confirm func(string) bool + // CommandOptions define how commands operate as an application + CommandOptions interface { + Confirm(string) bool + Args() []string + Transaction() *backend.Transaction + Writer() io.Writer + } + + // DefaultCommand is the default CLI app type for actual execution + DefaultCommand struct { + args []string + tx *backend.Transaction + } ) + +// NewDefaultCommand creates a new app command +func NewDefaultCommand(args []string) (*DefaultCommand, error) { + t, err := backend.NewTransaction() + if err != nil { + return nil, err + } + return &DefaultCommand{args: args, tx: t}, nil +} + +// Args will get the args passed to the application +func (a *DefaultCommand) Args() []string { + return a.args +} + +// Writer will get stdout +func (a *DefaultCommand) Writer() io.Writer { + return os.Stdout +} + +// Transaction will return the backend transaction +func (a *DefaultCommand) Transaction() *backend.Transaction { + return a.tx +} + +// Confirm will confirm with the user (dying if something abnormal happens) +func (a *DefaultCommand) Confirm(prompt string) bool { + yesNo, err := inputs.ConfirmYesNoPrompt(prompt) + if err != nil { + util.Dief("failed to read stdin for confirmation: %v", err) + } + return yesNo +} + +// IsPipe will indicate if we're receiving pipe input +func (a *DefaultCommand) IsPipe() bool { + return inputs.IsInputFromPipe() +} + +// TOTPToken will get the configured totp token name +func (a *DefaultCommand) TOTPToken() string { + return inputs.TOTPToken() +} + +// IsNoTOTP indicates if TOTP operations are disabled +func (a *DefaultCommand) IsNoTOTP() (bool, error) { + return inputs.IsNoTOTP() +} + +// Input will read user input +func (a *DefaultCommand) Input(pipe, multi bool) ([]byte, error) { + return inputs.GetUserInputPassword(pipe, multi) +} diff --git a/internal/app/hash.go b/internal/app/hash.go @@ -3,17 +3,18 @@ package app import ( "errors" "fmt" - "io" "strings" "github.com/enckse/lockbox/internal/backend" ) // Hash will hash 1-N files -func Hash(w io.Writer, args []string) error { +func Hash(cmd CommandOptions) error { + args := cmd.Args() if len(args) == 0 { return errors.New("hash requires a file") } + w := cmd.Writer() for _, a := range args { t, err := backend.Load(a) if err != nil { diff --git a/internal/app/hash_test.go b/internal/app/hash_test.go @@ -5,21 +5,19 @@ import ( "testing" "github.com/enckse/lockbox/internal/app" - "github.com/enckse/lockbox/internal/backend" ) func TestHash(t *testing.T) { - var buf bytes.Buffer - if err := app.Hash(&buf, []string{}); err.Error() != "hash requires a file" { + c := newMockCommand(t) + if err := app.Hash(c); err.Error() != "hash requires a file" { t.Errorf("invalid error: %v", err) } - setup(t) - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test1"), "pass") - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test3"), "pass") - if err := app.Hash(&buf, []string{"test.kdbx"}); err != nil { + c.buf = bytes.Buffer{} + c.args = []string{"test.kdbx"} + if err := app.Hash(c); err != nil { t.Errorf("invalid error: %v", err) } - if buf.String() == "" { + if c.buf.String() == "" { t.Error("nothing hashed") } } diff --git a/internal/app/insert.go b/internal/app/insert.go @@ -4,7 +4,6 @@ package app import ( "errors" "fmt" - "io" "strings" "github.com/enckse/lockbox/internal/backend" @@ -14,33 +13,30 @@ import ( type ( // InsertOptions are functions required for insert - InsertOptions struct { - IsPipe func() bool - Input func(bool, bool) ([]byte, error) - Confirm Confirm - } - // InsertArgsOptions supports cli arg parsing - InsertArgsOptions struct { - TOTPToken func() string - IsNoTOTP func() (bool, error) + InsertOptions interface { + CommandOptions + IsPipe() bool + Input(bool, bool) ([]byte, error) + TOTPToken() string + IsNoTOTP() (bool, error) } // InsertArgs are parsed insert settings for insert commands InsertArgs struct { Entry string Multi bool - Opts InsertOptions } ) // ReadArgs will read and check insert args -func (p InsertArgsOptions) ReadArgs(cmd InsertOptions, args []string) (InsertArgs, error) { +func ReadArgs(cmd InsertOptions) (InsertArgs, error) { multi := false isTOTP := false idx := 0 - noTOTP, err := p.IsNoTOTP() + noTOTP, err := cmd.IsNoTOTP() if err != nil { return InsertArgs{}, err } + args := cmd.Args() switch len(args) { case 0: return InsertArgs{}, errors.New("insert requires an entry") @@ -64,7 +60,7 @@ func (p InsertArgsOptions) ReadArgs(cmd InsertOptions, args []string) (InsertArg } entry := args[idx] if !noTOTP { - totpToken := p.TOTPToken() + totpToken := cmd.TOTPToken() hasSuffixTOTP := strings.HasSuffix(entry, backend.NewSuffix(totpToken)) if isTOTP { if !hasSuffixTOTP { @@ -77,24 +73,25 @@ func (p InsertArgsOptions) ReadArgs(cmd InsertOptions, args []string) (InsertArg } } - return InsertArgs{Opts: cmd, Multi: multi, Entry: entry}, nil + return InsertArgs{Multi: multi, Entry: entry}, nil } // Do will execute an insert -func (args InsertArgs) Do(w io.Writer, t *backend.Transaction) error { +func (args InsertArgs) Do(cmd InsertOptions) error { + t := cmd.Transaction() existing, err := t.Get(args.Entry, backend.BlankValue) if err != nil { return err } - isPipe := args.Opts.IsPipe() + isPipe := cmd.IsPipe() if existing != nil { if !isPipe { - if !args.Opts.Confirm("overwrite existing") { + if !cmd.Confirm("overwrite existing") { return nil } } } - password, err := args.Opts.Input(isPipe, args.Multi) + password, err := cmd.Input(isPipe, args.Multi) if err != nil { return fmt.Errorf("invalid input: %w", err) } @@ -103,7 +100,7 @@ func (args InsertArgs) Do(w io.Writer, t *backend.Transaction) error { return err } if !isPipe { - fmt.Fprintln(w) + fmt.Fprintln(cmd.Writer()) } return nil } diff --git a/internal/app/insert_test.go b/internal/app/insert_test.go @@ -3,65 +3,121 @@ package app_test import ( "bytes" "errors" + "io" "testing" "github.com/enckse/lockbox/internal/app" "github.com/enckse/lockbox/internal/backend" ) +type ( + mockInsert struct { + command *mockCommand + noTOTP func() (bool, error) + input func(bool, bool) ([]byte, error) + pipe func() bool + token func() string + } +) + +func newMockInsert(t *testing.T) *mockInsert { + m := &mockInsert{} + m.command = newMockCommand(t) + return m +} + +func (m *mockInsert) TOTPToken() string { + return m.token() +} + +func (m *mockInsert) IsPipe() bool { + return m.pipe() +} + +func (m *mockInsert) Input(pipe, multi bool) ([]byte, error) { + return m.input(pipe, multi) +} + +func (m *mockInsert) Args() []string { + return m.command.Args() +} + +func (m *mockInsert) Writer() io.Writer { + return &m.command.buf +} + +func (m *mockInsert) Confirm(p string) bool { + return m.command.Confirm(p) +} + +func (m *mockInsert) IsNoTOTP() (bool, error) { + return m.noTOTP() +} + +func (m *mockInsert) Transaction() *backend.Transaction { + return m.command.Transaction() +} + func TestInsertArgs(t *testing.T) { - obj := app.InsertOptions{} - p := app.InsertArgsOptions{} - p.IsNoTOTP = func() (bool, error) { + m := newMockInsert(t) + m.noTOTP = func() (bool, error) { return true, nil } - if _, err := p.ReadArgs(obj, []string{}); err == nil || err.Error() != "insert requires an entry" { + if _, err := app.ReadArgs(m); err == nil || err.Error() != "insert requires an entry" { t.Errorf("invalid error: %v", err) } - if _, err := p.ReadArgs(obj, []string{"test", "test", "test"}); err == nil || err.Error() != "too many arguments" { + m.command.args = []string{"test", "test", "test"} + if _, err := app.ReadArgs(m); err == nil || err.Error() != "too many arguments" { t.Errorf("invalid error: %v", err) } - r, err := p.ReadArgs(obj, []string{"test"}) + m.command.args = []string{"test"} + r, err := app.ReadArgs(m) if err != nil { t.Errorf("invalid error: %v", err) } if r.Multi || r.Entry != "test" { t.Error("invalid parse") } - if _, err := p.ReadArgs(obj, []string{"-t", "b"}); err == nil || err.Error() != "unknown argument" { + m.command.args = []string{"-t", "b"} + if _, err := app.ReadArgs(m); err == nil || err.Error() != "unknown argument" { t.Errorf("invalid error: %v", err) } - r, err = p.ReadArgs(obj, []string{"-multi", "test3"}) + m.command.args = []string{"-multi", "test3"} + r, err = app.ReadArgs(m) if err != nil { t.Errorf("invalid error: %v", err) } if !r.Multi || r.Entry != "test3" { t.Error("invalid parse") } - p.TOTPToken = func() string { + m.token = func() string { return "test3" } - r, err = p.ReadArgs(obj, []string{"-multi", "test/test3"}) + m.command.args = []string{"-multi", "test/test3"} + r, err = app.ReadArgs(m) if err != nil { t.Errorf("invalid error: %v", err) } - p.IsNoTOTP = func() (bool, error) { + m.noTOTP = func() (bool, error) { return false, nil } - if _, err := p.ReadArgs(obj, []string{"-multi", "test/test3"}); err == nil || err.Error() != "can not insert totp entry without totp flag" { + if _, err := app.ReadArgs(m); err == nil || err.Error() != "can not insert totp entry without totp flag" { t.Errorf("invalid error: %v", err) } - if _, err := p.ReadArgs(obj, []string{"test/test3"}); err == nil || err.Error() != "can not insert totp entry without totp flag" { + m.command.args = []string{"test/test3"} + if _, err := app.ReadArgs(m); err == nil || err.Error() != "can not insert totp entry without totp flag" { t.Errorf("invalid error: %v", err) } - r, err = p.ReadArgs(obj, []string{"-totp", "test/test3"}) + m.command.args = []string{"-totp", "test/test3"} + r, err = app.ReadArgs(m) if err != nil { t.Errorf("invalid error: %v", err) } if r.Entry != "test/test3" { t.Error("invalid parse") } - r, err = p.ReadArgs(obj, []string{"-totp", "test"}) + m.command.args = []string{"-totp", "test"} + r, err = app.ReadArgs(m) if err != nil { t.Errorf("invalid error: %v", err) } @@ -71,74 +127,63 @@ func TestInsertArgs(t *testing.T) { } func TestInsertDo(t *testing.T) { - setup(t) - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test1"), "pass") - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test3"), "pass") + m := newMockInsert(t) args := app.InsertArgs{} - var buf bytes.Buffer - args.Opts.IsPipe = func() bool { + m.pipe = func() bool { return false } args.Entry = "test/test2" - tx := fullSetup(t, true) - args.Opts.Confirm = func(string) bool { - return true - } - args.Opts.Input = func(bool, bool) ([]byte, error) { + m.command.confirm = false + m.input = func(bool, bool) ([]byte, error) { return nil, errors.New("failure") } - if err := args.Do(&buf, tx); err == nil || err.Error() != "invalid input: failure" { + m.command.buf = bytes.Buffer{} + if err := args.Do(m); err == nil || err.Error() != "invalid input: failure" { t.Errorf("invalid error: %v", err) } - args.Opts.Confirm = func(string) bool { - return false - } - args.Opts.IsPipe = func() bool { + m.command.confirm = false + m.pipe = func() bool { return true } - if err := args.Do(&buf, tx); err == nil || err.Error() != "invalid input: failure" { + if err := args.Do(m); err == nil || err.Error() != "invalid input: failure" { t.Errorf("invalid error: %v", err) } - args.Opts.Input = func(bool, bool) ([]byte, error) { + m.input = func(bool, bool) ([]byte, error) { return []byte("TEST"), nil } - args.Opts.Confirm = func(string) bool { - return true - } + m.command.confirm = true args.Entry = "a/b/c" - if err := args.Do(&buf, tx); err != nil { + if err := args.Do(m); err != nil { t.Errorf("invalid error: %v", err) } - if buf.String() != "" { + if m.command.buf.String() != "" { t.Error("invalid insert") } - args.Opts.IsPipe = func() bool { + m.pipe = func() bool { return false } - buf = bytes.Buffer{} - if err := args.Do(&buf, tx); err != nil { + m.command.buf = bytes.Buffer{} + if err := args.Do(m); err != nil { t.Errorf("invalid error: %v", err) } - if buf.String() == "" { + if m.command.buf.String() == "" { t.Error("invalid insert") } - buf = bytes.Buffer{} + m.command.buf = bytes.Buffer{} args.Entry = "test/test2/test1" - if err := args.Do(&buf, tx); err != nil { + if err := args.Do(m); err != nil { t.Errorf("invalid error: %v", err) } - if buf.String() == "" { + if m.command.buf.String() == "" { t.Error("invalid insert") } - args.Opts.Confirm = func(string) bool { - return false - } - buf = bytes.Buffer{} + m.command.confirm = false + m.command.buf = bytes.Buffer{} args.Entry = "test/test2/test1" - if err := args.Do(&buf, tx); err != nil { + if err := args.Do(m); err != nil { t.Errorf("invalid error: %v", err) } - if buf.String() != "" { + if m.command.buf.String() != "" { t.Error("invalid insert") } } diff --git a/internal/app/listfind.go b/internal/app/listfind.go @@ -3,13 +3,13 @@ package app import ( "errors" "fmt" - "io" "github.com/enckse/lockbox/internal/backend" ) // ListFind will list/find entries -func ListFind(t *backend.Transaction, w io.Writer, isFind bool, args []string) error { +func ListFind(cmd CommandOptions, isFind bool) error { + args := cmd.Args() opts := backend.QueryOptions{} opts.Mode = backend.ListMode if isFind { @@ -23,10 +23,11 @@ func ListFind(t *backend.Transaction, w io.Writer, isFind bool, args []string) e return errors.New("list does not support any arguments") } } - e, err := t.QueryCallback(opts) + e, err := cmd.Transaction().QueryCallback(opts) if err != nil { return err } + w := cmd.Writer() for _, f := range e { fmt.Fprintf(w, "%s\n", f.Path) } diff --git a/internal/app/listfind_test.go b/internal/app/listfind_test.go @@ -1,7 +1,6 @@ package app_test import ( - "bytes" "os" "strings" "testing" @@ -34,35 +33,29 @@ func setup(t *testing.T) *backend.Transaction { } func TestList(t *testing.T) { - setup(t) - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test1"), "pass") - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test3"), "pass") - tx := fullSetup(t, true) - var buf bytes.Buffer - if err := app.ListFind(tx, &buf, false, []string{}); err != nil { + m := newMockCommand(t) + if err := app.ListFind(m, false); err != nil { t.Errorf("invalid error: %v", err) } - if buf.String() == "" { + if m.buf.String() == "" { t.Error("nothing listed") } - if err := app.ListFind(tx, &buf, false, []string{"test"}); err.Error() != "list does not support any arguments" { + m.args = []string{"test"} + if err := app.ListFind(m, false); err.Error() != "list does not support any arguments" { t.Errorf("invalid error: %v", err) } } func TestFind(t *testing.T) { - setup(t) - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test1"), "pass") - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test3"), "pass") - tx := fullSetup(t, true) - var buf bytes.Buffer - if err := app.ListFind(tx, &buf, true, []string{}); err.Error() != "find requires search term" { + m := newMockCommand(t) + if err := app.ListFind(m, true); err.Error() != "find requires search term" { t.Errorf("invalid error: %v", err) } - if err := app.ListFind(tx, &buf, true, []string{"test1"}); err != nil { + m.args = []string{"test1"} + if err := app.ListFind(m, true); err != nil { t.Errorf("invalid error: %v", err) } - if buf.String() == "" || strings.Contains(buf.String(), "test3") { + if m.buf.String() == "" || strings.Contains(m.buf.String(), "test3") { t.Error("wrong find") } } diff --git a/internal/app/move.go b/internal/app/move.go @@ -7,10 +7,12 @@ import ( ) // Move is the CLI command to move entries -func Move(t *backend.Transaction, args []string, confirm Confirm) error { +func Move(cmd CommandOptions) error { + args := cmd.Args() if len(args) != 2 { return errors.New("src/dst required for move") } + t := cmd.Transaction() src := args[0] dst := args[1] srcExists, err := t.Get(src, backend.SecretValue) @@ -25,7 +27,7 @@ func Move(t *backend.Transaction, args []string, confirm Confirm) error { return errors.New("unable to get destination object") } if dstExists != nil { - if !confirm("overwrite destination") { + if !cmd.Confirm("overwrite destination") { return nil } } diff --git a/internal/app/move_test.go b/internal/app/move_test.go @@ -1,6 +1,8 @@ package app_test import ( + "bytes" + "io" "testing" "github.com/enckse/lockbox/internal/app" @@ -8,32 +10,54 @@ import ( ) type ( - mockConfirm struct { - called bool + mockCommand struct { + confirmed bool + confirm bool + args []string + t *testing.T + buf bytes.Buffer } ) -func (m *mockConfirm) prompt(string) bool { - m.called = true - return true -} - -func TestMove(t *testing.T) { +func newMockCommand(t *testing.T) *mockCommand { setup(t) fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test1"), "pass") fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test3"), "pass") - m := mockConfirm{} - if err := app.Move(fullSetup(t, true), []string{}, m.prompt); err.Error() != "src/dst required for move" { + return &mockCommand{t: t, confirmed: false, confirm: true} +} + +func (m *mockCommand) Confirm(string) bool { + m.confirmed = true + return m.confirm +} + +func (m *mockCommand) Transaction() *backend.Transaction { + return fullSetup(m.t, true) +} + +func (m *mockCommand) Args() []string { + return m.args +} + +func (m *mockCommand) Writer() io.Writer { + return &m.buf +} + +func TestMove(t *testing.T) { + m := newMockCommand(t) + if err := app.Move(m); err.Error() != "src/dst required for move" { t.Errorf("invalid error: %v", err) } - if err := app.Move(fullSetup(t, true), []string{"a", "b"}, m.prompt); err.Error() != "unable to get source entry" { + m.args = []string{"a", "b"} + if err := app.Move(m); err.Error() != "unable to get source entry" { t.Errorf("invalid error: %v", err) } - m.called = false - if err := app.Move(fullSetup(t, true), []string{"test/test2/test1", "test/test2/test3"}, m.prompt); err != nil { + m.confirmed = false + m.args = []string{"test/test2/test1", "test/test2/test3"} + if err := app.Move(m); err != nil { t.Errorf("invalid error: %v", err) } - if !m.called { + if !m.confirmed { t.Error("no move") } } diff --git a/internal/app/remove.go b/internal/app/remove.go @@ -4,23 +4,22 @@ package app import ( "errors" "fmt" - "io" - - "github.com/enckse/lockbox/internal/backend" ) // Remove will remove an entry -func Remove(w io.Writer, t *backend.Transaction, args []string, confirm Confirm) error { +func Remove(cmd CommandOptions) error { + args := cmd.Args() if len(args) != 1 { return errors.New("remove requires an entry") } + t := cmd.Transaction() deleting := args[0] postfixRemove := "y" existings, err := t.MatchPath(deleting) if err != nil { return err } - + w := cmd.Writer() if len(existings) > 1 { postfixRemove = "ies" fmt.Fprintln(w, "selected entities:") @@ -29,7 +28,7 @@ func Remove(w io.Writer, t *backend.Transaction, args []string, confirm Confirm) } fmt.Fprintln(w, "") } - if confirm(fmt.Sprintf("delete entr%s", postfixRemove)) { + if cmd.Confirm(fmt.Sprintf("delete entr%s", postfixRemove)) { if err := t.RemoveAll(existings); err != nil { return fmt.Errorf("unable to remove: %w", err) } diff --git a/internal/app/remove_test.go b/internal/app/remove_test.go @@ -5,26 +5,32 @@ import ( "testing" "github.com/enckse/lockbox/internal/app" - "github.com/enckse/lockbox/internal/backend" ) func TestRemove(t *testing.T) { - setup(t) - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test1"), "pass") - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test3"), "pass") - m := mockConfirm{} - var buf bytes.Buffer - if err := app.Remove(&buf, fullSetup(t, true), []string{}, m.prompt); err.Error() != "remove requires an entry" { + m := newMockCommand(t) + m.buf = bytes.Buffer{} + if err := app.Remove(m); err.Error() != "remove requires an entry" { t.Errorf("invalid error: %v", err) } - if err := app.Remove(&buf, fullSetup(t, true), []string{"a", "b"}, m.prompt); err.Error() != "remove requires an entry" { + m.args = []string{"a", "b"} + if err := app.Remove(m); err.Error() != "remove requires an entry" { t.Errorf("invalid error: %v", err) } - m.called = false - if err := app.Remove(&buf, fullSetup(t, true), []string{"tzzzest/test2/test1"}, m.prompt); err.Error() != "unable to remove: no entities given" { + m.confirmed = false + m.args = []string{"tzzzest/test2/test1"} + if err := app.Remove(m); err.Error() != "unable to remove: no entities given" { t.Errorf("invalid error: %v", err) } - if !m.called { + if !m.confirmed { + t.Error("no remove") + } + m.confirmed = false + m.args = []string{"test/test2/test1"} + if err := app.Remove(m); err != nil { + t.Errorf("invalid error: %v", err) + } + if !m.confirmed { t.Error("no remove") } } diff --git a/internal/app/showclip.go b/internal/app/showclip.go @@ -4,14 +4,14 @@ package app import ( "errors" "fmt" - "io" "github.com/enckse/lockbox/internal/backend" "github.com/enckse/lockbox/internal/platform" ) // ShowClip will handle showing/clipping an entry -func ShowClip(w io.Writer, t *backend.Transaction, isShow bool, args []string) error { +func ShowClip(cmd CommandOptions, isShow bool) error { + args := cmd.Args() if len(args) != 1 { return errors.New("entry required") } @@ -24,7 +24,7 @@ func ShowClip(w io.Writer, t *backend.Transaction, isShow bool, args []string) e return fmt.Errorf("unable to get clipboard: %w", err) } } - existing, err := t.Get(entry, backend.SecretValue) + existing, err := cmd.Transaction().Get(entry, backend.SecretValue) if err != nil { return err } @@ -32,7 +32,7 @@ func ShowClip(w io.Writer, t *backend.Transaction, isShow bool, args []string) e return nil } if isShow { - fmt.Fprintln(w, existing.Value) + fmt.Fprintln(cmd.Writer(), existing.Value) return nil } if err := clipboard.CopyTo(existing.Value); err != nil { diff --git a/internal/app/showclip_test.go b/internal/app/showclip_test.go @@ -6,33 +6,31 @@ import ( "testing" "github.com/enckse/lockbox/internal/app" - "github.com/enckse/lockbox/internal/backend" ) func TestShowClip(t *testing.T) { - setup(t) - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test1"), "pass") - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test3"), "pass") - tx := fullSetup(t, true) - var b bytes.Buffer - if err := app.ShowClip(&b, tx, true, []string{}); err.Error() != "entry required" { + m := newMockCommand(t) + if err := app.ShowClip(m, true); err.Error() != "entry required" { t.Errorf("invalid error: %v", err) } - if err := app.ShowClip(&b, tx, true, []string{"test/test2/test1"}); err != nil { + m.args = []string{"test/test2/test1"} + if err := app.ShowClip(m, true); err != nil { t.Errorf("invalid error: %v", err) } - if b.String() == "" { + if m.buf.String() == "" { t.Error("no show") } - b = bytes.Buffer{} - if err := app.ShowClip(&b, tx, true, []string{"tsest/test2/test1"}); err != nil { + m.buf = bytes.Buffer{} + m.args = []string{"test211/test2/test"} + if err := app.ShowClip(m, true); err != nil { t.Errorf("invalid error: %v", err) } - if b.String() != "" { + if m.buf.String() != "" { t.Error("no show") } os.Clearenv() - if err := app.ShowClip(&b, tx, false, []string{"tsest/test2/test1"}); err == nil { + m.args = []string{"tsest/test2/test1"} + if err := app.ShowClip(m, false); err == nil { t.Errorf("invalid error: %v", err) } } diff --git a/internal/app/stats.go b/internal/app/stats.go @@ -4,23 +4,23 @@ package app import ( "errors" "fmt" - "io" "github.com/enckse/lockbox/internal/backend" ) // Stats will retrieve entry stats -func Stats(w io.Writer, t *backend.Transaction, args []string) error { +func Stats(cmd CommandOptions) error { + args := cmd.Args() if len(args) != 1 { return errors.New("entry required") } entry := args[0] - v, err := t.Get(entry, backend.StatsValue) + v, err := cmd.Transaction().Get(entry, backend.StatsValue) if err != nil { return fmt.Errorf("unable to get stats: %w", err) } if v != nil { - fmt.Fprintln(w, v.Value) + fmt.Fprintln(cmd.Writer(), v.Value) } return nil } diff --git a/internal/app/stats_test.go b/internal/app/stats_test.go @@ -5,29 +5,26 @@ import ( "testing" "github.com/enckse/lockbox/internal/app" - "github.com/enckse/lockbox/internal/backend" ) func TestStats(t *testing.T) { - setup(t) - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test1"), "pass") - fullSetup(t, true).Insert(backend.NewPath("test", "test2", "test3"), "pass") - tx := fullSetup(t, true) - var b bytes.Buffer - if err := app.Stats(&b, tx, []string{}); err.Error() != "entry required" { + m := newMockCommand(t) + if err := app.Stats(m); err.Error() != "entry required" { t.Errorf("invalid error: %v", err) } - if err := app.Stats(&b, tx, []string{"test/test2/test1"}); err != nil { + m.args = []string{"test/test2/test1"} + if err := app.Stats(m); err != nil { t.Errorf("invalid error: %v", err) } - if b.String() == "" { + if m.buf.String() == "" { t.Error("no stats") } - b = bytes.Buffer{} - if err := app.Stats(&b, tx, []string{"tsest/test2/test1"}); err != nil { + m.buf = bytes.Buffer{} + m.args = []string{"tsest/test2/test1"} + if err := app.Stats(m); err != nil { t.Errorf("invalid error: %v", err) } - if b.String() != "" { + if m.buf.String() != "" { t.Error("no stats") } } diff --git a/scripts/testing/expected.log b/scripts/testing/expected.log @@ -1,8 +1,8 @@ -unable to check for existing entry (path can NOT end with separator) +path can NOT end with separator exit status 1 -unable to check for existing entry (path can NOT be rooted) +path can NOT be rooted exit status 1 -unable to check for existing entry (unwilling to operate on path with empty segment) +unwilling to operate on path with empty segment exit status 1 key/a/one keys/k/one