@@ -240,6 +240,92 @@ def test_playback_progress_collision_keeps_keepers_row(self):
240240 self .assertEqual (keeper_progress .position_seconds , 900 )
241241
242242
243+ class DedupeCrossProviderItemsTests (TestCase ):
244+ """Render-time dedupe uses verified provider identity without network calls."""
245+
246+ def _season_pair (self , * , parent_tvdb_id = "81189" ):
247+ tmdb_show = Item .objects .create (
248+ media_id = "1396" ,
249+ source = Sources .TMDB .value ,
250+ media_type = MediaTypes .TV .value ,
251+ title = "Breaking Bad" ,
252+ image = "" ,
253+ provider_external_ids = {"tvdb_id" : parent_tvdb_id },
254+ )
255+ tvdb_show = Item .objects .create (
256+ media_id = parent_tvdb_id ,
257+ source = Sources .TVDB .value ,
258+ media_type = MediaTypes .TV .value ,
259+ title = "Breaking Bad" ,
260+ image = "" ,
261+ )
262+ tmdb_season = Item .objects .create (
263+ media_id = tmdb_show .media_id ,
264+ source = Sources .TMDB .value ,
265+ media_type = MediaTypes .SEASON .value ,
266+ season_number = 1 ,
267+ title = "Breaking Bad" ,
268+ image = "" ,
269+ )
270+ tvdb_season = Item .objects .create (
271+ media_id = tvdb_show .media_id ,
272+ source = Sources .TVDB .value ,
273+ media_type = MediaTypes .SEASON .value ,
274+ season_number = 1 ,
275+ title = "Breaking Bad" ,
276+ image = "" ,
277+ )
278+ return tmdb_season , tvdb_season
279+
280+ def test_season_falls_back_to_parent_tvdb_id_and_honors_preference (self ):
281+ tmdb_season , tvdb_season = self ._season_pair ()
282+
283+ with self .assertNumQueries (1 ):
284+ tmdb_result = item_merge .dedupe_cross_provider_items (
285+ [tmdb_season , tvdb_season ],
286+ Sources .TMDB .value ,
287+ )
288+ tvdb_result = item_merge .dedupe_cross_provider_items (
289+ [tmdb_season , tvdb_season ],
290+ Sources .TVDB .value ,
291+ )
292+
293+ self .assertEqual (tmdb_result , [tmdb_season ])
294+ self .assertEqual (tvdb_result , [tvdb_season ])
295+
296+ @patch ("app.services.item_merge.tmdb.resolve_tvdb_id_for_tmdb_show" )
297+ def test_missing_parent_id_does_not_match_same_title (self , mock_resolve ):
298+ tmdb_season , tvdb_season = self ._season_pair (parent_tvdb_id = "81189" )
299+ Item .objects .filter (pk = tmdb_season .pk ).update (provider_external_ids = {})
300+ Item .objects .filter (media_id = "1396" , source = Sources .TMDB .value ).update (
301+ provider_external_ids = {},
302+ )
303+
304+ result = item_merge .dedupe_cross_provider_items (
305+ [tmdb_season , tvdb_season ],
306+ Sources .TMDB .value ,
307+ )
308+
309+ self .assertCountEqual (result , [tmdb_season , tvdb_season ])
310+ mock_resolve .assert_not_called ()
311+
312+ def test_explicit_season_id_takes_precedence_over_parent (self ):
313+ tmdb_season , tvdb_season = self ._season_pair ()
314+ Item .objects .filter (
315+ media_id = "1396" ,
316+ source = Sources .TMDB .value ,
317+ media_type = MediaTypes .TV .value ,
318+ ).update (provider_external_ids = {"tvdb_id" : "99999" })
319+ tmdb_season .provider_external_ids = {"tvdb_id" : tvdb_season .media_id }
320+
321+ result = item_merge .dedupe_cross_provider_items (
322+ [tmdb_season , tvdb_season ],
323+ Sources .TVDB .value ,
324+ )
325+
326+ self .assertEqual (result , [tvdb_season ])
327+
328+
243329class FindCrossProviderDuplicateTests (TestCase ):
244330 """Verified-identity lookup only - never title matching."""
245331
0 commit comments