One function reads a true or false value. The download name uses the file name cleaning of the ingest folder. The login code reads the login mode from the configuration, and has one lockout message. The title helper of FanFicFare is now unescape_title, because cwa.py has another plain_title.
297 lines
12 KiB
Python
297 lines
12 KiB
Python
"""Reading the configuration from environment variables."""
|
|
|
|
import importlib
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from bindery import config
|
|
|
|
|
|
@pytest.fixture
|
|
def reload_config(monkeypatch):
|
|
"""Load the config module again with a changed environment, and restore it afterwards."""
|
|
yield lambda: importlib.reload(config)
|
|
monkeypatch.undo()
|
|
importlib.reload(config)
|
|
|
|
|
|
def test_bool(monkeypatch):
|
|
for value in ("true", "Yes", " 1 ", "on", "y"):
|
|
monkeypatch.setenv("X_TEST", value)
|
|
assert config._bool("X_TEST")
|
|
for value in ("false", "0", "no", ""):
|
|
monkeypatch.setenv("X_TEST", value)
|
|
assert not config._bool("X_TEST")
|
|
monkeypatch.delenv("X_TEST")
|
|
assert config._bool("X_TEST", True)
|
|
# A value that is not clear gives the default.
|
|
monkeypatch.setenv("X_TEST", "maybe")
|
|
assert config._bool("X_TEST", True) and not config._bool("X_TEST")
|
|
|
|
|
|
def test_list(monkeypatch):
|
|
monkeypatch.setenv("X_TEST", " title, tags ,,series ")
|
|
assert config._list("X_TEST") == ["title", "tags", "series"]
|
|
monkeypatch.delenv("X_TEST")
|
|
assert config._list("X_TEST") == []
|
|
assert config._list("X_TEST", "title") == ["title"]
|
|
|
|
|
|
def test_relative_paths_become_absolute(monkeypatch, tmp_path, reload_config):
|
|
monkeypatch.chdir(tmp_path)
|
|
for name, value in [("CONFIG_DIR", "dev/config"), ("INGEST_DIR", "dev/ingest"), ("TMP_DIR", "dev/tmp")]:
|
|
monkeypatch.setenv(name, value)
|
|
monkeypatch.setenv("CWA_DB_PATH", "cwa/app.db")
|
|
monkeypatch.setenv("CALIBRE_DB_PATH", "library/metadata.db")
|
|
reload_config()
|
|
assert config.CONFIG_DIR == Path.cwd() / "dev/config"
|
|
assert config.INGEST_DIR == Path.cwd() / "dev/ingest"
|
|
assert config.TMP_DIR == Path.cwd() / "dev/tmp"
|
|
assert config.CWA_DB_PATH == Path.cwd() / "cwa/app.db"
|
|
assert config.CALIBRE_DB_PATH == Path.cwd() / "library/metadata.db"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("value", "expected"),
|
|
[
|
|
("", ""),
|
|
("/", ""),
|
|
("bindery", "/bindery"),
|
|
("/bindery/", "/bindery"),
|
|
(" /a/b ", "/a/b"),
|
|
],
|
|
)
|
|
def test_base_path(value, expected):
|
|
assert config._base_path(value) == expected
|
|
|
|
|
|
def test_values_from_the_environment(monkeypatch, reload_config):
|
|
monkeypatch.setenv("URL_BASE", "bindery/")
|
|
monkeypatch.setenv("AUTH_METHOD", " CWA ")
|
|
monkeypatch.setenv("UPDATE_INTERVAL_HOURS", "1.5")
|
|
monkeypatch.setenv("DEFAULT_LOCKED_FIELDS", "title, authors")
|
|
monkeypatch.setenv("CWA_ALLOWED_USERS", "alice,bob")
|
|
monkeypatch.setenv("BINDERY_ADMIN_USERS", "alice")
|
|
monkeypatch.setenv("CONFIG_DIR", "/data/bindery")
|
|
monkeypatch.setenv("TRUST_PROXY", "yes")
|
|
monkeypatch.setenv("CWA_DB_PATH", " /cwa/app.db ")
|
|
monkeypatch.setenv("CALIBRE_DB_PATH", "/library/metadata.db")
|
|
monkeypatch.setenv("BINDERY_USERNAME", " reader ")
|
|
monkeypatch.setenv("BINDERY_PASSWORD_HASH", " hash ")
|
|
monkeypatch.setenv("ALLOWED_HOSTS", "Bindery.Example.com, .home.example")
|
|
reload_config()
|
|
assert config.ALLOWED_HOSTS == ["bindery.example.com", ".home.example"]
|
|
assert config.URL_BASE == "/bindery"
|
|
assert config.AUTH_METHOD == "cwa"
|
|
assert config.UPDATE_INTERVAL_HOURS == 1.5
|
|
assert config.DEFAULT_LOCKED_FIELDS == ["title", "authors"]
|
|
assert config.CWA_ALLOWED_USERS == ["alice", "bob"]
|
|
assert config.ADMIN_USERS == ["alice"]
|
|
assert str(config.DB_PATH) == "/data/bindery/bindery.db"
|
|
assert config.TRUST_PROXY is True
|
|
assert str(config.CWA_DB_PATH) == "/cwa/app.db"
|
|
assert str(config.CALIBRE_DB_PATH) == "/library/metadata.db"
|
|
assert config.LOCAL_USERNAME == "reader"
|
|
assert config.LOCAL_PASSWORD_HASH == "hash"
|
|
|
|
|
|
def test_defaults(monkeypatch, reload_config):
|
|
for name in (
|
|
"URL_BASE",
|
|
"AUTH_METHOD",
|
|
"UPDATE_INTERVAL_HOURS",
|
|
"DEFAULT_LOCKED_FIELDS",
|
|
"LOG_LEVEL",
|
|
"PROXY_AUTH_USER_HEADER",
|
|
"PROXY_AUTH_TRUSTED_IPS",
|
|
"ALLOWED_HOSTS",
|
|
"BINDERY_ADMIN_USERS",
|
|
"CWA_ENABLED",
|
|
"SESSION_COOKIE_SECURE",
|
|
"TRUST_PROXY",
|
|
"CWA_DB_PATH",
|
|
"CALIBRE_DB_PATH",
|
|
"INGEST_DIR",
|
|
"BINDERY_USERNAME",
|
|
"BINDERY_PASSWORD_HASH",
|
|
):
|
|
monkeypatch.delenv(name, raising=False)
|
|
reload_config()
|
|
assert config.URL_BASE == ""
|
|
assert config.AUTH_METHOD == "none"
|
|
assert config.UPDATE_INTERVAL_HOURS == 24
|
|
assert config.DEFAULT_LOCKED_FIELDS == ["title"]
|
|
assert config.LOG_LEVEL == "INFO"
|
|
assert config.PROXY_AUTH_USER_HEADER == "X-Remote-User"
|
|
assert config.PROXY_AUTH_TRUSTED_IPS == [
|
|
"127.0.0.0/8",
|
|
"::1",
|
|
"10.0.0.0/8",
|
|
"172.16.0.0/12",
|
|
"192.168.0.0/16",
|
|
"fc00::/7",
|
|
"fe80::/10",
|
|
]
|
|
assert config.ALLOWED_HOSTS == []
|
|
assert config.ADMIN_USERS == []
|
|
assert config.CWA_ENABLED is True
|
|
assert config.IGNORED_SETTINGS == []
|
|
assert config.SESSION_COOKIE_SECURE is False
|
|
assert config.TRUST_PROXY is False
|
|
assert config.CWA_DB_PATH is None
|
|
assert config.CALIBRE_DB_PATH is None
|
|
assert config.INGEST_DIR is None
|
|
assert config.LOCAL_USERNAME == ""
|
|
assert config.LOCAL_PASSWORD_HASH == ""
|
|
|
|
|
|
@pytest.mark.parametrize("value", ["", " "])
|
|
def test_empty_ingest_directory_disables_uploads(monkeypatch, reload_config, value):
|
|
monkeypatch.setenv("INGEST_DIR", value)
|
|
reload_config()
|
|
assert config.INGEST_DIR is None
|
|
|
|
|
|
@pytest.mark.parametrize("value", ["false", " No ", "0", "off"])
|
|
def test_cwa_off_ignores_the_cwa_settings_but_not_the_ingest_folder(monkeypatch, reload_config, value):
|
|
monkeypatch.setenv("CWA_ENABLED", value)
|
|
monkeypatch.setenv("CWA_URL", "https://cwa.invalid")
|
|
monkeypatch.setenv("CWA_USERNAME", "bindery")
|
|
monkeypatch.setenv("CWA_PASSWORD", "secret")
|
|
monkeypatch.setenv("CWA_DB_PATH", "/cwa/app.db")
|
|
monkeypatch.setenv("CALIBRE_DB_PATH", "/library/metadata.db")
|
|
monkeypatch.setenv("INGEST_DIR", "/books")
|
|
reload_config()
|
|
assert config.CWA_ENABLED is False
|
|
assert (config.CWA_URL, config.CWA_USERNAME, config.CWA_PASSWORD) == ("", "", "")
|
|
assert (config.CWA_DB_PATH, config.CALIBRE_DB_PATH) == (None, None)
|
|
assert config.IGNORED_SETTINGS == ["CWA_URL", "CWA_USERNAME", "CWA_PASSWORD", "CWA_DB_PATH", "CALIBRE_DB_PATH"]
|
|
assert str(config.INGEST_DIR) == "/books"
|
|
|
|
|
|
@pytest.mark.parametrize("value", ["", "true", "maybe"])
|
|
def test_only_a_clear_false_turns_cwa_off(monkeypatch, reload_config, value):
|
|
monkeypatch.setenv("CWA_ENABLED", value)
|
|
monkeypatch.setenv("CWA_URL", "https://cwa.invalid")
|
|
reload_config()
|
|
assert config.CWA_ENABLED is True
|
|
assert config.CWA_URL == "https://cwa.invalid"
|
|
|
|
|
|
@pytest.mark.parametrize("ignored", [[], ["CWA_URL", "CALIBRE_DB_PATH"]])
|
|
def test_prepare_with_cwa_off_logs_nothing_about_missing_cwa(monkeypatch, tmp_path, caplog, ignored):
|
|
monkeypatch.setattr(config, "CWA_ENABLED", False)
|
|
monkeypatch.setattr(config, "IGNORED_SETTINGS", ignored)
|
|
monkeypatch.setattr(config, "INGEST_DIR", None)
|
|
monkeypatch.setattr(config, "CWA_URL", "")
|
|
monkeypatch.setattr(config, "CALIBRE_DB_PATH", None)
|
|
for name, folder in [("CONFIG_DIR", "config"), ("UPLOAD_DIR", "uploads"), ("TMP_DIR", "tmp")]:
|
|
monkeypatch.setattr(config, name, tmp_path / folder)
|
|
monkeypatch.setattr(config, "PERSONAL_INI", tmp_path / "config" / "personal.ini")
|
|
with caplog.at_level("INFO"):
|
|
config.prepare()
|
|
lines = [r.getMessage() for r in caplog.records]
|
|
expected = ["CWA_ENABLED is false. Bindery ignores CWA_URL, CALIBRE_DB_PATH."] if ignored else []
|
|
assert [line for line in lines if "CWA" in line or "INGEST" in line] == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("ingest", "url", "database", "expected"),
|
|
[
|
|
(False, "", None, ["INGEST_DIR is not set", "cannot find the library book of a story.", "CWA_ENABLED=false"]),
|
|
(True, "", None, ["so each update adds a new book to CWA.", "CWA_ENABLED=false"]),
|
|
(True, "", Path("/library/metadata.db"), ["CWA_URL is not set. Bindery cannot search", "CWA_ENABLED=false"]),
|
|
(False, "https://cwa.invalid", None, ["INGEST_DIR is not set", "CWA_ENABLED=false"]),
|
|
(True, "https://cwa.invalid", None, []),
|
|
],
|
|
)
|
|
def test_prepare_warns_about_each_missing_cwa_function(monkeypatch, tmp_path, caplog, ingest, url, database, expected):
|
|
monkeypatch.setattr(config, "CWA_URL", url)
|
|
monkeypatch.setattr(config, "CALIBRE_DB_PATH", database)
|
|
monkeypatch.setattr(config, "INGEST_DIR", tmp_path if ingest else None)
|
|
for name, folder in [("CONFIG_DIR", "config"), ("UPLOAD_DIR", "uploads"), ("TMP_DIR", "tmp")]:
|
|
monkeypatch.setattr(config, name, tmp_path / folder)
|
|
monkeypatch.setattr(config, "PERSONAL_INI", tmp_path / "config" / "personal.ini")
|
|
with caplog.at_level("WARNING"):
|
|
config.prepare()
|
|
warnings = [r.getMessage() for r in caplog.records]
|
|
assert len(warnings) == len(expected)
|
|
for warning, text in zip(warnings, expected, strict=True):
|
|
assert text in warning
|
|
|
|
|
|
def test_prepare_without_cwa_creates_only_bindery_folders(monkeypatch, tmp_path, caplog):
|
|
monkeypatch.setattr(config, "CWA_ENABLED", False)
|
|
monkeypatch.setattr(config, "INGEST_DIR", None)
|
|
monkeypatch.setattr(config, "CWA_URL", "")
|
|
monkeypatch.setattr(config, "CALIBRE_DB_PATH", None)
|
|
for name, folder in [("CONFIG_DIR", "config"), ("UPLOAD_DIR", "uploads"), ("TMP_DIR", "tmp")]:
|
|
monkeypatch.setattr(config, name, tmp_path / folder)
|
|
monkeypatch.setattr(config, "PERSONAL_INI", tmp_path / "config" / "personal.ini")
|
|
config.prepare()
|
|
assert config.PERSONAL_INI.exists()
|
|
assert {p.name for p in tmp_path.iterdir()} == {"config", "uploads", "tmp"}
|
|
assert caplog.records == []
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("url", "database", "warns"),
|
|
[("", None, True), ("https://cwa.invalid", None, False), ("", Path("/library/metadata.db"), False)],
|
|
)
|
|
def test_prepare_warns_about_an_ingest_folder_without_a_library(monkeypatch, tmp_path, caplog, url, database, warns):
|
|
monkeypatch.setattr(config, "CWA_URL", url)
|
|
monkeypatch.setattr(config, "CALIBRE_DB_PATH", database)
|
|
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 ("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
|
|
|
|
|
|
@pytest.mark.parametrize("exists", [False, True])
|
|
def test_prepare_does_not_make_the_ingest_folder(monkeypatch, tmp_path, caplog, exists):
|
|
monkeypatch.setattr(config, "CWA_URL", "https://cwa.invalid")
|
|
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")
|
|
if exists:
|
|
config.INGEST_DIR.mkdir()
|
|
config.prepare()
|
|
assert config.INGEST_DIR.exists() is exists
|
|
assert ("does not exist. Check INGEST_DIR and the volume mount" in caplog.text) is not exists
|