animetix-web / tests /api /games /test_animinator_coverage.py
MissawB's picture
Upload folder using huggingface_hub (part 5)
9226a4f verified
Raw
History Blame Contribute Delete
9.59 kB
"""Coverage tests for ``animetix.api.games.animinator`` (solo Animinator oracle
HTTP API: ask + guess).
The async SSE variant (``AniminatorStreamView`` in streams.py) is covered by
tests/api/test_animinator_stream_view.py — nothing here drives it. These tests
exercise the two synchronous DRF views end-to-end through the real Django
session. ``animinator_service`` / ``catalog_service`` are replaced with
instance-level container overrides; ``deduct_berrix`` is patched at the module
namespace so the GPU-turn charge doesn't need a seeded wallet (one test lets it
raise to prove the 402 is not swallowed into a 500).
"""
from unittest.mock import MagicMock, patch
import animetix.api.games.animinator as animinator_mod
import pytest
from animetix.api.billing import PaymentRequired
from animetix.containers import container
from animetix.models import GameplaySession
from dependency_injector import providers
from django.contrib.auth.models import User
from django.urls import reverse
from rest_framework.test import APIClient
@pytest.fixture(autouse=True)
def _wire_animinator():
container.wire(modules=[animinator_mod])
yield
@pytest.fixture
def api_client():
return APIClient()
@pytest.fixture
def auth_client(db):
user = User.objects.create_user(username="oracle-player", password="pw")
client = APIClient()
client.force_authenticate(user=user)
return client
def _oracle_service():
svc = MagicMock()
svc.ask_oracle_stream.return_value = iter(["Il ", "est ", "un ninja."])
return svc
def _catalog_service(data):
cs = MagicMock()
cs.load_data.return_value = data
return cs
def _catalog_data():
return {
"lookup": [{"title": "Naruto"}, {"title": "Luffy"}],
"title_to_full_data": {"Naruto": {}, "Luffy": {}},
}
def _override_core(**services):
return [
getattr(container.core, name).override(providers.Object(svc))
for name, svc in services.items()
]
def _seed_ask_session(client, secret="Naruto", questions_left=20, chat=None):
session = client.session
session["animinator_secret"] = secret
session["animinator_questions_left"] = questions_left
session["animinator_chat"] = chat if chat is not None else []
session["media_type"] = "Anime"
session.save()
# --------------------------------------------------------------------------- #
# AniminatorAskView (IsAuthenticated, consumes Berrix)
# --------------------------------------------------------------------------- #
@pytest.mark.django_db
def test_ask_requires_authentication(api_client):
resp = api_client.post(
reverse("api_animinator_ask"), {"question": "?"}, format="json"
)
assert resp.status_code in (401, 403)
@pytest.mark.django_db
def test_ask_missing_question_returns_400(auth_client):
resp = auth_client.post(reverse("api_animinator_ask"), {}, format="json")
assert resp.status_code == 400
assert resp.json()["error"] == "No question provided."
@pytest.mark.django_db
def test_ask_new_game_catalog_not_found_returns_404(auth_client):
cat = _catalog_service(None)
_override_core(catalog_service=cat, animinator_service=_oracle_service())
try:
with patch.object(animinator_mod, "deduct_berrix"):
resp = auth_client.post(
reverse("api_animinator_ask"), {"question": "ninja?"}, format="json"
)
finally:
container.core.catalog_service.reset_last_overriding()
container.core.animinator_service.reset_last_overriding()
assert resp.status_code == 404
assert resp.json()["error"] == "Catalog not found"
@pytest.mark.django_db
def test_ask_new_game_seeds_secret_and_returns_answer(auth_client):
cat = _catalog_service(_catalog_data())
oracle = _oracle_service()
_override_core(catalog_service=cat, animinator_service=oracle)
try:
with patch.object(animinator_mod, "deduct_berrix") as deduct:
resp = auth_client.post(
reverse("api_animinator_ask"),
{"question": "Est-ce un ninja ?", "media_type": "Anime"},
format="json",
)
finally:
container.core.catalog_service.reset_last_overriding()
container.core.animinator_service.reset_last_overriding()
assert resp.status_code == 200
body = resp.json()
# Streamed tokens are joined and HTML-sanitized.
assert body["answer"] == "Il est un ninja."
assert body["questions_left"] == 19
deduct.assert_called_once()
# A secret was chosen from the catalog pool and stored in the session.
assert auth_client.session["animinator_secret"] in ("Naruto", "Luffy")
oracle.ask_oracle_stream.assert_called_once()
@pytest.mark.django_db
def test_ask_last_question_ends_game_and_records_loss(auth_client):
_seed_ask_session(auth_client, secret="Naruto", questions_left=1)
oracle = _oracle_service()
_override_core(
catalog_service=_catalog_service(_catalog_data()), animinator_service=oracle
)
try:
with patch.object(animinator_mod, "deduct_berrix"):
resp = auth_client.post(
reverse("api_animinator_ask"), {"question": "dernier ?"}, format="json"
)
finally:
container.core.catalog_service.reset_last_overriding()
container.core.animinator_service.reset_last_overriding()
assert resp.status_code == 200
assert resp.json()["questions_left"] == 0
lost = GameplaySession.objects.filter(game_mode="animinator", was_won=False)
assert lost.exists()
assert auth_client.session.get("animinator_game_over") is True
@pytest.mark.django_db
def test_ask_insufficient_berrix_returns_402_not_500(auth_client):
_seed_ask_session(auth_client, secret="Naruto")
_override_core(
catalog_service=_catalog_service(_catalog_data()),
animinator_service=_oracle_service(),
)
try:
with patch.object(
animinator_mod, "deduct_berrix", side_effect=PaymentRequired()
):
resp = auth_client.post(
reverse("api_animinator_ask"), {"question": "ninja?"}, format="json"
)
finally:
container.core.catalog_service.reset_last_overriding()
container.core.animinator_service.reset_last_overriding()
# PaymentRequired is raised OUTSIDE the try/except, so it surfaces as 402
# rather than being swallowed into a generic 500.
assert resp.status_code == 402
@pytest.mark.django_db
def test_ask_oracle_failure_returns_500(auth_client):
_seed_ask_session(auth_client, secret="Naruto")
oracle = MagicMock()
oracle.ask_oracle_stream.side_effect = RuntimeError("oracle down")
_override_core(
catalog_service=_catalog_service(_catalog_data()), animinator_service=oracle
)
try:
with patch.object(animinator_mod, "deduct_berrix"):
resp = auth_client.post(
reverse("api_animinator_ask"), {"question": "ninja?"}, format="json"
)
finally:
container.core.catalog_service.reset_last_overriding()
container.core.animinator_service.reset_last_overriding()
assert resp.status_code == 500
assert resp.json()["error"] == "Internal server error"
# --------------------------------------------------------------------------- #
# AniminatorGuessView (AllowAny, CPU comparison)
# --------------------------------------------------------------------------- #
@pytest.mark.django_db
def test_guess_no_game_in_progress_returns_400(api_client):
resp = api_client.post(
reverse("api_animinator_guess"), {"guess": "Naruto"}, format="json"
)
assert resp.status_code == 400
assert resp.json()["error"] == "No game in progress"
@pytest.mark.django_db
def test_guess_missing_guess_returns_400(api_client):
_seed_ask_session(api_client, secret="Naruto")
_override_core(animinator_service=_oracle_service())
try:
resp = api_client.post(reverse("api_animinator_guess"), {}, format="json")
finally:
container.core.animinator_service.reset_last_overriding()
assert resp.status_code == 400
assert resp.json()["error"] == "guess is required"
@pytest.mark.django_db
def test_guess_correct_ends_game_and_reveals_secret(auth_client):
_seed_ask_session(auth_client, secret="Naruto", chat=[{"q": "a", "a": "b"}])
oracle = MagicMock()
oracle.check_guess.return_value = True
_override_core(animinator_service=oracle)
try:
resp = auth_client.post(
reverse("api_animinator_guess"), {"guess": "Naruto"}, format="json"
)
finally:
container.core.animinator_service.reset_last_overriding()
body = resp.json()
assert body["correct"] is True
assert body["game_over"] is True
assert body["secret"] == "Naruto"
assert GameplaySession.objects.filter(game_mode="animinator", was_won=True).exists()
# Secret cleared for replay.
assert auth_client.session["animinator_secret"] == ""
@pytest.mark.django_db
def test_guess_wrong_keeps_secret_hidden(api_client):
_seed_ask_session(api_client, secret="Naruto")
oracle = MagicMock()
oracle.check_guess.return_value = False
_override_core(animinator_service=oracle)
try:
resp = api_client.post(
reverse("api_animinator_guess"), {"guess": "Luffy"}, format="json"
)
finally:
container.core.animinator_service.reset_last_overriding()
body = resp.json()
assert body["correct"] is False
assert body["game_over"] is False
assert body["secret"] is None