Automate repair completion confirmation
Magent CI/CD / verify (push) Canceled after 4m31s
Magent CI/CD / deploy-prod (push) Canceled after 0s
Magent CI/CD / deploy-beta (push) Canceled after 0s

This commit is contained in:
2026-09-01 22:37:23 +12:00
parent ded794a819
commit 0b59289a2e
4 changed files with 413 additions and 6 deletions
+85 -2
View File
@@ -1575,6 +1575,8 @@ class MediaReplacementTests(unittest.IsolatedAsyncioTestCase):
radarr.search.assert_awaited_once_with(44)
add_activity.assert_called_once()
self.assertEqual(add_activity.call_args.kwargs["event_type"], "replacement_started")
self.assertIn('"repairTracking"', add_activity.call_args.kwargs["metadata_json"])
self.assertIn('"originalFileIds":[77]', add_activity.call_args.kwargs["metadata_json"])
update_issue.assert_called_once_with(12, status="in_progress", issue_resolved_at=None)
async def test_tv_replacement_options_return_only_safe_file_details(self) -> None:
@@ -1709,8 +1711,8 @@ class MediaReplacementTests(unittest.IsolatedAsyncioTestCase):
{"id": 89, "relativePath": "S01E02.mkv"},
]),
get_episodes=AsyncMock(return_value=[
{"id": 101, "episodeFileId": 88},
{"id": 102, "episodeFileId": 89},
{"id": 101, "episodeFileId": 88, "seasonNumber": 1, "episodeNumber": 1},
{"id": 102, "episodeFileId": 89, "seasonNumber": 1, "episodeNumber": 2},
]),
monitor_episodes=AsyncMock(return_value={"monitored": True}),
delete_episode_file=AsyncMock(return_value=None),
@@ -2182,6 +2184,87 @@ class IssueResolutionWorkflowTests(TempDatabaseMixin, unittest.IsolatedAsyncioTe
self.assertIsNotNone(closed["issue_resolved_at"])
self.assertEqual(db.list_portal_item_activity(int(issue["id"]))[-1]["event_type"], "resolution_confirmed")
async def test_replacement_waits_for_jellyfin_to_refresh_existing_movie(self) -> None:
tracking = {
"requestId": "144",
"actionId": "replace_media",
"mediaType": "movie",
"collectorId": 12,
"originalFileIds": [40],
"episodes": [],
"jellyfinFoundAtStart": True,
"jellyfinBaseline": [{"Id": "jf-1", "Etag": "old", "Path": "/movies/test.mkv"}],
}
unchanged = Snapshot(
request_id="144",
title="Test movie",
raw={
"arr": {"item": {"id": 12, "hasFile": True, "movieFile": {"id": 41}}},
"jellyfin": {
"found": True,
"item": {"Id": "jf-1", "Etag": "old", "Path": "/movies/test.mkv"},
},
},
)
refreshed = unchanged.model_copy(deep=True)
refreshed.raw["jellyfin"]["item"]["Etag"] = "new"
with patch.object(issue_resolution, "build_snapshot", new=AsyncMock(return_value=unchanged)):
waiting = await issue_resolution._media_repair_evidence(tracking)
with patch.object(issue_resolution, "build_snapshot", new=AsyncMock(return_value=refreshed)):
complete = await issue_resolution._media_repair_evidence(tracking)
self.assertFalse(waiting["complete"])
self.assertEqual(waiting["phase"], "indexing")
self.assertTrue(complete["complete"])
async def test_verified_media_repair_starts_confirmation_without_admin(self) -> None:
issue = self._create_issue()
db.add_portal_item_activity(
int(issue["id"]),
event_type="replacement_started",
actor_username="reporter",
actor_role="user",
message="Radarr started the replacement.",
metadata_json=(
'{"repairTracking":{"requestId":"144","actionId":"replace_media",'
'"mediaType":"movie","collectorId":12,"originalFileIds":[40],'
'"episodes":[],"jellyfinFoundAtStart":true,'
'"jellyfinBaseline":[{"Id":"jf-1","Etag":"old"}]}}'
),
)
with (
patch.object(
issue_resolution,
"_media_repair_evidence",
new=AsyncMock(
return_value={
"complete": True,
"phase": "complete",
"message": "Radarr imported the repaired movie and Jellyfin indexed it.",
}
),
),
patch.object(
issue_resolution,
"begin_issue_confirmation",
new=AsyncMock(return_value={"status": "awaiting_confirmation"}),
) as begin_confirmation,
):
result = await issue_resolution.process_active_media_repairs()
self.assertEqual(result["completed"], 1)
begin_confirmation.assert_awaited_once_with(
int(issue["id"]),
actor_username="Magent",
actor_role="system",
)
self.assertEqual(
db.list_portal_item_activity(int(issue["id"]))[-1]["event_type"],
"repair_verified",
)
async def test_zero_confirmation_emails_closes_issue_immediately(self) -> None:
issue = self._create_issue()
with patch.object(issue_resolution, "_workflow_settings", return_value=(0, 1, "days")):