action_spec.sh (61526B)
1 # pashage - age-backed POSIX password manager 2 # Copyright (C) 2024 Natasha Kerensikova 3 # 4 # This program is free software; you can redistribute it and/or 5 # modify it under the terms of the GNU General Public License 6 # as published by the Free Software Foundation; either version 2 7 # of the License, or (at your option) any later version. 8 # 9 # This program is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU General Public License for more details. 13 # 14 # You should have received a copy of the GNU General Public License 15 # along with this program; if not, write to the Free Software 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 18 # This test file fully covers all action functions in isolation, 19 # with all interactions fully mocked. 20 21 Describe 'Action Functions' 22 Include src/pashage.sh 23 if [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then 24 Set 'errexit:on' 'nounset:on' 25 else 26 Set 'errexit:on' 'nounset:on' 'pipefail:on' 27 fi 28 29 Describe 'do_copy_move' 30 DECISION=default 31 OVERWRITE=yes 32 PREFIX="${SHELLSPEC_WORKDIR}/prefix" 33 34 ACTION=Move 35 SCM_ACTION=scm_mv 36 37 do_decrypt() { 38 mocklog do_decrypt "$@" 39 %putsn data 40 } 41 42 do_encrypt() { 43 @cat >/dev/null 44 mocklog do_encrypt "$@" 45 } 46 47 basename() { @basename "$@"; } 48 cat() { @cat "$@"; } 49 dirname() { @dirname "$@"; } 50 51 mkdir() { mocklog mkdir "$@"; } 52 scm_add() { mocklog scm_add "$@"; } 53 scm_begin() { mocklog scm_begin "$@"; } 54 scm_commit() { mocklog scm_commit "$@"; } 55 scm_cp() { mocklog scm_cp "$@"; } 56 scm_mv() { mocklog scm_mv "$@"; } 57 scm_rm() { mocklog scm_rm "$@"; } 58 59 setup() { 60 @mkdir -p "${PREFIX}/sub/bare/sub" "${PREFIX}/subdir/notes.txt" 61 %putsn 'identity 1' >"${PREFIX}/.age-recipients" 62 %putsn 'identity 2' >"${PREFIX}/sub/.age-recipients" 63 %putsn 'identity 2' >"${PREFIX}/subdir/.age-recipients" 64 %putsn data >"${PREFIX}/sub/secret.age" 65 %putsn data >"${PREFIX}/sub/bare/deep.age" 66 %putsn data >"${PREFIX}/sub/bare/sub/deepest.age" 67 %putsn data >"${PREFIX}/subdir/lower.age" 68 %putsn data >"${PREFIX}/root.age" 69 %putsn data >"${PREFIX}/notes.txt" 70 } 71 72 cleanup() { 73 @rm -rf "${PREFIX}" 74 } 75 76 BeforeEach setup 77 AfterEach cleanup 78 79 It 'renames a file without re-encrypting' 80 result() { 81 %text:expand 82 #|$ mkdir -p -- ${PREFIX}/sub 83 #|$ scm_begin 84 #|$ scm_mv sub/secret.age sub/renamed.age 85 #|$ scm_commit Move sub/secret.age to sub/renamed.age 86 } 87 When call do_copy_move sub/secret sub/renamed 88 The status should be success 89 The output should be blank 90 The error should equal "$(result)" 91 End 92 93 It 're-encrypts when copying to another identity' 94 ACTION=Copy 95 SCM_ACTION=scm_cp 96 result() { 97 %text:expand 98 #|$ scm_begin 99 #|$ do_decrypt ${PREFIX}/root.age 100 #|$ do_encrypt sub/root.age 101 #|$ scm_add sub/root.age 102 #|$ scm_commit Copy root.age to sub/root.age 103 } 104 When call do_copy_move root sub/ 105 The status should be success 106 The output should be blank 107 The error should equal "$(result)" 108 End 109 110 It 'accepts explicit .age extensions' 111 ACTION=Copy 112 SCM_ACTION=scm_cp 113 result() { 114 %text:expand 115 #|$ mkdir -p -- ${PREFIX}/sub 116 #|$ scm_begin 117 #|$ do_decrypt ${PREFIX}/root.age 118 #|$ do_encrypt sub/moved.age 119 #|$ scm_add sub/moved.age 120 #|$ scm_commit Copy root.age to sub/moved.age 121 } 122 When call do_copy_move root.age sub/moved.age 123 The status should be success 124 The output should be blank 125 The error should equal "$(result)" 126 End 127 128 It 'can be prevented from re-encrypting when copying to another identity' 129 DECISION=keep 130 ACTION=Copy 131 SCM_ACTION=scm_cp 132 result() { %text 133 #|$ scm_begin 134 #|$ scm_cp root.age sub/root.age 135 #|$ scm_commit Copy root.age to sub/root.age 136 } 137 When call do_copy_move root sub/ 138 The status should be success 139 The output should be blank 140 The error should equal "$(result)" 141 End 142 143 It 'does not re-encrypt a non-encrypted file' 144 result() { %text 145 #|$ scm_begin 146 #|$ scm_mv notes.txt sub/notes.txt 147 #|$ scm_commit Move notes.txt to sub/notes.txt 148 } 149 When call do_copy_move notes.txt sub/ 150 The status should be success 151 The output should be blank 152 The error should equal "$(result)" 153 End 154 155 It 'does not re-encrypt a non-encrypted file even when forced' 156 DECISION=force 157 result() { %text 158 #|$ scm_begin 159 #|$ scm_mv notes.txt sub/notes.txt 160 #|$ scm_commit Move notes.txt to sub/notes.txt 161 } 162 When call do_copy_move notes.txt sub/ 163 The status should be success 164 The output should be blank 165 The error should equal "$(result)" 166 End 167 168 It 'moves a file without re-encrypting to another directory' 169 result() { %text 170 #|$ scm_begin 171 #|$ scm_mv sub/secret.age subdir/secret.age 172 #|$ scm_commit Move sub/secret.age to subdir/secret.age 173 } 174 When call do_copy_move sub/secret subdir 175 The status should be success 176 The output should be blank 177 The error should equal "$(result)" 178 End 179 180 It 'asks confirmation before overwriting a file' 181 OVERWRITE=no 182 rm() { mocklog rm "$@"; } 183 yesno() { 184 mocklog yesno "$@" 185 ANSWER=y 186 } 187 result() { 188 %text:expand 189 #|$ mkdir -p -- ${PREFIX}/sub 190 #|$ scm_begin 191 #|$ yesno sub/secret.age already exists. Overwrite? 192 #|$ rm -f -- ${PREFIX}/sub/secret.age 193 #|$ do_decrypt ${PREFIX}/root.age 194 #|$ do_encrypt sub/secret.age 195 #|$ scm_rm root.age 196 #|$ scm_add sub/secret.age 197 #|$ scm_commit Move root.age to sub/secret.age 198 } 199 When call do_copy_move root sub/secret 200 The status should be success 201 The output should be blank 202 The error should equal "$(result)" 203 End 204 205 It 'moves a whole directory with identity' 206 result() { 207 %text:expand 208 #|$ scm_begin 209 #|$ mkdir -p -- ${PREFIX}/subdir/sub 210 #|$ scm_mv sub/.age-recipients subdir/sub/.age-recipients 211 #|$ mkdir -p -- ${PREFIX}/subdir/sub/bare 212 #|$ scm_mv sub/bare/deep.age subdir/sub/bare/deep.age 213 #|$ mkdir -p -- ${PREFIX}/subdir/sub/bare/sub 214 #|$ scm_mv sub/bare/sub/deepest.age subdir/sub/bare/sub/deepest.age 215 #|$ scm_mv sub/secret.age subdir/sub/secret.age 216 #|$ scm_commit Move sub/ to subdir/sub/ 217 } 218 When call do_copy_move sub subdir/ 219 The status should be success 220 The output should be blank 221 The error should equal "$(result)" 222 End 223 224 It 'recursively moves files to a directory with the same identity' 225 result() { 226 %text:expand 227 #|$ scm_begin 228 #|$ mkdir -p -- ${PREFIX}/subdir/new-bare 229 #|$ scm_mv sub/bare/deep.age subdir/new-bare/deep.age 230 #|$ mkdir -p -- ${PREFIX}/subdir/new-bare/sub 231 #|$ scm_mv sub/bare/sub/deepest.age subdir/new-bare/sub/deepest.age 232 #|$ scm_commit Move sub/bare/ to subdir/new-bare/ 233 } 234 When call do_copy_move sub/bare subdir/new-bare 235 The status should be success 236 The output should be blank 237 The error should equal "$(result)" 238 End 239 240 It 'recursively re-encrypts a directory' 241 result() { 242 %text:expand 243 #|$ scm_begin 244 #|$ mkdir -p -- ${PREFIX}/new-bare 245 #|$ do_decrypt ${PREFIX}/sub/bare/deep.age 246 #|$ do_encrypt new-bare/deep.age 247 #|$ scm_rm sub/bare/deep.age 248 #|$ scm_add new-bare/deep.age 249 #|$ mkdir -p -- ${PREFIX}/new-bare/sub 250 #|$ do_decrypt ${PREFIX}/sub/bare/sub/deepest.age 251 #|$ do_encrypt new-bare/sub/deepest.age 252 #|$ scm_rm sub/bare/sub/deepest.age 253 #|$ scm_add new-bare/sub/deepest.age 254 #|$ scm_commit Move sub/bare/ to new-bare/ 255 } 256 When call do_copy_move sub/bare new-bare 257 The status should be success 258 The output should be blank 259 The error should equal "$(result)" 260 End 261 262 It 'recursively re-encrypts a directory with the same identity when forced' 263 DECISION=force 264 result() { 265 %text:expand 266 #|$ scm_begin 267 #|$ mkdir -p -- ${PREFIX}/subdir/new-bare 268 #|$ do_decrypt ${PREFIX}/sub/bare/deep.age 269 #|$ do_encrypt subdir/new-bare/deep.age 270 #|$ scm_rm sub/bare/deep.age 271 #|$ scm_add subdir/new-bare/deep.age 272 #|$ mkdir -p -- ${PREFIX}/subdir/new-bare/sub 273 #|$ do_decrypt ${PREFIX}/sub/bare/sub/deepest.age 274 #|$ do_encrypt subdir/new-bare/sub/deepest.age 275 #|$ scm_rm sub/bare/sub/deepest.age 276 #|$ scm_add subdir/new-bare/sub/deepest.age 277 #|$ scm_commit Move sub/bare/ to subdir/new-bare/ 278 } 279 When call do_copy_move sub/bare subdir/new-bare 280 The status should be success 281 The output should be blank 282 The error should equal "$(result)" 283 End 284 285 It 'interactively re-enecrypts or copies files from a directory' 286 DECISION=interactive 287 ACTION=Copy 288 SCM_ACTION=scm_cp 289 YESNO_NEXT=n 290 yesno() { 291 mocklog yesno "$@" 292 ANSWER="${YESNO_NEXT}" 293 YESNO_NEXT=y 294 } 295 result() { 296 %text:expand 297 #|$ scm_begin 298 #|$ mkdir -p -- ${PREFIX}/subdir/new-bare 299 #|$ yesno Reencrypt sub/bare/deep into subdir/new-bare/deep? 300 #|$ scm_cp sub/bare/deep.age subdir/new-bare/deep.age 301 #|$ mkdir -p -- ${PREFIX}/subdir/new-bare/sub 302 #|$ yesno Reencrypt sub/bare/sub/deepest into subdir/new-bare/sub/deepest? 303 #|$ do_decrypt ${PREFIX}/sub/bare/sub/deepest.age 304 #|$ do_encrypt subdir/new-bare/sub/deepest.age 305 #|$ scm_add subdir/new-bare/sub/deepest.age 306 #|$ scm_commit Copy sub/bare/ to subdir/new-bare/ 307 } 308 When call do_copy_move sub/bare subdir/new-bare 309 The status should be success 310 The output should be blank 311 The error should equal "$(result)" 312 End 313 314 It 'reports a file masqueraded as a directory' 315 When run do_copy_move root.age/ subdir 316 The output should be blank 317 The error should equal 'Error: root.age/ is not in the password store.' 318 The status should equal 1 319 End 320 321 It 'reports non-existent source' 322 When run do_copy_move nonexistent subdir 323 The output should be blank 324 The error should equal 'Error: nonexistent is not in the password store.' 325 The status should equal 1 326 End 327 328 It 'cannot merge similarly-named directories' 329 When run do_copy_move sub/bare/sub / 330 The output should be blank 331 The error should equal 'Error: / already contains sub' 332 The status should equal 1 333 End 334 335 It 'cannot move a directory into a file' 336 When run do_copy_move sub/ root.age 337 The output should be blank 338 The error should equal 'Error: root.age is not a directory' 339 The status should equal 1 340 End 341 342 It 'cannot overwrite a directory with a file' 343 When run do_copy_move notes.txt subdir 344 The output should be blank 345 The error should equal 'Error: subdir already contains notes.txt/' 346 The status should equal 1 347 End 348 349 # Unreachable branches in do_copy_move_file, defensively implemented 350 It 'defensively avois re-encrypting' 351 DECISION=keep 352 result() { 353 %text 354 #|$ scm_mv root.age non-existent 355 } 356 When run do_copy_move_file root.age non-existent 357 The status should be success 358 The output should be blank 359 The error should equal "$(result)" 360 End 361 362 It 'defensively checks internal consistency of DECISION' 363 DECISION=garbage 364 When run do_copy_move_file root.age non-existent 365 The output should be blank 366 The error should equal 'Unexpected DECISION value "garbage"' 367 The status should equal 1 368 End 369 End 370 371 Specify 'do_decrypt' 372 AGE=age 373 age() { 374 mocklog age "$@" 375 %= 'cleartext' 376 } 377 378 IDENTITIES_FILE='/path/to/identity' 379 When call do_decrypt '/path/to/encrypted/file.age' 380 The status should be success 381 The output should equal 'cleartext' 382 The error should equal \ 383 '$ age -d -i /path/to/identity -- /path/to/encrypted/file.age' 384 End 385 386 Describe 'do_decrypt_gpg' 387 It 'uses gpg when agent is not available' 388 gpg() { mocklog gpg "$@"; } 389 unset GPG_AGENT_INFO 390 unset GPG 391 When call do_decrypt_gpg /path/to/encrypted/file.gpg 392 The status should be success 393 The error should equal \ 394 '$ gpg -d --quiet --yes --compress-algo=none --no-encrypt-to -- /path/to/encrypted/file.gpg' 395 End 396 397 It 'uses gpg when agent is available' 398 gpg() { mocklog gpg "$@"; } 399 GPG_AGENT_INFO=agent-info 400 unset GPG 401 When call do_decrypt_gpg /path/to/encrypted/file.gpg 402 The status should be success 403 The error should equal \ 404 '$ gpg -d --quiet --yes --compress-algo=none --no-encrypt-to --batch --use-agent -- /path/to/encrypted/file.gpg' 405 End 406 407 It 'uses gpg2' 408 gpg2() { mocklog gpg2 "$@"; } 409 unset GPG_AGENT_INFO 410 unset GPG 411 When call do_decrypt_gpg /path/to/encrypted/file.gpg 412 The status should be success 413 The error should equal \ 414 '$ gpg2 -d --quiet --yes --compress-algo=none --no-encrypt-to --batch --use-agent -- /path/to/encrypted/file.gpg' 415 End 416 417 It 'uses user-provided command' 418 user_cmd() { mocklog user_cmd "$@"; } 419 unset GPG_AGENT_INFO 420 GPG=user_cmd 421 When call do_decrypt_gpg /path/to/encrypted/file.gpg 422 The status should be success 423 The error should equal \ 424 '$ user_cmd -d --quiet --yes --compress-algo=none --no-encrypt-to -- /path/to/encrypted/file.gpg' 425 End 426 427 It 'bails out when command cannot be guessed' 428 unset GPG 429 When run do_decrypt_gpg /path/to/encrypted/file.gpg 430 The error should equal 'GPG does not seem available' 431 The status should equal 1 432 End 433 End 434 435 Describe 'do_deinit' 436 DECISION=default 437 PREFIX="${SHELLSPEC_WORKDIR}/prefix" 438 439 do_reencrypt_dir() { mocklog do_reencrypt_dir "$@"; } 440 scm_begin() { mocklog scm_begin "$@"; } 441 scm_commit() { mocklog scm_commit "$@"; } 442 scm_rm() { mocklog scm_rm "$@"; } 443 444 setup() { 445 @mkdir -p "${PREFIX}/empty" "${PREFIX}/sub" 446 %putsn data > "${PREFIX}/.age-recipients" 447 %putsn data > "${PREFIX}/sub/.age-recipients" 448 } 449 450 cleanup() { 451 @rm -rf "${PREFIX}" 452 } 453 454 BeforeEach setup 455 AfterEach cleanup 456 457 It 'de-initializes the whole store' 458 result() { 459 %text:expand 460 #|$ scm_begin 461 #|$ scm_rm .age-recipients 462 #|$ do_reencrypt_dir ${PREFIX}/ 463 #|$ scm_commit Deinitialize store root 464 } 465 When call do_deinit '' 466 The status should be success 467 The output should be blank 468 The error should equal "$(result)" 469 End 470 471 It 'de-initializes a subdirectory' 472 result() { 473 %text:expand 474 #|$ scm_begin 475 #|$ scm_rm sub/.age-recipients 476 #|$ do_reencrypt_dir ${PREFIX}/sub 477 #|$ scm_commit Deinitialize sub 478 } 479 When call do_deinit sub 480 The status should be success 481 The output should be blank 482 The error should equal "$(result)" 483 End 484 485 It 'can de-initialize without re-encryption' 486 DECISION=keep 487 result() { 488 %text:expand 489 #|$ scm_begin 490 #|$ scm_rm sub/.age-recipients 491 #|$ scm_commit Deinitialize sub 492 } 493 When call do_deinit sub 494 The status should be success 495 The output should be blank 496 The error should equal "$(result)" 497 End 498 499 It 'reports impossible de-initialization' 500 When run do_deinit non-existent 501 The output should be blank 502 The error should equal 'No existing recipient to remove at non-existent' 503 The status should equal 1 504 End 505 End 506 507 Describe 'do_delete' 508 DECISION=force 509 RECURSIVE=yes 510 PREFIX="${SHELLSPEC_WORKDIR}/prefix" 511 512 dirname() { @dirname "$@"; } 513 scm_begin() { mocklog scm_begin "$@"; } 514 scm_commit() { mocklog scm_commit "$@"; } 515 scm_rm() { mocklog scm_rm "$@"; } 516 517 setup() { 518 @mkdir -p "${PREFIX}/empty" "${PREFIX}/sub" 519 %putsn data > "${PREFIX}/non-encrypted" 520 %putsn data > "${PREFIX}/sub.age" 521 %putsn data > "${PREFIX}/sub/entry.age" 522 } 523 524 cleanup() { 525 @rm -rf "${PREFIX}" 526 } 527 528 BeforeEach setup 529 AfterEach cleanup 530 531 It 'deletes a file after confirmation' 532 DECISION=default 533 yesno() { 534 mocklog yesno "$@" 535 ANSWER=y 536 } 537 result() { 538 %text:expand 539 #|$ yesno Are you sure you would like to delete sub/entry? 540 #|$ scm_begin 541 #|$ scm_rm sub/entry.age 542 #|$ scm_commit Remove sub/entry from store. 543 } 544 When call do_delete sub/entry 545 The status should be success 546 The output should be blank 547 The error should equal "$(result)" 548 End 549 550 It 'does not delete a file without confirmation' 551 DECISION=default 552 yesno() { 553 mocklog yesno "$@" 554 ANSWER=n 555 } 556 result() { 557 %text 558 #|$ yesno Are you sure you would like to delete sub/entry? 559 } 560 When call do_delete sub/entry 561 The status should be success 562 The output should be blank 563 The error should equal "$(result)" 564 End 565 566 It 'deletes a directory' 567 result() { 568 %text:expand 569 #|$ scm_begin 570 #|$ scm_rm empty/ 571 #|$ scm_commit Remove empty/ from store. 572 } 573 When call do_delete empty 574 The status should be success 575 The output should equal 'Removing empty/' 576 The error should equal "$(result)" 577 End 578 579 It 'deletes a file rather than a directory on ambiguity' 580 result() { 581 %text:expand 582 #|$ scm_begin 583 #|$ scm_rm sub.age 584 #|$ scm_commit Remove sub from store. 585 } 586 When call do_delete sub 587 The status should be success 588 The output should equal 'Removing sub' 589 The error should equal "$(result)" 590 End 591 592 It 'deletes a directory when explicitly asked' 593 result() { 594 %text:expand 595 #|$ scm_begin 596 #|$ scm_rm sub/ 597 #|$ scm_commit Remove sub/ from store. 598 } 599 When call do_delete sub/ 600 The status should be success 601 The output should equal 'Removing sub/' 602 The error should equal "$(result)" 603 End 604 605 It 'does not delete an explicit directory without RECURSIVE' 606 RECURSIVE=no 607 When run do_delete sub/ 608 The output should be blank 609 The error should equal 'Error: sub/ is a directory' 610 The status should equal 1 611 End 612 613 It 'does not delete an implicit directory without RECURSIVE' 614 RECURSIVE=no 615 When run do_delete empty 616 The output should be blank 617 The error should equal 'Error: empty/ is a directory' 618 The status should equal 1 619 End 620 621 It 'does not delete a non-encrypted file' 622 When run do_delete non-encrypted 623 The output should be blank 624 The error should equal \ 625 'Error: non-encrypted is not in the password store.' 626 The status should equal 1 627 End 628 629 It 'does not delete a file presented as a directory' 630 When run do_delete non-encrypted/ 631 The output should be blank 632 The error should equal \ 633 'Error: non-encrypted/ is not a directory.' 634 The status should equal 1 635 End 636 637 It 'reports a non-existent directory' 638 When run do_delete non-existent/ 639 The output should be blank 640 The error should equal \ 641 'Error: non-existent/ is not in the password store.' 642 The status should equal 1 643 End 644 End 645 646 Describe 'do_edit' 647 SECURE_TMPDIR="${SHELLSPEC_WORKDIR}/secure" 648 PREFIX="${SHELLSPEC_WORKDIR}/prefix" 649 650 diff(){ @diff "$@"; } 651 652 do_decrypt() { 653 mocklog do_decrypt "$@" 654 %= foo 655 } 656 old_do_decrypt() { 657 mocklog do_decrypt "$@" 658 %text 659 #|old line 1 660 #|old line 2 661 } 662 663 do_encrypt() { 664 mocklog do_encrypt "$@" 665 @sed 's/^/> /' >&2 666 } 667 668 mktemp() { 669 mocklog mktemp "$@" 670 %putsn "$2" 671 } 672 673 rm(){ mocklog rm "$@"; @rm "$@"; } 674 675 setup() { 676 @mkdir -p "${PREFIX}" 677 %text > "${PREFIX}/existing.age" 678 #|encrypted data 679 @mkdir -p "${SECURE_TMPDIR}" 680 %text > "${SECURE_TMPDIR}/new-cleartext.txt" 681 #|new line 1 682 #|old line 2 683 #|new line 3 684 } 685 686 scm_add() { mocklog scm_add "$@"; } 687 scm_begin() { mocklog scm_begin "$@"; } 688 scm_commit() { mocklog scm_commit "$@"; } 689 690 cleanup() { 691 @rm -rf "${PREFIX}" "${SECURE_TMPDIR}" 692 } 693 694 BeforeEach setup 695 AfterEach cleanup 696 697 It 'creates a new file' 698 edit(){ @cat "${SECURE_TMPDIR}/new-cleartext.txt" >|"$1"; } 699 result() { 700 %text:expand 701 #|$ mktemp -u ${SECURE_TMPDIR}/XXXXXX 702 #|$ scm_begin 703 #|$ do_encrypt sub/new.age 704 #|> new line 1 705 #|> old line 2 706 #|> new line 3 707 #|$ scm_add sub/new.age 708 #|$ scm_commit Add password for sub/new using edit. 709 #|$ rm ${SECURE_TMPDIR}/XXXXXX-sub-new.txt 710 } 711 EDIT_CMD=edit 712 When call do_edit sub/new 713 The status should be success 714 The output should be blank 715 The error should equal "$(result)" 716 End 717 718 It 'handles NOT creating a new file' 719 result() { 720 %text:expand 721 #|$ mktemp -u ${SECURE_TMPDIR}/XXXXXX 722 #|$ scm_begin 723 } 724 EDIT_CMD=true 725 When call do_edit new 726 The status should be success 727 The output should equal 'New password for new not saved.' 728 The error should equal "$(result)" 729 End 730 731 It 'updates a file' 732 edit(){ @cat "${SECURE_TMPDIR}/new-cleartext.txt" >|"$1"; } 733 cat(){ mocklog cat "$@"; @cat "$@"; } 734 result() { 735 %text:expand 736 #|$ mktemp -u ${SECURE_TMPDIR}/XXXXXX 737 #|$ do_decrypt ${PREFIX}/existing.age 738 #|$ cat ${SECURE_TMPDIR}/XXXXXX-existing.txt 739 #|$ scm_begin 740 #|$ do_encrypt existing.age 741 #|> new line 1 742 #|> old line 2 743 #|> new line 3 744 #|$ scm_add existing.age 745 #|$ scm_commit Edit password for existing using edit. 746 #|$ rm ${SECURE_TMPDIR}/XXXXXX-existing.txt 747 } 748 EDIT_CMD=edit 749 When call do_edit existing 750 The status should be success 751 The output should be blank 752 The error should equal "$(result)" 753 End 754 755 It 'does not re-encrypt an unchanged file' 756 cat(){ mocklog cat "$@"; @cat "$@"; } 757 result() { 758 %text:expand 759 #|$ mktemp -u ${SECURE_TMPDIR}/XXXXXX 760 #|$ do_decrypt ${PREFIX}/existing.age 761 #|$ cat ${SECURE_TMPDIR}/XXXXXX-existing.txt 762 #|$ scm_begin 763 #|$ rm ${SECURE_TMPDIR}/XXXXXX-existing.txt 764 } 765 EDIT_CMD=true 766 When call do_edit existing 767 The status should be success 768 The output should equal 'Password for existing unchanged.' 769 The error should equal "$(result)" 770 End 771 772 It 'uses VISUAL on non-dumb terminal' 773 edit() { mocklog edit "$@"; } 774 VISUAL=edit 775 TERM=non-dumb 776 EDITOR=false 777 unset EDIT_CMD 778 result() { 779 %text:expand 780 #|$ mktemp -u ${SECURE_TMPDIR}/XXXXXX 781 #|$ scm_begin 782 #|$ edit ${SECURE_TMPDIR}/XXXXXX-subdir-new.txt 783 } 784 When call do_edit subdir/new 785 The status should be success 786 The output should equal 'New password for subdir/new not saved.' 787 The error should equal "$(result)" 788 End 789 790 It 'uses EDITOR on dumb terminal' 791 edit() { mocklog edit "$@"; } 792 VISUAL=false 793 TERM=dumb 794 EDITOR=edit 795 unset EDIT_CMD 796 result() { 797 %text:expand 798 #|$ mktemp -u ${SECURE_TMPDIR}/XXXXXX 799 #|$ scm_begin 800 #|$ edit ${SECURE_TMPDIR}/XXXXXX-subdir-new.txt 801 } 802 When call do_edit subdir/new 803 The status should be success 804 The output should equal 'New password for subdir/new not saved.' 805 The error should equal "$(result)" 806 End 807 808 It 'uses EDITOR without terminal' 809 edit() { mocklog edit "$@"; } 810 VISUAL=false 811 EDITOR=edit 812 unset EDIT_CMD 813 unset TERM 814 result() { 815 %text:expand 816 #|$ mktemp -u ${SECURE_TMPDIR}/XXXXXX 817 #|$ scm_begin 818 #|$ edit ${SECURE_TMPDIR}/XXXXXX-subdir-new.txt 819 } 820 When call do_edit subdir/new 821 The status should be success 822 The output should equal 'New password for subdir/new not saved.' 823 The error should equal "$(result)" 824 End 825 826 It 'uses EDITOR on non-dumb terminal without VISUAL' 827 edit() { mocklog edit "$@"; } 828 TERM=non-dumb 829 EDITOR=edit 830 unset VISUAL 831 unset EDIT_CMD 832 result() { 833 %text:expand 834 #|$ mktemp -u ${SECURE_TMPDIR}/XXXXXX 835 #|$ scm_begin 836 #|$ edit ${SECURE_TMPDIR}/XXXXXX-subdir-new.txt 837 } 838 When call do_edit subdir/new 839 The status should be success 840 The output should equal 'New password for subdir/new not saved.' 841 The error should equal "$(result)" 842 End 843 844 It 'falls back on vi without EDITOR nor VISUAL' 845 vi() { mocklog vi "$@"; } 846 unset EDITOR 847 unset VISUAL 848 unset EDIT_CMD 849 result() { 850 %text:expand 851 #|$ mktemp -u ${SECURE_TMPDIR}/XXXXXX 852 #|$ scm_begin 853 #|$ vi ${SECURE_TMPDIR}/XXXXXX-subdir-new.txt 854 } 855 When call do_edit subdir/new 856 The status should be success 857 The output should equal 'New password for subdir/new not saved.' 858 The error should equal "$(result)" 859 End 860 861 It 'reports EDITOR exit code' 862 exit42() { mocklog editor "$@"; return 42; } 863 EDITOR=exit42 864 result() { 865 %text:expand 866 #|$ mktemp -u ${SECURE_TMPDIR}/XXXXXX 867 #|$ scm_begin 868 #|$ editor ${SECURE_TMPDIR}/XXXXXX-subdir-new.txt 869 #|Editor "exit42" exited with code 42 870 } 871 When run do_edit subdir/new 872 The status should equal 42 873 The error should equal "$(result)" 874 End 875 End 876 877 Describe 'do_encrypt' 878 unset PASHAGE_RECIPIENTS_FILE 879 unset PASSAGE_RECIPIENTS_FILE 880 unset PASHAGE_RECIPIENTS 881 unset PASSAGE_RECIPIENTS 882 AGE=age 883 PREFIX=/prefix 884 dirname() { @dirname "$@"; } 885 886 age() { mocklog age "$@"; } 887 mkdir() { mocklog mkdir "$@"; } 888 889 setup() { 890 %= data >"${SHELLSPEC_WORKDIR}/existing-file" 891 } 892 BeforeAll 'setup' 893 894 It 'falls back on identity when there is no recipient' 895 OVERWRITE=yes 896 IDENTITIES_FILE='/path/to/identity' 897 set_LOCAL_RECIPIENT_FILE() { 898 LOCAL_RECIPIENT_FILE='' 899 } 900 result() { 901 %text 902 #|$ mkdir -p /prefix/encrypted 903 #|$ age -e -i /path/to/identity -o /prefix/encrypted/file.age 904 } 905 When run do_encrypt 'encrypted/file.age' 906 The status should be success 907 The error should equal "$(result)" 908 End 909 910 It 'overwrites existing file only once' 911 OVERWRITE=once 912 PREFIX="${SHELLSPEC_WORKDIR}" 913 set_LOCAL_RECIPIENT_FILE() { 914 LOCAL_RECIPIENT_FILE='/path/to/recipients' 915 } 916 preserve() { %preserve OVERWRITE; } 917 AfterRun 'preserve' 918 result() { 919 %text:expand 920 #|$ mkdir -p ${PREFIX} 921 #|$ age -e -R /path/to/recipients -o ${PREFIX}/existing-file 922 } 923 When run do_encrypt 'existing-file' 924 The status should be success 925 The error should equal "$(result)" 926 The variable OVERWRITE should equal no 927 End 928 929 It 'overwrites existing file when requested' 930 OVERWRITE=yes 931 PREFIX="${SHELLSPEC_WORKDIR}" 932 set_LOCAL_RECIPIENT_FILE() { 933 LOCAL_RECIPIENT_FILE='/path/to/recipients' 934 } 935 preserve() { %preserve OVERWRITE; } 936 AfterRun 'preserve' 937 result() { 938 %text:expand 939 #|$ mkdir -p ${PREFIX} 940 #|$ age -e -R /path/to/recipients -o ${PREFIX}/existing-file 941 } 942 When run do_encrypt 'existing-file' 943 The status should be success 944 The error should equal "$(result)" 945 The variable OVERWRITE should equal yes 946 End 947 948 It 'refuses to overwrite an existing file' 949 OVERWRITE=no 950 PREFIX="${SHELLSPEC_WORKDIR}" 951 set_LOCAL_RECIPIENT_FILE() { 952 LOCAL_RECIPIENT_FILE='/path/to/recipients' 953 } 954 When run do_encrypt 'existing-file' 955 The error should equal 'Refusing to overwite existing-file' 956 The status should equal 1 957 End 958 959 It 'uses PASSAGE_RECIPIENTS rather than LOCAL_RECIPIENT_FILE' 960 PASSAGE_RECIPIENTS='inline-recipient-1 inline-recipient-2' 961 set_LOCAL_RECIPIENT_FILE() { 962 LOCAL_RECIPIENT_FILE='shadowed' 963 } 964 OVERWRITE=yes 965 result() { 966 %text 967 #|$ mkdir -p /prefix/encrypted 968 #|$ age -e -r inline-recipient-1 -r inline-recipient-2 -o /prefix/encrypted/file.age 969 } 970 971 When call do_encrypt 'encrypted/file.age' 972 The status should be success 973 The error should equal "$(result)" 974 End 975 976 It 'uses PASHAGE_RECIPIENTS rather than PASSAGE_RECIPIENTS' 977 PASHAGE_RECIPIENTS='inline-recipient-1 inline-recipient-2' 978 PASSAGE_RECIPIENTS='shadowed' 979 set_LOCAL_RECIPIENT_FILE() { 980 LOCAL_RECIPIENT_FILE='shadowed' 981 } 982 OVERWRITE=yes 983 result() { 984 %text 985 #|$ mkdir -p /prefix/encrypted 986 #|$ age -e -r inline-recipient-1 -r inline-recipient-2 -o /prefix/encrypted/file.age 987 } 988 989 When call do_encrypt 'encrypted/file.age' 990 The status should be success 991 The error should equal "$(result)" 992 End 993 994 It 'uses PASSAGE_RECIPIENTS_FILE rather than PASHAGE_RECIPIENTS' 995 PASSAGE_RECIPIENTS_FILE='/path/to/recipients' 996 PASHAGE_RECIPIENTS='shadowed' 997 PASSAGE_RECIPIENTS='shadowed' 998 set_LOCAL_RECIPIENT_FILE() { 999 LOCAL_RECIPIENT_FILE='shadowed' 1000 } 1001 OVERWRITE=yes 1002 result() { 1003 %text 1004 #|$ mkdir -p /prefix/encrypted 1005 #|$ age -e -R /path/to/recipients -o /prefix/encrypted/file.age 1006 } 1007 1008 When call do_encrypt 'encrypted/file.age' 1009 The status should be success 1010 The error should equal "$(result)" 1011 End 1012 1013 It 'uses PASHAGE_RECIPIENTS_FILE rather than PASSAGE_RECIPIENTS_FILE' 1014 PASHAGE_RECIPIENTS_FILE='/path/to/recipients' 1015 PASSAGE_RECIPIENTS_FILE='shadowed' 1016 PASHAGE_RECIPIENTS='shadowed' 1017 PASSAGE_RECIPIENTS='shadowed' 1018 set_LOCAL_RECIPIENT_FILE() { 1019 LOCAL_RECIPIENT_FILE='shadowed' 1020 } 1021 OVERWRITE=yes 1022 result() { 1023 %text 1024 #|$ mkdir -p /prefix/encrypted 1025 #|$ age -e -R /path/to/recipients -o /prefix/encrypted/file.age 1026 } 1027 1028 When call do_encrypt 'encrypted/file.age' 1029 The status should be success 1030 The error should equal "$(result)" 1031 End 1032 End 1033 1034 Describe 'do_generate' 1035 DECISION=default 1036 MULTILINE=no 1037 PREFIX="${SHELLSPEC_WORKDIR}/prefix" 1038 SHOW=none 1039 1040 dd() { %- 0123456789 ; } 1041 dirname() { @dirname "$@"; } 1042 tr() { @tr "$@"; } 1043 1044 do_encrypt() { 1045 mocklog do_encrypt "$@" 1046 @sed 's/^/> /' >&2 1047 } 1048 1049 do_show() { 1050 mocklog do_show "$@" 1051 @sed 's/^/> /' >&2 1052 } 1053 1054 mkdir() { mocklog mkdir "$@"; } 1055 scm_add() { mocklog scm_add "$@"; } 1056 scm_begin() { mocklog scm_begin "$@"; } 1057 scm_commit() { mocklog scm_commit "$@"; } 1058 1059 setup() { 1060 @mkdir -p "${PREFIX}/suspicious.age" 1061 %putsn data >"${PREFIX}/existing.age" 1062 } 1063 1064 cleanup() { 1065 @rm -rf "${PREFIX}" 1066 } 1067 1068 BeforeEach setup 1069 AfterEach cleanup 1070 1071 It 'detects short reads' 1072 When run do_generate suspicious 12 '[:alnum:]' 1073 The output should be blank 1074 The error should equal \ 1075 'Error while generating password: 10/12 bytes read' 1076 The status should equal 1 1077 End 1078 1079 It 'aborts when a directory is in the way' 1080 result(){ 1081 %text:expand 1082 #|$ scm_begin 1083 #|$ mkdir -p -- ${PREFIX} 1084 #|Cannot replace directory suspicious.age 1085 } 1086 When run do_generate suspicious 10 '[:alnum:]' 1087 The output should be blank 1088 The error should equal "$(result)" 1089 The status should equal 1 1090 End 1091 1092 It 'generates a new file' 1093 result(){ 1094 %text:expand 1095 #|$ scm_begin 1096 #|$ mkdir -p -- ${PREFIX}/sub 1097 #|$ do_encrypt sub/new.age 1098 #|> 0123456789 1099 #|$ scm_add ${PREFIX}/sub/new.age 1100 #|$ scm_commit Add generated password for sub/new. 1101 #|$ do_show sub/new 1102 #|> 0123456789 1103 } 1104 When call do_generate sub/new 10 '[:alnum:]' 1105 The status should be success 1106 The output should be blank 1107 The error should equal "$(result)" 1108 End 1109 1110 It 'displays a title before text output' 1111 SHOW=text 1112 BOLD_TEXT='(B)' 1113 NORMAL_TEXT='(N)' 1114 UNDERLINE_TEXT='(U)' 1115 NO_UNDERLINE_TEXT='(!U)' 1116 result(){ 1117 %text:expand 1118 #|$ scm_begin 1119 #|$ mkdir -p -- ${PREFIX}/sub 1120 #|$ do_encrypt sub/new.age 1121 #|> 0123456789 1122 #|$ scm_add ${PREFIX}/sub/new.age 1123 #|$ scm_commit Add generated password for sub/new. 1124 #|$ do_show sub/new 1125 #|> 0123456789 1126 } 1127 When call do_generate sub/new 10 '[:alnum:]' 1128 The status should be success 1129 The output should equal \ 1130 '(B)The generated password for (U)sub/new(!U) is:(N)' 1131 The error should equal "$(result)" 1132 End 1133 1134 It 'overwrites an existing file when forced' 1135 MULTILINE=no 1136 OVERWRITE=yes 1137 result(){ 1138 %text:expand 1139 #|$ scm_begin 1140 #|$ mkdir -p -- ${PREFIX} 1141 #|$ do_encrypt existing.age 1142 #|> 0123456789 1143 #|$ scm_add ${PREFIX}/existing.age 1144 #|$ scm_commit Add generated password for existing. 1145 #|$ do_show existing 1146 #|> 0123456789 1147 } 1148 When call do_generate existing 10 '[:alnum:]' 1149 The status should be success 1150 The output should be blank 1151 The error should equal "$(result)" 1152 End 1153 1154 It 'overwrites an existing file after confirmation' 1155 MULTILINE=no 1156 OVERWRITE=no 1157 yesno() { 1158 mocklog yesno "$@"; 1159 ANSWER=y 1160 } 1161 result(){ 1162 %text:expand 1163 #|$ scm_begin 1164 #|$ mkdir -p -- ${PREFIX} 1165 #|$ yesno An entry already exists for existing. Overwrite it? 1166 #|$ do_encrypt existing.age 1167 #|> 0123456789 1168 #|$ scm_add ${PREFIX}/existing.age 1169 #|$ scm_commit Add generated password for existing. 1170 #|$ do_show existing 1171 #|> 0123456789 1172 } 1173 When call do_generate existing 10 '[:alnum:]' 1174 The status should be success 1175 The output should be blank 1176 The error should equal "$(result)" 1177 The variable OVERWRITE should equal 'once' 1178 End 1179 1180 It 'does not overwrite an existing file without confirmation' 1181 MULTILINE=no 1182 OVERWRITE=no 1183 yesno() { 1184 mocklog yesno "$@"; 1185 ANSWER=n 1186 } 1187 result(){ 1188 %text:expand 1189 #|$ scm_begin 1190 #|$ mkdir -p -- ${PREFIX} 1191 #|$ yesno An entry already exists for existing. Overwrite it? 1192 } 1193 When call do_generate existing 10 '[:alnum:]' 1194 The status should be success 1195 The output should be blank 1196 The error should equal "$(result)" 1197 End 1198 1199 It 'updates the first line of an existing file' 1200 MULTILINE=no 1201 OVERWRITE=reuse 1202 do_decrypt() { 1203 mocklog do_decrypt "$@" 1204 %text 1205 #|old password 1206 #|line 2 1207 #|line 3 1208 } 1209 mv() { mocklog mv "$@"; } 1210 result(){ 1211 %text:expand 1212 #|$ scm_begin 1213 #|$ mkdir -p -- ${PREFIX} 1214 #|$ do_decrypt ${PREFIX}/existing.age 1215 #|$ do_encrypt existing.age 1216 #|> 0123456789 1217 #|> line 2 1218 #|> line 3 1219 #|$ scm_add ${PREFIX}/existing.age 1220 #|$ scm_commit Replace generated password for existing. 1221 #|$ do_show existing 1222 #|> 0123456789 1223 } 1224 When call do_generate existing 10 '[:alnum:]' 1225 The status should be success 1226 The output should equal 'Decrypting previous secret for existing' 1227 The error should equal "$(result)" 1228 End 1229 1230 It 'updates the only line of an existing one-line file' 1231 MULTILINE=no 1232 OVERWRITE=reuse 1233 do_decrypt() { 1234 mocklog do_decrypt "$@" 1235 %text 1236 #|old password 1237 } 1238 mv() { mocklog mv "$@"; } 1239 result(){ 1240 %text:expand 1241 #|$ scm_begin 1242 #|$ mkdir -p -- ${PREFIX} 1243 #|$ do_decrypt ${PREFIX}/existing.age 1244 #|$ do_encrypt existing.age 1245 #|> 0123456789 1246 #|$ scm_add ${PREFIX}/existing.age 1247 #|$ scm_commit Replace generated password for existing. 1248 #|$ do_show existing 1249 #|> 0123456789 1250 } 1251 When call do_generate existing 10 '[:alnum:]' 1252 The status should be success 1253 The output should equal 'Decrypting previous secret for existing' 1254 The error should equal "$(result)" 1255 End 1256 1257 It 'saves the password after showing it and getting confirmation' 1258 DECISION=interactive 1259 yesno() { 1260 mocklog yesno "$@" 1261 ANSWER=y 1262 } 1263 result(){ 1264 %text:expand 1265 #|$ do_show sub/new 1266 #|> 0123456789 1267 #|$ yesno Save generated password for sub/new? 1268 #|$ scm_begin 1269 #|$ mkdir -p -- ${PREFIX}/sub 1270 #|$ do_encrypt sub/new.age 1271 #|> 0123456789 1272 #|$ scm_add ${PREFIX}/sub/new.age 1273 #|$ scm_commit Add generated password for sub/new. 1274 } 1275 When call do_generate sub/new 10 '[:alnum:]' 1276 The status should be success 1277 The output should be blank 1278 The error should equal "$(result)" 1279 End 1280 1281 It 'does not save the password after showing it and getting cancellation' 1282 DECISION=interactive 1283 yesno() { 1284 mocklog yesno "$@" 1285 ANSWER=n 1286 } 1287 result(){ 1288 %text:expand 1289 #|$ do_show sub/new 1290 #|> 0123456789 1291 #|$ yesno Save generated password for sub/new? 1292 } 1293 When call do_generate sub/new 10 '[:alnum:]' 1294 The status should be success 1295 The output should be blank 1296 The error should equal "$(result)" 1297 End 1298 1299 It 'accepts an extra line after the generated secret' 1300 MULTILINE=yes 1301 Data 'comment line' 1302 When call do_generate sub/new 10 '[:alnum:]' 1303 result(){ 1304 %text:expand 1305 #|$ scm_begin 1306 #|$ mkdir -p -- ${PREFIX}/sub 1307 #|$ do_encrypt sub/new.age 1308 #|> 0123456789 1309 #|> comment line 1310 #|$ scm_add ${PREFIX}/sub/new.age 1311 #|$ scm_commit Add generated password for sub/new. 1312 #|$ do_show sub/new 1313 #|> 0123456789 1314 } 1315 The status should be success 1316 The output should equal 'Enter extra secrets then Ctrl+D when finished:' 1317 The error should equal "$(result)" 1318 End 1319 1320 It 'accepts several lines after the generated secret' 1321 MULTILINE=yes 1322 OVERWRITE=no 1323 Data 1324 #|comment line 1325 #|end of secret 1326 End 1327 yesno() { 1328 mocklog yesno "$@" 1329 ANSWER=y 1330 } 1331 When call do_generate existing 10 '[:alnum:]' 1332 result(){ 1333 %text:expand 1334 #|$ scm_begin 1335 #|$ mkdir -p -- ${PREFIX} 1336 #|$ yesno An entry already exists for existing. Overwrite it? 1337 #|$ do_encrypt existing.age 1338 #|> 0123456789 1339 #|> comment line 1340 #|> end of secret 1341 #|$ scm_add ${PREFIX}/existing.age 1342 #|$ scm_commit Add generated password for existing. 1343 #|$ do_show existing 1344 #|> 0123456789 1345 } 1346 The status should be success 1347 The output should equal 'Enter extra secrets then Ctrl+D when finished:' 1348 The error should equal "$(result)" 1349 End 1350 1351 It 'does not asks for extra lines after refusing to overwrite' 1352 MULTILINE=yes 1353 OVERWRITE=no 1354 Data 'n' 1355 yesno() { 1356 mocklog yesno "$@" 1357 ANSWER=n 1358 } 1359 When call do_generate existing 10 '[:alnum:]' 1360 result(){ 1361 %text:expand 1362 #|$ scm_begin 1363 #|$ mkdir -p -- ${PREFIX} 1364 #|$ yesno An entry already exists for existing. Overwrite it? 1365 } 1366 The status should be success 1367 The output should be blank 1368 The error should equal "$(result)" 1369 End 1370 1371 It 'inserts extra lines after the in-place secrets' 1372 MULTILINE=yes 1373 OVERWRITE=reuse 1374 do_decrypt() { 1375 mocklog do_decrypt "$@" 1376 %text:expand 1377 #|old password 1378 #|old annotation 1379 #|end of $* 1380 } 1381 Data 1382 #|comment line 1383 #|end of secret 1384 End 1385 yesno() { 1386 mocklog yesno "$@" 1387 ANSWER=y 1388 } 1389 When call do_generate existing 10 '[:alnum:]' 1390 o_result(){ 1391 %text 1392 #|Decrypting previous secret for existing 1393 #|Enter extra secrets then Ctrl+D when finished: 1394 } 1395 result(){ 1396 %text:expand 1397 #|$ scm_begin 1398 #|$ mkdir -p -- ${PREFIX} 1399 #|$ do_decrypt ${PREFIX}/existing.age 1400 #|$ do_encrypt existing.age 1401 #|> 0123456789 1402 #|> old annotation 1403 #|> end of ${PREFIX}/existing.age 1404 #|> comment line 1405 #|> end of secret 1406 #|$ scm_add ${PREFIX}/existing.age 1407 #|$ scm_commit Replace generated password for existing. 1408 #|$ do_show existing 1409 #|> 0123456789 1410 } 1411 The status should be success 1412 The output should equal "$(o_result)" 1413 The error should equal "$(result)" 1414 End 1415 End 1416 1417 Describe 'do_grep' 1418 PREFIX="${SHELLSPEC_WORKDIR}/prefix" 1419 BLUE_TEXT='(B)' 1420 BOLD_TEXT='(G)' 1421 NORMAL_TEXT='(N)' 1422 1423 do_decrypt() { @cat "$1"; } 1424 grep() { @grep "$1"; } 1425 1426 setup() { 1427 @mkdir -p "${PREFIX}/subdir" 1428 %putsn data >"${PREFIX}/non-match.age" 1429 %text >"${PREFIX}/subdir/match.age" 1430 #|non-match 1431 #|other 1432 #|suffix 1433 } 1434 1435 cleanup() { 1436 @rm -rf "${PREFIX}" 1437 } 1438 1439 BeforeEach setup 1440 AfterEach cleanup 1441 1442 It 'outputs matching files' 1443 result(){ 1444 %text 1445 #|(B)subdir/(G)match(N): 1446 #|other 1447 } 1448 start_do_grep(){ 1449 ( cd "${PREFIX}" && do_grep '' "$@" ) 1450 } 1451 When call start_do_grep ot 1452 The status should be success 1453 The output should equal "$(result)" 1454 End 1455 1456 It 'outputs all the matching lines' 1457 result(){ 1458 %text 1459 #|(B)subdir/(G)match(N): 1460 #|other 1461 #|suffix 1462 } 1463 start_do_grep(){ 1464 ( cd "${PREFIX}" && do_grep '' "$@" ) 1465 } 1466 When call start_do_grep -vea 1467 The status should be success 1468 The output should equal "$(result)" 1469 End 1470 End 1471 1472 Describe 'do_init' 1473 PREFIX="${SHELLSPEC_WORKDIR}/prefix" 1474 DECISION=default 1475 OVERWRITE=no 1476 1477 do_reencrypt_dir() { mocklog do_reencrypt_dir "$@"; } 1478 mkdir() { mocklog mkdir "$@"; @mkdir "$@"; } 1479 scm_add() { mocklog scm_add "$@"; } 1480 scm_begin() { mocklog scm_begin "$@"; } 1481 scm_commit() { mocklog scm_commit "$@"; } 1482 1483 cleanup() { 1484 @rm -rf "${PREFIX}" 1485 } 1486 1487 AfterEach cleanup 1488 1489 It 'initializes the store' 1490 result() { 1491 %text:expand 1492 #|$ mkdir -p -- ${PREFIX} 1493 #|$ scm_begin 1494 #|$ scm_add .age-recipients 1495 #|$ do_reencrypt_dir ${PREFIX} 1496 #|$ scm_commit Set age recipients at store root 1497 } 1498 When call do_init '' identity 1499 The status should be success 1500 The output should equal 'Password store recipients set at store root' 1501 The error should equal "$(result)" 1502 The file "${PREFIX}/.age-recipients" should be exist 1503 The contents of the file "${PREFIX}/.age-recipients" should equal \ 1504 'identity' 1505 End 1506 1507 It 'initializes a subdirectory' 1508 result() { 1509 %text:expand 1510 #|$ mkdir -p -- ${PREFIX}/sub 1511 #|$ scm_begin 1512 #|$ scm_add sub/.age-recipients 1513 #|$ do_reencrypt_dir ${PREFIX}/sub 1514 #|$ scm_commit Set age recipients at sub 1515 } 1516 two_id() { 1517 %text 1518 #|identity 1 1519 #|identity 2 1520 } 1521 When call do_init sub 'identity 1' 'identity 2' 1522 The status should be success 1523 The output should equal 'Password store recipients set at sub' 1524 The error should equal "$(result)" 1525 The file "${PREFIX}/sub/.age-recipients" should be exist 1526 The contents of the file "${PREFIX}/sub/.age-recipients" should equal \ 1527 "$(two_id)" 1528 End 1529 1530 It 'can initialize without re-encryption' 1531 DECISION=keep 1532 result() { 1533 %text:expand 1534 #|$ mkdir -p -- ${PREFIX} 1535 #|$ scm_begin 1536 #|$ scm_add .age-recipients 1537 #|$ scm_commit Set age recipients at store root 1538 } 1539 When call do_init '' identity 1540 The status should be success 1541 The output should equal 'Password store recipients set at store root' 1542 The error should equal "$(result)" 1543 The file "${PREFIX}/.age-recipients" should be exist 1544 The contents of the file "${PREFIX}/.age-recipients" should equal \ 1545 'identity' 1546 End 1547 End 1548 1549 Describe 'do_insert' 1550 PREFIX="${SHELLSPEC_WORKDIR}/prefix" 1551 1552 do_encrypt() { 1553 mocklog do_encrypt "$@" 1554 @sed 's/^/> /' >&2 1555 } 1556 1557 dirname() { @dirname "$@"; } 1558 head() { @head "$@"; } 1559 1560 mkdir() { mocklog mkdir "$@"; } 1561 scm_add() { mocklog scm_add "$@"; } 1562 scm_begin() { mocklog scm_begin "$@"; } 1563 scm_commit() { mocklog scm_commit "$@"; } 1564 1565 setup() { 1566 @mkdir -p "${PREFIX}" 1567 %putsn data >"${PREFIX}/existing.age" 1568 } 1569 1570 cleanup() { 1571 @rm -rf "${PREFIX}" 1572 } 1573 1574 BeforeEach setup 1575 AfterEach cleanup 1576 1577 It 'inserts a single line from standard input' 1578 ECHO=yes 1579 MULTILINE=no 1580 OVERWRITE=yes 1581 result() { 1582 %text:expand 1583 #|$ scm_begin 1584 #|$ mkdir -p -- ${PREFIX}/subdir 1585 #|$ do_encrypt subdir/new.age 1586 #|> line 1 1587 #|$ scm_add subdir/new.age 1588 #|$ scm_commit Add given password for subdir/new to store. 1589 } 1590 Data 1591 #|line 1 1592 #|line 2 1593 #|line 3 1594 End 1595 1596 When call do_insert 'subdir/new' 1597 The status should be success 1598 The output should equal 'Enter password for subdir/new: ' 1599 The error should equal "$(result)" 1600 End 1601 1602 It 'inserts the standard input until the first blank line' 1603 MULTILINE=yes 1604 OVERWRITE=yes 1605 o_result() { %text 1606 #|Enter contents of subdir/new and 1607 #|press Ctrl+D or enter an empty line when finished: 1608 } 1609 result() { 1610 %text:expand 1611 #|$ scm_begin 1612 #|$ mkdir -p -- ${PREFIX}/subdir 1613 #|$ do_encrypt subdir/new.age 1614 #|> line 1 1615 #|> line 2 1616 #|$ scm_add subdir/new.age 1617 #|$ scm_commit Add given password for subdir/new to store. 1618 } 1619 Data 1620 #|line 1 1621 #|line 2 1622 #| 1623 #|line 3 1624 #|line 4 1625 End 1626 1627 When call do_insert 'subdir/new' 1628 The status should be success 1629 The output should equal "$(o_result)" 1630 The error should equal "$(result)" 1631 End 1632 1633 It 'inserts the whole standard input without blank line' 1634 MULTILINE=yes 1635 OVERWRITE=yes 1636 o_result() { %text 1637 #|Enter contents of subdir/new and 1638 #|press Ctrl+D or enter an empty line when finished: 1639 } 1640 result() { 1641 %text:expand 1642 #|$ scm_begin 1643 #|$ mkdir -p -- ${PREFIX}/subdir 1644 #|$ do_encrypt subdir/new.age 1645 #|> line 1 1646 #|> line 2 1647 #|> line 3 1648 #|$ scm_add subdir/new.age 1649 #|$ scm_commit Add given password for subdir/new to store. 1650 } 1651 Data 1652 #|line 1 1653 #|line 2 1654 #|line 3 1655 End 1656 1657 When call do_insert 'subdir/new' 1658 The status should be success 1659 The output should equal "$(o_result)" 1660 The error should equal "$(result)" 1661 End 1662 1663 It 'checks password confirmation before inserting it' 1664 ECHO=no 1665 MULTILINE=no 1666 OVERWRITE=yes 1667 stty() { true; } 1668 o_result() { 1669 %text | @sed 's/\$$//' 1670 #|Enter password for subdir/new: $ 1671 #|Retype password for subdir/new: $ 1672 #|Passwords don't match$ 1673 #|Enter password for subdir/new: $ 1674 #|Retype password for subdir/new: $ 1675 } 1676 e_result() { 1677 %text:expand 1678 #|$ scm_begin 1679 #|$ mkdir -p -- ${PREFIX}/subdir 1680 #|$ do_encrypt subdir/new.age 1681 #|> line 3 1682 #|$ scm_add subdir/new.age 1683 #|$ scm_commit Add given password for subdir/new to store. 1684 } 1685 Data 1686 #|line 1 1687 #|line 2 1688 #|line 3 1689 #|line 3 1690 #|line 5 1691 End 1692 1693 When call do_insert 'subdir/new' 1694 The status should be success 1695 The output should equal "$(o_result)" 1696 The error should equal "$(e_result)" 1697 End 1698 1699 It 'asks confirmation before overwriting' 1700 MULTILINE=yes 1701 OVERWRITE=no 1702 yesno() { 1703 mocklog yesno "$@" 1704 ANSWER=y 1705 } 1706 o_result() { %text 1707 #|Enter contents of existing and 1708 #|press Ctrl+D or enter an empty line when finished: 1709 } 1710 result() { 1711 %text:expand 1712 #|$ yesno An entry already exists for existing. Overwrite it? 1713 #|$ scm_begin 1714 #|$ mkdir -p -- ${PREFIX} 1715 #|$ do_encrypt existing.age 1716 #|> password 1717 #|$ scm_add existing.age 1718 #|$ scm_commit Add given password for existing to store. 1719 } 1720 Data 'password' 1721 1722 When call do_insert 'existing' 1723 The status should be success 1724 The output should equal "$(o_result)" 1725 The error should equal "$(result)" 1726 The variable OVERWRITE should equal once 1727 End 1728 1729 It 'does not overwrite without confirmation' 1730 MULTILINE=yes 1731 OVERWRITE=no 1732 yesno() { 1733 mocklog yesno "$@" 1734 ANSWER=n 1735 } 1736 result() { 1737 %text:expand 1738 #|$ yesno An entry already exists for existing. Overwrite it? 1739 } 1740 Data 'password' 1741 1742 When call do_insert 'existing' 1743 The status should be success 1744 The output should be blank 1745 The error should equal "$(result)" 1746 End 1747 1748 It 'does not ask confirmation before overwriting when forced' 1749 MULTILINE=yes 1750 OVERWRITE=yes 1751 yesno() { 1752 mocklog yesno "$@" 1753 ANSWER=y 1754 } 1755 o_result() { %text 1756 #|Enter contents of existing and 1757 #|press Ctrl+D or enter an empty line when finished: 1758 } 1759 result() { 1760 %text:expand 1761 #|$ scm_begin 1762 #|$ mkdir -p -- ${PREFIX} 1763 #|$ do_encrypt existing.age 1764 #|> password 1765 #|$ scm_add existing.age 1766 #|$ scm_commit Add given password for existing to store. 1767 } 1768 Data 'password' 1769 1770 When call do_insert 'existing' 1771 The status should be success 1772 The output should equal "$(o_result)" 1773 The error should equal "$(result)" 1774 End 1775 End 1776 1777 Describe 'do_list_or_show' 1778 PREFIX="${SHELLSPEC_WORKDIR}/prefix" 1779 1780 do_decrypt() { 1781 mocklog do_decrypt "$@" 1782 %putsn data 1783 } 1784 1785 do_decrypt_gpg() { 1786 mocklog do_decrypt_gpg "$@" 1787 %putsn data 1788 } 1789 1790 do_show() { 1791 @cat >/dev/null 1792 mocklog do_show "$@" 1793 } 1794 1795 do_tree() { mocklog do_tree "$@"; } 1796 1797 setup() { 1798 @mkdir -p "${PREFIX}/subdir/subsub" "${PREFIX}/empty" "${PREFIX}/other" 1799 %putsn data >"${PREFIX}/root.age" 1800 %putsn data >"${PREFIX}/subdir/hidden" 1801 %putsn data >"${PREFIX}/subdir/subsub/old.gpg" 1802 %putsn data >"${PREFIX}/other/lower.age" 1803 } 1804 1805 cleanup() { 1806 @rm -rf "${PREFIX}" 1807 } 1808 1809 BeforeEach setup 1810 AfterEach cleanup 1811 1812 It 'lists the whole store' 1813 When call do_list_or_show '' 1814 The status should be success 1815 The output should be blank 1816 The error should equal "$ do_tree ${PREFIX} Password Store" 1817 End 1818 1819 It 'shows a decrypted age file' 1820 result() { 1821 %text:expand 1822 #|$ do_decrypt ${PREFIX}/other/lower.age 1823 #|$ do_show other/lower 1824 } 1825 When call do_list_or_show 'other/lower' 1826 The status should be success 1827 The error should equal "$(result)" 1828 End 1829 1830 It 'shows a decrypted gpg file' 1831 result() { 1832 %text:expand 1833 #|$ do_decrypt_gpg ${PREFIX}/subdir/subsub/old.gpg 1834 #|$ do_show subdir/subsub/old 1835 } 1836 When call do_list_or_show 'subdir/subsub/old' 1837 The status should be success 1838 The error should equal "$(result)" 1839 End 1840 1841 It 'lists a subdirectory' 1842 When call do_list_or_show 'subdir' 1843 The status should be success 1844 The output should be blank 1845 The error should equal "$ do_tree ${PREFIX}/subdir subdir" 1846 End 1847 1848 It 'does not show a non-encrypted file' 1849 When run do_list_or_show 'subdir/hidden' 1850 The output should be blank 1851 The error should equal \ 1852 'Error: subdir/hidden is not in the password store.' 1853 The status should equal 1 1854 End 1855 End 1856 1857 Describe 'do_reencrypt' 1858 PREFIX="${SHELLSPEC_WORKDIR}/prefix" 1859 DECISION=default 1860 1861 do_decrypt() { 1862 mocklog do_decrypt "$@" 1863 %putsn 'secret data' 1864 } 1865 1866 do_encrypt() { 1867 @cat >/dev/null 1868 mocklog do_encrypt "$@" 1869 } 1870 1871 mktemp() { %putsn "${2-$1}"; } 1872 mv() { mocklog mv "$@"; } 1873 scm_add() { mocklog scm_add "$@"; } 1874 scm_begin() { mocklog scm_begin "$@"; } 1875 scm_commit() { mocklog scm_commit "$@"; } 1876 1877 setup() { 1878 @mkdir -p "${PREFIX}/subdir/subsub" 1879 %putsn data >"${PREFIX}/root.age" 1880 %putsn data >"${PREFIX}/subdir/middle.age" 1881 %putsn data >"${PREFIX}/subdir/subsub/deep.age" 1882 } 1883 1884 cleanup() { 1885 @rm -rf "${PREFIX}" 1886 } 1887 1888 BeforeEach setup 1889 AfterEach cleanup 1890 1891 It 're-encrypts a single file' 1892 result() { 1893 %text:expand 1894 #|$ scm_begin 1895 #|$ do_decrypt ${PREFIX}/subdir/subsub/deep.age 1896 #|$ do_encrypt subdir/subsub/deep-XXXXXXXXX.age 1897 #|$ mv -f -- ${PREFIX}/subdir/subsub/deep-XXXXXXXXX.age ${PREFIX}/subdir/subsub/deep.age 1898 #|$ scm_add subdir/subsub/deep.age 1899 #|$ scm_commit Re-encrypt subdir/subsub/deep 1900 } 1901 When call do_reencrypt subdir/subsub/deep 1902 The status should be success 1903 The output should be blank 1904 The error should equal "$(result)" 1905 End 1906 1907 It 'recursively re-encrypts a directory' 1908 result() { 1909 %text:expand 1910 #|$ scm_begin 1911 #|$ do_decrypt ${PREFIX}/subdir/middle.age 1912 #|$ do_encrypt subdir/middle-XXXXXXXXX.age 1913 #|$ mv -f -- ${PREFIX}/subdir/middle-XXXXXXXXX.age ${PREFIX}/subdir/middle.age 1914 #|$ scm_add subdir/middle.age 1915 #|$ do_decrypt ${PREFIX}/subdir/subsub/deep.age 1916 #|$ do_encrypt subdir/subsub/deep-XXXXXXXXX.age 1917 #|$ mv -f -- ${PREFIX}/subdir/subsub/deep-XXXXXXXXX.age ${PREFIX}/subdir/subsub/deep.age 1918 #|$ scm_add subdir/subsub/deep.age 1919 #|$ scm_commit Re-encrypt subdir/ 1920 } 1921 When call do_reencrypt subdir/ 1922 The status should be success 1923 The output should be blank 1924 The error should equal "$(result)" 1925 End 1926 1927 It 'recursively re-encrypts the whole store as /' 1928 result() { 1929 %text:expand 1930 #|$ scm_begin 1931 #|$ do_decrypt ${PREFIX}/root.age 1932 #|$ do_encrypt root-XXXXXXXXX.age 1933 #|$ mv -f -- ${PREFIX}/root-XXXXXXXXX.age ${PREFIX}/root.age 1934 #|$ scm_add root.age 1935 #|$ do_decrypt ${PREFIX}/subdir/middle.age 1936 #|$ do_encrypt subdir/middle-XXXXXXXXX.age 1937 #|$ mv -f -- ${PREFIX}/subdir/middle-XXXXXXXXX.age ${PREFIX}/subdir/middle.age 1938 #|$ scm_add subdir/middle.age 1939 #|$ do_decrypt ${PREFIX}/subdir/subsub/deep.age 1940 #|$ do_encrypt subdir/subsub/deep-XXXXXXXXX.age 1941 #|$ mv -f -- ${PREFIX}/subdir/subsub/deep-XXXXXXXXX.age ${PREFIX}/subdir/subsub/deep.age 1942 #|$ scm_add subdir/subsub/deep.age 1943 #|$ scm_commit Re-encrypt / 1944 } 1945 When call do_reencrypt / 1946 The status should be success 1947 The output should be blank 1948 The error should equal "$(result)" 1949 End 1950 1951 It 'recursively re-encrypts the whole store as the empty string' 1952 result() { 1953 %text:expand 1954 #|$ scm_begin 1955 #|$ do_decrypt ${PREFIX}/root.age 1956 #|$ do_encrypt root-XXXXXXXXX.age 1957 #|$ mv -f -- ${PREFIX}/root-XXXXXXXXX.age ${PREFIX}/root.age 1958 #|$ scm_add root.age 1959 #|$ do_decrypt ${PREFIX}/subdir/middle.age 1960 #|$ do_encrypt subdir/middle-XXXXXXXXX.age 1961 #|$ mv -f -- ${PREFIX}/subdir/middle-XXXXXXXXX.age ${PREFIX}/subdir/middle.age 1962 #|$ scm_add subdir/middle.age 1963 #|$ do_decrypt ${PREFIX}/subdir/subsub/deep.age 1964 #|$ do_encrypt subdir/subsub/deep-XXXXXXXXX.age 1965 #|$ mv -f -- ${PREFIX}/subdir/subsub/deep-XXXXXXXXX.age ${PREFIX}/subdir/subsub/deep.age 1966 #|$ scm_add subdir/subsub/deep.age 1967 #|$ scm_commit Re-encrypt / 1968 } 1969 When call do_reencrypt '' 1970 The status should be success 1971 The output should be blank 1972 The error should equal "$(result)" 1973 End 1974 1975 It 'asks for confirmation before each file' 1976 DECISION=interactive 1977 YESNO_NEXT=n 1978 yesno() { 1979 mocklog yesno "$@" 1980 ANSWER="${YESNO_NEXT}" 1981 YESNO_NEXT=y 1982 } 1983 result() { 1984 %text:expand 1985 #|$ scm_begin 1986 #|$ yesno Re-encrypt subdir/middle? 1987 #|$ yesno Re-encrypt subdir/subsub/deep? 1988 #|$ do_decrypt ${PREFIX}/subdir/subsub/deep.age 1989 #|$ do_encrypt subdir/subsub/deep-XXXXXXXXX.age 1990 #|$ mv -f -- ${PREFIX}/subdir/subsub/deep-XXXXXXXXX.age ${PREFIX}/subdir/subsub/deep.age 1991 #|$ scm_add subdir/subsub/deep.age 1992 #|$ scm_commit Re-encrypt subdir/ 1993 } 1994 When call do_reencrypt subdir 1995 The status should be success 1996 The output should be blank 1997 The error should equal "$(result)" 1998 End 1999 2000 It 'reports a non-existent directory' 2001 result() { 2002 %text 2003 #|$ scm_begin 2004 #|Error: non-existent/ is not in the password store. 2005 } 2006 When run do_reencrypt non-existent/ 2007 The output should be blank 2008 The error should equal "$(result)" 2009 The status should equal 1 2010 End 2011 2012 It 'reports a non-existent file' 2013 result() { 2014 %text 2015 #|$ scm_begin 2016 #|Error: non-existent is not in the password store. 2017 } 2018 When run do_reencrypt non-existent 2019 The output should be blank 2020 The error should equal "$(result)" 2021 The status should equal 1 2022 End 2023 End 2024 2025 Describe 'do_show' 2026 cleartext(){ 2027 %text 2028 #|password line 2029 #|extra line 1 2030 #|extra line 2 2031 } 2032 2033 It 'shows a secret on standard output' 2034 cat() { @cat; } 2035 Data cleartext 2036 SHOW=text 2037 When call do_show 2038 The status should be success 2039 The output should equal "$(cleartext)" 2040 End 2041 2042 It 'pastes a secret into the clipboard' 2043 head() { @head "$@"; } 2044 tail() { @tail "$@"; } 2045 tr() { @tr "$@"; } 2046 platform_clip() { @cat >&2; } 2047 Data cleartext 2048 SELECTED_LINE=1 2049 SHOW=clip 2050 When call do_show title 2051 The status should be success 2052 The output should be blank 2053 The error should equal 'password line' 2054 End 2055 2056 It 'shows a secret as a QR-code' 2057 head() { @head "$@"; } 2058 tail() { @tail "$@"; } 2059 tr() { @tr "$@"; } 2060 platform_qrcode() { @cat >&2; } 2061 Data cleartext 2062 SELECTED_LINE=1 2063 SHOW=qrcode 2064 When call do_show title 2065 The status should be success 2066 The output should be blank 2067 The error should equal 'password line' 2068 End 2069 2070 It 'aborts on unexpected SHOW' 2071 SHOW=bad 2072 Data cleartext 2073 When run do_show title 2074 The output should be blank 2075 The error should equal 'Unexpected SHOW value "bad"' 2076 The status should equal 1 2077 End 2078 End 2079 2080 Describe 'do_tree' 2081 PREFIX="${SHELLSPEC_WORKDIR}/prefix" 2082 BLUE_TEXT='(B)' 2083 NORMAL_TEXT='(N)' 2084 RED_TEXT='(R)' 2085 TREE_T='T_' 2086 TREE_L='L_' 2087 TREE_I='I_' 2088 TREE__='__' 2089 2090 grep() { @grep "$@"; } 2091 2092 setup() { 2093 @mkdir -p "${PREFIX}/subdir/subsub" "${PREFIX}/empty" "${PREFIX}/other" 2094 %putsn data >"${PREFIX}/root.age" 2095 %putsn data >"${PREFIX}/subdir/hidden" 2096 %putsn data >"${PREFIX}/subdir/subsub/old.gpg" 2097 %putsn data >"${PREFIX}/other/lower.age" 2098 } 2099 2100 cleanup() { 2101 @rm -rf "${PREFIX}" 2102 } 2103 2104 BeforeEach setup 2105 AfterEach cleanup 2106 2107 It 'displays everything without a pattern' 2108 result() { 2109 %text 2110 #|Title 2111 #|T_(B)empty(N) 2112 #|T_(B)other(N) 2113 #|I_L_lower 2114 #|T_root 2115 #|L_(B)subdir(N) 2116 #|__L_(B)subsub(N) 2117 #|____L_(R)old(N) 2118 } 2119 When call do_tree "${PREFIX}" 'Title' 2120 The status should be success 2121 The output should equal "$(result)" 2122 End 2123 2124 It 'displays matching files and their non-matching parents' 2125 result() { 2126 %text 2127 #|Title 2128 #|T_(B)other(N) 2129 #|I_L_lower 2130 #|L_(B)subdir(N) 2131 #|__L_(B)subsub(N) 2132 #|____L_(R)old(N) 2133 } 2134 When call do_tree "${PREFIX}" 'Title' -i L 2135 The status should be success 2136 The output should equal "$(result)" 2137 End 2138 2139 It 'does not display matching directories' 2140 result() { 2141 %text 2142 #|Title 2143 #|L_root 2144 } 2145 When call do_tree "${PREFIX}" 'Title' t 2146 The status should be success 2147 The output should equal "$(result)" 2148 End 2149 2150 It 'might not display anything' 2151 When call do_tree "${PREFIX}" 'Title' z 2152 The status should be success 2153 The output should equal '' 2154 End 2155 2156 It 'does not display an empty title' 2157 When call do_tree "${PREFIX}" '' t 2158 The status should be success 2159 The output should equal 'L_root' 2160 End 2161 End 2162 End