From bfa47709b1db156227d5dc0b31e4ec26d82ba098 Mon Sep 17 00:00:00 2001 From: Flaminel Date: Sat, 17 Oct 2026 15:40:59 +0300 Subject: [PATCH] added live e2e for seeker wanted lists --- .../live-arr-fast/seeker-filters.api.spec.ts | 72 ++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/e2e/tests/live-arr-fast/seeker-filters.api.spec.ts b/e2e/tests/live-arr-fast/seeker-filters.api.spec.ts index 24e2197d..d179d9a6 100644 --- a/e2e/tests/live-arr-fast/seeker-filters.api.spec.ts +++ b/e2e/tests/live-arr-fast/seeker-filters.api.spec.ts @@ -23,7 +23,7 @@ import { TRIGGERED_SEARCH_TIMEOUT, type SeededArr, } from '../helpers/seeker-live'; -import { grabbableRelease, grabbableReleases } from '../helpers/mocks/torznab-stubs'; +import { grabbableRelease, grabbableReleases, torznabSearchStub } from '../helpers/mocks/torznab-stubs'; /** * The per-instance filters that decide whether the Seeker searches at all. @@ -162,6 +162,31 @@ test.describe('Seeker candidate filters', () => { await expectNoSearch(api, instanceId, SETTLE_MS); }); + test('searches an unmonitored movie once monitoredOnly is off', async ({ api }) => { + // The seed's other movie is unmonitored too, so it would also qualify once monitoredOnly + // is off. Tag it out of the run so only the movie this test unmonitors can be searched. + const skipTagId = await RADARR.arr.ensureTag(SKIP_TAG); + const others = (await RADARR.arr.get>('/api/v3/movie')).filter( + (movie) => movie.id !== RADARR.itemId, + ); + for (const other of others) { + const full = await RADARR.arr.get>(`/api/v3/movie/${other.id}`); + await RADARR.arr.put(`/api/v3/movie/${other.id}`, { ...full, tags: [skipTagId] }); + } + + const instanceId = await arrangeInstance(api, RADARR, { + instance: { monitoredOnly: false, skipTags: [SKIP_TAG] }, + }); + + await mutateMovie((movie) => ({ ...movie, monitored: false })); + await triggerSeeker(api); + + await expect.poll(async () => (await listSearchEvents(api, instanceId)).length, { timeout: SETTLE_MS }).toBe(1); + + const event = await firstSearchEvent(api, instanceId); + expect(event?.itemTitle).toContain(RADARR.seededTitle); + }); + test('skips a movie carrying a skipped tag', async ({ api }) => { const tagId = await RADARR.arr.ensureTag(SKIP_TAG); const instanceId = await arrangeInstance(api, RADARR, { instance: { skipTags: [SKIP_TAG] } }); @@ -351,3 +376,48 @@ test.describe('Seeker candidate filters', () => { expect(await listSearchEvents(api, instanceId)).toHaveLength(1); }); }); + +test.describe('Seeker monitored-only filters on Sonarr', () => { + /** + * Every episode in the seeded season is aired and fileless, so every one of + * them is a "missing" gap. Unmonitoring all of them (never just S01E01, + * whose id doubles as the series id and would hide an id mix-up) removes + * the only gap from the monitored=true list; it only reappears once + * monitoredOnly is off and the Seeker also reads the monitored=false list. + */ + + let library: Array> = []; + let episodeIds: number[] = []; + + test.beforeEach(async () => { + await resetLiveArrState(); + library = await snapshotLibrary(SONARR); + await indexerMock.stub(torznabSearchStub(SONARR.searchMode, [])); + + const episodes = await SONARR.arr.get>(`/api/v3/episode?seriesId=${SONARR.itemId}`); + episodeIds = episodes.map((episode) => episode.id); + await SONARR.arr.put('/api/v3/episode/monitor', { episodeIds, monitored: false }); + }); + + test.afterEach(async ({ api }) => { + await teardownInstances(api); + await SONARR.arr.put('/api/v3/episode/monitor', { episodeIds, monitored: true }); + await restoreLibrary(SONARR, library); + await resetLiveArrState(); + }); + + test('skips the season while its only gap is unmonitored', async ({ api }) => { + const instanceId = await arrangeInstance(api, SONARR, { instance: { monitoredOnly: true } }); + await expectNoSearch(api, instanceId, SETTLE_MS); + }); + + test('searches the season once monitoredOnly lets the unmonitored gap count', async ({ api }) => { + const instanceId = await arrangeInstance(api, SONARR, { instance: { monitoredOnly: false } }); + await triggerSeeker(api); + + await expect.poll(async () => (await listSearchEvents(api, instanceId)).length, { timeout: SETTLE_MS }).toBe(1); + + const event = await firstSearchEvent(api, instanceId); + expect(event?.itemTitle).toBe(`${SONARR.seededTitle} S01`); + }); +});