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:
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