mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 18:58:59 -04:00
added whisparr v2 seeker e2e tests
This commit is contained in:
1 parent
e27ce94209
commit
981185e56f
5 files changed
+190
-13
No files matched your search
@@ -5,6 +5,7 @@ import {
|
||||
RADARR,
|
||||
SONARR,
|
||||
WHISPARR,
|
||||
WHISPARR2,
|
||||
TRANSITION_TIMEOUT,
|
||||
TRIGGERED_SEARCH_TIMEOUT,
|
||||
arrangeInstance,
|
||||
@@ -44,8 +45,8 @@ test.describe('Seeker grab on demand', () => {
|
||||
await resetLiveArrState();
|
||||
});
|
||||
|
||||
for (const target of [SONARR, RADARR, WHISPARR, LIDARR]) {
|
||||
test(`records the real ${target.type} grab on the search event`, async ({ api }) => {
|
||||
for (const target of [SONARR, RADARR, WHISPARR, WHISPARR2, LIDARR]) {
|
||||
test(`records the real ${target.type} v${target.version} grab on the search event`, async ({ api }) => {
|
||||
test.setTimeout(TEST_TIMEOUT);
|
||||
|
||||
const startedAt = new Date().toISOString();
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
RADARR,
|
||||
SONARR,
|
||||
WHISPARR,
|
||||
WHISPARR2,
|
||||
addDecoyDownloads,
|
||||
arrangeInstance,
|
||||
arrangeQueueCleaner,
|
||||
@@ -528,6 +529,58 @@ test.describe('Seeker candidate filters on Whisparr v3', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Seeker candidate filters on Whisparr v2', () => {
|
||||
let library: Array<Record<string, unknown>> = [];
|
||||
let scenes: Array<{ id: number; monitored: boolean }> = [];
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await resetLiveArrState();
|
||||
library = await snapshotLibrary(WHISPARR2);
|
||||
scenes = await WHISPARR2.arr.get<Array<{ id: number; monitored: boolean }>>(
|
||||
`/api/v3/episode?seriesId=${WHISPARR2.itemId}`,
|
||||
);
|
||||
await indexerMock.stub(torznabSearchStub(WHISPARR2.searchMode, []));
|
||||
});
|
||||
|
||||
test.afterEach(async ({ api }) => {
|
||||
await teardownInstances(api);
|
||||
for (const monitored of [true, false]) {
|
||||
const episodeIds = scenes.filter((scene) => scene.monitored === monitored).map((scene) => scene.id);
|
||||
if (episodeIds.length > 0) {
|
||||
await WHISPARR2.arr.put('/api/v3/episode/monitor', { episodeIds, monitored });
|
||||
}
|
||||
}
|
||||
await restoreLibrary(WHISPARR2, library);
|
||||
await resetLiveArrState();
|
||||
});
|
||||
|
||||
test('searches the seeded site when nothing filters it out', async ({ api }) => {
|
||||
const instanceId = await arrangeInstance(api, WHISPARR2);
|
||||
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(WHISPARR2.seededTitle);
|
||||
});
|
||||
|
||||
test('skips a site carrying a skipped tag', async ({ api }) => {
|
||||
const tagId = await WHISPARR2.arr.ensureTag(SKIP_TAG);
|
||||
const instanceId = await arrangeInstance(api, WHISPARR2, { instance: { skipTags: [SKIP_TAG] } });
|
||||
|
||||
const site = await WHISPARR2.arr.get<Record<string, unknown>>(`/api/v3/series/${WHISPARR2.itemId}`);
|
||||
await WHISPARR2.arr.put(`/api/v3/series/${WHISPARR2.itemId}`, { ...site, tags: [tagId] });
|
||||
await expectNoSearch(api, instanceId, SETTLE_MS);
|
||||
});
|
||||
|
||||
test('skips the site while every scene is unmonitored and monitoredOnly is on', async ({ api }) => {
|
||||
const instanceId = await arrangeInstance(api, WHISPARR2, { instance: { monitoredOnly: true } });
|
||||
|
||||
await WHISPARR2.arr.put('/api/v3/episode/monitor', { episodeIds: scenes.map((scene) => scene.id), monitored: false });
|
||||
await expectNoSearch(api, instanceId, SETTLE_MS);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Seeker candidate filters on Lidarr', () => {
|
||||
let library: Array<Record<string, unknown>> = [];
|
||||
let albums: Array<Record<string, unknown>> = [];
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
RUN_TAG,
|
||||
SONARR,
|
||||
WHISPARR,
|
||||
WHISPARR2,
|
||||
TRIGGERED_SEARCH_TIMEOUT,
|
||||
arrangeInstance,
|
||||
listSearchEvents,
|
||||
@@ -203,6 +204,51 @@ test.describe('Seeker preview on Whisparr v3', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Seeker preview on Whisparr v2', () => {
|
||||
const { yearSearch, singleScene } = TEST_CONFIG.whisparr2Seed;
|
||||
|
||||
let library: Array<Record<string, unknown>> = [];
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await resetLiveArrState();
|
||||
library = await snapshotLibrary(WHISPARR2);
|
||||
await indexerMock.stub(torznabSearchStub(WHISPARR2.searchMode, []));
|
||||
});
|
||||
|
||||
test.afterEach(async ({ api }) => {
|
||||
await teardownInstances(api);
|
||||
await WHISPARR2.arr.put('/api/v3/episode/monitor', {
|
||||
episodeIds: yearSearch.scenes.map((scene) => scene.id),
|
||||
monitored: true,
|
||||
});
|
||||
await restoreLibrary(WHISPARR2, library);
|
||||
await resetLiveArrState();
|
||||
});
|
||||
|
||||
test('lists the seeded site by year and scene dates with funnel counts', async ({ api }) => {
|
||||
const instanceId = await arrangeInstance(api, WHISPARR2);
|
||||
|
||||
const preview = await getPreview(api, instanceId);
|
||||
|
||||
expect(preview.nextUp.map((u) => u.name)).toContain(`${WHISPARR2.seededTitle} ${yearSearch.year}`);
|
||||
expect(preview.funnel.libraryItems).toBe(library.length);
|
||||
expect(preview.funnel.eligibleUnits).toBeGreaterThan(0);
|
||||
expect(await listSearchEvents(api, instanceId)).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('lists a single scene by its release date once the year is not fully wanted', async ({ api }) => {
|
||||
await WHISPARR2.arr.put('/api/v3/episode/monitor', {
|
||||
episodeIds: yearSearch.scenes.map((scene) => scene.id),
|
||||
monitored: false,
|
||||
});
|
||||
const instanceId = await arrangeInstance(api, WHISPARR2);
|
||||
|
||||
const preview = await getPreview(api, instanceId);
|
||||
|
||||
expect(preview.nextUp.map((u) => u.name)).toContain(`${WHISPARR2.seededTitle} ${singleScene.monitored.releaseDate}`);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Seeker preview on Lidarr', () => {
|
||||
let library: Array<Record<string, unknown>> = [];
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { rmSync, truncateSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { test, expect } from '../fixtures/base';
|
||||
import { test, expect, TEST_CONFIG } from '../fixtures/base';
|
||||
import type { CleanuparrApi } from '../helpers/api';
|
||||
import { indexerMock, type LiveArrCommand } from '../helpers/live-arr';
|
||||
import {
|
||||
SONARR,
|
||||
WHISPARR2,
|
||||
type SeededArr,
|
||||
arrangeInstance,
|
||||
listSearchEvents,
|
||||
resetLiveArrState,
|
||||
@@ -41,13 +43,13 @@ interface Episode {
|
||||
monitored: boolean;
|
||||
}
|
||||
|
||||
async function searchCommandsAfter(lastId: number): Promise<LiveArrCommand[]> {
|
||||
const commands = await SONARR.arr.commands();
|
||||
async function searchCommandsAfter(lastId: number, target: SeededArr = SONARR): Promise<LiveArrCommand[]> {
|
||||
const commands = await target.arr.commands();
|
||||
return commands.filter((c) => c.id > lastId && (c.name === 'EpisodeSearch' || c.name === 'SeasonSearch'));
|
||||
}
|
||||
|
||||
async function lastCommandId(): Promise<number> {
|
||||
const commands = await SONARR.arr.commands();
|
||||
async function lastCommandId(target: SeededArr = SONARR): Promise<number> {
|
||||
const commands = await target.arr.commands();
|
||||
return Math.max(0, ...commands.map((c) => c.id));
|
||||
}
|
||||
|
||||
@@ -102,14 +104,18 @@ async function waitForIdleSonarr(): Promise<void> {
|
||||
}
|
||||
|
||||
/** Runs the Seeker once and returns the one search command it sent. */
|
||||
async function searchOnce(api: CleanuparrApi, instanceId: string): Promise<LiveArrCommand> {
|
||||
const before = await lastCommandId();
|
||||
async function searchOnce(
|
||||
api: CleanuparrApi,
|
||||
instanceId: string,
|
||||
target: SeededArr = SONARR,
|
||||
): Promise<LiveArrCommand> {
|
||||
const before = await lastCommandId(target);
|
||||
|
||||
await triggerSeeker(api);
|
||||
await expect.poll(async () => (await listSearchEvents(api, instanceId)).length, { timeout: SETTLE_MS }).toBe(1);
|
||||
|
||||
await expect.poll(async () => (await searchCommandsAfter(before)).length, { timeout: SETTLE_MS }).toBe(1);
|
||||
return (await searchCommandsAfter(before))[0];
|
||||
await expect.poll(async () => (await searchCommandsAfter(before, target)).length, { timeout: SETTLE_MS }).toBe(1);
|
||||
return (await searchCommandsAfter(before, target))[0];
|
||||
}
|
||||
|
||||
test.describe('Seeker Sonarr search mode', () => {
|
||||
@@ -181,3 +187,69 @@ test.describe('Seeker Sonarr search mode', () => {
|
||||
expect(command.body?.episodeIds).toEqual([findEpisode(episodes, 2).id]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Seeker Whisparr v2 search mode', () => {
|
||||
const { yearSearch, singleScene } = TEST_CONFIG.whisparr2Seed;
|
||||
|
||||
let library: Array<Record<string, unknown>>;
|
||||
let scenes: Array<{ id: number; monitored: boolean }>;
|
||||
|
||||
async function setMonitored(sceneIds: number[], monitored: boolean): Promise<void> {
|
||||
await WHISPARR2.arr.put('/api/v3/episode/monitor', { episodeIds: sceneIds, monitored });
|
||||
}
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await resetLiveArrState();
|
||||
await indexerMock.stub(torznabSearchStub(WHISPARR2.searchMode, []));
|
||||
library = await snapshotLibrary(WHISPARR2);
|
||||
scenes = await WHISPARR2.arr.get<Array<{ id: number; monitored: boolean }>>(
|
||||
`/api/v3/episode?seriesId=${WHISPARR2.itemId}`,
|
||||
);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ api }) => {
|
||||
await teardownInstances(api);
|
||||
for (const monitored of [true, false]) {
|
||||
const sceneIds = scenes.filter((scene) => scene.monitored === monitored).map((scene) => scene.id);
|
||||
if (sceneIds.length > 0) {
|
||||
await setMonitored(sceneIds, monitored);
|
||||
}
|
||||
}
|
||||
await restoreLibrary(WHISPARR2, library);
|
||||
await resetLiveArrState();
|
||||
});
|
||||
|
||||
test('auto searches a fully wanted year as a whole', async ({ api }) => {
|
||||
test.setTimeout(180_000);
|
||||
await setMonitored([singleScene.monitored.id], false);
|
||||
|
||||
const instanceId = await arrangeInstance(api, WHISPARR2, { instance: { sonarrSearchMode: 'Auto' } });
|
||||
const command = await searchOnce(api, instanceId, WHISPARR2);
|
||||
|
||||
expect(command.name).toBe('SeasonSearch');
|
||||
expect(command.body?.seasonNumber).toBe(yearSearch.year);
|
||||
});
|
||||
|
||||
test('auto searches a partly wanted year one scene at a time', async ({ api }) => {
|
||||
test.setTimeout(180_000);
|
||||
await setMonitored(yearSearch.scenes.map((scene) => scene.id), false);
|
||||
|
||||
const instanceId = await arrangeInstance(api, WHISPARR2, { instance: { sonarrSearchMode: 'Auto' } });
|
||||
const command = await searchOnce(api, instanceId, WHISPARR2);
|
||||
|
||||
expect(command.name).toBe('EpisodeSearch');
|
||||
expect(command.body?.episodeIds).toEqual([singleScene.monitored.id]);
|
||||
});
|
||||
|
||||
test('episode mode searches a fully wanted year one scene at a time', async ({ api }) => {
|
||||
test.setTimeout(180_000);
|
||||
await setMonitored([singleScene.monitored.id], false);
|
||||
|
||||
const instanceId = await arrangeInstance(api, WHISPARR2, { instance: { sonarrSearchMode: 'Episode' } });
|
||||
const command = await searchOnce(api, instanceId, WHISPARR2);
|
||||
|
||||
expect(command.name).toBe('EpisodeSearch');
|
||||
expect(command.body?.episodeIds).toHaveLength(1);
|
||||
expect(yearSearch.scenes.map((scene) => scene.id)).toContain(command.body?.episodeIds?.[0]);
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,7 @@ test.describe.serial('Seeker — config', () => {
|
||||
expect(body).not.toHaveProperty('useCustomFormatScore');
|
||||
});
|
||||
|
||||
test('lists a Whisparr v3 instance but not a v2 one', async ({ api, mocks }) => {
|
||||
test('lists Whisparr v2 and v3 instances', async ({ api, mocks }) => {
|
||||
await mocks.arr.stub(ArrStubs.arrHealthStub());
|
||||
|
||||
const created: string[] = [];
|
||||
@@ -42,8 +42,13 @@ test.describe.serial('Seeker — config', () => {
|
||||
const config = await (await api.seeker.getConfig()).json();
|
||||
const listed = config.instances.map((i: { arrInstanceId: string }) => i.arrInstanceId);
|
||||
|
||||
expect(listed).toContain(v2Id);
|
||||
expect(listed).toContain(v3Id);
|
||||
expect(listed).not.toContain(v2Id);
|
||||
|
||||
const versionOf = (id: string) =>
|
||||
config.instances.find((i: { arrInstanceId: string }) => i.arrInstanceId === id).instanceVersion;
|
||||
expect(versionOf(v2Id)).toBe(2);
|
||||
expect(versionOf(v3Id)).toBe(3);
|
||||
} finally {
|
||||
for (const id of created) {
|
||||
await api.arr.deleteInstance('whisparr', id);
|
||||
|
||||
Reference in new issue
Block a user