pashage_extra_spec.sh (58343B)
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(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} -- 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 End 974 975 Describe 'cmd_init' 976 It 're-encrypts the whole store using a recipient ids named like a flag' 977 When call cmd_init -- -p 'new-id' 978 The status should be success 979 The output should equal 'Password store recipients set at store root' 980 The error should be blank 981 expected_file() { %text 982 #|-p 983 #|new-id 984 } 985 The contents of file "${PREFIX}/.age-recipients" should \ 986 equal "$(expected_file)" 987 expected_log() { %text 988 #|Set age recipients at store root 989 #| 990 #| .age-recipients | 2 ++ 991 #| extra/subdir/file.age | 3 ++- 992 #| stale.age | 4 ++-- 993 #| subdir/file.age | 3 ++- 994 #| 4 files changed, 8 insertions(+), 4 deletions(-) 995 setup_log 996 } 997 The result of function check_git_log should be successful 998 End 999 1000 It 'does not re-encrypt with `keep` flag' 1001 When call cmd_init -k 'new-id' 1002 The status should be success 1003 The output should equal 'Password store recipients set at store root' 1004 The error should be blank 1005 The contents of file "${PREFIX}/.age-recipients" should equal 'new-id' 1006 expected_log() { %text 1007 #|Set age recipients at store root 1008 #| 1009 #| .age-recipients | 1 + 1010 #| 1 file changed, 1 insertion(+) 1011 setup_log 1012 } 1013 The result of function check_git_log should be successful 1014 End 1015 1016 It 'asks before re-encrypting each file with `interactive` flag' 1017 Data 1018 #|n 1019 #|y 1020 #|n 1021 End 1022 When call cmd_init -i 'new-id' 1023 The status should be success 1024 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' 1025 The error should be blank 1026 The contents of file "${PREFIX}/.age-recipients" should equal 'new-id' 1027 expected_log() { %text 1028 #|Set age recipients at store root 1029 #| 1030 #| .age-recipients | 1 + 1031 #| stale.age | 3 +-- 1032 #| 2 files changed, 2 insertions(+), 2 deletions(-) 1033 setup_log 1034 } 1035 The result of function check_git_log should be successful 1036 End 1037 1038 usage_text() { %text 1039 #|Usage: prg init [--interactive,-i | --keep,-k ] 1040 #| [--path=subfolder,-p subfolder] age-recipient ... 1041 } 1042 1043 It 'displays usage when using incompatible options (`-i` then `-k`)' 1044 PROGRAM=prg 1045 When run cmd_init --interactive --keep 'new-id' 1046 The status should equal 1 1047 The output should be blank 1048 The error should equal "$(usage_text)" 1049 The result of function check_git_log should be successful 1050 End 1051 1052 It 'displays usage when using incompatible options (`-k` then `-i`)' 1053 PROGRAM=prg 1054 When run cmd_init -ki 'new-id' 1055 The status should equal 1 1056 The output should be blank 1057 The error should equal "$(usage_text)" 1058 The result of function check_git_log should be successful 1059 End 1060 End 1061 1062 Describe 'cmd_insert' 1063 It 'inserts an entry encrypted using an explicit recipient file' 1064 PASHAGE_RECIPIENTS_FILE="${PREFIX}/fluff/.age-recipients" 1065 PASSAGE_RECIPIENTS_FILE="${PREFIX}/shared/.age-recipients" 1066 PASHAGE_RECIPIENTS='shadowed' 1067 PASSAGE_RECIPIENTS='shadowed' 1068 Data 'pass' 1069 When call cmd_insert -e shared/new-file 1070 The status should be success 1071 The output should include 'shared/new-file' 1072 expected_file() { %text:expand 1073 #|ageRecipient:master 1074 #|ageRecipient:myself 1075 #|age:pass 1076 } 1077 The contents of file "${PREFIX}/shared/new-file.age" should \ 1078 equal "$(expected_file)" 1079 expected_log() { %text 1080 #|Add given password for shared/new-file to store. 1081 #| 1082 #| shared/new-file.age | 3 +++ 1083 #| 1 file changed, 3 insertions(+) 1084 setup_log 1085 } 1086 The result of function check_git_log should be successful 1087 End 1088 1089 It 'inserts an entry encrypted using explicit recipients' 1090 PASHAGE_RECIPIENTS='force-1 force-2' 1091 PASSAGE_RECIPIENTS='shadowed' 1092 Data 'pass' 1093 When call cmd_insert -e shared/new-file 1094 The status should be success 1095 The output should include 'shared/new-file' 1096 expected_file() { %text:expand 1097 #|ageRecipient:force-1 1098 #|ageRecipient:force-2 1099 #|age:pass 1100 } 1101 The contents of file "${PREFIX}/shared/new-file.age" should \ 1102 equal "$(expected_file)" 1103 expected_log() { %text 1104 #|Add given password for shared/new-file to store. 1105 #| 1106 #| shared/new-file.age | 3 +++ 1107 #| 1 file changed, 3 insertions(+) 1108 setup_log 1109 } 1110 The result of function check_git_log should be successful 1111 End 1112 1113 It 'inserts several new single-line entries' 1114 stty() { false; } 1115 Data 1116 #|password-1 1117 #|n 1118 #|password-2 1119 #|password-3 1120 End 1121 When call cmd_insert -e newdir/pass-1 subdir/file newdir/pass-2 1122 The status should be success 1123 The error should be blank 1124 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: ' 1125 The contents of file "${PREFIX}/newdir/pass-1.age" \ 1126 should include "age:password-1" 1127 The contents of file "${PREFIX}/newdir/pass-2.age" \ 1128 should include "age:password-2" 1129 expected_log() { %text 1130 #|Add given password for newdir/pass-2 to store. 1131 #| 1132 #| newdir/pass-2.age | 2 ++ 1133 #| 1 file changed, 2 insertions(+) 1134 #|Add given password for newdir/pass-1 to store. 1135 #| 1136 #| newdir/pass-1.age | 2 ++ 1137 #| 1 file changed, 2 insertions(+) 1138 setup_log 1139 } 1140 The result of function check_git_log should be successful 1141 End 1142 1143 It 'inserts several new multi-line entries' 1144 stty() { false; } 1145 Data 1146 #|password-1 1147 #| extra spaced line 1148 #| 1149 #|y 1150 #|password-2 1151 #| extra tabbed line 1152 #| 1153 #|password-3 1154 End 1155 When call cmd_insert -m newdir/pass-1 subdir/file newdir/pass-2 1156 The status should be success 1157 The error should be blank 1158 expected_out() { %text 1159 #|Enter contents of newdir/pass-1 and 1160 #|press Ctrl+D or enter an empty line when finished: 1161 #|An entry already exists for subdir/file. Overwrite it? [y/n]Enter contents of subdir/file and 1162 #|press Ctrl+D or enter an empty line when finished: 1163 #|Enter contents of newdir/pass-2 and 1164 #|press Ctrl+D or enter an empty line when finished: 1165 } 1166 The output should equal "$(expected_out)" 1167 expected_file_1() { %text 1168 #|ageRecipient:myself 1169 #|age:password-1 1170 #|age: extra spaced line 1171 } 1172 expected_file_2() { %text 1173 #|ageRecipient:myself 1174 #|age:password-2 1175 #|age: extra tabbed line 1176 } 1177 expected_file_3() { %text 1178 #|ageRecipient:myself 1179 #|age:password-3 1180 } 1181 The contents of file "${PREFIX}/newdir/pass-1.age" \ 1182 should equal "$(expected_file_1)" 1183 The contents of file "${PREFIX}/subdir/file.age" \ 1184 should equal "$(expected_file_2)" 1185 The contents of file "${PREFIX}/newdir/pass-2.age" \ 1186 should equal "$(expected_file_3)" 1187 expected_log() { %text 1188 #|Add given password for newdir/pass-2 to store. 1189 #| 1190 #| newdir/pass-2.age | 2 ++ 1191 #| 1 file changed, 2 insertions(+) 1192 #|Add given password for subdir/file to store. 1193 #| 1194 #| subdir/file.age | 3 ++- 1195 #| 1 file changed, 2 insertions(+), 1 deletion(-) 1196 #|Add given password for newdir/pass-1 to store. 1197 #| 1198 #| newdir/pass-1.age | 3 +++ 1199 #| 1 file changed, 3 insertions(+) 1200 setup_log 1201 } 1202 The result of function check_git_log should be successful 1203 End 1204 1205 It 'inserts a new single-line entry on the second try' 1206 stty() { :; } 1207 Data 1208 #|first try 1209 #|First Try 1210 #|pass-word 1211 #|pass-word 1212 End 1213 When call cmd_insert newdir/newpass 1214 The status should be success 1215 The error should be blank 1216 expected_out() { %text | @sed 's/\$$//' 1217 #|Enter password for newdir/newpass: $ 1218 #|Retype password for newdir/newpass: $ 1219 #|Passwords don't match$ 1220 #|Enter password for newdir/newpass: $ 1221 #|Retype password for newdir/newpass: $ 1222 } 1223 The output should equal "$(expected_out)" 1224 The contents of file "${PREFIX}/newdir/newpass.age" \ 1225 should include "age:pass-word" 1226 expected_log() { %text 1227 #|Add given password for newdir/newpass to store. 1228 #| 1229 #| newdir/newpass.age | 2 ++ 1230 #| 1 file changed, 2 insertions(+) 1231 setup_log 1232 } 1233 The result of function check_git_log should be successful 1234 End 1235 1236 It 'overwrites an entry after confirmation' 1237 Data 1238 #|y 1239 #|pass-word 1240 End 1241 When call cmd_insert -e subdir/file 1242 The status should be success 1243 The error should be blank 1244 The output should equal 'An entry already exists for subdir/file. Overwrite it? [y/n]Enter password for subdir/file: ' 1245 expected_file() { %text 1246 #|ageRecipient:myself 1247 #|age:pass-word 1248 } 1249 The contents of file "${PREFIX}/subdir/file.age" \ 1250 should equal "$(expected_file)" 1251 expected_log() { %text 1252 #|Add given password for subdir/file to store. 1253 #| 1254 #| subdir/file.age | 2 +- 1255 #| 1 file changed, 1 insertion(+), 1 deletion(-) 1256 setup_log 1257 } 1258 The result of function check_git_log should be successful 1259 End 1260 1261 It 'does not overwrite an entry without confirmation' 1262 Data 1263 #|n 1264 #|pass-word 1265 End 1266 When call cmd_insert -e subdir/file 1267 The status should be success 1268 The error should be blank 1269 The output should equal \ 1270 'An entry already exists for subdir/file. Overwrite it? [y/n]' 1271 The result of function check_git_log should be successful 1272 End 1273 End 1274 1275 Describe 'cmd_list_or_show' 1276 It 'displays the whole store as a raw list' 1277 When call cmd_list_or_show --raw 1278 The status should be success 1279 The error should be blank 1280 expected_out() { %text 1281 #|extra/subdir/file 1282 #|extra/subdir.gpg 1283 #|fluff/one 1284 #|fluff/three 1285 #|fluff/two 1286 #|old 1287 #|stale 1288 #|stale.gpg 1289 #|subdir/file 1290 } 1291 The output should equal "$(expected_out)" 1292 End 1293 1294 It 'displays a subdirectory as a raw list' 1295 When call cmd_list_or_show -r fluff 1296 The status should be success 1297 The error should be blank 1298 expected_out() { %text 1299 #|fluff/one 1300 #|fluff/three 1301 #|fluff/two 1302 } 1303 The output should equal "$(expected_out)" 1304 End 1305 1306 It 'decrypts a GPG secret in the store using GPG' 1307 GPG=mock-gpg 1308 gpg() { false; } 1309 gpg2() { false; } 1310 When call cmd_list_or_show old 1311 The status should be success 1312 The error should be blank 1313 expected_out() { %text 1314 #|very-old-password 1315 #|Username: previous-life 1316 } 1317 The output should equal "$(expected_out)" 1318 End 1319 1320 It 'decrypts a GPG secret in the store using gpg2' 1321 unset GPG 1322 gpg() { false; } 1323 gpg2() { 1324 [ $# -eq 9 ] && [ "$6" = '--batch' ] && [ "$7" = '--use-agent' ] \ 1325 && mock-gpg "$1" "$2" "$3" "$4" "$5" "$8" "$9" 1326 } 1327 When call cmd_list_or_show old 1328 The status should be success 1329 The error should be blank 1330 expected_out() { %text 1331 #|very-old-password 1332 #|Username: previous-life 1333 } 1334 The output should equal "$(expected_out)" 1335 End 1336 1337 It 'decrypts a GPG secret in the store using gpg' 1338 unset GPG 1339 gpg() { mock-gpg "$@"; } 1340 When call cmd_list_or_show old 1341 The status should be success 1342 The error should be blank 1343 expected_out() { %text 1344 #|very-old-password 1345 #|Username: previous-life 1346 } 1347 The output should equal "$(expected_out)" 1348 End 1349 1350 It 'fails to decrypt a GPG secret without gpg' 1351 unset GPG 1352 When run cmd_list_or_show old 1353 The status should equal 1 1354 The error should equal 'GPG does not seem available' 1355 The output should be blank 1356 End 1357 1358 It 'displays both list and show usage on parse error with ambiguity' 1359 PROGRAM=prg 1360 COMMAND=both 1361 When run cmd_list_or_show -x 1362 The status should equal 1 1363 The output should be blank 1364 expected_err() { %text 1365 #|Usage: prg [list] [--raw,-r] [subfolder] 1366 #| prg [show] [--clip[=line-number],-c[line-number] | 1367 #| --qrcode[=line-number],-q[line-number]] pass-name 1368 } 1369 The error should equal "$(expected_err)" 1370 End 1371 1372 It 'displays list usage on parse error with list command' 1373 PROGRAM=prg 1374 COMMAND=list 1375 When run cmd_list_or_show -x 1376 The status should equal 1 1377 The output should be blank 1378 expected_err() { %text 1379 #|Usage: prg [list] [--raw,-r] [subfolder] 1380 } 1381 The error should equal "$(expected_err)" 1382 End 1383 1384 It 'displays show usage on parse error with show command' 1385 PROGRAM=prg 1386 COMMAND=show 1387 When run cmd_list_or_show -x 1388 The status should equal 1 1389 The output should be blank 1390 expected_err() { %text 1391 #|Usage: prg [show] [--clip[=line-number],-c[line-number] | 1392 #| --qrcode[=line-number],-q[line-number]] pass-name 1393 } 1394 The error should equal "$(expected_err)" 1395 End 1396 1397 It 'aborts on age decryption failure even without pipefail' 1398 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 1399 Set 'pipefail:off' 1400 fi 1401 AGE=false 1402 When run cmd_list_or_show stale 1403 The status should equal 1 1404 The error should equal \ 1405 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- ${PREFIX}/stale.age" 1406 The output should be blank 1407 The result of function check_git_log should be successful 1408 End 1409 1410 It 'aborts on gpg decryption failure even without pipefail' 1411 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 1412 Set 'pipefail:off' 1413 fi 1414 GPG=false 1415 When run cmd_list_or_show old 1416 The status should equal 1 1417 The error should equal "Fatal(1): false -d --quiet --yes --compress-algo=none --no-encrypt-to -- ${PREFIX}/old.gpg" 1418 The output should be blank 1419 The result of function check_git_log should be successful 1420 End 1421 End 1422 1423 # Describe 'cmd_move' is not needed (covered by 'cmd_copy_move') 1424 1425 Describe 'cmd_random' 1426 It 'generates random characters' 1427 When call cmd_random 2 '[:digit:]' 1428 The status should be success 1429 The error should be blank 1430 The output should match pattern '[0-9][0-9]' 1431 End 1432 1433 It 'defaults to using CHARACTER_SET' 1434 PREV_CHARACTER_SET="${CHARACTER_SET}" 1435 CHARACTER_SET='[:lower:]' 1436 When call cmd_random 2 1437 The status should be success 1438 The error should be blank 1439 The output should match pattern '[a-z][a-z]' 1440 CHARACTER_SET="${PREV_CHARACTER_SET}" 1441 End 1442 1443 It 'defaults to using both GENERATED_LENGTH and CHARACTER_SET' 1444 PREV_CHARACTER_SET="${CHARACTER_SET}" 1445 PREV_GENERATED_LENGTH="${GENERATED_LENGTH}" 1446 CHARACTER_SET='[:upper:]' 1447 GENERATED_LENGTH=5 1448 When call cmd_random 1449 The status should be success 1450 The error should be blank 1451 The output should match pattern '[A-Z][A-Z][A-Z][A-Z][A-Z]' 1452 CHARACTER_SET="${PREV_CHARACTER_SET}" 1453 GENERATED_LENGTH="${PREV_GENERATED_LENGTH}" 1454 End 1455 1456 It 'displays usage when called with too many arguments' 1457 PROGRAM=prg 1458 When run cmd_random 2 '[:digit:]' extra 1459 The status should equal 1 1460 The output should be blank 1461 The error should equal 'Usage: prg random [pass-length [character-set]]' 1462 End 1463 End 1464 1465 Describe 'cmd_reencrypt' 1466 usage_text() { %text 1467 #|Usage: prg reencrypt [--interactive,-i] pass-name|subfolder ... 1468 } 1469 1470 It 'reencrypts a single file' 1471 When call cmd_reencrypt stale 1472 The status should be success 1473 The error should be blank 1474 The output should be blank 1475 expected_file() { %text 1476 #|ageRecipient:myself 1477 #|age:0-password 1478 } 1479 The contents of file "${PREFIX}/stale.age" \ 1480 should equal "$(expected_file)" 1481 expected_log() { %text 1482 #|Re-encrypt stale 1483 #| 1484 #| stale.age | 1 - 1485 #| 1 file changed, 1 deletion(-) 1486 setup_log 1487 } 1488 The result of function check_git_log should be successful 1489 End 1490 1491 It 'reencrypts a single file interactively' 1492 Data 'y' 1493 When call cmd_reencrypt -i stale 1494 The status should be success 1495 The error should be blank 1496 The output should equal 'Re-encrypt stale? [y/n]' 1497 expected_file() { %text 1498 #|ageRecipient:myself 1499 #|age:0-password 1500 } 1501 The contents of file "${PREFIX}/stale.age" \ 1502 should equal "$(expected_file)" 1503 expected_log() { %text 1504 #|Re-encrypt stale 1505 #| 1506 #| stale.age | 1 - 1507 #| 1 file changed, 1 deletion(-) 1508 setup_log 1509 } 1510 The result of function check_git_log should be successful 1511 End 1512 1513 It 'does not reencrypt a single file when interactively refused' 1514 Data 'n' 1515 When call cmd_reencrypt --interactive stale 1516 The status should be success 1517 The error should be blank 1518 The output should equal 'Re-encrypt stale? [y/n]' 1519 expected_file() { %text 1520 #|ageRecipient:master 1521 #|ageRecipient:myself 1522 #|age:0-password 1523 } 1524 The contents of file "${PREFIX}/stale.age" \ 1525 should equal "$(expected_file)" 1526 The result of function check_git_log should be successful 1527 End 1528 1529 It 'reencrypts a directory recursively' 1530 When call cmd_reencrypt / 1531 The status should be success 1532 The error should be blank 1533 The output should be blank 1534 expected_file() { %text 1535 #|ageRecipient:myself 1536 #|age:0-password 1537 } 1538 The contents of file "${PREFIX}/stale.age" \ 1539 should equal "$(expected_file)" 1540 expected_log() { %text 1541 #|Re-encrypt / 1542 #| 1543 #| stale.age | 1 - 1544 #| 1 file changed, 1 deletion(-) 1545 setup_log 1546 } 1547 The result of function check_git_log should be successful 1548 End 1549 1550 It 'reencrypts a directory recursively and interactively' 1551 Data 1552 #|n 1553 #|y 1554 #|n 1555 End 1556 When call cmd_reencrypt -i '' 1557 The status should be success 1558 The error should be blank 1559 The output should equal 'Re-encrypt extra/subdir/file? [y/n]Re-encrypt stale? [y/n]Re-encrypt subdir/file? [y/n]' 1560 expected_file() { %text 1561 #|ageRecipient:myself 1562 #|age:0-password 1563 } 1564 The contents of file "${PREFIX}/stale.age" \ 1565 should equal "$(expected_file)" 1566 expected_log() { %text 1567 #|Re-encrypt / 1568 #| 1569 #| stale.age | 1 - 1570 #| 1 file changed, 1 deletion(-) 1571 setup_log 1572 } 1573 The result of function check_git_log should be successful 1574 End 1575 1576 It 'fails to reencrypt a file named like a flag without escape' 1577 PROGRAM=prg 1578 When run cmd_reencrypt -g 1579 The status should equal 1 1580 The error should equal "$(usage_text)" 1581 The output should be blank 1582 The result of function check_git_log should be successful 1583 End 1584 1585 It 'fails to reencrypt a non-existent direcotry' 1586 When run cmd_reencrypt -- -y/ 1587 The status should equal 1 1588 The error should equal 'Error: -y/ is not in the password store.' 1589 The output should be blank 1590 The result of function check_git_log should be successful 1591 End 1592 1593 It 'fails to reencrypt a non-existent file' 1594 When run cmd_reencrypt -- -y 1595 The status should equal 1 1596 The error should equal 'Error: -y is not in the password store.' 1597 The output should be blank 1598 The result of function check_git_log should be successful 1599 End 1600 1601 It 'rejects a path containing ..' 1602 When run cmd_reencrypt fluff/../stale 1603 The status should equal 1 1604 The output should be blank 1605 The error should include 'sneaky' 1606 The result of function check_git_log should be successful 1607 End 1608 1609 It 'aborts on age decryption failure even without pipefail' 1610 if ! [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 1611 Set 'pipefail:off' 1612 fi 1613 AGE=false 1614 When run cmd_reencrypt stale 1615 The status should equal 1 1616 The error should equal \ 1617 "Fatal(1): false -d -i ${IDENTITIES_FILE} -- ${PREFIX}/stale.age" 1618 The output should be blank 1619 The result of function check_git_log should be successful 1620 End 1621 End 1622 1623 Describe 'cmd_usage' 1624 It 'defaults to four-space indentation' 1625 PROGRAM=prg 1626 When call cmd_usage 1627 The status should be success 1628 The error should be blank 1629 The output should equal "$(cmd_usage ' ')" 1630 End 1631 1632 It 'fails with an unknown command' 1633 PROGRAM=prg 1634 When run cmd_usage 'Usage: ' bad version 1635 The status should equal 1 1636 The output should be blank 1637 The error should equal 'cmd_usage: unknown command "bad"' 1638 End 1639 End 1640 1641 # Describe 'cmd_version' is not needed (fully covered in pass_spec.sh) 1642 1643 Describe 'refuse to operate on dirty checkout:' 1644 make_dirty() { 1645 %putsn 'untracked data' >"${PREFIX}/untracked.txt" 1646 } 1647 BeforeEach make_dirty 1648 1649 git_log() { 1650 @rm -f "${PREFIX}/untracked.txt" 1651 @git -C "${PREFIX}" status --porcelain >&2 1652 @git -C "${PREFIX}" log --format='%s' --stat >|"${GITLOG}" 1653 } 1654 1655 # 'copy' relies on 'copy/move' 1656 1657 Example 'copy/move' 1658 When run cmd_copy_move stale subdir/ 1659 The status should equal 1 1660 The error should equal 'There are already pending changes.' 1661 The output should be blank 1662 The result of function check_git_log should be successful 1663 End 1664 1665 Example 'delete' 1666 When run cmd_delete -f stale 1667 The status should equal 1 1668 The error should equal 'There are already pending changes.' 1669 The output should equal 'Removing stale' 1670 The result of function check_git_log should be successful 1671 End 1672 1673 Example 'edit' 1674 VISUAL='false' 1675 When run cmd_edit subdir/file 1676 The status should equal 1 1677 The error should equal 'There are already pending changes.' 1678 The output should be blank 1679 The result of function check_git_log should be successful 1680 End 1681 1682 # 'find' does not change the repository 1683 1684 Example 'generate' 1685 When run cmd_generate new-pass 1686 The status should equal 1 1687 The error should equal 'There are already pending changes.' 1688 The output should be blank 1689 The result of function check_git_log should be successful 1690 End 1691 1692 # 'git' does not change directly the repository 1693 1694 Example 'gitconfig' 1695 When run cmd_gitconfig 1696 The status should equal 1 1697 The error should equal 'There are already pending changes.' 1698 The output should be blank 1699 The result of function check_git_log should be successful 1700 End 1701 1702 # 'grep' does not change the repository 1703 # 'help' does not change the repository 1704 1705 Example 'init' 1706 When run cmd_init -p subdir/ new-id 1707 The status should equal 1 1708 The error should equal 'There are already pending changes.' 1709 The output should be blank 1710 The result of function check_git_log should be successful 1711 End 1712 1713 Example 'init (deinit)' 1714 When run cmd_init -p fluff/ '' 1715 The status should equal 1 1716 The error should equal 'There are already pending changes.' 1717 The output should be blank 1718 The result of function check_git_log should be successful 1719 End 1720 1721 Example 'insert' 1722 When run cmd_insert -e fluff/four 1723 The status should equal 1 1724 The error should equal 'There are already pending changes.' 1725 The output should be blank 1726 The result of function check_git_log should be successful 1727 End 1728 1729 # 'list_or_show' does not change the repository 1730 # 'move' relies on 'copy/move' 1731 # 'random' does not change the repository 1732 1733 Example 'reencrypt' 1734 When run cmd_reencrypt stale 1735 The status should equal 1 1736 The error should equal 'There are already pending changes.' 1737 The output should be blank 1738 The result of function check_git_log should be successful 1739 End 1740 1741 # 'usage' does not change the repository 1742 # 'version' does not change the repository 1743 End 1744 1745 Describe 'unreachable defensive code' 1746 # This sections breaks the end-to-end scheme of this file 1747 # to reach full coverage, by precisely identifying unreachable lines 1748 # written for defensive programming against internal inconsistencies. 1749 1750 It 'includes invalid values of DECISION in do_copy_move_file' 1751 DECISION='invalid' 1752 When run do_copy_move_file subdir/file.age extra/file.age 1753 The status should equal 1 1754 The output should be blank 1755 The error should equal 'Unexpected DECISION value "invalid"' 1756 End 1757 1758 It 'includes overwriting a file using do_encrypt' 1759 OVERWRITE=no 1760 When run do_encrypt 'y.txt' 1761 The status should equal 1 1762 The output should be blank 1763 The error should equal 'Refusing to overwite y.txt' 1764 End 1765 1766 It 'includes invalid values of SHOW in do_show' 1767 SHOW='invalid' 1768 When run do_show 1769 The status should equal 1 1770 The output should be blank 1771 expected_err() { %text 1772 #|Usage: prg [show] [--clip[=line-number],-c[line-number] | 1773 #| --qrcode[=line-number],-q[line-number]] pass-name 1774 } 1775 The error should equal 'Unexpected SHOW value "invalid"' 1776 End 1777 1778 It 'includes interactive yesno' 1779 # Technically not unreachable, but not worse than faking a terminal 1780 # for each call of `yesno` when the whole test suite is outside 1781 # of terminal anyway 1782 1783 stty() { true; } 1784 Data 1785 #|x 1786 #|Y 1787 End 1788 When call yesno 'Prompt?' 1789 The status should be success 1790 The error should be blank 1791 The output should equal 'Prompt? [y/n]' 1792 The variable ANSWER should equal 'y' 1793 End 1794 End 1795 End