Fix jellyfin sync config merging

This commit is contained in:
Dymas
2026-05-26 11:42:21 +02:00
parent 4c430e1982
commit bf4fdc79f5
3 changed files with 44 additions and 5 deletions
+2 -1
View File
@@ -757,9 +757,10 @@ CONFIG_HTML = r"""<!doctype html>
const rootGroup = document.createElement("div"); const rootGroup = document.createElement("div");
rootGroup.className = "browser-roots"; rootGroup.className = "browser-roots";
for (const rootPath of roots) { for (const rootPath of roots) {
const active = data.path === rootPath || String(data.path || "").startsWith(`${rootPath}/`);
const button = document.createElement("button"); const button = document.createElement("button");
button.type = "button"; button.type = "button";
button.className = `ghost${rootPath === data.path ? " active" : ""}`; button.className = `ghost${active ? " active" : ""}`;
button.textContent = rootPath; button.textContent = rootPath;
button.addEventListener("click", () => loadPathBrowser(rootPath)); button.addEventListener("click", () => loadPathBrowser(rootPath));
rootGroup.appendChild(button); rootGroup.appendChild(button);
+6 -3
View File
@@ -629,14 +629,17 @@ class Handler(BaseHTTPRequestHandler):
Handler.update_config(self, Handler.require_json_object(self, self.body_json())) Handler.update_config(self, Handler.require_json_object(self, self.body_json()))
elif parsed.path == "/api/config/jellyfin/sync": elif parsed.path == "/api/config/jellyfin/sync":
payload = Handler.require_json_object(self, self.body_json()) payload = Handler.require_json_object(self, self.body_json())
current = Handler._context(self).get_config_snapshot()
merged = dict(current)
merged.update(payload)
for key, label in ( for key, label in (
("download_dir", "download directory"), ("download_dir", "download directory"),
("jellyfin_tv_dir", "Jellyfin TV directory"), ("jellyfin_tv_dir", "Jellyfin TV directory"),
("jellyfin_movie_dir", "Jellyfin movie directory"), ("jellyfin_movie_dir", "Jellyfin movie directory"),
): ):
if str(payload.get(key) or "").strip(): if str(merged.get(key) or "").strip():
Handler.validate_remote_path(self, payload[key], label) Handler.validate_remote_path(self, merged[key], label)
self.json(Handler._context(self).run_jellyfin_sync(payload)) self.json(Handler._context(self).run_jellyfin_sync(merged))
elif parsed.path == "/api/config/webhook/test": elif parsed.path == "/api/config/webhook/test":
payload = Handler.require_json_object(self, self.body_json()) payload = Handler.require_json_object(self, self.body_json())
validate_discord_webhook_url(payload.get("discord_webhook_url")) validate_discord_webhook_url(payload.get("discord_webhook_url"))
+36 -1
View File
@@ -3023,11 +3023,45 @@ class HandlerRouteTests(unittest.TestCase):
body = b'{"download_dir":"/tmp/example","jellyfin_sync_enabled":true,"jellyfin_tv_dir":"/tmp/jellyfin-tv","jellyfin_movie_dir":"/tmp/jellyfin-movies"}' body = b'{"download_dir":"/tmp/example","jellyfin_sync_enabled":true,"jellyfin_tv_dir":"/tmp/jellyfin-tv","jellyfin_movie_dir":"/tmp/jellyfin-movies"}'
handler = DummyHandler("/api/config/jellyfin/sync", body=body, content_length=len(body)) handler = DummyHandler("/api/config/jellyfin/sync", body=body, content_length=len(body))
payload = {"message": "Moved 1 watchlist entry to Jellyfin libraries.", "moved": 1, "total": 2, "items": []} payload = {"message": "Moved 1 watchlist entry to Jellyfin libraries.", "moved": 1, "total": 2, "items": []}
handler.handler_context = mock.Mock(run_jellyfin_sync=mock.Mock(return_value=payload)) handler.handler_context = mock.Mock(
get_config_snapshot=mock.Mock(return_value={}),
run_jellyfin_sync=mock.Mock(return_value=payload),
http_error=APP.HttpError,
)
APP.Handler.do_POST(handler) APP.Handler.do_POST(handler)
self.assertEqual(handler.json_payload, payload) self.assertEqual(handler.json_payload, payload)
self.assertEqual(handler.json_status, HTTPStatus.OK) self.assertEqual(handler.json_status, HTTPStatus.OK)
def test_jellyfin_sync_post_merges_partial_payload_with_saved_config(self):
body = b'{"jellyfin_sync_enabled":true}'
handler = DummyHandler("/api/config/jellyfin/sync", body=body, content_length=len(body))
payload = {"message": "Moved 0 watchlist entries to Jellyfin libraries.", "moved": 0, "total": 0, "items": []}
handler.handler_context = mock.Mock(
get_config_snapshot=mock.Mock(
return_value={
"download_dir": "/tmp/example",
"mode": "sub",
"quality": "best",
"jellyfin_sync_enabled": False,
"jellyfin_tv_dir": "/tmp/jellyfin-tv",
"jellyfin_movie_dir": "/tmp/jellyfin-movies",
}
),
run_jellyfin_sync=mock.Mock(return_value=payload),
)
APP.Handler.do_POST(handler)
self.assertEqual(handler.json_status, HTTPStatus.OK)
handler.handler_context.run_jellyfin_sync.assert_called_once_with(
{
"download_dir": "/tmp/example",
"mode": "sub",
"quality": "best",
"jellyfin_sync_enabled": True,
"jellyfin_tv_dir": "/tmp/jellyfin-tv",
"jellyfin_movie_dir": "/tmp/jellyfin-movies",
}
)
def test_generic_handler_exception_skips_traceback_when_debug_disabled(self): def test_generic_handler_exception_skips_traceback_when_debug_disabled(self):
handler = DummyHandler("/api/config") handler = DummyHandler("/api/config")
with mock.patch.object(http_handler, "debug_enabled", return_value=False), mock.patch.object( with mock.patch.object(http_handler, "debug_enabled", return_value=False), mock.patch.object(
@@ -3105,6 +3139,7 @@ class TemplateHelperTests(unittest.TestCase):
self.assertIn('id="pathBrowserOverlay"', APP.CONFIG_HTML) self.assertIn('id="pathBrowserOverlay"', APP.CONFIG_HTML)
self.assertIn('/api/fs/browse?path=${encodeURIComponent(path || state.pathBrowser.path || "")}', APP.CONFIG_HTML) self.assertIn('/api/fs/browse?path=${encodeURIComponent(path || state.pathBrowser.path || "")}', APP.CONFIG_HTML)
self.assertIn('rootGroup.className = "browser-roots";', APP.CONFIG_HTML) self.assertIn('rootGroup.className = "browser-roots";', APP.CONFIG_HTML)
self.assertIn('const active = data.path === rootPath || String(data.path || "").startsWith(`${rootPath}/`);', APP.CONFIG_HTML)
def test_search_page_sends_watchlist_auto_download_series(self): def test_search_page_sends_watchlist_auto_download_series(self):
self.assertIn('auto_download_series: $("seasonInput").value || "1"', APP.INDEX_HTML) self.assertIn('auto_download_series: $("seasonInput").value || "1"', APP.INDEX_HTML)