from unittest.mock import MagicMock, patch
def test_play_trailer_resolves_and_hands_off_to_kodi():
params = {
"yt_id": "dQw4w9WgXcQ",
"youtube_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"tmdb_id": "550",
"media_type": "movie",
"title": "Demo Movie",
}
list_item = MagicMock()
player = MagicMock()
with patch(
"lib.navigation.resolve_item_trailer",
return_value={
"trailer": {
"yt_id": "dQw4w9WgXcQ",
"youtube_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
},
"playback": {
"video_url": "https://video.example/trailer.m3u8",
"source_type": "hls",
},
},
) as resolve_item_trailer, patch(
"lib.navigation.ListItem", return_value=list_item
) as list_item_cls, patch(
"lib.navigation.notification"
) as notification, patch("xbmc.Player", return_value=player) as player_cls:
from lib.navigation import play_trailer
play_trailer(params)
resolve_item_trailer.assert_called_once_with(
yt_id="dQw4w9WgXcQ",
youtube_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
tmdb_id="550",
media_type="movie",
)
list_item_cls.assert_called_once_with(
label="Demo Movie",
path="https://video.example/trailer.m3u8",
)
list_item.setPath.assert_called_once_with("https://video.example/trailer.m3u8")
list_item.setProperty.assert_called_once_with("IsPlayable", "true")
player.play.assert_called_once_with("https://video.example/trailer.m3u8", list_item)
notification.assert_not_called()
def test_play_trailer_handles_adaptive_source_without_falling_back_to_youtube_addon():
list_item = MagicMock()
player = MagicMock()
with patch(
"lib.navigation.resolve_item_trailer",
return_value={
"trailer": {"yt_id": "dQw4w9WgXcQ"},
"playback": {
"video_url": "https://video.example/video-1080.mp4",
"audio_url": "https://video.example/audio-160.m4a",
"source_type": "adaptive",
},
},
), patch("lib.navigation.ListItem", return_value=list_item) as list_item_cls, patch(
"lib.navigation.execute_builtin"
) as execute_builtin, patch("xbmc.Player", return_value=player) as player_cls:
from lib.navigation import play_trailer
play_trailer({"tmdb_id": "550", "media_type": "movie", "title": "Demo Movie"})
list_item_cls.assert_called_once_with(
label="Demo Movie",
path="https://video.example/video-1080.mp4",
)
list_item.setPath.assert_called_once_with("https://video.example/video-1080.mp4")
assert list_item.setProperty.call_args_list == [
(("IsPlayable", "true"),),
(("inputstream.adaptive.manifest_type", "mpd"),),
]
player.play.assert_called_once_with("https://video.example/video-1080.mp4", list_item)
execute_builtin.assert_not_called()
def test_play_trailer_notifies_when_no_resolved_video_url():
with patch(
"lib.navigation.resolve_item_trailer",
return_value=None,
) as resolve_item_trailer, patch(
"lib.navigation.translation",
side_effect=lambda value: "Trailer unavailable" if value == 90673 else f"t-{value}",
), patch("lib.navigation.notification") as notification:
from lib.navigation import play_trailer
play_trailer({"tmdb_id": "1399", "media_type": "tv", "title": "Demo Show"})
resolve_item_trailer.assert_called_once_with(
yt_id=None,
youtube_url=None,
tmdb_id="1399",
media_type="tv",
)
notification.assert_called_once_with("Trailer unavailable")
def test_play_trailer_uses_mode_when_media_type_missing():
with patch(
"lib.navigation.resolve_item_trailer",
return_value={
"trailer": {"yt_id": None},
"playback": {"audio_url": "https://video.example/audio.m4a"},
},
) as resolve_item_trailer, patch(
"lib.navigation.translation",
side_effect=lambda value: "Trailer unavailable" if value == 90673 else f"t-{value}",
), patch("lib.navigation.notification") as notification:
from lib.navigation import play_trailer
play_trailer({"tmdb_id": "1399", "mode": "tv", "title": "Demo Show"})
resolve_item_trailer.assert_called_once_with(
yt_id=None,
youtube_url=None,
tmdb_id="1399",
media_type="tv",
)
notification.assert_called_once_with("Trailer unavailable")
def test_play_trailer_logs_when_resolution_fails():
with patch(
"lib.navigation.resolve_item_trailer",
return_value=None,
), patch("lib.navigation.kodilog") as log_mock, patch(
"lib.navigation.translation",
side_effect=lambda value: "Trailer unavailable" if value == 90673 else f"t-{value}",
), patch("lib.navigation.notification"):
from lib.navigation import play_trailer
play_trailer({"tmdb_id": "1399", "media_type": "tv", "title": "Demo Show"})
log_mock.assert_called()
def test_play_trailer_falls_back_to_youtube_addon_when_direct_playback_unavailable():
with patch(
"lib.navigation.resolve_item_trailer",
return_value={"trailer": {"yt_id": "dQw4w9WgXcQ"}, "playback": None},
) as resolve_item_trailer, patch(
"lib.navigation.is_youtube_addon_enabled",
return_value=True,
) as is_youtube_addon_enabled, patch(
"lib.navigation.execute_builtin"
) as execute_builtin, patch("lib.navigation.notification") as notification:
from lib.navigation import play_trailer
play_trailer(
{
"tmdb_id": "550",
"media_type": "movie",
"title": "Demo Movie",
"yt_id": "dQw4w9WgXcQ",
}
)
resolve_item_trailer.assert_called_once_with(
yt_id="dQw4w9WgXcQ",
youtube_url=None,
tmdb_id="550",
media_type="movie",
)
is_youtube_addon_enabled.assert_called_once_with()
execute_builtin.assert_called_once_with(
"PlayMedia(plugin://plugin.video.youtube/play/?video_id=dQw4w9WgXcQ)"
)
notification.assert_not_called()
def test_play_trailer_notifies_when_direct_playback_unavailable_and_no_youtube_id():
with patch(
"lib.navigation.resolve_item_trailer",
return_value=None,
) as resolve_item_trailer, patch(
"lib.navigation.is_youtube_addon_enabled",
return_value=True,
) as is_youtube_addon_enabled, patch(
"lib.navigation.translation",
side_effect=lambda value: "Trailer unavailable" if value == 90673 else f"t-{value}",
), patch("lib.navigation.execute_builtin") as execute_builtin, patch(
"lib.navigation.notification"
) as notification:
from lib.navigation import play_trailer
play_trailer(
{
"tmdb_id": "550",
"media_type": "movie",
"title": "Demo Movie",
}
)
resolve_item_trailer.assert_called_once_with(
yt_id=None,
youtube_url=None,
tmdb_id="550",
media_type="movie",
)
is_youtube_addon_enabled.assert_not_called()
execute_builtin.assert_not_called()
notification.assert_called_once_with("Trailer unavailable")
def test_play_trailer_falls_back_to_tmdb_derived_youtube_addon_playback():
with patch(
"lib.navigation.resolve_item_trailer",
return_value={
"trailer": {
"yt_id": "tmdb1234567",
"youtube_url": "https://www.youtube.com/watch?v=tmdb1234567",
},
"playback": None,
},
) as resolve_item_trailer, patch(
"lib.navigation.is_youtube_addon_enabled",
return_value=True,
) as is_youtube_addon_enabled, patch(
"lib.navigation.execute_builtin"
) as execute_builtin, patch("lib.navigation.notification") as notification:
from lib.navigation import play_trailer
play_trailer(
{
"tmdb_id": "550",
"media_type": "movie",
"title": "Demo Movie",
}
)
resolve_item_trailer.assert_called_once_with(
yt_id=None,
youtube_url=None,
tmdb_id="550",
media_type="movie",
)
is_youtube_addon_enabled.assert_called_once_with()
execute_builtin.assert_called_once_with(
"PlayMedia(plugin://plugin.video.youtube/play/?video_id=tmdb1234567)"
)
notification.assert_not_called()
def test_play_trailer_notifies_when_youtube_addon_is_missing_for_fallback():
with patch(
"lib.navigation.resolve_item_trailer",
return_value={
"trailer": {"youtube_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"},
"playback": None,
},
) as resolve_item_trailer, patch(
"lib.navigation.is_youtube_addon_enabled",
return_value=False,
) as is_youtube_addon_enabled, patch(
"lib.navigation.translation",
side_effect=lambda value: "Trailer unavailable" if value == 90673 else f"t-{value}",
), patch("lib.navigation.execute_builtin") as execute_builtin, patch(
"lib.navigation.notification"
) as notification:
from lib.navigation import play_trailer
play_trailer(
{
"tmdb_id": "550",
"media_type": "movie",
"title": "Demo Movie",
"youtube_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
}
)
resolve_item_trailer.assert_called_once_with(
yt_id=None,
youtube_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
tmdb_id="550",
media_type="movie",
)
is_youtube_addon_enabled.assert_called_once_with()
execute_builtin.assert_not_called()
notification.assert_called_once_with("Trailer unavailable")
def test_play_trailer_logs_unavailable_youtube_addon_fallback_details():
with patch(
"lib.navigation.resolve_item_trailer",
return_value={
"trailer": {
"yt_id": "tmdb1234567",
"youtube_url": "https://www.youtube.com/watch?v=tmdb1234567",
},
"playback": None,
},
), patch(
"lib.navigation.is_youtube_addon_enabled",
return_value=False,
), patch(
"lib.navigation.translation",
side_effect=lambda value: "Trailer unavailable" if value == 90673 else f"t-{value}",
), patch("lib.navigation.notification"), patch("lib.navigation.kodilog") as log_mock:
from lib.navigation import play_trailer
play_trailer(
{
"tmdb_id": "550",
"media_type": "movie",
"title": "Demo Movie",
}
)
logged_messages = [call.args[0] for call in log_mock.call_args_list]
assert (
"Trailer: youtube addon unavailable title='Demo Movie' media_type='movie' tmdb_id='550' fallback_yt_id='tmdb1234567'"
in logged_messages
)