lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 886cdc69b720a4273dcb9f27d67a8a193d6a726f
parent 2a77cd856ae5f288560904ab60f40c514daba028
Author: Sean Enck <sean@ttypty.com>
Date:   Thu, 30 Mar 2023 20:14:27 -0400

tests for rekey via unit testing

Diffstat:
Mcmd/main.go | 6+++++-
Minternal/app/rekey.go | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Ainternal/app/rekey_test.go | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 199 insertions(+), 25 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go @@ -66,7 +66,11 @@ func run() error { switch command { case cli.ReKeyCommand: if p.Confirm("proceed with rekey") { - return app.ReKey(p) + keyer, err := app.NewDefaultKeyer() + if err != nil { + return err + } + return app.ReKey(p.Writer(), keyer) } case cli.ListCommand: return app.List(p) diff --git a/internal/app/rekey.go b/internal/app/rekey.go @@ -3,6 +3,7 @@ package app import ( "errors" "fmt" + "io" "os" "os/exec" "strings" @@ -12,34 +13,90 @@ import ( "github.com/enckse/lockbox/internal/inputs" ) -func getCommandLines(exe string, args ...string) ([]string, error) { - out, err := exec.Command(exe, args...).Output() +type ( + // Keyer defines how rekeying happens + Keyer interface { + List() ([]string, error) + Stats(string) ([]string, error) + Show(string) ([]byte, error) + Insert(ReKeyEntry) error + } + // ReKeyEntry is an entry that is being rekeyed + ReKeyEntry struct { + Path string + Env []string + Data []byte + } + // DefaultKeyer is the default keyer for the application + DefaultKeyer struct { + exe string + } +) + +// NewDefaultKeyer initializes the default keyer +func NewDefaultKeyer() (DefaultKeyer, error) { + exe, err := os.Executable() + if err != nil { + return DefaultKeyer{}, err + } + return DefaultKeyer{exe: exe}, nil +} + +// List will get the list of keys in the store +func (r DefaultKeyer) List() ([]string, error) { + return r.getCommandLines(cli.ListCommand) +} + +// Stats will get stats for an entry +func (r DefaultKeyer) Stats(entry string) ([]string, error) { + return r.getCommandLines(cli.StatsCommand, entry) +} + +func (r DefaultKeyer) getCommandLines(args ...string) ([]string, error) { + out, err := exec.Command(r.exe, args...).Output() if err != nil { return nil, err } return strings.Split(strings.TrimSpace(string(out)), "\n"), nil } -// ReKey handles entry rekeying -func ReKey(cmd *DefaultCommand) error { - exe, err := os.Executable() - if err != nil { +// Show will get entry payload +func (r DefaultKeyer) Show(entry string) ([]byte, error) { + return exec.Command(r.exe, cli.ShowCommand, entry).Output() +} + +// Insert will insert the rekeying entry +func (r DefaultKeyer) Insert(entry ReKeyEntry) error { + cmd := exec.Command(r.exe, cli.InsertCommand, entry.Path) + cmd.Env = append(os.Environ(), entry.Env...) + in, err := cmd.StdinPipe() + if nil != err { return err } + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + go func() { + defer in.Close() + in.Write(entry.Data) + }() + return cmd.Run() +} + +// ReKey handles entry rekeying +func ReKey(writer io.Writer, r Keyer) error { env, err := inputs.GetReKey() if err != nil { return err } - entries, err := getCommandLines(exe, cli.ListCommand) + entries, err := r.List() if err != nil { return err } - writer := cmd.Writer() for _, entry := range entries { if _, err := fmt.Fprintf(writer, "rekeying: %s\n", entry); err != nil { return err } - stats, err := getCommandLines(exe, cli.StatsCommand, entry) + stats, err := r.Stats(entry) if err != nil { return fmt.Errorf("failed to get modtime, command failed: %w", err) } @@ -56,24 +113,14 @@ func ReKey(cmd *DefaultCommand) error { if modTime == "" { return errors.New("did not read modtime") } - data, err := exec.Command(exe, cli.ShowCommand, entry).Output() + data, err := r.Show(entry) if err != nil { return err } - cmd := exec.Command(exe, cli.InsertCommand, entry) - cmd.Env = append(os.Environ(), env...) - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", inputs.ModTimeEnv, modTime)) - in, err := cmd.StdinPipe() - if nil != err { - return err - } - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - go func() { - defer in.Close() - in.Write(data) - }() - if err := cmd.Run(); err != nil { + var insertEnv []string + insertEnv = append(insertEnv, env...) + insertEnv = append(insertEnv, fmt.Sprintf("%s=%s", inputs.ModTimeEnv, modTime)) + if err := r.Insert(ReKeyEntry{Path: entry, Env: insertEnv, Data: data}); err != nil { return err } } diff --git a/internal/app/rekey_test.go b/internal/app/rekey_test.go @@ -0,0 +1,123 @@ +package app_test + +import ( + "bytes" + "errors" + "fmt" + "os" + "testing" + + "github.com/enckse/lockbox/internal/app" +) + +type ( + mockKeyer struct { + entries []string + data map[string][]byte + stats map[string][]string + err error + rekeys []app.ReKeyEntry + } +) + +func (m *mockKeyer) List() ([]string, error) { + if m.err != nil { + return nil, m.err + } + return m.entries, nil +} + +func (m *mockKeyer) Show(entry string) ([]byte, error) { + val, ok := m.data[entry] + if !ok { + return nil, errors.New("no data") + } + return val, nil +} + +func (m *mockKeyer) Stats(entry string) ([]string, error) { + val, ok := m.stats[entry] + if !ok { + return nil, errors.New("no stats") + } + return val, nil +} + +func (m *mockKeyer) Insert(entry app.ReKeyEntry) error { + m.rekeys = append(m.rekeys, entry) + if entry.Path == "error" { + return errors.New("bad insert") + } + return nil +} + +func setupReKey() { + os.Setenv("LOCKBOX_KEY_NEW", "abc") + os.Setenv("LOCKBOX_STORE_NEW", "store") +} + +func TestErrors(t *testing.T) { + setupReKey() + var buf bytes.Buffer + m := &mockKeyer{} + m.err = errors.New("invalid ls") + if err := app.ReKey(&buf, m); err == nil || err.Error() != "invalid ls" { + t.Errorf("invalid error: %v", err) + } + m.err = nil + m.entries = []string{"test1", "error"} + if err := app.ReKey(&buf, m); err == nil || err.Error() != "failed to get modtime, command failed: no stats" { + t.Errorf("invalid error: %v", err) + } + m.stats = make(map[string][]string) + m.stats["test1"] = []string{"modtime"} + m.stats["error"] = []string{"modtime: 3"} + if err := app.ReKey(&buf, m); err == nil || err.Error() != "did not read modtime" { + t.Errorf("invalid error: %v", err) + } + m.stats["test1"] = []string{"modtime: 1", "modtime: 2"} + if err := app.ReKey(&buf, m); err == nil || err.Error() != "unable to read modtime, too many values" { + t.Errorf("invalid error: %v", err) + } + m.stats["test1"] = []string{"modtime: 1"} + if err := app.ReKey(&buf, m); err == nil || err.Error() != "no data" { + t.Errorf("invalid error: %v", err) + } + m.data = make(map[string][]byte) + m.data["test1"] = []byte{1} + m.data["error"] = []byte{2} + if err := app.ReKey(&buf, m); err == nil || err.Error() != "bad insert" { + t.Errorf("invalid error: %v", err) + } +} + +func TestReKey(t *testing.T) { + setupReKey() + var buf bytes.Buffer + if err := app.ReKey(&buf, &mockKeyer{}); err != nil { + t.Errorf("invalid error: %v", err) + } + if buf.String() != "" { + t.Error("no data") + } + m := &mockKeyer{} + m.entries = []string{"test1", "test2"} + m.data = make(map[string][]byte) + m.data["test1"] = []byte{1} + m.data["test2"] = []byte{2} + m.stats = make(map[string][]string) + m.stats["test1"] = []string{"modtime: 1", "modtime2"} + m.stats["test2"] = []string{"moime: 1", "modtime: 2"} + if err := app.ReKey(&buf, m); err != nil { + t.Errorf("invalid error: %v", err) + } + if buf.String() == "" { + t.Error("invalid data") + } + if len(m.rekeys) != 2 { + t.Error("invalid rekeys") + } + if fmt.Sprintf("%v", m.rekeys) != `[{test1 [LOCKBOX_KEYMODE= LOCKBOX_KEY=abc LOCKBOX_KEYFILE= LOCKBOX_STORE=store LOCKBOX_SET_MODTIME=1] [1]} {test2 [LOCKBOX_KEYMODE= LOCKBOX_KEY=abc LOCKBOX_KEYFILE= LOCKBOX_STORE=store LOCKBOX_SET_MODTIME=2] [2]}]` { + t.Errorf("invalid results: %v", m.rekeys) + } +}