diff --git a/tests/features/password.feature b/tests/features/password.feature index 332ba86e..304f51c5 100644 --- a/tests/features/password.feature +++ b/tests/features/password.feature @@ -4,113 +4,9 @@ Feature: Using the installed keyring Given we use the config "multiple.yaml" And we have a keyring When we run "jrnl simple --encrypt" and enter - """ - sabertooth - sabertooth - y - """ + sabertooth + sabertooth + Y Then the config for journal "simple" should have "encrypt" set to "bool:True" When we run "jrnl simple -n 1" Then the output should contain "2013-06-10 15:40 Life is good" - - Scenario: Encrypt journal with no keyring backend and do not store in keyring - Given we use the config "simple.yaml" - And we do not have a keyring - When we run "jrnl test entry" - And we run "jrnl --encrypt" and enter - """ - password - password - n - """ - Then we should get no error - And we should not see the message "Failed to retrieve keyring" - - Scenario: Encrypt journal with no keyring backend and do store in keyring - Given we use the config "simple.yaml" - And we do not have a keyring - When we run "jrnl test entry" - And we run "jrnl --encrypt" and enter - """ - password - password - y - """ - Then we should get no error - And we should not see the message "Failed to retrieve keyring" - # @todo add step to check contents of keyring - - @todo - Scenario: Open an encrypted journal with wrong password in keyring - # This should ask the user for the password after the keyring fails - - @todo - Scenario: Decrypt journal with password in keyring - - @todo - Scenario: Decrypt journal without a keyring - - Scenario: Encrypt journal when keyring exists but fails - Given we use the config "simple.yaml" - And we have a failed keyring - When we run "jrnl --encrypt" and enter - """ - this password will not be saved in keyring - this password will not be saved in keyring - y - """ - Then we should see the message "Failed to retrieve keyring" - And we should get no error - And we should be prompted for a password - And the config for journal "default" should have "encrypt" set to "bool:True" - - Scenario: Decrypt journal when keyring exists but fails - Given we use the config "encrypted.yaml" - And we have a failed keyring - When we run "jrnl --decrypt" and enter "bad doggie no biscuit" - Then we should see the message "Failed to retrieve keyring" - And we should get no error - And we should be prompted for a password - And we should see the message "Journal decrypted" - And the config for journal "default" should have "encrypt" set to "bool:False" - And the journal should have 2 entries - - Scenario: Open encrypted journal when keyring exists but fails - # This should ask the user for the password after the keyring fails - Given we use the config "encrypted.yaml" - And we have a failed keyring - When we run "jrnl -n 1" and enter "bad doggie no biscuit" - Then we should see the message "Failed to retrieve keyring" - And we should get no error - And we should be prompted for a password - And the output should contain "2013-06-10 15:40 Life is good" - - Scenario: Mistyping your password - Given we use the config "simple.yaml" - When we run "jrnl --encrypt" and enter - """ - swordfish - sordfish - """ - Then we should be prompted for a password - And we should see the message "Passwords did not match" - And the config for journal "default" should not have "encrypt" set - And the journal should have 2 entries - - Scenario: Mistyping your password, then getting it right - Given we use the config "simple.yaml" - When we run "jrnl --encrypt" and enter - """ - swordfish - sordfish - swordfish - swordfish - n - """ - Then we should be prompted for a password - And we should see the message "Passwords did not match" - And we should see the message "Journal encrypted" - And the config for journal "default" should have "encrypt" set to "bool:True" - When we run "jrnl -n 1" and enter "swordfish" - Then we should be prompted for a password - And the output should contain "2013-06-10 15:40 Life is good" diff --git a/tests/step_defs/conftest.py b/tests/step_defs/conftest.py index 6f86124b..2544c470 100644 --- a/tests/step_defs/conftest.py +++ b/tests/step_defs/conftest.py @@ -1,7 +1,12 @@ # Copyright (C) 2012-2021 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html +import ast import os +from collections import defaultdict +from keyring import backend +from keyring import set_keyring +from keyring import errors import re import shutil import tempfile @@ -16,14 +21,75 @@ import toml from jrnl import __version__ from jrnl.cli import cli +from jrnl.config import load_config from jrnl.os_compat import split_args +class TestKeyring(backend.KeyringBackend): + """A test keyring that just stores its values in a hash""" + + priority = 1 + keys = defaultdict(dict) + + def set_password(self, servicename, username, password): + self.keys[servicename][username] = password + + def get_password(self, servicename, username): + return self.keys[servicename].get(username) + + def delete_password(self, servicename, username): + self.keys[servicename][username] = None + + +class NoKeyring(backend.KeyringBackend): + """A keyring that simulated an environment with no keyring backend.""" + + priority = 2 + keys = defaultdict(dict) + + def set_password(self, servicename, username, password): + raise errors.NoKeyringError + + def get_password(self, servicename, username): + raise errors.NoKeyringError + + def delete_password(self, servicename, username): + raise errors.NoKeyringError + + +class FailedKeyring(backend.KeyringBackend): + """ + A keyring that cannot be retrieved. + """ + + priority = 2 + + def set_password(self, servicename, username, password): + raise errors.KeyringError + + def get_password(self, servicename, username): + raise errors.KeyringError + + def delete_password(self, servicename, username): + raise errors.KeyringError + + # ----- UTILS ----- # def failed_msg(msg, expected, actual): return f"{msg}\nExpected:\n{expected}\n---end---\nActual:\n{actual}\n---end---\n" +def read_value_from_string(string): + if string[0] == "{": + # Handle value being a dictionary + return ast.literal_eval(string) + + # Takes strings like "bool:true" or "int:32" and coerces them into proper type + t, value = string.split(":") + value = {"bool": lambda v: v.lower() == "true", "int": int, "str": str}[t](value) + return value + + # ----- FIXTURES ----- # @fixture def cli_run(): @@ -46,22 +112,38 @@ def toml_version(working_dir): pyproject_contents = toml.load(pyproject) return pyproject_contents["tool"]["poetry"]["version"] + @fixture def password(): - return '' + return "" @fixture def command(): - return '' + return "" @fixture def user_input(): - return '' + return "" + + +@fixture +def keyring(): + set_keyring(NoKeyring()) + + +@fixture +def config_data(config_path): + return load_config(config_path) # ----- STEPS ----- # +@given("we have a keyring", target_fixture="keyring") +def we_have_keyring(): + set_keyring(FailedKeyring()) + + @given(parse('we use the config "{config_file}"'), target_fixture="config_path") @given('we use the config ""', target_fixture="config_path") def we_use_the_config(config_file, temp_dir, working_dir): @@ -99,10 +181,13 @@ def use_password_forever(pw): @when('we run "jrnl"') @when(parse('we run "jrnl" and enter "{user_input}"')) @when(parse('we run "jrnl {command}" and enter\n{user_input}')) -def we_run(command, config_path, user_input, cli_run, capsys, password): +def we_run(command, config_path, user_input, cli_run, capsys, password, keyring): args = split_args(command) status = 0 + if not password and user_input: + password = user_input + # fmt: off # see: https://github.com/psf/black/issues/664 with \ @@ -163,9 +248,9 @@ def output_should_not_contain(output, cli_run): def output_should_be(output, cli_run): actual_out = cli_run["stdout"].strip() output = output.strip() - assert ( - output and output == actual_out - ), failed_msg('Output does not match.', output, actual_out) + assert output and output == actual_out, failed_msg( + "Output does not match.", output, actual_out + ) @then('the output should contain the date ""') @@ -183,3 +268,17 @@ def output_should_contain_version(cli_run, toml_version): def should_see_the_message(text, cli_run): out = cli_run["stderr"] assert text in out, [text, out] + + +@then(parse('the config should have "{key}" set to')) +@then(parse('the config should have "{key}" set to "{value}"')) +@then(parse('the config for journal "{journal}" should have "{key}" set to "{value}"')) +def config_var(config_data, key, value="", journal=None): + value = read_value_from_string(value) + + configuration = config_data + if journal: + configuration = configuration["journals"][journal] + + assert key in configuration + assert configuration[key] == value diff --git a/tests/step_defs/test_features.py b/tests/step_defs/test_features.py index 6afd5c84..0860059b 100644 --- a/tests/step_defs/test_features.py +++ b/tests/step_defs/test_features.py @@ -9,7 +9,7 @@ scenarios("../features/delete.feature") # scenarios("../features/format.feature") # scenarios("../features/import.feature") # scenarios("../features/multiple_journals.feature") -# scenarios("../features/password.feature") +scenarios("../features/password.feature") # scenarios("../features/search.feature") # scenarios("../features/star.feature") # scenarios("../features/tag.feature")