From 01e2cfdf6bb3095ab75eb97f1c88d76e01757488 Mon Sep 17 00:00:00 2001 From: pederhan Date: Thu, 12 Dec 2024 11:28:55 +0100 Subject: [PATCH] Test helper functions, replace use of deprecated logging function --- mreg_cli/config.py | 2 +- mreg_cli/outputmanager.py | 6 +-- mreg_cli/types.py | 13 +++++ tests/test_outputmanager.py | 101 ++++++++++++++++++++++++++++++++++++ tests/test_types.py | 15 ++++++ 5 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 tests/test_outputmanager.py create mode 100644 tests/test_types.py diff --git a/mreg_cli/config.py b/mreg_cli/config.py index c547901e..1572415e 100644 --- a/mreg_cli/config.py +++ b/mreg_cli/config.py @@ -181,7 +181,7 @@ def start_logging( try: logging.basicConfig( filename=logfile, - level=logging.getLevelName(level), + level=level.as_int(), format=LOGGING_FORMAT, datefmt="%Y-%m-%d %H:%M:%S", ) diff --git a/mreg_cli/outputmanager.py b/mreg_cli/outputmanager.py index b10873ef..e787bd77 100644 --- a/mreg_cli/outputmanager.py +++ b/mreg_cli/outputmanager.py @@ -21,7 +21,7 @@ from mreg_cli.errorbuilder import build_error_message from mreg_cli.exceptions import CliError, FileError -from mreg_cli.types import JsonMapping, RecordingEntry, TimeInfo +from mreg_cli.types import Json, JsonMapping, RecordingEntry, TimeInfo logger = logging.getLogger(__name__) @@ -77,8 +77,8 @@ def remove_comments(line: str) -> str: return find_char_outside_quotes(line, "#", False).rstrip(" ") -def remove_dict_key_recursive(obj: object, key: str) -> None: - """Remove a key from a dict, recursively. +def remove_dict_key_recursive(obj: Json, key: str) -> None: + """Remove a key from a dict or list of dicts, recursively. This is a destructive operation, and will modify the object in place. """ diff --git a/mreg_cli/types.py b/mreg_cli/types.py index bf000977..fc6f1467 100644 --- a/mreg_cli/types.py +++ b/mreg_cli/types.py @@ -4,6 +4,7 @@ import argparse import ipaddress +import logging from collections.abc import Callable from enum import StrEnum from functools import lru_cache @@ -161,6 +162,18 @@ def choices(cls) -> list[str]: """Return a list of all log levels as strings.""" return [str(c) for c in list(cls)] + def as_int(self) -> int: + """Convert the log level to an integer.""" + # logging.getLevelName considered a mistake - let's implement our own + _nameToLevel = { + self.CRITICAL: logging.CRITICAL, + self.ERROR: logging.ERROR, + self.WARNING: logging.WARNING, + self.INFO: logging.INFO, + self.DEBUG: logging.DEBUG, + } + return _nameToLevel[self] + T = TypeVar("T") diff --git a/tests/test_outputmanager.py b/tests/test_outputmanager.py new file mode 100644 index 00000000..71a38cad --- /dev/null +++ b/tests/test_outputmanager.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +from copy import deepcopy + +import pytest + +from mreg_cli.outputmanager import remove_dict_key_recursive +from mreg_cli.types import Json + + +@pytest.mark.parametrize( + "inp,key,expected", + [ + pytest.param( + {"a": 1, "b": 2}, + "a", + {"b": 2}, + id="simple_single_key_dict", + ), + pytest.param( + {"a": {"b": 2, "c": 3}, "d": 4}, + "b", + {"a": {"c": 3}, "d": 4}, + id="nested_dict_removal", + ), + pytest.param( + [{"a": 1}, {"a": 2}, {"b": 3}], + "a", + [{}, {}, {"b": 3}], + id="list_of_dicts", + ), + pytest.param( + {"a": [{"b": 1, "c": 2}, {"b": 3, "d": 4}], "e": {"b": 5, "f": {"b": 6}}}, + "b", + {"a": [{"c": 2}, {"d": 4}], "e": {"f": {}}}, + id="complex_nested_structure", + ), + pytest.param( + {"a": 1, "b": 2}, + "c", + {"a": 1, "b": 2}, + id="key_not_present", + ), + pytest.param( + {}, + "a", + {}, + id="empty_dict", + ), + pytest.param( + [], + "a", + [], + id="empty_list", + ), + pytest.param( + {"a": 1, "b": "string", "c": True, "d": None, "e": 1.5, "f": {"a": 2}}, + "a", + {"b": "string", "c": True, "d": None, "e": 1.5, "f": {}}, + id="mixed_types_with_nested_removal", + ), + pytest.param( + [[[{"a": 1}]], [[{"a": 2}]]], + "a", + [[[{}]], [[{}]]], + id="deeply_nested_lists", + ), + ], # type: ignore +) +def test_remove_dict_key_recursive(inp: Json, key: str, expected: Json) -> None: + """Test remove_dict_key_recursive with a variety of inputs.""" + # Call the function + remove_dict_key_recursive(inp, key) + + # Assert the result matches expected + assert inp == expected + + +def test_none_value_handling() -> None: + """Test that the function handles None values appropriately""" + obj: Json = None + remove_dict_key_recursive(obj, "any_key") # Should not raise any exception + assert obj is None + + +@pytest.mark.parametrize( + "inp", + [ + "string", + 123, + 1.5, + True, + False, + None, + ], +) +def test_primitive_value_handling(inp: Json) -> None: + """Test that the function handles primitive values appropriately.""" + original = deepcopy(inp) + remove_dict_key_recursive(inp, "any_key") # Should not raise any exception + assert inp == original # Should not modify primitive values diff --git a/tests/test_types.py b/tests/test_types.py new file mode 100644 index 00000000..c1ab1b41 --- /dev/null +++ b/tests/test_types.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from inline_snapshot import snapshot + +from mreg_cli.types import LogLevel + + +def test_loglevel_as_int() -> None: + """Test that all LogLevel members have an integer representation.""" + for level in list(LogLevel): + # If it doesn't implement as_int, this will raise a KeyError + assert level.as_int() is not None + + # Snapshot so we can see if any values are changed + assert [lvl.as_int() for lvl in list(LogLevel)] == snapshot([10, 20, 30, 40, 50])