lockbox

password manager
Log | Files | Refs | README | LICENSE

commit 6f87a87a3c1f401ef11cde0c58a484918575c73a
parent 95d8c8a95f6975d7d1567695585d8795367711a0
Author: Sean Enck <sean@ttypty.com>
Date:   Sun, 12 Mar 2023 10:16:27 -0400

moving back to bash for these tests

Diffstat:
MMakefile | 5++++-
Dinternal/test/expected.log | 107-------------------------------------------------------------------------------
Dinternal/test/integration.go | 229-------------------------------------------------------------------------------
Dinternal/test/integration_test.go | 46----------------------------------------------
Atests/expected.log | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rinternal/test/hooks/all.sh -> tests/hooks/all.sh | 0
Rinternal/test/hooks/test.sh -> tests/hooks/test.sh | 0
Atests/run.sh | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 238 insertions(+), 383 deletions(-)

diff --git a/Makefile b/Makefile @@ -13,9 +13,12 @@ $(TARGET): cmd/main.go internal/**/*.go go.* internal/cli/completions* @git describe --tags --abbrev=0 | sha256sum | cut -c 1-7 | sed 's/^/version:/g' @git tag --points-at HEAD | grep -q '' || echo "version:-1" -check: $(TARGET) +unittests: go test -v ./... +check: unittests $(TARGET) + cd tests && ./run.sh + clean: rm -rf $(BUILD) diff --git a/internal/test/expected.log b/internal/test/expected.log @@ -1,107 +0,0 @@ -path can NOT end with separator -exit status 1 -path can NOT be rooted -exit status 1 -unwilling to operate on path with empty segment -exit status 1 -key/a/one -keys/k/one -keys/k/one2 -keys2/k/three -delete entry? (y/N) -key/a/one -keys/k/one2 -keys2/k/three -key/a/one -keys/k/one2 -keys2/k/three -test2 -test3 -test4 -modtime: XXXX-XX-XX -test/k -XXXXXX -key/a/one: - modtime: XXXX-XX-XX - hash: ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff - -keys/k/one2: - modtime: XXXX-XX-XX - hash: 6d201beeefb589b08ef0672dac82353d0cbd9ad99e1642c83a1601f3d647bcca003257b5e8f31bdc1d73fbec84fb085c79d6e2677b7ff927e823a54e789140d9 - -keys2/k/three: - modtime: XXXX-XX-XX - hash: 132ab0244293c495a027cec12d0050598616daca888449920fc652719be0987830827d069ef78cc613e348de37c9b592d3406e2fb8d99a6961bf0c58da8a334f - -test/k/totp: - modtime: XXXX-XX-XX - hash: 7ef183065ba70aaa417b87ea0a96b7e550a938a52440c640a07537f7794d8a89e50078eca6a7cbcfacabd97a2db06d11e82ddf7556ca909c4df9fc0d006013b1 - -delete entry? (y/N) -delete entry? (y/N) -delete entry? (y/N) unable to remove: no entities given -exit status 1 - - -keys/k/one2 -keyx/d/e -delete entry? (y/N) -keys/k/one2 -pre insert keys/k2/t2/one -CALLED -post insert keys/k2/t2/one -CALLED - -keys/k/one2 -keys/k2/one -keys/k2/one2 -keys/k2/t1/one -keys/k2/t1/one2 -keys/k2/t2/one -keys/k2/t2/one2 -selected entities: - keys/k2/t1/one - keys/k2/t1/one2 - -delete entries? (y/N) pre rm keys/k2/t1/one -CALLED -pre rm keys/k2/t1/one2 -CALLED -post rm keys/k2/t1/one -CALLED -post rm keys/k2/t1/one2 -CALLED - -keys/k/one2 -keys/k2/one -keys/k2/one2 -keys/k2/t2/one -keys/k2/t2/one2 -selected entities: - keys/k2/one - keys/k2/one2 - keys/k2/t2/one - keys/k2/t2/one2 - -delete entries? (y/N) pre rm keys/k2/one -CALLED -pre rm keys/k2/one2 -CALLED -pre rm keys/k2/t2/one -CALLED -pre rm keys/k2/t2/one2 -CALLED -post rm keys/k2/one -CALLED -post rm keys/k2/one2 -CALLED -post rm keys/k2/t2/one -CALLED -post rm keys/k2/t2/one2 -CALLED - -keys/k/one2 - -proceed with rekey? (y/N) -keys/k/one2 -clipboard will clear in 5 seconds diff --git a/internal/test/integration.go b/internal/test/integration.go @@ -1,229 +0,0 @@ -// Package test has some integration tests for the binary lb variant -package test - -import ( - "bytes" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - "time" - - "github.com/enckse/pgl/os/exit" - "github.com/enckse/pgl/os/paths" -) - -var yes = []string{"y"} - -const ( - testKey = "plaintextkey" - clipRetry = 3 - clipWait = 1000 -) - -type ( - testRunner struct { - file *os.File - exe string - } -) - -func (r testRunner) runCommand(args []string, data []string) { - p := exec.Command(r.exe, args...) - var buf bytes.Buffer - for _, d := range data { - if _, err := buf.WriteString(fmt.Sprintf("%s\n", d)); err != nil { - exit.Dief("failed to write stdin: %v", err) - } - } - p.Stdout = r.file - p.Stderr = r.file - p.Stdin = &buf - if err := p.Run(); err != nil { - fmt.Fprintf(r.file, "%v\n", err) - } -} - -func (r testRunner) ls() { - r.runCommand([]string{"ls"}, nil) -} - -func (r testRunner) rm(k string) { - r.runCommand([]string{"rm", k}, yes) -} - -func (r testRunner) show(k string) { - r.runCommand([]string{"show", k}, nil) -} - -func (r testRunner) insert(k string, d []string) { - r.runCommand([]string{"insert", k}, d) -} - -func (r testRunner) totpList() { - r.runCommand([]string{"totp", "-list"}, nil) -} - -func (r testRunner) ln() { - r.file.Write([]byte("\n")) -} - -func replace(input string, re *regexp.Regexp, to string) string { - matches := re.FindAllStringSubmatch(input, -1) - res := input - for _, match := range matches { - for _, m := range match { - res = strings.ReplaceAll(res, m, to) - } - } - return res -} - -// Cleanup will cleanup the data log outputs -func Cleanup(dataFile string) error { - data, err := os.ReadFile(dataFile) - if err != nil { - return err - } - totp, err := regexp.Compile("^[0-9][0-9][0-9][0-9][0-9][0-9]$") - if err != nil { - return err - } - date := fmt.Sprintf("modtime: %s", time.Now().Format("2006-01-02")) - var results []string - for _, l := range strings.Split(string(data), "\n") { - payload := l - payload = replace(payload, totp, "XXXXXX") - if strings.Contains(payload, date) { - prefix := "" - if strings.HasPrefix(payload, " ") { - prefix = " " - } - payload = fmt.Sprintf("%s%s", prefix, "modtime: XXXX-XX-XX") - } - results = append(results, payload) - } - return os.WriteFile(dataFile, []byte(strings.Join(results, "\n")), 0o644) -} - -// Execute will run a test -func Execute(keyFile bool, exe, dataPath, logFile string) error { - f, err := os.Create(logFile) - if err != nil { - return err - } - defer f.Close() - cwd, err := os.Getwd() - if err != nil { - return err - } - useKeyFile := "" - if keyFile { - useKeyFile = filepath.Join(dataPath, "test.key") - if err := os.WriteFile(useKeyFile, []byte("thisisatest"), 0o644); err != nil { - return err - } - } - store := filepath.Join(dataPath, fmt.Sprintf("%s.kdbx", time.Now().Format("20060102150405"))) - os.Setenv("LOCKBOX_HOOKDIR", "") - os.Setenv("LOCKBOX_STORE", store) - os.Setenv("LOCKBOX_KEY", testKey) - os.Setenv("LOCKBOX_TOTP", "totp") - os.Setenv("LOCKBOX_INTERACTIVE", "no") - os.Setenv("LOCKBOX_READONLY", "no") - os.Setenv("LOCKBOX_KEYMODE", "plaintext") - os.Setenv("LOCKBOX_KEYFILE", useKeyFile) - runner := testRunner{file: f, exe: exe} - runner.insert("keys/k/one2", []string{"test2"}) - for _, k := range []string{"keys/k/one", "key/a/one", "keys/k/one", "keys/k/one/", "/keys/k/one", "keys/aa/b//s///e"} { - runner.insert(k, []string{"test"}) - } - runner.insert("keys2/k/three", []string{"test3", "test4"}) - runner.ls() - runner.rm("keys/k/one") - runner.ln() - runner.ls() - runner.runCommand([]string{"find", "e"}, nil) - runner.show("keys/k/one2") - runner.show("keys2/k/three") - runner.runCommand([]string{"stats", "keys2/k/three"}, nil) - for _, k := range []string{"test/k", "test/k/totp"} { - runner.runCommand([]string{"insert", "-totp", k}, []string{"5ae472abqdekjqykoyxk7hvc2leklq5n"}) - } - runner.totpList() - runner.runCommand([]string{"totp", "test/k"}, nil) - runner.runCommand([]string{"hash", store}, nil) - runner.rm("keys2/k/three") - runner.ln() - runner.rm("test/k/totp") - runner.ln() - runner.rm("test/k/one") - runner.ln() - runner.ln() - runner.runCommand([]string{"mv", "key/a/one", "keyx/d/e"}, nil) - runner.ls() - runner.rm("keyx/d/e") - runner.ln() - runner.ls() - runner.insert("keys/k2/one2", []string{"test2"}) - runner.insert("keys/k2/one", []string{"test"}) - runner.insert("keys/k2/t1/one2", []string{"test2"}) - runner.insert("keys/k2/t1/one", []string{"test"}) - runner.insert("keys/k2/t2/one2", []string{"test2"}) - os.Setenv("LOCKBOX_HOOKDIR", filepath.Join(cwd, "hooks")) - runner.insert("keys/k2/t2/one", []string{"test"}) - runner.ln() - runner.ls() - runner.rm("keys/k2/t1/*") - runner.ln() - runner.ls() - runner.rm("keys/k2/*") - runner.ln() - runner.ls() - runner.ln() - reKeyStore := fmt.Sprintf("%s.rekey.kdbx", store) - reKey := "rekey" - os.Setenv("LOCKBOX_STORE_NEW", reKeyStore) - os.Setenv("LOCKBOX_KEY_NEW", reKey) - os.Setenv("LOCKBOX_KEYMODE_NEW", "plaintext") - os.Setenv("LOCKBOX_KEYFILE_NEW", "") - runner.runCommand([]string{"rekey"}, yes) - os.Setenv("LOCKBOX_STORE", reKeyStore) - os.Setenv("LOCKBOX_KEYFILE", "") - os.Setenv("LOCKBOX_KEY", reKey) - runner.ln() - runner.ls() - return runner.clipboard(dataPath) -} - -func (r testRunner) clipboard(dataPath string) error { - clipCopyFile := filepath.Join(dataPath, "clipboard") - clipPasteFile := clipCopyFile + ".paste" - clipFiles := []string{clipCopyFile, clipPasteFile} - os.Setenv("LOCKBOX_CLIP_COPY", fmt.Sprintf("touch %s", clipCopyFile)) - os.Setenv("LOCKBOX_CLIP_PASTE", fmt.Sprintf("touch %s", clipPasteFile)) - os.Setenv("LOCKBOX_CLIP_MAX", "5") - r.runCommand([]string{"clip", "keys/k/one2"}, nil) - clipDur := time.Duration(clipWait) * time.Millisecond - tries := clipRetry - for { - if tries == 0 { - return errors.New("missing clipboard files") - } - foundClipCount := 0 - for _, f := range clipFiles { - if paths.Exist(f) { - foundClipCount++ - } - } - if foundClipCount == len(clipFiles) { - break - } - time.Sleep(clipDur) - tries-- - } - return nil -} diff --git a/internal/test/integration_test.go b/internal/test/integration_test.go @@ -1,46 +0,0 @@ -package test_test - -import ( - "os" - "os/exec" - "path/filepath" - "testing" - - "github.com/enckse/lockbox/internal/test" - "github.com/enckse/pgl/os/paths" -) - -const ( - testDir = "bin" -) - -func runTest(t *testing.T, keyFile bool) { - os.RemoveAll(testDir) - os.Mkdir(testDir, 0o755) - binary := filepath.Join("..", "..", testDir, "lb") - if !paths.Exist(binary) { - t.Error("no binary to test") - return - } - logFile := filepath.Join(testDir, "actual.log") - if err := test.Execute(keyFile, binary, testDir, logFile); err != nil { - t.Errorf("unexpected error: %v", err) - } - if err := test.Cleanup(logFile); err != nil { - t.Errorf("cleanup failed: %v", err) - } - diff := exec.Command("diff", "-u", logFile, "expected.log") - diff.Stdout = os.Stdout - diff.Stderr = os.Stderr - if err := diff.Run(); err != nil { - t.Errorf("diff failed: %v", err) - } -} - -func TestKeyFile(t *testing.T) { - runTest(t, true) -} - -func TestPasswordOnly(t *testing.T) { - runTest(t, false) -} diff --git a/tests/expected.log b/tests/expected.log @@ -0,0 +1,103 @@ +path can NOT end with separator +path can NOT be rooted +unwilling to operate on path with empty segment +key/a/one +keys/k/one +keys/k/one2 +keys2/k/three +delete entry? (y/N) +key/a/one +keys/k/one2 +keys2/k/three +key/a/one +keys/k/one2 +keys2/k/three +test2 +test3 +test4 +modtime: XXXX-XX-XX +test/k +XXXXXX +key/a/one: + modtime: XXXX-XX-XX + hash: ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff + +keys/k/one2: + modtime: XXXX-XX-XX + hash: 6d201beeefb589b08ef0672dac82353d0cbd9ad99e1642c83a1601f3d647bcca003257b5e8f31bdc1d73fbec84fb085c79d6e2677b7ff927e823a54e789140d9 + +keys2/k/three: + modtime: XXXX-XX-XX + hash: 132ab0244293c495a027cec12d0050598616daca888449920fc652719be0987830827d069ef78cc613e348de37c9b592d3406e2fb8d99a6961bf0c58da8a334f + +test/k/totp: + modtime: XXXX-XX-XX + hash: 7ef183065ba70aaa417b87ea0a96b7e550a938a52440c640a07537f7794d8a89e50078eca6a7cbcfacabd97a2db06d11e82ddf7556ca909c4df9fc0d006013b1 + +delete entry? (y/N) +delete entry? (y/N) +delete entry? (y/N) unable to remove: no entities given + + +keys/k/one2 +keyx/d/e +delete entry? (y/N) +keys/k/one2 +pre insert keys/k2/t2/one +CALLED +post insert keys/k2/t2/one +CALLED + +keys/k/one2 +keys/k2/one +keys/k2/one2 +keys/k2/t1/one +keys/k2/t1/one2 +keys/k2/t2/one +keys/k2/t2/one2 +selected entities: + keys/k2/t1/one + keys/k2/t1/one2 + +delete entries? (y/N) pre rm keys/k2/t1/one +CALLED +pre rm keys/k2/t1/one2 +CALLED +post rm keys/k2/t1/one +CALLED +post rm keys/k2/t1/one2 +CALLED + +keys/k/one2 +keys/k2/one +keys/k2/one2 +keys/k2/t2/one +keys/k2/t2/one2 +selected entities: + keys/k2/one + keys/k2/one2 + keys/k2/t2/one + keys/k2/t2/one2 + +delete entries? (y/N) pre rm keys/k2/one +CALLED +pre rm keys/k2/one2 +CALLED +pre rm keys/k2/t2/one +CALLED +pre rm keys/k2/t2/one2 +CALLED +post rm keys/k2/one +CALLED +post rm keys/k2/one2 +CALLED +post rm keys/k2/t2/one +CALLED +post rm keys/k2/t2/one2 +CALLED + +keys/k/one2 + +proceed with rekey? (y/N) +keys/k/one2 +clipboard will clear in 5 seconds diff --git a/internal/test/hooks/all.sh b/tests/hooks/all.sh diff --git a/internal/test/hooks/test.sh b/tests/hooks/test.sh diff --git a/tests/run.sh b/tests/run.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash +LB_BINARY=../bin/lb +DATA=bin +CLIP_WAIT=1 +CLIP_TRIES=3 +CLIP_COPY="$DATA/clip.copy" +CLIP_PASTE="$DATA/clip.paste" + +_execute() { + local rekey rekeyFile clipTries + export LOCKBOX_HOOKDIR="" + export LOCKBOX_STORE="${DATA}/passwords.kdbx" + export LOCKBOX_KEY="testingkey" + export LOCKBOX_TOTP=totp + export LOCKBOX_INTERACTIVE=no + export LOCKBOX_READONLY=no + export LOCKBOX_KEYMODE=plaintext + echo test2 |${LB_BINARY} insert keys/k/one2 + echo test |${LB_BINARY} insert keys/k/one + echo test |${LB_BINARY} insert key/a/one + echo test |${LB_BINARY} insert keys/k/one + echo test |${LB_BINARY} insert keys/k/one/ + echo test |${LB_BINARY} insert /keys/k/one + echo test |${LB_BINARY} insert keys/aa/b//s///e + printf "test3\ntest4\n" |${LB_BINARY} insert keys2/k/three + ${LB_BINARY} ls + echo y |${LB_BINARY} rm keys/k/one + echo + ${LB_BINARY} ls + ${LB_BINARY} find e + ${LB_BINARY} show keys/k/one2 + ${LB_BINARY} show keys2/k/three + ${LB_BINARY} stats keys2/k/three + echo 5ae472abqdekjqykoyxk7hvc2leklq5n |${LB_BINARY} insert -totp test/k + echo 5ae472abqdekjqykoyxk7hvc2leklq5n |${LB_BINARY} insert -totp test/k/totp + ${LB_BINARY} totp -list + ${LB_BINARY} totp test/k + ${LB_BINARY} hash "$LOCKBOX_STORE" + echo y |${LB_BINARY} rm keys2/k/three + echo + echo y |${LB_BINARY} rm test/k/totp + echo + echo y |${LB_BINARY} rm test/k/one + echo + echo + ${LB_BINARY} mv key/a/one keyx/d/e + ${LB_BINARY} ls + echo y |${LB_BINARY} rm keyx/d/e + echo + ${LB_BINARY} ls + echo test2 |${LB_BINARY} insert keys/k2/one2 + echo test |${LB_BINARY} insert keys/k2/one + echo test2 |${LB_BINARY} insert keys/k2/t1/one2 + echo test |${LB_BINARY} insert keys/k2/t1/one + echo test2 |${LB_BINARY} insert keys/k2/t2/one2 + export LOCKBOX_HOOKDIR="$PWD/hooks" + echo test |${LB_BINARY} insert keys/k2/t2/one + echo + ${LB_BINARY} ls + echo y |${LB_BINARY} rm keys/k2/t1/* + echo + ${LB_BINARY} ls + echo y |${LB_BINARY} rm keys/k2/* + echo + ${LB_BINARY} ls + echo + rekey="$LOCKBOX_STORE.rekey.kdbx" + rekeyFile="" + export LOCKBOX_STORE_NEW="$rekey" + export LOCKBOX_KEY_NEW="newkey" + export LOCKBOX_KEYMODE_NEW=plaintext + if [ -n "$LOCKBOX_KEYFILE" ]; then + rekeyFile="$DATA/newkeyfile" + echo "thisisanewkey" > "$rekeyFile" + fi + export LOCKBOX_KEYFILE_NEW="$rekeyFile" + echo y |${LB_BINARY} rekey + echo + ${LB_BINARY} ls + _clipboard +} + +_clipboard() { + export LOCKBOX_CLIP_COPY="touch $CLIP_COPY" + export LOCKBOX_CLIP_PASTE="touch $CLIP_PASTE" + export LOCKBOX_CLIP_MAX=5 + ${LB_BINARY} clip keys/k/one2 + clipTries="$CLIP_TRIES" + while [ "$clipTries" -gt 0 ] ; do + if [ -e "$CLIP_COPY" ] && [ -e "$CLIP_PASTE" ]; then + return + fi + sleep "$CLIP_WAIT" + clipTries=$((clipTries-1)) + done + echo "clipboard test failed" +} + +_cleanup() { + mkdir -p "$DATA" + find "$DATA" -type f -delete +} + +_evaluate() { + local logfile + logfile="$DATA/actual.log" + echo "$1" + echo "============" + _execute > "$logfile" 2>&1 + sed -i 's/modtime: [0-9].*$/modtime: XXXX-XX-XX/g' "$logfile" + sed -i 's/^[0-9][0-9][0-9][0-9][0-9][0-9]$/XXXXXX/g' "$logfile" + if ! diff -u "$logfile" "expected.log"; then + echo "failed" + exit 1 + fi + echo "passed" +} + +_run() { + export LOCKBOX_KEYFILE="" + _cleanup + _evaluate "password" + echo + _cleanup + KEYFILE="$DATA/test.key" + echo "thisisatest" > "$KEYFILE" + export LOCKBOX_KEYFILE="$KEYFILE" + _evaluate "keyfile" +} + +_run