Spaces:
Build error
Build error
Download tests/api/test_forge_vn_api.py from MissawB/animetix-web: direct link, hf CLI and curl.
- Browser
- Download file 7.57 kB
-
https://huggingface.co/spaces/MissawB/animetix-web/resolve/main/tests/api/test_forge_vn_api.py
- Command line
-
hf download hf://spaces/MissawB/animetix-web/tests/api/test_forge_vn_api.py
-
curl -L -o test_forge_vn_api.py https://huggingface.co/spaces/MissawB/animetix-web/resolve/main/tests/api/test_forge_vn_api.py
7.57 kB
| from unittest.mock import MagicMock | |
| import pytest | |
| from animetix.containers import container | |
| from animetix.models import CreativeFusion | |
| from dependency_injector import providers | |
| from django.contrib.auth.models import User | |
| from django.urls import reverse | |
| from rest_framework import status | |
| def mock_user(db): | |
| user = User.objects.create_user(username="testuser", password="password") | |
| return user | |
| def other_user(db): | |
| user = User.objects.create_user(username="otheruser", password="password") | |
| return user | |
| def sample_fusion(db, mock_user): | |
| return CreativeFusion.objects.create( | |
| title_a="Anime A", | |
| title_b="Anime B", | |
| media_type_a="Anime", | |
| media_type_b="Anime", | |
| scenario_text="Un scénario épique.", | |
| creator=mock_user, | |
| ) | |
| class TestTheaterGallery: | |
| """Public VN gallery (TheaterListView) — bounded, N+1-free, field-whitelisted.""" | |
| def _make_fusions(creator, count, script=True): | |
| return [ | |
| CreativeFusion.objects.create( | |
| title_a=f"A{i}", | |
| title_b=f"B{i}", | |
| media_type_a="Anime", | |
| media_type_b="Anime", | |
| scenario_text="Scénario.", | |
| creator=creator, | |
| vn_script={"title": f"S{i}", "scenes": []} if script else None, | |
| ) | |
| for i in range(count) | |
| ] | |
| def test_gallery_is_bounded(self, api_client, mock_user): | |
| from animetix.api.forge_vn import GALLERY_MAX_ITEMS | |
| self._make_fusions(mock_user, GALLERY_MAX_ITEMS + 5) | |
| url = reverse("api_theater_list") | |
| response = api_client.get(url) | |
| assert response.status_code == status.HTTP_200_OK | |
| assert len(response.data) == GALLERY_MAX_ITEMS | |
| def test_gallery_query_count_does_not_grow_with_items( | |
| self, api_client, mock_user, other_user | |
| ): | |
| from django.db import connection | |
| from django.test.utils import CaptureQueriesContext | |
| url = reverse("api_theater_list") | |
| api_client.get(url) # warm-up (middleware get_or_create SiteConfiguration) | |
| for fusion in self._make_fusions(mock_user, 3): | |
| fusion.likes.add(mock_user, other_user) | |
| with CaptureQueriesContext(connection) as small: | |
| response = api_client.get(url) | |
| assert response.status_code == status.HTTP_200_OK | |
| assert all(item["likes_count"] == 2 for item in response.data) | |
| for fusion in self._make_fusions(mock_user, 7): | |
| fusion.likes.add(mock_user, other_user) | |
| with CaptureQueriesContext(connection) as large: | |
| response = api_client.get(url) | |
| assert response.status_code == status.HTTP_200_OK | |
| assert len(response.data) == 10 | |
| # Sans prefetch, likes_count/creator_name émettent des requêtes PAR | |
| # LIGNE : passer de 3 à 10 items ferait grossir le décompte. | |
| assert len(large.captured_queries) == len(small.captured_queries) | |
| def test_gallery_does_not_expose_raw_liker_ids(self, api_client, mock_user): | |
| fusion = self._make_fusions(mock_user, 1)[0] | |
| fusion.likes.add(mock_user) | |
| url = reverse("api_theater_list") | |
| response = api_client.get(url) | |
| assert response.status_code == status.HTTP_200_OK | |
| item = response.data[0] | |
| assert "likes" not in item | |
| assert item["likes_count"] == 1 | |
| assert item["creator_name"] == mock_user.username | |
| def test_gallery_is_liked_reflects_authenticated_user( | |
| self, api_client, mock_user, other_user | |
| ): | |
| liked, not_liked = self._make_fusions(mock_user, 2) | |
| liked.likes.add(other_user) | |
| api_client.force_authenticate(user=other_user) | |
| url = reverse("api_theater_list") | |
| response = api_client.get(url) | |
| assert response.status_code == status.HTTP_200_OK | |
| by_id = {item["id"]: item for item in response.data} | |
| assert by_id[liked.id]["is_liked"] is True | |
| assert by_id[not_liked.id]["is_liked"] is False | |
| class TestForgeVNAPI: | |
| def test_get_vn_script(self, api_client, sample_fusion): | |
| url = reverse("api_forge_vn", kwargs={"fusion_id": sample_fusion.id}) | |
| response = api_client.get(url) | |
| assert response.status_code == status.HTTP_200_OK | |
| assert response.data["vn_script"] is None | |
| def test_generate_vn_script_unauthorized( | |
| self, api_client, other_user, sample_fusion | |
| ): | |
| api_client.force_authenticate(user=other_user) | |
| url = reverse("api_forge_vn", kwargs={"fusion_id": sample_fusion.id}) | |
| data = {"action": "generate"} | |
| response = api_client.post(url, data, format="json") | |
| assert response.status_code == status.HTTP_403_FORBIDDEN | |
| def test_generate_vn_script_success( | |
| self, api_client, mock_user, sample_fusion, mock_container | |
| ): | |
| api_client.force_authenticate(user=mock_user) | |
| url = reverse("api_forge_vn", kwargs={"fusion_id": sample_fusion.id}) | |
| data = {"action": "generate"} | |
| mock_script_data = { | |
| "title": "Fusion Script", | |
| "scenes": [ | |
| { | |
| "character": "Protagonist", | |
| "text": "Hello world", | |
| "mood": "Happy", | |
| "bg_prompt": "Forest", | |
| } | |
| ], | |
| } | |
| # Create a mock that behaves like a Pydantic model | |
| class MockScript: | |
| def model_dump(self): | |
| return mock_script_data | |
| # Configure the global mock_container | |
| mock_container.visual_novel_service.generate_script.return_value = MockScript() | |
| mock_container.visual_novel_service.return_value.generate_script.return_value = ( | |
| MockScript() | |
| ) | |
| mock_guardrail = MagicMock() | |
| mock_guardrail.validate_input.return_value = {"is_safe": True} | |
| mock_guardrail.validate_output.return_value = {"is_safe": True} | |
| mock_usage = MagicMock() | |
| mock_usage.check_quota.return_value = True | |
| with ( | |
| container.core.visual_novel_service.override( | |
| providers.Object(mock_container.visual_novel_service) | |
| ), | |
| container.core.guardrail_service.override(providers.Object(mock_guardrail)), | |
| container.infrastructure.usage_port.override(providers.Object(mock_usage)), | |
| ): | |
| response = api_client.post(url, data, format="json") | |
| assert response.status_code == status.HTTP_200_OK | |
| assert response.data["vn_script"]["title"] == "Fusion Script" | |
| # Verify persistence | |
| sample_fusion.refresh_from_db() | |
| assert sample_fusion.vn_script["title"] == "Fusion Script" | |
| # exercises the live VN-script generation pipeline (no ollama in CI) | |
| def test_update_vn_script_success(self, api_client, mock_user, sample_fusion): | |
| api_client.force_authenticate(user=mock_user) | |
| url = reverse("api_forge_vn", kwargs={"fusion_id": sample_fusion.id}) | |
| new_script = {"title": "Updated Title", "scenes": []} | |
| data = {"action": "update", "vn_script": new_script} | |
| response = api_client.post(url, data, format="json") | |
| assert response.status_code == status.HTTP_200_OK | |
| assert response.data["vn_script"]["title"] == "Updated Title" | |
| sample_fusion.refresh_from_db() | |
| assert sample_fusion.vn_script["title"] == "Updated Title" | |