pashage

Yet Another Opinionated Re-engineering of the Unix Password Store
git clone https://git.instinctive.eu/pashage.git
Log | Files | Refs | README | LICENSE

usage_spec.sh (62956B)


      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 command functions in isolation,
     19 # using mocks for all action and helper functions.
     20 # It mostly tests command-line parsing and environment transmission to actions.
     21 
     22 Describe 'Command-Line Parsing'
     23   Include src/pashage.sh
     24   if [ "${SHELLSPEC_SHELL_TYPE}" = sh ]; then
     25     Set 'errexit:on' 'nounset:on'
     26   else
     27     Set 'errexit:on' 'nounset:on' 'pipefail:on'
     28   fi
     29 
     30   PREFIX=/prefix
     31   PROGRAM=prg
     32 
     33   CHARACTER_SET='[:punct:][:alnum:]'
     34   CHARACTER_SET_NO_SYMBOLS='[:alnum:]'
     35 
     36   # mocks
     37   platform_tmpdir() {
     38     SECURE_TMPDIR=/tmp/secure
     39   }
     40 
     41   check_sneaky_path() { mocklog check_sneaky_path "$@"; }
     42   git() { mocklog git "$@"; }
     43   scm_add() { mocklog scm_add "$@"; }
     44   scm_begin() { mocklog scm_begin "$@"; }
     45   scm_commit() { mocklog scm_commit "$@"; }
     46 
     47   do_copy_move() {
     48     mocklog do_copy_move "$@"
     49     %text:expand >&2
     50     #|ACTION=${ACTION}
     51     #|DECISION=${DECISION}
     52     #|OVERWRITE=${OVERWRITE}
     53     #|SCM_ACTION=${SCM_ACTION}
     54   }
     55   do_decrypt() {
     56     mocklog do_decrypt "$@"
     57   }
     58   do_decrypt_gpg() {
     59     mocklog do_decrypt_gpg "$@"
     60   }
     61   do_deinit() {
     62     mocklog do_deinit "$@"
     63     %text:expand >&2
     64     #|DECISION=${DECISION}
     65   }
     66   do_delete() {
     67     mocklog do_delete "$@"
     68     %text:expand >&2
     69     #|DECISION=${DECISION}
     70     #|RECURSIVE=${RECURSIVE}
     71   }
     72   do_edit() {
     73     mocklog do_edit "$@"
     74     %text:expand >&2
     75     #|EDITOR=${EDITOR}
     76     #|SECURE_TMPDIR=${SECURE_TMPDIR}
     77     #|TERM=${TERM}
     78     #|VISUAL=${VISUAL}
     79   }
     80   do_encrypt() {
     81     mocklog do_encrypt "$@"
     82     %text:expand >&2
     83     #|IDENTITIES_FILE=${IDENTITIES_FILE}
     84     #|LOCAL_RECIPIENT_FILE=${LOCAL_RECIPIENT_FILE}
     85     #|PASHAGE_RECIPIENTS=${PASHAGE_RECIPIENTS}
     86     #|PASHAGE_RECIPIENTS_FILE=${PASHAGE_RECIPIENTS_FILE}
     87     #|PASSAGE_RECIPIENTS=${PASSAGE_RECIPIENTS}
     88     #|PASSAGE_RECIPIENTS_FILE=${PASSAGE_RECIPIENTS_FILE}
     89   }
     90   do_generate() {
     91     mocklog do_generate "$@"
     92     %text:expand >&2
     93     #|DECISION=${DECISION}
     94     #|MULTILINE=${MULTILINE}
     95     #|OVERWRITE=${OVERWRITE}
     96     #|SELECTED_LINE=${SELECTED_LINE}
     97     #|SHOW=${SHOW}
     98   }
     99   do_grep() {
    100     mocklog do_grep "$@"
    101   }
    102   do_init() {
    103     mocklog do_init "$@"
    104     %text:expand >&2
    105     #|DECISION=${DECISION}
    106     #|OVERWRITE=${OVERWRITE}
    107   }
    108   do_insert() {
    109     mocklog do_insert "$@"
    110     %text:expand >&2
    111     #|ECHO=${ECHO}
    112     #|MULTILINE=${MULTILINE}
    113     #|OVERWRITE=${OVERWRITE}
    114   }
    115   do_list_or_show() {
    116     mocklog do_list_or_show "$@"
    117     %text:expand >&2
    118     #|SELECTED_LINE=${SELECTED_LINE}
    119     #|SHOW=${SHOW}
    120   }
    121   do_reencrypt() {
    122     mocklog do_reencrypt "$@"
    123     %text:expand >&2
    124     #|DECISION=${DECISION}
    125   }
    126   do_reencrypt_dir() {
    127     mocklog do_reencrypt_dir "$@"
    128     %text:expand >&2
    129     #|DECISION=${DECISION}
    130   }
    131   do_reencrypt_file() {
    132     mocklog do_reencrypt_file "$@"
    133     %text:expand >&2
    134     #|DECISION=${DECISION}
    135   }
    136   do_show() {
    137     mocklog do_show "$@"
    138     %text:expand >&2
    139     #|SELECTED_LINE=${SELECTED_LINE}
    140     #|SHOW=${SHOW}
    141   }
    142   do_tree() {
    143     mocklog do_tree "$@"
    144   }
    145 
    146   Describe 'cmd_copy'
    147     COMMAND=copy
    148 
    149     It 'copies multiple files'
    150       result() {
    151         %text
    152         #|$ check_sneaky_path src1
    153         #|$ check_sneaky_path src2
    154         #|$ check_sneaky_path src3
    155         #|$ check_sneaky_path dest
    156         #|$ do_copy_move src1 dest/
    157         #|ACTION=Copy
    158         #|DECISION=default
    159         #|OVERWRITE=no
    160         #|SCM_ACTION=scm_cp
    161         #|$ do_copy_move src2 dest/
    162         #|ACTION=Copy
    163         #|DECISION=default
    164         #|OVERWRITE=no
    165         #|SCM_ACTION=scm_cp
    166         #|$ do_copy_move src3 dest/
    167         #|ACTION=Copy
    168         #|DECISION=default
    169         #|OVERWRITE=no
    170         #|SCM_ACTION=scm_cp
    171       }
    172       When call cmd_copy src1 src2 src3 dest
    173       The status should be success
    174       The output should be blank
    175       The error should equal "$(result)"
    176     End
    177 
    178     It 'copies forcefully with a long option'
    179       result() {
    180         %text
    181         #|$ check_sneaky_path src
    182         #|$ check_sneaky_path dest
    183         #|$ do_copy_move src dest
    184         #|ACTION=Copy
    185         #|DECISION=default
    186         #|OVERWRITE=yes
    187         #|SCM_ACTION=scm_cp
    188       }
    189       When call cmd_copy --force src dest
    190       The status should be success
    191       The output should be blank
    192       The error should equal "$(result)"
    193     End
    194 
    195     It 'copies forcefully with a short option'
    196       result() {
    197         %text
    198         #|$ check_sneaky_path src
    199         #|$ check_sneaky_path dest
    200         #|$ do_copy_move src dest
    201         #|ACTION=Copy
    202         #|DECISION=default
    203         #|OVERWRITE=yes
    204         #|SCM_ACTION=scm_cp
    205       }
    206       When call cmd_copy -f src dest
    207       The status should be success
    208       The output should be blank
    209       The error should equal "$(result)"
    210     End
    211 
    212     It 'always reencrypts with a long option'
    213       result() {
    214         %text
    215         #|$ check_sneaky_path src
    216         #|$ check_sneaky_path dest
    217         #|$ do_copy_move src dest
    218         #|ACTION=Copy
    219         #|DECISION=force
    220         #|OVERWRITE=no
    221         #|SCM_ACTION=scm_cp
    222       }
    223       When call cmd_copy --reencrypt src dest
    224       The status should be success
    225       The output should be blank
    226       The error should equal "$(result)"
    227     End
    228 
    229     It 'always reencrypts with a short option'
    230       result() {
    231         %text
    232         #|$ check_sneaky_path src
    233         #|$ check_sneaky_path dest
    234         #|$ do_copy_move src dest
    235         #|ACTION=Copy
    236         #|DECISION=force
    237         #|OVERWRITE=no
    238         #|SCM_ACTION=scm_cp
    239       }
    240       When call cmd_copy -e src dest
    241       The status should be success
    242       The output should be blank
    243       The error should equal "$(result)"
    244     End
    245 
    246     It 'interactively reencrypts with a long option'
    247       result() {
    248         %text
    249         #|$ check_sneaky_path src
    250         #|$ check_sneaky_path dest
    251         #|$ do_copy_move src dest
    252         #|ACTION=Copy
    253         #|DECISION=interactive
    254         #|OVERWRITE=no
    255         #|SCM_ACTION=scm_cp
    256       }
    257       When call cmd_copy --interactive src dest
    258       The status should be success
    259       The output should be blank
    260       The error should equal "$(result)"
    261     End
    262 
    263     It 'interactively reencrypts with a short option'
    264       result() {
    265         %text
    266         #|$ check_sneaky_path src
    267         #|$ check_sneaky_path dest
    268         #|$ do_copy_move src dest
    269         #|ACTION=Copy
    270         #|DECISION=interactive
    271         #|OVERWRITE=no
    272         #|SCM_ACTION=scm_cp
    273       }
    274       When call cmd_copy -i src dest
    275       The status should be success
    276       The output should be blank
    277       The error should equal "$(result)"
    278     End
    279 
    280     It 'never reencrypts with a long option'
    281       result() {
    282         %text
    283         #|$ check_sneaky_path src
    284         #|$ check_sneaky_path dest
    285         #|$ do_copy_move src dest
    286         #|ACTION=Copy
    287         #|DECISION=keep
    288         #|OVERWRITE=no
    289         #|SCM_ACTION=scm_cp
    290       }
    291       When call cmd_copy --keep src dest
    292       The status should be success
    293       The output should be blank
    294       The error should equal "$(result)"
    295     End
    296 
    297     It 'never reencrypts with a short option'
    298       result() {
    299         %text
    300         #|$ check_sneaky_path src
    301         #|$ check_sneaky_path dest
    302         #|$ do_copy_move src dest
    303         #|ACTION=Copy
    304         #|DECISION=keep
    305         #|OVERWRITE=no
    306         #|SCM_ACTION=scm_cp
    307       }
    308       When call cmd_copy -k src dest
    309       The status should be success
    310       The output should be blank
    311       The error should equal "$(result)"
    312     End
    313 
    314     It 'copies a file named like a flag'
    315       result() {
    316         %text
    317         #|$ check_sneaky_path -s
    318         #|$ check_sneaky_path dest
    319         #|$ do_copy_move -s dest
    320         #|ACTION=Copy
    321         #|DECISION=default
    322         #|OVERWRITE=no
    323         #|SCM_ACTION=scm_cp
    324       }
    325       When call cmd_copy -- -s dest
    326       The status should be success
    327       The output should be blank
    328       The error should equal "$(result)"
    329     End
    330 
    331     usage_text() { %text
    332       #|Usage: prg copy [--reencrypt,-e | --interactive,-i | --keep,-k ]
    333       #|                [--force,-f] old-path new-path
    334     }
    335 
    336     It 'reports a bad option'
    337       cat() { @cat; }
    338       When run cmd_copy -s arg
    339       The output should be blank
    340       The error should equal "$(usage_text)"
    341       The status should equal 1
    342     End
    343 
    344     It 'reports incompatible re-encryption options (-e and -i)'
    345       cat() { @cat; }
    346       When run cmd_copy -ei src dest
    347       The output should be blank
    348       The error should equal "$(usage_text)"
    349       The status should equal 1
    350     End
    351 
    352     It 'reports incompatible re-encryption options (-i and -k)'
    353       cat() { @cat; }
    354       When run cmd_copy -ik src dest
    355       The output should be blank
    356       The error should equal "$(usage_text)"
    357       The status should equal 1
    358     End
    359 
    360     It 'reports incompatible re-encryption options (-k and -e)'
    361       cat() { @cat; }
    362       When run cmd_copy -ke src dest
    363       The output should be blank
    364       The error should equal "$(usage_text)"
    365       The status should equal 1
    366     End
    367 
    368     It 'reports a lack of argument'
    369       cat() { @cat; }
    370       When run cmd_copy src
    371       The output should be blank
    372       The error should equal "$(usage_text)"
    373       The status should equal 1
    374     End
    375   End
    376 
    377   Describe 'cmd_copy_move'
    378     COMMAND=wrong
    379 
    380     It 'reports both commands when confused'
    381       cat() { @cat; }
    382       result() { %text
    383         #|Usage: prg copy [--reencrypt,-e | --interactive,-i | --keep,-k ]
    384         #|                [--force,-f] old-path new-path
    385         #|       prg move [--reencrypt,-e | --interactive,-i | --keep,-k ]
    386         #|                [--force,-f] old-path new-path
    387       }
    388       When run cmd_copy src
    389       The output should be blank
    390       The error should equal "$(result)"
    391       The status should equal 1
    392     End
    393   End
    394 
    395   Describe 'cmd_delete'
    396     COMMAND=delete
    397 
    398     It 'removes a file forcefully with a long option'
    399       result() {
    400         %text
    401         #|$ check_sneaky_path arg1
    402         #|$ do_delete arg1
    403         #|DECISION=force
    404         #|RECURSIVE=no
    405       }
    406       When call cmd_delete --force arg1
    407       The status should be success
    408       The output should be blank
    409       The error should equal "$(result)"
    410     End
    411 
    412     It 'removes a file forcefully with a short option'
    413       result() {
    414         %text
    415         #|$ check_sneaky_path arg1
    416         #|$ do_delete arg1
    417         #|DECISION=force
    418         #|RECURSIVE=no
    419       }
    420       When call cmd_delete -f arg1
    421       The status should be success
    422       The output should be blank
    423       The error should equal "$(result)"
    424     End
    425 
    426     It 'removes a directory recursively with a long option'
    427       result() {
    428         %text
    429         #|$ check_sneaky_path arg1
    430         #|$ do_delete arg1
    431         #|DECISION=default
    432         #|RECURSIVE=yes
    433       }
    434       When call cmd_delete --recursive arg1
    435       The status should be success
    436       The output should be blank
    437       The error should equal "$(result)"
    438     End
    439 
    440     It 'removes a directory recursively with a short option'
    441       result() {
    442         %text
    443         #|$ check_sneaky_path arg1
    444         #|$ do_delete arg1
    445         #|DECISION=default
    446         #|RECURSIVE=yes
    447       }
    448       When call cmd_delete -r arg1
    449       The status should be success
    450       The output should be blank
    451       The error should equal "$(result)"
    452     End
    453 
    454     It 'removes a directory recursively and forcefully with long options'
    455       result() {
    456         %text
    457         #|$ check_sneaky_path arg1
    458         #|$ do_delete arg1
    459         #|DECISION=force
    460         #|RECURSIVE=yes
    461       }
    462       When call cmd_delete --recursive --force arg1
    463       The status should be success
    464       The output should be blank
    465       The error should equal "$(result)"
    466     End
    467 
    468     It 'removes a directory recursively and forcefully with short options'
    469       result() {
    470         %text
    471         #|$ check_sneaky_path arg1
    472         #|$ do_delete arg1
    473         #|DECISION=force
    474         #|RECURSIVE=yes
    475       }
    476       When call cmd_delete -rf arg1
    477       The status should be success
    478       The output should be blank
    479       The error should equal "$(result)"
    480     End
    481 
    482     It 'removes a directory forcefully and recursively with short options'
    483       result() {
    484         %text
    485         #|$ check_sneaky_path arg1
    486         #|$ do_delete arg1
    487         #|DECISION=force
    488         #|RECURSIVE=yes
    489       }
    490       When call cmd_delete -fr arg1
    491       The status should be success
    492       The output should be blank
    493       The error should equal "$(result)"
    494     End
    495 
    496     It 'removes multiple files'
    497       result() {
    498         %text
    499         #|$ check_sneaky_path arg1
    500         #|$ check_sneaky_path arg2
    501         #|$ check_sneaky_path arg3
    502         #|$ do_delete arg1
    503         #|DECISION=default
    504         #|RECURSIVE=no
    505         #|$ do_delete arg2
    506         #|DECISION=default
    507         #|RECURSIVE=no
    508         #|$ do_delete arg3
    509         #|DECISION=default
    510         #|RECURSIVE=no
    511       }
    512       When call cmd_delete arg1 arg2 arg3
    513       The status should be success
    514       The output should be blank
    515       The error should equal "$(result)"
    516     End
    517 
    518     It 'removes a file named like a flag'
    519       result() {
    520         %text
    521         #|$ check_sneaky_path -f
    522         #|$ check_sneaky_path arg2
    523         #|$ do_delete -f
    524         #|DECISION=default
    525         #|RECURSIVE=no
    526         #|$ do_delete arg2
    527         #|DECISION=default
    528         #|RECURSIVE=no
    529       }
    530       When call cmd_delete -- -f arg2
    531       The status should be success
    532       The output should be blank
    533       The error should equal "$(result)"
    534     End
    535 
    536     It 'reports a bad option'
    537       cat() { @cat; }
    538       When run cmd_delete -u arg
    539       The output should be blank
    540       The error should equal \
    541         'Usage: prg delete [--recursive,-r] [--force,-f] pass-name'
    542       The status should equal 1
    543     End
    544 
    545     It 'reports a lack of argument'
    546       cat() { @cat; }
    547       When run cmd_delete
    548       The output should be blank
    549       The error should equal \
    550         'Usage: prg delete [--recursive,-r] [--force,-f] pass-name'
    551       The status should equal 1
    552     End
    553   End
    554 
    555   Describe 'cmd_edit'
    556     COMMAND=edit
    557     EDITOR=ed
    558     TERM=dumb
    559     VISUAL=vi
    560 
    561     It 'edits multiple files succesively'
    562       result() {
    563         %text
    564         #|$ check_sneaky_path arg1
    565         #|$ check_sneaky_path arg2
    566         #|$ check_sneaky_path arg3
    567         #|$ do_edit arg1
    568         #|EDITOR=ed
    569         #|SECURE_TMPDIR=/tmp/secure
    570         #|TERM=dumb
    571         #|VISUAL=vi
    572         #|$ do_edit arg2
    573         #|EDITOR=ed
    574         #|SECURE_TMPDIR=/tmp/secure
    575         #|TERM=dumb
    576         #|VISUAL=vi
    577         #|$ do_edit arg3
    578         #|EDITOR=ed
    579         #|SECURE_TMPDIR=/tmp/secure
    580         #|TERM=dumb
    581         #|VISUAL=vi
    582       }
    583       When call cmd_edit arg1 arg2 arg3
    584       The status should be success
    585       The output should be blank
    586       The error should equal "$(result)"
    587     End
    588 
    589     It 'reports a lack of argument'
    590       cat() { @cat; }
    591       When run cmd_edit
    592       The output should be blank
    593       The error should equal 'Usage: prg edit pass-name'
    594       The status should equal 1
    595     End
    596   End
    597 
    598   Describe 'cmd_find'
    599     COMMAND=find
    600 
    601     It 'uses the argument list directly'
    602       When call cmd_find -i pattern
    603       The status should be success
    604       The output should equal 'Search pattern: -i pattern'
    605       The error should equal '$ do_tree /prefix  -i pattern'
    606     End
    607 
    608     It 'reports a lack of argument'
    609       cat() { @cat; }
    610       When run cmd_find
    611       The output should be blank
    612       The error should equal 'Usage: prg find [GREP_OPTIONS] regex'
    613       The status should equal 1
    614     End
    615   End
    616 
    617   Describe 'cmd_generate'
    618     COMMAND=generate
    619     GENERATED_LENGTH=25
    620 
    621     usage_text() { %text
    622       #|Usage: prg generate [--no-symbols,-n] [--clip,-c | --qrcode,-q]
    623       #|                    [--in-place,-i | --force,-f] [--multiline,-m]
    624       #|                    [--try,-t] pass-name [pass-length [character-set]]
    625     }
    626 
    627     It 'generates a new entry with default length'
    628       result() {
    629         %text
    630         #|$ check_sneaky_path secret
    631         #|$ do_generate secret 25 [:punct:][:alnum:]
    632         #|DECISION=default
    633         #|MULTILINE=no
    634         #|OVERWRITE=no
    635         #|SELECTED_LINE=1
    636         #|SHOW=text
    637       }
    638       When call cmd_generate secret
    639       The status should be success
    640       The output should be blank
    641       The error should equal "$(result)"
    642     End
    643 
    644     It 'generates a new entry with explicit length'
    645       result() {
    646         %text
    647         #|$ check_sneaky_path secret
    648         #|$ do_generate secret 12 [:punct:][:alnum:]
    649         #|DECISION=default
    650         #|MULTILINE=no
    651         #|OVERWRITE=no
    652         #|SELECTED_LINE=1
    653         #|SHOW=text
    654       }
    655       When call cmd_generate secret 12
    656       The status should be success
    657       The output should be blank
    658       The error should equal "$(result)"
    659     End
    660 
    661     It 'generates a new entry with explicit length and character set'
    662       result() {
    663         %text
    664         #|$ check_sneaky_path secret
    665         #|$ do_generate secret 12 [A-Z]
    666         #|DECISION=default
    667         #|MULTILINE=no
    668         #|OVERWRITE=no
    669         #|SELECTED_LINE=1
    670         #|SHOW=text
    671       }
    672       When call cmd_generate secret 12 '[A-Z]'
    673       The status should be success
    674       The output should be blank
    675       The error should equal "$(result)"
    676     End
    677 
    678     It 'generates a new flag-like entry'
    679       result() {
    680         %text
    681         #|$ check_sneaky_path -f
    682         #|$ do_generate -f 25 [:punct:][:alnum:]
    683         #|DECISION=default
    684         #|MULTILINE=no
    685         #|OVERWRITE=no
    686         #|SELECTED_LINE=1
    687         #|SHOW=text
    688       }
    689       When call cmd_generate -- -f
    690       The status should be success
    691       The output should be blank
    692       The error should equal "$(result)"
    693     End
    694 
    695     It 'generates a new entry and copies it into the clipboard (long)'
    696       result() {
    697         %text
    698         #|$ check_sneaky_path secret
    699         #|$ do_generate secret 25 [:punct:][:alnum:]
    700         #|DECISION=default
    701         #|MULTILINE=no
    702         #|OVERWRITE=no
    703         #|SELECTED_LINE=1
    704         #|SHOW=clip
    705       }
    706       When call cmd_generate --clip secret
    707       The status should be success
    708       The output should be blank
    709       The error should equal "$(result)"
    710     End
    711 
    712     It 'generates a new entry and copies it into the clipboard (short)'
    713       result() {
    714         %text
    715         #|$ check_sneaky_path secret
    716         #|$ do_generate secret 25 [:punct:][:alnum:]
    717         #|DECISION=default
    718         #|MULTILINE=no
    719         #|OVERWRITE=no
    720         #|SELECTED_LINE=1
    721         #|SHOW=clip
    722       }
    723       When call cmd_generate -c secret
    724       The status should be success
    725       The output should be blank
    726       The error should equal "$(result)"
    727     End
    728 
    729     It 'generates a new entry and shows it as a QR-code (long)'
    730       result() {
    731         %text
    732         #|$ check_sneaky_path secret
    733         #|$ do_generate secret 25 [:punct:][:alnum:]
    734         #|DECISION=default
    735         #|MULTILINE=no
    736         #|OVERWRITE=no
    737         #|SELECTED_LINE=1
    738         #|SHOW=qrcode
    739       }
    740       When call cmd_generate --qrcode secret
    741       The status should be success
    742       The output should be blank
    743       The error should equal "$(result)"
    744     End
    745 
    746     It 'generates a new entry and shows it as a QR-code (short)'
    747       result() {
    748         %text
    749         #|$ check_sneaky_path secret
    750         #|$ do_generate secret 25 [:punct:][:alnum:]
    751         #|DECISION=default
    752         #|MULTILINE=no
    753         #|OVERWRITE=no
    754         #|SELECTED_LINE=1
    755         #|SHOW=qrcode
    756       }
    757       When call cmd_generate -q secret
    758       The status should be success
    759       The output should be blank
    760       The error should equal "$(result)"
    761     End
    762 
    763     It 'generates a new alphanumeric entry and copies it into the clipboard (long)'
    764       result() {
    765         %text
    766         #|$ check_sneaky_path secret
    767         #|$ do_generate secret 25 [:alnum:]
    768         #|DECISION=default
    769         #|MULTILINE=no
    770         #|OVERWRITE=no
    771         #|SELECTED_LINE=1
    772         #|SHOW=clip
    773       }
    774       When call cmd_generate --clip --no-symbols secret
    775       The status should be success
    776       The output should be blank
    777       The error should equal "$(result)"
    778     End
    779 
    780     It 'generates a new alphanumeric entry and copies it into the clipboard (short)'
    781       result() {
    782         %text
    783         #|$ check_sneaky_path secret
    784         #|$ do_generate secret 25 [:alnum:]
    785         #|DECISION=default
    786         #|MULTILINE=no
    787         #|OVERWRITE=no
    788         #|SELECTED_LINE=1
    789         #|SHOW=clip
    790       }
    791       When call cmd_generate -cn secret
    792       The status should be success
    793       The output should be blank
    794       The error should equal "$(result)"
    795     End
    796 
    797     It 'generates a new alphanumeric entry and shows it as a QR-code (long)'
    798       result() {
    799         %text
    800         #|$ check_sneaky_path secret
    801         #|$ do_generate secret 25 [:alnum:]
    802         #|DECISION=default
    803         #|MULTILINE=no
    804         #|OVERWRITE=no
    805         #|SELECTED_LINE=1
    806         #|SHOW=qrcode
    807       }
    808       When call cmd_generate --no-symbols --qrcode secret
    809       The status should be success
    810       The output should be blank
    811       The error should equal "$(result)"
    812     End
    813 
    814     It 'generates a new alphanumeric entry and shows it as a QR-code (short)'
    815       result() {
    816         %text
    817         #|$ check_sneaky_path secret
    818         #|$ do_generate secret 25 [:alnum:]
    819         #|DECISION=default
    820         #|MULTILINE=no
    821         #|OVERWRITE=no
    822         #|SELECTED_LINE=1
    823         #|SHOW=qrcode
    824       }
    825       When call cmd_generate -nq secret
    826       The status should be success
    827       The output should be blank
    828       The error should equal "$(result)"
    829     End
    830 
    831     It 'generates a new entry in place (long)'
    832       result() {
    833         %text
    834         #|$ check_sneaky_path secret
    835         #|$ do_generate secret 25 [:punct:][:alnum:]
    836         #|DECISION=default
    837         #|MULTILINE=no
    838         #|OVERWRITE=reuse
    839         #|SELECTED_LINE=1
    840         #|SHOW=text
    841       }
    842       When call cmd_generate --in-place secret
    843       The status should be success
    844       The output should be blank
    845       The error should equal "$(result)"
    846     End
    847 
    848     It 'generates a new entry in place (short)'
    849       result() {
    850         %text
    851         #|$ check_sneaky_path secret
    852         #|$ do_generate secret 25 [:punct:][:alnum:]
    853         #|DECISION=default
    854         #|MULTILINE=no
    855         #|OVERWRITE=reuse
    856         #|SELECTED_LINE=1
    857         #|SHOW=text
    858       }
    859       When call cmd_generate -i secret
    860       The status should be success
    861       The output should be blank
    862       The error should equal "$(result)"
    863     End
    864 
    865     It 'overwrites an existing entry (long)'
    866       result() {
    867         %text
    868         #|$ check_sneaky_path secret
    869         #|$ do_generate secret 25 [:punct:][:alnum:]
    870         #|DECISION=default
    871         #|MULTILINE=no
    872         #|OVERWRITE=yes
    873         #|SELECTED_LINE=1
    874         #|SHOW=text
    875       }
    876       When call cmd_generate --force secret
    877       The status should be success
    878       The output should be blank
    879       The error should equal "$(result)"
    880     End
    881 
    882     It 'overwrites an existing entry (short)'
    883       result() {
    884         %text
    885         #|$ check_sneaky_path secret
    886         #|$ do_generate secret 25 [:punct:][:alnum:]
    887         #|DECISION=default
    888         #|MULTILINE=no
    889         #|OVERWRITE=yes
    890         #|SELECTED_LINE=1
    891         #|SHOW=text
    892       }
    893       When call cmd_generate -f secret
    894       The status should be success
    895       The output should be blank
    896       The error should equal "$(result)"
    897     End
    898 
    899     It 'asks for confirmation before saving the generated password (long)'
    900       result() {
    901         %text
    902         #|$ check_sneaky_path secret
    903         #|$ do_generate secret 25 [:punct:][:alnum:]
    904         #|DECISION=interactive
    905         #|MULTILINE=no
    906         #|OVERWRITE=no
    907         #|SELECTED_LINE=1
    908         #|SHOW=text
    909       }
    910       When call cmd_generate --try secret
    911       The status should be success
    912       The output should be blank
    913       The error should equal "$(result)"
    914     End
    915 
    916     It 'asks for confirmation before saving the generated password (short)'
    917       result() {
    918         %text
    919         #|$ check_sneaky_path secret
    920         #|$ do_generate secret 25 [:punct:][:alnum:]
    921         #|DECISION=interactive
    922         #|MULTILINE=no
    923         #|OVERWRITE=no
    924         #|SELECTED_LINE=1
    925         #|SHOW=text
    926       }
    927       When call cmd_generate -t secret
    928       The status should be success
    929       The output should be blank
    930       The error should equal "$(result)"
    931     End
    932 
    933     It 'accepts extra lines after the generated secret (long)'
    934       result() {
    935         %text
    936         #|$ check_sneaky_path secret
    937         #|$ do_generate secret 25 [:punct:][:alnum:]
    938         #|DECISION=default
    939         #|MULTILINE=yes
    940         #|OVERWRITE=no
    941         #|SELECTED_LINE=1
    942         #|SHOW=text
    943       }
    944       When call cmd_generate --multiline secret
    945       The status should be success
    946       The output should be blank
    947       The error should equal "$(result)"
    948     End
    949 
    950     It 'accepts extra lines after the generated secret (short)'
    951       result() {
    952         %text
    953         #|$ check_sneaky_path secret
    954         #|$ do_generate secret 25 [:punct:][:alnum:]
    955         #|DECISION=default
    956         #|MULTILINE=yes
    957         #|OVERWRITE=no
    958         #|SELECTED_LINE=1
    959         #|SHOW=text
    960       }
    961       When call cmd_generate -m secret
    962       The status should be success
    963       The output should be blank
    964       The error should equal "$(result)"
    965     End
    966 
    967     It 'reports incompatible generation long options'
    968       cat() { @cat; }
    969       When run cmd_generate --in-place --force secret
    970       The output should be blank
    971       The error should equal "$(usage_text)"
    972       The status should equal 1
    973     End
    974 
    975     It 'reports incompatible generation short options'
    976       cat() { @cat; }
    977       When run cmd_generate -fi secret
    978       The output should be blank
    979       The error should equal "$(usage_text)"
    980       The status should equal 1
    981     End
    982 
    983     It 'reports incompatible show long options'
    984       cat() { @cat; }
    985       When run cmd_generate --qrcode --clip secret
    986       The output should be blank
    987       The error should equal "$(usage_text)"
    988       The status should equal 1
    989     End
    990 
    991     It 'reports incompatible show short options'
    992       cat() { @cat; }
    993       When run cmd_generate -cq secret
    994       The output should be blank
    995       The error should equal "$(usage_text)"
    996       The status should equal 1
    997     End
    998 
    999     It 'reports a bad option'
   1000       cat() { @cat; }
   1001       When run cmd_generate --bad secret
   1002       The output should be blank
   1003       The error should equal "$(usage_text)"
   1004       The status should equal 1
   1005     End
   1006 
   1007     It 'reports a lack of argument'
   1008       cat() { @cat; }
   1009       When run cmd_generate
   1010       The output should be blank
   1011       The error should equal "$(usage_text)"
   1012       The status should equal 1
   1013     End
   1014   End
   1015 
   1016   Describe 'cmd_git'
   1017     COMMAND=git
   1018 
   1019     cmd_gitconfig() { mocklog cmd_gitconfig "$@"; }
   1020     mkdir() { mocklog mkdir "$@"; @mkdir "$@"; }
   1021 
   1022     setup() {
   1023       @mkdir -p "${SHELLSPEC_WORKDIR}/repo/.git"
   1024       @mkdir -p "${SHELLSPEC_WORKDIR}/repo/sub"
   1025     }
   1026 
   1027     cleanup() {
   1028       @rm -rf "${SHELLSPEC_WORKDIR}/repo"
   1029     }
   1030 
   1031     BeforeEach setup
   1032     AfterEach cleanup
   1033 
   1034     It 'initializes a new repository'
   1035       PREFIX="${SHELLSPEC_WORKDIR}/repo/sub"
   1036       result() {
   1037         %text:expand
   1038         #|$ mkdir -p -- ${PREFIX}
   1039         #|$ git -C ${PREFIX} init
   1040         #|$ scm_add .
   1041         #|$ scm_commit Add current contents of password store.
   1042         #|$ cmd_gitconfig
   1043       }
   1044       When call cmd_git init
   1045       The status should be success
   1046       The output should be blank
   1047       The error should equal "$(result)"
   1048     End
   1049 
   1050     It 'clones a new repository'
   1051       PREFIX="${SHELLSPEC_WORKDIR}/repo/sub"
   1052       result() {
   1053         %text:expand
   1054         #|$ git clone origin ${PREFIX}
   1055         #|$ cmd_gitconfig
   1056       }
   1057       When call cmd_git clone origin
   1058       The status should be success
   1059       The output should be blank
   1060       The error should equal "$(result)"
   1061     End
   1062 
   1063     It 'runs the git command into the store'
   1064       PREFIX="${SHELLSPEC_WORKDIR}/repo"
   1065       When call cmd_git log --oneline
   1066       The status should be success
   1067       The output should be blank
   1068       The error should equal "$ git -C ${PREFIX} log --oneline"
   1069     End
   1070 
   1071     It 'transmits an empty argument list to git'
   1072       PREFIX="${SHELLSPEC_WORKDIR}/repo"
   1073       When run cmd_git
   1074       The status should be success
   1075       The output should be blank
   1076       The error should equal "$ git -C ${PREFIX}"
   1077     End
   1078 
   1079     It 'aborts without a git repository'
   1080       PREFIX="${SHELLSPEC_WORKDIR}/repo/sub"
   1081       When run cmd_git log
   1082       The output should be blank
   1083       The error should equal 'Error: the password store is not a git repository. Try "prg git init".'
   1084       The status should equal 1
   1085     End
   1086   End
   1087 
   1088   Describe 'cmd_gitconfig'
   1089     COMMAND=gitconfig
   1090     AGE=age
   1091     IDENTITIES_FILE=id
   1092 
   1093     grep() { @grep "$@"; }
   1094 
   1095     setup() {
   1096       @mkdir -p "${SHELLSPEC_WORKDIR}/repo/.git"
   1097       @mkdir -p "${SHELLSPEC_WORKDIR}/repo/sub-1/.git"
   1098       %putsn data >"${SHELLSPEC_WORKDIR}/repo/sub-1/.gitattributes"
   1099       @mkdir -p "${SHELLSPEC_WORKDIR}/repo/sub-2/.git"
   1100       %putsn '*.age diff=age' >"${SHELLSPEC_WORKDIR}/repo/sub-2/.gitattributes"
   1101     }
   1102 
   1103     cleanup() {
   1104       @rm -rf "${SHELLSPEC_WORKDIR}/repo"
   1105     }
   1106 
   1107     BeforeEach setup
   1108     AfterEach cleanup
   1109 
   1110     It 'configures a new repository'
   1111       PREFIX="${SHELLSPEC_WORKDIR}/repo"
   1112       result() {
   1113         %text:expand
   1114         #|$ scm_begin
   1115         #|$ scm_add .gitattributes
   1116         #|$ scm_commit Configure git repository for age file diff.
   1117         #|$ git -C ${PREFIX} config --local diff.age.binary true
   1118         #|$ git -C ${PREFIX} config --local diff.age.textconv age -d -i id
   1119       }
   1120       When call cmd_gitconfig
   1121       The status should be success
   1122       The output should be blank
   1123       The error should equal "$(result)"
   1124       The contents of file "${PREFIX}/.gitattributes" should equal '*.age diff=age'
   1125     End
   1126 
   1127     It 'expands an existing .gitattributes'
   1128       PREFIX="${SHELLSPEC_WORKDIR}/repo/sub-1"
   1129       result() {
   1130         %text:expand
   1131         #|$ scm_begin
   1132         #|$ scm_add .gitattributes
   1133         #|$ scm_commit Configure git repository for age file diff.
   1134         #|$ git -C ${PREFIX} config --local diff.age.binary true
   1135         #|$ git -C ${PREFIX} config --local diff.age.textconv age -d -i id
   1136       }
   1137       attrs() {
   1138         %text
   1139         #|data
   1140         #|*.age diff=age
   1141       }
   1142       When call cmd_gitconfig
   1143       The status should be success
   1144       The output should be blank
   1145       The error should equal "$(result)"
   1146       The contents of file "${PREFIX}/.gitattributes" should equal "$(attrs)"
   1147     End
   1148 
   1149     It 'configures a repository with a valid .gitattributes'
   1150       PREFIX="${SHELLSPEC_WORKDIR}/repo/sub-2"
   1151       result() {
   1152         %text:expand
   1153         #|$ git -C ${PREFIX} config --local diff.age.binary true
   1154         #|$ git -C ${PREFIX} config --local diff.age.textconv age -d -i id
   1155       }
   1156       When call cmd_gitconfig
   1157       The status should be success
   1158       The output should be blank
   1159       The error should equal "$(result)"
   1160       The contents of file "${PREFIX}/.gitattributes" should equal '*.age diff=age'
   1161     End
   1162 
   1163     It 'aborts without a git repository'
   1164       PREFIX="${SHELLSPEC_WORKDIR}/repo/prefix"
   1165       When run cmd_gitconfig
   1166       The output should be blank
   1167       The error should equal  'The store is not a git repository.'
   1168       The status should equal 1
   1169     End
   1170   End
   1171 
   1172   Describe 'cmd_grep'
   1173     COMMAND=grep
   1174     PREFIX="${SHELLSPEC_WORKDIR}"
   1175 
   1176     It 'uses the argument list directly'
   1177       When call cmd_grep -i pattern
   1178       The status should be success
   1179       The output should be blank
   1180       The error should equal '$ do_grep  -i pattern'
   1181     End
   1182 
   1183     It 'reports a lack of argument'
   1184       cat() { @cat; }
   1185       When run cmd_grep
   1186       The output should be blank
   1187       The error should equal 'Usage: prg grep [GREP_OPTIONS] search-regex'
   1188       The status should equal 1
   1189     End
   1190   End
   1191 
   1192   Describe 'cmd_help'
   1193     COMMAND=help
   1194 
   1195     It 'displays version and usage'
   1196       cmd_usage() { mocklog cmd_usage "$@"; }
   1197       cmd_version() { mocklog cmd_version "$@"; }
   1198       result() {
   1199         %text | @sed 's/\$$//'
   1200         #|$ cmd_version
   1201         #|$ cmd_usage     $
   1202       }
   1203       When call cmd_help
   1204       The status should be success
   1205       The output should be blank
   1206       The error should equal "$(result)"
   1207     End
   1208   End
   1209 
   1210   Describe 'cmd_init'
   1211     COMMAND=init
   1212 
   1213     It 'initializes the whole store'
   1214       result() {
   1215         %text | @sed 's/\$$//'
   1216         #|$ check_sneaky_path $
   1217         #|$ do_init  recipient-1 recipient-2
   1218         #|DECISION=default
   1219         #|OVERWRITE=yes
   1220       }
   1221       When call cmd_init recipient-1 recipient-2
   1222       The status should be success
   1223       The output should be blank
   1224       The error should equal "$(result)"
   1225     End
   1226 
   1227     It 'initializes the whole store conservatively with a long option'
   1228       result() {
   1229         %text | @sed 's/\$$//'
   1230         #|$ check_sneaky_path $
   1231         #|$ do_init  recipient-1 recipient-2
   1232         #|DECISION=keep
   1233         #|OVERWRITE=yes
   1234       }
   1235       When call cmd_init --keep recipient-1 recipient-2
   1236       The status should be success
   1237       The output should be blank
   1238       The error should equal "$(result)"
   1239     End
   1240 
   1241     It 'initializes the whole store conservatively with a short option'
   1242       result() {
   1243         %text | @sed 's/\$$//'
   1244         #|$ check_sneaky_path $
   1245         #|$ do_init  recipient-1 recipient-2
   1246         #|DECISION=keep
   1247         #|OVERWRITE=yes
   1248       }
   1249       When call cmd_init -k recipient-1 recipient-2
   1250       The status should be success
   1251       The output should be blank
   1252       The error should equal "$(result)"
   1253     End
   1254 
   1255     It 'initializes the whole store interactively with a long option'
   1256       result() {
   1257         %text | @sed 's/\$$//'
   1258         #|$ check_sneaky_path $
   1259         #|$ do_init  recipient-1 recipient-2
   1260         #|DECISION=interactive
   1261         #|OVERWRITE=yes
   1262       }
   1263       When call cmd_init --interactive recipient-1 recipient-2
   1264       The status should be success
   1265       The output should be blank
   1266       The error should equal "$(result)"
   1267     End
   1268 
   1269     It 'initializes the whole store interactively with a short option'
   1270       result() {
   1271         %text | @sed 's/\$$//'
   1272         #|$ check_sneaky_path $
   1273         #|$ do_init  recipient-1 recipient-2
   1274         #|DECISION=interactive
   1275         #|OVERWRITE=yes
   1276       }
   1277       When call cmd_init -i recipient-1 recipient-2
   1278       The status should be success
   1279       The output should be blank
   1280       The error should equal "$(result)"
   1281     End
   1282 
   1283     It 'initializes a subdirectory with a collapsed long option'
   1284       result() {
   1285         %text
   1286         #|$ check_sneaky_path sub
   1287         #|$ do_init sub recipient
   1288         #|DECISION=default
   1289         #|OVERWRITE=yes
   1290       }
   1291       When call cmd_init --path=sub recipient
   1292       The status should be success
   1293       The output should be blank
   1294       The error should equal "$(result)"
   1295     End
   1296 
   1297     It 'initializes a subdirectory with an expanded long option'
   1298       result() {
   1299         %text
   1300         #|$ check_sneaky_path sub
   1301         #|$ do_init sub recipient
   1302         #|DECISION=default
   1303         #|OVERWRITE=yes
   1304       }
   1305       When call cmd_init --path sub recipient
   1306       The status should be success
   1307       The output should be blank
   1308       The error should equal "$(result)"
   1309     End
   1310 
   1311     It 'initializes a subdirectory with a collapsed short option'
   1312       result() {
   1313         %text
   1314         #|$ check_sneaky_path sub
   1315         #|$ do_init sub recipient
   1316         #|DECISION=default
   1317         #|OVERWRITE=yes
   1318       }
   1319       When call cmd_init -psub recipient
   1320       The status should be success
   1321       The output should be blank
   1322       The error should equal "$(result)"
   1323     End
   1324 
   1325     It 'initializes a subdirectory with an expanded short option'
   1326       result() {
   1327         %text
   1328         #|$ check_sneaky_path sub
   1329         #|$ do_init sub recipient
   1330         #|DECISION=default
   1331         #|OVERWRITE=yes
   1332       }
   1333       When call cmd_init -p sub recipient
   1334       The status should be success
   1335       The output should be blank
   1336       The error should equal "$(result)"
   1337     End
   1338 
   1339     It 'interactively initializes a directory with collapsed short options'
   1340       result() {
   1341         %text
   1342         #|$ check_sneaky_path sub
   1343         #|$ do_init sub recipient
   1344         #|DECISION=interactive
   1345         #|OVERWRITE=yes
   1346       }
   1347       When call cmd_init -ipsub recipient
   1348       The status should be success
   1349       The output should be blank
   1350       The error should equal "$(result)"
   1351     End
   1352 
   1353     It 'conservatively initializes a directory with long options'
   1354       result() {
   1355         %text
   1356         #|$ check_sneaky_path sub
   1357         #|$ do_init sub recipient
   1358         #|DECISION=keep
   1359         #|OVERWRITE=yes
   1360       }
   1361       When call cmd_init --path sub --keep recipient
   1362       The status should be success
   1363       The output should be blank
   1364       The error should equal "$(result)"
   1365     End
   1366 
   1367     It 'de-initializes a subdirectory'
   1368       result() {
   1369         %text
   1370         #|$ check_sneaky_path sub
   1371         #|$ do_deinit sub
   1372         #|DECISION=default
   1373       }
   1374       When call cmd_init -p sub ''
   1375       The status should be success
   1376       The output should be blank
   1377       The error should equal "$(result)"
   1378     End
   1379 
   1380     It 'supports recipients starting with dash'
   1381       result() {
   1382         %text | @sed 's/\$$//'
   1383         #|$ check_sneaky_path $
   1384         #|$ do_init  -recipient
   1385         #|DECISION=default
   1386         #|OVERWRITE=yes
   1387       }
   1388       When call cmd_init -- -recipient
   1389       The status should be success
   1390       The output should be blank
   1391       The error should equal "$(result)"
   1392     End
   1393 
   1394     usage_text() { %text
   1395       #|Usage: prg init [--interactive,-i | --keep,-k ]
   1396       #|                [--path=subfolder,-p subfolder] age-recipient ...
   1397     }
   1398 
   1399     It 'reports a bad option'
   1400       cat() { @cat; }
   1401       When run cmd_init -q arg
   1402       The output should be blank
   1403       The error should equal "$(usage_text)"
   1404       The status should equal 1
   1405     End
   1406 
   1407     It 'reports conflicting options (`-i` then `-k`)'
   1408       cat() { @cat; }
   1409       When run cmd_init -ik arg
   1410       The output should be blank
   1411       The error should equal "$(usage_text)"
   1412       The status should equal 1
   1413     End
   1414 
   1415     It 'reports conflicting options (`-k` then `-i`)'
   1416       cat() { @cat; }
   1417       When run cmd_init --keep --interactive arg
   1418       The output should be blank
   1419       The error should equal "$(usage_text)"
   1420       The status should equal 1
   1421     End
   1422 
   1423     It 'reports a missing recipient'
   1424       cat() { @cat; }
   1425       When run cmd_init -p sub
   1426       The output should be blank
   1427       The error should equal "$(usage_text)"
   1428       The status should equal 1
   1429     End
   1430 
   1431     It 'reports a missing path'
   1432       cat() { @cat; }
   1433       When run cmd_init -p
   1434       The output should be blank
   1435       The error should equal "$(usage_text)"
   1436       The status should equal 1
   1437     End
   1438 
   1439     It 'reports a lack of argument'
   1440       cat() { @cat; }
   1441       When run cmd_init
   1442       The output should be blank
   1443       The error should equal "$(usage_text)"
   1444       The status should equal 1
   1445     End
   1446   End
   1447 
   1448   Describe 'cmd_insert'
   1449     COMMAND=insert
   1450 
   1451     It 'inserts a few new entries'
   1452       result() {
   1453         %text
   1454         #|$ check_sneaky_path secret1
   1455         #|$ check_sneaky_path secret2
   1456         #|$ do_insert secret1
   1457         #|ECHO=no
   1458         #|MULTILINE=no
   1459         #|OVERWRITE=no
   1460         #|$ do_insert secret2
   1461         #|ECHO=no
   1462         #|MULTILINE=no
   1463         #|OVERWRITE=no
   1464       }
   1465       When call cmd_insert secret1 secret2
   1466       The status should be success
   1467       The output should be blank
   1468       The error should equal "$(result)"
   1469     End
   1470 
   1471     It 'inserts a new flag-like entry'
   1472       result() {
   1473         %text
   1474         #|$ check_sneaky_path -c
   1475         #|$ do_insert -c
   1476         #|ECHO=no
   1477         #|MULTILINE=no
   1478         #|OVERWRITE=no
   1479       }
   1480       When call cmd_insert -- -c
   1481       The status should be success
   1482       The output should be blank
   1483       The error should equal "$(result)"
   1484     End
   1485 
   1486     It 'inserts a new entry with echo (short option)'
   1487       result() {
   1488         %text
   1489         #|$ check_sneaky_path secret
   1490         #|$ do_insert secret
   1491         #|ECHO=yes
   1492         #|MULTILINE=no
   1493         #|OVERWRITE=no
   1494       }
   1495       When call cmd_insert -e secret
   1496       The status should be success
   1497       The output should be blank
   1498       The error should equal "$(result)"
   1499     End
   1500 
   1501     It 'inserts a new entry with echo (long option)'
   1502       result() {
   1503         %text
   1504         #|$ check_sneaky_path secret
   1505         #|$ do_insert secret
   1506         #|ECHO=yes
   1507         #|MULTILINE=no
   1508         #|OVERWRITE=no
   1509       }
   1510       When call cmd_insert --echo secret
   1511       The status should be success
   1512       The output should be blank
   1513       The error should equal "$(result)"
   1514     End
   1515 
   1516     It 'forcefully inserts a new entry with echo (short option)'
   1517       result() {
   1518         %text
   1519         #|$ check_sneaky_path secret
   1520         #|$ do_insert secret
   1521         #|ECHO=yes
   1522         #|MULTILINE=no
   1523         #|OVERWRITE=yes
   1524       }
   1525       When call cmd_insert -fe secret
   1526       The status should be success
   1527       The output should be blank
   1528       The error should equal "$(result)"
   1529     End
   1530 
   1531     It 'forcefully inserts a new entry with echo (short options)'
   1532       result() {
   1533         %text
   1534         #|$ check_sneaky_path secret
   1535         #|$ do_insert secret
   1536         #|ECHO=yes
   1537         #|MULTILINE=no
   1538         #|OVERWRITE=yes
   1539       }
   1540       When call cmd_insert -e -f secret
   1541       The status should be success
   1542       The output should be blank
   1543       The error should equal "$(result)"
   1544     End
   1545 
   1546     It 'forcefully inserts a new entry with echo (long option)'
   1547       result() {
   1548         %text
   1549         #|$ check_sneaky_path secret
   1550         #|$ do_insert secret
   1551         #|ECHO=yes
   1552         #|MULTILINE=no
   1553         #|OVERWRITE=yes
   1554       }
   1555       When call cmd_insert --force --echo secret
   1556       The status should be success
   1557       The output should be blank
   1558       The error should equal "$(result)"
   1559     End
   1560 
   1561     It 'inserts a new multiline entry (short option)'
   1562       result() {
   1563         %text
   1564         #|$ check_sneaky_path secret
   1565         #|$ do_insert secret
   1566         #|ECHO=no
   1567         #|MULTILINE=yes
   1568         #|OVERWRITE=no
   1569       }
   1570       When call cmd_insert -m secret
   1571       The status should be success
   1572       The output should be blank
   1573       The error should equal "$(result)"
   1574     End
   1575 
   1576     It 'inserts a new multiline entry (long option)'
   1577       result() {
   1578         %text
   1579         #|$ check_sneaky_path secret
   1580         #|$ do_insert secret
   1581         #|ECHO=no
   1582         #|MULTILINE=yes
   1583         #|OVERWRITE=no
   1584       }
   1585       When call cmd_insert --multiline secret
   1586       The status should be success
   1587       The output should be blank
   1588       The error should equal "$(result)"
   1589     End
   1590 
   1591     It 'forcefully inserts a new multiline entry (short option)'
   1592       result() {
   1593         %text
   1594         #|$ check_sneaky_path secret
   1595         #|$ do_insert secret
   1596         #|ECHO=no
   1597         #|MULTILINE=yes
   1598         #|OVERWRITE=yes
   1599       }
   1600       When call cmd_insert -mf secret
   1601       The status should be success
   1602       The output should be blank
   1603       The error should equal "$(result)"
   1604     End
   1605 
   1606     It 'forcefully inserts a new multiline entry (short options)'
   1607       result() {
   1608         %text
   1609         #|$ check_sneaky_path secret
   1610         #|$ do_insert secret
   1611         #|ECHO=no
   1612         #|MULTILINE=yes
   1613         #|OVERWRITE=yes
   1614       }
   1615       When call cmd_insert -m -f secret
   1616       The status should be success
   1617       The output should be blank
   1618       The error should equal "$(result)"
   1619     End
   1620 
   1621     It 'forcefully inserts a new multiline entry (long option)'
   1622       result() {
   1623         %text
   1624         #|$ check_sneaky_path secret
   1625         #|$ do_insert secret
   1626         #|ECHO=no
   1627         #|MULTILINE=yes
   1628         #|OVERWRITE=yes
   1629       }
   1630       When call cmd_insert --force --multiline secret
   1631       The status should be success
   1632       The output should be blank
   1633       The error should equal "$(result)"
   1634     End
   1635 
   1636     It 'reports a bad option'
   1637       cat() { @cat; }
   1638       When run cmd_insert -u secret
   1639       The output should be blank
   1640       The error should equal \
   1641         'Usage: prg insert [--echo,-e | --multiline,-m] [--force,-f] pass-name'
   1642       The status should equal 1
   1643     End
   1644 
   1645     It 'reports incompatible long options'
   1646       cat() { @cat; }
   1647       When run cmd_insert --multiline --echo secret
   1648       The output should be blank
   1649       The error should equal \
   1650         'Usage: prg insert [--echo,-e | --multiline,-m] [--force,-f] pass-name'
   1651       The status should equal 1
   1652     End
   1653 
   1654     It 'reports incompatible short options'
   1655       cat() { @cat; }
   1656       When run cmd_insert -em secret
   1657       The output should be blank
   1658       The error should equal \
   1659         'Usage: prg insert [--echo,-e | --multiline,-m] [--force,-f] pass-name'
   1660       The status should equal 1
   1661     End
   1662 
   1663     It 'reports a lack of argument'
   1664       cat() { @cat; }
   1665       When run cmd_insert
   1666       The output should be blank
   1667       The error should equal \
   1668         'Usage: prg insert [--echo,-e | --multiline,-m] [--force,-f] pass-name'
   1669       The status should equal 1
   1670     End
   1671   End
   1672 
   1673   Describe 'cmd_list_or_show'
   1674     COMMAND=
   1675 
   1676     It 'lists the whole store'
   1677       result() {
   1678         %text | @sed 's/\$$//'
   1679         #|$ do_list_or_show $
   1680         #|SELECTED_LINE=1
   1681         #|SHOW=text
   1682       }
   1683       When call cmd_list_or_show
   1684       The status should be success
   1685       The output should be blank
   1686       The error should equal "$(result)"
   1687     End
   1688 
   1689     It 'shows multiple entries'
   1690       result() {
   1691         %text
   1692         #|$ check_sneaky_path arg1
   1693         #|$ check_sneaky_path arg2
   1694         #|$ check_sneaky_path arg3
   1695         #|$ do_list_or_show arg1
   1696         #|SELECTED_LINE=1
   1697         #|SHOW=text
   1698         #|$ do_list_or_show arg2
   1699         #|SELECTED_LINE=1
   1700         #|SHOW=text
   1701         #|$ do_list_or_show arg3
   1702         #|SELECTED_LINE=1
   1703         #|SHOW=text
   1704       }
   1705       When call cmd_list_or_show arg1 arg2 arg3
   1706       The status should be success
   1707       The output should be blank
   1708       The error should equal "$(result)"
   1709     End
   1710 
   1711     It 'shows a flag-like entry'
   1712       result() {
   1713         %text
   1714         #|$ check_sneaky_path -c
   1715         #|$ do_list_or_show -c
   1716         #|SELECTED_LINE=1
   1717         #|SHOW=text
   1718       }
   1719       When call cmd_list_or_show -- -c
   1720       The status should be success
   1721       The output should be blank
   1722       The error should equal "$(result)"
   1723     End
   1724 
   1725     It 'copies an entry into the clipboard (short option)'
   1726       result() {
   1727         %text
   1728         #|$ check_sneaky_path arg
   1729         #|$ do_list_or_show arg
   1730         #|SELECTED_LINE=1
   1731         #|SHOW=clip
   1732       }
   1733       When call cmd_list_or_show -c arg
   1734       The status should be success
   1735       The output should be blank
   1736       The error should equal "$(result)"
   1737     End
   1738 
   1739     It 'copies an entry into the clipboard (long option)'
   1740       result() {
   1741         %text
   1742         #|$ check_sneaky_path arg
   1743         #|$ do_list_or_show arg
   1744         #|SELECTED_LINE=1
   1745         #|SHOW=clip
   1746       }
   1747       When call cmd_list_or_show --clip arg
   1748       The status should be success
   1749       The output should be blank
   1750       The error should equal "$(result)"
   1751     End
   1752 
   1753     It 'copies a line of an entry into the clipboard (short option)'
   1754       result() {
   1755         %text
   1756         #|$ check_sneaky_path arg
   1757         #|$ do_list_or_show arg
   1758         #|SELECTED_LINE=2
   1759         #|SHOW=clip
   1760       }
   1761       When call cmd_list_or_show -c2 arg
   1762       The status should be success
   1763       The output should be blank
   1764       The error should equal "$(result)"
   1765     End
   1766 
   1767     It 'copies a line of an entry into the clipboard (short option)'
   1768       result() {
   1769         %text
   1770         #|$ check_sneaky_path arg
   1771         #|$ do_list_or_show arg
   1772         #|SELECTED_LINE=2
   1773         #|SHOW=clip
   1774       }
   1775       When call cmd_list_or_show --clip=2 arg
   1776       The status should be success
   1777       The output should be blank
   1778       The error should equal "$(result)"
   1779     End
   1780 
   1781     It 'shows an entry as a QR-code (short option)'
   1782       result() {
   1783         %text
   1784         #|$ check_sneaky_path arg
   1785         #|$ do_list_or_show arg
   1786         #|SELECTED_LINE=1
   1787         #|SHOW=qrcode
   1788       }
   1789       When call cmd_list_or_show -q arg
   1790       The status should be success
   1791       The output should be blank
   1792       The error should equal "$(result)"
   1793     End
   1794 
   1795     It 'shows an entry as a QR-code (long option)'
   1796       result() {
   1797         %text
   1798         #|$ check_sneaky_path arg
   1799         #|$ do_list_or_show arg
   1800         #|SELECTED_LINE=1
   1801         #|SHOW=qrcode
   1802       }
   1803       When call cmd_list_or_show --qrcode arg
   1804       The status should be success
   1805       The output should be blank
   1806       The error should equal "$(result)"
   1807     End
   1808 
   1809     It 'shows the line of an entry as a QR-code (short option)'
   1810       result() {
   1811         %text
   1812         #|$ check_sneaky_path arg
   1813         #|$ do_list_or_show arg
   1814         #|SELECTED_LINE=3
   1815         #|SHOW=qrcode
   1816       }
   1817       When call cmd_list_or_show -q3 arg
   1818       The status should be success
   1819       The output should be blank
   1820       The error should equal "$(result)"
   1821     End
   1822 
   1823     It 'shows the line of an entry as a QR-code (long option)'
   1824       result() {
   1825         %text
   1826         #|$ check_sneaky_path arg
   1827         #|$ do_list_or_show arg
   1828         #|SELECTED_LINE=3
   1829         #|SHOW=qrcode
   1830       }
   1831       When call cmd_list_or_show --qrcode=3 arg
   1832       The status should be success
   1833       The output should be blank
   1834       The error should equal "$(result)"
   1835     End
   1836 
   1837     It 'reports incompatible show options'
   1838       cat() { @cat; }
   1839       result() { %text
   1840         #|Usage: prg [list] [subfolder]
   1841         #|       prg [show] [--clip[=line-number],-c[line-number] |
   1842         #|                   --qrcode[=line-number],-q[line-number]] pass-name
   1843       }
   1844       When run cmd_list_or_show -q -c arg
   1845       The output should be blank
   1846       The error should equal "$(result)"
   1847       The status should equal 1
   1848     End
   1849 
   1850     It 'reports a bad option for both commands'
   1851       cat() { @cat; }
   1852       result() { %text
   1853         #|Usage: prg [list] [subfolder]
   1854         #|       prg [show] [--clip[=line-number],-c[line-number] |
   1855         #|                   --qrcode[=line-number],-q[line-number]] pass-name
   1856       }
   1857       When run cmd_list_or_show -f arg
   1858       The output should be blank
   1859       The error should equal "$(result)"
   1860       The status should equal 1
   1861     End
   1862 
   1863     It 'reports a bad option for list command'
   1864       COMMAND=list
   1865       cat() { @cat; }
   1866       result() { %text
   1867         #|Usage: prg [list] [subfolder]
   1868       }
   1869       When run cmd_list_or_show -f arg
   1870       The output should be blank
   1871       The error should equal "$(result)"
   1872       The status should equal 1
   1873     End
   1874 
   1875     It 'reports a bad option for show command'
   1876       COMMAND=show
   1877       cat() { @cat; }
   1878       result() { %text
   1879         #|Usage: prg [show] [--clip[=line-number],-c[line-number] |
   1880         #|                   --qrcode[=line-number],-q[line-number]] pass-name
   1881       }
   1882       When run cmd_list_or_show -f arg
   1883       The output should be blank
   1884       The error should equal "$(result)"
   1885       The status should equal 1
   1886     End
   1887   End
   1888 
   1889   Describe 'cmd_move'
   1890     COMMAND=move
   1891 
   1892     It 'moves multiple files'
   1893       result() {
   1894         %text
   1895         #|$ check_sneaky_path src1
   1896         #|$ check_sneaky_path src2
   1897         #|$ check_sneaky_path src3
   1898         #|$ check_sneaky_path dest
   1899         #|$ do_copy_move src1 dest/
   1900         #|ACTION=Move
   1901         #|DECISION=default
   1902         #|OVERWRITE=no
   1903         #|SCM_ACTION=scm_mv
   1904         #|$ do_copy_move src2 dest/
   1905         #|ACTION=Move
   1906         #|DECISION=default
   1907         #|OVERWRITE=no
   1908         #|SCM_ACTION=scm_mv
   1909         #|$ do_copy_move src3 dest/
   1910         #|ACTION=Move
   1911         #|DECISION=default
   1912         #|OVERWRITE=no
   1913         #|SCM_ACTION=scm_mv
   1914       }
   1915       When call cmd_move src1 src2 src3 dest
   1916       The status should be success
   1917       The output should be blank
   1918       The error should equal "$(result)"
   1919     End
   1920 
   1921     It 'moves forcefully with a long option'
   1922       result() {
   1923         %text
   1924         #|$ check_sneaky_path src
   1925         #|$ check_sneaky_path dest
   1926         #|$ do_copy_move src dest
   1927         #|ACTION=Move
   1928         #|DECISION=default
   1929         #|OVERWRITE=yes
   1930         #|SCM_ACTION=scm_mv
   1931       }
   1932       When call cmd_move --force src dest
   1933       The status should be success
   1934       The output should be blank
   1935       The error should equal "$(result)"
   1936     End
   1937 
   1938     It 'moves forcefully with a short option'
   1939       result() {
   1940         %text
   1941         #|$ check_sneaky_path src
   1942         #|$ check_sneaky_path dest
   1943         #|$ do_copy_move src dest
   1944         #|ACTION=Move
   1945         #|DECISION=default
   1946         #|OVERWRITE=yes
   1947         #|SCM_ACTION=scm_mv
   1948       }
   1949       When call cmd_move -f src dest
   1950       The status should be success
   1951       The output should be blank
   1952       The error should equal "$(result)"
   1953     End
   1954 
   1955     It 'always reencrypts with a long option'
   1956       result() {
   1957         %text
   1958         #|$ check_sneaky_path src
   1959         #|$ check_sneaky_path dest
   1960         #|$ do_copy_move src dest
   1961         #|ACTION=Move
   1962         #|DECISION=force
   1963         #|OVERWRITE=no
   1964         #|SCM_ACTION=scm_mv
   1965       }
   1966       When call cmd_move --reencrypt src dest
   1967       The status should be success
   1968       The output should be blank
   1969       The error should equal "$(result)"
   1970     End
   1971 
   1972     It 'always reencrypts with a short option'
   1973       result() {
   1974         %text
   1975         #|$ check_sneaky_path src
   1976         #|$ check_sneaky_path dest
   1977         #|$ do_copy_move src dest
   1978         #|ACTION=Move
   1979         #|DECISION=force
   1980         #|OVERWRITE=no
   1981         #|SCM_ACTION=scm_mv
   1982       }
   1983       When call cmd_move -e src dest
   1984       The status should be success
   1985       The output should be blank
   1986       The error should equal "$(result)"
   1987     End
   1988 
   1989     It 'interactively reencrypts with a long option'
   1990       result() {
   1991         %text
   1992         #|$ check_sneaky_path src
   1993         #|$ check_sneaky_path dest
   1994         #|$ do_copy_move src dest
   1995         #|ACTION=Move
   1996         #|DECISION=interactive
   1997         #|OVERWRITE=no
   1998         #|SCM_ACTION=scm_mv
   1999       }
   2000       When call cmd_move --interactive src dest
   2001       The status should be success
   2002       The output should be blank
   2003       The error should equal "$(result)"
   2004     End
   2005 
   2006     It 'interactively reencrypts with a short option'
   2007       result() {
   2008         %text
   2009         #|$ check_sneaky_path src
   2010         #|$ check_sneaky_path dest
   2011         #|$ do_copy_move src dest
   2012         #|ACTION=Move
   2013         #|DECISION=interactive
   2014         #|OVERWRITE=no
   2015         #|SCM_ACTION=scm_mv
   2016       }
   2017       When call cmd_move -i src dest
   2018       The status should be success
   2019       The output should be blank
   2020       The error should equal "$(result)"
   2021     End
   2022 
   2023     It 'never reencrypts with a long option'
   2024       result() {
   2025         %text
   2026         #|$ check_sneaky_path src
   2027         #|$ check_sneaky_path dest
   2028         #|$ do_copy_move src dest
   2029         #|ACTION=Move
   2030         #|DECISION=keep
   2031         #|OVERWRITE=no
   2032         #|SCM_ACTION=scm_mv
   2033       }
   2034       When call cmd_move --keep src dest
   2035       The status should be success
   2036       The output should be blank
   2037       The error should equal "$(result)"
   2038     End
   2039 
   2040     It 'never reencrypts with a short option'
   2041       result() {
   2042         %text
   2043         #|$ check_sneaky_path src
   2044         #|$ check_sneaky_path dest
   2045         #|$ do_copy_move src dest
   2046         #|ACTION=Move
   2047         #|DECISION=keep
   2048         #|OVERWRITE=no
   2049         #|SCM_ACTION=scm_mv
   2050       }
   2051       When call cmd_move -k src dest
   2052       The status should be success
   2053       The output should be blank
   2054       The error should equal "$(result)"
   2055     End
   2056 
   2057     It 'moves a file named like a flag'
   2058       result() {
   2059         %text
   2060         #|$ check_sneaky_path -s
   2061         #|$ check_sneaky_path dest
   2062         #|$ do_copy_move -s dest
   2063         #|ACTION=Move
   2064         #|DECISION=default
   2065         #|OVERWRITE=no
   2066         #|SCM_ACTION=scm_mv
   2067       }
   2068       When call cmd_move -- -s dest
   2069       The status should be success
   2070       The output should be blank
   2071       The error should equal "$(result)"
   2072     End
   2073 
   2074     usage_text() { %text
   2075       #|Usage: prg move [--reencrypt,-e | --interactive,-i | --keep,-k ]
   2076       #|                [--force,-f] old-path new-path
   2077     }
   2078 
   2079     It 'reports a bad option'
   2080       cat() { @cat; }
   2081       When run cmd_move -s arg
   2082       The output should be blank
   2083       The error should equal "$(usage_text)"
   2084       The status should equal 1
   2085     End
   2086 
   2087     It 'reports incompatible re-encryption options (-e and -i)'
   2088       cat() { @cat; }
   2089       When run cmd_move -ei src dest
   2090       The output should be blank
   2091       The error should equal "$(usage_text)"
   2092       The status should equal 1
   2093     End
   2094 
   2095     It 'reports incompatible re-encryption options (-i and -k)'
   2096       cat() { @cat; }
   2097       When run cmd_move -ik src dest
   2098       The output should be blank
   2099       The error should equal "$(usage_text)"
   2100       The status should equal 1
   2101     End
   2102 
   2103     It 'reports incompatible re-encryption options (-k and -e)'
   2104       cat() { @cat; }
   2105       When run cmd_move -ke src dest
   2106       The output should be blank
   2107       The error should equal "$(usage_text)"
   2108       The status should equal 1
   2109     End
   2110 
   2111     It 'reports a lack of argument'
   2112       cat() { @cat; }
   2113       When run cmd_move src
   2114       The output should be blank
   2115       The error should equal "$(usage_text)"
   2116       The status should equal 1
   2117     End
   2118   End
   2119 
   2120   Describe 'cmd_random'
   2121     COMMAND=generate
   2122     GENERATED_LENGTH=25
   2123 
   2124     random_chars() { mocklog random_chars "$@"; }
   2125 
   2126     It 'generates random characters with default parameters'
   2127       When run cmd_random
   2128       The status should be success
   2129       The error should equal \
   2130         "$ random_chars ${GENERATED_LENGTH} ${CHARACTER_SET}"
   2131     End
   2132 
   2133     It 'generates random characters with default character set'
   2134       When run cmd_random 8
   2135       The status should be success
   2136       The error should equal "$ random_chars 8 ${CHARACTER_SET}"
   2137     End
   2138 
   2139     It 'generates random characters with the given arguments'
   2140       When run cmd_random 8 a-z
   2141       The status should be success
   2142       The error should equal "$ random_chars 8 a-z"
   2143     End
   2144 
   2145     It 'reports too many arguments'
   2146       cat() { @cat; }
   2147       When run cmd_random 1 2 3
   2148       The output should be blank
   2149       The error should equal 'Usage: prg random [pass-length [character-set]]'
   2150       The status should equal 1
   2151     End
   2152   End
   2153 
   2154   Describe 'cmd_reencrypt'
   2155     COMMAND=reencrypt
   2156 
   2157     It 're-encrypts multiple files and directories'
   2158       result() {
   2159         %text
   2160         #|$ check_sneaky_path file-1
   2161         #|$ check_sneaky_path dir/
   2162         #|$ check_sneaky_path sub/file-2
   2163         #|$ do_reencrypt file-1
   2164         #|DECISION=default
   2165         #|$ do_reencrypt dir/
   2166         #|DECISION=default
   2167         #|$ do_reencrypt sub/file-2
   2168         #|DECISION=default
   2169       }
   2170       When call cmd_reencrypt file-1 dir/ sub/file-2
   2171       The status should be success
   2172       The output should be blank
   2173       The error should equal "$(result)"
   2174     End
   2175 
   2176     It 'interactively re-encrypts with a long option'
   2177       result() {
   2178         %text
   2179         #|$ check_sneaky_path arg
   2180         #|$ do_reencrypt arg
   2181         #|DECISION=interactive
   2182       }
   2183       When call cmd_reencrypt --interactive arg
   2184       The status should be success
   2185       The output should be blank
   2186       The error should equal "$(result)"
   2187     End
   2188 
   2189     It 'interactively re-encrypts with a short option'
   2190       result() {
   2191         %text
   2192         #|$ check_sneaky_path arg
   2193         #|$ do_reencrypt arg
   2194         #|DECISION=interactive
   2195       }
   2196       When call cmd_reencrypt -i arg
   2197       The status should be success
   2198       The output should be blank
   2199       The error should equal "$(result)"
   2200     End
   2201 
   2202     It 're-encrypts a file named like a flag'
   2203       result() {
   2204         %text
   2205         #|$ check_sneaky_path -s
   2206         #|$ do_reencrypt -s
   2207         #|DECISION=default
   2208       }
   2209       When call cmd_reencrypt -- -s
   2210       The status should be success
   2211       The output should be blank
   2212       The error should equal "$(result)"
   2213     End
   2214 
   2215     usage_text() { %text
   2216       #|Usage: prg reencrypt [--interactive,-i] pass-name|subfolder ...
   2217     }
   2218 
   2219     It 'reports a bad option'
   2220       cat() { @cat; }
   2221       When run cmd_reencrypt -s arg
   2222       The status should equal 1
   2223       The output should be blank
   2224       The error should equal "$(usage_text)"
   2225     End
   2226 
   2227     It 'reports a lack of argument'
   2228       cat() { @cat; }
   2229       When run cmd_reencrypt
   2230       The status should equal 1
   2231       The output should be blank
   2232       The error should equal "$(usage_text)"
   2233     End
   2234   End
   2235 
   2236   Describe 'cmd_usage'
   2237     COMMAND=usage
   2238     CLIP_TIME='$CLIP_TIME'
   2239     GENERATED_LENGTH='$GENERATED_LENGTH'
   2240     cat() { @cat; }
   2241 
   2242     It 'displays a human-reable usage string'
   2243       When call cmd_usage
   2244       The status should be success
   2245       The first line of output should equal 'Usage:'
   2246       The error should be blank
   2247     End
   2248 
   2249     It 'includes help about all commands'
   2250       When call cmd_usage
   2251       The status should be success
   2252       The output should include 'prg copy'
   2253       The output should include 'prg delete'
   2254       The output should include 'prg edit'
   2255       The output should include 'prg find'
   2256       The output should include 'prg generate'
   2257       The output should include 'prg git'
   2258       The output should include 'prg gitconfig'
   2259       The output should include 'prg grep'
   2260       The output should include 'prg help'
   2261       The output should include 'prg init'
   2262       The output should include 'prg insert'
   2263       The output should include 'prg [list]'
   2264       The output should include 'prg move'
   2265       The output should include 'prg random'
   2266       The output should include 'prg reencrypt'
   2267       The output should include 'prg [show]'
   2268       The output should include 'prg version'
   2269     End
   2270 
   2271     It 'rejects unknown commands'
   2272       When run cmd_usage '> ' foo
   2273       The output should be blank
   2274       The error should equal 'cmd_usage: unknown command "foo"'
   2275       The status should equal 1
   2276     End
   2277   End
   2278 
   2279   Describe 'cmd_version'
   2280     COMMAND=version
   2281 
   2282     It 'shows the version box'
   2283       cat() { @cat; }
   2284       result() {
   2285         %text
   2286         #|==============================================
   2287         #|= pashage: age-backed POSIX password manager =
   2288         #|=                                            =
   2289         #|=                   v*
   2290         #|=                                            =
   2291         #|=            Natasha Kerensikova             =
   2292         #|=                                            =
   2293         #|=                 Based on:                  =
   2294         #|=   password-store  by Jason A. Donenfeld    =
   2295         #|=          passage  by Filippo Valsorda      =
   2296         #|=             pash  by Dylan Araps           =
   2297         #|==============================================
   2298       }
   2299       When call cmd_version
   2300       The status should be success
   2301       The output should match pattern "$(result)"
   2302     End
   2303   End
   2304 End