Misc features: Retries, user search config, sort by format, admin download control (#679)

- Added the manual retry option for failed downloads
- Added the ability to retry failed post-processing using existing
downloaded file
- Added admin-visible "Download as" selector, admin chooses a user to
download on-behalf of - inherits their output preferences.
- Added search mode and default metadata provider / release source
options to User Preferences and My Account settings.
- Added sort by format option in release results
- Added {OriginalName} renaming field option, to retain the exact
downloaded filename
- Frontend dependency updates - fixes rollup vulnerability from this
week

Closes #662 #656 #649 #562
This commit is contained in:
Alex
2026-03-01 19:47:57 +00:00
committed by GitHub
parent ea0d06ae08
commit 9593c040b0
52 changed files with 3288 additions and 658 deletions
+91
View File
@@ -106,6 +106,20 @@ class TestAdminUsersListEndpoint:
users = resp.json
assert "password_hash" not in users[0]
def test_list_users_hides_internal_no_auth_activity_user(self, admin_client, user_db):
user_db.create_user(
username="__shelfmark_noauth_activity__",
display_name="No-auth Activity",
role="admin",
)
user_db.create_user(username="alice", email="alice@example.com")
resp = admin_client.get("/api/admin/users")
assert resp.status_code == 200
usernames = [u["username"] for u in resp.json]
assert "__shelfmark_noauth_activity__" not in usernames
assert "alice" in usernames
def test_list_users_includes_auth_source_and_is_active(self, admin_client, user_db):
user_db.create_user(username="local_user", auth_source="builtin")
user_db.create_user(
@@ -1151,6 +1165,83 @@ class TestAdminDeliveryPreferences:
assert resp.status_code == 403
# ---------------------------------------------------------------------------
# GET /api/admin/users/<id>/search-preferences
# ---------------------------------------------------------------------------
class TestAdminSearchPreferences:
"""Tests for GET /api/admin/users/<id>/search-preferences."""
@pytest.fixture(autouse=True)
def setup_config(self, tmp_path, monkeypatch):
import json
from pathlib import Path
config_dir = str(tmp_path)
monkeypatch.setenv("CONFIG_DIR", config_dir)
monkeypatch.setattr("shelfmark.config.env.CONFIG_DIR", Path(config_dir))
plugins_dir = tmp_path / "plugins"
plugins_dir.mkdir()
search_mode_config = {
"SEARCH_MODE": "direct",
"METADATA_PROVIDER": "openlibrary",
"METADATA_PROVIDER_AUDIOBOOK": "",
"DEFAULT_RELEASE_SOURCE": "direct_download",
}
(plugins_dir / "search_mode.json").write_text(json.dumps(search_mode_config))
from shelfmark.core.config import config as app_config
app_config.refresh()
def test_returns_curated_fields_and_effective_values(self, admin_client, user_db):
user = user_db.create_user(username="alice")
user_db.set_user_settings(
user["id"],
{
"SEARCH_MODE": "universal",
"METADATA_PROVIDER": "openlibrary",
"DEFAULT_RELEASE_SOURCE": "prowlarr",
},
)
resp = admin_client.get(f"/api/admin/users/{user['id']}/search-preferences")
assert resp.status_code == 200
data = resp.json
assert data["tab"] == "search_mode"
assert data["keys"] == [
"SEARCH_MODE",
"METADATA_PROVIDER",
"METADATA_PROVIDER_AUDIOBOOK",
"DEFAULT_RELEASE_SOURCE",
]
field_keys = [field["key"] for field in data["fields"]]
assert set(field_keys) == set(data["keys"])
assert data["userOverrides"]["SEARCH_MODE"] == "universal"
assert data["userOverrides"]["METADATA_PROVIDER"] == "openlibrary"
assert data["userOverrides"]["DEFAULT_RELEASE_SOURCE"] == "prowlarr"
assert data["effective"]["SEARCH_MODE"]["source"] == "user_override"
assert data["effective"]["SEARCH_MODE"]["value"] == "universal"
assert data["effective"]["METADATA_PROVIDER"]["source"] == "user_override"
assert data["effective"]["METADATA_PROVIDER_AUDIOBOOK"]["source"] in {"global_config", "default"}
assert data["effective"]["DEFAULT_RELEASE_SOURCE"]["source"] == "user_override"
assert data["effective"]["DEFAULT_RELEASE_SOURCE"]["value"] == "prowlarr"
def test_returns_404_for_unknown_user(self, admin_client):
resp = admin_client.get("/api/admin/users/9999/search-preferences")
assert resp.status_code == 404
def test_requires_admin(self, regular_client, user_db):
user = user_db.create_user(username="alice")
resp = regular_client.get(f"/api/admin/users/{user['id']}/search-preferences")
assert resp.status_code == 403
# ---------------------------------------------------------------------------
# GET /api/admin/users/<id>/notification-preferences
# ---------------------------------------------------------------------------