Files
plezy/test/utils/external_ids_matching_test.dart
T
edde746 bc0d14a749 fix(explore): list every library copy of a title, not one per server
`MediaServerClient.findByExternalIds` returned `MediaItem?`, so the Explore
"In these libraries" chooser could never show more than one copy per server.
A movie held by both a 4K library and an HD library on one Plex server
therefore resolved to whichever copy came back first, with no way to reach
the other.

Return every id-verified match instead. `/library/all` is already
server-wide and each `Metadata` entry carries its own `librarySectionID`,
so both copies come back labelled with no extra request; Plex was simply
taking `Metadata[0]` and the title ladder was returning on its first hit.
An exact-guid hit no longer short-circuits the title search either — a
library still on a legacy agent has a different primary guid and is
invisible to the `guid=` filter.

Copies are deduped by global key and ordered best-first, and each row now
states its resolution, since library names need not mention it.

Resolution passes merge rather than replace: the cross-server fan-out logs
and skips per-server failures, so a later pass can come back short a server
that answered an earlier one, and a failed pass no longer claims the title
left the library. Duplicate keys fold field by field, because Jellyfin's
library stamp is a best-effort ancestors lookup that returns the item bare
when it fails and an unstamped row is indistinguishable from its sibling.
Focus nodes are keyed by copy and reclaimed after a merge re-sorts the
rows, so a dpad user is not thrown to a different copy.

close #1754
2026-08-02 06:40:04 +02:00

73 lines
2.5 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/utils/external_ids.dart';
void main() {
group('ExternalIds.intersects', () {
test('matches when any shared id form is equal', () {
const trakt = ExternalIds(imdb: 'tt0133093', tmdb: 603);
expect(trakt.intersects(const ExternalIds(tmdb: 603)), isTrue);
expect(trakt.intersects(const ExternalIds(imdb: 'tt0133093', tvdb: 999)), isTrue);
});
test('never matches on absent or differing ids', () {
const trakt = ExternalIds(imdb: 'tt0133093');
expect(trakt.intersects(const ExternalIds(tmdb: 603)), isFalse);
expect(trakt.intersects(const ExternalIds(imdb: 'tt9999999')), isFalse);
expect(const ExternalIds().intersects(const ExternalIds()), isFalse);
});
});
group('ExternalIds.jellyfinCandidatesMatching', () {
const target = ExternalIds(imdb: 'tt0133093', tmdb: 603);
test('picks the candidate whose ProviderIds intersect, skipping others', () {
final candidates = <Map<String, dynamic>>[
{
'Name': 'The Matrix Reloaded',
'ProviderIds': {'Imdb': 'tt0234215', 'Tmdb': '604'},
},
{'Name': 'No provider ids'},
{
'Name': 'The Matrix',
'ProviderIds': {'Tmdb': '603'},
},
];
final matches = ExternalIds.jellyfinCandidatesMatching(candidates, target);
expect(matches, hasLength(1));
expect(matches.single['Name'], 'The Matrix');
});
test('returns every library copy of the same title, in response order', () {
final candidates = <Map<String, dynamic>>[
{
'Id': 'movie-4k',
'Name': 'The Matrix (4K)',
'ProviderIds': {'Tmdb': '603'},
},
{
'Id': 'movie-other',
'Name': 'The Matrix Reloaded',
'ProviderIds': {'Imdb': 'tt0234215'},
},
{
'Id': 'movie-hd',
'Name': 'The Matrix',
'ProviderIds': {'Imdb': 'tt0133093'},
},
];
final matches = ExternalIds.jellyfinCandidatesMatching(candidates, target);
expect(matches.map((candidate) => candidate['Id']), ['movie-4k', 'movie-hd']);
});
test('returns no candidates when nothing verifies', () {
final candidates = <Map<String, dynamic>>[
{
'Name': 'Similar title, different film',
'ProviderIds': {'Imdb': 'tt0234215'},
},
];
expect(ExternalIds.jellyfinCandidatesMatching(candidates, target), isEmpty);
});
});
}