diff --git a/src/core/services/anilist/anilist-updater.test.ts b/src/core/services/anilist/anilist-updater.test.ts index 3b99e1c4..d5e961f9 100644 --- a/src/core/services/anilist/anilist-updater.test.ts +++ b/src/core/services/anilist/anilist-updater.test.ts @@ -605,3 +605,33 @@ test('updateAnilistPostWatchProgress returns error when search fails', async () globalThis.fetch = originalFetch; } }); + +test('updateAnilistPostWatchProgress does not requeue a search that matched nothing', async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = (async () => + createJsonResponse({ data: { Page: { media: [] } } })) as typeof fetch; + + try { + const result = await updateAnilistPostWatchProgress('token', 'Unknown Show', 3); + assert.equal(result.status, 'error'); + assert.equal(result.retryable, false); + assert.match(result.message, /no matches/i); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test('updateAnilistPostWatchProgress still allows retry when the search itself fails', async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = (async () => + createJsonResponse({ errors: [{ message: 'upstream exploded' }] })) as typeof fetch; + + try { + const result = await updateAnilistPostWatchProgress('token', 'Demo Show', 3); + assert.equal(result.status, 'error'); + assert.notEqual(result.retryable, false); + assert.match(result.message, /search failed/i); + } finally { + globalThis.fetch = originalFetch; + } +}); diff --git a/src/core/services/anilist/anilist-updater.ts b/src/core/services/anilist/anilist-updater.ts index bbb6da8f..21149fc2 100644 --- a/src/core/services/anilist/anilist-updater.ts +++ b/src/core/services/anilist/anilist-updater.ts @@ -305,7 +305,13 @@ export async function updateAnilistPostWatchProgress( } if (!resolution) { - return { status: 'error', message: 'AniList search returned no matches.' }; + // A well-formed search that matched nothing is deterministic for this title, so + // requeueing it just burns rate limit. Transient failures throw and are caught above. + return { + status: 'error', + retryable: false, + message: 'AniList search returned no matches.', + }; } if (!resolution.seasonResolved) { // Updating the season 1 entry here is worse than not updating at all.