pashage_extra_spec.sh (56365B)
1 # pashage - age-backed POSIX password manager 2 # Copyright (C) 2024 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 End 608 609 Describe 'cmd_generate' 610 It 'uses the character set given explicitly instead of environment' 611 CHARACTER_SET='[0-9]' 612 CHARACTER_SET_NO_SYMBOLS='[0-9]' 613 When call cmd_generate new 5 '[:upper:]' 614 The status should be success 615 The error should be blank 616 The lines of output should equal 2 617 The line 1 of output should \ 618 equal '(B)The generated password for (U)new(!U) is:(N)' 619 The line 2 of output should match pattern '[A-Z][A-Z][A-Z][A-Z][A-Z]' 620 expected_log() { %text 621 #|Add generated password for new. 622 #| 623 #| new.age | 2 ++ 624 #| 1 file changed, 2 insertions(+) 625 setup_log 626 } 627 The result of function check_git_log should be successful 628 End 629 630 It 'overwrites after asking for confirmation' 631 Data 'y' 632 When call cmd_generate subdir/file 10 633 The status should be success 634 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)' 635 The error should be blank 636 expected_log() { %text 637 #|Add generated password for subdir/file. 638 #| 639 #| subdir/file.age | 2 +- 640 #| 1 file changed, 1 insertion(+), 1 deletion(-) 641 setup_log 642 } 643 The result of function check_git_log should be successful 644 End 645 646 It 'does nothing without confirmation' 647 Data 'n' 648 When call cmd_generate subdir/file 10 649 The status should be success 650 The output should equal \ 651 'An entry already exists for subdir/file. Overwrite it? [y/n]' 652 The error should be blank 653 The result of function check_git_log should be successful 654 End 655 656 It 'cannot overwrite a directory' 657 run_test() { 658 mkdir -p "${PREFIX}/new-secret.age" && \ 659 cmd_generate -f new-secret 10 660 } 661 When run run_test 662 The status should equal 1 663 The output should be blank 664 The error should equal 'Cannot replace directory new-secret.age' 665 The result of function check_git_log should be successful 666 End 667 668 It 'aborts on decryption failure even without pipefail' 669 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 670 Set 'pipefail:off' 671 fi 672 AGE=false 673 tail() { @tail "$@"; } 674 When run cmd_generate --in-place stale 675 The status should equal 1 676 The error should equal \ 677 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- ${PREFIX}/stale.age" 678 The output should equal 'Decrypting previous secret for stale' 679 The result of function check_git_log should be successful 680 End 681 682 It 'saves after showing and getting confirmation' 683 Data 'y' 684 When call cmd_generate --try new 685 The status should be success 686 The error should be blank 687 The file "${PREFIX}/new.age" should be exist 688 expected_out() { 689 %putsn '(B)The generated password for (U)new(!U) is:(N)' 690 @sed '$s/^age://p;d' "${PREFIX}/new.age" 691 %putsn 'Save generated password for new? [y/n]' 692 } 693 The output should equal "$(expected_out)" 694 expected_log() { %text 695 #|Add generated password for new. 696 #| 697 #| new.age | 2 ++ 698 #| 1 file changed, 2 insertions(+) 699 setup_log 700 } 701 The result of function check_git_log should be successful 702 End 703 704 It 'does not save after showing and getting cancellation' 705 Data 'n' 706 When call cmd_generate --try new 5 '[:lower:]' 707 The status should be success 708 The error should be blank 709 The lines of output should equal 3 710 The line 1 of output should \ 711 equal '(B)The generated password for (U)new(!U) is:(N)' 712 The line 2 of output should match pattern '[a-z][a-z][a-z][a-z][a-z]' 713 The line 3 of output should \ 714 equal 'Save generated password for new? [y/n]' 715 The result of function check_git_log should be successful 716 End 717 718 It 'accepts an extra line after the generated secret' 719 Data 'extra comment line' 720 When call cmd_generate --multiline new 15 721 The status should be success 722 The error should be blank 723 The lines of output should equal 3 724 The line 1 of output should \ 725 equal 'Enter extra secrets then Ctrl+D when finished:' 726 The line 2 of output should \ 727 equal '(B)The generated password for (U)new(!U) is:(N)' 728 The line 3 of output should match pattern '???????????????' 729 The lines of contents of file "${PREFIX}/new.age" should equal 3 730 The line 3 of contents of file "${PREFIX}/new.age" should \ 731 equal 'age:extra comment line' 732 expected_log() { %text 733 #|Add generated password for new. 734 #| 735 #| new.age | 3 +++ 736 #| 1 file changed, 3 insertions(+) 737 setup_log 738 } 739 The result of function check_git_log should be successful 740 End 741 742 It 'accepts extra lines after the generated secret when overwriting' 743 Data 744 #|Extra: line 745 #|Extra: end of input 746 End 747 When call cmd_generate --multiline --force fluff/three 5 748 The status should be success 749 The error should be blank 750 The lines of output should equal 3 751 The line 1 of output should \ 752 equal 'Enter extra secrets then Ctrl+D when finished:' 753 The line 2 of output should \ 754 equal '(B)The generated password for (U)fluff/three(!U) is:(N)' 755 The line 3 of output should match pattern '?????' 756 The lines of contents of file "${PREFIX}/fluff/three.age" should equal 5 757 The line 4 of contents of file "${PREFIX}/fluff/three.age" should \ 758 equal 'age:Extra: line' 759 The line 5 of contents of file "${PREFIX}/fluff/three.age" should \ 760 equal 'age:Extra: end of input' 761 expected_log() { %text 762 #|Add generated password for fluff/three. 763 #| 764 #| fluff/three.age | 6 +++--- 765 #| 1 file changed, 3 insertions(+), 3 deletions(-) 766 setup_log 767 } 768 The result of function check_git_log should be successful 769 End 770 771 It 'accepts extra lines after the generated secret after in-place data' 772 Data 773 #|Extra: line 774 #|Extra: end of input 775 End 776 When call cmd_generate --multiline --in-place fluff/three 5 777 The status should be success 778 The error should be blank 779 The lines of output should equal 4 780 The line 1 of output should \ 781 equal 'Decrypting previous secret for fluff/three' 782 The line 2 of output should \ 783 equal 'Enter extra secrets then Ctrl+D when finished:' 784 The line 3 of output should \ 785 equal '(B)The generated password for (U)fluff/three(!U) is:(N)' 786 The line 4 of output should match pattern '?????' 787 The lines of contents of file "${PREFIX}/fluff/three.age" should equal 7 788 The line 4 of contents of file "${PREFIX}/fluff/three.age" should \ 789 equal 'age:Username: 3Jane' 790 The line 5 of contents of file "${PREFIX}/fluff/three.age" should \ 791 equal 'age:URL: https://example.com/login' 792 The line 6 of contents of file "${PREFIX}/fluff/three.age" should \ 793 equal 'age:Extra: line' 794 The line 7 of contents of file "${PREFIX}/fluff/three.age" should \ 795 equal 'age:Extra: end of input' 796 expected_log() { %text 797 #|Replace generated password for fluff/three. 798 #| 799 #| fluff/three.age | 4 +++- 800 #| 1 file changed, 3 insertions(+), 1 deletion(-) 801 setup_log 802 } 803 The result of function check_git_log should be successful 804 End 805 End 806 807 Describe 'cmd_git' 808 It 'initializes a clone like a new repository' 809 SOURCE="${PREFIX}" 810 PREFIX="${SHELLSPEC_WORKDIR}/clone" 811 expected_err() { %text:expand 812 #|Cloning into '${PREFIX}'... 813 #|done. 814 } 815 When call cmd_git clone "${SOURCE}" 816 The status should be success 817 The output should be blank 818 The error should equal "$(expected_err)" 819 The file "${PREFIX}/.gitattributes" should be exist 820 The contents of file "${PREFIX}/.gitattributes" should equal \ 821 '*.age diff=age' 822 expected_log() { %text 823 #|Configure git repository for age file diff. 824 #| 825 #| .gitattributes | 1 + 826 #| 1 file changed, 1 insertion(+) 827 setup_log_bin 828 } 829 The result of function check_git_log should be successful 830 PREFIX="${SOURCE}" 831 End 832 End 833 834 Describe 'cmd_grep' 835 It 'aborts on decryption failure even without pipefail' 836 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 837 Set 'pipefail:off' 838 fi 839 grep() { @grep "$@"; } 840 AGE=false 841 When run cmd_grep foo 842 The status should equal 1 843 The error should equal \ 844 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- file.age" 845 The output should be blank 846 The result of function check_git_log should be successful 847 End 848 End 849 850 Describe 'cmd_gitconfig' 851 grep() { @grep "$@"; } 852 853 It 'creates a new .gitattributes and configures diff' 854 When call cmd_gitconfig 855 The status should be success 856 The output should be blank 857 The error should be blank 858 The file "${PREFIX}/.gitattributes" should be exist 859 The contents of file "${PREFIX}/.gitattributes" should equal \ 860 '*.age diff=age' 861 expected_log() { %text 862 #|Configure git repository for age file diff. 863 #| 864 #| .gitattributes | 1 + 865 #| 1 file changed, 1 insertion(+) 866 setup_log_bin 867 } 868 The result of function check_git_log should be successful 869 End 870 871 It 'expands an existing .gitattributes' 872 run_test() { 873 %putsn '# Existing but empty' >"${PREFIX}/.gitattributes" 874 @git -C "${PREFIX}" add .gitattributes >/dev/null 875 @git -C "${PREFIX}" commit -m 'Test case setup' >/dev/null 876 cmd_gitconfig 877 } 878 When call run_test 879 The status should be success 880 The output should be blank 881 The error should be blank 882 expected_file() { %text 883 #|# Existing but empty 884 #|*.age diff=age 885 } 886 The file "${PREFIX}/.gitattributes" should be exist 887 The contents of file "${PREFIX}/.gitattributes" should \ 888 equal "$(expected_file)" 889 expected_log() { %text 890 #|Configure git repository for age file diff. 891 #| 892 #| .gitattributes | 1 + 893 #| 1 file changed, 1 insertion(+) 894 #|Test case setup 895 #| 896 #| .gitattributes | 1 + 897 #| 1 file changed, 1 insertion(+) 898 setup_log_bin 899 } 900 The result of function check_git_log should be successful 901 End 902 903 It 'is idempotent' 904 run_test() { 905 cmd_gitconfig && cmd_gitconfig 906 } 907 When call run_test 908 The status should be success 909 The output should be blank 910 The error should be blank 911 The file "${PREFIX}/.gitattributes" should be exist 912 The contents of file "${PREFIX}/.gitattributes" should equal \ 913 '*.age diff=age' 914 expected_log() { %text 915 #|Configure git repository for age file diff. 916 #| 917 #| .gitattributes | 1 + 918 #| 1 file changed, 1 insertion(+) 919 setup_log_bin 920 } 921 The result of function check_git_log should be successful 922 End 923 End 924 925 Describe 'cmd_help' 926 It 'displays a help text with pashage-specific supported commands' 927 PROGRAM=prg 928 When call cmd_help 929 The status should be success 930 The output should include ' prg copy ' 931 The output should include ' prg delete ' 932 The output should include ' prg gitconfig' 933 The output should include ' prg move ' 934 The output should include ' prg random ' 935 The output should include ' prg reencrypt ' 936 End 937 End 938 939 Describe 'cmd_init' 940 It 're-encrypts the whole store using a recipient ids named like a flag' 941 When call cmd_init -- -p 'new-id' 942 The status should be success 943 The output should equal 'Password store recipients set at store root' 944 The error should be blank 945 expected_file() { %text 946 #|-p 947 #|new-id 948 } 949 The contents of file "${PREFIX}/.age-recipients" should \ 950 equal "$(expected_file)" 951 expected_log() { %text 952 #|Set age recipients at store root 953 #| 954 #| .age-recipients | 2 ++ 955 #| extra/subdir/file.age | 3 ++- 956 #| stale.age | 4 ++-- 957 #| subdir/file.age | 3 ++- 958 #| 4 files changed, 8 insertions(+), 4 deletions(-) 959 setup_log 960 } 961 The result of function check_git_log should be successful 962 End 963 964 It 'does not re-encrypt with `keep` flag' 965 When call cmd_init -k 'new-id' 966 The status should be success 967 The output should equal 'Password store recipients set at store root' 968 The error should be blank 969 The contents of file "${PREFIX}/.age-recipients" should equal 'new-id' 970 expected_log() { %text 971 #|Set age recipients at store root 972 #| 973 #| .age-recipients | 1 + 974 #| 1 file changed, 1 insertion(+) 975 setup_log 976 } 977 The result of function check_git_log should be successful 978 End 979 980 It 'asks before re-encrypting each file with `interactive` flag' 981 Data 982 #|n 983 #|y 984 #|n 985 End 986 When call cmd_init -i 'new-id' 987 The status should be success 988 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' 989 The error should be blank 990 The contents of file "${PREFIX}/.age-recipients" should equal 'new-id' 991 expected_log() { %text 992 #|Set age recipients at store root 993 #| 994 #| .age-recipients | 1 + 995 #| stale.age | 3 +-- 996 #| 2 files changed, 2 insertions(+), 2 deletions(-) 997 setup_log 998 } 999 The result of function check_git_log should be successful 1000 End 1001 1002 usage_text() { %text 1003 #|Usage: prg init [--interactive,-i | --keep,-k ] 1004 #| [--path=subfolder,-p subfolder] age-recipient ... 1005 } 1006 1007 It 'displays usage when using incompatible options (`-i` then `-k`)' 1008 PROGRAM=prg 1009 When run cmd_init --interactive --keep 'new-id' 1010 The status should equal 1 1011 The output should be blank 1012 The error should equal "$(usage_text)" 1013 The result of function check_git_log should be successful 1014 End 1015 1016 It 'displays usage when using incompatible options (`-k` then `-i`)' 1017 PROGRAM=prg 1018 When run cmd_init -ki 'new-id' 1019 The status should equal 1 1020 The output should be blank 1021 The error should equal "$(usage_text)" 1022 The result of function check_git_log should be successful 1023 End 1024 End 1025 1026 Describe 'cmd_insert' 1027 It 'inserts an entry encrypted using an explicit recipient file' 1028 PASHAGE_RECIPIENTS_FILE="${PREFIX}/fluff/.age-recipients" 1029 PASSAGE_RECIPIENTS_FILE="${PREFIX}/shared/.age-recipients" 1030 PASHAGE_RECIPIENTS='shadowed' 1031 PASSAGE_RECIPIENTS='shadowed' 1032 Data 'pass' 1033 When call cmd_insert -e shared/new-file 1034 The status should be success 1035 The output should include 'shared/new-file' 1036 expected_file() { %text:expand 1037 #|ageRecipient:master 1038 #|ageRecipient:myself 1039 #|age:pass 1040 } 1041 The contents of file "${PREFIX}/shared/new-file.age" should \ 1042 equal "$(expected_file)" 1043 expected_log() { %text 1044 #|Add given password for shared/new-file to store. 1045 #| 1046 #| shared/new-file.age | 3 +++ 1047 #| 1 file changed, 3 insertions(+) 1048 setup_log 1049 } 1050 The result of function check_git_log should be successful 1051 End 1052 1053 It 'inserts an entry encrypted using explicit recipients' 1054 PASHAGE_RECIPIENTS='force-1 force-2' 1055 PASSAGE_RECIPIENTS='shadowed' 1056 Data 'pass' 1057 When call cmd_insert -e shared/new-file 1058 The status should be success 1059 The output should include 'shared/new-file' 1060 expected_file() { %text:expand 1061 #|ageRecipient:force-1 1062 #|ageRecipient:force-2 1063 #|age:pass 1064 } 1065 The contents of file "${PREFIX}/shared/new-file.age" should \ 1066 equal "$(expected_file)" 1067 expected_log() { %text 1068 #|Add given password for shared/new-file to store. 1069 #| 1070 #| shared/new-file.age | 3 +++ 1071 #| 1 file changed, 3 insertions(+) 1072 setup_log 1073 } 1074 The result of function check_git_log should be successful 1075 End 1076 1077 It 'inserts several new single-line entries' 1078 stty() { false; } 1079 Data 1080 #|password-1 1081 #|n 1082 #|password-2 1083 #|password-3 1084 End 1085 When call cmd_insert -e newdir/pass-1 subdir/file newdir/pass-2 1086 The status should be success 1087 The error should be blank 1088 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: ' 1089 The contents of file "${PREFIX}/newdir/pass-1.age" \ 1090 should include "age:password-1" 1091 The contents of file "${PREFIX}/newdir/pass-2.age" \ 1092 should include "age:password-2" 1093 expected_log() { %text 1094 #|Add given password for newdir/pass-2 to store. 1095 #| 1096 #| newdir/pass-2.age | 2 ++ 1097 #| 1 file changed, 2 insertions(+) 1098 #|Add given password for newdir/pass-1 to store. 1099 #| 1100 #| newdir/pass-1.age | 2 ++ 1101 #| 1 file changed, 2 insertions(+) 1102 setup_log 1103 } 1104 The result of function check_git_log should be successful 1105 End 1106 1107 It 'inserts several new multi-line entries' 1108 stty() { false; } 1109 Data 1110 #|password-1 1111 #| extra spaced line 1112 #| 1113 #|y 1114 #|password-2 1115 #| extra tabbed line 1116 #| 1117 #|password-3 1118 End 1119 When call cmd_insert -m newdir/pass-1 subdir/file newdir/pass-2 1120 The status should be success 1121 The error should be blank 1122 expected_out() { %text 1123 #|Enter contents of newdir/pass-1 and 1124 #|press Ctrl+D or enter an empty line when finished: 1125 #|An entry already exists for subdir/file. Overwrite it? [y/n]Enter contents of subdir/file and 1126 #|press Ctrl+D or enter an empty line when finished: 1127 #|Enter contents of newdir/pass-2 and 1128 #|press Ctrl+D or enter an empty line when finished: 1129 } 1130 The output should equal "$(expected_out)" 1131 expected_file_1() { %text 1132 #|ageRecipient:myself 1133 #|age:password-1 1134 #|age: extra spaced line 1135 } 1136 expected_file_2() { %text 1137 #|ageRecipient:myself 1138 #|age:password-2 1139 #|age: extra tabbed line 1140 } 1141 expected_file_3() { %text 1142 #|ageRecipient:myself 1143 #|age:password-3 1144 } 1145 The contents of file "${PREFIX}/newdir/pass-1.age" \ 1146 should equal "$(expected_file_1)" 1147 The contents of file "${PREFIX}/subdir/file.age" \ 1148 should equal "$(expected_file_2)" 1149 The contents of file "${PREFIX}/newdir/pass-2.age" \ 1150 should equal "$(expected_file_3)" 1151 expected_log() { %text 1152 #|Add given password for newdir/pass-2 to store. 1153 #| 1154 #| newdir/pass-2.age | 2 ++ 1155 #| 1 file changed, 2 insertions(+) 1156 #|Add given password for subdir/file to store. 1157 #| 1158 #| subdir/file.age | 3 ++- 1159 #| 1 file changed, 2 insertions(+), 1 deletion(-) 1160 #|Add given password for newdir/pass-1 to store. 1161 #| 1162 #| newdir/pass-1.age | 3 +++ 1163 #| 1 file changed, 3 insertions(+) 1164 setup_log 1165 } 1166 The result of function check_git_log should be successful 1167 End 1168 1169 It 'inserts a new single-line entry on the second try' 1170 stty() { :; } 1171 Data 1172 #|first try 1173 #|First Try 1174 #|pass-word 1175 #|pass-word 1176 End 1177 When call cmd_insert newdir/newpass 1178 The status should be success 1179 The error should be blank 1180 expected_out() { %text | @sed 's/\$$//' 1181 #|Enter password for newdir/newpass: $ 1182 #|Retype password for newdir/newpass: $ 1183 #|Passwords don't match$ 1184 #|Enter password for newdir/newpass: $ 1185 #|Retype password for newdir/newpass: $ 1186 } 1187 The output should equal "$(expected_out)" 1188 The contents of file "${PREFIX}/newdir/newpass.age" \ 1189 should include "age:pass-word" 1190 expected_log() { %text 1191 #|Add given password for newdir/newpass to store. 1192 #| 1193 #| newdir/newpass.age | 2 ++ 1194 #| 1 file changed, 2 insertions(+) 1195 setup_log 1196 } 1197 The result of function check_git_log should be successful 1198 End 1199 1200 It 'overwrites an entry after confirmation' 1201 Data 1202 #|y 1203 #|pass-word 1204 End 1205 When call cmd_insert -e subdir/file 1206 The status should be success 1207 The error should be blank 1208 The output should equal 'An entry already exists for subdir/file. Overwrite it? [y/n]Enter password for subdir/file: ' 1209 expected_file() { %text 1210 #|ageRecipient:myself 1211 #|age:pass-word 1212 } 1213 The contents of file "${PREFIX}/subdir/file.age" \ 1214 should equal "$(expected_file)" 1215 expected_log() { %text 1216 #|Add given password for subdir/file to store. 1217 #| 1218 #| subdir/file.age | 2 +- 1219 #| 1 file changed, 1 insertion(+), 1 deletion(-) 1220 setup_log 1221 } 1222 The result of function check_git_log should be successful 1223 End 1224 1225 It 'does not overwrite an entry without confirmation' 1226 Data 1227 #|n 1228 #|pass-word 1229 End 1230 When call cmd_insert -e subdir/file 1231 The status should be success 1232 The error should be blank 1233 The output should equal \ 1234 'An entry already exists for subdir/file. Overwrite it? [y/n]' 1235 The result of function check_git_log should be successful 1236 End 1237 End 1238 1239 Describe 'cmd_list_or_show' 1240 It 'decrypts a GPG secret in the store using GPG' 1241 GPG=mock-gpg 1242 gpg() { false; } 1243 gpg2() { false; } 1244 When call cmd_list_or_show old 1245 The status should be success 1246 The error should be blank 1247 expected_out() { %text 1248 #|very-old-password 1249 #|Username: previous-life 1250 } 1251 The output should equal "$(expected_out)" 1252 End 1253 1254 It 'decrypts a GPG secret in the store using gpg2' 1255 unset GPG 1256 gpg() { false; } 1257 gpg2() { 1258 [ $# -eq 9 ] && [ "$6" = '--batch' ] && [ "$7" = '--use-agent' ] \ 1259 && mock-gpg "$1" "$2" "$3" "$4" "$5" "$8" "$9" 1260 } 1261 When call cmd_list_or_show old 1262 The status should be success 1263 The error should be blank 1264 expected_out() { %text 1265 #|very-old-password 1266 #|Username: previous-life 1267 } 1268 The output should equal "$(expected_out)" 1269 End 1270 1271 It 'decrypts a GPG secret in the store using gpg' 1272 unset GPG 1273 gpg() { mock-gpg "$@"; } 1274 When call cmd_list_or_show old 1275 The status should be success 1276 The error should be blank 1277 expected_out() { %text 1278 #|very-old-password 1279 #|Username: previous-life 1280 } 1281 The output should equal "$(expected_out)" 1282 End 1283 1284 It 'fails to decrypt a GPG secret without gpg' 1285 unset GPG 1286 When run cmd_list_or_show old 1287 The status should equal 1 1288 The error should equal 'GPG does not seem available' 1289 The output should be blank 1290 End 1291 1292 It 'displays both list and show usage on parse error with ambiguity' 1293 PROGRAM=prg 1294 COMMAND=both 1295 When run cmd_list_or_show -x 1296 The status should equal 1 1297 The output should be blank 1298 expected_err() { %text 1299 #|Usage: prg [list] [subfolder] 1300 #| prg [show] [--clip[=line-number],-c[line-number] | 1301 #| --qrcode[=line-number],-q[line-number]] pass-name 1302 } 1303 The error should equal "$(expected_err)" 1304 End 1305 1306 It 'displays list usage on parse error with list command' 1307 PROGRAM=prg 1308 COMMAND=list 1309 When run cmd_list_or_show -x 1310 The status should equal 1 1311 The output should be blank 1312 expected_err() { %text 1313 #|Usage: prg [list] [subfolder] 1314 } 1315 The error should equal "$(expected_err)" 1316 End 1317 1318 It 'displays show usage on parse error with show command' 1319 PROGRAM=prg 1320 COMMAND=show 1321 When run cmd_list_or_show -x 1322 The status should equal 1 1323 The output should be blank 1324 expected_err() { %text 1325 #|Usage: prg [show] [--clip[=line-number],-c[line-number] | 1326 #| --qrcode[=line-number],-q[line-number]] pass-name 1327 } 1328 The error should equal "$(expected_err)" 1329 End 1330 1331 It 'aborts on age decryption failure even without pipefail' 1332 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 1333 Set 'pipefail:off' 1334 fi 1335 AGE=false 1336 When run cmd_list_or_show stale 1337 The status should equal 1 1338 The error should equal \ 1339 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- ${PREFIX}/stale.age" 1340 The output should be blank 1341 The result of function check_git_log should be successful 1342 End 1343 1344 It 'aborts on gpg decryption failure even without pipefail' 1345 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 1346 Set 'pipefail:off' 1347 fi 1348 GPG=false 1349 When run cmd_list_or_show old 1350 The status should equal 1 1351 The error should equal "Fatal(1): false -d --quiet --yes --compress-algo=none --no-encrypt-to -- ${PREFIX}/old.gpg" 1352 The output should be blank 1353 The result of function check_git_log should be successful 1354 End 1355 End 1356 1357 # Describe 'cmd_move' is not needed (covered by 'cmd_copy_move') 1358 1359 Describe 'cmd_random' 1360 It 'generates random characters' 1361 When call cmd_random 2 '[:digit:]' 1362 The status should be success 1363 The error should be blank 1364 The output should match pattern '[0-9][0-9]' 1365 End 1366 1367 It 'defaults to using CHARACTER_SET' 1368 PREV_CHARACTER_SET="${CHARACTER_SET}" 1369 CHARACTER_SET='[:lower:]' 1370 When call cmd_random 2 1371 The status should be success 1372 The error should be blank 1373 The output should match pattern '[a-z][a-z]' 1374 CHARACTER_SET="${PREV_CHARACTER_SET}" 1375 End 1376 1377 It 'defaults to using both GENERATED_LENGTH and CHARACTER_SET' 1378 PREV_CHARACTER_SET="${CHARACTER_SET}" 1379 PREV_GENERATED_LENGTH="${GENERATED_LENGTH}" 1380 CHARACTER_SET='[:upper:]' 1381 GENERATED_LENGTH=5 1382 When call cmd_random 1383 The status should be success 1384 The error should be blank 1385 The output should match pattern '[A-Z][A-Z][A-Z][A-Z][A-Z]' 1386 CHARACTER_SET="${PREV_CHARACTER_SET}" 1387 GENERATED_LENGTH="${PREV_GENERATED_LENGTH}" 1388 End 1389 1390 It 'displays usage when called with too many arguments' 1391 PROGRAM=prg 1392 When run cmd_random 2 '[:digit:]' extra 1393 The status should equal 1 1394 The output should be blank 1395 The error should equal 'Usage: prg random [pass-length [character-set]]' 1396 End 1397 End 1398 1399 Describe 'cmd_reencrypt' 1400 usage_text() { %text 1401 #|Usage: prg reencrypt [--interactive,-i] pass-name|subfolder ... 1402 } 1403 1404 It 'reencrypts a single file' 1405 When call cmd_reencrypt stale 1406 The status should be success 1407 The error should be blank 1408 The output should be blank 1409 expected_file() { %text 1410 #|ageRecipient:myself 1411 #|age:0-password 1412 } 1413 The contents of file "${PREFIX}/stale.age" \ 1414 should equal "$(expected_file)" 1415 expected_log() { %text 1416 #|Re-encrypt stale 1417 #| 1418 #| stale.age | 1 - 1419 #| 1 file changed, 1 deletion(-) 1420 setup_log 1421 } 1422 The result of function check_git_log should be successful 1423 End 1424 1425 It 'reencrypts a single file interactively' 1426 Data 'y' 1427 When call cmd_reencrypt -i stale 1428 The status should be success 1429 The error should be blank 1430 The output should equal 'Re-encrypt stale? [y/n]' 1431 expected_file() { %text 1432 #|ageRecipient:myself 1433 #|age:0-password 1434 } 1435 The contents of file "${PREFIX}/stale.age" \ 1436 should equal "$(expected_file)" 1437 expected_log() { %text 1438 #|Re-encrypt stale 1439 #| 1440 #| stale.age | 1 - 1441 #| 1 file changed, 1 deletion(-) 1442 setup_log 1443 } 1444 The result of function check_git_log should be successful 1445 End 1446 1447 It 'does not reencrypt a single file when interactively refused' 1448 Data 'n' 1449 When call cmd_reencrypt --interactive stale 1450 The status should be success 1451 The error should be blank 1452 The output should equal 'Re-encrypt stale? [y/n]' 1453 expected_file() { %text 1454 #|ageRecipient:master 1455 #|ageRecipient:myself 1456 #|age:0-password 1457 } 1458 The contents of file "${PREFIX}/stale.age" \ 1459 should equal "$(expected_file)" 1460 The result of function check_git_log should be successful 1461 End 1462 1463 It 'reencrypts a directory recursively' 1464 When call cmd_reencrypt / 1465 The status should be success 1466 The error should be blank 1467 The output should be blank 1468 expected_file() { %text 1469 #|ageRecipient:myself 1470 #|age:0-password 1471 } 1472 The contents of file "${PREFIX}/stale.age" \ 1473 should equal "$(expected_file)" 1474 expected_log() { %text 1475 #|Re-encrypt / 1476 #| 1477 #| stale.age | 1 - 1478 #| 1 file changed, 1 deletion(-) 1479 setup_log 1480 } 1481 The result of function check_git_log should be successful 1482 End 1483 1484 It 'reencrypts a directory recursively and interactively' 1485 Data 1486 #|n 1487 #|y 1488 #|n 1489 End 1490 When call cmd_reencrypt -i '' 1491 The status should be success 1492 The error should be blank 1493 The output should equal 'Re-encrypt extra/subdir/file? [y/n]Re-encrypt stale? [y/n]Re-encrypt subdir/file? [y/n]' 1494 expected_file() { %text 1495 #|ageRecipient:myself 1496 #|age:0-password 1497 } 1498 The contents of file "${PREFIX}/stale.age" \ 1499 should equal "$(expected_file)" 1500 expected_log() { %text 1501 #|Re-encrypt / 1502 #| 1503 #| stale.age | 1 - 1504 #| 1 file changed, 1 deletion(-) 1505 setup_log 1506 } 1507 The result of function check_git_log should be successful 1508 End 1509 1510 It 'fails to reencrypt a file named like a flag without escape' 1511 PROGRAM=prg 1512 When run cmd_reencrypt -g 1513 The status should equal 1 1514 The error should equal "$(usage_text)" 1515 The output should be blank 1516 The result of function check_git_log should be successful 1517 End 1518 1519 It 'fails to reencrypt a non-existent direcotry' 1520 When run cmd_reencrypt -- -y/ 1521 The status should equal 1 1522 The error should equal 'Error: -y/ is not in the password store.' 1523 The output should be blank 1524 The result of function check_git_log should be successful 1525 End 1526 1527 It 'fails to reencrypt a non-existent file' 1528 When run cmd_reencrypt -- -y 1529 The status should equal 1 1530 The error should equal 'Error: -y is not in the password store.' 1531 The output should be blank 1532 The result of function check_git_log should be successful 1533 End 1534 1535 It 'rejects a path containing ..' 1536 When run cmd_reencrypt fluff/../stale 1537 The status should equal 1 1538 The output should be blank 1539 The error should include 'sneaky' 1540 The result of function check_git_log should be successful 1541 End 1542 1543 It 'aborts on age decryption failure even without pipefail' 1544 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 1545 Set 'pipefail:off' 1546 fi 1547 AGE=false 1548 When run cmd_reencrypt stale 1549 The status should equal 1 1550 The error should equal \ 1551 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- ${PREFIX}/stale.age" 1552 The output should be blank 1553 The result of function check_git_log should be successful 1554 End 1555 End 1556 1557 Describe 'cmd_usage' 1558 It 'defaults to four-space indentation' 1559 PROGRAM=prg 1560 When call cmd_usage 1561 The status should be success 1562 The error should be blank 1563 The output should equal "$(cmd_usage ' ')" 1564 End 1565 1566 It 'fails with an unknown command' 1567 PROGRAM=prg 1568 When run cmd_usage 'Usage: ' bad version 1569 The status should equal 1 1570 The output should be blank 1571 The error should equal 'cmd_usage: unknown command "bad"' 1572 End 1573 End 1574 1575 # Describe 'cmd_version' is not needed (fully covered in pass_spec.sh) 1576 1577 Describe 'refuse to operate on dirty checkout:' 1578 make_dirty() { 1579 %putsn 'untracked data' >"${PREFIX}/untracked.txt" 1580 } 1581 BeforeEach make_dirty 1582 1583 git_log() { 1584 @rm -f "${PREFIX}/untracked.txt" 1585 @git -C "${PREFIX}" status --porcelain >&2 1586 @git -C "${PREFIX}" log --format='%s' --stat >|"${GITLOG}" 1587 } 1588 1589 # 'copy' relies on 'copy/move' 1590 1591 Example 'copy/move' 1592 When run cmd_copy_move stale subdir/ 1593 The status should equal 1 1594 The error should equal 'There are already pending changes.' 1595 The output should be blank 1596 The result of function check_git_log should be successful 1597 End 1598 1599 Example 'delete' 1600 When run cmd_delete -f stale 1601 The status should equal 1 1602 The error should equal 'There are already pending changes.' 1603 The output should equal 'Removing stale' 1604 The result of function check_git_log should be successful 1605 End 1606 1607 Example 'edit' 1608 VISUAL='false' 1609 When run cmd_edit subdir/file 1610 The status should equal 1 1611 The error should equal 'There are already pending changes.' 1612 The output should be blank 1613 The result of function check_git_log should be successful 1614 End 1615 1616 # 'find' does not change the repository 1617 1618 Example 'generate' 1619 When run cmd_generate new-pass 1620 The status should equal 1 1621 The error should equal 'There are already pending changes.' 1622 The output should be blank 1623 The result of function check_git_log should be successful 1624 End 1625 1626 # 'git' does not change directly the repository 1627 1628 Example 'gitconfig' 1629 When run cmd_gitconfig 1630 The status should equal 1 1631 The error should equal 'There are already pending changes.' 1632 The output should be blank 1633 The result of function check_git_log should be successful 1634 End 1635 1636 # 'grep' does not change the repository 1637 # 'help' does not change the repository 1638 1639 Example 'init' 1640 When run cmd_init -p subdir/ new-id 1641 The status should equal 1 1642 The error should equal 'There are already pending changes.' 1643 The output should be blank 1644 The result of function check_git_log should be successful 1645 End 1646 1647 Example 'init (deinit)' 1648 When run cmd_init -p fluff/ '' 1649 The status should equal 1 1650 The error should equal 'There are already pending changes.' 1651 The output should be blank 1652 The result of function check_git_log should be successful 1653 End 1654 1655 Example 'insert' 1656 When run cmd_insert -e fluff/four 1657 The status should equal 1 1658 The error should equal 'There are already pending changes.' 1659 The output should be blank 1660 The result of function check_git_log should be successful 1661 End 1662 1663 # 'list_or_show' does not change the repository 1664 # 'move' relies on 'copy/move' 1665 # 'random' does not change the repository 1666 1667 Example 'reencrypt' 1668 When run cmd_reencrypt stale 1669 The status should equal 1 1670 The error should equal 'There are already pending changes.' 1671 The output should be blank 1672 The result of function check_git_log should be successful 1673 End 1674 1675 # 'usage' does not change the repository 1676 # 'version' does not change the repository 1677 End 1678 1679 Describe 'unreachable defensive code' 1680 # This sections breaks the end-to-end scheme of this file 1681 # to reach full coverage, by precisely identifying unreachable lines 1682 # written for defensive programming against internal inconsistencies. 1683 1684 It 'includes invalid values of DECISION in do_copy_move_file' 1685 DECISION='invalid' 1686 When run do_copy_move_file subdir/file.age extra/file.age 1687 The status should equal 1 1688 The output should be blank 1689 The error should equal 'Unexpected DECISION value "invalid"' 1690 End 1691 1692 It 'includes overwriting a file using do_encrypt' 1693 OVERWRITE=no 1694 When run do_encrypt 'y.txt' 1695 The status should equal 1 1696 The output should be blank 1697 The error should equal 'Refusing to overwite y.txt' 1698 End 1699 1700 It 'includes invalid values of SHOW in do_show' 1701 SHOW='invalid' 1702 When run do_show 1703 The status should equal 1 1704 The output should be blank 1705 expected_err() { %text 1706 #|Usage: prg [show] [--clip[=line-number],-c[line-number] | 1707 #| --qrcode[=line-number],-q[line-number]] pass-name 1708 } 1709 The error should equal 'Unexpected SHOW value "invalid"' 1710 End 1711 1712 It 'includes interactive yesno' 1713 # Technically not unreachable, but not worse than faking a terminal 1714 # for each call of `yesno` when the whole test suite is outside 1715 # of terminal anyway 1716 1717 stty() { true; } 1718 Data 1719 #|x 1720 #|Y 1721 End 1722 When call yesno 'Prompt?' 1723 The status should be success 1724 The error should be blank 1725 The output should equal 'Prompt? [y/n]' 1726 The variable ANSWER should equal 'y' 1727 End 1728 End 1729 End