Add config overrides steps to pytest

This requires some patching around the config object, which now happens
in every test.

Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>
This commit is contained in:
Jonathan Wren 2021-10-02 16:52:47 -07:00
parent e16e3a1f62
commit 9cd2250a61
8 changed files with 138 additions and 39 deletions

View file

@ -40,9 +40,7 @@ def run(args):
original_config = config.copy()
# Apply config overrides
overrides = args.config_override
if overrides:
config = apply_overrides(overrides, config)
config = apply_overrides(args, config)
args = get_journal_name(args, config)
config = scope_config(config, args.journal_name)

View file

@ -1,7 +1,8 @@
from .config import update_config, make_yaml_valid_dict
from argparse import Namespace
# import logging
def apply_overrides(overrides: list, base_config: dict) -> dict:
def apply_overrides(args: Namespace, base_config: dict) -> dict:
"""Unpack CLI provided overrides into the configuration tree.
:param overrides: List of configuration key-value pairs collected from the CLI
@ -11,6 +12,10 @@ def apply_overrides(overrides: list, base_config: dict) -> dict:
:return: Configuration to be used during runtime with the overrides applied
:rtype: dict
"""
overrides = vars(args).get("config_override", None)
if not overrides:
return base_config
cfg_with_overrides = base_config.copy()
for pairs in overrides:

View file

@ -8,13 +8,11 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
And the editor should not have been called
# @todo implement this step in pytest (doesn't currently support overrides)
@skip
Scenario: Postconfig commands with overrides
Given we use the config "basic_encrypted.yaml"
And we use the password "test" if prompted
When we run "jrnl --decrypt --config-override highlight false --config-override editor nano"
Then the config should contain "highlight: false"
Then the config in memory should contain "highlight: false"
Then the editor should not have been called
@ -38,23 +36,24 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
# @todo implement this step in pytest (doesn't currently support overrides)
@skip
Scenario: Override color selections with runtime overrides
Given we use the config "basic_encrypted.yaml"
And we use the password "test" if prompted
When we run "jrnl -1 --config-override colors.body blue"
Then the config should have "colors.body" set to "blue"
Then the config in memory should contain "colors.body: blue"
# @todo implement this step in pytest (doesn't currently support overrides)
@skip
Scenario: Apply multiple config overrides
Given we use the config "basic_encrypted.yaml"
And we use the password "test" if prompted
When we run "jrnl -1 --config-override colors.body green --config-override editor 'nano'"
Then the config should have "colors.body" set to "green"
And the config should have "editor" set to "nano"
Then the config in memory should contain
editor: nano
colors:
title: none
body: green
tags: none
date: none
Scenario: Override default journal

View file

@ -143,10 +143,15 @@ def user_input():
@fixture
def config_data(config_path):
def config_on_disk(config_path):
return load_config(config_path)
@fixture
def config_in_memory():
return dict()
@fixture
def journal_name():
return None

View file

@ -1,6 +1,7 @@
# Copyright (C) 2012-2021 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html
import functools
import os
@ -38,3 +39,13 @@ def assert_equal_tags_ignoring_order(
[actual_tags, expected_tags],
[expected_content, actual_content],
]
# @see: https://stackoverflow.com/a/65782539/569146
def get_nested_val(dictionary, path, *default):
try:
return functools.reduce(lambda x, y: x[y], path.split("."), dictionary)
except KeyError:
if default:
return default[0]
raise

View file

