2024-05-10 00:12:25 +02:00
|
|
|
import bz2
|
2024-05-11 17:13:48 +02:00
|
|
|
import json
|
2024-05-10 00:12:25 +02:00
|
|
|
from pathlib import Path
|
|
|
|
|
from unittest.mock import AsyncMock
|
|
|
|
|
|
|
|
|
|
import bs4
|
2021-06-21 18:54:03 +02:00
|
|
|
import pytest
|
2023-02-04 18:15:14 +01:00
|
|
|
|
2024-05-10 00:12:25 +02:00
|
|
|
from unwind import imdb
|
2021-06-21 18:54:03 +02:00
|
|
|
from unwind.imdb import imdb_rating_from_score, score_from_imdb_rating
|
|
|
|
|
|
2024-05-10 00:12:25 +02:00
|
|
|
testsdir = Path(__file__).parent
|
|
|
|
|
fixturesdir = testsdir / "fixtures"
|
|
|
|
|
|
2021-06-21 18:54:03 +02:00
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("rating", (x / 10 for x in range(10, 101)))
|
2023-02-04 18:12:50 +01:00
|
|
|
def test_rating_conversion(rating: float):
|
2021-06-21 18:54:03 +02:00
|
|
|
assert rating == imdb_rating_from_score(score_from_imdb_rating(rating))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("score", range(0, 101))
|
2023-02-04 18:12:50 +01:00
|
|
|
def test_score_conversion(score: int):
|
2021-06-21 18:54:03 +02:00
|
|
|
# Because our score covers 101 discrete values and IMDb's rating only 91
|
|
|
|
|
# discrete values, the mapping is non-injective, i.e. 10 values can't be
|
|
|
|
|
# mapped uniquely.
|
|
|
|
|
non_injective = set(range(5, 100, 10))
|
|
|
|
|
if score in non_injective:
|
|
|
|
|
pytest.skip(f"Score cannot be mapped back correctly: {score}")
|
|
|
|
|
|
|
|
|
|
assert score == score_from_imdb_rating(imdb_rating_from_score(score))
|
2024-05-10 00:12:25 +02:00
|
|
|
|
|
|
|
|
|
2024-07-14 16:09:02 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"fixture",
|
|
|
|
|
(
|
|
|
|
|
("most_popular_100.html.bz2"),
|
|
|
|
|
("most_popular_100-20240714.html.bz2"),
|
|
|
|
|
),
|
|
|
|
|
)
|
2024-05-10 00:12:25 +02:00
|
|
|
@pytest.mark.asyncio
|
2024-07-14 16:09:02 +02:00
|
|
|
async def test_load_most_popular_100(monkeypatch, fixture: str):
|
|
|
|
|
with bz2.open(fixturesdir / fixture, "rb") as f:
|
2024-05-10 00:12:25 +02:00
|
|
|
html = f.read()
|
|
|
|
|
soup = bs4.BeautifulSoup(html, "html5lib")
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(imdb, "asoup_from_url", AsyncMock(return_value=soup))
|
|
|
|
|
|
|
|
|
|
movie_ids = await imdb.load_most_popular_100()
|
2024-07-14 16:09:02 +02:00
|
|
|
assert len(set(movie_ids)) == 100
|
2024-05-10 00:12:25 +02:00
|
|
|
assert all(id_.startswith("tt") for id_ in movie_ids)
|
|
|
|
|
|
|
|
|
|
|
2024-07-14 16:09:02 +02:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"fixture",
|
|
|
|
|
(
|
|
|
|
|
("bottom_100.html.bz2"),
|
|
|
|
|
("bottom_100-20240714.html.bz2"),
|
|
|
|
|
),
|
|
|
|
|
)
|
2024-05-10 00:12:25 +02:00
|
|
|
@pytest.mark.asyncio
|
2024-07-14 16:09:02 +02:00
|
|
|
async def test_load_bottom_100(monkeypatch, fixture: str):
|
|
|
|
|
with bz2.open(fixturesdir / fixture, "rb") as f:
|
2024-05-10 00:12:25 +02:00
|
|
|
html = f.read()
|
|
|
|
|
soup = bs4.BeautifulSoup(html, "html5lib")
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(imdb, "asoup_from_url", AsyncMock(return_value=soup))
|
|
|
|
|
|
|
|
|
|
movie_ids = await imdb.load_bottom_100()
|
2024-07-14 16:09:02 +02:00
|
|
|
assert len(set(movie_ids)) == 100
|
2024-05-10 00:12:25 +02:00
|
|
|
assert all(id_.startswith("tt") for id_ in movie_ids)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_load_top_250(monkeypatch):
|
|
|
|
|
with bz2.open(fixturesdir / "top250.gql.json.bz2", "rb") as f:
|
|
|
|
|
jsonstr = f.read()
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(imdb, "adownload", AsyncMock(return_value=jsonstr))
|
|
|
|
|
|
|
|
|
|
movie_ids = await imdb.load_top_250()
|
|
|
|
|
assert len(movie_ids) == 250
|
|
|
|
|
assert all(id_.startswith("tt") for id_ in movie_ids)
|
2024-05-10 00:13:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_load_ratings_page(monkeypatch):
|
|
|
|
|
with bz2.open(fixturesdir / "ratings-ur655321.html.bz2", "rb") as f:
|
|
|
|
|
html = f.read()
|
|
|
|
|
soup = bs4.BeautifulSoup(html, "html5lib")
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(imdb, "asoup_from_url", AsyncMock(return_value=soup))
|
|
|
|
|
|
2024-05-11 17:13:48 +02:00
|
|
|
page = await imdb._load_ratings_page("fakeurl", "ur655321")
|
2024-05-10 00:13:32 +02:00
|
|
|
assert len(page.ratings) == 100
|
|
|
|
|
assert page.imdb_user_id is not None
|
|
|
|
|
assert page.imdb_user_id == "ur655321"
|
|
|
|
|
assert page.imdb_user_name == "AlexUltra"
|
|
|
|
|
assert page.next_page_url is not None
|
|
|
|
|
assert page.next_page_url.startswith("/user/ur655321/ratings?")
|
2024-05-11 17:13:48 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _mock_response(content: bytes):
|
|
|
|
|
class MockResponse:
|
|
|
|
|
def raise_for_status(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def json(self):
|
|
|
|
|
return json.loads(content)
|
|
|
|
|
|
|
|
|
|
return MockResponse()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_load_ratings_page_20240510(monkeypatch):
|
|
|
|
|
with bz2.open(fixturesdir / "ratings-ur655321-20240510.html.bz2", "rb") as f:
|
|
|
|
|
html = f.read()
|
|
|
|
|
soup = bs4.BeautifulSoup(html, "html5lib")
|
|
|
|
|
monkeypatch.setattr(imdb, "asoup_from_url", AsyncMock(return_value=soup))
|
|
|
|
|
|
|
|
|
|
with bz2.open(fixturesdir / "ratings-ur655321-20240510.gql.json.bz2", "rb") as f:
|
|
|
|
|
jsonstr = f.read()
|
|
|
|
|
async with imdb.asession() as s:
|
|
|
|
|
monkeypatch.setattr(s, "post", AsyncMock(return_value=_mock_response(jsonstr)))
|
|
|
|
|
page = await imdb._load_ratings_page("fakeurl", "ur655321")
|
|
|
|
|
assert len(page.ratings) == 100
|
|
|
|
|
assert page.imdb_user_id is not None
|
|
|
|
|
assert page.imdb_user_id == "ur655321"
|
|
|
|
|
assert page.imdb_user_name == "AlexUltra"
|
|
|
|
|
assert page.next_page_url is None, "not supported for new ratings page"
|
|
|
|
|
|
|
|
|
|
def movie(item: dict):
|
|
|
|
|
for rating in page.ratings:
|
|
|
|
|
assert rating.movie
|
|
|
|
|
if rating.movie.imdb_id == item["imdb_id"]:
|
|
|
|
|
rating_dict = {key: getattr(rating.movie, key) for key in item.keys()}
|
|
|
|
|
return rating_dict
|
|
|
|
|
raise AssertionError()
|
|
|
|
|
|
|
|
|
|
a_movie = {
|
|
|
|
|
"title": "Kung Fu Panda 4",
|
|
|
|
|
"release_year": 2024,
|
|
|
|
|
"media_type": "Movie",
|
|
|
|
|
"imdb_id": "tt21692408",
|
|
|
|
|
"imdb_score": 59,
|
|
|
|
|
"imdb_votes": 36000,
|
|
|
|
|
"runtime": 94,
|
|
|
|
|
}
|
|
|
|
|
assert a_movie == movie(a_movie)
|
|
|
|
|
|
|
|
|
|
a_running_tvseries = {
|
|
|
|
|
"title": "Palm Royale",
|
|
|
|
|
"release_year": 2024,
|
|
|
|
|
"media_type": "TV Series",
|
|
|
|
|
"imdb_id": "tt8888540",
|
|
|
|
|
"imdb_score": 64,
|
|
|
|
|
"imdb_votes": 6000,
|
|
|
|
|
}
|
|
|
|
|
assert a_running_tvseries == movie(a_running_tvseries)
|
|
|
|
|
|
|
|
|
|
a_finished_tvseries = {
|
|
|
|
|
"title": "Fawlty Towers",
|
|
|
|
|
"release_year": 1975,
|
|
|
|
|
"media_type": "TV Series",
|
|
|
|
|
"imdb_id": "tt0072500",
|
|
|
|
|
"imdb_score": 87,
|
|
|
|
|
"imdb_votes": 100000,
|
|
|
|
|
}
|
|
|
|
|
assert a_finished_tvseries == movie(a_finished_tvseries)
|
|
|
|
|
|
|
|
|
|
a_tvepisode = {
|
|
|
|
|
"title": "Columbo / No Time to Die",
|
|
|
|
|
"original_title": None,
|
|
|
|
|
"release_year": 1992,
|
|
|
|
|
"media_type": "TV Episode",
|
|
|
|
|
"imdb_id": "tt0103987",
|
|
|
|
|
"imdb_score": 59,
|
|
|
|
|
"imdb_votes": 2100,
|
|
|
|
|
"runtime": 98,
|
|
|
|
|
}
|
|
|
|
|
assert a_tvepisode == movie(a_tvepisode)
|
|
|
|
|
|
|
|
|
|
a_videogame = {
|
|
|
|
|
"title": "Alan Wake",
|
|
|
|
|
"original_title": None,
|
|
|
|
|
"release_year": 2010,
|
|
|
|
|
"media_type": "Video Game",
|
|
|
|
|
"imdb_id": "tt0466662",
|
|
|
|
|
"imdb_score": 82,
|
|
|
|
|
"imdb_votes": 7300,
|
|
|
|
|
}
|
|
|
|
|
assert a_videogame == movie(a_videogame)
|