pashage_extra_spec.sh (57457B)
1 # pashage - age-backed POSIX password manager 2 # Copyright (C) 2024-2025 Natasha Kerensikova 3 # 4 # This program is free software; you can redistribute it and/or 5 # modify it under the terms of the GNU General Public License 6 # as published by the Free Software Foundation; either version 2 7 # of the License, or (at your option) any later version. 8 # 9 # This program is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU General Public License for more details. 13 # 14 # You should have received a copy of the GNU General Public License 15 # along with this program; if not, write to the Free Software 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 18 # This test file exercises the whole software through command functions, 19 # using minimal mocking, limited to cryptography (to make it more robust 20 # and easier to debug), like the `pass_usage.sh` suite. 21 # It complements `pass_usage.sh` with pashage-specific behavior, aiming for 22 # maximal coverage of normal code paths. 23 24 Describe 'Integrated Command Functions' 25 Include src/pashage.sh 26 if [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 27 Set 'errexit:on' 'nounset:on' 28 else 29 Set 'errexit:on' 'nounset:on' 'pipefail:on' 30 fi 31 32 GITLOG="${SHELLSPEC_WORKDIR}/git-log.txt" 33 34 AGE='mock-age' 35 IDENTITIES_FILE="${SHELLSPEC_WORKDIR}/age-identities" 36 PREFIX="${SHELLSPEC_WORKDIR}/store" 37 38 CHARACTER_SET='[:punct:][:alnum:]' 39 CHARACTER_SET_NO_SYMBOLS='[:alnum:]' 40 CLIP_TIME=45 41 GENERATED_LENGTH=25 42 X_SELECTION=clipboard 43 44 TREE__=' ' 45 TREE_I='| ' 46 TREE_T='|- ' 47 TREE_L='`- ' 48 49 BOLD_TEXT='(B)' 50 NORMAL_TEXT='(N)' 51 RED_TEXT='(R)' 52 BLUE_TEXT='(B)' 53 UNDERLINE_TEXT='(U)' 54 NO_UNDERLINE_TEXT='(!U)' 55 56 git_log() { 57 @git -C "${PREFIX}" status --porcelain >&2 58 @git -C "${PREFIX}" log --format='%s' --stat >|"${GITLOG}" 59 } 60 61 setup_log() { %text 62 #|Initial setup 63 #| 64 #| extra/subdir/file.age | 2 ++ 65 #| fluff/.age-recipients | 2 ++ 66 #| fluff/one.age | 3 +++ 67 #| fluff/three.age | 5 +++++ 68 #| fluff/two.age | 4 ++++ 69 #| old.gpg | 3 +++ 70 #| shared/.age-recipients | 2 ++ 71 #| stale.age | 3 +++ 72 #| subdir/file.age | 2 ++ 73 #| y.txt | 3 +++ 74 #| 10 files changed, 29 insertions(+) 75 } 76 77 setup_log_bin() { %text 78 #|Initial setup 79 #| 80 #| extra/subdir/file.age | Bin 0 -> 33 bytes 81 #| fluff/.age-recipients | 2 ++ 82 #| fluff/one.age | Bin 0 -> 55 bytes 83 #| fluff/three.age | Bin 0 -> 110 bytes 84 #| fluff/two.age | Bin 0 -> 90 bytes 85 #| old.gpg | 3 +++ 86 #| shared/.age-recipients | 2 ++ 87 #| stale.age | Bin 0 -> 55 bytes 88 #| subdir/file.age | Bin 0 -> 33 bytes 89 #| y.txt | 3 +++ 90 #| 10 files changed, 10 insertions(+) 91 } 92 93 expected_log() { setup_log; } # Default log to override as needed 94 95 check_git_log() { 96 git_log && expected_log | diff -u "${GITLOG}" - >&2 97 } 98 99 setup_id() { 100 @mkdir -p "${PREFIX}/$1" 101 @cat >"${PREFIX}/$1/.age-recipients" 102 } 103 104 setup_secret() { 105 [ "$1" = "${1%/*}" ] || @mkdir -p "${PREFIX}/${1%/*}" 106 @sed 's/^/age/' >"${PREFIX}/$1.age" 107 } 108 109 setup() { 110 @git init -q -b main "${PREFIX}" 111 @git -C "${PREFIX}" config --local user.name 'Test User' 112 @git -C "${PREFIX}" config --local user.email 'test@example.com' 113 %putsn 'myself' >"${IDENTITIES_FILE}" 114 %text | setup_secret 'subdir/file' 115 #|Recipient:myself 116 #|:p4ssw0rd 117 %text | setup_secret 'extra/subdir/file' 118 #|Recipient:myself 119 #|:Pa55worD 120 %text | setup_id 'shared' 121 #|myself 122 #|friend 123 %text | setup_id 'fluff' 124 #|master 125 #|myself 126 %text | setup_secret 'fluff/one' 127 #|Recipient:master 128 #|Recipient:myself 129 #|:1-password 130 %text | setup_secret 'fluff/two' 131 #|Recipient:master 132 #|Recipient:myself 133 #|:2-password 134 #|:URL: https://example.com/login 135 %text | setup_secret 'fluff/three' 136 #|Recipient:master 137 #|Recipient:myself 138 #|:3-password 139 #|:Username: 3Jane 140 #|:URL: https://example.com/login 141 %text | setup_secret 'stale' 142 #|Recipient:master 143 #|Recipient:myself 144 #|:0-password 145 %text >"${PREFIX}/old.gpg" 146 #|gpgRecipient:myOldSelf 147 #|gpg:very-old-password 148 #|gpg:Username: previous-life 149 %text >"${PREFIX}/y.txt" 150 #|# Title 151 #|Line of text 152 #|End of note 153 @git -C "${PREFIX}" add . 154 @git -C "${PREFIX}" commit -m 'Initial setup' >/dev/null 155 156 # Check setup_log consistency 157 git_log 158 setup_log | @diff -u - "${GITLOG}" 159 } 160 161 cleanup() { 162 @rm -rf "${PREFIX}" 163 @rm -f "${IDENTITIES_FILE}" 164 @rm -rf "${SHELLSPEC_WORKDIR}/clone" 165 @rm -rf "${SHELLSPEC_WORKDIR}/secure" 166 } 167 168 BeforeEach setup 169 AfterEach cleanup 170 171 basename() { @basename "$@"; } 172 cat() { @cat "$@"; } 173 cp() { @cp "$@"; } 174 dd() { @dd "$@"; } 175 diff() { @diff "$@"; } 176 dirname() { @dirname "$@"; } 177 git() { @git "$@"; } 178 mkdir() { @mkdir "$@"; } 179 mktemp() { @mktemp "$@"; } 180 mv() { @mv "$@"; } 181 rm() { @rm "$@"; } 182 tr() { @tr "$@"; } 183 184 platform_tmpdir() { 185 SECURE_TMPDIR="${SHELLSPEC_WORKDIR}/secure" 186 @mkdir -p "${SECURE_TMPDIR}" 187 } 188 189 # Describe 'cmd_copy' is not needed (covered by 'cmd_copy_move') 190 191 Describe 'cmd_copy_move' 192 It 'processes several files and directories into a directory' 193 When call cmd_move extra stale subdir 194 The status should be success 195 The error should be blank 196 The output should be blank 197 expected_log() { %text 198 #|Move stale.age to subdir/stale.age 199 #| 200 #| stale.age => subdir/stale.age | 0 201 #| 1 file changed, 0 insertions(+), 0 deletions(-) 202 #|Move extra/ to subdir/extra/ 203 #| 204 #| {extra => subdir/extra}/subdir/file.age | 0 205 #| 1 file changed, 0 insertions(+), 0 deletions(-) 206 setup_log 207 } 208 The result of function check_git_log should be successful 209 End 210 211 It 'processes unencrypted files' 212 When run cmd_move y.txt shared/yy 213 The status should be success 214 The error should be blank 215 The output should be blank 216 expected_log() { %text 217 #|Move y.txt to shared/yy 218 #| 219 #| y.txt => shared/yy | 0 220 #| 1 file changed, 0 insertions(+), 0 deletions(-) 221 setup_log 222 } 223 The result of function check_git_log should be successful 224 End 225 226 It 'does not overwrite a file without confirmation' 227 Data 'n' 228 When call cmd_copy subdir/file stale 229 The status should be success 230 The error should be blank 231 The output should equal 'stale.age already exists. Overwrite? [y/n]' 232 The result of function check_git_log should be successful 233 End 234 235 It 'overwrites a file after confirmation' 236 Data 'y' 237 When call cmd_copy subdir/file stale 238 The status should be success 239 The error should be blank 240 The output should equal 'stale.age already exists. Overwrite? [y/n]' 241 expected_log() { %text 242 #|Copy subdir/file.age to stale.age 243 #| 244 #| stale.age | 3 +-- 245 #| 1 file changed, 1 insertion(+), 2 deletions(-) 246 setup_log 247 } 248 The result of function check_git_log should be successful 249 End 250 251 It 'does not re-encrypt by default when recipients do not change' 252 When call cmd_move stale renamed 253 The status should be success 254 The error should be blank 255 The output should be blank 256 expected_log() { %text 257 #|Move stale.age to renamed.age 258 #| 259 #| stale.age => renamed.age | 0 260 #| 1 file changed, 0 insertions(+), 0 deletions(-) 261 setup_log 262 } 263 The result of function check_git_log should be successful 264 End 265 266 It 're-encrypts by default when recipients change' 267 When call cmd_move stale shared 268 The status should be success 269 The error should be blank 270 The output should be blank 271 expected_file() { %text 272 #|ageRecipient:myself 273 #|ageRecipient:friend 274 #|age:0-password 275 } 276 The contents of file "${PREFIX}/shared/stale.age" should \ 277 equal "$(expected_file)" 278 expected_log() { %text 279 #|Move stale.age to shared/stale.age 280 #| 281 #| stale.age => shared/stale.age | 2 +- 282 #| 1 file changed, 1 insertion(+), 1 deletion(-) 283 setup_log 284 } 285 The result of function check_git_log should be successful 286 End 287 288 It 'always re-encrypts when forced' 289 When call cmd_move --reencrypt stale renamed 290 The status should be success 291 The error should be blank 292 The output should be blank 293 expected_log() { %text 294 #|Move stale.age to renamed.age 295 #| 296 #| stale.age => renamed.age | 1 - 297 #| 1 file changed, 1 deletion(-) 298 setup_log 299 } 300 The result of function check_git_log should be successful 301 End 302 303 It 'never re-encrypts when forced' 304 When call cmd_move --keep stale shared 305 The status should be success 306 The error should be blank 307 The output should be blank 308 expected_log() { %text 309 #|Move stale.age to shared/stale.age 310 #| 311 #| stale.age => shared/stale.age | 0 312 #| 1 file changed, 0 insertions(+), 0 deletions(-) 313 setup_log 314 } 315 The result of function check_git_log should be successful 316 End 317 318 It 'interactively re-encrypts when asked' 319 Data 320 #|n 321 #|y 322 End 323 When call cmd_move --interactive stale extra/subdir/file shared 324 The status should be success 325 The error should be blank 326 The output should equal 'Reencrypt stale into shared/stale? [y/n]Reencrypt extra/subdir/file into shared/file? [y/n]' 327 expected_file() { %text 328 #|ageRecipient:myself 329 #|ageRecipient:friend 330 #|age:Pa55worD 331 } 332 The contents of file "${PREFIX}/shared/file.age" should \ 333 equal "$(expected_file)" 334 expected_log() { %text 335 #|Move extra/subdir/file.age to shared/file.age 336 #| 337 #| {extra/subdir => shared}/file.age | 1 + 338 #| 1 file changed, 1 insertion(+) 339 #|Move stale.age to shared/stale.age 340 #| 341 #| stale.age => shared/stale.age | 0 342 #| 1 file changed, 0 insertions(+), 0 deletions(-) 343 setup_log 344 } 345 The result of function check_git_log should be successful 346 End 347 348 It 'aborts on decryption failure even without pipefail' 349 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 350 Set 'pipefail:off' 351 fi 352 AGE=false 353 When run cmd_move --reencrypt stale renamed 354 The status should equal 1 355 The error should equal \ 356 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- ${PREFIX}/stale.age" 357 The output should be blank 358 The result of function check_git_log should be successful 359 End 360 361 It 'displays usage when called with incompatible reencryption arguments' 362 PROGRAM=prg 363 COMMAND=copy 364 When run cmd_copy_move -eik stale shared/ 365 The status should equal 1 366 The output should be blank 367 expected_err() { %text 368 #|Usage: prg copy [--reencrypt,-e | --interactive,-i | --keep,-k ] 369 #| [--force,-f] old-path new-path 370 } 371 The error should equal "$(expected_err)" 372 The result of function check_git_log should be successful 373 End 374 375 It 'displays copy usage with `c*` commands' 376 PROGRAM=prg 377 COMMAND=curious 378 When run cmd_copy_move single 379 The status should equal 1 380 The output should be blank 381 expected_err() { %text 382 #|Usage: prg copy [--reencrypt,-e | --interactive,-i | --keep,-k ] 383 #| [--force,-f] old-path new-path 384 } 385 The error should equal "$(expected_err)" 386 The result of function check_git_log should be successful 387 End 388 389 It 'displays move usage with `m*` commands' 390 PROGRAM=prg 391 COMMAND=memory 392 When run cmd_copy_move single 393 The status should equal 1 394 The output should be blank 395 expected_err() { %text 396 #|Usage: prg move [--reencrypt,-e | --interactive,-i | --keep,-k ] 397 #| [--force,-f] old-path new-path 398 } 399 The error should equal "$(expected_err)" 400 The result of function check_git_log should be successful 401 End 402 403 It 'displays both usages when in doubt' 404 PROGRAM=prg 405 COMMAND=bad 406 When run cmd_copy_move single 407 The status should equal 1 408 The output should be blank 409 expected_err() { %text 410 #|Usage: prg copy [--reencrypt,-e | --interactive,-i | --keep,-k ] 411 #| [--force,-f] old-path new-path 412 #| prg move [--reencrypt,-e | --interactive,-i | --keep,-k ] 413 #| [--force,-f] old-path new-path 414 } 415 The error should equal "$(expected_err)" 416 The result of function check_git_log should be successful 417 End 418 End 419 420 Describe 'cmd_delete' 421 It 'deletes multiple files at once, prompting before each one' 422 Data 423 #|y 424 #|n 425 #|y 426 End 427 When call cmd_delete stale subdir/file fluff/two 428 The status should be success 429 The output should equal 'Are you sure you would like to delete stale? [y/n]Are you sure you would like to delete subdir/file? [y/n]Are you sure you would like to delete fluff/two? [y/n]' 430 The error should be blank 431 The file "${PREFIX}/fluff/two.age" should not be exist 432 The file "${PREFIX}/stale.age" should not be exist 433 The file "${PREFIX}/subdir/file.age" should be exist 434 expected_log() { %text 435 #|Remove fluff/two from store. 436 #| 437 #| fluff/two.age | 4 ---- 438 #| 1 file changed, 4 deletions(-) 439 #|Remove stale from store. 440 #| 441 #| stale.age | 3 --- 442 #| 1 file changed, 3 deletions(-) 443 setup_log 444 } 445 The result of function check_git_log should be successful 446 End 447 End 448 449 Describe 'cmd_edit' 450 It 'uses EDITOR in a dumb terminal' 451 unset EDIT_CMD 452 EDITOR=false 453 TERM=dumb 454 VISUAL=true 455 When run cmd_edit stale 456 The status should equal 1 457 The output should be blank 458 The error should equal 'Editor "false" exited with code 1' 459 expected_file() { %text:expand 460 #|ageRecipient:master 461 #|ageRecipient:myself 462 #|age:0-password 463 } 464 The contents of file "${PREFIX}/stale.age" should \ 465 equal "$(expected_file)" 466 The result of function check_git_log should be successful 467 End 468 469 It 'uses EDITOR when VISUAL is not set' 470 unset EDIT_CMD 471 EDITOR=false 472 TERM=not-dumb 473 unset VISUAL 474 When run cmd_edit stale 475 The status should equal 1 476 The output should be blank 477 The error should equal 'Editor "false" exited with code 1' 478 expected_file() { %text:expand 479 #|ageRecipient:master 480 #|ageRecipient:myself 481 #|age:0-password 482 } 483 The contents of file "${PREFIX}/stale.age" should \ 484 equal "$(expected_file)" 485 The result of function check_git_log should be successful 486 End 487 488 It 'uses VISUAL in a non-dumb terminal' 489 unset EDIT_CMD 490 EDITOR=true 491 TERM=not-dumb 492 VISUAL=false 493 When run cmd_edit stale 494 The status should equal 1 495 The output should be blank 496 The error should equal 'Editor "false" exited with code 1' 497 expected_file() { %text:expand 498 #|ageRecipient:master 499 #|ageRecipient:myself 500 #|age:0-password 501 } 502 The contents of file "${PREFIX}/stale.age" should \ 503 equal "$(expected_file)" 504 The result of function check_git_log should be successful 505 End 506 507 It 'falls back on vi without EDITOR nor visual' 508 unset EDIT_CMD 509 unset EDITOR 510 unset VISUAL 511 When run cmd_edit subdir/new 512 The status should equal 127 513 The output should be blank 514 The line 1 of error should include 'not found' 515 The line 2 of error should equal 'Editor "vi" exited with code 127' 516 The file "${PREFIX}/subdir/new.age" should not be exist 517 The file "${PREFIX}/subdir/new.gpg" should not be exist 518 The result of function check_git_log should be successful 519 End 520 521 It 'reports unchanged file' 522 EDIT_CMD=true 523 When call cmd_edit stale 524 The status should be success 525 The output should equal 'Password for stale unchanged.' 526 The error should be blank 527 expected_file() { %text:expand 528 #|ageRecipient:master 529 #|ageRecipient:myself 530 #|age:0-password 531 } 532 The contents of file "${PREFIX}/stale.age" should \ 533 equal "$(expected_file)" 534 The result of function check_git_log should be successful 535 End 536 537 It 'allows lack of file creation without error' 538 EDIT_CMD=true 539 When run cmd_edit subdir/new 540 The status should be success 541 The output should equal 'New password for subdir/new not saved.' 542 The error should be blank 543 The file "${PREFIX}/subdir/new.age" should not be exist 544 The file "${PREFIX}/subdir/new.gpg" should not be exist 545 The result of function check_git_log should be successful 546 End 547 548 It 'reports editor failure' 549 ret42() { return 42; } 550 EDIT_CMD=ret42 551 When run cmd_edit subdir/new 552 The status should equal 42 553 The output should be blank 554 The error should equal 'Editor "ret42" exited with code 42' 555 The file "${PREFIX}/subdir/new.age" should not be exist 556 The file "${PREFIX}/subdir/new.gpg" should not be exist 557 The result of function check_git_log should be successful 558 End 559 560 It 'aborts on decryption failure even without pipefail' 561 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 562 Set 'pipefail:off' 563 fi 564 AGE=false 565 tail() { @tail "$@"; } 566 When run cmd_edit stale 567 The status should equal 1 568 The error should equal \ 569 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- ${PREFIX}/stale.age" 570 The output should be blank 571 The result of function check_git_log should be successful 572 End 573 End 574 575 Describe 'cmd_find' 576 grep() { @grep "$@"; } 577 578 It 'interprets the pattern as a regular expression' 579 expected_output() { %text 580 #|Search pattern: ^o 581 #||- (B)fluff(N) 582 #|| `- one 583 #|`- (R)old(N) 584 } 585 When call cmd_find '^o' 586 The status should be success 587 The output should equal "$(expected_output)" 588 The error should be blank 589 The result of function check_git_log should be successful 590 End 591 592 It 'forwards flags to grep' 593 expected_output() { %text 594 #|Search pattern: -E -i F|I 595 #||- (B)extra(N) 596 #|| `- (B)subdir(N) 597 #|| `- file 598 #|`- (B)subdir(N) 599 #| `- file 600 } 601 When call cmd_find -E -i 'F|I' 602 The status should be success 603 The output should equal "$(expected_output)" 604 The error should be blank 605 The result of function check_git_log should be successful 606 End 607 608 It 'can output a raw list of secrets' 609 expected_output() { %text 610 #|extra/subdir/file 611 #|subdir/file 612 } 613 When call cmd_find -r -E -i 'F|I' 614 The status should be success 615 The output should equal "$(expected_output)" 616 The error should be blank 617 The result of function check_git_log should be successful 618 End 619 End 620 621 Describe 'cmd_generate' 622 It 'uses the character set given explicitly instead of environment' 623 CHARACTER_SET='[0-9]' 624 CHARACTER_SET_NO_SYMBOLS='[0-9]' 625 When call cmd_generate new 5 '[:upper:]' 626 The status should be success 627 The error should be blank 628 The lines of output should equal 2 629 The line 1 of output should \ 630 equal '(B)The generated password for (U)new(!U) is:(N)' 631 The line 2 of output should match pattern '[A-Z][A-Z][A-Z][A-Z][A-Z]' 632 expected_log() { %text 633 #|Add generated password for new. 634 #| 635 #| new.age | 2 ++ 636 #| 1 file changed, 2 insertions(+) 637 setup_log 638 } 639 The result of function check_git_log should be successful 640 End 641 642 It 'overwrites after asking for confirmation' 643 Data 'y' 644 When call cmd_generate subdir/file 10 645 The status should be success 646 The output should start with 'An entry already exists for subdir/file. Overwrite it? [y/n](B)The generated password for (U)subdir/file(!U) is:(N)' 647 The error should be blank 648 expected_log() { %text 649 #|Add generated password for subdir/file. 650 #| 651 #| subdir/file.age | 2 +- 652 #| 1 file changed, 1 insertion(+), 1 deletion(-) 653 setup_log 654 } 655 The result of function check_git_log should be successful 656 End 657 658 It 'does nothing without confirmation' 659 Data 'n' 660 When call cmd_generate subdir/file 10 661 The status should be success 662 The output should equal \ 663 'An entry already exists for subdir/file. Overwrite it? [y/n]' 664 The error should be blank 665 The result of function check_git_log should be successful 666 End 667 668 It 'cannot overwrite a directory' 669 run_test() { 670 mkdir -p "${PREFIX}/new-secret.age" && \ 671 cmd_generate -f new-secret 10 672 } 673 When run run_test 674 The status should equal 1 675 The output should be blank 676 The error should equal 'Cannot replace directory new-secret.age' 677 The result of function check_git_log should be successful 678 End 679 680 It 'aborts on decryption failure even without pipefail' 681 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 682 Set 'pipefail:off' 683 fi 684 AGE=false 685 tail() { @tail "$@"; } 686 When run cmd_generate --in-place stale 687 The status should equal 1 688 The error should equal \ 689 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- ${PREFIX}/stale.age" 690 The output should equal 'Decrypting previous secret for stale' 691 The result of function check_git_log should be successful 692 End 693 694 It 'saves after showing and getting confirmation' 695 Data 'y' 696 When call cmd_generate --try new 697 The status should be success 698 The error should be blank 699 The file "${PREFIX}/new.age" should be exist 700 expected_out() { 701 %putsn '(B)The generated password for (U)new(!U) is:(N)' 702 @sed '$s/^age://p;d' "${PREFIX}/new.age" 703 %putsn 'Save generated password for new? [y/n]' 704 } 705 The output should equal "$(expected_out)" 706 expected_log() { %text 707 #|Add generated password for new. 708 #| 709 #| new.age | 2 ++ 710 #| 1 file changed, 2 insertions(+) 711 setup_log 712 } 713 The result of function check_git_log should be successful 714 End 715 716 It 'does not save after showing and getting cancellation' 717 Data 'n' 718 When call cmd_generate --try new 5 '[:lower:]' 719 The status should be success 720 The error should be blank 721 The lines of output should equal 3 722 The line 1 of output should \ 723 equal '(B)The generated password for (U)new(!U) is:(N)' 724 The line 2 of output should match pattern '[a-z][a-z][a-z][a-z][a-z]' 725 The line 3 of output should \ 726 equal 'Save generated password for new? [y/n]' 727 The result of function check_git_log should be successful 728 End 729 730 It 'accepts an extra line after the generated secret' 731 Data 'extra comment line' 732 When call cmd_generate --multiline new 15 733 The status should be success 734 The error should be blank 735 The lines of output should equal 3 736 The line 1 of output should \ 737 equal 'Enter extra secrets then Ctrl+D when finished:' 738 The line 2 of output should \ 739 equal '(B)The generated password for (U)new(!U) is:(N)' 740 The line 3 of output should match pattern '???????????????' 741 The lines of contents of file "${PREFIX}/new.age" should equal 3 742 The line 3 of contents of file "${PREFIX}/new.age" should \ 743 equal 'age:extra comment line' 744 expected_log() { %text 745 #|Add generated password for new. 746 #| 747 #| new.age | 3 +++ 748 #| 1 file changed, 3 insertions(+) 749 setup_log 750 } 751 The result of function check_git_log should be successful 752 End 753 754 It 'accepts extra lines after the generated secret when overwriting' 755 Data 756 #|Extra: line 757 #|Extra: end of input 758 End 759 When call cmd_generate --multiline --force fluff/three 5 760 The status should be success 761 The error should be blank 762 The lines of output should equal 3 763 The line 1 of output should \ 764 equal 'Enter extra secrets then Ctrl+D when finished:' 765 The line 2 of output should \ 766 equal '(B)The generated password for (U)fluff/three(!U) is:(N)' 767 The line 3 of output should match pattern '?????' 768 The lines of contents of file "${PREFIX}/fluff/three.age" should equal 5 769 The line 4 of contents of file "${PREFIX}/fluff/three.age" should \ 770 equal 'age:Extra: line' 771 The line 5 of contents of file "${PREFIX}/fluff/three.age" should \ 772 equal 'age:Extra: end of input' 773 expected_log() { %text 774 #|Add generated password for fluff/three. 775 #| 776 #| fluff/three.age | 6 +++--- 777 #| 1 file changed, 3 insertions(+), 3 deletions(-) 778 setup_log 779 } 780 The result of function check_git_log should be successful 781 End 782 783 It 'accepts extra lines after the generated secret after in-place data' 784 Data 785 #|Extra: line 786 #|Extra: end of input 787 End 788 When call cmd_generate --multiline --in-place fluff/three 5 789 The status should be success 790 The error should be blank 791 The lines of output should equal 4 792 The line 1 of output should \ 793 equal 'Decrypting previous secret for fluff/three' 794 The line 2 of output should \ 795 equal 'Enter extra secrets then Ctrl+D when finished:' 796 The line 3 of output should \ 797 equal '(B)The generated password for (U)fluff/three(!U) is:(N)' 798 The line 4 of output should match pattern '?????' 799 The lines of contents of file "${PREFIX}/fluff/three.age" should equal 7 800 The line 4 of contents of file "${PREFIX}/fluff/three.age" should \ 801 equal 'age:Username: 3Jane' 802 The line 5 of contents of file "${PREFIX}/fluff/three.age" should \ 803 equal 'age:URL: https://example.com/login' 804 The line 6 of contents of file "${PREFIX}/fluff/three.age" should \ 805 equal 'age:Extra: line' 806 The line 7 of contents of file "${PREFIX}/fluff/three.age" should \ 807 equal 'age:Extra: end of input' 808 expected_log() { %text 809 #|Replace generated password for fluff/three. 810 #| 811 #| fluff/three.age | 4 +++- 812 #| 1 file changed, 3 insertions(+), 1 deletion(-) 813 setup_log 814 } 815 The result of function check_git_log should be successful 816 End 817 End 818 819 Describe 'cmd_git' 820 It 'initializes a clone like a new repository' 821 SOURCE="${PREFIX}" 822 PREFIX="${SHELLSPEC_WORKDIR}/clone" 823 expected_err() { %text:expand 824 #|Cloning into '${PREFIX}'... 825 #|done. 826 } 827 When call cmd_git clone "${SOURCE}" 828 The status should be success 829 The output should be blank 830 The error should equal "$(expected_err)" 831 The file "${PREFIX}/.gitattributes" should be exist 832 The contents of file "${PREFIX}/.gitattributes" should equal \ 833 '*.age diff=age' 834 expected_log() { %text 835 #|Configure git repository for age file diff. 836 #| 837 #| .gitattributes | 1 + 838 #| 1 file changed, 1 insertion(+) 839 setup_log_bin 840 } 841 The result of function check_git_log should be successful 842 PREFIX="${SOURCE}" 843 End 844 End 845 846 Describe 'cmd_grep' 847 It 'aborts on decryption failure even without pipefail' 848 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 849 Set 'pipefail:off' 850 fi 851 grep() { @grep "$@"; } 852 AGE=false 853 When run cmd_grep foo 854 The status should equal 1 855 The error should equal \ 856 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- file.age" 857 The output should be blank 858 The result of function check_git_log should be successful 859 End 860 End 861 862 Describe 'cmd_gitconfig' 863 grep() { @grep "$@"; } 864 865 It 'creates a new .gitattributes and configures diff' 866 When call cmd_gitconfig 867 The status should be success 868 The output should be blank 869 The error should be blank 870 The file "${PREFIX}/.gitattributes" should be exist 871 The contents of file "${PREFIX}/.gitattributes" should equal \ 872 '*.age diff=age' 873 expected_log() { %text 874 #|Configure git repository for age file diff. 875 #| 876 #| .gitattributes | 1 + 877 #| 1 file changed, 1 insertion(+) 878 setup_log_bin 879 } 880 The result of function check_git_log should be successful 881 End 882 883 It 'expands an existing .gitattributes' 884 run_test() { 885 %putsn '# Existing but empty' >"${PREFIX}/.gitattributes" 886 @git -C "${PREFIX}" add .gitattributes >/dev/null 887 @git -C "${PREFIX}" commit -m 'Test case setup' >/dev/null 888 cmd_gitconfig 889 } 890 When call run_test 891 The status should be success 892 The output should be blank 893 The error should be blank 894 expected_file() { %text 895 #|# Existing but empty 896 #|*.age diff=age 897 } 898 The file "${PREFIX}/.gitattributes" should be exist 899 The contents of file "${PREFIX}/.gitattributes" should \ 900 equal "$(expected_file)" 901 expected_log() { %text 902 #|Configure git repository for age file diff. 903 #| 904 #| .gitattributes | 1 + 905 #| 1 file changed, 1 insertion(+) 906 #|Test case setup 907 #| 908 #| .gitattributes | 1 + 909 #| 1 file changed, 1 insertion(+) 910 setup_log_bin 911 } 912 The result of function check_git_log should be successful 913 End 914 915 It 'is idempotent' 916 run_test() { 917 cmd_gitconfig && cmd_gitconfig 918 } 919 When call run_test 920 The status should be success 921 The output should be blank 922 The error should be blank 923 The file "${PREFIX}/.gitattributes" should be exist 924 The contents of file "${PREFIX}/.gitattributes" should equal \ 925 '*.age diff=age' 926 expected_log() { %text 927 #|Configure git repository for age file diff. 928 #| 929 #| .gitattributes | 1 + 930 #| 1 file changed, 1 insertion(+) 931 setup_log_bin 932 } 933 The result of function check_git_log should be successful 934 End 935 End 936 937 Describe 'cmd_help' 938 It 'displays a help text with pashage-specific supported commands' 939 PROGRAM=prg 940 When call cmd_help 941 The status should be success 942 The output should include ' prg copy ' 943 The output should include ' prg delete ' 944 The output should include ' prg gitconfig' 945 The output should include ' prg move ' 946 The output should include ' prg random ' 947 The output should include ' prg reencrypt ' 948 End 949 End 950 951 Describe 'cmd_init' 952 It 're-encrypts the whole store using a recipient ids named like a flag' 953 When call cmd_init -- -p 'new-id' 954 The status should be success 955 The output should equal 'Password store recipients set at store root' 956 The error should be blank 957 expected_file() { %text 958 #|-p 959 #|new-id 960 } 961 The contents of file "${PREFIX}/.age-recipients" should \ 962 equal "$(expected_file)" 963 expected_log() { %text 964 #|Set age recipients at store root 965 #| 966 #| .age-recipients | 2 ++ 967 #| extra/subdir/file.age | 3 ++- 968 #| stale.age | 4 ++-- 969 #| subdir/file.age | 3 ++- 970 #| 4 files changed, 8 insertions(+), 4 deletions(-) 971 setup_log 972 } 973 The result of function check_git_log should be successful 974 End 975 976 It 'does not re-encrypt with `keep` flag' 977 When call cmd_init -k 'new-id' 978 The status should be success 979 The output should equal 'Password store recipients set at store root' 980 The error should be blank 981 The contents of file "${PREFIX}/.age-recipients" should equal 'new-id' 982 expected_log() { %text 983 #|Set age recipients at store root 984 #| 985 #| .age-recipients | 1 + 986 #| 1 file changed, 1 insertion(+) 987 setup_log 988 } 989 The result of function check_git_log should be successful 990 End 991 992 It 'asks before re-encrypting each file with `interactive` flag' 993 Data 994 #|n 995 #|y 996 #|n 997 End 998 When call cmd_init -i 'new-id' 999 The status should be success 1000 The output should equal 'Re-encrypt extra/subdir/file? [y/n]Re-encrypt stale? [y/n]Re-encrypt subdir/file? [y/n]Password store recipients set at store root' 1001 The error should be blank 1002 The contents of file "${PREFIX}/.age-recipients" should equal 'new-id' 1003 expected_log() { %text 1004 #|Set age recipients at store root 1005 #| 1006 #| .age-recipients | 1 + 1007 #| stale.age | 3 +-- 1008 #| 2 files changed, 2 insertions(+), 2 deletions(-) 1009 setup_log 1010 } 1011 The result of function check_git_log should be successful 1012 End 1013 1014 usage_text() { %text 1015 #|Usage: prg init [--interactive,-i | --keep,-k ] 1016 #| [--path=subfolder,-p subfolder] age-recipient ... 1017 } 1018 1019 It 'displays usage when using incompatible options (`-i` then `-k`)' 1020 PROGRAM=prg 1021 When run cmd_init --interactive --keep 'new-id' 1022 The status should equal 1 1023 The output should be blank 1024 The error should equal "$(usage_text)" 1025 The result of function check_git_log should be successful 1026 End 1027 1028 It 'displays usage when using incompatible options (`-k` then `-i`)' 1029 PROGRAM=prg 1030 When run cmd_init -ki 'new-id' 1031 The status should equal 1 1032 The output should be blank 1033 The error should equal "$(usage_text)" 1034 The result of function check_git_log should be successful 1035 End 1036 End 1037 1038 Describe 'cmd_insert' 1039 It 'inserts an entry encrypted using an explicit recipient file' 1040 PASHAGE_RECIPIENTS_FILE="${PREFIX}/fluff/.age-recipients" 1041 PASSAGE_RECIPIENTS_FILE="${PREFIX}/shared/.age-recipients" 1042 PASHAGE_RECIPIENTS='shadowed' 1043 PASSAGE_RECIPIENTS='shadowed' 1044 Data 'pass' 1045 When call cmd_insert -e shared/new-file 1046 The status should be success 1047 The output should include 'shared/new-file' 1048 expected_file() { %text:expand 1049 #|ageRecipient:master 1050 #|ageRecipient:myself 1051 #|age:pass 1052 } 1053 The contents of file "${PREFIX}/shared/new-file.age" should \ 1054 equal "$(expected_file)" 1055 expected_log() { %text 1056 #|Add given password for shared/new-file to store. 1057 #| 1058 #| shared/new-file.age | 3 +++ 1059 #| 1 file changed, 3 insertions(+) 1060 setup_log 1061 } 1062 The result of function check_git_log should be successful 1063 End 1064 1065 It 'inserts an entry encrypted using explicit recipients' 1066 PASHAGE_RECIPIENTS='force-1 force-2' 1067 PASSAGE_RECIPIENTS='shadowed' 1068 Data 'pass' 1069 When call cmd_insert -e shared/new-file 1070 The status should be success 1071 The output should include 'shared/new-file' 1072 expected_file() { %text:expand 1073 #|ageRecipient:force-1 1074 #|ageRecipient:force-2 1075 #|age:pass 1076 } 1077 The contents of file "${PREFIX}/shared/new-file.age" should \ 1078 equal "$(expected_file)" 1079 expected_log() { %text 1080 #|Add given password for shared/new-file to store. 1081 #| 1082 #| shared/new-file.age | 3 +++ 1083 #| 1 file changed, 3 insertions(+) 1084 setup_log 1085 } 1086 The result of function check_git_log should be successful 1087 End 1088 1089 It 'inserts several new single-line entries' 1090 stty() { false; } 1091 Data 1092 #|password-1 1093 #|n 1094 #|password-2 1095 #|password-3 1096 End 1097 When call cmd_insert -e newdir/pass-1 subdir/file newdir/pass-2 1098 The status should be success 1099 The error should be blank 1100 The output should equal 'Enter password for newdir/pass-1: An entry already exists for subdir/file. Overwrite it? [y/n]Enter password for newdir/pass-2: ' 1101 The contents of file "${PREFIX}/newdir/pass-1.age" \ 1102 should include "age:password-1" 1103 The contents of file "${PREFIX}/newdir/pass-2.age" \ 1104 should include "age:password-2" 1105 expected_log() { %text 1106 #|Add given password for newdir/pass-2 to store. 1107 #| 1108 #| newdir/pass-2.age | 2 ++ 1109 #| 1 file changed, 2 insertions(+) 1110 #|Add given password for newdir/pass-1 to store. 1111 #| 1112 #| newdir/pass-1.age | 2 ++ 1113 #| 1 file changed, 2 insertions(+) 1114 setup_log 1115 } 1116 The result of function check_git_log should be successful 1117 End 1118 1119 It 'inserts several new multi-line entries' 1120 stty() { false; } 1121 Data 1122 #|password-1 1123 #| extra spaced line 1124 #| 1125 #|y 1126 #|password-2 1127 #| extra tabbed line 1128 #| 1129 #|password-3 1130 End 1131 When call cmd_insert -m newdir/pass-1 subdir/file newdir/pass-2 1132 The status should be success 1133 The error should be blank 1134 expected_out() { %text 1135 #|Enter contents of newdir/pass-1 and 1136 #|press Ctrl+D or enter an empty line when finished: 1137 #|An entry already exists for subdir/file. Overwrite it? [y/n]Enter contents of subdir/file and 1138 #|press Ctrl+D or enter an empty line when finished: 1139 #|Enter contents of newdir/pass-2 and 1140 #|press Ctrl+D or enter an empty line when finished: 1141 } 1142 The output should equal "$(expected_out)" 1143 expected_file_1() { %text 1144 #|ageRecipient:myself 1145 #|age:password-1 1146 #|age: extra spaced line 1147 } 1148 expected_file_2() { %text 1149 #|ageRecipient:myself 1150 #|age:password-2 1151 #|age: extra tabbed line 1152 } 1153 expected_file_3() { %text 1154 #|ageRecipient:myself 1155 #|age:password-3 1156 } 1157 The contents of file "${PREFIX}/newdir/pass-1.age" \ 1158 should equal "$(expected_file_1)" 1159 The contents of file "${PREFIX}/subdir/file.age" \ 1160 should equal "$(expected_file_2)" 1161 The contents of file "${PREFIX}/newdir/pass-2.age" \ 1162 should equal "$(expected_file_3)" 1163 expected_log() { %text 1164 #|Add given password for newdir/pass-2 to store. 1165 #| 1166 #| newdir/pass-2.age | 2 ++ 1167 #| 1 file changed, 2 insertions(+) 1168 #|Add given password for subdir/file to store. 1169 #| 1170 #| subdir/file.age | 3 ++- 1171 #| 1 file changed, 2 insertions(+), 1 deletion(-) 1172 #|Add given password for newdir/pass-1 to store. 1173 #| 1174 #| newdir/pass-1.age | 3 +++ 1175 #| 1 file changed, 3 insertions(+) 1176 setup_log 1177 } 1178 The result of function check_git_log should be successful 1179 End 1180 1181 It 'inserts a new single-line entry on the second try' 1182 stty() { :; } 1183 Data 1184 #|first try 1185 #|First Try 1186 #|pass-word 1187 #|pass-word 1188 End 1189 When call cmd_insert newdir/newpass 1190 The status should be success 1191 The error should be blank 1192 expected_out() { %text | @sed 's/\$$//' 1193 #|Enter password for newdir/newpass: $ 1194 #|Retype password for newdir/newpass: $ 1195 #|Passwords don't match$ 1196 #|Enter password for newdir/newpass: $ 1197 #|Retype password for newdir/newpass: $ 1198 } 1199 The output should equal "$(expected_out)" 1200 The contents of file "${PREFIX}/newdir/newpass.age" \ 1201 should include "age:pass-word" 1202 expected_log() { %text 1203 #|Add given password for newdir/newpass to store. 1204 #| 1205 #| newdir/newpass.age | 2 ++ 1206 #| 1 file changed, 2 insertions(+) 1207 setup_log 1208 } 1209 The result of function check_git_log should be successful 1210 End 1211 1212 It 'overwrites an entry after confirmation' 1213 Data 1214 #|y 1215 #|pass-word 1216 End 1217 When call cmd_insert -e subdir/file 1218 The status should be success 1219 The error should be blank 1220 The output should equal 'An entry already exists for subdir/file. Overwrite it? [y/n]Enter password for subdir/file: ' 1221 expected_file() { %text 1222 #|ageRecipient:myself 1223 #|age:pass-word 1224 } 1225 The contents of file "${PREFIX}/subdir/file.age" \ 1226 should equal "$(expected_file)" 1227 expected_log() { %text 1228 #|Add given password for subdir/file to store. 1229 #| 1230 #| subdir/file.age | 2 +- 1231 #| 1 file changed, 1 insertion(+), 1 deletion(-) 1232 setup_log 1233 } 1234 The result of function check_git_log should be successful 1235 End 1236 1237 It 'does not overwrite an entry without confirmation' 1238 Data 1239 #|n 1240 #|pass-word 1241 End 1242 When call cmd_insert -e subdir/file 1243 The status should be success 1244 The error should be blank 1245 The output should equal \ 1246 'An entry already exists for subdir/file. Overwrite it? [y/n]' 1247 The result of function check_git_log should be successful 1248 End 1249 End 1250 1251 Describe 'cmd_list_or_show' 1252 It 'displays the whole store as a raw list' 1253 When call cmd_list_or_show --raw 1254 The status should be success 1255 The error should be blank 1256 expected_out() { %text 1257 #|extra/subdir/file 1258 #|fluff/one 1259 #|fluff/three 1260 #|fluff/two 1261 #|old 1262 #|stale 1263 #|subdir/file 1264 } 1265 The output should equal "$(expected_out)" 1266 End 1267 1268 It 'displays a subdirectory as a raw list' 1269 When call cmd_list_or_show -r fluff 1270 The status should be success 1271 The error should be blank 1272 expected_out() { %text 1273 #|fluff/one 1274 #|fluff/three 1275 #|fluff/two 1276 } 1277 The output should equal "$(expected_out)" 1278 End 1279 1280 It 'decrypts a GPG secret in the store using GPG' 1281 GPG=mock-gpg 1282 gpg() { false; } 1283 gpg2() { false; } 1284 When call cmd_list_or_show old 1285 The status should be success 1286 The error should be blank 1287 expected_out() { %text 1288 #|very-old-password 1289 #|Username: previous-life 1290 } 1291 The output should equal "$(expected_out)" 1292 End 1293 1294 It 'decrypts a GPG secret in the store using gpg2' 1295 unset GPG 1296 gpg() { false; } 1297 gpg2() { 1298 [ $# -eq 9 ] && [ "$6" = '--batch' ] && [ "$7" = '--use-agent' ] \ 1299 && mock-gpg "$1" "$2" "$3" "$4" "$5" "$8" "$9" 1300 } 1301 When call cmd_list_or_show old 1302 The status should be success 1303 The error should be blank 1304 expected_out() { %text 1305 #|very-old-password 1306 #|Username: previous-life 1307 } 1308 The output should equal "$(expected_out)" 1309 End 1310 1311 It 'decrypts a GPG secret in the store using gpg' 1312 unset GPG 1313 gpg() { mock-gpg "$@"; } 1314 When call cmd_list_or_show old 1315 The status should be success 1316 The error should be blank 1317 expected_out() { %text 1318 #|very-old-password 1319 #|Username: previous-life 1320 } 1321 The output should equal "$(expected_out)" 1322 End 1323 1324 It 'fails to decrypt a GPG secret without gpg' 1325 unset GPG 1326 When run cmd_list_or_show old 1327 The status should equal 1 1328 The error should equal 'GPG does not seem available' 1329 The output should be blank 1330 End 1331 1332 It 'displays both list and show usage on parse error with ambiguity' 1333 PROGRAM=prg 1334 COMMAND=both 1335 When run cmd_list_or_show -x 1336 The status should equal 1 1337 The output should be blank 1338 expected_err() { %text 1339 #|Usage: prg [list] [--raw,-r] [subfolder] 1340 #| prg [show] [--clip[=line-number],-c[line-number] | 1341 #| --qrcode[=line-number],-q[line-number]] pass-name 1342 } 1343 The error should equal "$(expected_err)" 1344 End 1345 1346 It 'displays list usage on parse error with list command' 1347 PROGRAM=prg 1348 COMMAND=list 1349 When run cmd_list_or_show -x 1350 The status should equal 1 1351 The output should be blank 1352 expected_err() { %text 1353 #|Usage: prg [list] [--raw,-r] [subfolder] 1354 } 1355 The error should equal "$(expected_err)" 1356 End 1357 1358 It 'displays show usage on parse error with show command' 1359 PROGRAM=prg 1360 COMMAND=show 1361 When run cmd_list_or_show -x 1362 The status should equal 1 1363 The output should be blank 1364 expected_err() { %text 1365 #|Usage: prg [show] [--clip[=line-number],-c[line-number] | 1366 #| --qrcode[=line-number],-q[line-number]] pass-name 1367 } 1368 The error should equal "$(expected_err)" 1369 End 1370 1371 It 'aborts on age decryption failure even without pipefail' 1372 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 1373 Set 'pipefail:off' 1374 fi 1375 AGE=false 1376 When run cmd_list_or_show stale 1377 The status should equal 1 1378 The error should equal \ 1379 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- ${PREFIX}/stale.age" 1380 The output should be blank 1381 The result of function check_git_log should be successful 1382 End 1383 1384 It 'aborts on gpg decryption failure even without pipefail' 1385 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 1386 Set 'pipefail:off' 1387 fi 1388 GPG=false 1389 When run cmd_list_or_show old 1390 The status should equal 1 1391 The error should equal "Fatal(1): false -d --quiet --yes --compress-algo=none --no-encrypt-to -- ${PREFIX}/old.gpg" 1392 The output should be blank 1393 The result of function check_git_log should be successful 1394 End 1395 End 1396 1397 # Describe 'cmd_move' is not needed (covered by 'cmd_copy_move') 1398 1399 Describe 'cmd_random' 1400 It 'generates random characters' 1401 When call cmd_random 2 '[:digit:]' 1402 The status should be success 1403 The error should be blank 1404 The output should match pattern '[0-9][0-9]' 1405 End 1406 1407 It 'defaults to using CHARACTER_SET' 1408 PREV_CHARACTER_SET="${CHARACTER_SET}" 1409 CHARACTER_SET='[:lower:]' 1410 When call cmd_random 2 1411 The status should be success 1412 The error should be blank 1413 The output should match pattern '[a-z][a-z]' 1414 CHARACTER_SET="${PREV_CHARACTER_SET}" 1415 End 1416 1417 It 'defaults to using both GENERATED_LENGTH and CHARACTER_SET' 1418 PREV_CHARACTER_SET="${CHARACTER_SET}" 1419 PREV_GENERATED_LENGTH="${GENERATED_LENGTH}" 1420 CHARACTER_SET='[:upper:]' 1421 GENERATED_LENGTH=5 1422 When call cmd_random 1423 The status should be success 1424 The error should be blank 1425 The output should match pattern '[A-Z][A-Z][A-Z][A-Z][A-Z]' 1426 CHARACTER_SET="${PREV_CHARACTER_SET}" 1427 GENERATED_LENGTH="${PREV_GENERATED_LENGTH}" 1428 End 1429 1430 It 'displays usage when called with too many arguments' 1431 PROGRAM=prg 1432 When run cmd_random 2 '[:digit:]' extra 1433 The status should equal 1 1434 The output should be blank 1435 The error should equal 'Usage: prg random [pass-length [character-set]]' 1436 End 1437 End 1438 1439 Describe 'cmd_reencrypt' 1440 usage_text() { %text 1441 #|Usage: prg reencrypt [--interactive,-i] pass-name|subfolder ... 1442 } 1443 1444 It 'reencrypts a single file' 1445 When call cmd_reencrypt stale 1446 The status should be success 1447 The error should be blank 1448 The output should be blank 1449 expected_file() { %text 1450 #|ageRecipient:myself 1451 #|age:0-password 1452 } 1453 The contents of file "${PREFIX}/stale.age" \ 1454 should equal "$(expected_file)" 1455 expected_log() { %text 1456 #|Re-encrypt stale 1457 #| 1458 #| stale.age | 1 - 1459 #| 1 file changed, 1 deletion(-) 1460 setup_log 1461 } 1462 The result of function check_git_log should be successful 1463 End 1464 1465 It 'reencrypts a single file interactively' 1466 Data 'y' 1467 When call cmd_reencrypt -i stale 1468 The status should be success 1469 The error should be blank 1470 The output should equal 'Re-encrypt stale? [y/n]' 1471 expected_file() { %text 1472 #|ageRecipient:myself 1473 #|age:0-password 1474 } 1475 The contents of file "${PREFIX}/stale.age" \ 1476 should equal "$(expected_file)" 1477 expected_log() { %text 1478 #|Re-encrypt stale 1479 #| 1480 #| stale.age | 1 - 1481 #| 1 file changed, 1 deletion(-) 1482 setup_log 1483 } 1484 The result of function check_git_log should be successful 1485 End 1486 1487 It 'does not reencrypt a single file when interactively refused' 1488 Data 'n' 1489 When call cmd_reencrypt --interactive stale 1490 The status should be success 1491 The error should be blank 1492 The output should equal 'Re-encrypt stale? [y/n]' 1493 expected_file() { %text 1494 #|ageRecipient:master 1495 #|ageRecipient:myself 1496 #|age:0-password 1497 } 1498 The contents of file "${PREFIX}/stale.age" \ 1499 should equal "$(expected_file)" 1500 The result of function check_git_log should be successful 1501 End 1502 1503 It 'reencrypts a directory recursively' 1504 When call cmd_reencrypt / 1505 The status should be success 1506 The error should be blank 1507 The output should be blank 1508 expected_file() { %text 1509 #|ageRecipient:myself 1510 #|age:0-password 1511 } 1512 The contents of file "${PREFIX}/stale.age" \ 1513 should equal "$(expected_file)" 1514 expected_log() { %text 1515 #|Re-encrypt / 1516 #| 1517 #| stale.age | 1 - 1518 #| 1 file changed, 1 deletion(-) 1519 setup_log 1520 } 1521 The result of function check_git_log should be successful 1522 End 1523 1524 It 'reencrypts a directory recursively and interactively' 1525 Data 1526 #|n 1527 #|y 1528 #|n 1529 End 1530 When call cmd_reencrypt -i '' 1531 The status should be success 1532 The error should be blank 1533 The output should equal 'Re-encrypt extra/subdir/file? [y/n]Re-encrypt stale? [y/n]Re-encrypt subdir/file? [y/n]' 1534 expected_file() { %text 1535 #|ageRecipient:myself 1536 #|age:0-password 1537 } 1538 The contents of file "${PREFIX}/stale.age" \ 1539 should equal "$(expected_file)" 1540 expected_log() { %text 1541 #|Re-encrypt / 1542 #| 1543 #| stale.age | 1 - 1544 #| 1 file changed, 1 deletion(-) 1545 setup_log 1546 } 1547 The result of function check_git_log should be successful 1548 End 1549 1550 It 'fails to reencrypt a file named like a flag without escape' 1551 PROGRAM=prg 1552 When run cmd_reencrypt -g 1553 The status should equal 1 1554 The error should equal "$(usage_text)" 1555 The output should be blank 1556 The result of function check_git_log should be successful 1557 End 1558 1559 It 'fails to reencrypt a non-existent direcotry' 1560 When run cmd_reencrypt -- -y/ 1561 The status should equal 1 1562 The error should equal 'Error: -y/ is not in the password store.' 1563 The output should be blank 1564 The result of function check_git_log should be successful 1565 End 1566 1567 It 'fails to reencrypt a non-existent file' 1568 When run cmd_reencrypt -- -y 1569 The status should equal 1 1570 The error should equal 'Error: -y is not in the password store.' 1571 The output should be blank 1572 The result of function check_git_log should be successful 1573 End 1574 1575 It 'rejects a path containing ..' 1576 When run cmd_reencrypt fluff/../stale 1577 The status should equal 1 1578 The output should be blank 1579 The error should include 'sneaky' 1580 The result of function check_git_log should be successful 1581 End 1582 1583 It 'aborts on age decryption failure even without pipefail' 1584 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 1585 Set 'pipefail:off' 1586 fi 1587 AGE=false 1588 When run cmd_reencrypt stale 1589 The status should equal 1 1590 The error should equal \ 1591 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- ${PREFIX}/stale.age" 1592 The output should be blank 1593 The result of function check_git_log should be successful 1594 End 1595 End 1596 1597 Describe 'cmd_usage' 1598 It 'defaults to four-space indentation' 1599 PROGRAM=prg 1600 When call cmd_usage 1601 The status should be success 1602 The error should be blank 1603 The output should equal "$(cmd_usage ' ')" 1604 End 1605 1606 It 'fails with an unknown command' 1607 PROGRAM=prg 1608 When run cmd_usage 'Usage: ' bad version 1609 The status should equal 1 1610 The output should be blank 1611 The error should equal 'cmd_usage: unknown command "bad"' 1612 End 1613 End 1614 1615 # Describe 'cmd_version' is not needed (fully covered in pass_spec.sh) 1616 1617 Describe 'refuse to operate on dirty checkout:' 1618 make_dirty() { 1619 %putsn 'untracked data' >"${PREFIX}/untracked.txt" 1620 } 1621 BeforeEach make_dirty 1622 1623 git_log() { 1624 @rm -f "${PREFIX}/untracked.txt" 1625 @git -C "${PREFIX}" status --porcelain >&2 1626 @git -C "${PREFIX}" log --format='%s' --stat >|"${GITLOG}" 1627 } 1628 1629 # 'copy' relies on 'copy/move' 1630 1631 Example 'copy/move' 1632 When run cmd_copy_move stale subdir/ 1633 The status should equal 1 1634 The error should equal 'There are already pending changes.' 1635 The output should be blank 1636 The result of function check_git_log should be successful 1637 End 1638 1639 Example 'delete' 1640 When run cmd_delete -f stale 1641 The status should equal 1 1642 The error should equal 'There are already pending changes.' 1643 The output should equal 'Removing stale' 1644 The result of function check_git_log should be successful 1645 End 1646 1647 Example 'edit' 1648 VISUAL='false' 1649 When run cmd_edit subdir/file 1650 The status should equal 1 1651 The error should equal 'There are already pending changes.' 1652 The output should be blank 1653 The result of function check_git_log should be successful 1654 End 1655 1656 # 'find' does not change the repository 1657 1658 Example 'generate' 1659 When run cmd_generate new-pass 1660 The status should equal 1 1661 The error should equal 'There are already pending changes.' 1662 The output should be blank 1663 The result of function check_git_log should be successful 1664 End 1665 1666 # 'git' does not change directly the repository 1667 1668 Example 'gitconfig' 1669 When run cmd_gitconfig 1670 The status should equal 1 1671 The error should equal 'There are already pending changes.' 1672 The output should be blank 1673 The result of function check_git_log should be successful 1674 End 1675 1676 # 'grep' does not change the repository 1677 # 'help' does not change the repository 1678 1679 Example 'init' 1680 When run cmd_init -p subdir/ new-id 1681 The status should equal 1 1682 The error should equal 'There are already pending changes.' 1683 The output should be blank 1684 The result of function check_git_log should be successful 1685 End 1686 1687 Example 'init (deinit)' 1688 When run cmd_init -p fluff/ '' 1689 The status should equal 1 1690 The error should equal 'There are already pending changes.' 1691 The output should be blank 1692 The result of function check_git_log should be successful 1693 End 1694 1695 Example 'insert' 1696 When run cmd_insert -e fluff/four 1697 The status should equal 1 1698 The error should equal 'There are already pending changes.' 1699 The output should be blank 1700 The result of function check_git_log should be successful 1701 End 1702 1703 # 'list_or_show' does not change the repository 1704 # 'move' relies on 'copy/move' 1705 # 'random' does not change the repository 1706 1707 Example 'reencrypt' 1708 When run cmd_reencrypt stale 1709 The status should equal 1 1710 The error should equal 'There are already pending changes.' 1711 The output should be blank 1712 The result of function check_git_log should be successful 1713 End 1714 1715 # 'usage' does not change the repository 1716 # 'version' does not change the repository 1717 End 1718 1719 Describe 'unreachable defensive code' 1720 # This sections breaks the end-to-end scheme of this file 1721 # to reach full coverage, by precisely identifying unreachable lines 1722 # written for defensive programming against internal inconsistencies. 1723 1724 It 'includes invalid values of DECISION in do_copy_move_file' 1725 DECISION='invalid' 1726 When run do_copy_move_file subdir/file.age extra/file.age 1727 The status should equal 1 1728 The output should be blank 1729 The error should equal 'Unexpected DECISION value "invalid"' 1730 End 1731 1732 It 'includes overwriting a file using do_encrypt' 1733 OVERWRITE=no 1734 When run do_encrypt 'y.txt' 1735 The status should equal 1 1736 The output should be blank 1737 The error should equal 'Refusing to overwite y.txt' 1738 End 1739 1740 It 'includes invalid values of SHOW in do_show' 1741 SHOW='invalid' 1742 When run do_show 1743 The status should equal 1 1744 The output should be blank 1745 expected_err() { %text 1746 #|Usage: prg [show] [--clip[=line-number],-c[line-number] | 1747 #| --qrcode[=line-number],-q[line-number]] pass-name 1748 } 1749 The error should equal 'Unexpected SHOW value "invalid"' 1750 End 1751 1752 It 'includes interactive yesno' 1753 # Technically not unreachable, but not worse than faking a terminal 1754 # for each call of `yesno` when the whole test suite is outside 1755 # of terminal anyway 1756 1757 stty() { true; } 1758 Data 1759 #|x 1760 #|Y 1761 End 1762 When call yesno 'Prompt?' 1763 The status should be success 1764 The error should be blank 1765 The output should equal 'Prompt? [y/n]' 1766 The variable ANSWER should equal 'y' 1767 End 1768 End 1769 End