test(cli): ✅ add tests for prune command
add tests for prune command and some code optimization and formatting.
This commit is contained in:
@@ -34,6 +34,17 @@ def tmp_setup_logging(tmp_log_file):
|
||||
setup_logging(file_path=tmp_log_file)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def tmp_cli_logging(monkeypatch, tmp_path):
|
||||
"""Keep the CLI callback from writing run logs into the project `output/logs`.
|
||||
|
||||
The callback builds `LOGGING_PATH / <timestamp>.log` on every command call,
|
||||
so `runner.invoke()` would otherwise create a file in the working tree and
|
||||
add two handlers to the root logger for each test.
|
||||
"""
|
||||
monkeypatch.setattr("logwatcher.cli.LOGGING_PATH", tmp_path / "logs")
|
||||
|
||||
|
||||
@pytest.fixture(name="original_log_dir")
|
||||
def original_log_dir_fixture() -> Path:
|
||||
"""Return log directory containing real logs.
|
||||
|
||||
+78
-5
@@ -9,6 +9,8 @@ from unittest.mock import MagicMock
|
||||
|
||||
from logwatcher import __version__
|
||||
from logwatcher.cli import _get_files_from_dir, app
|
||||
from logwatcher.config import RESULT_PATH
|
||||
from logwatcher.utils import is_log_file
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
@@ -35,9 +37,7 @@ def get_argument_from_files(log_dir: Path, output_path: Path) -> list[str]:
|
||||
List of arguments in string format
|
||||
|
||||
"""
|
||||
files = [
|
||||
str(file) for file in log_dir.iterdir() if file.suffix in ("", ".txt", ".log")
|
||||
]
|
||||
files = [str(file) for file in log_dir.iterdir() if is_log_file(file)]
|
||||
|
||||
arguments = ["from-files", "--output-dir", str(output_path)]
|
||||
for file in files:
|
||||
@@ -384,24 +384,28 @@ def test_cli_from_files_valid_logs(output_path: Path, valid_log_dir: Path):
|
||||
def test_cli_from_mails_no_output_dir(monkeypatch, make_mock_account):
|
||||
"""Assert everything works when no output_path is provided.
|
||||
|
||||
Must use "output/" directory, located in project root.
|
||||
Must use `RESULT_PATH` as the default output directory.
|
||||
"""
|
||||
messages = [
|
||||
Message(subject="subject test 1", text_body="body test 1"),
|
||||
Message(subject="subject test 2", text_body="body test 2"),
|
||||
Message(subject="subject test 3", text_body="body test 3"),
|
||||
]
|
||||
write_log_report_mock = MagicMock()
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(
|
||||
"logwatcher.cli.connect_to_mailbox",
|
||||
lambda: make_mock_account(messages=messages, analyzed_folder="Analyzed"),
|
||||
)
|
||||
m.setattr("logwatcher.notifier.Message.send", MagicMock(return_value=None))
|
||||
m.setattr("logwatcher.cli.write_log_report", write_log_report_mock)
|
||||
m.setattr("logwatcher.cli.send_n2_report", MagicMock())
|
||||
|
||||
arguments = ["from-mails"]
|
||||
results = runner.invoke(app, arguments)
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert write_log_report_mock.call_args.kwargs["output_dir"] == RESULT_PATH
|
||||
|
||||
|
||||
def test_cli_from_mails_invalid_output_dir(tmp_path, monkeypatch):
|
||||
@@ -465,3 +469,72 @@ def test_cli_from_mails_valid_output_dir(tmp_path, monkeypatch, make_mock_accoun
|
||||
assert len(output_children) == len(expected)
|
||||
for name in expected:
|
||||
assert name in file_names
|
||||
|
||||
|
||||
def test_prune_help_lists_targets():
|
||||
"""Assert the prune command lists both of its targets."""
|
||||
results = runner.invoke(app, ["prune", "--help"])
|
||||
|
||||
assert results.exit_code == 0
|
||||
assert "--mails" in results.output
|
||||
assert "--logs" in results.output
|
||||
|
||||
|
||||
def test_prune_without_target_raises():
|
||||
"""Assert that prune without any target is rejected."""
|
||||
results = runner.invoke(app, ["prune"])
|
||||
|
||||
assert results.exit_code == 2
|
||||
assert "At least one of --mails or --logs must be provided." in results.output
|
||||
|
||||
|
||||
def test_prune_logs_only(monkeypatch):
|
||||
"""Assert `--logs` prunes logs only, without connecting to the mailbox."""
|
||||
connect_mock = MagicMock()
|
||||
prune_mails_mock = MagicMock()
|
||||
get_logs_mock = MagicMock(return_value=[MagicMock()])
|
||||
archive_logs_mock = MagicMock()
|
||||
prune_logs_mock = MagicMock()
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr("logwatcher.cli.connect_to_mailbox", connect_mock)
|
||||
m.setattr("logwatcher.cli.prune_mails", prune_mails_mock)
|
||||
m.setattr("logwatcher.cli.get_logs_to_archive", get_logs_mock)
|
||||
m.setattr("logwatcher.cli.archive_logs", archive_logs_mock)
|
||||
m.setattr("logwatcher.cli.prune_logs", prune_logs_mock)
|
||||
|
||||
results = runner.invoke(app, ["prune", "--logs"])
|
||||
|
||||
assert results.exit_code == 0
|
||||
get_logs_mock.assert_called_once()
|
||||
archive_logs_mock.assert_called_once()
|
||||
prune_logs_mock.assert_called_once()
|
||||
connect_mock.assert_not_called()
|
||||
prune_mails_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_prune_mails_only(monkeypatch):
|
||||
"""Assert `--mails` prunes mails only, without touching log archives."""
|
||||
fake_account = MagicMock()
|
||||
connect_mock = MagicMock(return_value=fake_account)
|
||||
prune_mails_mock = MagicMock()
|
||||
get_logs_mock = MagicMock()
|
||||
archive_logs_mock = MagicMock()
|
||||
prune_logs_mock = MagicMock()
|
||||
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr("logwatcher.cli.connect_to_mailbox", connect_mock)
|
||||
m.setattr("logwatcher.cli.prune_mails", prune_mails_mock)
|
||||
m.setattr("logwatcher.cli.get_logs_to_archive", get_logs_mock)
|
||||
m.setattr("logwatcher.cli.archive_logs", archive_logs_mock)
|
||||
m.setattr("logwatcher.cli.prune_logs", prune_logs_mock)
|
||||
|
||||
results = runner.invoke(app, ["prune", "--mails"])
|
||||
|
||||
assert results.exit_code == 0
|
||||
connect_mock.assert_called_once()
|
||||
prune_mails_mock.assert_called_once()
|
||||
assert prune_mails_mock.call_args.args[1] is fake_account
|
||||
get_logs_mock.assert_not_called()
|
||||
archive_logs_mock.assert_not_called()
|
||||
prune_logs_mock.assert_not_called()
|
||||
|
||||
@@ -114,9 +114,6 @@ class OkAccount:
|
||||
self.protocol = OkProtocol()
|
||||
|
||||
|
||||
#########################################
|
||||
# _find_log_attachment #
|
||||
#########################################
|
||||
def test_find_log_attachment_returns_log_file(
|
||||
empty_mail: Message,
|
||||
):
|
||||
@@ -221,9 +218,6 @@ def test_find_log_attachment_rejects_non_file_attachment(empty_mail: Message):
|
||||
assert attachment is None
|
||||
|
||||
|
||||
############################
|
||||
# _get_attachment_content #
|
||||
############################
|
||||
def test_get_attachment_content_decodes_windows_1252(original_log_dir: Path):
|
||||
"""Decodes content with accents correctly."""
|
||||
file_with_accents = original_log_dir / "CR_20260727110008.txt"
|
||||
@@ -249,9 +243,6 @@ def test_get_attachment_content_empty_bytes():
|
||||
assert content == ""
|
||||
|
||||
|
||||
########################
|
||||
# _get_mail_body(mail) #
|
||||
########################
|
||||
def test_get_mail_body_from_marker_to_end(empty_mail: Message):
|
||||
"""Returns body starting at 'Répertoire scanné'."""
|
||||
empty_mail.text_body += "Répertoire scanné : \\192.168.60.40\\e$\\MDC_1110\\Logs"
|
||||
@@ -291,9 +282,6 @@ def test_get_mail_body_multiple_markers(empty_mail: Message):
|
||||
assert "Répertoire scanné\nRépertoire scanné\nRépertoire scanné" == content
|
||||
|
||||
|
||||
################################
|
||||
# _has_attachment_marker(body) #
|
||||
################################
|
||||
def test_has_attachment_marker_true():
|
||||
"""Returns True when the exact pattern is present."""
|
||||
body_with_pattern = "Compte-rendu du suivi des imports NOSYMAG du 02/09/2026 à 15:00:10\n\nLe compte-rendu contient plus de 100 lignes.\nSe reporter à la pièce jointe."
|
||||
@@ -318,9 +306,6 @@ def test_has_attachment_marker_empty_string():
|
||||
assert not _has_attachment_marker(body_empty)
|
||||
|
||||
|
||||
########################
|
||||
# connect_to_mailbox() #
|
||||
########################
|
||||
def test_connect_to_mailbox_success(monkeypatch: MonkeyPatch, caplog, set_env):
|
||||
"""Returns an Account when credentials are valid."""
|
||||
with monkeypatch.context() as m, caplog.at_level(logging.INFO, "logwatcher"):
|
||||
|
||||
@@ -235,7 +235,7 @@ def _test_write_log_report(
|
||||
SourceType.FILE,
|
||||
relevant,
|
||||
irrelevant,
|
||||
datetime.now(FRENCH_TIMEZONE).strftime(DATETIME_FORMAT),
|
||||
today,
|
||||
start_date,
|
||||
end_date,
|
||||
nb_files,
|
||||
|
||||
Reference in New Issue
Block a user