From e50797bba66ce1b63c70e3c2921deca26a56dc8c Mon Sep 17 00:00:00 2001 From: maurane Date: Fri, 25 Sep 2026 17:59:27 +0200 Subject: [PATCH] =?UTF-8?q?test(cli):=20=E2=9C=85=20add=20tests=20for=20pr?= =?UTF-8?q?une=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add tests for prune command and some code optimization and formatting. --- tests/conftest.py | 11 +++++ tests/test_cli.py | 85 ++++++++++++++++++++++++++++++++++++--- tests/test_mail_reader.py | 15 ------- tests/test_reporter.py | 2 +- 4 files changed, 91 insertions(+), 22 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9ff605f..ed9796b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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 / .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. diff --git a/tests/test_cli.py b/tests/test_cli.py index 8f9a5b6..f3e4d4c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -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 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() diff --git a/tests/test_mail_reader.py b/tests/test_mail_reader.py index 3d12685..7180622 100644 --- a/tests/test_mail_reader.py +++ b/tests/test_mail_reader.py @@ -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"): diff --git a/tests/test_reporter.py b/tests/test_reporter.py index be17eee..3ca8909 100644 --- a/tests/test_reporter.py +++ b/tests/test_reporter.py @@ -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,