"""Integration tests for user ratings (section 17, additive) -- PUT/DELETE a rating on a movie or series, and list the caller's own rated movies/ series. Exercised at the HTTP layer, same convention as test_auth_api.py, since the auth header handling is part of the real contract.""" from __future__ import annotations import pytest from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession from tests.integration.factories import ( make_movie, make_provider, make_series, movie_aurora_id, series_aurora_id, ) async def _register(client: AsyncClient, email: str) -> str: response = await client.post( "/api/v1/auth/register", json={"email": email, "password": "correct-horse-battery"} ) assert response.status_code == 201 return response.json()["access_token"] def _auth(token: str) -> dict[str, str]: return {"Authorization": f"Bearer {token}"} @pytest.mark.asyncio async def test_rate_movie_creates_a_rating(client: AsyncClient, db_session: AsyncSession): movie = await make_movie(db_session, provider=await make_provider(db_session)) await db_session.commit() token = await _register(client, "neo@zion.net") response = await client.put( f"/api/v1/movies/{movie_aurora_id(movie)}/rating", json={"value": 9.0}, headers=_auth(token) ) assert response.status_code == 200 body = response.json() assert body["value"] == 9.0 assert "created_at" in body and "updated_at" in body @pytest.mark.asyncio async def test_rate_movie_twice_updates_rather_than_duplicates( client: AsyncClient, db_session: AsyncSession ): movie = await make_movie(db_session, provider=await make_provider(db_session)) await db_session.commit() token = await _register(client, "trinity@zion.net") await client.put(f"/api/v1/movies/{movie_aurora_id(movie)}/rating", json={"value": 6.0}, headers=_auth(token)) response = await client.put( f"/api/v1/movies/{movie_aurora_id(movie)}/rating", json={"value": 8.5}, headers=_auth(token) ) assert response.status_code == 200 assert response.json()["value"] == 8.5 listing = await client.get("/api/v1/account/ratings/movies", headers=_auth(token)) assert listing.json()["total_results"] == 1 assert listing.json()["results"][0]["rating"] == 8.5 @pytest.mark.asyncio async def test_rate_movie_requires_auth(client: AsyncClient, db_session: AsyncSession): movie = await make_movie(db_session, provider=await make_provider(db_session)) await db_session.commit() response = await client.put(f"/api/v1/movies/{movie_aurora_id(movie)}/rating", json={"value": 5.0}) assert response.status_code == 401 @pytest.mark.asyncio async def test_rate_movie_404_for_unknown_movie(client: AsyncClient, db_session: AsyncSession): from app.core.ids import aurora_id, new_uuid7 token = await _register(client, "morpheus@zion.net") unknown_id = aurora_id("movie", new_uuid7()) response = await client.put( f"/api/v1/movies/{unknown_id}/rating", json={"value": 5.0}, headers=_auth(token) ) assert response.status_code == 404 @pytest.mark.asyncio async def test_rate_movie_rejects_out_of_range_value(client: AsyncClient, db_session: AsyncSession): movie = await make_movie(db_session, provider=await make_provider(db_session)) await db_session.commit() token = await _register(client, "cypher@zion.net") too_high = await client.put( f"/api/v1/movies/{movie_aurora_id(movie)}/rating", json={"value": 11.0}, headers=_auth(token) ) too_low = await client.put( f"/api/v1/movies/{movie_aurora_id(movie)}/rating", json={"value": 0.0}, headers=_auth(token) ) assert too_high.status_code == 422 assert too_low.status_code == 422 @pytest.mark.asyncio async def test_unrate_movie_removes_it_from_the_list(client: AsyncClient, db_session: AsyncSession): movie = await make_movie(db_session, provider=await make_provider(db_session)) await db_session.commit() token = await _register(client, "tank@zion.net") await client.put(f"/api/v1/movies/{movie_aurora_id(movie)}/rating", json={"value": 7.0}, headers=_auth(token)) delete_response = await client.delete( f"/api/v1/movies/{movie_aurora_id(movie)}/rating", headers=_auth(token) ) assert delete_response.status_code == 204 listing = await client.get("/api/v1/account/ratings/movies", headers=_auth(token)) assert listing.json()["total_results"] == 0 @pytest.mark.asyncio async def test_list_rated_movies_is_scoped_to_the_caller(client: AsyncClient, db_session: AsyncSession): movie = await make_movie(db_session, provider=await make_provider(db_session)) await db_session.commit() owner_token = await _register(client, "dozer@zion.net") other_token = await _register(client, "switch@zion.net") await client.put( f"/api/v1/movies/{movie_aurora_id(movie)}/rating", json={"value": 10.0}, headers=_auth(owner_token) ) owner_listing = await client.get("/api/v1/account/ratings/movies", headers=_auth(owner_token)) other_listing = await client.get("/api/v1/account/ratings/movies", headers=_auth(other_token)) assert owner_listing.json()["total_results"] == 1 assert other_listing.json()["total_results"] == 0 @pytest.mark.asyncio async def test_list_rated_movies_requires_auth(client: AsyncClient): response = await client.get("/api/v1/account/ratings/movies") assert response.status_code == 401 @pytest.mark.asyncio async def test_rate_series_creates_a_rating(client: AsyncClient, db_session: AsyncSession): series = await make_series(db_session) await db_session.commit() token = await _register(client, "avon@baltimore.net") response = await client.put( f"/api/v1/series/{series_aurora_id(series)}/rating", json={"value": 4.5}, headers=_auth(token) ) assert response.status_code == 200 assert response.json()["value"] == 4.5 listing = await client.get("/api/v1/account/ratings/series", headers=_auth(token)) assert listing.json()["total_results"] == 1 assert listing.json()["results"][0]["rating"] == 4.5 @pytest.mark.asyncio async def test_rate_series_404_for_unknown_series(client: AsyncClient, db_session: AsyncSession): from app.core.ids import aurora_id, new_uuid7 token = await _register(client, "stringer@baltimore.net") unknown_id = aurora_id("series", new_uuid7()) response = await client.put( f"/api/v1/series/{unknown_id}/rating", json={"value": 5.0}, headers=_auth(token) ) assert response.status_code == 404 @pytest.mark.asyncio async def test_unrate_series_removes_it_from_the_list(client: AsyncClient, db_session: AsyncSession): series = await make_series(db_session) await db_session.commit() token = await _register(client, "mcnulty@baltimore.net") await client.put( f"/api/v1/series/{series_aurora_id(series)}/rating", json={"value": 6.0}, headers=_auth(token) ) delete_response = await client.delete( f"/api/v1/series/{series_aurora_id(series)}/rating", headers=_auth(token) ) assert delete_response.status_code == 204 listing = await client.get("/api/v1/account/ratings/series", headers=_auth(token)) assert listing.json()["total_results"] == 0