diff --git a/joft/utils.py b/joft/utils.py index d54d638..f47ee14 100644 --- a/joft/utils.py +++ b/joft/utils.py @@ -15,6 +15,19 @@ def load_and_parse_yaml_file(path: str) -> typing.Dict[str, typing.Any]: return yaml_obj +def read_and_validate_config( + path: str | pathlib.Path, +) -> typing.Dict[str, typing.Any] | None: + """Read and return config file is it's valid. Return None otherwise.""" + with open(path, "rb") as fp: + config = tomllib.load(fp) + + config["jira"]["server"]["hostname"] + config["jira"]["server"]["pat_token"] + + return config + + def load_toml_app_config() -> typing.Any: possible_paths = [] @@ -26,7 +39,27 @@ def load_toml_app_config() -> typing.Any: for path in possible_paths: config_file_path = pathlib.Path(path) / "joft.config.toml" if config_file_path.is_file(): - break + try: + config = read_and_validate_config(config_file_path) + except Exception as e: + err_msg = textwrap.dedent(f"""\ + [ERROR] Configuration file {config_file_path} is invalid: + + {type(e).__name__} - {str(e)} + + Configuration file should have the following content: + + [jira.server] + hostname = "" + pat_token = "" + + and should be stored in one of the following directories: + {', '.join(possible_paths)}\ + """) + print(err_msg) + sys.exit(1) + else: + return config else: err_msg = textwrap.dedent(f"""\ [ERROR] Cannot find configuration file 'joft.config.toml'. @@ -43,8 +76,3 @@ def load_toml_app_config() -> typing.Any: print(err_msg) sys.exit(1) - - with open(config_file_path, "rb") as fp: - config = tomllib.load(fp) - - return config diff --git a/tests/test_utils.py b/tests/test_utils.py index f5c361f..96ebb6e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -81,3 +81,53 @@ def test_load_toml_app_config_no_config_found(mock_platformdirs, mock_cwd) -> No assert "Cannot find configuration file" in mock_stdout.getvalue() assert sys_exit.value.args[0] == 1 + + +@unittest.mock.patch("joft.utils.pathlib.Path.cwd") +def test_load_toml_app_config_invalid_config_found(mock_cwd) -> None: + """ + Test that we will end with a non-zero error code when there is an invalid + config present and printing a message on the stdout. + """ + + invalid_config_file_contents = """[jira.server] + pat_token = "__pat_token__" + """ + + with tempfile.TemporaryDirectory() as tmpdir: + mock_cwd.return_value = tmpdir + + config_file_path = os.path.join(tmpdir, "joft.config.toml") + with open(config_file_path, "w") as fp: + fp.write(invalid_config_file_contents) + + with unittest.mock.patch("sys.stdout", new=io.StringIO()) as mock_stdout: + with pytest.raises(SystemExit) as sys_exit: + joft.utils.load_toml_app_config() + + assert f"Configuration file {config_file_path} is invalid" in mock_stdout.getvalue() + assert "KeyError - 'hostname'" in mock_stdout.getvalue() + assert sys_exit.value.args[0] == 1 + + +@pytest.mark.parametrize( + "config_file_content, raises", + [ + ("[jira.server]\nhostname = 'foo'\npat_token = 'bar'", None), + ("", KeyError), + ("[jira.server]\nhostname = 'foo'", KeyError), + ("[jira.server]\npat_token = 'bar'", KeyError), + ("hostname = 'foo'\npat_token = 'bar'", KeyError), + ], +) +def test_read_and_validate_config(config_file_content, raises, tmp_path) -> None: + config_file_path = tmp_path / "joft.config.toml" + config_file_path.write_text(config_file_content) + + if raises is None: + config = joft.utils.read_and_validate_config(config_file_path) + assert config["jira"]["server"]["hostname"] == "foo" + assert config["jira"]["server"]["pat_token"] == "bar" + else: + with pytest.raises(raises): + config = joft.utils.read_and_validate_config(config_file_path)