Source code for tests.test_config

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""Configuration file loading, validation and export."""

from __future__ import annotations

from mailbox import Maildir
from textwrap import dedent

import pytest

from .conftest import MailFactory


[docs] @pytest.fixture() def make_config(tmp_path): """Write a TOML configuration file and return its path as a string.""" def _make_config(content): conf_file = tmp_path.joinpath("mdedup.toml") conf_file.write_text(dedent(content), encoding="UTF-8") return str(conf_file) return _make_config
[docs] def test_config_file_drives_run(invoke, make_box, make_config): """Strategy, action and dry-run picked up from a configuration file.""" duplicate = MailFactory() box_path, _, _ = make_box(Maildir, [duplicate, duplicate]) conf_path = make_config("""\ [mdedup] strategies = ["select-one"] action = "delete-discarded" dry_run = true """) result = invoke("--config", conf_path, box_path) assert result.exit_code == 0 assert "select-one strategy will be applied" in result.stderr assert "Perform delete-discarded action" in result.stderr assert "DRY RUN:" in result.stderr assert "none will be altered" in result.stderr
[docs] def test_config_kebab_case_keys(invoke, make_box, make_config): """Kebab-case keys, the TOML convention matching the CLI flags, are accepted.""" box_path, _, _ = make_box(Maildir, [MailFactory()]) conf_path = make_config("""\ [mdedup] hash-only = true """) result = invoke("--config", conf_path, box_path) assert result.exit_code == 0 assert "Hash:" in result.stdout
[docs] def test_config_unknown_key_rejected(invoke, make_box, make_config): """A typo in the configuration file aborts the run instead of being ignored.""" box_path, _, _ = make_box(Maildir, [MailFactory()]) conf_path = make_config("""\ [mdedup] strategyy = ["discard-older"] """) result = invoke("--config", conf_path, box_path) assert result.exit_code == 1 assert "Unknown configuration key 'strategyy'." in result.stderr
[docs] def test_config_preset(invoke, make_box, make_config): """A preset picked up from a configuration file applies its options.""" box_path, _, _ = make_box(Maildir, [MailFactory()]) conf_path = make_config("""\ [mdedup] preset = "conservative" """) result = invoke("--config", conf_path, "--hash-only", box_path) assert result.exit_code == 0 assert "Apply the options of the conservative preset:" in result.stderr assert "info: --raw-headers --hash-body raw " in result.stderr
[docs] def test_config_value_overrides_a_preset(invoke, make_box, make_config): """A configuration file value overrides the preset, and the run says so. A forgotten key would otherwise change a run that reads as conservative. """ box_path, _, _ = make_box(Maildir, [MailFactory()]) conf_path = make_config("""\ [mdedup] hash-body = "decoded" """) result = invoke( "--config", conf_path, "--preset=conservative", "--hash-only", box_path ) assert result.exit_code == 0 assert "Options override the conservative preset: -b/--hash-body." in result.stderr
[docs] def test_config_mail_sources_blocked(invoke, make_box, make_config): """Mail sources are command-line only: a config file cannot point at boxes.""" box_path, _, _ = make_box(Maildir, [MailFactory()]) # The value is irrelevant: mail sources are refused by key, whatever they point # at. A static string sidesteps interpolating a Windows box path into TOML. conf_path = make_config("""\ [mdedup] mail_sources = ["some-box"] """) result = invoke("--config", conf_path, box_path) assert result.exit_code == 1 assert ( "Configuration key 'mail_sources' is not allowed in configuration files." in result.stderr )
[docs] @pytest.mark.parametrize( ("setting", "message"), ( pytest.param( 'strategies = ["select-matching-path"]', "--regexp is required", id="constraint", ), # Only a configuration file can set an empty list: without any --hash-header, # the command line falls back to the default headers. pytest.param( "hash-headers = []", "At least one header ID must be provided.", id="no-hash-header", ), ), ) def test_config_values_checked(invoke, make_box, make_config, setting, message): """Config-sourced values go through the checks of command-line ones.""" box_path, _, _ = make_box(Maildir, [MailFactory()]) conf_path = make_config(f"[mdedup]\n{setting}\n") result = invoke("--config", conf_path, box_path) assert result.exit_code == 2 assert message in result.stderr
[docs] def test_cli_overrides_config(invoke, make_config): """Command-line values take precedence over configuration file values.""" conf_path = make_config("""\ [mdedup] size_threshold = 1024 """) result = invoke( "--config", conf_path, "--size-threshold", "2048", "--params", "--table-format", "csv", ) assert result.exit_code == 0 row = [ line for line in result.stdout.splitlines() if line.startswith("mdedup.size_threshold,") ] assert len(row) == 1 assert ",'2048',COMMANDLINE" in row[0]
[docs] def test_pyproject_autodiscovery(invoke, make_box, monkeypatch, tmp_path): """A pyproject.toml with a [tool.mdedup] section is discovered from cwd.""" box_path, _, _ = make_box(Maildir, [MailFactory()]) tmp_path.joinpath("pyproject.toml").write_text( dedent("""\ [tool.mdedup] hash_only = true """), encoding="UTF-8", ) monkeypatch.chdir(tmp_path) result = invoke(box_path) assert result.exit_code == 0 assert "Hash:" in result.stdout
[docs] def test_params_reflects_autodiscovered_config(invoke, monkeypatch, tmp_path): """--params shows values sourced from an autodiscovered configuration file.""" tmp_path.joinpath("pyproject.toml").write_text( dedent("""\ [tool.mdedup] size_threshold = 1024 """), encoding="UTF-8", ) monkeypatch.chdir(tmp_path) result = invoke("--params", "--table-format", "csv") assert result.exit_code == 0 row = [ line for line in result.stdout.splitlines() if line.startswith("mdedup.size_threshold,") ] assert len(row) == 1 assert ",1024,DEFAULT_MAP" in row[0]
[docs] def test_export_config_toml_roundtrip(invoke, make_config): """The TOML export names every configurable option and loads back cleanly. Loading the exported file back under strict mode is the round-trip guarantee: every key the export emits is recognized by the CLI. """ result = invoke("--export-config", "toml") assert result.exit_code == 0 export = result.stdout assert "[mdedup]" in export # Multi-value options read as lists, unset options as commented-out keys, # and keys use the canonical kebab-case spelling. assert "strategies = []" in export assert "hash-headers = [" in export assert "# input-format =" in export assert "# regexp =" in export assert "# export =" in export # Command-line-only and self-referential parameters are not exported. assert "mail_sources" not in export assert "config" not in export.replace("export_config", "").replace( "validate_config", "" ) conf_path = make_config(export) result = invoke("--config", conf_path, "--params", "--table-format", "csv") assert result.exit_code == 0