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