mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-09-24 11:40:46 +01:00
- Adds a non-root startup path at user 1000:1000 - skips privilege escalation and ownership checks. Works e.g. for kubernetes deployments (user 1000:1000 and runAsNonRoot enabled). - Remove startup check/chown commands for user-owned folders. Checks can be done with a "Test destination" button in settings which performs a test write. Users are responsible for fixing their own permissions. - Update docs
262 lines
9.1 KiB
Python
262 lines
9.1 KiB
Python
from unittest.mock import patch
|
|
|
|
|
|
def _base_email_mode_values() -> dict[str, object]:
|
|
return {
|
|
"BOOKS_OUTPUT_MODE": "email",
|
|
"EMAIL_SMTP_HOST": "smtp.example.com",
|
|
"EMAIL_FROM": "Shelfmark <mail@example.com>",
|
|
}
|
|
|
|
|
|
def test_on_save_downloads_allows_empty_default_email_recipient(monkeypatch):
|
|
from shelfmark.config.settings import _on_save_downloads
|
|
|
|
monkeypatch.setattr("shelfmark.config.settings.load_config_file", lambda _tab: {})
|
|
|
|
values = {
|
|
**_base_email_mode_values(),
|
|
"EMAIL_RECIPIENT": "",
|
|
}
|
|
|
|
result = _on_save_downloads(values)
|
|
|
|
assert result["error"] is False
|
|
assert result["values"]["EMAIL_RECIPIENT"] == ""
|
|
|
|
|
|
def test_on_save_downloads_validates_default_email_recipient_format(monkeypatch):
|
|
from shelfmark.config.settings import _on_save_downloads
|
|
|
|
monkeypatch.setattr("shelfmark.config.settings.load_config_file", lambda _tab: {})
|
|
|
|
values = {
|
|
**_base_email_mode_values(),
|
|
"EMAIL_RECIPIENT": "Reader <reader@example.com>",
|
|
}
|
|
|
|
result = _on_save_downloads(values)
|
|
|
|
assert result["error"] is True
|
|
assert "valid plain email address" in result["message"]
|
|
|
|
|
|
def test_download_settings_email_recipient_field_uses_default_label():
|
|
from shelfmark.config.settings import download_settings
|
|
|
|
fields = download_settings()
|
|
email_field = next(field for field in fields if getattr(field, "key", None) == "EMAIL_RECIPIENT")
|
|
|
|
assert email_field.label == "Default Email Recipient"
|
|
assert "Optional fallback" in email_field.description
|
|
|
|
|
|
def test_download_settings_booklore_destination_field_defaults_to_library():
|
|
from shelfmark.config.settings import download_settings
|
|
|
|
fields = download_settings()
|
|
destination_field = next(field for field in fields if getattr(field, "key", None) == "BOOKLORE_DESTINATION")
|
|
|
|
assert destination_field.default == "library"
|
|
option_values = {option["value"] for option in destination_field.options}
|
|
assert option_values == {"library", "bookdrop"}
|
|
|
|
|
|
def test_download_settings_grimmory_copy_is_exposed_in_ui_metadata():
|
|
from shelfmark.config.settings import download_settings
|
|
|
|
fields = download_settings()
|
|
|
|
output_mode_field = next(field for field in fields if getattr(field, "key", None) == "BOOKS_OUTPUT_MODE")
|
|
grimmory_option = next(option for option in output_mode_field.options if option["value"] == "booklore")
|
|
heading_field = next(field for field in fields if getattr(field, "key", None) == "booklore_heading")
|
|
url_field = next(field for field in fields if getattr(field, "key", None) == "BOOKLORE_HOST")
|
|
|
|
assert grimmory_option["label"] == "Grimmory (API)"
|
|
assert grimmory_option["description"] == "Upload files directly to Grimmory"
|
|
assert heading_field.title == "Grimmory"
|
|
assert "(Formerly Booklore)" in heading_field.description
|
|
assert url_field.label == "Grimmory URL"
|
|
|
|
|
|
def test_download_settings_booklore_library_and_path_depend_on_library_destination():
|
|
from shelfmark.config.settings import download_settings
|
|
|
|
fields = download_settings()
|
|
library_field = next(field for field in fields if getattr(field, "key", None) == "BOOKLORE_LIBRARY_ID")
|
|
path_field = next(field for field in fields if getattr(field, "key", None) == "BOOKLORE_PATH_ID")
|
|
|
|
assert library_field.show_when == [
|
|
{"field": "BOOKS_OUTPUT_MODE", "value": "booklore"},
|
|
{"field": "BOOKLORE_DESTINATION", "value": "library"},
|
|
]
|
|
assert path_field.show_when == [
|
|
{"field": "BOOKS_OUTPUT_MODE", "value": "booklore"},
|
|
{"field": "BOOKLORE_DESTINATION", "value": "library"},
|
|
]
|
|
|
|
|
|
def test_download_settings_destination_test_buttons_exist():
|
|
from shelfmark.config.settings import download_settings
|
|
|
|
fields = download_settings()
|
|
books_button = next(field for field in fields if getattr(field, "key", None) == "test_destination")
|
|
audiobook_button = next(
|
|
field for field in fields if getattr(field, "key", None) == "test_destination_audiobook"
|
|
)
|
|
|
|
assert books_button.label == "Test Destination"
|
|
assert books_button.style == "primary"
|
|
assert books_button.show_when == {"field": "BOOKS_OUTPUT_MODE", "value": "folder"}
|
|
assert audiobook_button.label == "Test Destination"
|
|
assert audiobook_button.style == "primary"
|
|
assert audiobook_button.universal_only is True
|
|
|
|
|
|
def test_test_books_destination_uses_current_values(tmp_path):
|
|
from shelfmark.config.download_settings_handlers import check_books_destination
|
|
|
|
destination = tmp_path / "books"
|
|
|
|
result = check_books_destination({"DESTINATION": str(destination)})
|
|
|
|
assert result["success"] is True
|
|
assert result["message"] == f"Books destination is writable: {destination}"
|
|
assert destination.exists()
|
|
|
|
|
|
def test_test_audiobook_destination_falls_back_to_books_destination(tmp_path):
|
|
from shelfmark.config.download_settings_handlers import check_audiobook_destination
|
|
|
|
destination = tmp_path / "books"
|
|
|
|
result = check_audiobook_destination(
|
|
{
|
|
"DESTINATION": str(destination),
|
|
"DESTINATION_AUDIOBOOK": "",
|
|
}
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["message"] == (
|
|
f"Audiobook destination is writable: {destination} (using the Books destination)"
|
|
)
|
|
|
|
|
|
def test_test_books_destination_uses_base_path_for_user_placeholder(tmp_path):
|
|
from shelfmark.config.download_settings_handlers import check_books_destination
|
|
|
|
destination = tmp_path / "books"
|
|
|
|
result = check_books_destination({"DESTINATION": f"{destination}/{{User}}"})
|
|
|
|
assert result["success"] is True
|
|
assert result["message"] == (
|
|
f"Books destination is writable: {destination} "
|
|
f"(tested base path {destination} from configured template {destination}/{{User}})"
|
|
)
|
|
assert not (destination / "{User}").exists()
|
|
|
|
|
|
def test_test_books_destination_uses_base_path_for_lowercase_user_placeholder(tmp_path):
|
|
from shelfmark.config.download_settings_handlers import check_books_destination
|
|
|
|
destination = tmp_path / "books"
|
|
|
|
result = check_books_destination({"DESTINATION": f"{destination}/{{user}}"})
|
|
|
|
assert result["success"] is True
|
|
assert result["message"] == (
|
|
f"Books destination is writable: {destination} "
|
|
f"(tested base path {destination} from configured template {destination}/{{user}})"
|
|
)
|
|
assert not (destination / "{user}").exists()
|
|
|
|
|
|
def test_test_books_destination_rejects_relative_user_placeholder_path():
|
|
from shelfmark.config.download_settings_handlers import check_books_destination
|
|
|
|
result = check_books_destination({"DESTINATION": "{User}/books"})
|
|
|
|
assert result["success"] is False
|
|
assert result["message"] == "Destination must be absolute: {User}/books"
|
|
|
|
|
|
def test_test_books_destination_requires_value():
|
|
from shelfmark.config.download_settings_handlers import check_books_destination
|
|
|
|
result = check_books_destination({"DESTINATION": ""})
|
|
|
|
assert result["success"] is False
|
|
assert result["message"] == "Books destination is required"
|
|
|
|
|
|
def test_test_books_destination_uses_persisted_value_when_current_values_missing(monkeypatch, tmp_path):
|
|
from shelfmark.config.download_settings_handlers import check_books_destination
|
|
from shelfmark.core.config import config
|
|
|
|
destination = tmp_path / "persisted-books"
|
|
|
|
def _fake_get(key: str, default=None):
|
|
if key == "DESTINATION":
|
|
return str(destination)
|
|
return default
|
|
|
|
monkeypatch.setattr(config, "get", _fake_get)
|
|
|
|
result = check_books_destination()
|
|
|
|
assert result["success"] is True
|
|
assert result["message"] == f"Books destination is writable: {destination}"
|
|
|
|
|
|
def test_test_audiobook_destination_preserves_books_fallback_suffix_on_failure(tmp_path):
|
|
from shelfmark.config.download_settings_handlers import check_audiobook_destination
|
|
|
|
destination = tmp_path / "books"
|
|
|
|
def _fake_validate_destination(path, status_callback):
|
|
status_callback("error", f"Destination not writable: {path}")
|
|
return False
|
|
|
|
with patch(
|
|
"shelfmark.download.postprocess.destination.validate_destination",
|
|
side_effect=_fake_validate_destination,
|
|
):
|
|
result = check_audiobook_destination(
|
|
{
|
|
"DESTINATION": str(destination),
|
|
"DESTINATION_AUDIOBOOK": "",
|
|
}
|
|
)
|
|
|
|
assert result["success"] is False
|
|
assert result["message"] == (
|
|
f"Destination not writable: {destination} (using the Books destination)"
|
|
)
|
|
|
|
|
|
def test_execute_action_passes_unsaved_values_to_destination_test(tmp_path):
|
|
import shelfmark.config.settings # noqa: F401
|
|
from shelfmark.core.settings_registry import execute_action
|
|
|
|
destination = tmp_path / "action-books"
|
|
captured: dict[str, object] = {}
|
|
|
|
def _fake_validate_destination(path, status_callback):
|
|
captured["path"] = path
|
|
return True
|
|
|
|
with patch(
|
|
"shelfmark.download.postprocess.destination.validate_destination",
|
|
side_effect=_fake_validate_destination,
|
|
):
|
|
result = execute_action(
|
|
"downloads",
|
|
"test_destination",
|
|
{"DESTINATION": str(destination)},
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert captured["path"] == destination
|