Fix queue worker failure handling and thumbnail fallbacks
This commit is contained in:
+120
@@ -225,6 +225,48 @@ class DownloadQueueCancelTests(unittest.TestCase):
|
||||
killpg.assert_called_once_with(4321, queue_jobs.signal.SIGTERM)
|
||||
|
||||
|
||||
class DownloadQueueWorkerFailureTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
queue = APP.DownloadQueue(lambda: {"mode": "sub", "quality": "best", "download_dir": "/tmp/example"}, start_worker=False)
|
||||
with queue._connect() as conn:
|
||||
conn.execute("DELETE FROM jobs")
|
||||
|
||||
def test_run_job_marks_startup_failure_and_cleans_staging(self):
|
||||
queue = APP.DownloadQueue(lambda: {"mode": "sub", "quality": "best", "download_dir": "/tmp/example"}, start_worker=False)
|
||||
job = queue.add({"query": "Queue Show", "title": "Queue Show", "season": "1", "episodes": "1"})
|
||||
staging_dir = queue_jobs.job_staging_dir(job)
|
||||
|
||||
with mock.patch.object(queue_jobs.subprocess, "Popen", side_effect=FileNotFoundError("ani-cli missing")):
|
||||
queue._run_job(job)
|
||||
|
||||
stored = queue._find(job["id"])
|
||||
self.assertEqual(stored["status"], "failed")
|
||||
self.assertEqual(stored["exit_code"], 1)
|
||||
self.assertIn("Could not start download", stored["log"][-1])
|
||||
self.assertFalse(staging_dir.exists())
|
||||
|
||||
def test_failed_download_cleans_staging_dir(self):
|
||||
queue = APP.DownloadQueue(lambda: {"mode": "sub", "quality": "best", "download_dir": "/tmp/example"}, start_worker=False)
|
||||
job = queue.add({"query": "Queue Show", "title": "Queue Show", "season": "1", "episodes": "1"})
|
||||
staging_dir = queue_jobs.job_staging_dir(job)
|
||||
staging_dir.mkdir(parents=True, exist_ok=True)
|
||||
(staging_dir / "partial.mp4").write_text("partial", encoding="utf-8")
|
||||
|
||||
class FakeProc:
|
||||
pid = 4321
|
||||
stdout = []
|
||||
|
||||
def wait(self):
|
||||
return 1
|
||||
|
||||
with mock.patch.object(queue_jobs.subprocess, "Popen", return_value=FakeProc()):
|
||||
queue._run_job(job)
|
||||
|
||||
stored = queue._find(job["id"])
|
||||
self.assertEqual(stored["status"], "failed")
|
||||
self.assertFalse(staging_dir.exists())
|
||||
|
||||
|
||||
class WatchlistRefreshAllTests(unittest.TestCase):
|
||||
def test_refresh_all_iterates_every_show_id(self):
|
||||
store = object.__new__(APP.WatchlistStore)
|
||||
@@ -475,6 +517,84 @@ class ThumbnailEventRecoveryTests(unittest.TestCase):
|
||||
|
||||
self.assertIsNone(second)
|
||||
|
||||
def test_ensure_thumbnail_falls_back_from_stale_animeschedule_route(self):
|
||||
now = APP.now_iso()
|
||||
item = {
|
||||
"show_id": "show-thumb-2",
|
||||
"title": "Route Fallback Show",
|
||||
"category": "watching",
|
||||
"downloaded": False,
|
||||
"status": "tracked",
|
||||
"status_message": "Waiting for thumbnail.",
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
"last_checked": None,
|
||||
"sub_count": 0,
|
||||
"dub_count": 0,
|
||||
"expected_count": 0,
|
||||
"airing_status": None,
|
||||
"sub_latest_episode": None,
|
||||
"dub_latest_episode": None,
|
||||
"animeschedule_route": "stale-route",
|
||||
"animeschedule_title": "Route Fallback Show",
|
||||
"anidb_aid": None,
|
||||
"anidb_title": None,
|
||||
"thumbnail_path": None,
|
||||
"thumbnail_checked_at": None,
|
||||
}
|
||||
with APP.WATCHLIST.lock, APP.WATCHLIST._connect() as conn:
|
||||
APP.WATCHLIST._upsert_conn(conn, item)
|
||||
|
||||
with mock.patch.object(APP, "fetch_animeschedule_cover_by_route", side_effect=RuntimeError("stale route")), mock.patch.object(
|
||||
APP, "fetch_animeschedule_cover", return_value={"route": "fresh-route", "title": "Route Fallback Show", "url": "https://example.com/cover.jpg"}
|
||||
) as title_lookup, mock.patch.object(APP, "cache_thumbnail_image", return_value="show-thumb-2.jpg"):
|
||||
result = APP.WATCHLIST.ensure_thumbnail("show-thumb-2")
|
||||
|
||||
title_lookup.assert_called_once_with("Route Fallback Show")
|
||||
self.assertEqual(result, APP.THUMBNAIL_ROOT / "show-thumb-2.jpg")
|
||||
refreshed = APP.WATCHLIST.get("show-thumb-2")
|
||||
self.assertEqual(refreshed["animeschedule_route"], "fresh-route")
|
||||
|
||||
def test_ensure_thumbnail_falls_back_from_stale_anidb_aid(self):
|
||||
now = APP.now_iso()
|
||||
item = {
|
||||
"show_id": "show-thumb-3",
|
||||
"title": "AniDB Fallback Show",
|
||||
"category": "watching",
|
||||
"downloaded": False,
|
||||
"status": "tracked",
|
||||
"status_message": "Waiting for thumbnail.",
|
||||
"created_at": now,
|
||||
"updated_at": now,
|
||||
"last_checked": None,
|
||||
"sub_count": 0,
|
||||
"dub_count": 0,
|
||||
"expected_count": 0,
|
||||
"airing_status": None,
|
||||
"sub_latest_episode": None,
|
||||
"dub_latest_episode": None,
|
||||
"animeschedule_route": None,
|
||||
"animeschedule_title": None,
|
||||
"anidb_aid": "12345",
|
||||
"anidb_title": "AniDB Fallback Show",
|
||||
"thumbnail_path": None,
|
||||
"thumbnail_checked_at": None,
|
||||
}
|
||||
with APP.WATCHLIST.lock, APP.WATCHLIST._connect() as conn:
|
||||
APP.WATCHLIST._upsert_conn(conn, item)
|
||||
|
||||
with mock.patch.object(APP, "fetch_animeschedule_cover", side_effect=RuntimeError("animeschedule failed")), mock.patch.object(
|
||||
APP, "fetch_anidb_cover_by_aid", side_effect=RuntimeError("stale aid")
|
||||
), mock.patch.object(
|
||||
APP, "fetch_anidb_cover", return_value={"aid": "67890", "title": "AniDB Fallback Show", "url": "https://example.com/cover.jpg"}
|
||||
) as title_lookup, mock.patch.object(APP, "cache_thumbnail_image", return_value="show-thumb-3.jpg"):
|
||||
result = APP.WATCHLIST.ensure_thumbnail("show-thumb-3")
|
||||
|
||||
title_lookup.assert_called_once_with("AniDB Fallback Show")
|
||||
self.assertEqual(result, APP.THUMBNAIL_ROOT / "show-thumb-3.jpg")
|
||||
refreshed = APP.WATCHLIST.get("show-thumb-3")
|
||||
self.assertEqual(refreshed["anidb_aid"], "67890")
|
||||
|
||||
|
||||
class WatchlistCompletionTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
||||
Reference in New Issue
Block a user