audio-processor / test_job_entity_external_id.py
vitek
add bearer token to endpoints
acb0ec6
raw
history blame
12.6 kB
"""Unit tests for Job entity and repository with external job ID support."""
import pytest
import asyncio
from datetime import datetime, timedelta
from domain.entities.job import Job
from domain.exceptions.domain_exceptions import DuplicateExternalJobIdError, ValidationError
from domain.services.validation_service import ValidationService
from infrastructure.repositories.job_repository import InMemoryJobRepository, JobRecord
class TestJobEntityWithExternalId:
"""Test Job entity with external job ID and bearer token support."""
def test_job_creation_with_external_id(self):
"""Test creating a job with external job ID."""
job = Job.create_new(
video_filename="test.mp4",
file_size_bytes=1000000,
output_format="mp3",
quality="medium",
external_job_id="ext-job-123",
bearer_token="bearer-token-xyz"
)
assert job.external_job_id == "ext-job-123"
assert job.bearer_token == "bearer-token-xyz"
assert job.has_external_job_id is True
assert job.id is not None # Internal ID still generated
def test_job_creation_without_external_id(self):
"""Test creating a job without external job ID (backwards compatibility)."""
job = Job.create_new(
video_filename="test.mp4",
file_size_bytes=1000000,
output_format="mp3",
quality="medium"
)
assert job.external_job_id is None
assert job.bearer_token is None
assert job.has_external_job_id is False
assert job.id is not None # Internal ID still generated
def test_job_creation_with_empty_external_id(self):
"""Test creating a job with empty external job ID."""
job = Job.create_new(
video_filename="test.mp4",
file_size_bytes=1000000,
output_format="mp3",
quality="medium",
external_job_id="",
bearer_token=None
)
assert job.external_job_id == ""
assert job.has_external_job_id is False
def test_clear_bearer_token(self):
"""Test clearing bearer token for security."""
job = Job.create_new(
video_filename="test.mp4",
file_size_bytes=1000000,
output_format="mp3",
quality="medium",
bearer_token="secret-token"
)
assert job.bearer_token == "secret-token"
original_updated_at = job.updated_at
job.clear_bearer_token()
assert job.bearer_token is None
assert job.updated_at > original_updated_at
def test_has_external_job_id_property(self):
"""Test the has_external_job_id property."""
# With valid external ID
job1 = Job.create_new("test.mp4", 1000, "mp3", "medium", external_job_id="ext-123")
assert job1.has_external_job_id is True
# With None external ID
job2 = Job.create_new("test.mp4", 1000, "mp3", "medium", external_job_id=None)
assert job2.has_external_job_id is False
# With empty string external ID
job3 = Job.create_new("test.mp4", 1000, "mp3", "medium", external_job_id="")
assert job3.has_external_job_id is False
class TestValidationServiceExternalJobId:
"""Test ValidationService external job ID validation."""
def setup_method(self):
"""Set up test fixtures."""
self.validation_service = ValidationService(
max_file_size_mb=100.0,
supported_video_formats=['.mp4', '.avi'],
supported_audio_formats=['mp3', 'aac']
)
def test_validate_external_job_id_valid(self):
"""Test validation of valid external job IDs."""
# Valid alphanumeric
self.validation_service.validate_external_job_id("job123")
# Valid with underscores and hyphens
self.validation_service.validate_external_job_id("job_123-abc")
# Valid single character
self.validation_service.validate_external_job_id("a")
# Valid 50 characters (max length)
long_id = "a" * 50
self.validation_service.validate_external_job_id(long_id)
# Valid None (optional field)
self.validation_service.validate_external_job_id(None)
# Valid empty string (optional field)
self.validation_service.validate_external_job_id("")
def test_validate_external_job_id_invalid(self):
"""Test validation of invalid external job IDs."""
# Too long (51 characters)
with pytest.raises(ValidationError, match="must be 50 characters or less"):
long_id = "a" * 51
self.validation_service.validate_external_job_id(long_id)
# Contains invalid characters
with pytest.raises(ValidationError, match="must contain only alphanumeric"):
self.validation_service.validate_external_job_id("job@123")
with pytest.raises(ValidationError, match="must contain only alphanumeric"):
self.validation_service.validate_external_job_id("job 123") # space
with pytest.raises(ValidationError, match="must contain only alphanumeric"):
self.validation_service.validate_external_job_id("job.123") # dot
with pytest.raises(ValidationError, match="must contain only alphanumeric"):
self.validation_service.validate_external_job_id("job/123") # slash
class TestJobRepositoryWithExternalId:
"""Test InMemoryJobRepository with external job ID support."""
def setup_method(self):
"""Set up test fixtures."""
self.repo = InMemoryJobRepository()
@pytest.mark.asyncio
async def test_create_job_with_external_id(self):
"""Test creating a job with external job ID."""
job = await self.repo.create(
job_id="internal-123",
filename="test.mp4",
file_size_mb=10.0,
output_format="mp3",
quality="medium",
external_job_id="ext-job-456",
bearer_token="bearer-xyz"
)
assert job.id == "internal-123"
assert job.external_job_id == "ext-job-456"
assert job.bearer_token == "bearer-xyz"
assert job.filename == "test.mp4"
@pytest.mark.asyncio
async def test_create_job_without_external_id(self):
"""Test creating a job without external job ID (backwards compatibility)."""
job = await self.repo.create(
job_id="internal-123",
filename="test.mp4",
file_size_mb=10.0,
output_format="mp3",
quality="medium"
)
assert job.id == "internal-123"
assert job.external_job_id is None
assert job.bearer_token is None
@pytest.mark.asyncio
async def test_duplicate_external_job_id_raises_error(self):
"""Test that duplicate external job IDs raise an error."""
# Create first job
await self.repo.create(
job_id="internal-1",
filename="test1.mp4",
file_size_mb=10.0,
output_format="mp3",
quality="medium",
external_job_id="duplicate-id"
)
# Try to create second job with same external ID
with pytest.raises(DuplicateExternalJobIdError) as exc_info:
await self.repo.create(
job_id="internal-2",
filename="test2.mp4",
file_size_mb=20.0,
output_format="aac",
quality="high",
external_job_id="duplicate-id"
)
assert exc_info.value.external_job_id == "duplicate-id"
assert "already exists" in str(exc_info.value)
@pytest.mark.asyncio
async def test_get_by_external_id_success(self):
"""Test retrieving a job by external job ID."""
# Create job with external ID
await self.repo.create(
job_id="internal-123",
filename="test.mp4",
file_size_mb=10.0,
output_format="mp3",
quality="medium",
external_job_id="ext-job-456"
)
# Retrieve by external ID
job = await self.repo.get_by_external_id("ext-job-456")
assert job is not None
assert job.id == "internal-123"
assert job.external_job_id == "ext-job-456"
@pytest.mark.asyncio
async def test_get_by_external_id_not_found(self):
"""Test retrieving a job by non-existent external job ID."""
job = await self.repo.get_by_external_id("non-existent-id")
assert job is None
@pytest.mark.asyncio
async def test_get_by_internal_id_still_works(self):
"""Test that retrieving by internal ID still works."""
# Create job with external ID
await self.repo.create(
job_id="internal-123",
filename="test.mp4",
file_size_mb=10.0,
output_format="mp3",
quality="medium",
external_job_id="ext-job-456"
)
# Retrieve by internal ID
job = await self.repo.get("internal-123")
assert job is not None
assert job.id == "internal-123"
assert job.external_job_id == "ext-job-456"
@pytest.mark.asyncio
async def test_clear_bearer_token(self):
"""Test clearing bearer token from repository."""
# Create job with bearer token
await self.repo.create(
job_id="internal-123",
filename="test.mp4",
file_size_mb=10.0,
output_format="mp3",
quality="medium",
bearer_token="secret-token"
)
# Verify token exists
job = await self.repo.get("internal-123")
assert job.bearer_token == "secret-token"
# Clear token
result = await self.repo.clear_bearer_token("internal-123")
assert result is True
# Verify token cleared
job = await self.repo.get("internal-123")
assert job.bearer_token is None
@pytest.mark.asyncio
async def test_clear_bearer_token_nonexistent_job(self):
"""Test clearing bearer token for non-existent job."""
result = await self.repo.clear_bearer_token("non-existent-id")
assert result is False
@pytest.mark.asyncio
async def test_delete_job_with_external_id(self):
"""Test deleting a job removes it from external ID index."""
# Create job with external ID
await self.repo.create(
job_id="internal-123",
filename="test.mp4",
file_size_mb=10.0,
output_format="mp3",
quality="medium",
external_job_id="ext-job-456"
)
# Verify job exists
job = await self.repo.get_by_external_id("ext-job-456")
assert job is not None
# Delete job
result = await self.repo.delete("internal-123")
assert result is True
# Verify job no longer accessible by external ID
job = await self.repo.get_by_external_id("ext-job-456")
assert job is None
# Verify job no longer accessible by internal ID
job = await self.repo.get("internal-123")
assert job is None
@pytest.mark.asyncio
async def test_multiple_jobs_with_external_ids(self):
"""Test managing multiple jobs with different external IDs."""
# Create multiple jobs
await self.repo.create("internal-1", "test1.mp4", 10.0, "mp3", "medium", "ext-1")
await self.repo.create("internal-2", "test2.mp4", 20.0, "aac", "high", "ext-2")
await self.repo.create("internal-3", "test3.mp4", 30.0, "wav", "low") # No external ID
# Verify all can be retrieved correctly
job1 = await self.repo.get_by_external_id("ext-1")
assert job1.id == "internal-1"
job2 = await self.repo.get_by_external_id("ext-2")
assert job2.id == "internal-2"
job3 = await self.repo.get("internal-3")
assert job3.external_job_id is None
# Verify external ID lookup for job without external ID returns None
job3_by_ext = await self.repo.get_by_external_id("internal-3")
assert job3_by_ext is None