Harden queue matching and retry behavior

This commit is contained in:
Dymas
2026-05-26 00:02:02 +02:00
parent 1ceb773f99
commit b72d93268c
8 changed files with 114 additions and 40 deletions
+86 -7
View File
@@ -301,6 +301,51 @@ class QueueApiTests(unittest.TestCase):
self.assertEqual(handler.json_status, HTTPStatus.OK)
self.assertEqual(handler.json_payload["id"], created["id"])
def test_retry_rejects_done_jobs(self):
created = self.queue.add(
{
"query": "Done Show",
"title": "Done Show",
"anime_name": "Done Show",
"mode": "sub",
"quality": "best",
"episodes": "1",
"download_dir": "/tmp/example",
"season": "1",
}
)
with self.queue.lock:
job = self.queue._find(created["id"])
job["status"] = "done"
self.queue._save_job_locked(job)
with self.assertRaisesRegex(ValueError, "Only failed or canceled jobs can be retried"):
self.queue.retry(created["id"])
def test_retry_allows_canceled_jobs(self):
created = self.queue.add(
{
"query": "Canceled Show",
"title": "Canceled Show",
"anime_name": "Canceled Show",
"mode": "sub",
"quality": "best",
"episodes": "1",
"download_dir": "/tmp/example",
"season": "1",
}
)
with self.queue.lock:
job = self.queue._find(created["id"])
job["status"] = "canceled"
job["cancel_requested"] = True
self.queue._save_job_locked(job)
retried = self.queue.retry(created["id"])
self.assertEqual(retried["status"], "pending")
self.assertFalse(retried["cancel_requested"])
def test_save_job_splits_log_and_extra_payload_columns(self):
queue = APP.DownloadQueue(lambda: {"mode": "sub", "quality": "best", "download_dir": "/tmp/example"}, start_worker=False)
job = {
@@ -1326,6 +1371,19 @@ class WatchlistCompletionTests(unittest.TestCase):
self.assertNotIn("-S", APP.app_support.command_for_job(job))
def test_command_for_job_ignores_legacy_result_index(self):
command = APP.app_support.command_for_job(
{
"query": "Queue Show",
"quality": "best",
"episodes": "1-10",
"mode": "dub",
"result_index": 2,
}
)
self.assertNotIn("-S", command)
def test_download_watchlist_item_queues_full_episode_range_for_selected_mode(self):
item = {
"show_id": "show-42",
@@ -1337,9 +1395,7 @@ class WatchlistCompletionTests(unittest.TestCase):
job = {"id": "job-1", "show_id": "show-42", "episodes": "1-12", "mode": "dub"}
with mock.patch.object(APP, "ensure_runtime", return_value={"watchlist": APP.WATCHLIST, "download_queue": mock.Mock(), "config": {"quality": "best", "download_dir": "/tmp/downloads"}}), mock.patch.object(
APP.WATCHLIST, "get", return_value=item
), mock.patch.object(APP, "search_anime", return_value=[{"id": "show-42", "title": "Queue Show", "query": "Queue Show", "index": 3}]), mock.patch.object(
APP, "episode_list", return_value=["1", "2", "12"]
):
), mock.patch.object(APP, "episode_list", return_value=["1", "2", "12"]):
runtime = APP.ensure_runtime()
runtime["download_queue"].add.return_value = job
result = APP.download_watchlist_item("show-42", "dub")
@@ -1362,6 +1418,30 @@ class WatchlistCompletionTests(unittest.TestCase):
self.assertEqual(result["job"], job)
self.assertIn("Queued Queue Show for dub download.", result["message"])
def test_download_watchlist_item_does_not_require_search_match(self):
item = {
"show_id": "show-42",
"title": "Queue Show",
"auto_download_name": "Queue Show",
"auto_download_series": "1",
"media_type": "tv",
}
runtime = {
"watchlist": APP.WATCHLIST,
"download_queue": mock.Mock(),
"config": {"quality": "best", "download_dir": "/tmp/downloads"},
}
runtime["download_queue"].add.return_value = {"id": "job-1"}
with mock.patch.object(APP, "ensure_runtime", return_value=runtime), mock.patch.object(
APP.WATCHLIST, "get", return_value=item
), mock.patch.object(APP, "search_anime", side_effect=AssertionError("search should not be required")), mock.patch.object(
APP, "episode_list", return_value=["1", "2"]
):
result = APP.download_watchlist_item("show-42", "dub")
self.assertEqual(result["job"]["id"], "job-1")
class JellyfinSyncTests(unittest.TestCase):
def setUp(self):
@@ -2048,7 +2128,7 @@ class AutoDownloadQueueTests(unittest.TestCase):
with mock.patch.object(APP, "ensure_runtime", return_value=runtime), mock.patch.object(
APP,
"search_anime",
return_value=[{"id": "show-42", "title": "Queue Show", "query": "Queue Show", "index": 2}],
side_effect=AssertionError("search should not be required"),
):
result = APP.queue_watchlist_auto_download(item, refresh_source="scheduled")
@@ -2102,7 +2182,7 @@ class AutoDownloadQueueTests(unittest.TestCase):
with mock.patch.object(APP, "ensure_runtime", return_value=runtime), mock.patch.object(
APP,
"search_anime",
return_value=[{"id": "show-99", "title": "Backlog Show", "query": "Backlog Show", "index": 1}],
side_effect=AssertionError("search should not be required"),
):
result = APP.queue_watchlist_auto_download(item, refresh_source="manual")
@@ -2156,7 +2236,7 @@ class AutoDownloadQueueTests(unittest.TestCase):
with mock.patch.object(APP, "ensure_runtime", return_value=runtime), mock.patch.object(
APP,
"search_anime",
return_value=[{"id": "show-77", "title": "Split Season Show", "query": "Split Season Show", "index": 1}],
side_effect=AssertionError("search should not be required"),
):
result = APP.queue_watchlist_auto_download(item, refresh_source="manual")
@@ -2177,7 +2257,6 @@ class AutoDownloadQueueTests(unittest.TestCase):
}
)
class StartupBehaviorTests(unittest.TestCase):
def test_main_does_not_eagerly_initialize_runtime(self):
server = mock.Mock()