mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-18 03:58:32 +02:00
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:
parent
e16e3a1f62
commit
9cd2250a61
8 changed files with 138 additions and 39 deletions
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ".")
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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():
|
||||
|
|
Loading…
Add table
Reference in a new issue