mirror of
https://github.com/seerr-team/seerr.git
synced 2026-06-02 13:20:22 -04:00
200 lines
5.3 KiB
TypeScript
200 lines
5.3 KiB
TypeScript
import type { PlexWatchlistItem } from '@server/api/plextv';
|
|
import PlexTvAPI from '@server/api/plextv';
|
|
import {
|
|
MediaRequestStatus,
|
|
MediaStatus,
|
|
MediaType,
|
|
} from '@server/constants/media';
|
|
import { getRepository } from '@server/datasource';
|
|
import Media from '@server/entity/Media';
|
|
import { MediaRequest } from '@server/entity/MediaRequest';
|
|
import { User } from '@server/entity/User';
|
|
import { UserSettings } from '@server/entity/UserSettings';
|
|
import { Permission } from '@server/lib/permissions';
|
|
import { setupTestDb } from '@server/test/db';
|
|
import assert from 'node:assert/strict';
|
|
import { beforeEach, describe, it } from 'node:test';
|
|
|
|
let watchlistItems: PlexWatchlistItem[] = [];
|
|
|
|
Object.defineProperty(PlexTvAPI.prototype, 'getWatchlist', {
|
|
get() {
|
|
return async () => ({
|
|
offset: 0,
|
|
size: 20,
|
|
totalSize: watchlistItems.length,
|
|
items: watchlistItems,
|
|
});
|
|
},
|
|
set() {},
|
|
configurable: true,
|
|
});
|
|
|
|
let requestCalls: { mediaId: number; mediaType: MediaType }[] = [];
|
|
|
|
Object.defineProperty(MediaRequest, 'request', {
|
|
value: async (body: { mediaId: number; mediaType: MediaType }) => {
|
|
requestCalls.push({ mediaId: body.mediaId, mediaType: body.mediaType });
|
|
return {} as MediaRequest;
|
|
},
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
|
|
import watchlistSync from '@server/lib/watchlistsync';
|
|
|
|
setupTestDb();
|
|
|
|
async function configureSyncUser(): Promise<User> {
|
|
const userRepository = getRepository(User);
|
|
const admin = await userRepository.findOneOrFail({ where: { id: 1 } });
|
|
|
|
admin.plexToken = 'test-plex-token';
|
|
admin.permissions = Permission.AUTO_REQUEST;
|
|
await userRepository.save(admin);
|
|
|
|
const userSettingsRepository = getRepository(UserSettings);
|
|
await userSettingsRepository.save(
|
|
new UserSettings({
|
|
user: admin,
|
|
watchlistSyncMovies: true,
|
|
watchlistSyncTv: true,
|
|
})
|
|
);
|
|
|
|
return admin;
|
|
}
|
|
|
|
async function seedMedia(
|
|
tmdbId: number,
|
|
mediaType: MediaType,
|
|
status: MediaStatus
|
|
): Promise<void> {
|
|
const mediaRepository = getRepository(Media);
|
|
await mediaRepository.save(
|
|
new Media({
|
|
tmdbId,
|
|
mediaType,
|
|
status,
|
|
status4k: MediaStatus.UNKNOWN,
|
|
})
|
|
);
|
|
}
|
|
|
|
function movieItem(tmdbId: number, title: string): PlexWatchlistItem {
|
|
return { ratingKey: `rk-${tmdbId}`, tmdbId, title, type: 'movie' };
|
|
}
|
|
|
|
function showItem(tmdbId: number, title: string): PlexWatchlistItem {
|
|
return {
|
|
ratingKey: `rk-${tmdbId}`,
|
|
tmdbId,
|
|
tvdbId: tmdbId * 1000,
|
|
title,
|
|
type: 'show',
|
|
};
|
|
}
|
|
|
|
describe('WatchlistSync re-request gating', () => {
|
|
beforeEach(() => {
|
|
requestCalls = [];
|
|
watchlistItems = [];
|
|
});
|
|
|
|
it('re-requests DELETED watchlist items and skips non-requestable ones', async () => {
|
|
await configureSyncUser();
|
|
|
|
await seedMedia(100, MediaType.MOVIE, MediaStatus.DELETED);
|
|
await seedMedia(101, MediaType.MOVIE, MediaStatus.UNKNOWN);
|
|
await seedMedia(102, MediaType.MOVIE, MediaStatus.AVAILABLE);
|
|
await seedMedia(103, MediaType.MOVIE, MediaStatus.BLOCKLISTED);
|
|
|
|
await seedMedia(200, MediaType.TV, MediaStatus.DELETED);
|
|
await seedMedia(201, MediaType.TV, MediaStatus.AVAILABLE);
|
|
|
|
watchlistItems = [
|
|
movieItem(100, 'Deleted Movie'),
|
|
movieItem(101, 'Unknown Movie'),
|
|
movieItem(102, 'Available Movie'),
|
|
movieItem(103, 'Blocklisted Movie'),
|
|
showItem(200, 'Deleted Show'),
|
|
showItem(201, 'Available Show'),
|
|
];
|
|
|
|
await watchlistSync.syncWatchlist();
|
|
|
|
const requestedArray = requestCalls.map(
|
|
(c) => `${c.mediaType}:${c.mediaId}`
|
|
);
|
|
const requested = new Set(requestedArray);
|
|
|
|
assert.strictEqual(
|
|
requestedArray.length,
|
|
requested.size,
|
|
'Each item should be requested exactly once'
|
|
);
|
|
|
|
assert.ok(
|
|
requested.has(`${MediaType.MOVIE}:100`),
|
|
'DELETED movie on the watchlist should be re-requested'
|
|
);
|
|
|
|
assert.ok(
|
|
requested.has(`${MediaType.MOVIE}:101`),
|
|
'UNKNOWN movie should be requested'
|
|
);
|
|
assert.ok(
|
|
!requested.has(`${MediaType.MOVIE}:102`),
|
|
'AVAILABLE movie should NOT be requested'
|
|
);
|
|
assert.ok(
|
|
!requested.has(`${MediaType.MOVIE}:103`),
|
|
'BLOCKLISTED movie should NOT be requested'
|
|
);
|
|
|
|
assert.ok(
|
|
requested.has(`${MediaType.TV}:200`),
|
|
'DELETED show should be re-requested'
|
|
);
|
|
assert.ok(
|
|
!requested.has(`${MediaType.TV}:201`),
|
|
'AVAILABLE show should NOT be requested'
|
|
);
|
|
});
|
|
|
|
it('re-requests DELETED watchlist items even when a stale auto-request exists', async () => {
|
|
const user = await configureSyncUser();
|
|
|
|
await seedMedia(100, MediaType.MOVIE, MediaStatus.DELETED);
|
|
|
|
const media = await getRepository(Media).findOneOrFail({
|
|
where: { tmdbId: 100, mediaType: MediaType.MOVIE },
|
|
});
|
|
|
|
await getRepository(MediaRequest).save(
|
|
new MediaRequest({
|
|
type: MediaType.MOVIE,
|
|
status: MediaRequestStatus.COMPLETED,
|
|
media,
|
|
requestedBy: user,
|
|
is4k: false,
|
|
isAutoRequest: true,
|
|
})
|
|
);
|
|
|
|
watchlistItems = [movieItem(100, 'Deleted Movie')];
|
|
|
|
await watchlistSync.syncWatchlist();
|
|
|
|
const calls = requestCalls.filter(
|
|
(c) => c.mediaType === MediaType.MOVIE && c.mediaId === 100
|
|
);
|
|
|
|
assert.strictEqual(
|
|
calls.length,
|
|
1,
|
|
'DELETED movie should be re-requested even when a stale auto-request exists'
|
|
);
|
|
});
|
|
});
|