diff --git a/.gitignore b/.gitignore index 41d2df7b..a2bb0798 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ # C extensions *.so -.python-version # Packages *.egg @@ -17,7 +16,10 @@ sdist develop-eggs .installed.cfg lib64 + +# Versioning .python-version +.tool-version # Installer logs pip-log.txt diff --git a/tests/bdd/features/config_file.feature b/tests/bdd/features/config_file.feature index ce4f042b..4f306b54 100644 --- a/tests/bdd/features/config_file.feature +++ b/tests/bdd/features/config_file.feature @@ -29,7 +29,7 @@ Feature: Multiple journals Given the config "multiple.yaml" exists And we use the config "basic_onefile.yaml" When we run "jrnl --cf multiple.yaml work a long day in the office" - Then we should see the message "Entry added to work journal" + Then the output should contain "Entry added to work journal" Scenario: Write to specified journal with a timestamp using an alternate config Given the config "multiple.yaml" exists @@ -64,7 +64,7 @@ Feature: Multiple journals Given the config "bug343.yaml" exists And we use the config "basic_onefile.yaml" When we run "jrnl --cf bug343.yaml a long day in the office" - Then we should see the message "No default journal configured" + Then the output should contain "No default journal configured" Scenario: Don't crash if no file exists for a configured encrypted journal using an alternate config Given the config "multiple.yaml" exists @@ -73,7 +73,7 @@ Feature: Multiple journals these three eyes these three eyes n - Then we should see the message "Encrypted journal 'new_encrypted' created" + Then the output should contain "Encrypted journal 'new_encrypted' created" Scenario: Don't overwrite main config when encrypting a journal in an alternate config Given the config "basic_onefile.yaml" exists @@ -82,11 +82,14 @@ Feature: Multiple journals these three eyes these three eyes n - Then we should see the message "Journal encrypted to features/journals/basic_onefile.journal" - And the config should contain "encrypt: false" # multiple.yaml remains unchanged + Then the output should contain "Journal encrypted to features/journals/basic_onefile.journal" + And the config should contain "encrypt: false" + Scenario: Don't overwrite main config when decrypting a journal in an alternate config Given the config "editor_encrypted.yaml" exists + And we use the password "bad doggie no biscuit" if prompted And we use the config "basic_encrypted.yaml" When we run "jrnl --cf editor_encrypted.yaml --decrypt" - Then the config should contain "encrypt: true" # basic_encrypted remains unchanged + Then the config should contain "encrypt: true" + And the output should not contain "Wrong password" diff --git a/tests/bdd/features/datetime.feature b/tests/bdd/features/datetime.feature index 0da3027f..167dcf33 100644 --- a/tests/bdd/features/datetime.feature +++ b/tests/bdd/features/datetime.feature @@ -4,7 +4,7 @@ Feature: Reading and writing to journal with custom date formats # https://github.com/jrnl-org/jrnl/issues/117 Given we use the config "simple.yaml" When we run "jrnl 2013-11-30 15:42: Project Started." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -999" Then the output should contain "2013-11-30 15:42 Project Started." @@ -13,7 +13,7 @@ Feature: Reading and writing to journal with custom date formats # https://github.com/jrnl-org/jrnl/issues/185 Given we use the config "simple.yaml" When we run "jrnl 26/06/2099: Planet? Earth. Year? 2099." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -999" Then the output should contain "2099-06-26 09:00 Planet?" @@ -34,7 +34,7 @@ Feature: Reading and writing to journal with custom date formats Scenario Outline: Writing an entry from command line with custom date Given we use the config "" When we run "jrnl " - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -n 1" Then the output should contain "" @@ -87,7 +87,7 @@ Feature: Reading and writing to journal with custom date formats Given we use the config "simple.yaml" And now is "2019-03-12 01:30:32 PM" When we run "jrnl " - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -1" Then the output should contain "" Then the output should contain the date "" @@ -109,7 +109,7 @@ Feature: Reading and writing to journal with custom date formats Given we use the config "simple.yaml" And now is "2019-03-12 01:30:32 PM" When we run "jrnl " - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -1" Then the output should contain "" Then the output should contain the date "" diff --git a/tests/bdd/features/encrypt.feature b/tests/bdd/features/encrypt.feature index af141172..7f466c1d 100644 --- a/tests/bdd/features/encrypt.feature +++ b/tests/bdd/features/encrypt.feature @@ -2,8 +2,9 @@ Feature: Encrypting and decrypting journals Scenario: Decrypting a journal Given we use the config "encrypted.yaml" + # And we use the password "bad doggie no biscuit" if prompted When we run "jrnl --decrypt" and enter "bad doggie no biscuit" - Then we should see the message "Journal decrypted" + Then the output should contain "Journal decrypted" And the config for journal "default" should contain "encrypt: false" When we run "jrnl -99 --short" Then the output should be @@ -35,7 +36,7 @@ Feature: Encrypting and decrypting journals swordfish n Then we should get no error - And we should see the message "Journal encrypted" + And the output should contain "Journal encrypted" And the config for journal "default" should contain "encrypt: true" When we run "jrnl -n 1" and enter "swordfish" Then we should be prompted for a password diff --git a/tests/bdd/features/file_storage.feature b/tests/bdd/features/file_storage.feature index f81f2710..89069568 100644 --- a/tests/bdd/features/file_storage.feature +++ b/tests/bdd/features/file_storage.feature @@ -3,7 +3,7 @@ Feature: Journals iteracting with the file system in a way that users can see Scenario: Adding entries to a Folder journal should generate date files Given we use the config "empty_folder.yaml" When we run "jrnl 23 July 2013: Testing folder journal." - Then we should see the message "Entry added" + Then the output should contain "Entry added" And the journal directory should contain 2013/07/23.txt @@ -11,7 +11,7 @@ Feature: Journals iteracting with the file system in a way that users can see Given we use the config "empty_folder.yaml" When we run "jrnl 23 July 2013: Testing folder journal." And we run "jrnl 3/7/2014: Second entry of journal." - Then we should see the message "Entry added" + Then the output should contain "Entry added" And the journal directory should contain 2013/07/23.txt @@ -32,6 +32,7 @@ Feature: Journals iteracting with the file system in a way that users can see Then the output should contain "This is a new entry in my journal" Scenario: Creating journal with relative path should update to absolute path + Given we use no config When we run "jrnl hello world" and enter test.txt n diff --git a/tests/bdd/features/multiple_journals.feature b/tests/bdd/features/multiple_journals.feature index 381463bf..35df069b 100644 --- a/tests/bdd/features/multiple_journals.feature +++ b/tests/bdd/features/multiple_journals.feature @@ -34,7 +34,7 @@ Feature: Multiple journals Scenario: Tell user which journal was used Given we use the config "multiple.yaml" When we run "jrnl work a long day in the office" - Then we should see the message "Entry added to work journal" + Then the output should contain "Entry added to work journal" Scenario: Write to specified journal with a timestamp Given we use the config "multiple.yaml" diff --git a/tests/bdd/features/override.feature b/tests/bdd/features/override.feature index b29b11f0..fb3c279e 100644 --- a/tests/bdd/features/override.feature +++ b/tests/bdd/features/override.feature @@ -3,9 +3,12 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys Scenario: Override configured editor with built-in input === editor:'' Given we use the config "basic_encrypted.yaml" And we use the password "test" if prompted - When we run "jrnl --config-override editor ''" + When we run "jrnl --config-override editor ''" and enter + This is a journal entry Then the stdin prompt should have been called And the editor should not have been called + When we run "jrnl -1" + Then the output should contain "This is a journal entry" Scenario: Postconfig commands with overrides @@ -61,7 +64,7 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys And we use the password "test" if prompted When we run "jrnl --config-override journals.default features/journals/simple.journal 20 Mar 2000: The rain in Spain comes from clouds" Then we should get no error - And we should see the message "Entry added" + And the output should contain "Entry added" When we run "jrnl -3 --config-override journals.default features/journals/simple.journal" Then the output should be 2000-03-20 09:00 The rain in Spain comes from clouds @@ -78,7 +81,7 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys And we use the password "test" if prompted When we run "jrnl --config-override journals.temp features/journals/simple.journal temp Sep 06 1969: @say Ni" Then we should get no error - And we should see the message "Entry added" + And the output should contain "Entry added" When we run "jrnl --config-override journals.temp features/journals/simple.journal temp -3" Then the output should be 1969-09-06 09:00 @say Ni diff --git a/tests/bdd/features/password.feature b/tests/bdd/features/password.feature index ed8aea5f..23a08aab 100644 --- a/tests/bdd/features/password.feature +++ b/tests/bdd/features/password.feature @@ -55,7 +55,7 @@ Feature: Using the installed keyring 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" + Then the output should contain "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 contain "encrypt: true" @@ -69,7 +69,7 @@ Feature: Using the installed keyring Then the error output should contain "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 output should contain "Journal decrypted" And the config for journal "default" should contain "encrypt: false" When we run "jrnl --short" Then we should not be prompted for a password @@ -96,7 +96,7 @@ Feature: Using the installed keyring swordfish sordfish Then we should be prompted for a password - And we should see the message "Passwords did not match" + And the output should contain "Passwords did not match" And the config for journal "default" should not contain "encrypt: true" When we run "jrnl --short" Then the output should be @@ -113,8 +113,8 @@ Feature: Using the installed keyring 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 output should contain "Passwords did not match" + And the output should contain "Journal encrypted" And the config for journal "default" should contain "encrypt: true" When we run "jrnl -1" and enter "swordfish" Then we should be prompted for a password diff --git a/tests/bdd/features/search.feature b/tests/bdd/features/search.feature index 8d951aaf..d52273f6 100644 --- a/tests/bdd/features/search.feature +++ b/tests/bdd/features/search.feature @@ -3,7 +3,7 @@ Feature: Searching in a journal Scenario Outline: Displaying entries using -on today should display entries created today Given we use the config "" When we run "jrnl today: Adding an entry right now." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -on today" Then the output should contain "Adding an entry right now." But the output should not contain "Everything is alright" @@ -18,11 +18,11 @@ Feature: Searching in a journal Scenario Outline: Displaying entries using -from day should display correct entries Given we use the config "" When we run "jrnl yesterday: This thing happened yesterday" - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl today at 11:59pm: Adding an entry right now." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl tomorrow: A future entry." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -from today" Then the output should contain "Adding an entry right now." And the output should contain "A future entry." @@ -37,11 +37,11 @@ Feature: Searching in a journal Scenario Outline: Displaying entries using -from and -to day should display correct entries Given we use the config "" When we run "jrnl yesterday: This thing happened yesterday" - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl today at 11:59pm: Adding an entry right now." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl tomorrow: A future entry." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -from yesterday -to today" Then the output should contain "This thing happened yesterday" And the output should contain "Adding an entry right now." @@ -118,9 +118,9 @@ Feature: Searching in a journal Scenario: Out of order entries to a Folder journal should be listed in date order Given we use the config "empty_folder.yaml" When we run "jrnl 3/7/2014 4:37pm: Second entry of journal." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl 23 July 2013: Testing folder journal." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -2" Then the output should be 2013-07-23 09:00 Testing folder journal. diff --git a/tests/bdd/features/star.feature b/tests/bdd/features/star.feature index 7b1b42f1..f1340f7b 100644 --- a/tests/bdd/features/star.feature +++ b/tests/bdd/features/star.feature @@ -3,7 +3,7 @@ Feature: Starring entries Scenario Outline: Starring an entry will mark it in the journal file Given we use the config "" When we run "jrnl 20 july 2013 *: Best day of my life!" - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -on 2013-07-20 -starred" Then the output should contain "2013-07-20 09:00 Best day of my life!" @@ -30,6 +30,6 @@ Feature: Starring entries Scenario: Starring an entry will mark it in an encrypted journal Given we use the config "encrypted.yaml" When we run "jrnl 20 july 2013 *: Best day of my life!" and enter "bad doggie no biscuit" - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -on 2013-07-20 -starred" and enter "bad doggie no biscuit" Then the output should contain "2013-07-20 09:00 Best day of my life!" diff --git a/tests/bdd/features/write.feature b/tests/bdd/features/write.feature index 29af9b9e..a2c2a85b 100644 --- a/tests/bdd/features/write.feature +++ b/tests/bdd/features/write.feature @@ -46,7 +46,7 @@ Feature: Writing new entries. Given we use the config "" And we use the password "bad doggie no biscuit" if prompted When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -n 1" Then the output should contain "2013-07-23 09:00 A cold and stormy day." @@ -61,7 +61,7 @@ Feature: Writing new entries. Given we use the config "" And we use the password "test" if prompted When we run "jrnl this is a partial --edit" - Then we should see the message "Entry added" + Then the output should contain "Entry added" Then the editor should have been called And the editor file content should be this is a partial @@ -110,7 +110,7 @@ Feature: Writing new entries. Given we use the config "" And we use the password "bad doggie no biscuit" if prompted When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -n 1" Then the output should not contain "Life is good" @@ -125,7 +125,7 @@ Feature: Writing new entries. Given we use the config "" And we use the password "bad doggie no biscuit" if prompted When we run "jrnl 04-24-2014: Created a new website - empty.com. Hope to get a lot of traffic." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -1" Then the output should be 2014-04-24 09:00 Created a new website - empty.com. @@ -142,7 +142,7 @@ Feature: Writing new entries. Given we use the config "" And we use the password "bad doggie no biscuit" if prompted When we run "jrnl 23 july 2013: 🌞 sunny day. Saw an 🐘" - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -n 1" Then the output should contain "🌞" And the output should contain "🐘" @@ -199,7 +199,7 @@ Feature: Writing new entries. Scenario: Title with an embedded period on DayOne journal Given we use the config "dayone.yaml" When we run "jrnl 04-24-2014: Ran 6.2 miles today in 1:02:03. I am feeling sore because I forgot to stretch." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -1" Then the output should be 2014-04-24 09:00 Ran 6.2 miles today in 1:02:03. @@ -208,7 +208,7 @@ Feature: Writing new entries. Scenario: Opening an folder that's not a DayOne folder should treat as folder journal Given we use the config "empty_folder.yaml" When we run "jrnl 23 july 2013: Testing folder journal." - Then we should see the message "Entry added" + Then the output should contain "Entry added" When we run "jrnl -1" Then the output should be "2013-07-23 09:00 Testing folder journal." diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py index 1b4c74dc..8d820db6 100644 --- a/tests/lib/fixtures.py +++ b/tests/lib/fixtures.py @@ -8,17 +8,19 @@ import tempfile from keyring import backend from keyring import errors -from keyring import set_keyring from pytest import fixture +from unittest.mock import patch +from .helpers import get_fixture import toml from jrnl.config import load_config +from jrnl.os_compat import split_args # --- Keyring --- # @fixture def keyring(): - set_keyring(NoKeyring()) + return NoKeyring() @fixture @@ -75,13 +77,90 @@ class FailedKeyring(backend.KeyringBackend): # ----- Misc ----- # @fixture -def cli_run(): - return {"status": 0, "stdout": None, "stderr": None} +def cli_run( + mock_factories, + mock_args, + mock_is_tty, + mock_config_path, + mock_editor, + mock_user_input, + mock_overrides, + mock_password, +): + # Check if we need more mocks + mock_factories.update(mock_args) + mock_factories.update(mock_is_tty) + mock_factories.update(mock_overrides) + mock_factories.update(mock_editor) + mock_factories.update(mock_config_path) + mock_factories.update(mock_user_input) + mock_factories.update(mock_password) + + return { + "status": 0, + "stdout": None, + "stderr": None, + "mocks": {}, + "mock_factories": mock_factories, + } @fixture -def mocks(): - return dict() +def mock_factories(): + return {} + + +@fixture +def mock_args(cache_dir, request): + def _mock_args(): + command = get_fixture(request, "command", "") + + if cache_dir["exists"]: + command = command.format(cache_dir=cache_dir["path"]) + + args = split_args(command) + + return patch("sys.argv", ["jrnl"] + args) + + return {"args": _mock_args} + + +@fixture +def mock_is_tty(is_tty): + return {"is_tty": lambda: patch("sys.stdin.isatty", return_value=is_tty)} + + +@fixture +def mock_overrides(config_in_memory): + from jrnl.override import apply_overrides + + def my_overrides(*args, **kwargs): + result = apply_overrides(*args, **kwargs) + config_in_memory["overrides"] = result + return result + + return { + "overrides": lambda: patch( + "jrnl.jrnl.apply_overrides", side_effect=my_overrides + ) + } + + +@fixture +def mock_config_path(request): + config_path = get_fixture(request, "config_path") + + if not config_path: + return {} + + return { + "config_path_install": lambda: patch( + "jrnl.install.get_config_path", return_value=config_path + ), + "config_path_config": lambda: patch( + "jrnl.config.get_config_path", return_value=config_path + ), + } @fixture @@ -94,12 +173,6 @@ def working_dir(request): return os.path.join(request.config.rootpath, "tests") -@fixture -def config_path(temp_dir): - os.chdir(temp_dir.name) - return temp_dir.name + "/jrnl.yaml" - - @fixture def toml_version(working_dir): pyproject = os.path.join(working_dir, "..", "pyproject.toml") @@ -108,8 +181,23 @@ def toml_version(working_dir): @fixture -def password(): - return "" +def mock_password(request): + def _mock_password(): + password = get_fixture(request, "password") + user_input = get_fixture(request, "user_input") + + if password: + password = password.splitlines() + + elif user_input: + password = user_input.splitlines() + + if not password: + password = Exception("Unexpected call for password") + + return patch("getpass.getpass", side_effect=password) + + return {"getpass": _mock_password} @fixture @@ -127,19 +215,36 @@ def str_value(): return "" -@fixture -def command(): - return "" - - @fixture def should_not(): return False @fixture -def user_input(): - return "" +def mock_user_input(request, is_tty): + def _generator(target): + def _mock_user_input(): + user_input = get_fixture(request, "user_input", None) + + if user_input is None: + user_input = Exception("Unexpected call for user input") + else: + user_input = user_input.splitlines() if is_tty else [user_input] + + return patch(target, side_effect=user_input) + + return _mock_user_input + + return { + "stdin": _generator("sys.stdin.read"), + "input": _generator("builtins.input"), + } + + +@fixture +def is_tty(input_method): + assert input_method in ["", "enter", "pipe"] + return input_method != "pipe" @fixture @@ -187,7 +292,7 @@ def editor_state(): @fixture -def editor(editor_state): +def mock_editor(editor_state): def _mock_editor(editor_command): tmpfile = editor_command[-1] @@ -203,4 +308,4 @@ def editor(editor_state): file_content = f.read() editor_state["tmpfile"]["content"] = file_content - return _mock_editor + return {"editor": lambda: patch("subprocess.call", side_effect=_mock_editor)} diff --git a/tests/lib/given_steps.py b/tests/lib/given_steps.py index f3e6b69c..ba619dba 100644 --- a/tests/lib/given_steps.py +++ b/tests/lib/given_steps.py @@ -11,7 +11,6 @@ from unittest.mock import MagicMock from unittest.mock import patch from xml.etree import ElementTree -from keyring import set_keyring from pytest_bdd import given from pytest_bdd.parsers import parse @@ -20,6 +19,7 @@ from jrnl.time import __get_pdt_calendar from .fixtures import FailedKeyring from .fixtures import TestKeyring +from .helpers import get_fixture @given(parse("we {editor_method} to the editor if opened\n{editor_input}")) @@ -36,9 +36,8 @@ def we_enter_editor(editor_method, editor_input, editor_state): editor_state["intent"] = {"method": file_method, "input": editor_input} -@given(parse('now is ""')) @given(parse('now is "{date_str}"')) -def now_is_str(date_str, mocks): +def now_is_str(date_str, mock_factories): class DatetimeMagicMock(MagicMock): # needed because jrnl does some reflection on datetime def __instancecheck__(self, subclass): @@ -63,8 +62,8 @@ def now_is_str(date_str, mocks): date_str_input, mocked_now() ) - mocks["datetime"] = patch("datetime.datetime", new=datetime_mock) - mocks["calendar_parse"] = patch( + mock_factories["datetime"] = lambda: patch("datetime.datetime", new=datetime_mock) + mock_factories["calendar_parse"] = lambda: patch( "jrnl.time.__get_pdt_calendar", return_value=calendar_mock ) @@ -73,17 +72,22 @@ def now_is_str(date_str, mocks): @given(parse("we have a {keyring_type} keyring"), target_fixture="keyring") def we_have_type_of_keyring(keyring_type): if keyring_type == "failed": - set_keyring(FailedKeyring()) + return FailedKeyring() else: - set_keyring(TestKeyring()) + return TestKeyring() @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): +@given(parse("we use no config"), target_fixture="config_path") +def we_use_the_config(request, temp_dir, working_dir): + config_file = get_fixture(request, "config_file") + # Move into temp dir as cwd os.chdir(temp_dir.name) + if not config_file: + return os.path.join(temp_dir.name, "non_existing_config.yaml") + # Copy the config file over config_source = os.path.join(working_dir, "data", "configs", config_file) config_dest = os.path.join(temp_dir.name, config_file) @@ -106,7 +110,6 @@ def we_use_the_config(config_file, temp_dir, working_dir): @given(parse('the config "{config_file}" exists'), target_fixture="config_path") -@given('the config "" exists', target_fixture="config_path") def config_exists(config_file, temp_dir, working_dir): config_source = os.path.join(working_dir, "data", "configs", config_file) config_dest = os.path.join(temp_dir.name, config_file) diff --git a/tests/lib/helpers.py b/tests/lib/helpers.py index 2e1f454a..ad68cde3 100644 --- a/tests/lib/helpers.py +++ b/tests/lib/helpers.py @@ -49,3 +49,24 @@ def get_nested_val(dictionary, path, *default): if default: return default[0] raise + + +# @see: https://stackoverflow.com/a/41599695/569146 +def spy_wrapper(wrapped_function): + from unittest import mock + + mock = mock.MagicMock() + + def wrapper(self, *args, **kwargs): + mock(*args, **kwargs) + return wrapped_function(self, *args, **kwargs) + + wrapper.mock = mock + return wrapper + + +def get_fixture(request, name, default=None): + result = default + if name in request.node.fixturenames: + result = request.getfixturevalue(name) + return result diff --git a/tests/lib/then_steps.py b/tests/lib/then_steps.py index b4011938..0464158f 100644 --- a/tests/lib/then_steps.py +++ b/tests/lib/then_steps.py @@ -30,38 +30,47 @@ def output_should_match(regex, cli_run): assert matches, f"\nRegex didn't match:\n{regex}\n{str(out)}\n{str(matches)}" -@then(parse("the output should contain\n{expected_output}")) -@then(parse('the output should contain "{expected_output}"')) -@then('the output should contain ""') -@then(parse("the {which_output_stream} output should contain\n{expected_output}")) -@then(parse('the {which_output_stream} output should contain "{expected_output}"')) -def output_should_contain(expected_output, which_output_stream, cli_run): +@then(parse("the output {should_or_should_not} contain\n{expected_output}")) +@then(parse('the output {should_or_should_not} contain "{expected_output}"')) +@then( + parse( + "the {which_output_stream} output {should_or_should_not} contain\n{expected_output}" + ) +) +@then( + parse( + 'the {which_output_stream} output {should_or_should_not} contain "{expected_output}"' + ) +) +def output_should_contain( + expected_output, which_output_stream, cli_run, should_or_should_not +): + we_should = parse_should_or_should_not(should_or_should_not) + assert expected_output if which_output_stream is None: - assert (expected_output in cli_run["stdout"]) or ( - expected_output in cli_run["stderr"] + assert ((expected_output in cli_run["stdout"]) == we_should) or ( + (expected_output in cli_run["stderr"]) == we_should ) elif which_output_stream == "standard": - assert expected_output in cli_run["stdout"] + assert (expected_output in cli_run["stdout"]) == we_should elif which_output_stream == "error": - assert expected_output in cli_run["stderr"] + assert (expected_output in cli_run["stderr"]) == we_should else: - assert expected_output in cli_run[which_output_stream] + assert (expected_output in cli_run[which_output_stream]) == we_should @then(parse("the output should not contain\n{expected_output}")) @then(parse('the output should not contain "{expected_output}"')) -@then('the output should not contain ""') def output_should_not_contain(expected_output, cli_run): assert expected_output not in cli_run["stdout"] @then(parse("the output should be\n{expected_output}")) @then(parse('the output should be "{expected_output}"')) -@then('the output should be ""') def output_should_be(expected_output, cli_run): actual = cli_run["stdout"].strip() expected = expected_output.strip() @@ -75,7 +84,6 @@ def output_should_be_empty(cli_run): @then(parse('the output should contain the date "{date}"')) -@then('the output should contain the date ""') def output_should_contain_date(date, cli_run): assert date and date in cli_run["stdout"] @@ -94,12 +102,6 @@ def output_should_be_columns_wide(cli_run, width): assert len(line) <= width -@then(parse('we should see the message "{text}"')) -def should_see_the_message(text, cli_run): - out = cli_run["stderr"] - assert text in out, [text, out] - - @then( parse( 'the config for journal "{journal_name}" {should_or_should_not} contain "{some_yaml}"' @@ -126,10 +128,7 @@ def config_var_on_disk(config_on_disk, journal_name, should_or_should_not, some_ # `expected` objects formatted in yaml only compare one level deep actual_slice = {key: actual.get(key, None) for key in expected.keys()} - if we_should: - assert expected == actual_slice - else: - assert expected != actual_slice + assert (expected == actual_slice) == we_should @then( @@ -160,10 +159,7 @@ def config_var_in_memory( # `expected` objects formatted in yaml only compare one level deep actual_slice = {key: get_nested_val(actual, key) for key in expected.keys()} - if we_should: - assert expected == actual_slice - else: - assert expected != actual_slice + assert (expected == actual_slice) == we_should @then("we should be prompted for a password") @@ -355,10 +351,7 @@ def count_elements(number, item, cli_run): def count_editor_args(num_args, cli_run, editor_state, should_or_should_not): we_should = parse_should_or_should_not(should_or_should_not) - if we_should: - assert cli_run["mocks"]["editor"].called - else: - assert not cli_run["mocks"]["editor"].called + assert cli_run["mocks"]["editor"].called == we_should if isinstance(num_args, int): assert len(editor_state["command"]) == int(num_args) @@ -368,10 +361,7 @@ def count_editor_args(num_args, cli_run, editor_state, should_or_should_not): def stdin_prompt_called(cli_run, should_or_should_not): we_should = parse_should_or_should_not(should_or_should_not) - if we_should: - assert cli_run["mocks"]["stdin"].called - else: - assert not cli_run["mocks"]["stdin"].called + assert cli_run["mocks"]["stdin"].called == we_should @then(parse('the editor filename should end with "{suffix}"')) diff --git a/tests/lib/when_steps.py b/tests/lib/when_steps.py index 80d8a7fb..c1d391a5 100644 --- a/tests/lib/when_steps.py +++ b/tests/lib/when_steps.py @@ -3,14 +3,12 @@ from contextlib import ExitStack import os -from unittest.mock import patch -from pytest_bdd import parsers from pytest_bdd import when from pytest_bdd.parsers import parse +from pytest_bdd.parsers import re from jrnl.cli import cli -from jrnl.os_compat import split_args @when(parse('we change directory to "{directory_name}"')) @@ -21,103 +19,38 @@ def when_we_change_directory(directory_name): os.chdir(directory_name) +# These variables are used in the `@when(re(...))` section below +command = '(?P[^"]+)' +input_method = "(?Penter|pipe)" +user_input = '(?P[^"]+)' + + @when(parse('we run "jrnl {command}" and {input_method}\n{user_input}')) -@when( - parsers.re( - 'we run "jrnl (?P[^"]+)" and (?Penter|pipe) "(?P[^"]+)"' - ) -) -@when(parse('we run "jrnl" and {input_method} "{user_input}"')) +@when(re(f'we run "jrnl {command}" and {input_method} "{user_input}"')) +@when(re(f'we run "jrnl" and {input_method} "{user_input}"')) @when(parse('we run "jrnl {command}"')) -@when('we run "jrnl "') @when('we run "jrnl"') -def we_run( - command, - config_path, - config_in_memory, - user_input, - cli_run, - capsys, - password, - cache_dir, - editor, - keyring, - input_method, - mocks, -): - assert input_method in ["", "enter", "pipe"] - is_tty = input_method != "pipe" +def we_run_jrnl(cli_run, capsys, keyring): + from keyring import set_keyring - if cache_dir["exists"]: - command = command.format(cache_dir=cache_dir["path"]) - - args = split_args(command) - status = 0 - - if user_input: - user_input = user_input.splitlines() if is_tty else [user_input] - - if password: - password = password.splitlines() - - if not password and user_input: - password = user_input + set_keyring(keyring) with ExitStack() as stack: - # Always mock - from jrnl.override import apply_overrides + mocks = cli_run["mocks"] + factories = cli_run["mock_factories"] - def my_overrides(*args, **kwargs): - result = apply_overrides(*args, **kwargs) - config_in_memory["overrides"] = result - return result - - stack.enter_context( - patch("jrnl.jrnl.apply_overrides", side_effect=my_overrides) - ) - - # Conditionally mock - stack.enter_context(patch("sys.argv", ["jrnl"] + args)) - - mock_stdin = stack.enter_context( - patch("sys.stdin.read", side_effect=user_input) - ) - stack.enter_context(patch("sys.stdin.isatty", return_value=is_tty)) - mock_input = stack.enter_context( - patch("builtins.input", side_effect=user_input) - ) - mock_getpass = stack.enter_context( - patch("getpass.getpass", side_effect=password) - ) - - if "datetime" in mocks: - stack.enter_context(mocks["datetime"]) - stack.enter_context(mocks["calendar_parse"]) - - stack.enter_context( - patch("jrnl.install.get_config_path", return_value=config_path) - ) - stack.enter_context( - patch("jrnl.config.get_config_path", return_value=config_path) - ) - mock_editor = stack.enter_context(patch("subprocess.call", side_effect=editor)) + for id in factories: + mocks[id] = stack.enter_context(factories[id]()) try: - cli(args) + cli() except StopIteration: # This happens when input is expected, but don't have any input left pass except SystemExit as e: - status = e.code + cli_run["status"] = e.code captured = capsys.readouterr() - cli_run["status"] = status cli_run["stdout"] = captured.out cli_run["stderr"] = captured.err - cli_run["mocks"] = { - "stdin": mock_stdin, - "input": mock_input, - "getpass": mock_getpass, - "editor": mock_editor, - }