diff --git a/bindery/config.py b/bindery/config.py index ac09fad..a4173b8 100644 --- a/bindery/config.py +++ b/bindery/config.py @@ -27,6 +27,22 @@ def _optional_path(name: str) -> Path | None: return Path(value).absolute() if value else None +# A bad value stops the start with one clear line, not with a traceback. +def _number(name: str, default: str) -> float: + value = os.getenv(name, default).strip() + try: + return float(value) + except ValueError: + raise SystemExit(f"{name} must be a number, not {value!r}") from None + + +def _choice(name: str, default: str, choices: tuple[str, ...]) -> str: + value = os.getenv(name, default).strip().upper() + if value not in choices: + raise SystemExit(f"{name} must be one of {', '.join(choices)}, not {value!r}") + return value + + def _base_path(value: str) -> str: path = value.strip().strip("/") return f"/{path}" if path else "" @@ -37,7 +53,7 @@ PROJECT_ROOT = Path(__file__).resolve().parent.parent CONFIG_DIR = _path("CONFIG_DIR", "/config") INGEST_DIR = _optional_path("INGEST_DIR") TMP_DIR = _path("TMP_DIR", "/tmp/bindery") -LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").strip().upper() +LOG_LEVEL = _choice("LOG_LEVEL", "INFO", ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")) # A path prefix, such as /bindery, for a reverse proxy that forwards the full path. URL_BASE = _base_path(os.getenv("URL_BASE", "")) @@ -49,7 +65,7 @@ CWA_PASSWORD = os.getenv("CWA_PASSWORD", "") CWA_DB_PATH = _optional_path("CWA_DB_PATH") # Calibre-Web app.db, for logins CALIBRE_DB_PATH = _optional_path("CALIBRE_DB_PATH") # calibre metadata.db, for book IDs -UPDATE_INTERVAL_HOURS = float(os.getenv("UPDATE_INTERVAL_HOURS", "24")) +UPDATE_INTERVAL_HOURS = _number("UPDATE_INTERVAL_HOURS", "24") DEFAULT_LOCKED_FIELDS = _list("DEFAULT_LOCKED_FIELDS", "title") # Logins: none, local, cwa or proxy. An API key works in every mode. @@ -89,6 +105,8 @@ def prepare() -> None: if not PERSONAL_INI.exists(): shutil.copy(DEFAULT_PERSONAL_INI, PERSONAL_INI) logger.info(f"Created the default FanFicFare configuration at {PERSONAL_INI}") + if CWA_PASSWORD and not CWA_USERNAME: + logger.warning("CWA_PASSWORD is set, but CWA_USERNAME is not. Bindery sends no login to CWA.") if INGEST_DIR and not (CWA_URL or CALIBRE_DB_PATH): logger.warning( "INGEST_DIR is set, but CWA_URL and CALIBRE_DB_PATH are not. Bindery cannot find the library book " diff --git a/tests/test_config.py b/tests/test_config.py index 999da69..68a2cf5 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -155,3 +155,36 @@ def test_prepare_warns_about_an_ingest_folder_without_a_library(monkeypatch, tmp with caplog.at_level("WARNING"): config.prepare() assert ("each update adds a new book" in caplog.text) is warns + + +@pytest.mark.parametrize( + ("name", "value", "error"), + [ + ("UPDATE_INTERVAL_HOURS", "daily", "UPDATE_INTERVAL_HOURS must be a number, not 'daily'"), + ("LOG_LEVEL", "loud", "LOG_LEVEL must be one of DEBUG, INFO, WARNING, ERROR, CRITICAL, not 'LOUD'"), + ], +) +def test_a_bad_value_stops_the_start_with_a_clear_message(monkeypatch, reload_config, name, value, error): + monkeypatch.setenv(name, value) + with pytest.raises(SystemExit) as stop: + reload_config() + assert str(stop.value) == error + + +def test_log_level_in_lower_case(monkeypatch, reload_config): + monkeypatch.setenv("LOG_LEVEL", " debug ") + reload_config() + assert config.LOG_LEVEL == "DEBUG" + + +@pytest.mark.parametrize(("username", "warns"), [("", True), ("bindery", False)]) +def test_prepare_warns_about_a_cwa_password_without_a_user_name(monkeypatch, tmp_path, caplog, username, warns): + monkeypatch.setattr(config, "CWA_URL", "https://cwa.invalid") + monkeypatch.setattr(config, "CWA_USERNAME", username) + monkeypatch.setattr(config, "CWA_PASSWORD", "secret") + for name, folder in [("CONFIG_DIR", "config"), ("UPLOAD_DIR", "uploads"), ("TMP_DIR", "tmp"), ("INGEST_DIR", "in")]: + monkeypatch.setattr(config, name, tmp_path / folder) + monkeypatch.setattr(config, "PERSONAL_INI", tmp_path / "config" / "personal.ini") + with caplog.at_level("WARNING"): + config.prepare() + assert ("CWA_PASSWORD is set" in caplog.text) is warns