Turn CWA off unless CWA_ENABLED is true

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.
This commit is contained in:
tim
2026-10-06 16:59:15 +02:00
parent 9a48356155
commit ca3d730129
9 changed files with 93 additions and 68 deletions
+1
View File
@@ -10,6 +10,7 @@ for name, sub in [("CONFIG_DIR", "config"), ("INGEST_DIR", "ingest"), ("TMP_DIR"
(_tmp / "ingest").mkdir() # Bindery does not make the ingest folder
os.environ["BINDERY_START_THREADS"] = "false"
os.environ["UPDATE_INTERVAL_HOURS"] = "0"
os.environ["CWA_ENABLED"] = "true" # most tests are about a Bindery with CWA
for name in (
"CWA_URL",
"CWA_USERNAME",
+14 -6
View File
@@ -115,8 +115,8 @@ test('each group of settings is an open card, and the download help starts close
const cards = main.children.filter((el) => el.tagName !== 'h1');
assert.deepEqual(cards.map((el) => [el.tagName, el.className, el.open, el.children[0].textContent]), [
['details', 'card', true, 'Recheck recent chapters'],
['details', 'card', true, 'Uploads to CWA'],
['details', 'card', true, 'Book Downloads'],
['details', 'card', true, 'Uploads to CWA'],
['details', 'card', true, 'Jobs list'],
['details', 'card', true, 'Global replacement rules'],
['details', 'card', true, 'FanFicFare settings (personal.ini)'],
@@ -789,9 +789,17 @@ test('with CWA off, the pages show nothing about CWA', async () => {
assert.ok(section(main, 'Add stories'));
assert.equal(section(main, 'Import from the library'), undefined);
assert.doesNotMatch(main.textContent, /CWA|INGEST_DIR|library/i);
settingsPage(main, {}, off);
await settle();
assert.ok(section(main, 'Jobs list'));
assert.equal(section(main, 'Uploads to CWA'), undefined);
assert.doesNotMatch(main.textContent, /CWA|INGEST_DIR|library/i);
for (const ingest of [true, false]) {
api.on('GET', 'settings', { recent_chapters: 3, personal_ini: '[defaults]', ingest_available: ingest });
settingsPage(main, {}, off);
await settle();
assert.ok(section(main, 'Jobs list'));
assert.equal(section(main, 'Uploads to CWA'), undefined);
assert.doesNotMatch(main.textContent, /CWA|library/i);
// The alert about INGEST_DIR is under Book Downloads, also with CWA off.
const note = nodes(section(main, 'Book Downloads')).find((el) => el.attributes.id === 'ingest-note');
assert.equal(note.hidden, ingest);
assert.equal(note.className, 'notice');
assert.match(note.textContent, /INGEST_DIR is not set\. Bindery sends no book to the ingest folder/);
}
});
+2 -2
View File
@@ -54,7 +54,7 @@ test('the upload switches load and save on the settings page', async (t) => {
assert.equal(warning.hidden, true);
assert.equal(link.attributes['aria-describedby'], '');
assert.match(warning.textContent, /Updates without a saved CWA book ID create duplicate books if CWA imports them/);
assert.equal(nodes(card).find((el) => el.attributes.id === 'ingest-note').hidden, true);
assert.equal(nodes(section(main, 'Book Downloads')).find((el) => el.attributes.id === 'ingest-note').hidden, true);
assert.equal(pause.disabled, false);
assert.equal(hold.disabled, false);
assert.equal(link.disabled, false);
@@ -129,7 +129,7 @@ for (const linkBooks of [true, false]) {
assert.equal(nodes(card).find((el) => el.attributes.id === 'link-note').hidden, linkBooks);
assert.equal(pause.checked, true);
assert.equal(pause.disabled, true);
const note = nodes(card).find((el) => el.attributes.id === 'ingest-note');
const note = nodes(section(main, 'Book Downloads')).find((el) => el.attributes.id === 'ingest-note');
assert.equal(note.hidden, false);
assert.equal(note.className, 'notice');
assert.equal(pause.attributes['aria-describedby'], note.attributes.id);
+15 -10
View File
@@ -143,7 +143,7 @@ def test_defaults(monkeypatch, reload_config):
]
assert config.ALLOWED_HOSTS == []
assert config.ADMIN_USERS == []
assert config.CWA_ENABLED is True
assert config.CWA_ENABLED is False
assert config.IGNORED_SETTINGS == []
assert config.SESSION_COOKIE_SECURE is False
assert config.TRUST_PROXY is False
@@ -161,9 +161,12 @@ def test_empty_ingest_directory_disables_uploads(monkeypatch, reload_config, val
assert config.INGEST_DIR is None
@pytest.mark.parametrize("value", ["false", " No ", "0", "off"])
@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):
monkeypatch.setenv("CWA_ENABLED", 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")
@@ -178,8 +181,8 @@ def test_cwa_off_ignores_the_cwa_settings_but_not_the_ingest_folder(monkeypatch,
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):
@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()
@@ -197,17 +200,19 @@ def test_prepare_with_cwa_off_logs_nothing_about_missing_cwa(folders, monkeypatc
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 []
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.", "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"]),
(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, []),
],
)
+14 -6
View File
@@ -40,7 +40,7 @@ def library(isolated, monkeypatch):
monkeypatch.setattr(orchestrator, "_find_book", Mock(return_value=None))
@pytest.mark.parametrize("enabled", [False, True])
@pytest.mark.parametrize("enabled", [None, False, True])
def test_app_starts_without_any_cwa_configuration(tmp_path, enabled):
env = {
**os.environ,
@@ -51,7 +51,14 @@ def test_app_starts_without_any_cwa_configuration(tmp_path, enabled):
"URL_BASE": "",
"CWA_ENABLED": str(enabled),
}
for name in ("INGEST_DIR", "CWA_URL", "CWA_DB_PATH", "CALIBRE_DB_PATH"):
# CWA is off unless CWA_ENABLED turns it on.
for name in (
"INGEST_DIR",
"CWA_URL",
"CWA_DB_PATH",
"CALIBRE_DB_PATH",
*(["CWA_ENABLED"] if enabled is None else []),
):
env.pop(name, None)
result = subprocess.run(
[
@@ -60,6 +67,7 @@ def test_app_starts_without_any_cwa_configuration(tmp_path, enabled):
"from bindery.web import app; "
"client = app.test_client(); "
"assert client.get('/api/health').status_code == 200; "
f"assert client.get('/api/config').json['cwa_enabled'] is {bool(enabled)}; "
"settings = client.get('/api/settings').json; "
"assert settings['pause_uploads'] is True; "
"assert settings['ingest_available'] is False; "
@@ -73,9 +81,9 @@ def test_app_starts_without_any_cwa_configuration(tmp_path, enabled):
)
assert result.returncode == 0, result.stderr
assert "ERROR" not in result.stderr
# Bindery names what is missing, until the user says that there is no CWA.
for text in ("WARNING", "INGEST_DIR is not set", "CWA_URL and CALIBRE_DB_PATH are not set", "CWA_ENABLED=false"):
assert (text in result.stderr) is enabled
# Bindery names what is missing only for a user who turned CWA on.
for text in ("WARNING", "INGEST_DIR is not set", "CWA_URL and CALIBRE_DB_PATH are not set", "remove CWA_ENABLED"):
assert (text in result.stderr) is bool(enabled)
if not enabled:
assert "CWA" not in result.stderr and "INGEST" not in result.stderr
@@ -134,7 +142,7 @@ def test_cwa_off_shows_in_the_config_and_refuses_cwa_logins(client, monkeypatch,
monkeypatch.setattr(config, "CWA_DB_PATH", "/cwa/app.db")
response = client.get("/api/stories")
assert response.status_code == 503
assert response.json["error"].startswith("AUTH_METHOD=cwa needs CWA, but CWA_ENABLED is false.")
assert response.json["error"].startswith("AUTH_METHOD=cwa needs CWA_ENABLED=true.")
def test_without_cwa_the_download_template_uses_the_story_id(client, cwa_off):