test: add scanner update rate override for testing (#3241)

This commit is contained in:
Michael Thomas
2026-09-13 07:32:51 +08:00
committed by GitHub
parent de57e7ac6c
commit 7a76142ae3
4 changed files with 73 additions and 36 deletions
@@ -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';
@@ -342,7 +343,7 @@ describe('Jellyfin Scanner', () => {
return [];
};
await jellyfinFullScanner.run();
await runWithMockTimers(() => jellyfinFullScanner.run());
const updated = await mediaRepository.findOneOrFail({
where: { tmdbId: 5000 },
@@ -420,7 +421,7 @@ describe('Jellyfin Scanner', () => {
return [];
};
await jellyfinFullScanner.run();
await runWithMockTimers(() => jellyfinFullScanner.run());
const updated = await mediaRepository.findOneOrFail({
where: { tmdbId: 5001 },
@@ -497,7 +498,7 @@ describe('Jellyfin Scanner', () => {
return [];
};
await jellyfinFullScanner.run();
await runWithMockTimers(() => jellyfinFullScanner.run());
const updated = await mediaRepository.findOneOrFail({
where: { tmdbId: 5002 },
+19 -18
View File
@@ -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 },
+16 -15
View File
@@ -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';
@@ -116,6 +116,7 @@ Object.defineProperty(TheMovieDb.prototype, 'getTvShowForScan', {
configurable: true,
});
import { sonarrScanner } from '@server/lib/scanners/sonarr';
mock.method(MediaRequest, 'sendNotification', async () => undefined);
setupTestDb();
@@ -206,7 +207,7 @@ describe('Sonarr Scanner', () => {
getSeriesImpl = async () => [];
await sonarrScanner.run();
await runWithMockTimers(() => sonarrScanner.run());
const updated = await mediaRepository.findOneOrFail({
where: { tmdbId: 1050 },
@@ -236,7 +237,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 },
@@ -266,7 +267,7 @@ describe('Sonarr Scanner', () => {
configureSonarr([{ syncEnabled: true }]);
getSeriesImpl = async () => [];
await sonarrScanner.run();
await runWithMockTimers(() => sonarrScanner.run());
const updated = await mediaRepository.findOneOrFail({
where: { tmdbId: 1001 },
@@ -316,7 +317,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 },
@@ -350,7 +351,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 },
@@ -376,7 +377,7 @@ describe('Sonarr Scanner', () => {
configureSonarr([{ syncEnabled: true }]);
getSeriesImpl = async () => [];
await sonarrScanner.run();
await runWithMockTimers(() => sonarrScanner.run());
const updated = await mediaRepository.findOneOrFail({
where: { tmdbId: 1004 },
@@ -425,7 +426,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 },
@@ -453,7 +454,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 },
@@ -484,7 +485,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 },
@@ -520,7 +521,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 },
@@ -575,7 +576,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 },
@@ -649,7 +650,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 },
@@ -698,7 +699,7 @@ describe('Sonarr Scanner', () => {
configureSonarr([{ syncEnabled: true }]);
getSeriesImpl = async () => [];
await sonarrScanner.run();
await runWithMockTimers(() => sonarrScanner.run());
const updatedMedia = await mediaRepository.findOneOrFail({
where: { tmdbId: 2005 },
@@ -817,7 +818,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 },
+34
View File
@@ -0,0 +1,34 @@
import { mock } from 'node:test';
export async function runWithMockTimers<T>(
runFn: () => Promise<T>,
tickMs = 4000
): Promise<T> {
mock.timers.enable({ apis: ['setTimeout'] });
try {
let settled = false;
const runPromise = runFn();
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);
}
// The final tick may have settled runPromise, but `settled` only updates
// in a microtask; yield once more before the guard check to avoid false
// negatives when the resolving tick lands on the last iteration.
await new Promise((resolve) => setImmediate(resolve));
if (!settled) {
throw new Error(
`runWithMockTimers: promise did not settle after ${maxTicks} ticks`
);
}
return await runPromise;
} finally {
mock.timers.reset();
}
}