From d1844c01a0c8213a9702941e4421ed92a9d44536 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Mon, 2 Mar 2026 09:19:36 -0500 Subject: [PATCH] [tests] Speed up tests --- pyproject.toml | 3 +- tests/videos_tests/test_api.py | 98 +++++++++++++++++++++++++++ tests/videos_tests/test_imdb.py | 6 +- vrobbler/apps/scrobbles/scrobblers.py | 71 +++++++++++++++---- vrobbler/settings-testing.py | 20 ++++++ 5 files changed, 180 insertions(+), 18 deletions(-) create mode 100644 tests/videos_tests/test_api.py diff --git a/pyproject.toml b/pyproject.toml index 2743381..6bf7a56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,8 +82,7 @@ types-requests = "^2.27" bandit = "^1.7.4" [tool.pytest.ini_options] -minversion = "6.0" -addopts = "-ra -q --reuse-db" +addopts = "-ra -q --reuse-db --no-migrations" testpaths = ["tests"] DJANGO_SETTINGS_MODULE='vrobbler.settings-testing' diff --git a/tests/videos_tests/test_api.py b/tests/videos_tests/test_api.py new file mode 100644 index 0000000..6499bb6 --- /dev/null +++ b/tests/videos_tests/test_api.py @@ -0,0 +1,98 @@ +import pytest +from django.contrib.auth import get_user_model +from rest_framework.authtoken.models import Token + +from videos.models import Channel, Series, Video + +User = get_user_model() + + +@pytest.fixture +def auth_headers(): + user = User.objects.create(email="api@test.com") + token = Token.objects.create(user=user) + return {"HTTP_AUTHORIZATION": f"Token {token.key}"} + + +@pytest.fixture +def channel(): + return Channel.objects.create(name="Test Channel") + + +@pytest.fixture +def series(): + return Series.objects.create( + name="Test Series", + ) + + +@pytest.fixture +def video(channel): + return Video.objects.create( + title="Test Video", + imdb_id="tt1234567", + channel=channel, + ) + + +@pytest.mark.django_db +class TestVideoAPI: + def test_list_videos(self, client, auth_headers, video): + response = client.get("/api/v1/videos/", **auth_headers) + assert response.status_code == 200 + assert len(response.data["results"]) == 1 + assert response.data["results"][0]["title"] == "Test Video" + + def test_get_video(self, client, auth_headers, video): + response = client.get(f"/api/v1/videos/{video.id}/", **auth_headers) + assert response.status_code == 200 + assert response.data["title"] == "Test Video" + + def test_filter_videos_by_channel(self, client, auth_headers, channel, video): + response = client.get(f"/api/v1/videos/?channel={channel.id}", **auth_headers) + assert response.status_code == 200 + assert len(response.data["results"]) == 1 + assert response.data["results"][0]["channel"] == channel.id + + +@pytest.mark.django_db +class TestChannelAPI: + def test_list_channels(self, client, auth_headers, channel): + response = client.get("/api/v1/channels/", **auth_headers) + assert response.status_code == 200 + assert len(response.data["results"]) == 1 + assert response.data["results"][0]["name"] == "Test Channel" + + def test_get_channel(self, client, auth_headers, channel): + response = client.get(f"/api/v1/channels/{channel.id}/", **auth_headers) + assert response.status_code == 200 + assert response.data["name"] == "Test Channel" + + +@pytest.mark.django_db +class TestSeriesAPI: + def test_list_series(self, client, auth_headers, series): + response = client.get("/api/v1/series/", **auth_headers) + assert response.status_code == 200 + assert len(response.data["results"]) == 1 + assert response.data["results"][0]["name"] == "Test Series" + + def test_get_series(self, client, auth_headers, series): + response = client.get(f"/api/v1/series/{series.id}/", **auth_headers) + assert response.status_code == 200 + assert response.data["name"] == "Test Series" + + +@pytest.mark.django_db +class TestVideoAPIUnauthorized: + def test_list_videos_unauthenticated(self, client, video): + response = client.get("/api/v1/videos/") + assert response.status_code == 401 + + def test_list_channels_unauthenticated(self, client, channel): + response = client.get("/api/v1/channels/") + assert response.status_code == 401 + + def test_list_series_unauthenticated(self, client, series): + response = client.get("/api/v1/series/") + assert response.status_code == 401 diff --git a/tests/videos_tests/test_imdb.py b/tests/videos_tests/test_imdb.py index ef05a29..aff2e45 100644 --- a/tests/videos_tests/test_imdb.py +++ b/tests/videos_tests/test_imdb.py @@ -1,10 +1,14 @@ +import pytest from videos.sources.imdb import lookup_video_from_imdb + +@pytest.mark.skip(reason="Should not hit IMDB api in CI") def test_lookup_imdb_without_tt(): metadata = lookup_video_from_imdb("8946378") - print(metadata.__dict__) assert not metadata.imdb_id + +@pytest.mark.skip(reason="Should not hit IMDB api in CI") def test_lookup_imdb_with_tt(): metadata = lookup_video_from_imdb("tt8946378") assert metadata.title == "Knives Out" diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py index 66d2a0c..4d28185 100644 --- a/vrobbler/apps/scrobbles/scrobblers.py +++ b/vrobbler/apps/scrobbles/scrobblers.py @@ -64,7 +64,10 @@ def mopidy_scrobble_media(post_data: dict, user_id: int) -> Scrobble: if media_type == Scrobble.MediaType.PODCAST_EPISODE: parsed_data = parse_mopidy_uri(post_data.get("mopidy_uri", "")) if not parsed_data: - logger.warning("Tried to scrobble podcast but no uri found", extra={"post_data": post_data}) + logger.warning( + "Tried to scrobble podcast but no uri found", + extra={"post_data": post_data}, + ) return Scrobble() media_obj = PodcastEpisode.find_or_create(**parsed_data) @@ -167,7 +170,10 @@ def web_scrobbler_scrobble_media( def manual_scrobble_video( - video_id: str, user_id: int, source: str = "IMDb", action: Optional[str] = None + video_id: str, + user_id: int, + source: str = "IMDb", + action: Optional[str] = None, ): video = Video.find_or_create(video_id) @@ -200,7 +206,10 @@ def manual_scrobble_video( def manual_scrobble_event( - thesportsdb_id: str, user_id: int, source: str = "TheSportsDB", action: Optional[str] = None + thesportsdb_id: str, + user_id: int, + source: str = "TheSportsDB", + action: Optional[str] = None, ): data_dict = lookup_event_from_thesportsdb(thesportsdb_id) @@ -215,7 +224,10 @@ def manual_scrobble_event( def manual_scrobble_video_game( - hltb_id: str, user_id: int, source: str = "HLTB", action: Optional[str] = None + hltb_id: str, + user_id: int, + source: str = "HLTB", + action: Optional[str] = None, ): game = VideoGame.objects.filter(hltb_id=hltb_id).first() if not game: @@ -255,7 +267,10 @@ def manual_scrobble_video_game( def manual_scrobble_book( - title: str, user_id: int, source: str = "Google Books", action: Optional[str] = None + title: str, + user_id: int, + source: str = "Google Books", + action: Optional[str] = None, ): log = {} page = None @@ -280,7 +295,9 @@ def manual_scrobble_book( if not page: page = 1 - logger.info("[scrobblers] Book page included in scrobble, should update!") + logger.info( + "[scrobblers] Book page included in scrobble, should update!" + ) source = READCOMICSONLINE_URL.replace("https://", "") @@ -306,8 +323,9 @@ def manual_scrobble_book( }, ) - - scrobble = Scrobble.create_or_update(book, user_id, scrobble_dict, read_log_page=page) + scrobble = Scrobble.create_or_update( + book, user_id, scrobble_dict, read_log_page=page + ) if action == "stop": if url: @@ -322,7 +340,10 @@ def manual_scrobble_book( def manual_scrobble_board_game( - bggeek_id: str, user_id: int, source: str = "BGG", action: Optional[str] = None + bggeek_id: str, + user_id: int, + source: str = "BGG", + action: Optional[str] = None, ) -> Scrobble | None: boardgame = BoardGame.find_or_create(bggeek_id) @@ -524,7 +545,10 @@ def email_scrobble_board_game( def manual_scrobble_from_url( - url: str, user_id: int, source: str = "Vrobbler", action: Optional[str] = None + url: str, + user_id: int, + source: str = "Vrobbler", + action: Optional[str] = None, ) -> Scrobble: """We have scrobblable media URLs, and then any other webpages that we want to scrobble as a media type in and of itself. This checks whether @@ -840,7 +864,12 @@ def emacs_scrobble_task( return scrobble -def manual_scrobble_task(url: str, user_id: int, source: str = "Vrobbler", action: Optional[str] = None): +def manual_scrobble_task( + url: str, + user_id: int, + source: str = "Vrobbler", + action: Optional[str] = None, +): source_id = re.findall(r"\d+", url)[0] description = "" @@ -872,7 +901,10 @@ def manual_scrobble_task(url: str, user_id: int, source: str = "Vrobbler", actio def manual_scrobble_webpage( - url: str, user_id: int, source: str = "Bookmarklet", action: Optional[str] = None + url: str, + user_id: int, + source: str = "Bookmarklet", + action: Optional[str] = None, ): webpage = WebPage.find_or_create({"url": url}) @@ -999,7 +1031,10 @@ def web_scrobbler_scrobble_video_or_song( def manual_scrobble_beer( - untappd_id: str, user_id: int, source: str = "Untappd", action: Optional[str] = None + untappd_id: str, + user_id: int, + source: str = "Untappd", + action: Optional[str] = None, ): beer = Beer.find_or_create(untappd_id) @@ -1028,7 +1063,10 @@ def manual_scrobble_beer( def manual_scrobble_puzzle( - ipdb_id: str, user_id: int, source: str = "IPDb", action: Optional[str] = None + ipdb_id: str, + user_id: int, + source: str = "IPDb", + action: Optional[str] = None, ): puzzle = Puzzle.find_or_create(ipdb_id) @@ -1057,7 +1095,10 @@ def manual_scrobble_puzzle( def manual_scrobble_brickset( - brickset_id: str, user_id: int, source: str = "BrickSet", action: Optional[str] = None + brickset_id: str, + user_id: int, + source: str = "BrickSet", + action: Optional[str] = None, ): brickset = BrickSet.find_or_create(brickset_id) diff --git a/vrobbler/settings-testing.py b/vrobbler/settings-testing.py index b4b4943..285c5fd 100644 --- a/vrobbler/settings-testing.py +++ b/vrobbler/settings-testing.py @@ -1 +1,21 @@ from vrobbler.settings import * + +TESTING = True + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": "/tmp/testdb.sqlite3", + } +} + +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "unique-snowflake", + } +} + +PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.MD5PasswordHasher", +]