Add provider update startup check
This commit is contained in:
@@ -23,6 +23,7 @@ from urllib.parse import quote, urljoin, urlparse
|
|||||||
|
|
||||||
import app_support
|
import app_support
|
||||||
import provider_bridge
|
import provider_bridge
|
||||||
|
import provider_updates
|
||||||
from app_support import (
|
from app_support import (
|
||||||
AGENT,
|
AGENT,
|
||||||
ANIDB_ANIME_PAGE,
|
ANIDB_ANIME_PAGE,
|
||||||
@@ -97,6 +98,8 @@ WATCHLIST_REFRESH = None
|
|||||||
WATCHLIST_AUTO_REFRESH = None
|
WATCHLIST_AUTO_REFRESH = None
|
||||||
WATCHLIST_JELLYFIN_SYNC = None
|
WATCHLIST_JELLYFIN_SYNC = None
|
||||||
WATCHLIST_BACKUP_FORMAT_VERSION = 1
|
WATCHLIST_BACKUP_FORMAT_VERSION = 1
|
||||||
|
PROVIDER_UPDATE_STATUS = provider_updates.default_status()
|
||||||
|
PROVIDER_UPDATE_CHECK_THREAD = None
|
||||||
|
|
||||||
|
|
||||||
def parse_iso_timestamp(value):
|
def parse_iso_timestamp(value):
|
||||||
@@ -3183,6 +3186,44 @@ def runtime_state():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_provider_update_status():
|
||||||
|
with RUNTIME_LOCK:
|
||||||
|
return json.loads(json.dumps(PROVIDER_UPDATE_STATUS))
|
||||||
|
|
||||||
|
|
||||||
|
def set_provider_update_status(status):
|
||||||
|
if not isinstance(status, dict):
|
||||||
|
return
|
||||||
|
with RUNTIME_LOCK:
|
||||||
|
global PROVIDER_UPDATE_STATUS
|
||||||
|
PROVIDER_UPDATE_STATUS = json.loads(json.dumps(status))
|
||||||
|
|
||||||
|
|
||||||
|
def run_provider_update_check():
|
||||||
|
try:
|
||||||
|
set_provider_update_status(provider_updates.check_provider_updates())
|
||||||
|
except Exception as exc:
|
||||||
|
debug_log("provider_updates.error", error=exc)
|
||||||
|
set_provider_update_status(provider_updates.error_status(exc))
|
||||||
|
|
||||||
|
|
||||||
|
def start_provider_update_check():
|
||||||
|
global PROVIDER_UPDATE_CHECK_THREAD
|
||||||
|
with RUNTIME_LOCK:
|
||||||
|
if PROVIDER_UPDATE_CHECK_THREAD is not None and PROVIDER_UPDATE_CHECK_THREAD.is_alive():
|
||||||
|
return PROVIDER_UPDATE_CHECK_THREAD
|
||||||
|
PROVIDER_UPDATE_STATUS.update(
|
||||||
|
provider_updates.default_status("checking", "Checking provider JavaScript for upstream updates...")
|
||||||
|
)
|
||||||
|
PROVIDER_UPDATE_CHECK_THREAD = threading.Thread(
|
||||||
|
target=run_provider_update_check,
|
||||||
|
name="provider-update-check",
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
PROVIDER_UPDATE_CHECK_THREAD.start()
|
||||||
|
return PROVIDER_UPDATE_CHECK_THREAD
|
||||||
|
|
||||||
|
|
||||||
def build_runtime(start_workers=None):
|
def build_runtime(start_workers=None):
|
||||||
ensure_project_app_root()
|
ensure_project_app_root()
|
||||||
worker_state = background_workers_enabled() if start_workers is None else bool(start_workers) and background_workers_enabled()
|
worker_state = background_workers_enabled() if start_workers is None else bool(start_workers) and background_workers_enabled()
|
||||||
@@ -3323,6 +3364,7 @@ Handler = build_handler_class(
|
|||||||
episode_list=episode_list,
|
episode_list=episode_list,
|
||||||
export_watchlist_backup=export_watchlist_backup,
|
export_watchlist_backup=export_watchlist_backup,
|
||||||
get_config_snapshot=get_config_snapshot,
|
get_config_snapshot=get_config_snapshot,
|
||||||
|
get_provider_update_status=get_provider_update_status,
|
||||||
get_watchlist_homepage_summary=get_watchlist_homepage_summary,
|
get_watchlist_homepage_summary=get_watchlist_homepage_summary,
|
||||||
get_jellyfin_sync_status=get_jellyfin_sync_status,
|
get_jellyfin_sync_status=get_jellyfin_sync_status,
|
||||||
get_watchlist=get_watchlist,
|
get_watchlist=get_watchlist,
|
||||||
@@ -3367,6 +3409,7 @@ def main():
|
|||||||
host = server_host()
|
host = server_host()
|
||||||
port = server_port()
|
port = server_port()
|
||||||
server = ThreadingHTTPServer((host, port), Handler)
|
server = ThreadingHTTPServer((host, port), Handler)
|
||||||
|
start_provider_update_check()
|
||||||
debug_log("server.start", version=VERSION, host=host, port=port, argv=sys.argv[1:])
|
debug_log("server.start", version=VERSION, host=host, port=port, argv=sys.argv[1:])
|
||||||
print(f"Serving {APP_NAME} {VERSION} on http://{host}:{port}/")
|
print(f"Serving {APP_NAME} {VERSION} on http://{host}:{port}/")
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -243,6 +243,14 @@ CONFIG_HTML = r"""<!doctype html>
|
|||||||
}
|
}
|
||||||
.badge.ok { color: #95e4ba; border-color: rgba(149, 228, 186, 0.24); }
|
.badge.ok { color: #95e4ba; border-color: rgba(149, 228, 186, 0.24); }
|
||||||
.badge.bad { color: #ffd0da; border-color: rgba(255, 144, 164, 0.22); }
|
.badge.bad { color: #ffd0da; border-color: rgba(255, 144, 164, 0.22); }
|
||||||
|
.badge.warn { color: #ffe6a8; border-color: rgba(255, 217, 118, 0.28); }
|
||||||
|
.provider-update-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.provider-update-list:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.field-hint {
|
.field-hint {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -466,6 +474,13 @@ CONFIG_HTML = r"""<!doctype html>
|
|||||||
</div>
|
</div>
|
||||||
<button class="ghost wide-button" id="openChangelogBtn" type="button">Changelog</button>
|
<button class="ghost wide-button" id="openChangelogBtn" type="button">Changelog</button>
|
||||||
</section>
|
</section>
|
||||||
|
<section class="settings" id="providerUpdatesPanel">
|
||||||
|
<h2>Provider updates</h2>
|
||||||
|
<div class="stack muted">
|
||||||
|
<p id="providerUpdatesLine">Checking upstream provider files...</p>
|
||||||
|
<div class="provider-update-list" id="providerUpdatesList"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
@@ -1084,6 +1099,52 @@ CONFIG_HTML = r"""<!doctype html>
|
|||||||
$("providerLine").textContent = `Providers: ${(data.providers || []).join(", ") || "Unavailable"}`;
|
$("providerLine").textContent = `Providers: ${(data.providers || []).join(", ") || "Unavailable"}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function providerUpdateBadgeClass(status) {
|
||||||
|
if (status === "up_to_date") return "ok";
|
||||||
|
if (status === "updates_available") return "warn";
|
||||||
|
if (status === "error") return "bad";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderProviderUpdates(data) {
|
||||||
|
const line = $("providerUpdatesLine");
|
||||||
|
const list = $("providerUpdatesList");
|
||||||
|
const status = data.status || "idle";
|
||||||
|
const checked = data.checked_files ? `${data.checked_files} checked` : "";
|
||||||
|
const checkedAt = data.checked_at ? ` · ${new Date(data.checked_at).toLocaleString()}` : "";
|
||||||
|
line.textContent = `${data.message || "Provider update status unavailable."}${checked ? ` · ${checked}` : ""}${checkedAt}`;
|
||||||
|
list.innerHTML = "";
|
||||||
|
|
||||||
|
const files = [
|
||||||
|
...(data.updated_files || []).map((item) => item.path),
|
||||||
|
...(data.missing_upstream_files || [])
|
||||||
|
];
|
||||||
|
if (!files.length && status !== "checking") {
|
||||||
|
const badge = document.createElement("span");
|
||||||
|
badge.className = `badge ${providerUpdateBadgeClass(status)}`;
|
||||||
|
badge.textContent = status === "up_to_date" ? "up to date" : status.replace(/_/g, " ");
|
||||||
|
list.appendChild(badge);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const path of files) {
|
||||||
|
const badge = document.createElement("span");
|
||||||
|
badge.className = `badge ${providerUpdateBadgeClass(status)}`;
|
||||||
|
badge.textContent = path;
|
||||||
|
list.appendChild(badge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadProviderUpdates() {
|
||||||
|
try {
|
||||||
|
renderProviderUpdates(await api("/api/provider-updates"));
|
||||||
|
} catch (error) {
|
||||||
|
renderProviderUpdates({
|
||||||
|
status: "error",
|
||||||
|
message: error.message || "Provider update status unavailable."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function openChangelog() {
|
async function openChangelog() {
|
||||||
try {
|
try {
|
||||||
changelogContent.textContent = "Loading changelog...";
|
changelogContent.textContent = "Loading changelog...";
|
||||||
@@ -1138,9 +1199,11 @@ CONFIG_HTML = r"""<!doctype html>
|
|||||||
await pollJellyfinSyncStatus(false);
|
await pollJellyfinSyncStatus(false);
|
||||||
await loadDeps();
|
await loadDeps();
|
||||||
await loadVersion();
|
await loadVersion();
|
||||||
|
await loadProviderUpdates();
|
||||||
window.setInterval(() => {
|
window.setInterval(() => {
|
||||||
pollJellyfinSyncStatus();
|
pollJellyfinSyncStatus();
|
||||||
}, 2500);
|
}, 2500);
|
||||||
|
window.setInterval(loadProviderUpdates, 10000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setNotice(error.message);
|
setNotice(error.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class HandlerContext:
|
|||||||
export_watchlist_backup: object
|
export_watchlist_backup: object
|
||||||
get_config_snapshot: object
|
get_config_snapshot: object
|
||||||
get_jellyfin_sync_status: object
|
get_jellyfin_sync_status: object
|
||||||
|
get_provider_update_status: object
|
||||||
get_watchlist_homepage_summary: object
|
get_watchlist_homepage_summary: object
|
||||||
get_watchlist: object
|
get_watchlist: object
|
||||||
get_watchlist_refresh_status: object
|
get_watchlist_refresh_status: object
|
||||||
@@ -602,6 +603,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.json({"content": load_project_text_file(CHANGELOG_FILE, default="Changelog unavailable.")})
|
self.json({"content": load_project_text_file(CHANGELOG_FILE, default="Changelog unavailable.")})
|
||||||
elif parsed.path == "/api/dependencies":
|
elif parsed.path == "/api/dependencies":
|
||||||
self.json(dependency_status())
|
self.json(dependency_status())
|
||||||
|
elif parsed.path == "/api/provider-updates":
|
||||||
|
self.json(Handler._context(self).get_provider_update_status())
|
||||||
elif parsed.path == "/api/fs/browse":
|
elif parsed.path == "/api/fs/browse":
|
||||||
params = parse_qs(parsed.query)
|
params = parse_qs(parsed.query)
|
||||||
client_host = Handler._effective_client_host(self)
|
client_host = Handler._effective_client_host(self)
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""Check bundled provider JavaScript against the upstream extensions repo."""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from app_support import AGENT, PROJECT_ROOT
|
||||||
|
|
||||||
|
|
||||||
|
UPSTREAM_REPO_URL = "https://github.com/TheYogMehta/extensions"
|
||||||
|
UPSTREAM_BRANCH = "main"
|
||||||
|
UPSTREAM_TREE_URL = (
|
||||||
|
f"https://api.github.com/repos/TheYogMehta/extensions/git/trees/{UPSTREAM_BRANCH}?recursive=1"
|
||||||
|
)
|
||||||
|
UPSTREAM_RAW_BASE = f"https://raw.githubusercontent.com/TheYogMehta/extensions/{UPSTREAM_BRANCH}/"
|
||||||
|
LOCAL_PROVIDER_ROOT = PROJECT_ROOT / "providers" / "extensions"
|
||||||
|
UPSTREAM_PROVIDER_PREFIX = "extensions/"
|
||||||
|
|
||||||
|
|
||||||
|
def now_iso():
|
||||||
|
return datetime.now(timezone.utc).isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def default_status(status="idle", message="Provider update check has not run yet."):
|
||||||
|
return {
|
||||||
|
"status": status,
|
||||||
|
"message": message,
|
||||||
|
"checked_at": "",
|
||||||
|
"upstream_url": UPSTREAM_REPO_URL,
|
||||||
|
"upstream_branch": UPSTREAM_BRANCH,
|
||||||
|
"upstream_tree_sha": "",
|
||||||
|
"checked_files": 0,
|
||||||
|
"updated_files": [],
|
||||||
|
"missing_upstream_files": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def local_provider_js_files(root=LOCAL_PROVIDER_ROOT):
|
||||||
|
root = Path(root)
|
||||||
|
if not root.exists():
|
||||||
|
return {}
|
||||||
|
files = {}
|
||||||
|
for path in sorted(root.rglob("*.js")):
|
||||||
|
if path.is_file():
|
||||||
|
files[path.relative_to(root).as_posix()] = path
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def sha256_bytes(content):
|
||||||
|
return hashlib.sha256(content).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_json(url, timeout=10):
|
||||||
|
request = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
headers={
|
||||||
|
"Accept": "application/vnd.github+json",
|
||||||
|
"User-Agent": AGENT,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||||
|
return json.loads(response.read().decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_bytes(url, timeout=10):
|
||||||
|
request = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
headers={
|
||||||
|
"Accept": "application/octet-stream",
|
||||||
|
"User-Agent": AGENT,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||||
|
return response.read()
|
||||||
|
|
||||||
|
|
||||||
|
def upstream_provider_js_paths(timeout=10):
|
||||||
|
payload = fetch_json(UPSTREAM_TREE_URL, timeout=timeout)
|
||||||
|
tree = payload.get("tree") if isinstance(payload, dict) else []
|
||||||
|
paths = set()
|
||||||
|
for item in tree or []:
|
||||||
|
path = str((item or {}).get("path") or "")
|
||||||
|
if (item or {}).get("type") == "blob" and path.startswith(UPSTREAM_PROVIDER_PREFIX) and path.endswith(".js"):
|
||||||
|
paths.add(path[len(UPSTREAM_PROVIDER_PREFIX) :])
|
||||||
|
return paths, str(payload.get("sha") or "") if isinstance(payload, dict) else ""
|
||||||
|
|
||||||
|
|
||||||
|
def check_provider_updates(local_root=LOCAL_PROVIDER_ROOT, timeout=10):
|
||||||
|
checked_at = now_iso()
|
||||||
|
local_files = local_provider_js_files(local_root)
|
||||||
|
if not local_files:
|
||||||
|
status = default_status("error", "No local provider JavaScript files were found.")
|
||||||
|
status["checked_at"] = checked_at
|
||||||
|
return status
|
||||||
|
|
||||||
|
upstream_paths, upstream_tree_sha = upstream_provider_js_paths(timeout=timeout)
|
||||||
|
updated_files = []
|
||||||
|
missing_upstream_files = []
|
||||||
|
checked_files = 0
|
||||||
|
|
||||||
|
for relative_path, local_path in local_files.items():
|
||||||
|
if relative_path not in upstream_paths:
|
||||||
|
missing_upstream_files.append(relative_path)
|
||||||
|
continue
|
||||||
|
checked_files += 1
|
||||||
|
local_content = local_path.read_bytes()
|
||||||
|
remote_url = f"{UPSTREAM_RAW_BASE}{UPSTREAM_PROVIDER_PREFIX}{relative_path}"
|
||||||
|
remote_content = fetch_bytes(remote_url, timeout=timeout)
|
||||||
|
local_hash = sha256_bytes(local_content)
|
||||||
|
remote_hash = sha256_bytes(remote_content)
|
||||||
|
if local_hash != remote_hash:
|
||||||
|
updated_files.append(
|
||||||
|
{
|
||||||
|
"path": relative_path,
|
||||||
|
"local_sha256": local_hash,
|
||||||
|
"upstream_sha256": remote_hash,
|
||||||
|
"upstream_url": remote_url,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
has_updates = bool(updated_files or missing_upstream_files)
|
||||||
|
if has_updates:
|
||||||
|
message = "Provider updates are available from TheYogMehta/extensions."
|
||||||
|
status_name = "updates_available"
|
||||||
|
else:
|
||||||
|
message = "Bundled provider JavaScript is up to date."
|
||||||
|
status_name = "up_to_date"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": status_name,
|
||||||
|
"message": message,
|
||||||
|
"checked_at": checked_at,
|
||||||
|
"upstream_url": UPSTREAM_REPO_URL,
|
||||||
|
"upstream_branch": UPSTREAM_BRANCH,
|
||||||
|
"upstream_tree_sha": upstream_tree_sha,
|
||||||
|
"checked_files": checked_files,
|
||||||
|
"updated_files": updated_files,
|
||||||
|
"missing_upstream_files": missing_upstream_files,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def error_status(exc):
|
||||||
|
message = str(exc)
|
||||||
|
if isinstance(exc, urllib.error.URLError) and getattr(exc, "reason", None):
|
||||||
|
message = str(exc.reason)
|
||||||
|
status = default_status("error", f"Provider update check failed: {message}")
|
||||||
|
status["checked_at"] = now_iso()
|
||||||
|
return status
|
||||||
+55
-1
@@ -24,6 +24,7 @@ import queue_jobs
|
|||||||
import http_handler
|
import http_handler
|
||||||
import provider_bridge
|
import provider_bridge
|
||||||
import provider_downloader
|
import provider_downloader
|
||||||
|
import provider_updates
|
||||||
|
|
||||||
os.environ.setdefault("KAIZOKU_DISABLE_WORKER", "1")
|
os.environ.setdefault("KAIZOKU_DISABLE_WORKER", "1")
|
||||||
os.environ["KAIZOKU_STATE_ROOT"] = TEMP_STATE.name
|
os.environ["KAIZOKU_STATE_ROOT"] = TEMP_STATE.name
|
||||||
@@ -3231,11 +3232,14 @@ class StartupBehaviorTests(unittest.TestCase):
|
|||||||
|
|
||||||
with mock.patch.object(APP, "initialize_runtime") as initialize_runtime, mock.patch.object(
|
with mock.patch.object(APP, "initialize_runtime") as initialize_runtime, mock.patch.object(
|
||||||
APP, "ThreadingHTTPServer", return_value=server
|
APP, "ThreadingHTTPServer", return_value=server
|
||||||
), mock.patch.object(APP, "shutdown_runtime") as shutdown_runtime:
|
), mock.patch.object(APP, "shutdown_runtime") as shutdown_runtime, mock.patch.object(
|
||||||
|
APP, "start_provider_update_check"
|
||||||
|
) as start_provider_update_check:
|
||||||
shutdown_runtime.return_value = True
|
shutdown_runtime.return_value = True
|
||||||
APP.main()
|
APP.main()
|
||||||
|
|
||||||
initialize_runtime.assert_not_called()
|
initialize_runtime.assert_not_called()
|
||||||
|
start_provider_update_check.assert_called_once()
|
||||||
shutdown_runtime.assert_called_once_with(wait=True, cancel_active_downloads=True)
|
shutdown_runtime.assert_called_once_with(wait=True, cancel_active_downloads=True)
|
||||||
server.serve_forever.assert_called_once()
|
server.serve_forever.assert_called_once()
|
||||||
server.server_close.assert_called_once()
|
server.server_close.assert_called_once()
|
||||||
@@ -3344,6 +3348,56 @@ class StartupBehaviorTests(unittest.TestCase):
|
|||||||
APP.initialize_runtime(start_workers=False)
|
APP.initialize_runtime(start_workers=False)
|
||||||
|
|
||||||
|
|
||||||
|
class ProviderUpdateTests(unittest.TestCase):
|
||||||
|
def test_provider_update_check_reports_up_to_date_files(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
provider_file = root / "Anime" / "pahe.js"
|
||||||
|
provider_file.parent.mkdir(parents=True)
|
||||||
|
provider_file.write_text("module.exports = {};\n", encoding="utf-8")
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
provider_updates, "upstream_provider_js_paths", return_value=({"Anime/pahe.js"}, "tree-sha")
|
||||||
|
), mock.patch.object(provider_updates, "fetch_bytes", return_value=provider_file.read_bytes()):
|
||||||
|
status = provider_updates.check_provider_updates(local_root=root)
|
||||||
|
|
||||||
|
self.assertEqual(status["status"], "up_to_date")
|
||||||
|
self.assertEqual(status["checked_files"], 1)
|
||||||
|
self.assertEqual(status["updated_files"], [])
|
||||||
|
self.assertEqual(status["upstream_tree_sha"], "tree-sha")
|
||||||
|
|
||||||
|
def test_provider_update_check_reports_changed_files(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
provider_file = root / "Anime" / "pahe.js"
|
||||||
|
provider_file.parent.mkdir(parents=True)
|
||||||
|
provider_file.write_text("local version\n", encoding="utf-8")
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
provider_updates, "upstream_provider_js_paths", return_value=({"Anime/pahe.js"}, "tree-sha")
|
||||||
|
), mock.patch.object(provider_updates, "fetch_bytes", return_value=b"upstream version\n"):
|
||||||
|
status = provider_updates.check_provider_updates(local_root=root)
|
||||||
|
|
||||||
|
self.assertEqual(status["status"], "updates_available")
|
||||||
|
self.assertEqual(status["checked_files"], 1)
|
||||||
|
self.assertEqual([item["path"] for item in status["updated_files"]], ["Anime/pahe.js"])
|
||||||
|
|
||||||
|
def test_provider_update_api_returns_current_status(self):
|
||||||
|
status = provider_updates.default_status("updates_available", "Providers changed.")
|
||||||
|
status["checked_files"] = 3
|
||||||
|
status["updated_files"] = [{"path": "Anime/pahe.js"}]
|
||||||
|
APP.set_provider_update_status(status)
|
||||||
|
handler = DummyHandler("/api/provider-updates")
|
||||||
|
handler.command = "GET"
|
||||||
|
|
||||||
|
APP.Handler.do_GET(handler)
|
||||||
|
|
||||||
|
self.assertEqual(handler.json_status, HTTPStatus.OK)
|
||||||
|
self.assertEqual(handler.json_payload["status"], "updates_available")
|
||||||
|
self.assertEqual(handler.json_payload["checked_files"], 3)
|
||||||
|
self.assertEqual(handler.json_payload["updated_files"][0]["path"], "Anime/pahe.js")
|
||||||
|
|
||||||
|
|
||||||
class HandlerRouteTests(unittest.TestCase):
|
class HandlerRouteTests(unittest.TestCase):
|
||||||
def test_runtime_backed_route_respects_worker_disable_flag(self):
|
def test_runtime_backed_route_respects_worker_disable_flag(self):
|
||||||
handler = DummyHandler("/api/watchlist/refresh-status")
|
handler = DummyHandler("/api/watchlist/refresh-status")
|
||||||
|
|||||||
Reference in New Issue
Block a user