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:
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)
+ }
+}