| """ |
| Integration tests for Progress API endpoints |
| """ |
|
|
| from unittest.mock import MagicMock, patch |
|
|
| import pytest |
| from fastapi.testclient import TestClient |
|
|
| from src.server.main import app |
| from src.server.utils.progress import ProgressTracker |
|
|
|
|
| @pytest.fixture |
| def client(): |
| """Create test client""" |
| return TestClient(app) |
|
|
|
|
| @pytest.fixture(autouse=True) |
| def clear_progress_states(): |
| """Clear all progress states before each test""" |
| ProgressTracker._progress_states.clear() |
| yield |
| ProgressTracker._progress_states.clear() |
|
|
|
|
| class TestProgressAPI: |
| """Test suite for Progress API endpoints""" |
|
|
| def test_get_progress_success(self, client): |
| """Test getting progress for an existing operation""" |
| |
| progress_id = "test-progress-123" |
| tracker = ProgressTracker(progress_id, operation_type="crawl") |
| tracker.state.update( |
| { |
| "status": "crawling", |
| "progress": 50, |
| "log": "Processing pages", |
| "processed_pages": 5, |
| "total_pages": 10, |
| "current_url": "https://example.com/page5", |
| } |
| ) |
|
|
| |
| response = client.get(f"/api/progress/{progress_id}") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
|
|
| assert data["progressId"] == progress_id |
| assert data["status"] == "crawling" |
| assert data["progress"] == 50 |
| assert data["message"] == "Processing pages" |
| assert data["processedPages"] == 5 |
| assert data["totalPages"] == 10 |
| assert data["currentUrl"] == "https://example.com/page5" |
|
|
| def test_get_progress_not_found(self, client): |
| """Test getting progress for non-existent operation""" |
| response = client.get("/api/progress/non-existent-id") |
|
|
| assert response.status_code == 404 |
| data = response.json() |
| assert "error" in data["detail"] |
| assert "not found" in data["detail"]["error"].lower() |
|
|
| def test_get_progress_with_etag(self, client): |
| """Test ETag support for progress endpoint""" |
| |
| progress_id = "test-etag-123" |
| tracker = ProgressTracker(progress_id, operation_type="upload") |
| tracker.state.update({"status": "processing", "progress": 30, "log": "Processing file"}) |
|
|
| |
| response1 = client.get(f"/api/progress/{progress_id}") |
| assert response1.status_code == 200 |
| etag = response1.headers.get("etag") |
| assert etag is not None |
|
|
| |
| response2 = client.get(f"/api/progress/{progress_id}", headers={"If-None-Match": etag}) |
| assert response2.status_code == 304 |
|
|
| |
| tracker.state["progress"] = 50 |
|
|
| |
| response3 = client.get(f"/api/progress/{progress_id}", headers={"If-None-Match": etag}) |
| assert response3.status_code == 200 |
| new_etag = response3.headers.get("etag") |
| assert new_etag != etag |
|
|
| def test_list_active_operations(self, client): |
| """Test listing all active operations""" |
| |
| tracker1 = ProgressTracker("crawl-1", operation_type="crawl") |
| tracker1.state.update({"status": "crawling", "progress": 30, "log": "Crawling site 1"}) |
|
|
| tracker2 = ProgressTracker("upload-1", operation_type="upload") |
| tracker2.state.update({"status": "processing", "progress": 60, "log": "Processing document"}) |
|
|
| |
| tracker3 = ProgressTracker("completed-1", operation_type="crawl") |
| tracker3.state.update({"status": "completed", "progress": 100, "log": "Done"}) |
|
|
| |
| response = client.get("/api/progress/") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
|
|
| assert "operations" in data |
| assert "count" in data |
| assert data["count"] == 2 |
|
|
| |
| operations = data["operations"] |
| op_ids = [op["operation_id"] for op in operations] |
| assert "crawl-1" in op_ids |
| assert "upload-1" in op_ids |
| assert "completed-1" not in op_ids |
|
|
| def test_list_active_operations_empty(self, client): |
| """Test listing when no active operations""" |
| response = client.get("/api/progress/") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
|
|
| assert data["operations"] == [] |
| assert data["count"] == 0 |
|
|
| def test_progress_response_for_crawl_operation(self, client): |
| """Test progress response for crawl operation with all fields""" |
| progress_id = "crawl-test-456" |
| tracker = ProgressTracker(progress_id, operation_type="crawl") |
| tracker.state.update( |
| { |
| "status": "code_extraction", |
| "progress": 45, |
| "log": "Extracting code examples", |
| "crawl_type": "normal", |
| "current_url": "https://example.com/docs", |
| "total_pages": 20, |
| "processed_pages": 10, |
| "code_blocks_found": 15, |
| "completed_summaries": 5, |
| "total_summaries": 15, |
| } |
| ) |
|
|
| response = client.get(f"/api/progress/{progress_id}") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
|
|
| |
| assert data["status"] == "code_extraction" |
| assert data["progress"] == 45 |
| assert data["crawlType"] == "normal" |
| assert data["currentUrl"] == "https://example.com/docs" |
| assert data["totalPages"] == 20 |
| assert data["processedPages"] == 10 |
| assert data["codeBlocksFound"] == 15 |
| assert data["completedSummaries"] == 5 |
| assert data["totalSummaries"] == 15 |
|
|
| def test_progress_response_for_upload_operation(self, client): |
| """Test progress response for upload operation""" |
| progress_id = "upload-test-789" |
| tracker = ProgressTracker(progress_id, operation_type="upload") |
| tracker.state.update( |
| { |
| "status": "storing", |
| "progress": 75, |
| "log": "Storing chunks", |
| "filename": "document.pdf", |
| "chunks_stored": 75, |
| "total_chunks": 100, |
| } |
| ) |
|
|
| response = client.get(f"/api/progress/{progress_id}") |
|
|
| assert response.status_code == 200 |
| data = response.json() |
|
|
| |
| assert data["status"] == "storing" |
| assert data["progress"] == 75 |
| assert data["message"] == "Storing chunks" |
|
|
| def test_progress_headers(self, client): |
| """Test response headers for progress endpoint""" |
| progress_id = "header-test-123" |
| tracker = ProgressTracker(progress_id, operation_type="crawl") |
| tracker.state.update({"status": "running", "progress": 25}) |
|
|
| response = client.get(f"/api/progress/{progress_id}") |
|
|
| assert response.status_code == 200 |
|
|
| |
| assert "ETag" in response.headers |
| assert "Last-Modified" in response.headers |
| assert "Cache-Control" in response.headers |
| assert response.headers["Cache-Control"] == "no-cache, must-revalidate" |
| assert response.headers["X-Poll-Interval"] == "1000" |
|
|
| def test_progress_completed_operation_headers(self, client): |
| """Test headers for completed operation""" |
| progress_id = "completed-test-456" |
| tracker = ProgressTracker(progress_id, operation_type="crawl") |
| tracker.state.update({"status": "completed", "progress": 100}) |
|
|
| response = client.get(f"/api/progress/{progress_id}") |
|
|
| assert response.status_code == 200 |
| assert response.headers["X-Poll-Interval"] == "0" |
|
|
| def test_progress_error_handling(self, client): |
| """Test error handling in progress endpoint""" |
| |
| with patch.object(ProgressTracker, "get_progress", side_effect=Exception("Database error")): |
| response = client.get("/api/progress/any-id") |
|
|
| assert response.status_code == 500 |
| data = response.json() |
| assert "error" in data["detail"] |
|
|
| def test_list_operations_error_handling(self, client): |
| """Test error handling in list operations endpoint""" |
| |
| with patch.object( |
| ProgressTracker, "_progress_states", new_callable=lambda: MagicMock(side_effect=Exception("Memory error")) |
| ): |
| response = client.get("/api/progress/") |
|
|
| |
| assert response.status_code in [200, 500] |
|
|