fix(jellyfin): use season episode endpoint
This commit is contained in:
@@ -2906,6 +2906,44 @@ void main() {
|
||||
expect(requestUri!.queryParameters['Limit'], '10');
|
||||
});
|
||||
|
||||
test('fetchSeasonEpisodesPage uses Jellyfin episode endpoint scoped to season', () async {
|
||||
Uri? requestUri;
|
||||
final mock = MockClient((req) async {
|
||||
if (req.url.path == '/Shows/show-1/Episodes') {
|
||||
requestUri = req.url;
|
||||
return http.Response(
|
||||
jsonEncode({
|
||||
'Items': [
|
||||
{'Id': 'episode-1', 'Name': 'Episode', 'Type': 'Episode'},
|
||||
],
|
||||
'TotalRecordCount': 40,
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json'},
|
||||
);
|
||||
}
|
||||
return http.Response('not found', 404);
|
||||
});
|
||||
final client = JellyfinClient.forTesting(connection: _conn(), httpClient: mock);
|
||||
addTearDown(client.close);
|
||||
|
||||
final page = await client.fetchSeasonEpisodesPage('show-1', 'season-1', start: 20, size: 10);
|
||||
|
||||
expect(page.items.single.id, 'episode-1');
|
||||
expect(page.totalCount, 40);
|
||||
expect(page.offset, 20);
|
||||
expect(requestUri, isNotNull);
|
||||
expect(requestUri!.queryParameters['SeasonId'], 'season-1');
|
||||
expect(requestUri!.queryParameters['StartIndex'], '20');
|
||||
expect(requestUri!.queryParameters['Limit'], '10');
|
||||
expect(requestUri!.queryParameters['EnableTotalRecordCount'], 'true');
|
||||
expect(requestUri!.queryParameters['IsMissing'], 'false');
|
||||
expect(requestUri!.queryParameters['IsVirtualUnaired'], 'false');
|
||||
expect(requestUri!.queryParameters['Fields']!.split(','), contains('MediaSources'));
|
||||
expect(requestUri!.queryParameters.containsKey('SortBy'), isFalse);
|
||||
expect(requestUri!.queryParameters.containsKey('SortOrder'), isFalse);
|
||||
});
|
||||
|
||||
test('fetchChildrenPage orders direct episode children by season and episode index', () async {
|
||||
Uri? requestUri;
|
||||
final mock = MockClient((req) async {
|
||||
|
||||
@@ -43,6 +43,8 @@ MediaItem _episode(
|
||||
durationMs: durationMs,
|
||||
);
|
||||
|
||||
MediaItem _clip(String id) => MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.clip, title: 'Clip');
|
||||
|
||||
class _RecordingClient implements MediaServerClient {
|
||||
_RecordingClient({this.childrenByParent = const {}, this.childrenPageByParent = const {}, this.itemsById = const {}});
|
||||
|
||||
@@ -76,6 +78,30 @@ class _RecordingClient implements MediaServerClient {
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _SeasonPagingRecordingClient extends _RecordingClient implements SeasonEpisodePagingClient {
|
||||
_SeasonPagingRecordingClient({this.seasonPageBySeason = const {}});
|
||||
|
||||
final Map<({String seriesId, String seasonId}), List<MediaItem>> seasonPageBySeason;
|
||||
final seasonEpisodePageCalls = <({String seriesId, String seasonId, int? start, int? size})>[];
|
||||
|
||||
@override
|
||||
Future<LibraryPage<MediaItem>> fetchSeasonEpisodesPage(
|
||||
String seriesId,
|
||||
String seasonId, {
|
||||
int? start,
|
||||
int? size,
|
||||
abort,
|
||||
}) async {
|
||||
seasonEpisodePageCalls.add((seriesId: seriesId, seasonId: seasonId, start: start, size: size));
|
||||
final all = seasonPageBySeason[(seriesId: seriesId, seasonId: seasonId)] ?? const <MediaItem>[];
|
||||
final offset = start ?? 0;
|
||||
final limit = size ?? all.length;
|
||||
final end = (offset + limit).clamp(0, all.length).toInt();
|
||||
final items = offset >= all.length ? const <MediaItem>[] : all.sublist(offset, end);
|
||||
return LibraryPage(items: items, totalCount: all.length, offset: offset);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('defaultPlaybackSeason skips specials when a regular season exists', () {
|
||||
final special = _season('specials', index: 0);
|
||||
@@ -180,6 +206,21 @@ void main() {
|
||||
expect(client.childrenPageCalls, [(parentId: 'season-1', start: 0, size: 1)]);
|
||||
});
|
||||
|
||||
test('fetchFirstEpisodeForSeason uses season episode paging when available', () async {
|
||||
final episode = _episode('episode-1');
|
||||
final client = _SeasonPagingRecordingClient(
|
||||
seasonPageBySeason: {
|
||||
(seriesId: 'show-1', seasonId: 'season-1'): [episode, _episode('episode-2')],
|
||||
},
|
||||
);
|
||||
|
||||
final result = await fetchFirstEpisodeForSeason(client, 'season-1', seriesId: 'show-1');
|
||||
|
||||
expect(result, same(episode));
|
||||
expect(client.childrenPageCalls, isEmpty);
|
||||
expect(client.seasonEpisodePageCalls, [(seriesId: 'show-1', seasonId: 'season-1', start: 0, size: 1)]);
|
||||
});
|
||||
|
||||
test('fetchSeasonEpisodePage normalizes show and season identity', () async {
|
||||
final show = MediaItem(
|
||||
id: 'show-1',
|
||||
@@ -213,6 +254,32 @@ void main() {
|
||||
expect(page.items.single.libraryId, show.libraryId);
|
||||
});
|
||||
|
||||
test('fetchSeasonEpisodePage uses season episode paging when available', () async {
|
||||
final show = MediaItem(id: 'show-1', backend: MediaBackend.plex, kind: MediaKind.show, title: 'Show');
|
||||
final season = _season('season-1');
|
||||
final row = _episode('episode-1');
|
||||
final client = _SeasonPagingRecordingClient(
|
||||
seasonPageBySeason: {
|
||||
(seriesId: show.id, seasonId: season.id): [row],
|
||||
},
|
||||
);
|
||||
|
||||
final page = await fetchSeasonEpisodePage(client, show: show, season: season, start: 0, size: 10);
|
||||
|
||||
expect(client.childrenPageCalls, isEmpty);
|
||||
expect(client.seasonEpisodePageCalls, [(seriesId: show.id, seasonId: season.id, start: 0, size: 10)]);
|
||||
expect(page.items.single.id, row.id);
|
||||
});
|
||||
|
||||
test('normalizeSeasonEpisodes ignores non-episode rows', () {
|
||||
final show = MediaItem(id: 'show-1', backend: MediaBackend.plex, kind: MediaKind.show, title: 'Show');
|
||||
final season = _season('season-1');
|
||||
|
||||
final normalized = normalizeSeasonEpisodes([_clip('extra-1'), _episode('episode-1')], show: show, season: season);
|
||||
|
||||
expect(normalized.map((item) => item.id), ['episode-1']);
|
||||
});
|
||||
|
||||
test('fetchRepresentativeVersions uses paged lookup for season metadata', () async {
|
||||
final versions = [const MediaVersion(id: '1080', videoResolution: '1080')];
|
||||
final episodeRow = _episode('episode-1');
|
||||
|
||||
Reference in New Issue
Block a user