diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f01d0c4..d95cf6a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,9 +27,5 @@ jobs: pip install -r requirements.txt - name: Run unit tests - env: - NLTK_DATA: ${{ secrets.NLTK_DATA }} - TWITCH_CLIENT_ID: ${{ secrets.TWITCH_CLIENT_ID }} - TWITCH_CLIENT_SECRET: ${{ secrets.TWITCH_CLIENT_SECRET }} run: | python -m unittest discover -s src/tests -p 'test_*.py' diff --git a/requirements.txt b/requirements.txt index 62a2c4d..5470baf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ thefuzz==0.22.1 torch==2.2.2 transformers==4.39.2 accelerate==0.28.0 +mock==5.1.0 \ No newline at end of file diff --git a/resources/form.scss b/resources/form.scss index f7d636b..0a6145d 100644 --- a/resources/form.scss +++ b/resources/form.scss @@ -16,7 +16,7 @@ $font-size: 16px; .QLineEdit { color: $secondary-accent-color; - border:none; + border: none; border-bottom: 2px solid $accent-color; padding-bottom: 2px; background: $main-color; @@ -27,14 +27,17 @@ $font-size: 16px; border-radius: 5px; color: $accent-color; background-color: $main-color; + &:hover { border: 2px solid $secondary-color; background-color: $secondary-color; } + &:pressed { border: 2px solid $accent-color; background-color: $accent-color; } + &:disabled { color: $main-color; background-color: $main-color; diff --git a/src/image/character_info.py b/src/image/character_info.py index e242bdf..1cf7f87 100644 --- a/src/image/character_info.py +++ b/src/image/character_info.py @@ -7,13 +7,14 @@ "/animagine-xl-3.1/raw/main/wildcard/characterfull.txt") -def get_all_characters(): +def get_all_characters() -> list[str]: """ Get all characters from the character list. """ - with requests.get(CHARACTER_LIST_URL, timeout=3) as response: - response.raise_for_status() - return response.text.split("\n") + response = requests.get(CHARACTER_LIST_URL, timeout=3) + response.raise_for_status() + text: list[str] = response.text.split("\n") + return text def get_closest_character(name): diff --git a/src/image/game_info.py b/src/image/game_info.py index 34ec73b..be1787d 100644 --- a/src/image/game_info.py +++ b/src/image/game_info.py @@ -73,7 +73,6 @@ def authenticate(self): """ if self.token and time.time() - self.authentication_time < self.token['expires_in']: return - url = "https://id.twitch.tv/oauth2/token" payload = { "client_id": self.client_id, @@ -84,6 +83,7 @@ def authenticate(self): response.raise_for_status() self.token = response.json() self.authentication_time = time.time() + return def get_game_info(self, name): """ diff --git a/src/main.py b/src/main.py index 91bd195..4690b9a 100644 --- a/src/main.py +++ b/src/main.py @@ -194,8 +194,10 @@ def generate_image(self): "Please enter an anime character name.") return self.input_controls["generate_button"].setDisabled(True) + input_values = {key: widget.text().lower().replace(',', ' ').lstrip().rstrip() for key, widget in self.inputs.items()} + threading.Thread(target=self.image_generator.process_image, args=(input_values, self)).start() @@ -215,6 +217,7 @@ def show_image(self): for item in self.image_controls.values(): item.show() + if len(self.image_generator.images) != 1: for item in self.image_navigation_buttons.values(): item.show() diff --git a/src/tests/test_character_info.py b/src/tests/test_character_info.py index 01f68d3..5f3ecc3 100644 --- a/src/tests/test_character_info.py +++ b/src/tests/test_character_info.py @@ -1,33 +1,90 @@ import unittest +from unittest.mock import patch, Mock from src.image.character_info import ( get_all_characters, get_closest_character, get_closest_characters ) class TestCharacterFunctions(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.character_list = ["souryuu asuka langley", "warrior of light (ff14)"] + def __init__(self, methodName: str = "runTest") -> None: + super().__init__(methodName) + self.character_list = ["souryuu asuka langley", "warrior of light (ff14)"] + + @patch('requests.get') + def test_get_all_characters(self, mock_get): + mock_response = Mock() + response_body = ("1girl, souryuu asuka langley, neon genesis evangelion\n" + "1girl, warrior of light \\(ff14\\), final fantasy\n" + "1girl, akiyama mio, k-on!\n" + "1girl, tifa lockhart, final fantasy\n" + "1girl, 2b \\(nier:automata\\), nier \\(series\\)\n" + "1girl, nakano azusa, k-on!\n" + "1girl, rem \\(re:zero\\), re:zero kara hajimeru isekai seikatsu\n" + "1girl, hirasawa yui, k-on!\n" + "1girl, gotoh hitori, bocchi the rock!\n" + "1girl, ayanami rei, neon genesis evangelion\n" + "1boy, male focus, joseph joestar, jojo no kimyou na bouken\n" + "1girl, matoi ryuuko, kill la kill\n" + "1girl, yor briar, spy x family\n" + "1girl, tainaka ritsu, k-on!\n") + mock_response.text = response_body + mock_get.return_value = mock_response - def test_get_all_characters(self): characters = get_all_characters() self.assertIsInstance(characters, list) self.assertTrue(all(isinstance(c, str) for c in characters)) - def test_get_closest_character(self): + @patch('requests.get') + def test_get_closest_character(self, mock_get): + mock_response = Mock() + response_body = ("1girl, souryuu asuka langley, neon genesis evangelion\n" + "1girl, warrior of light \\(ff14\\), final fantasy\n" + "1girl, akiyama mio, k-on!\n" + "1girl, tifa lockhart, final fantasy\n" + "1girl, 2b \\(nier:automata\\), nier \\(series\\)\n" + "1girl, nakano azusa, k-on!\n" + "1girl, rem \\(re:zero\\), re:zero kara hajimeru isekai seikatsu\n" + "1girl, hirasawa yui, k-on!\n" + "1girl, gotoh hitori, bocchi the rock!\n" + "1girl, ayanami rei, neon genesis evangelion\n" + "1boy, male focus, joseph joestar, jojo no kimyou na bouken\n" + "1girl, matoi ryuuko, kill la kill\n" + "1girl, yor briar, spy x family\n" + "1girl, tainaka ritsu, k-on!\n") + mock_response.text = response_body + mock_get.return_value = mock_response closest_name = get_closest_character("Asuka Langley") self.assertEqual(closest_name, "1girl, souryuu asuka langley, neon genesis evangelion") - def test_get_closest_characters(self): + @patch('requests.get') + def test_get_closest_characters(self, mock_get): + mock_response = Mock() + response_body = ("1boy, male focus, uzumaki naruto, naruto \\(series\\)\n" + "1boy, male focus, uzumaki boruto, naruto \\(series\\)\n" + "1girl, uzumaki himawari, naruto \\(series\\)\n" + "1girl, carrot \\(one piece\\), one piece\n" + "1girl, uzumaki kushina, naruto \\(series\\)\n") + mock_response.text = response_body + mock_get.return_value = mock_response + closest_names = get_closest_characters("Naruto Uzumaki", 3) self.assertEqual(closest_names, ['1boy, male focus, uzumaki naruto, naruto \\(series\\)', '1boy, male focus, uzumaki boruto, naruto \\(series\\)', '1girl, uzumaki himawari, naruto \\(series\\)']) - def test_get_closest_character_not_found(self): + @patch('requests.get') + def test_get_closest_character_not_found(self, mock_get): + mock_response = Mock() + response_body = ("1boy, male focus, uzumaki naruto, naruto \\(series\\)\n" + "1boy, male focus, uzumaki boruto, naruto \\(series\\)\n" + "1girl, uzumaki himawari, naruto \\(series\\)\n" + "1girl, carrot \\(one piece\\), one piece\n" + "1girl, uzumaki kushina, naruto \\(series\\)\n") + mock_response.text = response_body + mock_get.return_value = mock_response closest_name = get_closest_character("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") self.assertIsNone(closest_name) diff --git a/src/tests/test_game_info.py b/src/tests/test_game_info.py index afc3152..6a6f430 100644 --- a/src/tests/test_game_info.py +++ b/src/tests/test_game_info.py @@ -1,51 +1,107 @@ import time import unittest +from unittest.mock import patch, Mock from src.image.game_info import ( GameInfo, get_year_recency, get_summary_keywords, is_relevant_adjective ) +TEKKEN_GAME_INFO = { + 'id': 7498, + 'first_release_date': 1424217600, + 'genres': [4], + 'name': 'Tekken 7', + 'summary': ('Experience the epic conclusion of the Mishima clan and unravel ' + 'the reasons behind each step of their ceaseless fight. ' + 'Powered by Unreal Engine 4, Tekken 7 features stunning ' + 'story-driven cinematic battles and intense duels that ' + 'can be enjoyed with friends and rivals alike through ' + 'innovative fight mechanics.') +} + class TestGameInfo(unittest.TestCase): - def setUp(self): + def __init__(self, methodName: str = "runTest") -> None: + super().__init__(methodName) self.game_info = GameInfo() - def test_authenticate(self): + @patch('requests.post') + def test_authenticate(self, mock_post): + """ + Test Twitch API Authentication + """ + mock_response = Mock() + response_body = { + "access_token": "someAccessToken", + "expires_in": 9112004, + "token_type": "bearer" + } + mock_response.json.return_value = response_body + mock_post.return_value = mock_response + self.game_info.authenticate() + self.assertIsNotNone(self.game_info.token) - self.assertIsNotNone(self.game_info.authentication_time) - self.assertTrue(time.time() - self.game_info.authentication_time - < self.game_info.token['expires_in']) - - def test_get_game_info(self): - game_data = self.game_info.get_game_info('Tekken 7') - - self.assertIsNotNone(game_data) - self.assertEqual(game_data['name'], 'Tekken 7') - self.assertEqual(game_data['first_release_date'], 1424217600) - self.assertEqual(game_data['genres'], [4]) - self.assertEqual(game_data['summary'], 'Experience the epic conclusion of the ' - 'Mishima clan and unravel the ' - 'reasons behind each step of their ceaseless fight. ' - 'Powered by Unreal ' - 'Engine 4, Tekken 7 features stunning story-driven ' - 'cinematic battles ' - 'and intense duels that can be enjoyed with friends ' - 'and rivals alike ' - 'through innovative fight mechanics.') - - def test_get_genres(self): - genres = self.game_info.get_genres([4]) - - self.assertEqual(genres, ['Fighting']) + self.assertTrue(time.time() - response_body["expires_in"] + < self.game_info.authentication_time) + + @patch("requests.post") + @patch("src.image.game_info.GameInfo.authenticate", Mock()) + def test_get_game_info(self, mock_post_info): + """ + Test IGDB Response on https://api.igdb.com/v4/games + """ + mock_response = Mock() + response_body = [TEKKEN_GAME_INFO, {}] + mock_response.json.return_value = response_body + mock_post_info.return_value = mock_response + self.game_info.token = { + "access_token": "someAccessToken", + "expires_in": 9112004, + "token_type": "bearer" + } + info = self.game_info.get_game_info("Tekken 7") + + self.assertEqual(response_body[0], info) + + @patch("requests.post") + @patch("src.image.game_info.GameInfo.authenticate", Mock()) + def test_get_genres(self, mock_post): + """ + Test IGDB Response on https://api.igdb.com/v4/genres + """ + mock_response = Mock() + response_body = [{ + "id": 4, + "name": "Fighting" + }] + mock_response.json.return_value = response_body + mock_post.return_value = mock_response + self.game_info.token = { + "access_token": "someAccessToken", + "expires_in": 9112004, + "token_type": "bearer" + } + self.game_info.get_genres([4]) + + self.assertEqual(response_body[0]["name"], 'Fighting') + @patch("src.image.game_info.GameInfo.get_game_info", Mock(return_value=TEKKEN_GAME_INFO)) + @patch("src.image.game_info.GameInfo.get_genres", Mock(return_value=['Fighting'])) + @patch("src.image.game_info.GameInfo.authenticate", Mock()) def test_get_game_keywords(self): + """ + Test IGDB Response on https://api.igdb.com/v4/genres + """ + self.game_info.token = { + "access_token": "someAccessToken", + "expires_in": 9112004, + "token_type": "bearer" + } keywords = self.game_info.get_game_keywords('Tekken 7') self.assertIsNotNone(keywords) - self.assertEqual(keywords[0], 'mid') - # The order of the keywords may vary - self.assertIn('Fighting', keywords[1]) + self.assertIn("Fighting", keywords[1]) def test_get_year_recency(self): self.assertEqual(get_year_recency(time.time()), 'newest')