Fix jellyfin sync config merging
This commit is contained in:
+2
-1
@@ -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
@@ -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
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user