pashage

Yet Another Opinionated Re-engineering of the Unix Password Store
git clone https://git.instinctive.eu/pashage.git
Log | Files | Refs | README | LICENSE

commit 7ea4c34c68e07b6b7031ef4c6afcb085e66d93fc
parent b2b89b3e1ec123f050e1417fe46a79966b0410bd
Author: Natasha Kerensikova <natgh@instinctive.eu>
Date:   Sat, 21 Sep 2024 15:32:37 +0000

pass-like behavior is fully tested
Diffstat:
Mspec/pass_spec.sh | 718++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mspec/support/bin/mock-gpg | 43+++++++++++++++++++++++++++++++++----------
2 files changed, 729 insertions(+), 32 deletions(-)

diff --git a/spec/pass_spec.sh b/spec/pass_spec.sh @@ -25,10 +25,10 @@ # to check the store behavior. # So it only works when using shellspec with bash and calling the `pass` # script directly (e.g. on FreeBSD `/usr/local/libexec/password-store/pass` -# instead of `/usr/local/bin/pass`. +# instead of `/usr/local/bin/pass`). Parameters # script/path scriptname encryption - /usr/bin/pass pass gpg +# /usr/bin/pass pass gpg ./src/run.sh pashage age End @@ -52,6 +52,8 @@ Describe 'Pass-like command' #|Initial setup #| #| .gpg-id | 1 + + #| extra/subdir/file.age | 2 ++ + #| extra/subdir/file.gpg | 2 ++ #| fluff/.age-recipients | 2 ++ #| fluff/.gpg-id | 2 ++ #| fluff/one.age | 3 +++ @@ -62,9 +64,11 @@ Describe 'Pass-like command' #| fluff/two.gpg | 4 ++++ #| shared/.age-recipients | 2 ++ #| shared/.gpg-id | 2 ++ + #| stale.age | 3 +++ + #| stale.gpg | 3 +++ #| subdir/file.age | 2 ++ #| subdir/file.gpg | 2 ++ - #| 13 files changed, 37 insertions(+) + #| 17 files changed, 47 insertions(+) } setup_id() { @@ -74,7 +78,7 @@ Describe 'Pass-like command' } setup_secret() { - @mkdir -p "${PREFIX}/${1%/*}" + [ "$1" = "${1%/*}" ] || @mkdir -p "${PREFIX}/${1%/*}" @sed 's/^/age/' >"${PREFIX}/$1.age" @sed 's/^age/gpg/' "${PREFIX}/$1.age" >"${PREFIX}/$1.gpg" } @@ -88,6 +92,9 @@ Describe 'Pass-like command' %text | setup_secret 'subdir/file' #|Recipient:myself #|:p4ssw0rd + %text | setup_secret 'extra/subdir/file' + #|Recipient:myself + #|:Pa55worD %text | setup_id 'shared' #|myself #|friend @@ -109,6 +116,10 @@ Describe 'Pass-like command' #|:3-password #|:Username: 3Jane #|:URL: https://example.com/login + %text | setup_secret 'stale' + #|Recipient:master + #|Recipient:myself + #|:0-password @git -C "${PREFIX}" add . @git -C "${PREFIX}" commit -m 'Initial setup' >/dev/null @@ -142,15 +153,37 @@ Describe 'Pass-like command' @cat "$@" End + Mock cp + @cp "$@" + End + Mock cut . "${SHELLSPEC_SUPPORT_BIN}" invoke cut "$@" End + Mock dd + . "${SHELLSPEC_SUPPORT_BIN}" + invoke dd "$@" + End + + Mock diff + @diff "$@" + End + Mock dirname @dirname "$@" End + Mock ed + . "${SHELLSPEC_SUPPORT_BIN}" + if [ "${1-}" = '-c' ]; then + shift + invoke touch "$@" + fi + invoke ed "$@" + End + Mock feh printf '$ feh %s\n' "$*" >&2 @cat >&2 @@ -195,6 +228,11 @@ Describe 'Pass-like command' invoke od -v -t x1 | sed 's/ */ /g;s/ *$//' >&2 End + Mock mktemp + . "${SHELLSPEC_SUPPORT_BIN}" + invoke mktemp "$@" + End + Mock rm @rm "$@" End @@ -248,7 +286,137 @@ Describe 'Pass-like command' fi End - #TODO: init + Describe 'init' + It 're-encrypts the whole store using a new recipient id' + Skip if 'pass needs bash' check_skip $1 + When run script $1 init 'new-id' + The output should include 'Password store' + expected_log() { + if [ "$2" = pashage ]; then + %text + #|Set age recipients at store root + #| + #| .age-recipients | 1 + + #| extra/subdir/file.age | 2 +- + #| stale.age | 3 +-- + #| subdir/file.age | 2 +- + #| 4 files changed, 4 insertions(+), 4 deletions(-) + else + %text:expand + #|Reencrypt password store using new GPG id new-id. + #| + #| extra/subdir/file.$1 | 2 +- + #| stale.$1 | 3 +-- + #| subdir/file.$1 | 2 +- + #| 3 files changed, 3 insertions(+), 4 deletions(-) + #|Set GPG id to new-id. + #| + #| .gpg-id | 2 +- + #| 1 file changed, 1 insertion(+), 1 deletion(-) + fi + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" + End + + It 're-encrypts a subdirectory using a new recipient id' + Skip if 'pass needs bash' check_skip $1 + When run script $1 init -p subdir 'new-id' + The output should start with 'Password store' + The output should include 'subdir' + expected_log() { + if [ "$2" = pashage ]; then + %text + #|Set age recipients at subdir + #| + #| subdir/.age-recipients | 1 + + #| subdir/file.age | 2 +- + #| 2 files changed, 2 insertions(+), 1 deletion(-) + else + %text:expand + #|Reencrypt password store using new GPG id new-id (subdir). + #| + #| subdir/file.$1 | 2 +- + #| 1 file changed, 1 insertion(+), 1 deletion(-) + #|Set GPG id to new-id (subdir). + #| + #| subdir/.gpg-id | 1 + + #| 1 file changed, 1 insertion(+) + fi + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" + End + + It 're-encrypts a subdirectory after replacing recipient ids' + Skip if 'pass needs bash' check_skip $1 + When run script $1 init -p fluff 'new-id' 'new-master' + The output should start with 'Password store' + The output should include 'fluff' + expected_log() { + if [ "$2" = pashage ]; then + %text + #|Set age recipients at fluff + #| + #| fluff/.age-recipients | 4 ++-- + #| fluff/one.age | 4 ++-- + #| fluff/three.age | 4 ++-- + #| fluff/two.age | 4 ++-- + #| 4 files changed, 8 insertions(+), 8 deletions(-) + else + %text:expand + #|Reencrypt password store using new GPG id new-id, new-master (fluff). + #| + #| fluff/one.$1 | 4 ++-- + #| fluff/three.$1 | 4 ++-- + #| fluff/two.$1 | 4 ++-- + #| 3 files changed, 6 insertions(+), 6 deletions(-) + #|Set GPG id to new-id, new-master (fluff). + #| + #| fluff/.gpg-id | 4 ++-- + #| 1 file changed, 2 insertions(+), 2 deletions(-) + fi + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" + End + + It 're-encrypts a subdirectory after removing dedicated recipient ids' + Skip if 'pass needs bash' check_skip $1 + When run script $1 init -p fluff '' + The status should be successful + expected_log() { + if [ "$2" = pashage ]; then + %text + #|Deinitialize fluff + #| + #| fluff/.age-recipients | 2 -- + #| fluff/one.age | 1 - + #| fluff/three.age | 1 - + #| fluff/two.age | 1 - + #| 4 files changed, 5 deletions(-) + else + %text:expand + #|Reencrypt password store using new GPG id (fluff). + #| + #| fluff/one.$1 | 1 - + #| fluff/three.$1 | 1 - + #| fluff/two.$1 | 1 - + #| 3 files changed, 3 deletions(-) + #|Deinitialize ${PREFIX}/fluff/.gpg-id (fluff). + #| + #| fluff/.gpg-id | 2 -- + #| 1 file changed, 2 deletions(-) + fi + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" + End + End Describe 'ls' It 'lists a directory' @@ -276,17 +444,23 @@ Describe 'Pass-like command' Skip if 'pass needs bash' check_skip $1 When run script $1 The line 1 of output should equal 'Password Store' - The line 2 of output should include 'fluff' - The line 3 of output should include 'one' - The line 4 of output should include 'one' - The line 5 of output should include 'three' - The line 6 of output should include 'three' - The line 7 of output should include 'two' - The line 8 of output should include 'two' - The line 9 of output should include 'shared' - The line 10 of output should include 'subdir' - The line 11 of output should include 'file' - The line 12 of output should include 'file' + The line 2 of output should include 'extra' + The line 3 of output should include 'subdir' + The line 4 of output should include 'file' + The line 5 of output should include 'file' + The line 6 of output should include 'fluff' + The line 7 of output should include 'one' + The line 8 of output should include 'one' + The line 9 of output should include 'three' + The line 10 of output should include 'three' + The line 11 of output should include 'two' + The line 12 of output should include 'two' + The line 13 of output should include 'shared' + The line 14 of output should include 'stale' + The line 15 of output should include 'stale' + The line 16 of output should include 'subdir' + The line 17 of output should include 'file' + The line 18 of output should include 'file' End It 'does not list a file masquerading as a directory' @@ -518,8 +692,247 @@ Describe 'Pass-like command' End End - #TODO: edit - #TODO: generate + Describe 'edit' + EDITOR=ed + TERM=dumb + + It 'creates a file using EDITOR' + EDITOR='ed -c' + Skip if 'pass needs bash' check_skip $1 + Data + #|a + #|New password + #|New annotation + #|. + #|wq + End + When run script $1 edit subdir/new + The file "${PREFIX}/subdir/new.$3" should be exist + expected_file() { %text:expand + #|$1Recipient:myself + #|$1:New password + #|$1:New annotation + } + The contents of file "${PREFIX}/subdir/new.$3" should \ + equal "$(expected_file "$3")" + expected_log() { %text:expand + #|Add password for subdir/new using ed -c. + #| + #| subdir/new.$1 | 3 +++ + #| 1 file changed, 3 insertions(+) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3)" + End + + It 'updates a file using EDITOR' + Skip if 'pass needs bash' check_skip $1 + Data + #|2i + #|New line + #|. + #|wq + End + When run script $1 edit fluff/two + expected_file() { %text:expand + #|$1Recipient:master + #|$1Recipient:myself + #|$1:2-password + #|$1:New line + #|$1:URL: https://example.com/login + } + The contents of file "${PREFIX}/fluff/two.$3" should \ + equal "$(expected_file "$3")" + expected_log() { %text:expand + #|Edit password for fluff/two using ed. + #| + #| fluff/two.$1 | 1 + + #| 1 file changed, 1 insertion(+) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3)" + End + + It 'reencrypts an updated file using EDITOR' + Skip if 'pass needs bash' check_skip $1 + Data + #|a + #|New option + #|. + #|wq + End + When run script $1 edit stale + expected_file() { %text:expand + #|$1Recipient:myself + #|$1:0-password + #|$1:New option + } + The contents of file "${PREFIX}/stale.$3" should \ + equal "$(expected_file "$3")" + expected_log() { %text:expand + #|Edit password for stale using ed. + #| + #| stale.$1 | 2 +- + #| 1 file changed, 1 insertion(+), 1 deletion(-) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3)" + End + + It 'does not reencrypt an unchanged file using EDITOR' + Skip if 'pass needs bash' check_skip $1 + Data 'q' + When run script $1 edit stale + expected_file() { %text:expand + #|$1Recipient:master + #|$1Recipient:myself + #|$1:0-password + } + The contents of file "${PREFIX}/stale.$3" should \ + equal "$(expected_file "$3")" + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(setup_log)" + End + + It 'allows cancelling file creation' + Skip if 'pass needs bash' check_skip $1 + Data 'q' + When run script $1 edit subdir/new + The status should be successful + The file "${PREFIX}/subdir/new.age" should not be exist + The file "${PREFIX}/subdir/new.gpg" should not be exist + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(setup_log)" + End + End + + Describe 'generate' + It 'generates a new file' + Skip if 'pass needs bash' check_skip $1 + When run script $1 generate newdir/newfile + The output should include 'The generated password for' + The file "${PREFIX}/newdir/newfile.$3" should be exist + The lines of contents of file "${PREFIX}/newdir/newfile.$3" should \ + equal 2 + The line 1 of contents of file "${PREFIX}/newdir/newfile.$3" should \ + equal "$3Recipient:myself" + The output should \ + include "$(@sed -n "2s/$3://p" "${PREFIX}/newdir/newfile.$3")" + expected_log() { %text:expand + #|Add generated password for newdir/newfile. + #| + #| newdir/newfile.$1 | 2 ++ + #| 1 file changed, 2 insertions(+) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3)" + End + + It 'generates a new file without symbols' + Skip if 'pass needs bash' check_skip $1 + When run script $1 generate -n newfile 4 + The output should include 'The generated password for' + The file "${PREFIX}/newfile.$3" should be exist + The lines of contents of file "${PREFIX}/newfile.$3" should \ + equal 2 + The line 1 of contents of file "${PREFIX}/newfile.$3" should \ + equal "$3Recipient:myself" + The line 2 of contents of file "${PREFIX}/newfile.$3" should \ + match pattern "$3:[0-9a-zA-z][0-9a-zA-z][0-9a-zA-z][0-9a-zA-z]" + The output should \ + include "$(@sed -n "2s/$3://p" "${PREFIX}/newfile.$3")" + expected_log() { %text:expand + #|Add generated password for newfile. + #| + #| newfile.$1 | 2 ++ + #| 1 file changed, 2 insertions(+) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3)" + End + + It 'replaces an existing file when forced' + Skip if 'pass needs bash' check_skip $1 + When run script $1 generate -f fluff/three 20 + The output should include 'The generated password for' + The lines of contents of file "${PREFIX}/fluff/three.$3" should equal 3 + The output should \ + include "$(@sed -n "3s/$3://p" "${PREFIX}/fluff/three.$3")" + expected_log() { %text:expand + #|Add generated password for fluff/three. + #| + #| fluff/three.$1 | 4 +--- + #| 1 file changed, 1 insertion(+), 3 deletions(-) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3)" + End + + It 'replaces the first line of an existing file' + Skip if 'pass needs bash' check_skip $1 + When run script $1 generate -ni fluff/three 4 + The output should include 'The generated password for' + The lines of contents of file "${PREFIX}/fluff/three.$3" should equal 5 + The line 3 of contents of file "${PREFIX}/fluff/three.$3" should \ + match pattern "$3:[0-9a-zA-z][0-9a-zA-z][0-9a-zA-z][0-9a-zA-z]" + The output should \ + include "$(@sed -n "3s/$3://p" "${PREFIX}/fluff/three.$3")" + expected_log() { %text:expand + #|Replace generated password for fluff/three. + #| + #| fluff/three.$1 | 2 +- + #| 1 file changed, 1 insertion(+), 1 deletion(-) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3)" + End + + It 'pastes the generated password into the clipboard' + DISPLAY=mock + Skip if 'pass needs bash' check_skip $1 + When run script $1 generate -nc subdir/new + The output should not include 'The generated password for' + The output should not \ + include "$(@sed -n "2s/$3://p" "${PREFIX}/subdir/new.$3")" + The output should include \ + 'Copied subdir/new to clipboard. Will clear in 45 seconds.' + The error should start with '$ xclip -selection clipboard' + expected_log() { %text:expand + #|Add generated password for subdir/new. + #| + #| subdir/new.$1 | 2 ++ + #| 1 file changed, 2 insertions(+) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3)" + End + + It 'displays the generated password as a QR-code' + DISPLAY=mock + Skip if 'pass needs bash' check_skip $1 + When run script $1 generate -qn new + The output should not include 'The generated password for' + The output should not include "$(@sed -n "2s/$3://p" "${PREFIX}/new.$3")" + The error should start with "$ feh -x --title $2: new -g +200+200 -" + expected_log() { %text:expand + #|Add generated password for new. + #| + #| new.$1 | 2 ++ + #| 1 file changed, 2 insertions(+) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3)" + End + End Describe 'rm' It 'removes a file without confirmation when forced' @@ -539,7 +952,38 @@ Describe 'Pass-like command' The contents of file "${GITLOG}" should equal "$(expected_log $3)" End - #TODO: rm -rf + It 'does not remove a directory without `-r` even when forced' + Skip if 'pass needs bash' check_skip $1 + When run script $1 rm -f fluff + The error should include 'fluff/' + The error should include 's a directory' + The directory "${PREFIX}/fluff" should be exist + The file "${PREFIX}/fluff/one.$3" should be exist + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(setup_log)" + End + + It 'removes a directory when forced and recursive' + Skip if 'pass needs bash' check_skip $1 + When run script $1 rm -rf fluff/ + The directory "${PREFIX}/fluff" should not be exist + expected_log() { %text:expand + #|Remove fluff/ from store. + #| + #| fluff/.age-recipients | 2 -- + #| fluff/.gpg-id | 2 -- + #| fluff/one.age | 3 --- + #| fluff/one.gpg | 3 --- + #| fluff/three.age | 5 ----- + #| fluff/three.gpg | 5 ----- + #| fluff/two.age | 4 ---- + #| fluff/two.gpg | 4 ---- + #| 8 files changed, 28 deletions(-) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3)" + End End Describe 'mv' @@ -721,11 +1165,241 @@ Describe 'Pass-like command' The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" End - #TODO: recursive mv -f + It 'does not merge directories recursively' + Skip if 'pass needs bash' check_skip $1 + When run script $1 mv -f subdir/ extra/ + The error should include 'subdir' + The error should include 'extra/' + The directory "${PREFIX}/subdir" should be exist + The file "${PREFIX}/subdir/file.$3" should be exist + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(setup_log)" + End + End + + Describe 'cp' + It 'copies a file without reencrypting' + Skip if 'pass needs bash' check_skip $1 + When run script $1 cp subdir/file subdir/copy + The error should be blank + file_contents() { %text:expand + #|${1}Recipient:myself + #|${1}:p4ssw0rd + } + The contents of file "${PREFIX}/subdir/copy.$3" \ + should equal "$(file_contents "$3")" + expected_log() { + if [ "$2" = pashage ]; then + %putsn 'Copy subdir/file.age to subdir/copy.age' + else + %putsn 'Copy subdir/file to subdir/copy.' + fi + %text:expand + #| + #| subdir/copy.$1 | 2 ++ + #| 1 file changed, 2 insertions(+) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" + End + + It 'reencrypts a copied file' + Skip if 'pass needs bash' check_skip $1 + When run script $1 cp subdir/file shared/copy + The error should be blank + file_contents() { %text:expand + #|${1}Recipient:myself + #|${1}Recipient:friend + #|${1}:p4ssw0rd + } + The contents of file "${PREFIX}/shared/copy.$3" \ + should equal "$(file_contents "$3")" + expected_log() { + if [ "$2" = pashage ]; then + %putsn 'Copy subdir/file.age to shared/copy.age' + else + %putsn 'Copy subdir/file to shared/copy.' + fi + %text:expand + #| + #| shared/copy.$1 | 3 +++ + #| 1 file changed, 3 insertions(+) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" + End + + It 'reencrypts relevant files in a copied directory' + Skip if 'pass needs bash' check_skip $1 + When run script $1 cp subdir shared/ + The error should be blank + file_contents() { %text:expand + #|${1}Recipient:myself + #|${1}Recipient:friend + #|${1}:p4ssw0rd + } + The contents of file "${PREFIX}/shared/subdir/file.$3" \ + should equal "$(file_contents "$3")" + expected_log() { + if [ "$2" = pashage ]; then + %putsn 'Copy subdir/ to shared/subdir/' + else + %putsn 'Copy subdir to shared/.' + fi + if [ "$1" = age ]; then + %text:expand + #| + #| shared/subdir/file.age | 3 +++ + #| shared/subdir/file.gpg | 2 ++ + #| 2 files changed, 5 insertions(+) + else + %text:expand + #| + #| shared/subdir/file.age | 2 ++ + #| shared/subdir/file.gpg | 3 +++ + #| 2 files changed, 5 insertions(+) + fi + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" + End + + It 'copies a directory with recipients' + Skip if 'pass needs bash' check_skip $1 + When run script $1 cp fluff filler + The error should be blank + The directory "${PREFIX}/filler" should be exist + expected_log() { + if [ "$2" = pashage ]; then + %putsn 'Copy fluff/ to filler/' + else + %putsn 'Copy fluff to filler.' + fi + %text:expand + #| + #| filler/.age-recipients | 2 ++ + #| filler/.gpg-id | 2 ++ + #| filler/one.age | 3 +++ + #| filler/one.gpg | 3 +++ + #| filler/three.age | 5 +++++ + #| filler/three.gpg | 5 +++++ + #| filler/two.age | 4 ++++ + #| filler/two.gpg | 4 ++++ + #| 8 files changed, 28 insertions(+) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" + End + + It 'copies a directory without recipients' + Skip if 'pass needs bash' check_skip $1 + When run script $1 cp subdir newdir + The error should be blank + file_contents() { %text:expand + #|${1}Recipient:myself + #|${1}:p4ssw0rd + } + The contents of file "${PREFIX}/newdir/file.$3" \ + should equal "$(file_contents "$3")" + expected_log() { + if [ "$2" = pashage ]; then + %putsn 'Copy subdir/ to newdir/' + else + %putsn 'Copy subdir to newdir.' + fi + %text:expand + #| + #| newdir/file.age | 2 ++ + #| newdir/file.gpg | 2 ++ + #| 2 files changed, 4 insertions(+) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" + End + + It 'overwrites an existing file when forced' + Skip if 'pass needs bash' check_skip $1 + When run script $1 cp -f fluff/two fluff/one + file_contents() { %text:expand + #|${1}Recipient:master + #|${1}Recipient:myself + #|${1}:2-password + #|${1}:URL: https://example.com/login + } + The contents of file "${PREFIX}/fluff/one.$3" \ + should equal "$(file_contents "$3")" + expected_log() { + if [ "$2" = pashage ]; then + %putsn 'Copy fluff/two.age to fluff/one.age' + else + %putsn 'Copy fluff/two to fluff/one.' + fi + %text:expand + #| + #| fluff/one.$1 | 3 ++- + #| 1 file changed, 2 insertions(+), 1 deletion(-) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" + End + + It 'overwrites collisions when copying recursively and forcefully' + Skip if 'pass needs bash' check_skip $1 + When run script $1 cp -f subdir/ extra/ + expected_log() { + if [ "$2" = pashage ]; then + %putsn 'Copy subdir/ to extra/subdir/' + else + %putsn 'Copy subdir/ to extra/.' + fi + %text:expand + #| + #| extra/subdir/file.age | 2 +- + #| extra/subdir/file.gpg | 2 +- + #| 2 files changed, 2 insertions(+), 2 deletions(-) + setup_log + } + The result of function git_log should be successful + The contents of file "${GITLOG}" should equal "$(expected_log $3 $2)" + End End - #TODO: cp - #TODO: git + Describe 'git' + It 'transmits arguments to git' + Skip if 'pass needs bash' check_skip $1 + When run script $1 git log --format='%s' --stat + The output should equal "$(setup_log)" + End + + remove_git() { rm -rf "${PREFIX}/.git"; } + BeforeEach remove_git + + It 'fails without a git repository' + Skip if 'pass needs bash' check_skip $1 + When run script $1 git log + The status should equal 1 + The output should be blank + The error should match pattern \ + 'Error: the password store is not a git repository. Try "* git init".' + End + + It 're-initializes the git repository' + Skip if 'pass needs bash' check_skip $1 + When run script $1 git init -b trunk + The status should be successful + The output should start with \ + "Initialized empty Git repository in ${PREFIX}/.git" + The error should be blank + The directory "${PREFIX}/.git" should be exist + The file "${PREFIX}/.gitattributes" should be exist + End + End Describe 'help' It 'displays a help text with supported commands' diff --git a/spec/support/bin/mock-gpg b/spec/support/bin/mock-gpg @@ -17,34 +17,57 @@ case "$1" in -e) shift MOCK_AGE_OUTPUT="$(@mktemp "$(dirname "$2")/mock-gpg-encrypt.XXXXXXX")" + DEST='-' while [ $# -gt 0 ]; do case "$1" in -r) printf 'gpgRecipient:%s\n' "$2" >>"${MOCK_AGE_OUTPUT}" shift 2 ;; -o) - @sed 's/^/gpg:/' >>"${MOCK_AGE_OUTPUT}" - @mv -f "${MOCK_AGE_OUTPUT}" "$2" + DEST="$2" shift 2 break ;; *) - die "Unexpected arguments togpg -e [...] $*\n" + die "Unexpected arguments to gpg -e [...] $*\n" ;; esac done - [ $# -eq 4 ] || die "Unexpected arguments to gpg -e [...] $*\n" + if [ $# -lt 4 ] || [ $# -gt 5 ]; then + die "Unexpected arguments to gpg -e [...] $*\n" + fi check_eq "$1" '--quiet' "Unexpected gpg -e \$1: \"$1\"" check_eq "$2" '--yes' "Unexpected gpg -e \$2: \"$2\"" check_eq "$3" '--compress-algo=none' "Unexpected gpg -e \$3: \"$3\"" check_eq "$4" '--no-encrypt-to' "Unexpected gpg -e \$4: \"$4\"" + if [ $# -eq 5 ]; then + @sed 's/^/gpg:/' "$5" >>"${MOCK_AGE_OUTPUT}" + else + @sed 's/^/gpg:/' >>"${MOCK_AGE_OUTPUT}" + fi + if [ "${DEST}" = '-' ]; then + @cat "${MOCK_AGE_OUTPUT}" + @rm -f "${MOCK_AGE_OUTPUT}" + else + @mv -f "${MOCK_AGE_OUTPUT}" "${DEST}" + fi ;; -d) - check_eq "$2" '--quiet' "Unexpected gpg -d \$2: \"$2\"" - check_eq "$3" '--yes' "Unexpected gpg -d \$3: \"$3\"" - check_eq "$4" '--compress-algo=none' "Unexpected gpg -d \$4: \"$4\"" - check_eq "$5" '--no-encrypt-to' "Unexpected gpg -d \$5: \"$5\"" - @grep -v '^gpg' "$6" >&2 && die "Bad encrypted file \"$6\"" - @sed -n 's/^gpg://p' "$6" + shift + OUTPUT='-' + if [ "$1" = '-o' ]; then + OUTPUT="$2" + shift 2 + fi + check_eq "$1" '--quiet' "Unexpected gpg -d \$1: \"$1\"" + check_eq "$2" '--yes' "Unexpected gpg -d \$2: \"$2\"" + check_eq "$3" '--compress-algo=none' "Unexpected gpg -d \$3: \"$3\"" + check_eq "$4" '--no-encrypt-to' "Unexpected gpg -d \$4: \"$4\"" + @grep -v '^gpg' "$5" >&2 && die "Bad encrypted file \"$5\"" + if [ "${OUTPUT}" = '-' ]; then + @sed -n 's/^gpg://p' "$5" + else + @sed -n 's/^gpg://p' "$5" >|"${OUTPUT}" + fi ;; --list-config) [ $# -eq 2 ] || die "Unexpected arguments to gpg $*\n"