diff --git a/minigalaxy/ui/library.py b/minigalaxy/ui/library.py index 99d7bad7..7628c762 100644 --- a/minigalaxy/ui/library.py +++ b/minigalaxy/ui/library.py @@ -139,7 +139,7 @@ def __get_installed_games(self) -> List[Game]: os.makedirs(library_dir, mode=0o755) directories = os.listdir(library_dir) games = [] - game_categories_dict = read_game_categories_file() + game_categories_dict = read_game_categories_file(CATEGORIES_FILE_PATH) for directory in directories: full_path = os.path.join(self.config.install_dir, directory) # Only scan directories @@ -184,7 +184,7 @@ def __add_games_from_api(self): self.games[self.games.index(game)].category = game.category if len(game.category) > 0: # exclude games without set category game_category_dict[game.name] = game.category - update_game_categories_file(game_category_dict) + update_game_categories_file(game_category_dict, CATEGORIES_FILE_PATH) def get_installed_windows_games(full_path, game_categories_dict=None): @@ -205,14 +205,14 @@ def get_installed_windows_games(full_path, game_categories_dict=None): return games -def update_game_categories_file(game_category_dict): +def update_game_categories_file(game_category_dict, categories_file_path): if len(game_category_dict) == 0: return - if not os.path.exists(CATEGORIES_FILE_PATH): # if file does not exist, create it and write dict - with open(CATEGORIES_FILE_PATH, 'wt') as fd: + if not os.path.exists(categories_file_path): # if file does not exist, create it and write dict + with open(categories_file_path, 'wt') as fd: json.dump(game_category_dict, fd) else: - with open(CATEGORIES_FILE_PATH, 'r+t') as fd: # if file exists, write dict only if not equal to file data + with open(categories_file_path, 'r+t') as fd: # if file exists, write dict only if not equal to file data cached_game_category_dict = json.load(fd) if game_category_dict != cached_game_category_dict: fd.seek(os.SEEK_SET) @@ -220,9 +220,9 @@ def update_game_categories_file(game_category_dict): json.dump(game_category_dict, fd) -def read_game_categories_file(): +def read_game_categories_file(categories_file_path): cached_game_category_dict = {} - if os.path.exists(CATEGORIES_FILE_PATH): - with open(CATEGORIES_FILE_PATH, 'rt') as fd: + if os.path.exists(categories_file_path): + with open(categories_file_path, 'rt') as fd: cached_game_category_dict = json.load(fd) return cached_game_category_dict diff --git a/tests/test_api.py b/tests/test_api.py index be364763..ef394efb 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -19,7 +19,7 @@ API_GET_INFO_STELLARIS = [{'id': '51622789000874509', 'game_id': '51154268886064420', 'platform_id': 'gog', 'external_id': '1508702879', 'game': {'genres': [{'id': '51071904337940794', 'name': {'*': 'Strategy', 'en-US': 'Strategy'}, 'slug': 'strategy'}], 'summary': {'*': 'Stellaris description'}, 'visible_in_library': True, 'aggregated_rating': 78.5455, 'game_modes': [{'id': '53051895165351137', 'name': 'Single player', 'slug': 'single-player'}, {'id': '53051908711988230', 'name': 'Multiplayer', 'slug': 'multiplayer'}], 'horizontal_artwork': {'url_format': 'https://images.gog.com/742acfb77ec51ca48c9f96947bf1fc0ad8f0551c9c9f338021e8baa4f08e449f{formatter}.{ext}?namespace=gamesdb'}, 'background': {'url_format': 'https://images.gog.com/742acfb77ec51ca48c9f96947bf1fc0ad8f0551c9c9f338021e8baa4f08e449f{formatter}.{ext}?namespace=gamesdb'}, 'vertical_cover': {'url_format': 'https://images.gog.com/8d822a05746670fb2540e9c136f0efaed6a2d5ab698a9f8bd7f899d21f2022d2{formatter}.{ext}?namespace=gamesdb'}, 'cover': {'url_format': 'https://images.gog.com/8d822a05746670fb2540e9c136f0efaed6a2d5ab698a9f8bd7f899d21f2022d2{formatter}.{ext}?namespace=gamesdb'}, 'logo': {'url_format': 'https://images.gog.com/c50a5d26c42d84b4b884976fb89d10bb3e97ebda0c0450285d92b8c50844d788{formatter}.{ext}?namespace=gamesdb'}, 'icon': {'url_format': 'https://images.gog.com/c85cf82e6019dd52fcdf1c81d17687dd52807835f16aa938abd2a34e5d9b99d0{formatter}.{ext}?namespace=gamesdb'}, 'square_icon': {'url_format': 'https://images.gog.com/c3adc81bf37f1dd89c9da74c13967a08b9fd031af4331750dbc65ab0243493c8{formatter}.{ext}?namespace=gamesdb'}}}] GAMESDB_INFO_STELLARIS = {'cover': 'https://images.gog.com/8d822a05746670fb2540e9c136f0efaed6a2d5ab698a9f8bd7f899d21f2022d2.png?namespace=gamesdb', 'vertical_cover': 'https://images.gog.com/8d822a05746670fb2540e9c136f0efaed6a2d5ab698a9f8bd7f899d21f2022d2.png?namespace=gamesdb', 'background': 'https://images.gog.com/742acfb77ec51ca48c9f96947bf1fc0ad8f0551c9c9f338021e8baa4f08e449f.png?namespace=gamesdb', 'summary': {'*': 'Stellaris description'}, 'genre': {'*': 'Strategy', 'en-US': 'Strategy'}} API_GET_INFO_BLACKWELL = [{'id': '51437500425760439', 'game_id': '51295394952128810', 'platform_id': 'gog', 'external_id': '1207662883', 'dlcs_ids': [], 'dlcs': [], 'parent_id': None, 'supported_operating_systems': [{'slug': 'linux', 'name': 'Linux'}, {'slug': 'osx', 'name': 'macOS'}, {'slug': 'windows', 'name': 'Windows'}], 'available_languages': [{'code': 'en-US'}], 'first_release_date': '2006-12-23T00:00:00+0000', 'game': {'id': '51295394952128810', 'parent_id': None, 'dlcs_ids': [], 'first_release_date': '2006-12-23T00:00:00+0000', 'releases': [{'id': '51295394972780958', 'platform_id': 'steam', 'external_id': '80330', 'release_per_platform_id': 'steam_80330'}, {'id': '51437500425760439', 'platform_id': 'gog', 'external_id': '1207662883', 'release_per_platform_id': 'gog_1207662883'}, {'id': '52472176412391232', 'platform_id': 'humble', 'external_id': 'theblackwelllegacy', 'release_per_platform_id': 'humble_theblackwelllegacy'}, {'id': '52990950679797056', 'platform_id': 'humble', 'external_id': 'blackwelllegacy_steam', 'release_per_platform_id': 'humble_blackwelllegacy_steam'}, {'id': '54824915113880126', 'platform_id': 'amiga', 'external_id': 'blackwell1', 'release_per_platform_id': 'amiga_blackwell1'}, {'id': '56154123001776496', 'platform_id': 'humble', 'external_id': 'blackwelllegacy_bundle_steam', 'release_per_platform_id': 'humble_blackwelllegacy_bundle_steam'}, {'id': '51295394952128810', 'platform_id': 'generic', 'external_id': '51295394952128810', 'release_per_platform_id': 'generic_51295394952128810'}], 'title': {'*': 'The Blackwell Legacy', 'en-US': 'The Blackwell Legacy'}, 'sorting_title': {'*': 'Blackwell Legacy', 'en-US': 'Blackwell Legacy'}, 'type': 'game', 'developers_ids': ['51141380061810434'], 'developers': [{'id': '51141380061810434', 'name': 'Wadjet Eye Games', 'slug': 'wadjet-eye-games'}], 'publishers_ids': ['51141380061810434'], 'publishers': [{'id': '51141380061810434', 'name': 'Wadjet Eye Games', 'slug': 'wadjet-eye-games'}], 'genres_ids': ['51071842251704278', '51121492616405278', '51141224673762034', '51141224986801358'], 'genres': [{'id': '51071842251704278', 'name': {'*': 'Adventure', 'en-US': 'Adventure'}, 'slug': 'adventure'}, {'id': '51121492616405278', 'name': {'*': 'Indie', 'en-US': 'Indie'}, 'slug': 'indie'}, {'id': '51141224673762034', 'name': {'*': 'Puzzle', 'en-US': 'Puzzle'}, 'slug': 'puzzle'}, {'id': '51141224986801358', 'name': {'*': 'Point-and-click', 'en-US': 'Point-and-click'}, 'slug': 'point-and-click'}], 'themes_ids': ['51141224799400616', '51141227696328058', '51141227910729860'], 'themes': [{'id': '51141224799400616', 'name': {'*': 'Comedy', 'en-US': 'Comedy'}, 'slug': 'comedy'}, {'id': '51141227696328058', 'name': {'*': 'Mystery', 'en-US': 'Mystery'}, 'slug': 'mystery'}, {'id': '51141227910729860', 'name': {'*': 'Drama', 'en-US': 'Drama'}, 'slug': 'drama'}], 'screenshots': [{'url_format': 'https://images.gog.com/368f07e0edcce9191e60a004b0ef7bf8d9a97eac65b66e5e497f9e2728045255{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/075326e70912449e02c3d4593a26317ad8d7f8ba5f2d0d3aa216a5a467130f34{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/2c71a940d797c466a563ecfc132933300ce37bd996140dbaaf48333d2f628fb3{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/d8c490c12f0534adeb69fdeb467a4719d24765a85a58a880f9aab13008d14683{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/5724fb0d43b4b753e89a6dec52b40a8c401396d6b5ecaa228115a3b06c5fd374{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/7478e2b53e403ee04eb27331ecebad0d6e3403e76c747fffd6f5b2303005b174{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/bd6f6ba043f9ae0883c196086841891ac5ed11dcf933ab2ca3f104312be10802{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/c37db76ad0d3f6471a5bbac1cc5a17393ee5a2c8ae16150cc221aac5855dd788{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/9ec05f3b79a0e3bd9a2b4eb29788222b5723feb6273b6a0bbb711d53d6a93b25{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/7376efd38b2aa383e16d066c6cfbc78fefa9b870f91033666a08bef85bac6db9{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/78145ccd85eca1fec952a464b3b9aeea7b5288518282e8194e3a0991fd9aef77{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/d46d89dfabe116db2f428138d3a3481faf91e00759ef97e02645da8f2652df3a{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/01e93965ea0ce72fdb50472e08a11924a9c5aa45f86a6aed4deeff987010df34{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/c2f064852161738df48918111327c108cdb014bffff487caa13caa2f7c2d5750{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/5cedd80d5f46fa20d4f95e9b8305d608b44c098b4741d2347e0365c2c9932cda{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/b80f003398c1d4e63ae2054665ee90785a9ea526b371859269e92c51ac7840c1{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/702fe48f311ef0d398e219cf28eda78b64bf3104dda937983b7ec621d0845caa{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/73b9adb70ec1c2bfc75cd6286a4b1f5c81959a4e25ce4e0527dd9a61d28c7953{formatter}.{ext}?namespace=gamesdb'}], 'videos': [{'provider': 'youtube', 'video_id': 'j026WKDQhi8', 'thumbnail_id': 'j026WKDQhi8', 'name': 'Trailer'}], 'artworks': [{'url_format': 'https://images.gog.com/19eba3c8303ce3cdb45cc9f2f94993efd2620f7f12eeac814512e64d26e88f0f{formatter}.{ext}?namespace=gamesdb'}, {'url_format': 'https://images.gog.com/978a934689c303511c425450c62db88edd11ac4da9026d801159decf605cd93e{formatter}.{ext}?namespace=gamesdb'}], 'summary': {'*': "omitted for test", 'en-US': "omitted for test"}, 'visible_in_library': True, 'aggregated_rating': 70, 'game_modes': [{'id': '53051895165351137', 'name': 'Single player', 'slug': 'single-player'}], 'horizontal_artwork': {'url_format': 'https://images.gog.com/e6e6d9001ec0c990327bd8f20630b5e39d0b5d33f69778163025d9d7b04f3c44{formatter}.{ext}?namespace=gamesdb'}, 'background': {'url_format': 'https://images.gog.com/e6e6d9001ec0c990327bd8f20630b5e39d0b5d33f69778163025d9d7b04f3c44{formatter}.{ext}?namespace=gamesdb'}, 'vertical_cover': {'url_format': 'https://images.gog.com/87f87afa3a55ca5eb59df9a66c5063c202281432262b3f0fe8ef8e324270df61{formatter}.{ext}?namespace=gamesdb'}, 'cover': {'url_format': 'https://images.gog.com/87f87afa3a55ca5eb59df9a66c5063c202281432262b3f0fe8ef8e324270df61{formatter}.{ext}?namespace=gamesdb'}, 'logo': {'url_format': 'https://images.gog.com/a82f4f793fc7e981b7a8d429508b11ba7ec5783f3ae5db6a8d3508bd139ba122{formatter}.{ext}?namespace=gamesdb'}, 'square_icon': {'url_format': 'https://images.gog.com/978a934689c303511c425450c62db88edd11ac4da9026d801159decf605cd93e{formatter}.{ext}?namespace=gamesdb'}, 'global_popularity_all_time': 0, 'global_popularity_current': 0, 'series': {'id': '53060497266141230', 'name': 'Blackwell', 'slug': 'blackwell'}}, 'title': {'*': 'Blackwell Legacy', 'en-US': 'The Blackwell Legacy'}, 'sorting_title': {'*': 'Blackwell Legacy', 'en-US': 'Blackwell Legacy'}, 'type': 'game', 'summary': {'*': "When Rosa Blackwell's only relative dies after twenty years in a coma, she thinks the worst is over. This all changes when Joey Mallone, a sardonic ghost from the 1930s, blows into her life and tells her that she is a medium. Whether they like it or not, it is up to them to cure the supernatural ills of New York in this critically-acclaimed series of point-and-click adventure games. \r\nWhen three NYU students kill themselves one after the other, nobody thinks that a sinister force is at work. Nobody but fledgling medium Rosa Blackwell and her new spirit guide Joey Mallone. It's trial by fire as they set these troubled spirits to rest.", 'en-US': "When Rosa Blackwell's only relative dies after twenty years in a coma, she thinks the worst is over. This all changes when Joey Mallone, a sardonic ghost from the 1930s, blows into her life and tells her that she is a medium. Whether they like it or not, it is up to them to cure the supernatural ills of New York in this critically-acclaimed series of point-and-click adventure games.\nWhen three NYU students kill themselves one after the other, nobody thinks that a sinister force is at work. Nobody but fledgling medium Rosa Blackwell and her new spirit guide Joey Mallone. It's trial by fire as they set these troubled spirits to rest."}, 'videos': [{'provider': 'youtube', 'video_id': 'vkinYRD5sr4', 'thumbnail_id': 'vkinYRD5sr4', 'name': None}, {'provider': 'youtube', 'video_id': 'gLcoCPfc1zE', 'thumbnail_id': 'gLcoCPfc1zE', 'name': None}], 'game_modes': [{'id': '53051895165351137', 'name': 'Single player', 'slug': 'single-player'}], 'logo': {'url_format': 'https://images.gog.com/a82f4f793fc7e981b7a8d429508b11ba7ec5783f3ae5db6a8d3508bd139ba122{formatter}.{ext}?namespace=gamesdb'}, 'series': {'id': '53060497266141230', 'name': 'Blackwell', 'slug': 'blackwell'}}] -GAMESDB_INFO_BLACKWELL = {'background': 'https://images.gog.com/e6e6d9001ec0c990327bd8f20630b5e39d0b5d33f69778163025d9d7b04f3c44.png?namespace=gamesdb','cover': 'https://images.gog.com/87f87afa3a55ca5eb59df9a66c5063c202281432262b3f0fe8ef8e324270df61.png?namespace=gamesdb','genre': {'*': 'Adventure, Indie, Puzzle, Point-and-click', 'en-US': 'Adventure, Indie, Puzzle, Point-and-click'},'summary': {'*': 'omitted for test', 'en-US': 'omitted for test'},'vertical_cover': 'https://images.gog.com/87f87afa3a55ca5eb59df9a66c5063c202281432262b3f0fe8ef8e324270df61.png?namespace=gamesdb'} +GAMESDB_INFO_BLACKWELL = {'background': 'https://images.gog.com/e6e6d9001ec0c990327bd8f20630b5e39d0b5d33f69778163025d9d7b04f3c44.png?namespace=gamesdb', 'cover': 'https://images.gog.com/87f87afa3a55ca5eb59df9a66c5063c202281432262b3f0fe8ef8e324270df61.png?namespace=gamesdb', 'genre': {'*': 'Adventure, Indie, Puzzle, Point-and-click', 'en-US': 'Adventure, Indie, Puzzle, Point-and-click'}, 'summary': {'*': 'omitted for test', 'en-US': 'omitted for test'}, 'vertical_cover': 'https://images.gog.com/87f87afa3a55ca5eb59df9a66c5063c202281432262b3f0fe8ef8e324270df61.png?namespace=gamesdb'} class TestApi(TestCase): @@ -113,7 +113,7 @@ def test1_get_library(self): config = MagicMock() api = Api(config, session) api.active_token = True - response_dict = {'totalPages': 1, 'products': [{'id': 1097893768, 'title': 'Neverwinter Nights: Enhanced Edition', 'image': '//images-2.gog-statics.com/8706f7fb87a4a41bc34254f3b49f59f96cf13d067b2c8bbfd8d41c327392052a', 'url': '/game/neverwinter_nights_enhanced_edition_pack', 'worksOn': {'Windows': True, 'Mac': True, 'Linux': True}}]} + response_dict = {'totalPages': 1, 'products': [{'id': 1097893768, 'title': 'Neverwinter Nights: Enhanced Edition', 'image': '//images-2.gog-statics.com/8706f7fb87a4a41bc34254f3b49f59f96cf13d067b2c8bbfd8d41c327392052a', 'url': '/game/neverwinter_nights_enhanced_edition_pack', 'worksOn': {'Windows': True, 'Mac': True, 'Linux': True}, "category": "Role-playing"}]} api.active_token_expiration_time = time.time() + 10.0 response_mock = MagicMock() response_mock.json.return_value = response_dict diff --git a/tests/test_ui_library.py b/tests/test_ui_library.py index be9fd81d..30774159 100644 --- a/tests/test_ui_library.py +++ b/tests/test_ui_library.py @@ -1,6 +1,10 @@ +import json +import os import sys +import uuid from unittest import TestCase, mock from unittest.mock import MagicMock, patch, mock_open +import tempfile m_gtk = MagicMock() m_gi = MagicMock() @@ -8,6 +12,7 @@ m_preferences = MagicMock() m_gametile = MagicMock() m_gametilelist = MagicMock() +m_categoryfilters = MagicMock() class UnitTestGtkTemplate: @@ -56,8 +61,10 @@ class Notify: sys.modules['minigalaxy.ui.preferences'] = m_preferences sys.modules['minigalaxy.ui.gametile'] = m_gametile sys.modules['minigalaxy.ui.gametilelist'] = m_gametilelist +sys.modules['minigalaxy.ui.categoryfilters'] = m_categoryfilters from minigalaxy.game import Game # noqa: E402 -from minigalaxy.ui.library import Library, get_installed_windows_games # noqa: E402 +from minigalaxy.ui.library import Library, get_installed_windows_games, read_game_categories_file, \ + update_game_categories_file # noqa: E402 SELF_GAMES = {"Neverwinter Nights: Enhanced Edition": "1097893768", "Beneath A Steel Sky": "1207658695", "Stellaris (English)": "1508702879"} @@ -204,6 +211,58 @@ def test2_get_installed_windows_game(self, mock_listdir): obs = games[0].name self.assertEqual(exp, obs) + def test_read_game_categories_file_should_return_populated_dict(self): + with tempfile.NamedTemporaryFile(mode='w+t', delete=False) as tmpfile: + tmpfile.write('{"Test Game":"Adventure"}') + tmpfile.flush() + + actual = read_game_categories_file(tmpfile.name) + + self.assertTrue(len(actual)) + self.assertEqual(actual, {'Test Game': 'Adventure'}) + + @mock.patch('os.path.exists') + def test_update_game_categories_file_should_skip_for_empty_dict(self, mock_path_exists: MagicMock): + mock_path_exists.side_effect = Exception("Test error") + + update_game_categories_file({}, None) + + self.assertFalse(mock_path_exists.called) + + def test_update_game_categories_file_should_create_file_if_not_found(self): + initially_non_existent_file = f'/tmp/{uuid.uuid4()}.json' + self.assertFalse(os.path.exists(initially_non_existent_file)) + expected = {'Test game': 'Adventure'} + + update_game_categories_file(expected, initially_non_existent_file) + + self.assertTrue(os.path.exists(initially_non_existent_file)) + self.assertDictEqual(expected, read_game_categories_file(initially_non_existent_file)) + + def test_update_game_categories_file_should_skip_if_file_found_with_identical_contents(self): + expected = {"Test Game": "Adventure"} + with tempfile.NamedTemporaryFile(mode='r+t', delete=False) as tmpfile: + json.dump(expected, tmpfile) + tmpfile.flush() + + update_game_categories_file(expected, tmpfile.name) + + tmpfile.seek(os.SEEK_SET) + actual = json.load(tmpfile) + self.assertDictEqual(actual, expected) + + def test_update_game_categories_file_should_overwrite_file_if_contents_differ(self): + with tempfile.NamedTemporaryFile(mode='w+t', delete=False) as tmpfile: + tmpfile.write('{"Test Game":"Adventure"}') + tmpfile.flush() + expected = {"Test Game": "Adventure", "Another Game": "Strategy"} + + update_game_categories_file(expected, tmpfile.name) + + tmpfile.seek(os.SEEK_SET) + actual = json.load(tmpfile) + self.assertDictEqual(actual, expected) + del sys.modules['gi'] del sys.modules['gi.repository'] @@ -211,3 +270,4 @@ def test2_get_installed_windows_game(self, mock_listdir): del sys.modules['minigalaxy.ui.preferences'] del sys.modules['minigalaxy.ui.gametile'] del sys.modules['minigalaxy.ui.gametilelist'] +del sys.modules['minigalaxy.ui.categoryfilters'] diff --git a/tests/test_ui_window.py b/tests/test_ui_window.py index 7f040fbf..8725c731 100644 --- a/tests/test_ui_window.py +++ b/tests/test_ui_window.py @@ -9,6 +9,7 @@ m_preferences = MagicMock() m_login = MagicMock() m_about = MagicMock() +m_categoryfilters = MagicMock() class UnitTestGtkTemplate: @@ -56,6 +57,7 @@ def __init__(self, title): sys.modules['minigalaxy.ui.login'] = m_login sys.modules['minigalaxy.ui.about'] = m_about sys.modules['minigalaxy.ui.gtk'] = u_gi_repository +sys.modules['minigalaxy.ui.categoryfilters'] = m_categoryfilters from minigalaxy.ui.window import Window # noqa: E402