352 lines
No EOL
11 KiB
Python
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={}
|
|
) |