Skip to content

Commit 4ba9c28

Browse files
committed
fix: update tv show status to partially available when seasons are missing
1 parent a8f147d commit 4ba9c28

7 files changed

Lines changed: 657 additions & 79 deletions

File tree

next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./.next/types/routes.d.ts";
3+
import "./.next/dev/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.

server/lib/availabilitySync.test.ts

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import type { PlexMetadata } from '@server/api/plexapi';
1010
import PlexAPI from '@server/api/plexapi';
1111
import type { SonarrSeason, SonarrSeries } from '@server/api/servarr/sonarr';
1212
import SonarrAPI from '@server/api/servarr/sonarr';
13+
import TheMovieDb from '@server/api/themoviedb';
14+
import type {
15+
TmdbTvDetails,
16+
TmdbTvSeasonResult,
17+
} from '@server/api/themoviedb/interfaces';
1318
import { MediaStatus, MediaType } from '@server/constants/media';
1419
import { MediaServerType } from '@server/constants/server';
1520
import { getRepository } from '@server/datasource';
@@ -117,6 +122,72 @@ Object.defineProperty(SonarrAPI.prototype, 'getSeriesById', {
117122
configurable: true,
118123
});
119124

125+
// --- Mock TheMovieDb ---
126+
let getTvShowImpl: (args: {
127+
tvId: number;
128+
language?: string;
129+
}) => Promise<TmdbTvDetails> = async () => fakeTmdbShow(1);
130+
131+
Object.defineProperty(TheMovieDb.prototype, 'getTvShow', {
132+
get() {
133+
return async (args: { tvId: number; language?: string }) =>
134+
getTvShowImpl(args);
135+
},
136+
set() {},
137+
configurable: true,
138+
});
139+
140+
// --- Helpers ---
141+
142+
function fakeTmdbShow(
143+
tmdbId: number,
144+
seasons: TmdbTvSeasonResult[] = [
145+
{
146+
id: 1,
147+
air_date: '2024-01-01',
148+
episode_count: 10,
149+
name: 'Season 1',
150+
overview: '',
151+
season_number: 1,
152+
},
153+
]
154+
): TmdbTvDetails {
155+
return {
156+
id: tmdbId,
157+
content_ratings: { results: [] },
158+
created_by: [],
159+
episode_run_time: [],
160+
first_air_date: '2024-01-01',
161+
genres: [],
162+
homepage: '',
163+
in_production: false,
164+
languages: ['en'],
165+
last_air_date: '2024-01-01',
166+
name: 'Test Show',
167+
networks: [],
168+
number_of_episodes: 10,
169+
number_of_seasons: seasons.length,
170+
origin_country: ['US'],
171+
original_language: 'en',
172+
original_name: 'Test Show',
173+
overview: '',
174+
popularity: 0,
175+
production_companies: [],
176+
production_countries: [],
177+
spoken_languages: [],
178+
seasons,
179+
status: 'Ended',
180+
type: 'Scripted',
181+
vote_average: 0,
182+
vote_count: 0,
183+
aggregate_credits: { cast: [] },
184+
credits: { crew: [] },
185+
external_ids: {},
186+
keywords: { results: [] },
187+
videos: { results: [] },
188+
};
189+
}
190+
120191
import availabilitySync from '@server/lib/availabilitySync';
121192

