video-accessibility/backend/tests/unit/test_models.py
2025-08-24 16:28:33 -05:00

352 lines
No EOL
11 KiB
Python

from datetime import datetime
import pytest
from pydantic import ValidationError
from app.models.job import (
Job,
JobCreate,
JobStatus,
JobUpdate,
LangOutput,
RequestedOutputs,
Source,
)
from app.models.user import User, UserCreate, UserRole
class TestJobModel:
"""Test Job model validation and behavior"""
def test_job_creation_minimal(self):
"""Test creating job with minimal required fields"""
job_data = {
"client_id": "user123",
"title": "Test Video",
"source": {
"filename": "test.mp4",
"gcs_uri": "gs://bucket/test.mp4"
},
"requested_outputs": {
"captions_vtt": True,
"audio_description_vtt": True,
"audio_description_mp3": True
}
}
job = Job(**job_data)
assert job.client_id == "user123"
assert job.title == "Test Video"
assert job.status == JobStatus.CREATED
assert job.source.language == "en" # Default value
assert job.requested_outputs.captions_vtt is True
def test_job_creation_full(self):
"""Test creating job with all fields"""
job_data = {
"_id": "job123",
"client_id": "user123",
"title": "Test Video",
"source": {
"filename": "test.mp4",
"original_filename": "original_test.mp4",
"gcs_uri": "gs://bucket/test.mp4",
"duration_s": 120.5,
"language": "en"
},
"requested_outputs": {
"captions_vtt": True,
"audio_description_vtt": True,
"audio_description_mp3": True,
"languages": ["es", "fr"],
"transcreation": ["es"]
},
"status": "pending_qc",
"outputs": {
"en": {
"captions_vtt_gcs": "gs://bucket/en/captions.vtt",
"ad_vtt_gcs": "gs://bucket/en/ad.vtt"
}
},
"ai": {
"confidence": 0.95,
"ingestion_json": {"test": "data"}
},
"created_at": datetime.utcnow(),
"updated_at": datetime.utcnow()
}
job = Job(**job_data)
assert job.id == "job123"
assert job.source.duration_s == 120.5
assert job.requested_outputs.languages == ["es", "fr"]
assert job.requested_outputs.transcreation == ["es"]
assert job.ai.confidence == 0.95
def test_job_status_enum(self):
"""Test job status enum values"""
assert JobStatus.CREATED == "created"
assert JobStatus.PENDING_QC == "pending_qc"
assert JobStatus.APPROVED_ENGLISH == "approved_english"
assert JobStatus.COMPLETED == "completed"
def test_source_validation(self):
"""Test source model validation"""
# Valid source
source = Source(
filename="test.mp4",
gcs_uri="gs://bucket/test.mp4"
)
assert source.language == "en" # Default
# Invalid language (too short)
with pytest.raises(ValidationError):
Source(
filename="test.mp4",
gcs_uri="gs://bucket/test.mp4",
language="x" # Too short
)
def test_requested_outputs_defaults(self):
"""Test RequestedOutputs default values"""
outputs = RequestedOutputs()
assert outputs.captions_vtt is True
assert outputs.audio_description_vtt is True
assert outputs.audio_description_mp3 is True
assert outputs.languages == []
assert outputs.transcreation == []
def test_lang_output_model(self):
"""Test language output model"""
lang_output = LangOutput(
captions_vtt_gcs="gs://bucket/es/captions.vtt",
ad_vtt_gcs="gs://bucket/es/ad.vtt",
ad_mp3_gcs="gs://bucket/es/ad.mp3",
origin="translate",
qa_notes="Looks good"
)
assert lang_output.origin == "translate"
assert lang_output.qa_notes == "Looks good"
def test_lang_output_invalid_origin(self):
"""Test LangOutput with invalid origin value"""
with pytest.raises(ValidationError):
LangOutput(
captions_vtt_gcs="gs://bucket/es/captions.vtt",
origin="invalid_origin" # Not in allowed values
)
def test_job_create_schema(self):
"""Test JobCreate schema"""
job_create = JobCreate(
title="Test Video",
language="en",
requested_outputs=RequestedOutputs(
captions_vtt=True,
languages=["es", "fr"]
)
)
assert job_create.title == "Test Video"
assert job_create.language == "en"
assert job_create.requested_outputs.languages == ["es", "fr"]
def test_job_update_schema(self):
"""Test JobUpdate schema"""
job_update = JobUpdate(
title="Updated Title",
status=JobStatus.PENDING_QC
)
assert job_update.title == "Updated Title"
assert job_update.status == JobStatus.PENDING_QC
assert job_update.review is None # Optional field
class TestUserModel:
"""Test User model validation and behavior"""
def test_user_creation_minimal(self):
"""Test creating user with minimal required fields"""
user_data = {
"email": "test@example.com",
"hashed_password": "hashed_password_here",
"full_name": "Test User"
}
user = User(**user_data)
assert user.email == "test@example.com"
assert user.role == UserRole.CLIENT # Default
assert user.is_active is True # Default
def test_user_creation_full(self):
"""Test creating user with all fields"""
user_data = {
"_id": "user123",
"email": "admin@example.com",
"hashed_password": "hashed_password_here",
"full_name": "Admin User",
"role": "admin",
"is_active": True,
"created_at": datetime.utcnow(),
"updated_at": datetime.utcnow()
}
user = User(**user_data)
assert user.id == "user123"
assert user.role == UserRole.ADMIN
assert user.is_active is True
def test_user_invalid_email(self):
"""Test user creation with invalid email"""
with pytest.raises(ValidationError):
User(
email="invalid_email", # Not a valid email format
hashed_password="hashed_password",
full_name="Test User"
)
def test_user_role_enum(self):
"""Test user role enum values"""
assert UserRole.CLIENT == "client"
assert UserRole.REVIEWER == "reviewer"
assert UserRole.ADMIN == "admin"
def test_user_create_schema(self):
"""Test UserCreate schema"""
user_create = UserCreate(
email="newuser@example.com",
password="plain_password",
full_name="New User",
role=UserRole.REVIEWER
)
assert user_create.email == "newuser@example.com"
assert user_create.password == "plain_password"
assert user_create.role == UserRole.REVIEWER
def test_user_create_default_role(self):
"""Test UserCreate with default role"""
user_create = UserCreate(
email="newuser@example.com",
password="plain_password",
full_name="New User"
)
assert user_create.role == UserRole.CLIENT
class TestJobStatusTransitions:
"""Test job status state machine transitions"""
def test_valid_status_transitions(self):
"""Test that job can be created with valid statuses"""
valid_statuses = [
JobStatus.CREATED,
JobStatus.INGESTING,
JobStatus.AI_PROCESSING,
JobStatus.PENDING_QC,
JobStatus.APPROVED_ENGLISH,
JobStatus.REJECTED,
JobStatus.TRANSLATING,
JobStatus.TTS_GENERATING,
JobStatus.PENDING_FINAL_REVIEW,
JobStatus.COMPLETED
]
job_base = {
"client_id": "user123",
"title": "Test Video",
"source": {
"filename": "test.mp4",
"gcs_uri": "gs://bucket/test.mp4"
},
"requested_outputs": {
"captions_vtt": True
}
}
for status in valid_statuses:
job = Job(**{**job_base, "status": status})
assert job.status == status
def test_job_status_string_values(self):
"""Test that job status enum has correct string values"""
# This ensures the state machine values match the plan
expected_statuses = [
"created", "ingesting", "ai_processing", "pending_qc",
"approved_english", "rejected", "translating",
"tts_generating", "pending_final_review", "completed"
]
actual_statuses = [status.value for status in JobStatus]
for expected in expected_statuses:
assert expected in actual_statuses
class TestModelFieldValidation:
"""Test field-level validation for models"""
def test_source_language_constraint(self):
"""Test source language field constraints"""
# Valid languages
valid_languages = ["en", "es", "fr", "de", "pt-BR", "zh-CN"]
for lang in valid_languages:
source = Source(
filename="test.mp4",
gcs_uri="gs://bucket/test.mp4",
language=lang
)
assert source.language == lang
def test_source_language_too_short(self):
"""Test source language validation for too short values"""
with pytest.raises(ValidationError):
Source(
filename="test.mp4",
gcs_uri="gs://bucket/test.mp4",
language="x" # Too short
)
def test_source_language_too_long(self):
"""Test source language validation for too long values"""
with pytest.raises(ValidationError):
Source(
filename="test.mp4",
gcs_uri="gs://bucket/test.mp4",
language="this_is_too_long" # Too long
)
def test_job_title_required(self):
"""Test that job title is required"""
with pytest.raises(ValidationError):
Job(
client_id="user123",
# title missing
source={
"filename": "test.mp4",
"gcs_uri": "gs://bucket/test.mp4"
},
requested_outputs={}
)
def test_client_id_required(self):
"""Test that client_id is required"""
with pytest.raises(ValidationError):
Job(
# client_id missing
title="Test Video",
source={
"filename": "test.mp4",
"gcs_uri": "gs://bucket/test.mp4"
},
requested_outputs={}
)