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()
|
original_config = config.copy()
|
||||||
|
|
||||||
# Apply config overrides
|
# Apply config overrides
|
||||||
overrides = args.config_override
|
config = apply_overrides(args, config)
|
||||||
if overrides:
|
|
||||||
config = apply_overrides(overrides, config)
|
|
||||||
|
|
||||||
args = get_journal_name(args, config)
|
args = get_journal_name(args, config)
|
||||||
config = scope_config(config, args.journal_name)
|
config = scope_config(config, args.journal_name)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from .config import update_config, make_yaml_valid_dict
|
from .config import update_config, make_yaml_valid_dict
|
||||||
|
from argparse import Namespace
|
||||||
|
|
||||||
# import logging
|
# 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.
|
"""Unpack CLI provided overrides into the configuration tree.
|
||||||
|
|
||||||
:param overrides: List of configuration key-value pairs collected from the CLI
|
: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
|
:return: Configuration to be used during runtime with the overrides applied
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
|
overrides = vars(args).get("config_override", None)
|
||||||
|
if not overrides:
|
||||||
|
return base_config
|
||||||
|
|
||||||
cfg_with_overrides = base_config.copy()
|
cfg_with_overrides = base_config.copy()
|
||||||
for pairs in overrides:
|
for pairs in overrides:
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,11 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys
|
||||||
And the editor should not have been called
|
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
|
Scenario: Postconfig commands with overrides
|
||||||
Given we use the config "basic_encrypted.yaml"
|
Given we use the config "basic_encrypted.yaml"
|
||||||
And we use the password "test" if prompted
|
And we use the password "test" if prompted
|
||||||
When we run "jrnl --decrypt --config-override highlight false --config-override editor nano"
|
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
|
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
|
Scenario: Override color selections with runtime overrides
|
||||||
Given we use the config "basic_encrypted.yaml"
|
Given we use the config "basic_encrypted.yaml"
|
||||||
And we use the password "test" if prompted
|
And we use the password "test" if prompted
|
||||||
When we run "jrnl -1 --config-override colors.body blue"
|
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
|
Scenario: Apply multiple config overrides
|
||||||
Given we use the config "basic_encrypted.yaml"
|
Given we use the config "basic_encrypted.yaml"
|
||||||
And we use the password "test" if prompted
|
And we use the password "test" if prompted
|
||||||
When we run "jrnl -1 --config-override colors.body green --config-override editor 'nano'"
|
When we run "jrnl -1 --config-override colors.body green --config-override editor 'nano'"
|
||||||
Then the config should have "colors.body" set to "green"
|
Then the config in memory should contain
|
||||||
And the config should have "editor" set to "nano"
|
editor: nano
|
||||||
|
colors:
|
||||||
|
title: none
|
||||||
|
body: green
|
||||||
|
tags: none
|
||||||
|
date: none
|
||||||
|
|
||||||
|
|
||||||
Scenario: Override default journal
|
Scenario: Override default journal
|
||||||
|
|
|
@ -143,10 +143,15 @@ def user_input():
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
@fixture
|
||||||
def config_data(config_path):
|
def config_on_disk(config_path):
|
||||||
return load_config(config_path)
|
return load_config(config_path)
|
||||||
|
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def config_in_memory():
|
||||||
|
return dict()
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
@fixture
|
||||||
def journal_name():
|
def journal_name():
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Copyright (C) 2012-2021 jrnl contributors
|
# Copyright (C) 2012-2021 jrnl contributors
|
||||||
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
# License: https://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
|
import functools
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,3 +39,13 @@ def assert_equal_tags_ignoring_order(
|
||||||
[actual_tags, expected_tags],
|
[actual_tags, expected_tags],
|
||||||
[expected_content, actual_content],
|
[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 assert_equal_tags_ignoring_order
|
||||||
from .helpers import does_directory_contain_files
|
from .helpers import does_directory_contain_files
|
||||||
from .helpers import parse_should_or_should_not
|
from .helpers import parse_should_or_should_not
|
||||||
|
from .helpers import get_nested_val
|
||||||
|
|
||||||
|
|
||||||
@then("we should get no error")
|
@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 "{some_yaml}"'))
|
||||||
@then(parse("the config {should_or_should_not} contain\n{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)
|
we_should = parse_should_or_should_not(should_or_should_not)
|
||||||
|
|
||||||
actual = config_data
|
actual = config_on_disk
|
||||||
if journal_name:
|
if journal_name:
|
||||||
actual = actual["journals"][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
|
actual_slice = actual
|
||||||
if type(actual) is dict:
|
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()}
|
actual_slice = {key: actual.get(key, None) for key in expected.keys()}
|
||||||
|
|
||||||
if we_should:
|
if we_should:
|
||||||
|
@ -129,6 +131,40 @@ def config_var(config_data, journal_name, should_or_should_not, some_yaml):
|
||||||
assert expected != actual_slice
|
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")
|
@then("we should be prompted for a password")
|
||||||
def password_was_called(cli_run):
|
def password_was_called(cli_run):
|
||||||
assert cli_run["mocks"]["getpass"].called
|
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}"))
|
@then(parse("the journal directory should contain\n{file_list}"))
|
||||||
def journal_directory_should_contain(config_data, file_list):
|
def journal_directory_should_contain(config_on_disk, file_list):
|
||||||
scoped_config = scope_config(config_data, "default")
|
scoped_config = scope_config(config_on_disk, "default")
|
||||||
|
|
||||||
assert does_directory_contain_files(file_list, scoped_config["journal"])
|
assert does_directory_contain_files(file_list, scoped_config["journal"])
|
||||||
|
|
||||||
|
|
||||||
@then(parse('journal "{journal_name}" should not exist'))
|
@then(parse('journal "{journal_name}" should not exist'))
|
||||||
def journal_directory_should_not_exist(config_data, journal_name):
|
def journal_directory_should_not_exist(config_on_disk, journal_name):
|
||||||
scoped_config = scope_config(config_data, journal_name)
|
scoped_config = scope_config(config_on_disk, journal_name)
|
||||||
|
|
||||||
assert not does_directory_contain_files(
|
assert not does_directory_contain_files(
|
||||||
scoped_config["journal"], "."
|
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"))
|
@then(parse("the journal {should_or_should_not} exist"))
|
||||||
def journal_should_not_exist(config_data, should_or_should_not):
|
def journal_should_not_exist(config_on_disk, should_or_should_not):
|
||||||
scoped_config = scope_config(config_data, "default")
|
scoped_config = scope_config(config_on_disk, "default")
|
||||||
expected_path = scoped_config["journal"]
|
expected_path = scoped_config["journal"]
|
||||||
|
|
||||||
contains_files = does_directory_contain_files(expected_path, ".")
|
contains_files = does_directory_contain_files(expected_path, ".")
|
||||||
|
|
|
@ -34,6 +34,7 @@ def when_we_change_directory(directory_name):
|
||||||
def we_run(
|
def we_run(
|
||||||
command,
|
command,
|
||||||
config_path,
|
config_path,
|
||||||
|
config_in_memory,
|
||||||
user_input,
|
user_input,
|
||||||
cli_run,
|
cli_run,
|
||||||
capsys,
|
capsys,
|
||||||
|
@ -63,7 +64,19 @@ def we_run(
|
||||||
password = user_input
|
password = user_input
|
||||||
|
|
||||||
with ExitStack() as stack:
|
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))
|
stack.enter_context(patch("sys.argv", ["jrnl"] + args))
|
||||||
|
|
||||||
mock_stdin = stack.enter_context(
|
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 _recursively_apply
|
||||||
from jrnl.override import apply_overrides
|
from jrnl.override import apply_overrides
|
||||||
|
|
||||||
|
from argparse import Namespace
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def minimal_config():
|
def minimal_config():
|
||||||
|
@ -18,31 +20,61 @@ def minimal_config():
|
||||||
return cfg
|
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):
|
def test_apply_override(minimal_config):
|
||||||
overrides = [["editor", "nano"]]
|
overrides = {"config_override": [["editor", "nano"]]}
|
||||||
apply_overrides(overrides, minimal_config)
|
apply_overrides(expected_args(overrides), minimal_config)
|
||||||
assert minimal_config["editor"] == "nano"
|
assert minimal_config["editor"] == "nano"
|
||||||
|
|
||||||
|
|
||||||
def test_override_dot_notation(minimal_config):
|
def test_override_dot_notation(minimal_config):
|
||||||
overrides = [["colors.body", "blue"]]
|
overrides = {"config_override": [["colors.body", "blue"]]}
|
||||||
|
apply_overrides(expected_args(overrides), minimal_config)
|
||||||
cfg = apply_overrides(overrides=overrides, base_config=minimal_config)
|
assert minimal_config["colors"] == {"body": "blue", "date": "green"}
|
||||||
assert cfg["colors"] == {"body": "blue", "date": "green"}
|
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_overrides(minimal_config):
|
def test_multiple_overrides(minimal_config):
|
||||||
overrides = [
|
overrides = {
|
||||||
|
"config_override": [
|
||||||
["colors.title", "magenta"],
|
["colors.title", "magenta"],
|
||||||
["editor", "nano"],
|
["editor", "nano"],
|
||||||
["journals.burner", "/tmp/journals/burner.jrnl"],
|
["journals.burner", "/tmp/journals/burner.jrnl"],
|
||||||
] # as returned by parse_args, saved in parser.config_override
|
]
|
||||||
|
}
|
||||||
|
|
||||||
cfg = apply_overrides(overrides, minimal_config)
|
actual = apply_overrides(expected_args(overrides), minimal_config)
|
||||||
assert cfg["editor"] == "nano"
|
assert actual["editor"] == "nano"
|
||||||
assert cfg["colors"]["title"] == "magenta"
|
assert actual["colors"]["title"] == "magenta"
|
||||||
assert "burner" in cfg["journals"]
|
assert "burner" in actual["journals"]
|
||||||
assert cfg["journals"]["burner"] == "/tmp/journals/burner.jrnl"
|
assert actual["journals"]["burner"] == "/tmp/journals/burner.jrnl"
|
||||||
|
|
||||||
|
|
||||||
def test_recursively_apply():
|
def test_recursively_apply():
|
||||||
|
|
Loading…
Add table
Reference in a new issue