import os
import sys
import unittest
from io import StringIO
from unittest.mock import MagicMock, patch
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
class TestMainCliArgParsing(unittest.TestCase):
def _run(self, args):
with patch("sys.argv", ["main.py"] + args):
with patch("main.MetadataDb") as MockDb, \
patch("main.TheGamesDbClient") as MockClient, \
patch("main.Syncer") as MockSyncer:
mock_db_instance = MagicMock()
mock_client_instance = MagicMock()
mock_syncer_instance = MagicMock()
MockDb.return_value.__enter__ = MagicMock(return_value=mock_db_instance)
MockDb.return_value.__exit__ = MagicMock(return_value=False)
MockClient.return_value = mock_client_instance
MockSyncer.return_value = mock_syncer_instance
mock_db_instance.get_game_counts.return_value = {}
mock_client_instance.get_api_limit.return_value = {
"remaining_monthly_allowance": 900, "extra_allowance": 0
}
mock_db_instance.list_games.return_value = []
mock_db_instance.get_game_images.return_value = []
mock_db_instance.get_image_base_urls.return_value = {}
captured = StringIO()
import main as m
m.main(output=captured)
return mock_db_instance, mock_client_instance, mock_syncer_instance, captured.getvalue()
def test_sync_nes_calls_syncer_sync(self):
_, _, syncer, _ = self._run(["sync", "--platform", "nes", "--api-key", "testkey"])
syncer.sync.assert_called_once()
call_kwargs = syncer.sync.call_args[1]
self.assertEqual(call_kwargs["platform_id"], 7)
def test_sync_force_full_passes_flag(self):
_, _, syncer, _ = self._run(["sync", "--platform", "nes", "--force-full", "--api-key", "testkey"])
call_kwargs = syncer.sync.call_args[1]
self.assertTrue(call_kwargs["force_full"])
def test_sync_without_api_key_raises(self):
with patch("sys.argv", ["main.py", "sync", "--platform", "nes"]), \
patch.dict("os.environ", {}, clear=True):
import main as m
with self.assertRaises(SystemExit):
m.main(output=StringIO())
def test_sync_reads_api_key_from_env(self):
with patch("sys.argv", ["main.py", "sync", "--platform", "nes"]), \
patch.dict("os.environ", {"THEGAMESDB_API_KEY": "envkey"}), \
patch("main.MetadataDb") as MockDb, \
patch("main.TheGamesDbClient") as MockClient, \
patch("main.Syncer") as MockSyncer:
mock_db_instance = MagicMock()
MockDb.return_value.__enter__ = MagicMock(return_value=mock_db_instance)
MockDb.return_value.__exit__ = MagicMock(return_value=False)
MockClient.return_value = MagicMock()
MockSyncer.return_value = MagicMock()
import main as m
m.main(output=StringIO())
MockClient.assert_called_once()
init_kwargs = MockClient.call_args[1]
self.assertEqual(init_kwargs["api_key"], "envkey")
def test_sync_cli_api_key_overrides_env(self):
with patch("sys.argv", ["main.py", "sync", "--platform", "nes", "--api-key", "clikey"]), \
patch.dict("os.environ", {"THEGAMESDB_API_KEY": "envkey"}), \
patch("main.MetadataDb") as MockDb, \
patch("main.TheGamesDbClient") as MockClient, \
patch("main.Syncer") as MockSyncer:
mock_db_instance = MagicMock()
MockDb.return_value.__enter__ = MagicMock(return_value=mock_db_instance)
MockDb.return_value.__exit__ = MagicMock(return_value=False)
MockClient.return_value = MagicMock()
MockSyncer.return_value = MagicMock()
import main as m
m.main(output=StringIO())
init_kwargs = MockClient.call_args[1]
self.assertEqual(init_kwargs["api_key"], "clikey")
def test_list_platform_calls_list_games(self):
db, _, _, _ = self._run(["list", "--platform", "nes", "--api-key", "testkey"])
db.list_games.assert_called_once()
call_kwargs = db.list_games.call_args[1]
self.assertEqual(call_kwargs["platform_id"], 7)
def test_list_game_id_filters_by_id(self):
db, _, _, _ = self._run(["list", "--game-id", "135", "--api-key", "testkey"])
db.list_games.assert_called_once()
call_kwargs = db.list_games.call_args[1]
self.assertEqual(call_kwargs["game_id"], 135)
def test_status_command_prints_game_counts(self):
with patch("sys.argv", ["main.py", "status", "--api-key", "testkey"]), \
patch("main.MetadataDb") as MockDb, \
patch("main.TheGamesDbClient") as MockClient, \
patch("main.Syncer"):
mock_db_instance = MagicMock()
MockDb.return_value.__enter__ = MagicMock(return_value=mock_db_instance)
MockDb.return_value.__exit__ = MagicMock(return_value=False)
mock_db_instance.get_game_counts.return_value = {7: 42}
mock_db_instance.list_platforms.return_value = [
{"id": 7, "name": "Nintendo Entertainment System (NES)", "alias": "nes"}
]
MockClient.return_value.get_api_limit.return_value = {
"remaining_monthly_allowance": 900, "extra_allowance": 5
}
captured = StringIO()
import main as m
m.main(output=captured)
output = captured.getvalue()
self.assertIn("42", output)
def test_images_command_prints_urls(self):
with patch("sys.argv", ["main.py", "images", "135", "--api-key", "testkey"]), \
patch("main.MetadataDb") as MockDb, \
patch("main.TheGamesDbClient"), \
patch("main.Syncer"):
mock_db_instance = MagicMock()
MockDb.return_value.__enter__ = MagicMock(return_value=mock_db_instance)
MockDb.return_value.__exit__ = MagicMock(return_value=False)
mock_db_instance.get_game_images.return_value = [
{"id": 718, "type": "boxart", "side": "back",
"filename": "boxart/back/135-2.jpg", "resolution": "1000x1435"}
]
mock_db_instance.build_image_url.return_value = (
"https://cdn.thegamesdb.net/images/original/boxart/back/135-2.jpg"
)
captured = StringIO()
import main as m
m.main(output=captured)
output = captured.getvalue()
self.assertIn("boxart/back/135-2.jpg", output)
def test_sync_all_platforms_syncs_nes_gb_gbc_gba(self):
_, _, syncer, _ = self._run(["sync", "--platform", "all", "--api-key", "testkey"])
platform_ids = [c[1]["platform_id"] for c in syncer.sync.call_args_list]
self.assertIn(7, platform_ids) self.assertIn(4, platform_ids) self.assertIn(41, platform_ids) self.assertIn(5, platform_ids)
class TestInfoCommand(unittest.TestCase):
def _run_info(self, args, search_results=None):
search_results = search_results or []
with patch("sys.argv", ["main.py", "info"] + args), \
patch("main.MetadataDb") as MockDb, \
patch("main.TheGamesDbClient"), \
patch("main.Syncer"):
mock_db = MagicMock()
MockDb.return_value.__enter__ = MagicMock(return_value=mock_db)
MockDb.return_value.__exit__ = MagicMock(return_value=False)
mock_db.search_games.return_value = search_results
mock_db.get_game_genres.return_value = []
mock_db.get_game_developers.return_value = []
mock_db.get_game_publishers.return_value = []
mock_db.get_game_images.return_value = []
mock_db.get_reference.return_value = None
captured = StringIO()
import main as m
m.main(output=captured)
return mock_db, captured.getvalue()
def test_info_calls_search_games_with_name(self):
db, _ = self._run_info(["castlevania"])
db.search_games.assert_called_once()
call_args = db.search_games.call_args
args_list = list(call_args[0]) + list(call_args[1].values())
self.assertIn("castlevania", args_list)
def test_info_with_platform_passes_platform_id(self):
db, _ = self._run_info(["castlevania", "--platform", "nes"])
call_kwargs = db.search_games.call_args[1]
self.assertEqual(call_kwargs.get("platform_id"), 7)
def test_info_no_results_prints_not_found(self):
_, output = self._run_info(["zeldaXXX"])
self.assertIn("No games found", output)
self.assertIn("zeldaXXX", output)
def test_info_prints_game_title_and_id(self):
game = {
"id": 135, "game_title": "Castlevania", "platform_id": 7,
"release_date": "1987-05-01", "rating": "E", "players": 1,
"overview": "Fight Dracula.", "coop": "No", "youtube": "",
"alternates": None, "last_updated": "2025-01-01",
}
_, output = self._run_info(["castlevania"], search_results=[game])
self.assertIn("135", output)
self.assertIn("Castlevania", output)
def test_info_resolves_genre_names(self):
game = {
"id": 135, "game_title": "Castlevania", "platform_id": 7,
"release_date": "", "rating": "", "players": None,
"overview": "", "coop": "", "youtube": "",
"alternates": None, "last_updated": "",
}
with patch("sys.argv", ["main.py", "info", "castlevania"]), \
patch("main.MetadataDb") as MockDb, \
patch("main.TheGamesDbClient"), \
patch("main.Syncer"):
mock_db = MagicMock()
MockDb.return_value.__enter__ = MagicMock(return_value=mock_db)
MockDb.return_value.__exit__ = MagicMock(return_value=False)
mock_db.search_games.return_value = [game]
mock_db.get_game_genres.return_value = [15]
mock_db.get_game_developers.return_value = []
mock_db.get_game_publishers.return_value = []
mock_db.get_game_images.return_value = []
mock_db.get_reference.side_effect = lambda table, eid: (
{"id": 15, "name": "Action"} if table == "genres" and eid == 15 else None
)
captured = StringIO()
import main as m
m.main(output=captured)
self.assertIn("Action", captured.getvalue())
def test_info_shows_image_count(self):
game = {
"id": 135, "game_title": "Castlevania", "platform_id": 7,
"release_date": "", "rating": "", "players": None,
"overview": "", "coop": "", "youtube": "",
"alternates": None, "last_updated": "",
}
with patch("sys.argv", ["main.py", "info", "castlevania"]), \
patch("main.MetadataDb") as MockDb, \
patch("main.TheGamesDbClient"), \
patch("main.Syncer"):
mock_db = MagicMock()
MockDb.return_value.__enter__ = MagicMock(return_value=mock_db)
MockDb.return_value.__exit__ = MagicMock(return_value=False)
mock_db.search_games.return_value = [game]
mock_db.get_game_genres.return_value = []
mock_db.get_game_developers.return_value = []
mock_db.get_game_publishers.return_value = []
mock_db.get_game_image_counts_by_type.return_value = {
"boxart": 2, "screenshot": 3, "fanart": 1,
}
mock_db.get_reference.return_value = None
captured = StringIO()
import main as m
m.main(output=captured)
output = captured.getvalue()
self.assertIn("boxart", output)
self.assertIn("2", output)
self.assertIn("screenshot", output)
self.assertIn("3", output)
self.assertIn("fanart", output)
self.assertIn("1", output)
if __name__ == "__main__":
unittest.main()