test: refactor CLI tests and expand coverage

This commit is contained in:
2025-12-30 18:29:34 +01:00
parent e364598414
commit 313b6fc453

View File

@@ -6,91 +6,224 @@ import pytest
import skywipe.cli as cli
TEST_COMMAND = "posts"
TEST_ERROR_MESSAGE = "boom"
TEST_LOGGER_NAME = "test.cli"
def _setup_parser_mocks(monkeypatch, commands=None):
if commands is None:
commands = {TEST_COMMAND: "only posts"}
monkeypatch.setattr(cli.registry, "get_all_commands", lambda: commands)
def _setup_main_mocks(monkeypatch, calls, requires_config=False, config_data=None):
_setup_parser_mocks(monkeypatch)
monkeypatch.setattr(cli.registry, "requires_config",
lambda name: requires_config)
def mock_execute(name, skip_confirmation=False):
calls["execute"] = (name, skip_confirmation)
def mock_setup_logger(verbose, log_file):
calls["setup"].append((verbose, log_file))
def mock_require_config(logger):
calls["require_config"].append(logger)
monkeypatch.setattr(cli.registry, "execute", mock_execute)
monkeypatch.setattr(cli, "setup_logger", mock_setup_logger)
monkeypatch.setattr(cli, "require_config", mock_require_config)
monkeypatch.setattr(cli, "get_logger",
lambda: logging.getLogger(TEST_LOGGER_NAME))
if config_data is not None:
monkeypatch.setattr(cli.Configuration, "load",
lambda self: config_data)
def _setup_error_mocks(monkeypatch, calls, error_factory):
_setup_parser_mocks(monkeypatch)
monkeypatch.setattr(cli.registry, "requires_config", lambda name: False)
monkeypatch.setattr(cli.registry, "execute", error_factory)
monkeypatch.setattr(cli, "setup_logger", lambda verbose, log_file: None)
monkeypatch.setattr(cli, "get_logger",
lambda: logging.getLogger(TEST_LOGGER_NAME))
def mock_handle_error(error, logger, exit_on_error=False):
calls["handle_error"] = (type(error).__name__,
str(error), exit_on_error)
monkeypatch.setattr(cli, "handle_error", mock_handle_error)
def test_create_parser_includes_commands(monkeypatch):
monkeypatch.setattr(cli.registry, "get_all_commands",
lambda: {"posts": "only posts"})
_setup_parser_mocks(monkeypatch)
parser = cli.create_parser()
args = parser.parse_args(["posts"])
assert args.command == "posts"
args = parser.parse_args([TEST_COMMAND])
assert args.command == TEST_COMMAND
def test_create_parser_handles_multiple_commands(monkeypatch):
commands = {
"posts": "only posts",
"likes": "only likes",
"reposts": "only reposts"
}
_setup_parser_mocks(monkeypatch, commands)
parser = cli.create_parser()
args1 = parser.parse_args(["posts"])
args2 = parser.parse_args(["likes"])
args3 = parser.parse_args(["reposts"])
assert args1.command == "posts"
assert args2.command == "likes"
assert args3.command == "reposts"
def test_create_parser_parses_yes_flag(monkeypatch):
_setup_parser_mocks(monkeypatch)
parser = cli.create_parser()
args = parser.parse_args(["--yes", TEST_COMMAND])
assert args.command == TEST_COMMAND
assert args.yes is True
def test_create_parser_parses_without_yes_flag(monkeypatch):
_setup_parser_mocks(monkeypatch)
parser = cli.create_parser()
args = parser.parse_args([TEST_COMMAND])
assert args.command == TEST_COMMAND
assert getattr(args, "yes", False) is False
def test_create_parser_version_flag_exits(monkeypatch):
_setup_parser_mocks(monkeypatch)
parser = cli.create_parser()
with pytest.raises(SystemExit) as excinfo:
parser.parse_args(["--version"])
assert excinfo.value.code == 0
def test_create_parser_requires_command(monkeypatch):
_setup_parser_mocks(monkeypatch)
parser = cli.create_parser()
with pytest.raises(SystemExit):
parser.parse_args([])
def test_create_parser_rejects_invalid_command(monkeypatch):
_setup_parser_mocks(monkeypatch)
parser = cli.create_parser()
with pytest.raises(SystemExit):
parser.parse_args(["invalid_command"])
def test_require_config_exits_when_missing(monkeypatch):
monkeypatch.setattr(cli.Configuration, "exists", lambda self: False)
logger = logging.getLogger("test.cli")
logger = logging.getLogger(TEST_LOGGER_NAME)
with pytest.raises(SystemExit) as excinfo:
cli.require_config(logger)
assert excinfo.value.code == 1
def test_require_config_does_not_exit_when_exists(monkeypatch):
monkeypatch.setattr(cli.Configuration, "exists", lambda self: True)
logger = logging.getLogger(TEST_LOGGER_NAME)
cli.require_config(logger)
def test_main_executes_without_config(monkeypatch):
calls = {"execute": None, "setup": []}
calls = {"execute": None, "setup": [], "require_config": []}
_setup_main_mocks(monkeypatch, calls, requires_config=False)
monkeypatch.setattr(cli.registry, "get_all_commands",
lambda: {"posts": "only posts"})
monkeypatch.setattr(cli.registry, "requires_config", lambda name: False)
monkeypatch.setattr(cli.registry, "execute", lambda name, skip_confirmation=False: calls.update(
{"execute": (name, skip_confirmation)}
))
monkeypatch.setattr(cli, "setup_logger", lambda verbose,
log_file: calls["setup"].append((verbose, log_file)))
monkeypatch.setattr(cli, "get_logger",
lambda: logging.getLogger("test.cli"))
monkeypatch.setattr(sys, "argv", ["skywipe", "--yes", "posts"])
monkeypatch.setattr(sys, "argv", ["skywipe", "--yes", TEST_COMMAND])
cli.main()
assert len(calls["require_config"]) == 0
assert calls["setup"] == [(False, cli.LOG_FILE)]
assert calls["execute"] == ("posts", True)
assert calls["execute"] == (TEST_COMMAND, True)
def test_main_loads_config_and_sets_verbose(monkeypatch):
calls = {"setup": [], "execute": None, "require_config": 0}
calls = {"setup": [], "execute": None, "require_config": []}
_setup_main_mocks(monkeypatch, calls, requires_config=True,
config_data={"verbose": True})
monkeypatch.setattr(cli.registry, "get_all_commands",
lambda: {"posts": "only posts"})
monkeypatch.setattr(cli.registry, "requires_config", lambda name: True)
monkeypatch.setattr(cli.registry, "execute", lambda name, skip_confirmation=False: calls.update(
{"execute": (name, skip_confirmation)}
))
monkeypatch.setattr(cli, "require_config", lambda logger: calls.update(
{"require_config": calls["require_config"] + 1}
))
monkeypatch.setattr(cli.Configuration, "load",
lambda self: {"verbose": True})
monkeypatch.setattr(cli, "setup_logger", lambda verbose,
log_file: calls["setup"].append((verbose, log_file)))
monkeypatch.setattr(cli, "get_logger",
lambda: logging.getLogger("test.cli"))
monkeypatch.setattr(sys, "argv", ["skywipe", "posts"])
monkeypatch.setattr(sys, "argv", ["skywipe", TEST_COMMAND])
cli.main()
assert calls["require_config"] == 1
assert len(calls["require_config"]) == 1
assert calls["setup"] == [(False, cli.LOG_FILE), (True, cli.LOG_FILE)]
assert calls["execute"] == ("posts", False)
assert calls["execute"] == (TEST_COMMAND, False)
def test_main_handles_execute_error(monkeypatch):
calls = {"handle_error": None}
@pytest.mark.parametrize("config_data,expected_verbose", [
({}, False),
({"verbose": False}, False),
])
def test_main_config_verbose_defaults(monkeypatch, config_data, expected_verbose):
calls = {"setup": [], "execute": None, "require_config": []}
_setup_main_mocks(monkeypatch, calls, requires_config=True,
config_data=config_data)
monkeypatch.setattr(cli.registry, "get_all_commands",
lambda: {"posts": "only posts"})
monkeypatch.setattr(cli.registry, "requires_config", lambda name: False)
monkeypatch.setattr(sys, "argv", ["skywipe", TEST_COMMAND])
cli.main()
def raise_error(*_args, **_kwargs):
raise ValueError("boom")
assert len(calls["require_config"]) == 1
assert calls["setup"] == [(False, cli.LOG_FILE),
(expected_verbose, cli.LOG_FILE)]
assert calls["execute"] == (TEST_COMMAND, False)
monkeypatch.setattr(cli.registry, "execute", raise_error)
def test_main_handles_config_load_error(monkeypatch):
calls = {"handle_error": None, "require_config": []}
def mock_require_config(logger):
calls["require_config"].append(logger)
def raise_config_error(self):
raise RuntimeError("config error")
def mock_handle_error(error, logger, exit_on_error=False):
calls["handle_error"] = (type(error).__name__,
str(error), exit_on_error)
_setup_parser_mocks(monkeypatch)
monkeypatch.setattr(cli.registry, "requires_config", lambda name: True)
monkeypatch.setattr(cli, "require_config", mock_require_config)
monkeypatch.setattr(cli.Configuration, "load", raise_config_error)
monkeypatch.setattr(cli, "setup_logger", lambda verbose, log_file: None)
monkeypatch.setattr(cli, "get_logger",
lambda: logging.getLogger("test.cli"))
def fake_handle_error(error, logger, exit_on_error=False):
calls["handle_error"] = (str(error), exit_on_error)
monkeypatch.setattr(cli, "handle_error", fake_handle_error)
monkeypatch.setattr(sys, "argv", ["skywipe", "posts"])
lambda: logging.getLogger(TEST_LOGGER_NAME))
monkeypatch.setattr(cli, "handle_error", mock_handle_error)
monkeypatch.setattr(sys, "argv", ["skywipe", TEST_COMMAND])
cli.main()
assert calls["handle_error"] == ("boom", True)
assert len(calls["require_config"]) == 1
assert calls["handle_error"] is not None
assert calls["handle_error"][0] == "RuntimeError"
assert calls["handle_error"][2] is True
@pytest.mark.parametrize("error_class,error_message", [
(ValueError, TEST_ERROR_MESSAGE),
(RuntimeError, "runtime error"),
(KeyError, "missing key"),
])
def test_main_handles_execute_errors(monkeypatch, error_class, error_message):
calls = {"handle_error": None}
def raise_error(*_args, **_kwargs):
raise error_class(error_message)
_setup_error_mocks(monkeypatch, calls, raise_error)
monkeypatch.setattr(sys, "argv", ["skywipe", TEST_COMMAND])
cli.main()
assert calls["handle_error"] is not None
assert calls["handle_error"][0] == error_class.__name__
assert calls["handle_error"][1] == error_message
assert calls["handle_error"][2] is True