| """Unit tests for projects API polling endpoints with ETag support.""" |
|
|
| from unittest.mock import AsyncMock, MagicMock, patch |
|
|
| from fastapi.testclient import TestClient |
|
|
| from src.server.auth.dependencies import get_current_user |
|
|
| |
| from src.server.main import app |
|
|
|
|
| |
| def setup_module(module): |
| |
| app.dependency_overrides[get_current_user] = lambda: { |
| "id": "admin-polling", |
| "role": "system_admin", |
| "department": "Engineering", |
| } |
|
|
|
|
| def teardown_module(module): |
| app.dependency_overrides.pop(get_current_user, None) |
|
|
|
|
| |
| mock_task_service = AsyncMock() |
| mock_task_service_class = MagicMock(return_value=mock_task_service) |
| task_service_patch = patch("src.server.api_routes.projects.ops.TaskService", mock_task_service_class) |
|
|
| |
| task_service_patch.start() |
|
|
| client = TestClient(app) |
|
|
|
|
| class TestProjectsListPolling: |
| def test_list_projects_http_with_etag(self): |
| with ( |
| patch("src.server.api_routes.projects.core.ProjectService") as mock_proj_class, |
| patch("src.server.api_routes.projects.core.SourceLinkingService") as mock_source_class, |
| ): |
| mock_proj_instance = MagicMock() |
| mock_proj_instance.list_projects = AsyncMock( |
| return_value=( |
| True, |
| {"projects": [{"id": "proj-1", "title": "Test Project", "department": "Engineering"}]}, |
| ) |
| ) |
| mock_proj_class.return_value = mock_proj_instance |
|
|
| mock_source_instance = MagicMock() |
| mock_source_instance.format_projects_with_sources = AsyncMock( |
| return_value=[{"id": "proj-1", "title": "Test Project", "department": "Engineering"}] |
| ) |
| mock_source_class.return_value = mock_source_instance |
|
|
| response1 = client.get("/api/projects") |
| assert response1.status_code == 200 |
| assert "ETag" in response1.headers |
| etag = response1.headers["ETag"] |
|
|
| response2 = client.get("/api/projects", headers={"If-None-Match": etag}) |
| assert response2.status_code == 304 |
|
|
|
|
| class TestProjectTasksPolling: |
| def test_list_project_tasks_http_polling(self): |
| mock_task_service.reset_mock() |
| mock_task_service.list_tasks = AsyncMock( |
| return_value=(True, {"tasks": [{"id": "task-1", "title": "Test Task", "status": "todo"}]}) |
| ) |
|
|
| with patch("src.server.api_routes.projects.core.ProjectService") as mock_proj_class: |
| mock_proj_instance = MagicMock() |
| mock_proj_class.return_value = mock_proj_instance |
| mock_proj_instance.get_project = AsyncMock( |
| return_value=(True, {"project": {"id": "proj-1", "department": "Engineering"}}) |
| ) |
|
|
| response = client.get("/api/projects/proj-1/tasks") |
| assert response.status_code == 200 |
| assert len(response.json()) == 1 |
|
|
|
|
| class TestPollingEdgeCases: |
| def test_project_not_found_no_etag(self): |
| with patch("src.server.api_routes.projects.core.ProjectService") as mock_proj_class: |
| mock_proj_instance = MagicMock() |
| mock_proj_class.return_value = mock_proj_instance |
| mock_proj_instance.get_project = AsyncMock(return_value=(False, {"error": "Project not found"})) |
|
|
| response = client.get("/api/projects/non-existent") |
| |
| assert response.status_code in [404, 403] |
|
|