@ -15,6 +15,7 @@ from jrnl.config import scope_config
from .helpers import assert_equal_tags_ignoring_order
from .helpers import does_directory_contain_files
from .helpers import parse_should_or_should_not
from .helpers import get_nested_val
@then("we should get no error")
@ -110,10 +111,10 @@ def should_see_the_message(text, cli_run):
)
@then(parse('the config {should_or_should_not} contain "{some_yaml}"'))
@then(parse("the config {should_or_should_not} contain\n{some_yaml}"))
def config_var(config_data, journal_name, should_or_should_not, some_yaml):
def config_var_on_disk(config_on_disk, journal_name, should_or_should_not, some_yaml):
we_should = parse_should_or_should_not(should_or_should_not)
actual = config_data
actual = config_on_disk
if journal_name:
actual = actual["journals"][journal_name]
@ -121,6 +122,7 @@ def config_var(config_data, journal_name, should_or_should_not, some_yaml):
actual_slice = actual
if type(actual) is dict:
# `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:
@ -129,6 +131,40 @@ def config_var(config_data, journal_name, should_or_should_not, some_yaml):
assert expected != actual_slice
@then(
parse(
'the config in memory for journal "{journal_name}" {should_or_should_not} contain "{some_yaml}"'
)
)
@then(
parse(
'the config in memory for journal "{journal_name}" {should_or_should_not} contain\n{some_yaml}'
)
)
@then(parse('the config in memory {should_or_should_not} contain "{some_yaml}"'))
@then(parse("the config in memory {should_or_should_not} contain\n{some_yaml}"))
def config_var_in_memory(
config_in_memory, journal_name, should_or_should_not, some_yaml
):
we_should = parse_should_or_should_not(should_or_should_not)
actual = config_in_memory["overrides"]
if journal_name:
actual = actual["journals"][journal_name]
expected = yaml.load(some_yaml, Loader=yaml.SafeLoader)
actual_slice = actual
if type(actual) is dict:
# `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
@then("we should be prompted for a password")
def password_was_called(cli_run):
assert cli_run["mocks"]["getpass"].called
@ -145,15 +181,15 @@ def assert_dir_contains_files(file_list, cache_dir):
@then(parse("the journal directory should contain\n{file_list}"))
def journal_directory_should_contain(config_data, file_list):
scoped_config = scope_config(config_data, "default")
def journal_directory_should_contain(config_on_disk, file_list):
scoped_config = scope_config(config_on_disk, "default")
assert does_directory_contain_files(file_list, scoped_config["journal"])
@then(parse('journal "{journal_name}" should not exist'))
def journal_directory_should_not_exist(config_data, journal_name):
scoped_config = scope_config(config_data, journal_name)
def journal_directory_should_not_exist(config_on_disk, journal_name):
scoped_config = scope_config(config_on_disk, journal_name)
assert not does_directory_contain_files(
scoped_config["journal"], "."
@ -161,8 +197,8 @@ def journal_directory_should_not_exist(config_data, journal_name):
@then(parse("the journal {should_or_should_not} exist"))
def journal_should_not_exist(config_data, should_or_should_not):
scoped_config = scope_config(config_data, "default")
def journal_should_not_exist(config_on_disk, should_or_should_not):
scoped_config = scope_config(config_on_disk, "default")
expected_path = scoped_config["journal"]
contains_files = does_directory_contain_files(expected_path, ".")

View file

@ -34,6 +34,7 @@ def when_we_change_directory(directory_name):
def we_run(
command,
config_path,
config_in_memory,
user_input,
cli_run,
capsys,
@ -63,7 +64,19 @@ def we_run(
password = user_input
with ExitStack() as stack:
# Always mock
from jrnl.override import apply_overrides
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(

View file

@ -6,6 +6,8 @@ from jrnl.override import _get_key_and_value_from_pair
from jrnl.override import _recursively_apply
from jrnl.override import apply_overrides
from argparse import Namespace
@pytest.fixture()
def minimal_config():
@ -18,31 +20,61 @@ def minimal_config():
return cfg
def expected_args(overrides):
default_args = {
"contains": None,
"debug": False,
"delete": False,
"edit": False,
"end_date": None,
"today_in_history": False,
"month": None,
"day": None,
"year": None,
"excluded": [],
"export": False,
"filename": None,
"limit": None,
"on_date": None,
"preconfig_cmd": None,
"postconfig_cmd": None,
"short": False,
"starred": False,
"start_date": None,
"strict": False,
"tags": False,
"text": [],
"config_override": [],
}
return Namespace(**{**default_args, **overrides})
def test_apply_override(minimal_config):
overrides = [["editor", "nano"]]
apply_overrides(overrides, minimal_config)
overrides = {"config_override": [["editor", "nano"]]}
apply_overrides(expected_args(overrides), minimal_config)
assert minimal_config["editor"] == "nano"
def test_override_dot_notation(minimal_config):
overrides = [["colors.body", "blue"]]
cfg = apply_overrides(overrides=overrides, base_config=minimal_config)
assert cfg["colors"] == {"body": "blue", "date": "green"}
overrides = {"config_override": [["colors.body", "blue"]]}
apply_overrides(expected_args(overrides), minimal_config)
assert minimal_config["colors"] == {"body": "blue", "date": "green"}
def test_multiple_overrides(minimal_config):
overrides = [
["colors.title", "magenta"],
["editor", "nano"],
["journals.burner", "/tmp/journals/burner.jrnl"],
] # as returned by parse_args, saved in parser.config_override
overrides = {
"config_override": [
["colors.title", "magenta"],
["editor", "nano"],
["journals.burner", "/tmp/journals/burner.jrnl"],
]
}
cfg = apply_overrides(overrides, minimal_config)
assert cfg["editor"] == "nano"
assert cfg["colors"]["title"] == "magenta"
assert "burner" in cfg["journals"]
assert cfg["journals"]["burner"] == "/tmp/journals/burner.jrnl"
actual = apply_overrides(expected_args(overrides), minimal_config)
assert actual["editor"] == "nano"
assert actual["colors"]["title"] == "magenta"
assert "burner" in actual["journals"]
assert actual["journals"]["burner"] == "/tmp/journals/burner.jrnl"
def test_recursively_apply():