def test_main_no_errors(self, mock_player): mock_player.get_current_url = mock.MagicMock() mock_player.dialog_ok = mock.MagicMock() addon.main() mock_player.dialog_ok.assert_not_called()
def test_index(mocker, index, main, xbmcplugin, ExtendedListItem): main() expected_results = [ (handle, plugin.format("profile"), u"Профиль", False), (handle, plugin.format("search?type=None"), u"Поиск", False), (handle, plugin.format("items?type=None"), u"Последние", True), (handle, plugin.format("items?type=None&shortcut=%2Fpopular"), u"Популярные", True), (handle, plugin.format("items?type=None&shortcut=%2Fhot"), u"Популярные", True), (handle, plugin.format("tv"), u"ТВ", True), (handle, plugin.format("bookmarks"), u"Закладки", True), (handle, plugin.format("watching"), u"Я смотрю", True), (handle, plugin.format("watching_movies"), u"Недосмотренные", True), (handle, plugin.format("collections"), u"Подборки", True), (handle, plugin.format("item_index?type=movie"), u"Фильмы", True), (handle, plugin.format("item_index?type=serial"), u"Сериалы", True), (handle, plugin.format("item_index?type=tvshow"), u"ТВ шоу", True), (handle, plugin.format("item_index?type=4k"), u"4K", True), (handle, plugin.format("item_index?type=3d"), u"3D", True), (handle, plugin.format("item_index?type=concert"), u"Концерты", True), (handle, plugin.format("item_index?type=documovie"), u"Документальные фильмы", True), (handle, plugin.format("item_index?type=docuserial"), u"Документальные сериалы", True) ] for result in expected_results: handle_, link, title, is_directory = result ExtendedListItem.assert_any_call(title.encode("utf-8")) li = ExtendedListItem() xbmcplugin.addDirectoryItem.assert_any_call(handle_, link, li, is_directory) xbmcplugin.endOfDirectory.assert_called_once_with(handle)
def test_view_seasons(main, view_seasons, ExtendedListItem, xbmcplugin): from resources.lib.utils import video_info main() item = actionView_seasons_response["item"] i = actionView_seasons_response["item"]["id"] seasons = actionView_seasons_response["item"]["seasons"] for season in seasons: ExtendedListItem.assert_any_call( "Сезон {}".format(season["number"]), video_info=video_info(item, { "season": season["number"], "playcount": -1, "mediatype": "season" }), poster=item["posters"]["big"], properties={"id": item["id"]}, addContextMenuItems=True, ) link = plugin.format( "view_season_episodes?season_number={}&id={}".format( season["number"], i)) xbmcplugin.addDirectoryItem.assert_any_call(handle, link, ExtendedListItem(), True) xbmcplugin.endOfDirectory.assert_called_once_with(handle, cacheToDisc=False)
def test_index(mocker, index, main, xbmcplugin, ExtendedListItem): from resources.lib.utils import build_icon_path main() expected_results = [ (handle, plugin.format("profile"), u"Профиль", "profile", False), (handle, plugin.format("search?type=None"), u"Поиск", "search", False), (handle, plugin.format("bookmarks"), u"Закладки", "bookmarks", True), (handle, plugin.format("watching"), u"Я смотрю", "watching", True), (handle, plugin.format("watching_movies"), u"Недосмотренные", "watching_movies", True), (handle, plugin.format("items?type=None"), u"Последние", "new", True), ( handle, plugin.format("items?type=None&shortcut=%2Fpopular"), u"Популярные", "popular", True, ), (handle, plugin.format("items?type=None&shortcut=%2Fhot"), u"Горячие", "hot", True), (handle, plugin.format("tv"), u"ТВ", "tv", True), (handle, plugin.format("collections"), u"Подборки", "collections", True), (handle, plugin.format("item_index?type=movie"), u"Фильмы", "movie", True), (handle, plugin.format("item_index?type=serial"), u"Сериалы", "serial", True), (handle, plugin.format("item_index?type=tvshow"), u"ТВ шоу", "tvshow", True), (handle, plugin.format("item_index?type=4k"), u"4K", "4k", True), (handle, plugin.format("item_index?type=3d"), u"3D", "3d", True), (handle, plugin.format("item_index?type=concert"), u"Концерты", "concert", True), ( handle, plugin.format("item_index?type=documovie"), u"Документальные фильмы", "documovie", True, ), ( handle, plugin.format("item_index?type=docuserial"), u"Документальные сериалы", "docuserial", True, ), ] for result in expected_results: handle_, link, title, icon, is_directory = result img = build_icon_path(icon) ExtendedListItem.assert_any_call(title.encode("utf-8"), iconImage=img, thumbnailImage=img) li = ExtendedListItem() xbmcplugin.addDirectoryItem.assert_any_call(handle_, link, li, is_directory) xbmcplugin.endOfDirectory.assert_called_once_with(handle)
def test_main_error_network(self, mock_player, mock_resolve): mock_resolve.side_effect = addon.kinoman_api.NetworkError mock_player.get_current_url = mock.MagicMock() mock_player.dialog_ok = mock.MagicMock() addon.main() mock_player.dialog_ok.assert_called_once_with( "Kinoman.Uz", "Проблема сети, попробуйте позже")
def test_main_error_missing_video(self, mock_player, mock_resolve): mock_resolve.side_effect = addon.kinoman_api.MissingVideoError mock_player.get_current_url = mock.MagicMock() mock_player.dialog_ok = mock.MagicMock() addon.main() mock_player.dialog_ok.assert_called_once_with("Kinoman.Uz", "Видео отсутствует")
def test_main_error_login_error(self, mock_player, mock_kinoman, mock_resolve): mock_kinoman.LoginError = Exception mock_resolve.side_effect = mock_kinoman.LoginError("test login error") mock_player.get_current_url = mock.MagicMock() mock_player.dialog_ok = mock.MagicMock() addon.main() mock_player.dialog_ok.assert_called_once_with( "Kinoman.Uz", "Ошибка авторизации: test login error")
def test_view_season_episodes(request, main, view_season_episodes, ExtendedListItem, xbmcplugin): from resources.lib.utils import video_info main() item = actionView_seasons_response["item"] season = item["seasons"][0] watching_season = watching_info_response_with_seasons["item"]["seasons"][ season["number"] - 1] i = item["id"] for episode in season["episodes"]: watching_episode = watching_season["episodes"][episode["number"] - 1] episode_title = "s{:02d}e{:02d}".format(season["number"], episode["number"]) if episode["title"]: episode_title = "{} | {}".format(episode_title, episode["title"].encode("utf-8")) info = video_info( item, { "season": season["number"], "episode": episode["number"], "tvshowtitle": episode["title"], "time": watching_episode["time"], "duration": watching_episode["duration"], "playcount": watching_episode["status"], "mediatype": "episode", }, ) link = plugin.format("play?{}".format( urlencode({ "id": i, "index": episode["number"] }))) ExtendedListItem.assert_any_call( episode_title, thumbnailImage=episode["thumbnail"], poster=item["posters"]["big"], video_info=info, properties={ "id": item["id"], "isPlayable": "true" }, addContextMenuItems=True, ) xbmcplugin.addDirectoryItem.assert_any_call(handle, link, ExtendedListItem(), False) xbmcplugin.setContent.assert_called_once_with(handle, "episodes") xbmcplugin.endOfDirectory.assert_called_once_with(handle, cacheToDisc=False)
def test_search(self, mock_print): with mock.patch( "sys.argv", ["plugin://test.plugin", "1", "/search/мастер и маргарита/"] ): addon.main() search_results = mock_print.call_args[0][0] for video in search_results: if video["path"] == "/movie/166/": self.assertEqual(video["label"], "Мастер и Маргарита (2005)") break else: self.fail("Test video not found in the listing, probably something wrong")
def test_search_filter(self, mock_print): with mock.patch("sys.argv", ["plugin://test.plugin", "1", "/search_filter/"]): addon.main() # 4 steps -> video category, age rating, genre, year for i in range(1, 5): next_menu = mock_print.call_args[0][0][1] with mock.patch( "sys.argv", ["plugin://test.plugin", "1", next_menu["path"]] ): mock_print.reset_mock() addon.main() if i <= 3: self.assertRegexpMatches( next_menu["path"], r"^/search_filter/{}/.*$".format(i) ) else: self.assertRegexpMatches(next_menu["path"], r"^/list_movies/.*$")
def test_view_episodes(request, main, view_episodes, ExtendedListItem, xbmcplugin): from resources.lib.addonutils import video_info main() item = actionView_without_seasons_response["item"] watching_info = watching_info_response_without_seasons["item"] for video in item["videos"]: watching_episode = watching_info["videos"][video["number"] - 1] episode_title = "e{:02d}".format(video["number"]) if video["title"]: episode_title = "{} | {}".format(episode_title, video["title"].encode("utf-8")) info = video_info( item, { "episode": video["number"], "playcount": video["watched"], "time": watching_episode["time"], "duration": watching_episode["duration"], "mediatype": "episode" }) link = plugin.format("play?{}".format( urlencode({ "id": item["id"], "title": episode_title, "video_data": json.dumps(video), "video_info": json.dumps(info), "poster": item["posters"]["big"] }))) ExtendedListItem.assert_any_call(episode_title, thumbnailImage=video["thumbnail"], video_info=info, poster=item["posters"]["big"], properties={ "id": item["id"], "isPlayable": "true" }, addContextMenuItems=True) xbmcplugin.addDirectoryItem.assert_any_call(handle, link, ExtendedListItem(), False) xbmcplugin.setContent.assert_called_once_with(handle, "episodes") xbmcplugin.endOfDirectory.assert_called_once_with(handle)
def test_play(play, main, ExtendedListItem, xbmcplugin): stream, video_quality = play main() title = actionPlay_response["item"]["title"].encode("utf-8") link = "https://example.com/{}/{}".format(stream, video_quality.rstrip("p")) ExtendedListItem.assert_called_with( title, path=link, properties={ "id": str(actionPlay_response["item"]["id"]), "play_duration": 0, "play_resumetime": 0, "video_number": 1, "season_number": "", "playcount": 0 }, poster=None, subtitles=[]) li = ExtendedListItem(title, path=link) xbmcplugin.setResolvedUrl.assert_called_once_with(handle, True, li)
def test_movie_playback(self, mock_print, mock_play): with mock.patch("sys.argv", ["plugin://test.plugin", "1", "/movie/73/"]): addon.main() video_types = mock_print.call_args[0][0] for video in video_types: self.assertRegexpMatches(video["path"], r"^/play/73/.*$") self.assertRegexpMatches(video["label"], r"^Воспроизвести \(.*\)$") with mock.patch("sys.argv", ["plugin://test.plugin", "1", video["path"]]): mock_play.reset_mock() addon.main() playback_url = mock_play.call_args[0][0] self.assertRegexpMatches( playback_url, r"^https://(online|dl)[0-9]+\.kinoman\.uz/dl/(online|movie)" r"/[0-9]+/.*\..*?$", )
def test_search_history(self, mock_print): addon.player.set_setting("search_history_status", True) addon.search_clear() with mock.patch( "sys.argv", ["plugin://test.plugin", "1", "/search/test search/"] ): addon.main() mock_print.reset_mock() with mock.patch("sys.argv", ["plugin://test.plugin", "1", "/"]): addon.main() main_menu = mock_print.call_args[0][0] for item in main_menu: if item["path"] == "/search_history/": break else: self.fail("Search history menu is missing with nonempty history") mock_print.reset_mock() with mock.patch("sys.argv", ["plugin://test.plugin", "1", "/search_history/"]): addon.main() search_menu = mock_print.call_args[0][0] self.assertEqual(search_menu[-1]["path"], "/search/test%20search/") self.assertEqual(search_menu[-1]["label"], "test search") addon.search_clear() addon.player.set_setting("search_history_status", False)
def test_series_playback(self, mock_print, mock_play): with mock.patch("sys.argv", ["plugin://test.plugin", "1", "/movie/166/"]): addon.main() video_types = mock_print.call_args[0][0] for video in video_types: self.assertRegexpMatches(video["path"], r"^/movie/166/[a-z]+/$") self.assertRegexpMatches(video["label"], r"^Смотреть серии \(.*\)$") with mock.patch("sys.argv", ["plugin://test.plugin", "1", video["path"]]): mock_print.reset_mock() addon.main() episodes_list = mock_print.call_args[0][0] for e_video in [episodes_list[0], episodes_list[-1]]: self.assertRegexpMatches( e_video["path"], r"^/play/166/.*?$", ) with mock.patch( "sys.argv", ["plugin://test.plugin", "1", e_video["path"]] ): mock_play.reset_mock() addon.main() playback_url = mock_play.call_args[0][0] self.assertRegexpMatches( playback_url, r"^https://(online|dl)[0-9]+\.kinoman\.uz/dl/(online|movie)" r"/[0-9]+/.*\..*?$", )
def test_items(main, items, ExtendedListItem, xbmcplugin, mocker): from resources.lib.addonutils import video_info, trailer_link from resources.lib.addonworker import mediatype_map main() def make_info(item): extra_info = { "trailer": trailer_link(item), "mediatype": mediatype_map[item["type"]] } if item["type"] not in ["serial", "docuserial", "tvshow"]: extra_info.update({"time": 0, "duration": 1, "playcount": 0}) return json.dumps(video_info(item, extra_info)) expected_results = [] for item in actionItems_response["items"]: expected_results.append({ "title": item["title"].encode("utf-8"), "id": item["id"], "poster": item["posters"]["big"], "video_info": make_info(item) }) links = [ plugin.format("play?{}".format(urlencode(expected_results[0]))), plugin.format("play?{}".format(urlencode(expected_results[1]))), plugin.format("view_seasons?id={}".format(expected_results[2]["id"])), plugin.format("view_seasons?id={}".format(expected_results[3]["id"])), plugin.format("view_seasons?id={}".format(expected_results[4]["id"])), ] is_dirs = [False, False, True, True, True] for result, link, is_dir in zip(expected_results, links, is_dirs): ExtendedListItem.assert_any_call( result["title"], poster=result["poster"], properties={"id": result["id"]}, ) li = ExtendedListItem() xbmcplugin.addDirectoryItem.assert_any_call(handle, link, li, is_dir) xbmcplugin.endOfDirectory.assert_called_once_with(handle)
def test_movie_sections(self, mock_print): with mock.patch("sys.argv", ["plugin://test.plugin", "1", "/"]): addon.main() movie_sections = mock_print.call_args[0][0] for section in movie_sections: if section["label"] == "Избранное": break with mock.patch("sys.argv", ["plugin://test.plugin", "1", section["path"]]): mock_print.reset_mock() addon.main() movies_list = mock_print.call_args[0][0] self.assertRegexpMatches(movies_list[0]["path"], r"^/movie/[0-9]+/$") # Checking if paginator works by going to last two pages # of the list (if this list has multiple pages) if movies_list[-1]["label"].startswith("-->"): last_page = int( re.search( r"^--> \(2 / ([0-9]+)\)$", movies_list[-1]["label"] ).group(1) ) for page_n in range(last_page - 1, last_page + 1): with mock.patch( "sys.argv", [ "plugin://test.plugin", "1", section["path"] + "&page={}".format(page_n), ], ): mock_print.reset_mock() addon.main() page_movies = mock_print.call_args[0][0] # I think there is an off-by-one error somewhere on server # side, it glitched once, so putting this special case here if not page_movies and page_n == last_page: continue if page_n == last_page - 1: self.assertRegexpMatches( page_movies[-1]["label"], r"^--> \({} / {}\)$".format(last_page, last_page), ) elif page_n == last_page: self.assertNotRegexpMatches( page_movies[-1]["label"], r"^--> \([0-9]+ / [0-9]+\)$", )
# import the kodi python modules we are going to use # see the kodi api docs to find out what functionality each module provides import xbmc import xbmcaddon import sys import addon # getting the assetTYPE so we can later confirm that it's a movie or tvshow assetTYPE = xbmc.getInfoLabel('ListItem.DBtype') #calling the main function if assetTYPE is movie or tvshow if assetTYPE == 'movie' or assetTYPE == 'tvshow': addon.main() else: assetMsg = "Invalid item selected: '%s'" % assetTYPE xbmc.executebuiltin("Notification(\"Tag Quick Edit\", \"%s\")" % assetMsg)
def exploration_loop(log): import addon start_time = time.time() entry_point_reached = False # Init Route to root root = Route(label='Root menu', path=[1]) Route.add_route_to_explore(root) while (Route.continue_epxloration()): log.debug('Loop entry') current_route = Route.get_route_to_explore() # Check if the entry point was reached if len(current_route.path) == len( Config.get('entry_point')) and not entry_point_reached: log.debug('Entry point ({}) reached'.format( Config.get('entry_point'))) entry_point_reached = True # Hock sys.argv with mock.patch('sys.argv', current_route.get_fake_sys_argv()): if Config.get('autoreload_addon'): # We need to reload the addon module in order to be able # to modify the source code of the addon on the fly without restarting # the simulator # (usefull during dev) for k, v in sys.modules.items(): # print(str(k) + ' :: ' + str(v)) if 'plugin.video.catchuptvandmore' in str(v) and \ 'plugin.video.catchuptvandmore/addon.py' not in str(v): # print('\tRELOAD') reload(v) reload(addon) # We need to clean this var RuntimeErrorCQ.last_error_message = "" # Simulate the addon execution addon.main() # We have to know the next item to epxlore # If next_item = -2 we just reload the addon we the same route next_item = -2 # If an error was trigger if RuntimeErrorCQ.last_menu_triggered_error: log.warn('') log.warn( WARNING + ' The last selection triggered an error (see log above) ' + WARNING) log.warn('') error = RuntimeErrorCQ(path=Route.pretty_exploring_routes()) print(error) RuntimeErrorCQ.reset_error_trigger() if len(RuntimeErrorCQ.all_errors) >= Config.get( 'exit_after_x_errors'): log.info( 'Max number of error reached ({}) --> Exit'.format( Config.get('exit_after_x_errors'))) next_item = -1 else: log.info( '[DEBUG] Max number of error not reached --> Go back') next_item = 0 # Else if the current directory is a playable item elif Directory.is_current_directory_playable(): item = Directory.current_directory.items[1] log.info('Playable URL: {}'.format(item.url)) next_item = 0 if not Config.get('disable_video_player'): player = Player(item.url) player.play() # Else if succeeded is False (Happen when "No video found" notif is trigger) elif Directory.current_directory.succeeded is False: log.info( 'endOfDirectory was called with succeeded=False --> Go back' ) next_item = 0 # Else print the current directory else: print(Directory.current_directory) print(Route.pretty_exploring_routes() + '\n') # If the entry_point was not reached we follow the entry point path if not entry_point_reached: next_item = Config.get('entry_point')[len( current_route.path)] if next_item == 'R': next_item = randint( 1, len(Directory.current_directory.items)) log.info( 'Entry point not yet reached --> random next item: {}' .format(next_item)) else: next_item = int(next_item) log.info( 'Entry point not yet reached --> next item: {}'. format(next_item)) # Else if we are in auto exploration elif Config.get('auto_exploration'): log.debug('Auto exploration mode') # If needed, add items of the current menu to explore later AutoExploration.add_items_current_menu( current_route.path, Directory.current_directory) # We wait a fake time sys.stdout.flush() time.sleep(Config.get('wait_time')) # We ask for the next item to epxlore next_item = AutoExploration.next_item_to_explore( current_route.path, Directory.current_directory) log.info( 'next_item selected by auto exploration: {}'.format( next_item)) # Else we ask the user to choose the next item number else: # We wait the user input try: sys.stdout.flush() next_item = int( input( 'Next item to select? (-1 to exit, <enter> to reload same directory)\n' )) log.info('Choosen next_item: {}'.format(next_item)) except Exception: pass print('') # Check for timeout delta_time = time.time() - start_time if Config.get('timeout') != -1 and delta_time >= Config.get( 'timeout'): log.warn('AUTO EXPLORATION TIMEOUT --> Exit exploration') next_item = -1 # Else if the user wants to exit the simulator, let's break the loop if next_item == -1: break if Directory.current_directory is None: next_item = -2 else: # If there is no item for this value, reload the same menu to prevent error if next_item > len(Directory.current_directory.items) or ( next_item == 0 and len(current_route.path) <= 1): next_item = -2 # If next_item has the default value just reload the same menu if next_item == -2: pass # Else if the user want to go back in the previous menu elif next_item == 0: Route.previous_route() # Else if the user want an item else: selected_item = Directory.current_directory.items[next_item] Route.add_item_to_explore(selected_item)
def test_root(self, mock_print): expected_result = [ { "label": "Фильмы", "path": "/list_movies/?category=movies&" "content_type_id=1&genre_black_list=12", "is_folder": True, }, { "label": "Фильмы (премьеры)", "path": "/list_movies/?category=movies&" "content_type_id=1&genre_black_list=12&sort_type=1", "is_folder": True, }, { "label": "Сериалы", "path": "/list_movies/?category=tv-series&" "content_type_id=2&genre_black_list=12", "is_folder": True, }, { "label": "Сериалы (премьеры)", "path": "/list_movies/?category=tv-series&" "content_type_id=2&genre_black_list=12&sort_type=1", "is_folder": True, }, { "label": "Мультфильмы", "path": "/list_movies/?category=cartoons&" "content_type_id=0&genre_list=12", "is_folder": True, }, { "label": "Мультфильмы (премьеры)", "path": "/list_movies/?category=cartoons&" "content_type_id=0&genre_list=12&sort_type=1", "is_folder": True, }, { "label": "ТВ-программы", "path": "/list_movies/?category=tv-shows&" "content_type_id=3&genre_black_list=12", "is_folder": True, }, { "label": "ТВ-программы (премьеры)", "path": "/list_movies/?category=tv-shows&" "content_type_id=3&genre_black_list=12&sort_type=1", "is_folder": True, }, { "label": "Видеоблоги", "path": "/list_movies/?category=vlogs&" "content_type_id=4&genre_black_list=12", "is_folder": True, }, { "label": "Видеоблоги (премьеры)", "path": "/list_movies/?category=vlogs&" "content_type_id=4&genre_black_list=12&sort_type=1", "is_folder": True, }, { "label": "Избранное", "path": "/list_movies/?category=favorite&content_type_id=0&favorite=1" "&title=%D0%B8%D0%B7%D0%B1%D1%80%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5", "is_folder": True, }, { "label": "Поиск по фильтру", "path": "/search_filter/", "video_data": {"art": {"icon": "DefaultAddonsSearch.png"}}, "is_folder": True, }, { "label": "Поиск", "path": "/search/", "video_data": {"art": {"icon": "DefaultAddonsSearch.png"}}, "is_folder": True, }, ] with mock.patch("sys.argv", ["plugin://test.plugin", "1", "/"]): addon.main() mock_print.assert_called_once_with(expected_result, cache=False)