Files
pixel/backend/tests/test_mappers_unit.py
张鹏 f9f4560459 Initial commit: Pixel AI comic/video creation platform
- FastAPI backend with SQLModel, Alembic migrations, AgentScope agents
- Next.js 15 frontend with React 19, Tailwind, Zustand, React Flow
- Multi-provider AI system (DashScope, Kling, MiniMax, Volcengine, OpenAI, etc.)
- All HTTP clients migrated from sync requests to async httpx
- Admin-managed API keys via environment variables
- SSRF vulnerability fixed in ensure_url()
2026-04-29 01:20:12 +08:00

776 lines
25 KiB
Python

"""
Unit tests for data model mappers
Tests mapper conversion correctness between entities and schemas.
"""
import pytest
from datetime import datetime
import uuid
from src.models.entities import (
ProjectDB,
AssetDB,
EpisodeDB,
StoryboardDB,
TaskDB,
CanvasMetadataDB,
)
from src.models.schemas import (
CreateProjectRequest,
UpdateProjectRequest,
CreateCharacterAssetRequest,
CreateSceneAssetRequest,
CreatePropAssetRequest,
UpdateAssetRequest,
CreateEpisodeRequest,
UpdateEpisodeRequest,
CreateStoryboardRequest,
UpdateStoryboardRequest,
)
from src.mappers import (
ProjectMapper,
AssetMapper,
EpisodeMapper,
StoryboardMapper,
TaskMapper,
CanvasMetadataMapper,
)
class TestProjectMapper:
"""Test ProjectMapper conversion correctness"""
def test_to_entity_from_create_request(self):
"""Test converting CreateProjectRequest to ProjectDB entity"""
request = CreateProjectRequest(
name="Test Project",
description="Test Description",
type="video",
chapters=[{"title": "Chapter 1"}],
assets=[{"name": "Asset 1"}]
)
entity = ProjectMapper.to_entity(request)
assert entity.name == "Test Project"
assert entity.description == "Test Description"
assert entity.type == "video"
assert entity.status == "active"
assert entity.id is not None
assert entity.created_at is not None
assert entity.updated_at is not None
assert entity.chapters == [{"title": "Chapter 1"}]
def test_to_entity_with_custom_id(self):
"""Test converting with custom ID"""
request = CreateProjectRequest(name="Test Project")
custom_id = str(uuid.uuid4())
entity = ProjectMapper.to_entity(request, project_id=custom_id)
assert entity.id == custom_id
def test_to_schema_from_entity(self):
"""Test converting ProjectDB entity to schema"""
now = datetime.now().timestamp()
entity = ProjectDB(
id="proj_123",
name="Test Project",
description="Test Description",
type="video",
status="active",
created_at=now,
updated_at=now,
resolution="1920x1080",
ratio="16:9"
)
schema = ProjectMapper.to_schema(entity)
assert schema.id == "proj_123"
assert schema.name == "Test Project"
assert schema.description == "Test Description"
assert schema.type == "video"
assert schema.status == "active"
assert schema.resolution == "1920x1080"
assert schema.ratio == "16:9"
def test_update_entity(self):
"""Test updating ProjectDB entity from UpdateProjectRequest"""
entity = ProjectDB(
name="Original Name",
description="Original Description",
resolution="1280x720"
)
update_request = UpdateProjectRequest(
name="Updated Name",
resolution="1920x1080"
)
updated = ProjectMapper.update_entity(entity, update_request)
assert updated.name == "Updated Name"
assert updated.resolution == "1920x1080"
assert updated.description == "Original Description" # unchanged
assert updated.updated_at > entity.created_at
def test_update_entity_partial(self):
"""Test partial update of ProjectDB entity"""
entity = ProjectDB(
name="Original Name",
description="Original Description",
resolution="1280x720"
)
update_request = UpdateProjectRequest(name="Updated Name")
updated = ProjectMapper.update_entity(entity, update_request)
assert updated.name == "Updated Name"
assert updated.description == "Original Description"
assert updated.resolution == "1280x720"
def test_roundtrip_conversion(self):
"""Test roundtrip conversion: Request -> Entity -> Schema"""
request = CreateProjectRequest(
name="Test Project",
description="Test Description",
type="video"
)
entity = ProjectMapper.to_entity(request)
schema = ProjectMapper.to_schema(entity)
assert schema.name == request.name
assert schema.description == request.description
assert schema.type == request.type
class TestAssetMapper:
"""Test AssetMapper conversion correctness"""
def test_to_entity_character_asset(self):
"""Test converting CreateCharacterAssetRequest to AssetDB"""
request = CreateCharacterAssetRequest(
type="character",
name="Hero",
desc="Main character",
tags=["protagonist"],
age="25",
gender="male",
role="hero",
appearance="Tall and strong"
)
entity = AssetMapper.to_entity(request, project_id="proj_123")
assert entity.project_id == "proj_123"
assert entity.type == "character"
assert entity.name == "Hero"
assert entity.desc == "Main character"
assert entity.tags == ["protagonist"]
assert entity.extra_data["age"] == "25"
assert entity.extra_data["gender"] == "male"
assert entity.extra_data["role"] == "hero"
assert entity.extra_data["appearance"] == "Tall and strong"
def test_to_entity_scene_asset(self):
"""Test converting CreateSceneAssetRequest to AssetDB"""
request = CreateSceneAssetRequest(
type="scene",
name="Forest",
desc="Dark forest",
location="Northern Woods",
time_of_day="night",
atmosphere="mysterious"
)
entity = AssetMapper.to_entity(request, project_id="proj_123")
assert entity.type == "scene"
assert entity.name == "Forest"
assert entity.extra_data["location"] == "Northern Woods"
assert entity.extra_data["time_of_day"] == "night"
assert entity.extra_data["atmosphere"] == "mysterious"
def test_to_entity_prop_asset(self):
"""Test converting CreatePropAssetRequest to AssetDB"""
request = CreatePropAssetRequest(
type="prop",
name="Magic Sword",
desc="Ancient sword",
usage="weapon"
)
entity = AssetMapper.to_entity(request, project_id="proj_123")
assert entity.type == "prop"
assert entity.name == "Magic Sword"
assert entity.extra_data["usage"] == "weapon"
def test_to_schema_character_asset(self):
"""Test converting AssetDB to CharacterAsset schema"""
entity = AssetDB(
id="asset_123",
project_id="proj_123",
type="character",
name="Hero",
desc="Main character",
tags=["protagonist"],
extra_data={"age": "25", "gender": "male", "role": "hero"}
)
schema = AssetMapper.to_schema(entity)
assert schema.id == "asset_123"
assert schema.type == "character"
assert schema.name == "Hero"
assert schema.age == "25"
assert schema.gender == "male"
assert schema.role == "hero"
def test_to_schema_scene_asset(self):
"""Test converting AssetDB to SceneAsset schema"""
entity = AssetDB(
id="asset_123",
project_id="proj_123",
type="scene",
name="Forest",
desc="Dark forest",
extra_data={"location": "Northern Woods", "time_of_day": "night"}
)
schema = AssetMapper.to_schema(entity)
assert schema.type == "scene"
assert schema.name == "Forest"
assert schema.location == "Northern Woods"
assert schema.time_of_day == "night"
def test_update_entity(self):
"""Test updating AssetDB entity"""
entity = AssetDB(
project_id="proj_123",
type="character",
name="Original Name",
desc="Original Description",
extra_data={"age": "25"}
)
update_request = UpdateAssetRequest(
name="Updated Name",
age="26"
)
updated = AssetMapper.update_entity(entity, update_request)
assert updated.name == "Updated Name"
assert updated.extra_data["age"] == "26"
assert updated.desc == "Original Description"
def test_roundtrip_conversion_character(self):
"""Test roundtrip conversion for character asset"""
request = CreateCharacterAssetRequest(
type="character",
name="Hero",
desc="Main character",
age="25",
gender="male"
)
entity = AssetMapper.to_entity(request, project_id="proj_123")
schema = AssetMapper.to_schema(entity)
assert schema.name == request.name
assert schema.desc == request.desc
assert schema.age == request.age
assert schema.gender == request.gender
class TestEpisodeMapper:
"""Test EpisodeMapper conversion correctness"""
def test_to_entity(self):
"""Test converting CreateEpisodeRequest to EpisodeDB"""
request = CreateEpisodeRequest(
title="Episode 1",
order=1,
desc="First episode",
status="draft"
)
entity = EpisodeMapper.to_entity(request, project_id="proj_123")
assert entity.project_id == "proj_123"
assert entity.title == "Episode 1"
assert entity.order_index == 1
assert entity.desc == "First episode"
assert entity.status == "draft"
assert entity.id is not None
def test_to_schema(self):
"""Test converting EpisodeDB to Episode schema"""
entity = EpisodeDB(
id="ep_123",
project_id="proj_123",
order_index=1,
title="Episode 1",
desc="First episode",
content="Episode content",
status="draft"
)
schema = EpisodeMapper.to_schema(entity)
assert schema.id == "ep_123"
assert schema.title == "Episode 1"
assert schema.order == 1
assert schema.desc == "First episode"
assert schema.content == "Episode content"
assert schema.status == "draft"
def test_update_entity(self):
"""Test updating EpisodeDB entity"""
entity = EpisodeDB(
project_id="proj_123",
order_index=1,
title="Original Title",
status="draft"
)
update_request = UpdateEpisodeRequest(
title="Updated Title",
status="production"
)
updated = EpisodeMapper.update_entity(entity, update_request)
assert updated.title == "Updated Title"
assert updated.status == "production"
assert updated.order_index == 1 # unchanged
def test_roundtrip_conversion(self):
"""Test roundtrip conversion for episode"""
request = CreateEpisodeRequest(
title="Episode 1",
order=1,
desc="First episode"
)
entity = EpisodeMapper.to_entity(request, project_id="proj_123")
schema = EpisodeMapper.to_schema(entity)
assert schema.title == request.title
assert schema.order == request.order
assert schema.desc == request.desc
class TestStoryboardMapper:
"""Test StoryboardMapper conversion correctness"""
def test_to_entity(self):
"""Test converting CreateStoryboardRequest to StoryboardDB"""
request = CreateStoryboardRequest(
episode_id="ep_123",
order=1,
shot="Shot 1",
desc="Opening scene",
duration="5s",
type="image",
scene_id="scene_123",
character_ids=["char_1", "char_2"],
prop_ids=["prop_1"],
camera_angle="wide",
lens="50mm",
location="forest",
time="morning"
)
entity = StoryboardMapper.to_entity(request, project_id="proj_123")
assert entity.project_id == "proj_123"
assert entity.episode_id == "ep_123"
assert entity.order_index == 1
assert entity.shot == "Shot 1"
assert entity.desc == "Opening scene"
assert entity.duration == "5s"
assert entity.type == "image"
assert entity.scene_id == "scene_123"
assert entity.character_ids == ["char_1", "char_2"]
assert entity.prop_ids == ["prop_1"]
assert entity.camera_angle == "wide"
assert entity.lens == "50mm"
assert entity.location == "forest"
assert entity.time == "morning"
def test_to_schema(self):
"""Test converting StoryboardDB to Storyboard schema"""
entity = StoryboardDB(
id="sb_123",
project_id="proj_123",
episode_id="ep_123",
order_index=1,
shot="Shot 1",
desc="Opening scene",
duration="5s",
type="image",
scene_id="scene_123",
character_ids=["char_1"],
camera_angle="wide",
location="forest"
)
schema = StoryboardMapper.to_schema(entity)
assert schema.id == "sb_123"
assert schema.episode_id == "ep_123"
assert schema.order == 1
assert schema.shot == "Shot 1"
assert schema.scene_id == "scene_123"
assert schema.character_ids == ["char_1"]
assert schema.camera_angle == "wide"
assert schema.location == "forest"
def test_update_entity(self):
"""Test updating StoryboardDB entity"""
entity = StoryboardDB(
project_id="proj_123",
episode_id="ep_123",
order_index=1,
shot="Original Shot",
desc="Original Description",
duration="5s",
type="image"
)
update_request = UpdateStoryboardRequest(
shot="Updated Shot",
duration="10s",
camera_angle="close-up"
)
updated = StoryboardMapper.update_entity(entity, update_request)
assert updated.shot == "Updated Shot"
assert updated.duration == "10s"
assert updated.camera_angle == "close-up"
assert updated.desc == "Original Description" # unchanged
def test_roundtrip_conversion(self):
"""Test roundtrip conversion for storyboard"""
request = CreateStoryboardRequest(
episode_id="ep_123",
order=1,
shot="Shot 1",
desc="Opening scene",
duration="5s",
type="image",
camera_angle="wide"
)
entity = StoryboardMapper.to_entity(request, project_id="proj_123")
schema = StoryboardMapper.to_schema(entity)
assert schema.episode_id == request.episode_id
assert schema.order == request.order
assert schema.shot == request.shot
assert schema.camera_angle == request.camera_angle
class TestTaskMapper:
"""Test TaskMapper conversion correctness"""
def test_to_entity(self):
"""Test creating TaskDB entity from parameters"""
entity = TaskMapper.to_entity(
task_type="image",
model="flux-dev",
params={"prompt": "test"},
status="pending",
user_id="user_123",
project_id="proj_123",
max_retries=5
)
assert entity.type == "image"
assert entity.model == "flux-dev"
assert entity.params == {"prompt": "test"}
assert entity.status == "pending"
assert entity.user_id == "user_123"
assert entity.project_id == "proj_123"
assert entity.max_retries == 5
assert entity.retry_count == 0
assert entity.id is not None
def test_to_entity_with_custom_id(self):
"""Test creating TaskDB with custom ID"""
custom_id = str(uuid.uuid4())
entity = TaskMapper.to_entity(
task_type="video",
model="kling-v1",
params={"prompt": "test"},
task_id=custom_id
)
assert entity.id == custom_id
def test_to_schema(self):
"""Test converting TaskDB to Task schema"""
now = datetime.now().timestamp()
entity = TaskDB(
id="task_123",
type="image",
status="success",
created_at=now,
updated_at=now,
model="flux-dev",
params={"prompt": "test"},
provider_task_id="provider_123",
result={"url": "https://example.com/image.png"},
retry_count=1,
max_retries=3,
started_at=now,
completed_at=now + 10,
user_id="user_123",
project_id="proj_123"
)
schema = TaskMapper.to_schema(entity)
assert schema.id == "task_123"
assert schema.type == "image"
assert schema.status == "success"
assert schema.model == "flux-dev"
assert schema.provider_task_id == "provider_123"
assert schema.result["url"] == "https://example.com/image.png"
assert schema.retry_count == 1
assert schema.user_id == "user_123"
def test_update_status_to_processing(self):
"""Test updating task status to processing"""
entity = TaskDB(
type="image",
status="pending",
model="flux-dev",
params={"prompt": "test"}
)
updated = TaskMapper.update_status(
entity,
status="processing",
provider_task_id="provider_123"
)
assert updated.status == "processing"
assert updated.provider_task_id == "provider_123"
assert updated.started_at is not None
assert updated.completed_at is None
def test_update_status_to_success(self):
"""Test updating task status to success"""
entity = TaskDB(
type="image",
status="processing",
model="flux-dev",
params={"prompt": "test"}
)
result = {"url": "https://example.com/image.png"}
updated = TaskMapper.update_status(
entity,
status="success",
result=result
)
assert updated.status == "success"
assert updated.result == result
assert updated.completed_at is not None
def test_update_status_to_failed(self):
"""Test updating task status to failed"""
entity = TaskDB(
type="image",
status="processing",
model="flux-dev",
params={"prompt": "test"}
)
updated = TaskMapper.update_status(
entity,
status="failed",
error="Generation failed"
)
assert updated.status == "failed"
assert updated.error == "Generation failed"
assert updated.completed_at is not None
def test_increment_retry(self):
"""Test incrementing retry count"""
entity = TaskDB(
type="image",
status="failed",
model="flux-dev",
params={"prompt": "test"},
retry_count=0
)
updated = TaskMapper.increment_retry(entity)
assert updated.retry_count == 1
assert updated.updated_at > entity.created_at
def test_multiple_retry_increments(self):
"""Test multiple retry increments"""
entity = TaskDB(
type="image",
status="failed",
model="flux-dev",
params={"prompt": "test"},
retry_count=0,
max_retries=3
)
# Increment 3 times
for i in range(3):
entity = TaskMapper.increment_retry(entity)
assert entity.retry_count == i + 1
assert entity.retry_count == 3
assert entity.retry_count == entity.max_retries
class TestCanvasMetadataMapper:
"""Test CanvasMetadataMapper conversion correctness"""
def test_to_entity_general_canvas(self):
"""Test creating general canvas metadata entity"""
from src.models.schemas import CreateGeneralCanvasRequest
request = CreateGeneralCanvasRequest(
name="Main Canvas",
description="Main project canvas"
)
entity = CanvasMetadataMapper.to_entity(
schema=request,
project_id="proj_123"
)
assert entity.project_id == "proj_123"
assert entity.canvas_type == "general"
assert entity.name == "Main Canvas"
assert entity.description == "Main project canvas"
assert entity.order_index == 0
assert entity.is_pinned is False
assert entity.related_entity_type is None
assert entity.related_entity_id is None
def test_create_asset_canvas(self):
"""Test creating asset canvas metadata entity"""
entity = CanvasMetadataMapper.create_asset_canvas(
project_id="proj_123",
asset_id="asset_123",
asset_name="Hero"
)
assert entity.project_id == "proj_123"
assert entity.canvas_type == "asset"
assert entity.related_entity_type == "asset"
assert entity.related_entity_id == "asset_123"
assert entity.name == "Hero Canvas"
def test_create_storyboard_canvas(self):
"""Test creating storyboard canvas metadata entity"""
entity = CanvasMetadataMapper.create_storyboard_canvas(
project_id="proj_123",
storyboard_id="sb_123",
storyboard_shot="Shot 1"
)
assert entity.project_id == "proj_123"
assert entity.canvas_type == "storyboard"
assert entity.related_entity_type == "storyboard"
assert entity.related_entity_id == "sb_123"
assert entity.name == "Shot 1 Canvas"
def test_to_schema(self):
"""Test converting CanvasMetadataDB to schema"""
now = datetime.now().timestamp()
entity = CanvasMetadataDB(
id="canvas_123",
project_id="proj_123",
canvas_type="general",
name="Main Canvas",
description="Main project canvas",
order_index=0,
is_pinned=True,
tags=["main", "primary"],
node_count=5,
last_accessed_at=now,
access_count=10,
created_at=now,
updated_at=now
)
schema = CanvasMetadataMapper.to_schema(entity)
assert schema.id == "canvas_123"
assert schema.project_id == "proj_123"
assert schema.canvas_type == "general"
assert schema.name == "Main Canvas"
assert schema.is_pinned is True
assert len(schema.tags) == 2
assert schema.node_count == 5
assert schema.access_count == 10
def test_update_entity(self):
"""Test updating canvas metadata"""
from src.models.schemas import UpdateCanvasMetadataRequest
entity = CanvasMetadataDB(
project_id="proj_123",
canvas_type="general",
name="Original Name",
description="Original Description",
order_index=0,
is_pinned=False,
tags=["old"]
)
update_request = UpdateCanvasMetadataRequest(
name="Updated Name",
isPinned=True,
tags=["new", "updated"]
)
updated = CanvasMetadataMapper.update_entity(entity, update_request)
assert updated.name == "Updated Name"
assert updated.is_pinned is True
assert updated.tags == ["new", "updated"]
assert updated.description == "Original Description" # unchanged
def test_update_access(self):
"""Test updating canvas access tracking"""
entity = CanvasMetadataDB(
project_id="proj_123",
canvas_type="general",
name="Main Canvas",
order_index=0,
is_pinned=False,
access_count=5
)
initial_access_count = entity.access_count
updated = CanvasMetadataMapper.update_access(entity)
assert updated.access_count == initial_access_count + 1
assert updated.last_accessed_at is not None
if __name__ == "__main__":
pytest.main([__file__, "-v"])