CWA_ENABLED now defaults to false. If a CWA variable is set while CWA is off, the start log has a warning that names it. On the Settings page, the alert about INGEST_DIR moves to Book Downloads and shows with CWA on or off. The Uploads to CWA card moves below Book Downloads.
298 lines
12 KiB
Python
298 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)
|
|
|
|
|
|
@pytest.fixture
|
|
def folders(monkeypatch, tmp_path):
|
|
"""Put the folders of Bindery in a temporary folder. The ingest folder does not exist yet."""
|
|
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")
|
|
|
|
|
|
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 False
|
|
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", [None, "", "false", " No ", "0", "maybe"])
|
|
def test_cwa_off_ignores_the_cwa_settings_but_not_the_ingest_folder(monkeypatch, reload_config, value):
|
|
if value is None:
|
|
monkeypatch.delenv("CWA_ENABLED")
|
|
else:
|
|
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", " Yes ", "1", "on"])
|
|
def test_only_a_clear_true_turns_cwa_on(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(folders, 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)
|
|
with caplog.at_level("INFO"):
|
|
config.prepare()
|
|
lines = [r.getMessage() for r in caplog.records]
|
|
ignores = "CWA_ENABLED is not true. Bindery ignores CWA_URL, CALIBRE_DB_PATH. To use CWA, set CWA_ENABLED=true."
|
|
expected = [ignores] if ignored else []
|
|
assert [r.levelname for r in caplog.records if "CWA" in r.getMessage()] == ["WARNING"] * len(expected)
|
|
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.", "remove CWA_ENABLED"]),
|
|
(True, "", None, ["so each update adds a new book to CWA.", "remove CWA_ENABLED"]),
|
|
(True, "", Path("/library/metadata.db"), ["CWA_URL is not set. Bindery cannot search", "remove CWA_ENABLED"]),
|
|
(False, "https://cwa.invalid", None, ["INGEST_DIR is not set", "remove CWA_ENABLED"]),
|
|
(True, "https://cwa.invalid", None, []),
|
|
],
|
|
)
|
|
def test_prepare_warns_about_each_missing_cwa_function(
|
|
folders, 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)
|
|
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(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)
|
|
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(
|
|
folders, monkeypatch, tmp_path, caplog, url, database, warns
|
|
):
|
|
monkeypatch.setattr(config, "CWA_URL", url)
|
|
monkeypatch.setattr(config, "CALIBRE_DB_PATH", database)
|
|
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(
|
|
folders, 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")
|
|
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(folders, monkeypatch, tmp_path, caplog, exists):
|
|
monkeypatch.setattr(config, "CWA_URL", "https://cwa.invalid")
|
|
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
|