diff --git a/server/lib/scanners/jellyfin/jellyfin.test.ts b/server/lib/scanners/jellyfin/jellyfin.test.ts index 51f97d09a8..47c826637a 100644 --- a/server/lib/scanners/jellyfin/jellyfin.test.ts +++ b/server/lib/scanners/jellyfin/jellyfin.test.ts @@ -18,6 +18,7 @@ import { User } from '@server/entity/User'; import type { Library } from '@server/lib/settings'; import { getSettings } from '@server/lib/settings'; import { setupTestDb } from '@server/test/db'; +import { runWithMockTimers } from '@server/test/runWithMockTimers'; import assert from 'node:assert/strict'; import { beforeEach, describe, it } from 'node:test'; @@ -333,7 +334,7 @@ describe('Jellyfin Scanner', () => { return []; }; - await jellyfinFullScanner.run(); + await runWithMockTimers(jellyfinFullScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 5000 }, @@ -411,7 +412,7 @@ describe('Jellyfin Scanner', () => { return []; }; - await jellyfinFullScanner.run(); + await runWithMockTimers(jellyfinFullScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 5001 }, @@ -488,7 +489,7 @@ describe('Jellyfin Scanner', () => { return []; }; - await jellyfinFullScanner.run(); + await runWithMockTimers(jellyfinFullScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 5002 }, diff --git a/server/lib/scanners/radarr/radarr.test.ts b/server/lib/scanners/radarr/radarr.test.ts index 7faeed97f1..0f14c86de3 100644 --- a/server/lib/scanners/radarr/radarr.test.ts +++ b/server/lib/scanners/radarr/radarr.test.ts @@ -13,6 +13,7 @@ import { radarrScanner } from '@server/lib/scanners/radarr'; import type { RadarrSettings } from '@server/lib/settings'; import { getSettings } from '@server/lib/settings'; import { setupTestDb } from '@server/test/db'; +import { runWithMockTimers } from '@server/test/runWithMockTimers'; import assert from 'node:assert/strict'; import { beforeEach, describe, it, mock } from 'node:test'; @@ -93,7 +94,7 @@ describe('Radarr Scanner', () => { fakeRadarrMovie({ monitored: false, hasFile: false }), ]; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 550 }, @@ -109,7 +110,7 @@ describe('Radarr Scanner', () => { fakeRadarrMovie({ tmdbId: 777, monitored: false, hasFile: false }), ]; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const media = await mediaRepository.findOne({ where: { tmdbId: 777 }, @@ -131,7 +132,7 @@ describe('Radarr Scanner', () => { fakeRadarrMovie({ tmdbId: 551, monitored: true, hasFile: true }), ]; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 551 }, @@ -153,7 +154,7 @@ describe('Radarr Scanner', () => { fakeRadarrMovie({ tmdbId: 552, monitored: true, hasFile: false }), ]; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 552 }, @@ -175,7 +176,7 @@ describe('Radarr Scanner', () => { fakeRadarrMovie({ tmdbId: 553, monitored: true, hasFile: false }), ]; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 553 }, @@ -197,7 +198,7 @@ describe('Radarr Scanner', () => { fakeRadarrMovie({ tmdbId: 554, monitored: false, hasFile: true }), ]; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 554 }, @@ -223,7 +224,7 @@ describe('Radarr Scanner', () => { getMoviesImpl = async () => []; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 950 }, @@ -243,7 +244,7 @@ describe('Radarr Scanner', () => { configureRadarr([{ syncEnabled: true }]); getMoviesImpl = async () => [fakeRadarrMovie({ tmdbId: 1, id: 99 })]; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 999 }, @@ -263,7 +264,7 @@ describe('Radarr Scanner', () => { configureRadarr([{ syncEnabled: true }]); getMoviesImpl = async () => []; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 888 }, @@ -285,7 +286,7 @@ describe('Radarr Scanner', () => { fakeRadarrMovie({ tmdbId: 700, monitored: true, hasFile: false }), ]; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 700 }, @@ -306,7 +307,7 @@ describe('Radarr Scanner', () => { configureRadarr([{ syncEnabled: true }]); getMoviesImpl = async () => []; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 800 }, @@ -344,7 +345,7 @@ describe('Radarr Scanner', () => { return [fakeRadarrMovie({ tmdbId: 903, id: 11 })]; }; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updatedOrphan = await mediaRepository.findOneOrFail({ where: { tmdbId: 901 }, @@ -372,7 +373,7 @@ describe('Radarr Scanner', () => { configureRadarr([{ syncEnabled: true, is4k: true }]); getMoviesImpl = async () => [fakeRadarrMovie({ tmdbId: 1, id: 99 })]; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 960 }, @@ -393,7 +394,7 @@ describe('Radarr Scanner', () => { configureRadarr([{ syncEnabled: true, is4k: true }]); getMoviesImpl = async () => []; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 961 }, @@ -436,7 +437,7 @@ describe('Radarr Scanner', () => { configureRadarr([{ syncEnabled: true }]); getMoviesImpl = async () => [fakeRadarrMovie({ tmdbId: 1, id: 99 })]; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updatedMedia = await mediaRepository.findOneOrFail({ where: { tmdbId: 1003596 }, @@ -484,7 +485,7 @@ describe('Radarr Scanner', () => { fakeRadarrMovie({ tmdbId: 700, monitored: true, hasFile: false }), ]; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updatedRequest = await requestRepository.findOneOrFail({ where: { id: request.id }, @@ -525,7 +526,7 @@ describe('Radarr Scanner', () => { configureRadarr([{ syncEnabled: true }]); getMoviesImpl = async () => []; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updatedMedia = await mediaRepository.findOneOrFail({ where: { tmdbId: 1234 }, @@ -621,7 +622,7 @@ describe('Radarr Scanner', () => { return [fakeRadarrMovie({ tmdbId: 2, id: 88 })]; }; - await radarrScanner.run(); + await runWithMockTimers(radarrScanner.run()); const updatedMedia = await mediaRepository.findOneOrFail({ where: { tmdbId: 1003598 }, diff --git a/server/lib/scanners/sonarr/sonarr.test.ts b/server/lib/scanners/sonarr/sonarr.test.ts index e012c979eb..b226176c7b 100644 --- a/server/lib/scanners/sonarr/sonarr.test.ts +++ b/server/lib/scanners/sonarr/sonarr.test.ts @@ -15,10 +15,10 @@ import Media from '@server/entity/Media'; import MediaRequest from '@server/entity/MediaRequest'; import Season from '@server/entity/Season'; import { User } from '@server/entity/User'; -import { sonarrScanner } from '@server/lib/scanners/sonarr'; import type { SonarrSettings } from '@server/lib/settings'; import { getSettings } from '@server/lib/settings'; import { setupTestDb } from '@server/test/db'; +import { runWithMockTimers } from '@server/test/runWithMockTimers'; import assert from 'node:assert/strict'; import { beforeEach, describe, it, mock } from 'node:test'; @@ -103,6 +103,8 @@ Object.defineProperty(TheMovieDb.prototype, 'getTvShow', { configurable: true, }); +import { sonarrScanner } from '@server/lib/scanners/sonarr'; + mock.method(MediaRequest, 'sendNotification', async () => undefined); setupTestDb(); @@ -193,7 +195,7 @@ describe('Sonarr Scanner', () => { getSeriesImpl = async () => []; - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 1050 }, @@ -223,7 +225,7 @@ describe('Sonarr Scanner', () => { configureSonarr([{ syncEnabled: true }]); getSeriesImpl = async () => [fakeSonarrSeries({ tvdbId: 999 })]; - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 1000 }, @@ -253,7 +255,7 @@ describe('Sonarr Scanner', () => { configureSonarr([{ syncEnabled: true }]); getSeriesImpl = async () => []; - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 1001 }, @@ -303,7 +305,7 @@ describe('Sonarr Scanner', () => { getShowByTvdbIdImpl = async () => fakeTmdbShow(1); getTvShowImpl = async () => fakeTmdbShow(1); - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 1 }, @@ -337,7 +339,7 @@ describe('Sonarr Scanner', () => { configureSonarr([{ syncEnabled: true }]); getSeriesImpl = async () => [fakeSonarrSeries({ tvdbId: 999 })]; - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 1003 }, @@ -363,7 +365,7 @@ describe('Sonarr Scanner', () => { configureSonarr([{ syncEnabled: true }]); getSeriesImpl = async () => []; - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 1004 }, @@ -412,7 +414,7 @@ describe('Sonarr Scanner', () => { getShowByTvdbIdImpl = async () => fakeTmdbShow(2); getTvShowImpl = async () => fakeTmdbShow(2); - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updatedOrphan = await mediaRepository.findOneOrFail({ where: { tmdbId: 1010 }, @@ -440,7 +442,7 @@ describe('Sonarr Scanner', () => { configureSonarr([{ syncEnabled: true }]); getSeriesImpl = async () => [fakeSonarrSeries({ tvdbId: 999 })]; - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 1020 }, @@ -471,7 +473,7 @@ describe('Sonarr Scanner', () => { configureSonarr([{ syncEnabled: true, is4k: true }]); getSeriesImpl = async () => [fakeSonarrSeries({ tvdbId: 999 })]; - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 1030 }, @@ -507,7 +509,7 @@ describe('Sonarr Scanner', () => { configureSonarr([{ syncEnabled: true, is4k: true }]); getSeriesImpl = async () => [fakeSonarrSeries({ tvdbId: 999 })]; - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updated = await mediaRepository.findOneOrFail({ where: { tmdbId: 1031 }, @@ -562,7 +564,7 @@ describe('Sonarr Scanner', () => { configureSonarr([{ syncEnabled: true }]); getSeriesImpl = async () => [fakeSonarrSeries({ tvdbId: 999 })]; - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updatedMedia = await mediaRepository.findOneOrFail({ where: { tmdbId: 2000 }, @@ -636,7 +638,7 @@ describe('Sonarr Scanner', () => { getShowByTvdbIdImpl = async () => fakeTmdbShow(2001); getTvShowImpl = async () => fakeTmdbShow(2001); - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updatedRequest = await requestRepository.findOneOrFail({ where: { id: request.id }, @@ -685,7 +687,7 @@ describe('Sonarr Scanner', () => { configureSonarr([{ syncEnabled: true }]); getSeriesImpl = async () => []; - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updatedMedia = await mediaRepository.findOneOrFail({ where: { tmdbId: 2005 }, @@ -804,7 +806,7 @@ describe('Sonarr Scanner', () => { tvdbId === 666 ? fakeTmdbShow(2002) : fakeTmdbShow(997); getTvShowImpl = async ({ tvId }) => fakeTmdbShow(tvId); - await sonarrScanner.run(); + await runWithMockTimers(sonarrScanner.run()); const updatedMedia = await mediaRepository.findOneOrFail({ where: { tmdbId: 2002 }, diff --git a/server/test/runWithMockTimers.ts b/server/test/runWithMockTimers.ts new file mode 100644 index 0000000000..2da3da8767 --- /dev/null +++ b/server/test/runWithMockTimers.ts @@ -0,0 +1,29 @@ +import { mock } from 'node:test'; + +export async function runWithMockTimers( + runPromise: Promise, + tickMs = 4000 +): Promise { + mock.timers.enable({ apis: ['setTimeout'] }); + try { + let settled = false; + runPromise + .catch(() => undefined) + .finally(() => { + settled = true; + }); + const maxTicks = 100000; + for (let i = 0; i < maxTicks && !settled; i++) { + await new Promise((resolve) => setImmediate(resolve)); + mock.timers.tick(tickMs); + } + if (!settled) { + throw new Error( + `runWithMockTimers: promise did not settle after ${maxTicks} ticks` + ); + } + return await runPromise; + } finally { + mock.timers.reset(); + } +}