"""Unit tests for progress response models.""" import pytest from pydantic import ValidationError from src.server.models.progress_models import ( BaseProgressResponse, CrawlProgressResponse, ProgressDetails, ProjectCreationProgressResponse, UploadProgressResponse, create_progress_response, ) class TestProgressDetails: """Test cases for ProgressDetails model.""" def test_create_with_snake_case_fields(self): """Test creating ProgressDetails with snake_case field names.""" details = ProgressDetails( current_chunk=25, total_chunks=100, current_batch=3, total_batches=6, chunks_per_second=5.5 ) assert details.current_chunk == 25 assert details.total_chunks == 100 assert details.current_batch == 3 assert details.total_batches == 6 assert details.chunks_per_second == 5.5 def test_create_with_camel_case_fields(self): """Test creating ProgressDetails with camelCase field names.""" details = ProgressDetails( currentChunk=25, totalChunks=100, currentBatch=3, totalBatches=6, chunksPerSecond=5.5 ) assert details.current_chunk == 25 assert details.total_chunks == 100 assert details.current_batch == 3 assert details.total_batches == 6 assert details.chunks_per_second == 5.5 def test_model_dump_uses_aliases(self): """Test that model_dump uses camelCase aliases.""" details = ProgressDetails( current_chunk=25, total_chunks=100, chunks_per_second=2.5 ) data = details.model_dump(by_alias=True) assert "currentChunk" in data assert "totalChunks" in data assert "chunksPerSecond" in data assert "current_chunk" not in data assert "total_chunks" not in data class TestBaseProgressResponse: """Test cases for BaseProgressResponse model.""" def test_create_minimal_response(self): """Test creating minimal progress response.""" response = BaseProgressResponse( progress_id="test-123", status="running", progress=50.0, message="Processing..." ) assert response.progress_id == "test-123" assert response.status == "running" assert response.progress == 50.0 assert response.message == "Processing..." def test_progress_validation(self): """Test that progress is validated to be between 0-100.""" # Valid progress response = BaseProgressResponse( progress_id="test-123", status="running", progress=50.0 ) assert response.progress == 50.0 # Invalid progress - too high with pytest.raises(ValidationError): BaseProgressResponse( progress_id="test-123", status="running", progress=150.0 ) # Invalid progress - too low with pytest.raises(ValidationError): BaseProgressResponse( progress_id="test-123", status="running", progress=-10.0 ) def test_logs_validation_and_conversion(self): """Test logs field validation and conversion.""" # Test with list of strings response = BaseProgressResponse( progress_id="test-123", status="running", progress=50.0, logs=["Starting", "Processing", "Almost done"] ) assert response.logs == ["Starting", "Processing", "Almost done"] # Test with single string response = BaseProgressResponse( progress_id="test-123", status="running", progress=50.0, logs="Single log message" ) assert response.logs == ["Single log message"] # Test with list of dicts (log entries) response = BaseProgressResponse( progress_id="test-123", status="running", progress=50.0, logs=[ {"message": "Starting", "timestamp": "2024-01-01T10:00:00"}, {"message": "Processing", "timestamp": "2024-01-01T10:01:00"} ] ) assert response.logs == ["Starting", "Processing"] def test_camel_case_aliases(self): """Test that camelCase aliases work correctly.""" response = BaseProgressResponse( progressId="test-123", # camelCase status="running", progress=50.0, currentStep="processing", # camelCase stepMessage="Working on it" # camelCase ) assert response.progress_id == "test-123" assert response.current_step == "processing" assert response.step_message == "Working on it" class TestCrawlProgressResponse: """Test cases for CrawlProgressResponse model.""" def test_create_crawl_response_with_batch_info(self): """Test creating crawl response with batch processing information.""" response = CrawlProgressResponse( progress_id="crawl-123", status="document_storage", progress=45.0, message="Processing batch 3/6", total_pages=60, processed_pages=60, current_batch=3, total_batches=6, completed_batches=2, chunks_in_batch=25, active_workers=4 ) assert response.progress_id == "crawl-123" assert response.status == "document_storage" assert response.current_batch == 3 assert response.total_batches == 6 assert response.completed_batches == 2 assert response.chunks_in_batch == 25 assert response.active_workers == 4 def test_create_with_code_extraction_fields(self): """Test creating crawl response with code extraction fields.""" response = CrawlProgressResponse( progress_id="crawl-123", status="code_extraction", progress=75.0, code_blocks_found=150, code_examples_stored=120, completed_documents=45, total_documents=50, completed_summaries=30, total_summaries=40 ) assert response.code_blocks_found == 150 assert response.code_examples_stored == 120 assert response.completed_documents == 45 assert response.total_documents == 50 assert response.completed_summaries == 30 assert response.total_summaries == 40 def test_status_validation(self): """Test that only valid crawl statuses are accepted.""" valid_statuses = [ "starting", "analyzing", "crawling", "processing", "source_creation", "document_storage", "code_extraction", "code_storage", "finalization", "completed", "failed", "cancelled", "stopping", "error" ] for status in valid_statuses: response = CrawlProgressResponse( progress_id="test-123", status=status, progress=50.0 ) assert response.status == status # Invalid status should raise validation error with pytest.raises(ValidationError): CrawlProgressResponse( progress_id="test-123", status="invalid_status", progress=50.0 ) def test_camel_case_field_aliases(self): """Test that crawl-specific fields use camelCase aliases.""" response = CrawlProgressResponse( progress_id="test-123", status="code_extraction", progress=50.0, currentUrl="https://example.com/page1", # camelCase totalPages=100, # camelCase processedPages=50, # camelCase codeBlocksFound=75, # camelCase totalBatches=6, # camelCase currentBatch=3 # camelCase ) assert response.current_url == "https://example.com/page1" assert response.total_pages == 100 assert response.processed_pages == 50 assert response.code_blocks_found == 75 assert response.total_batches == 6 assert response.current_batch == 3 def test_duration_conversion(self): """Test that duration is converted to string.""" # Test with float response = CrawlProgressResponse( progress_id="test-123", status="completed", progress=100.0, duration=123.45 ) assert response.duration == "123.45" # Test with int response = CrawlProgressResponse( progress_id="test-123", status="completed", progress=100.0, duration=120 ) assert response.duration == "120" # Test with None response = CrawlProgressResponse( progress_id="test-123", status="processing", # Use valid crawl status progress=50.0, duration=None ) assert response.duration is None class TestUploadProgressResponse: """Test cases for UploadProgressResponse model.""" def test_create_upload_response(self): """Test creating upload progress response.""" response = UploadProgressResponse( progress_id="upload-123", status="storing", progress=80.0, upload_type="document", file_name="document.pdf", file_type="application/pdf", chunks_stored=400, word_count=5000 ) assert response.progress_id == "upload-123" assert response.status == "storing" assert response.upload_type == "document" assert response.file_name == "document.pdf" assert response.file_type == "application/pdf" assert response.chunks_stored == 400 assert response.word_count == 5000 def test_upload_status_validation(self): """Test upload status validation.""" valid_statuses = [ "starting", "reading", "text_extraction", "chunking", "source_creation", "summarizing", "storing", "completed", "failed", "cancelled", "error" ] for status in valid_statuses: response = UploadProgressResponse( progress_id="test-123", status=status, progress=50.0 ) assert response.status == status class TestProjectCreationProgressResponse: """Test cases for ProjectCreationProgressResponse model.""" def test_project_creation_status_validation(self): """Test project creation status validation.""" valid_statuses = [ "starting", "analyzing", "generating_prp", "creating_tasks", "organizing", "completed", "failed", "error" ] for status in valid_statuses: response = ProjectCreationProgressResponse( progress_id="test-123", status=status, progress=50.0 ) assert response.status == status # Invalid status should raise validation error with pytest.raises(ValidationError): ProjectCreationProgressResponse( progress_id="test-123", status="invalid_status", progress=50.0 ) class TestProgressResponseFactory: """Test cases for create_progress_response factory function.""" def test_create_crawl_response(self): """Test creating crawl progress response via factory.""" progress_data = { "progress_id": "crawl-123", "status": "document_storage", "progress": 50, "log": "Processing batch 3/6", "current_batch": 3, "total_batches": 6, "total_pages": 60, "processed_pages": 60 } response = create_progress_response("crawl", progress_data) assert isinstance(response, CrawlProgressResponse) assert response.progress_id == "crawl-123" assert response.status == "document_storage" assert response.current_batch == 3 assert response.total_batches == 6 def test_create_upload_response(self): """Test creating upload progress response via factory.""" progress_data = { "progress_id": "upload-123", "status": "storing", "progress": 75, "log": "Storing document chunks", "file_name": "document.pdf", "chunks_stored": 300 } response = create_progress_response("upload", progress_data) assert isinstance(response, UploadProgressResponse) assert response.progress_id == "upload-123" assert response.status == "storing" assert response.file_name == "document.pdf" assert response.chunks_stored == 300 def test_create_response_with_details(self): """Test that factory creates details object from progress data.""" progress_data = { "progress_id": "test-123", "status": "processing", "progress": 50, "current_batch": 3, "total_batches": 6, "current_chunk": 150, "total_chunks": 300, "chunks_per_second": 5.5 } response = create_progress_response("crawl", progress_data) assert response.details is not None assert response.details.current_batch == 3 assert response.details.total_batches == 6 assert response.details.current_chunk == 150 assert response.details.total_chunks == 300 assert response.details.chunks_per_second == 5.5 def test_factory_handles_missing_fields(self): """Test that factory handles missing required fields gracefully.""" # Missing status progress_data = { "progress_id": "test-123", "progress": 50 } response = create_progress_response("crawl", progress_data) assert response.status == "starting" # Default # Missing progress progress_data = { "progress_id": "test-123", "status": "processing" } response = create_progress_response("crawl", progress_data) assert response.progress == 0 # Default def test_factory_unknown_operation_type(self): """Test factory with unknown operation type falls back to base response.""" progress_data = { "progress_id": "test-123", "status": "processing", "progress": 50 } response = create_progress_response("unknown_type", progress_data) assert isinstance(response, BaseProgressResponse) assert not isinstance(response, CrawlProgressResponse) def test_factory_validation_error_fallback(self): """Test that factory falls back to base response on validation errors.""" # Create invalid data that would fail CrawlProgressResponse validation progress_data = { "progress_id": "test-123", "status": "invalid_crawl_status", # Invalid status "progress": 50 } response = create_progress_response("crawl", progress_data) # Should fall back to BaseProgressResponse assert isinstance(response, BaseProgressResponse) assert response.progress_id == "test-123"