122193
setupTestDb();
@@ -304,6 +375,18 @@ describe('AvailabilitySync', () => {
304375
getSeriesByIdImpl = async () => {
305376
throw new Error('404');
306377
};
378+
getTvShowImpl = async ({ tvId }) =>
379+
fakeTmdbShow(
380+
tvId,
381+
Array.from({ length: 12 }, (_, i) => ({
382+
id: i + 1,
383+
air_date: '2024-01-01',
384+
episode_count: 10,
385+
name: `Season ${i + 1}`,
386+
overview: '',
387+
season_number: i + 1,
388+
}))
389+
);
307390

308391
const userRepository = getRepository(User);
309392
const existingAdmin = await userRepository.findOne({ where: { id: 1 } });
@@ -688,6 +771,106 @@ describe('AvailabilitySync', () => {
688771
'Show should remain AVAILABLE when getEpisodes fails'
689772
);
690773
});
774+
775+
it('should mark show as PARTIALLY_AVAILABLE when some seasons are available and some are unknown', async () => {
776+
configureJellyfin();
777+
configureSonarr([{ syncEnabled: true }]);
778+
779+
const mediaRepository = getRepository(Media);
780+
781+
const media = new Media();
782+
media.tmdbId = 1412;
783+
media.mediaType = MediaType.TV;
784+
media.status = MediaStatus.AVAILABLE;
785+
media.jellyfinMediaId = 'jellyfin-partial-id';
786+
media.externalServiceId = 103;
787+
media.seasons = [
788+
new Season({
789+
seasonNumber: 1,
790+
status: MediaStatus.AVAILABLE,
791+
status4k: MediaStatus.UNKNOWN,
792+
}),
793+
new Season({
794+
seasonNumber: 2,
795+
status: MediaStatus.AVAILABLE,
796+
status4k: MediaStatus.UNKNOWN,
797+
}),
798+
new Season({
799+
seasonNumber: 3,
800+
status: MediaStatus.UNKNOWN,
801+
status4k: MediaStatus.UNKNOWN,
802+
}),
803+
new Season({
804+
seasonNumber: 4,
805+
status: MediaStatus.UNKNOWN,
806+
status4k: MediaStatus.UNKNOWN,
807+
}),
808+
];
809+
810+
await mediaRepository.save(media);
811+
812+
getItemDataImpl = async (id: string) => {
813+
if (id === 'jellyfin-partial-id') {
814+
return fakeJellyfinShow('jellyfin-partial-id', '1412');
815+
}
816+
return undefined;
817+
};
818+
819+
getSeasonsImpl = async (seriesID: string) => {
820+
if (seriesID === 'jellyfin-partial-id') {
821+
return [
822+
fakeJellyfinSeason(1, 'jellyfin-partial-s1-id'),
823+
fakeJellyfinSeason(2, 'jellyfin-partial-s2-id'),
824+
];
825+
}
826+
return [];
827+
};
828+
829+
getEpisodesImpl = async (_seriesID: string, seasonID: string) => {
830+
if (seasonID === 'jellyfin-partial-s1-id') {
831+
return fakeJellyfinEpisodes(10);
832+
}
833+
if (seasonID === 'jellyfin-partial-s2-id') {
834+
return fakeJellyfinEpisodes(10);
835+
}
836+
return [];
837+
};
838+
839+
getSeriesByIdImpl = async (id: number) => {
840+
if (id === 103) {
841+
return {
842+
tvdbId: 99997,
843+
id: 103,
844+
title: 'Partial Show',
845+
titleSlug: 'partial-show',
846+
monitored: true,
847+
statistics: {
848+
episodeFileCount: 20,
849+
totalEpisodeCount: 40,
850+
episodeCount: 40,
851+
percentOfEpisodes: 50,
852+
sizeOnDisk: 0,
853+
seasonCount: 4,
854+
},
855+
seasons: fakeSonarrSeasons(4, { 1: 10, 2: 10 }),
856+
} as unknown as SonarrSeries;
857+
}
858+
throw new Error('404');
859+
};
860+
861+
await availabilitySync.run();
862+
863+
const updated = await mediaRepository.findOneOrFail({
864+
where: { tmdbId: 1412 },
865+
relations: ['seasons'],
866+
});
867+
868+
assert.strictEqual(
869+
updated.status,
870+
MediaStatus.PARTIALLY_AVAILABLE,
871+
'Show should be PARTIALLY_AVAILABLE when some seasons are available and some are unknown'
872+
);
873+
});
691874
});
692875

693876
describe('TV season availability - Plex', () => {

0 commit comments

Comments
 (0)