test_projects_api.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. """Integration tests for Projects API endpoints."""
  2. import pytest
  3. from httpx import AsyncClient
  4. class TestProjectsAPI:
  5. """Integration tests for /api/v1/projects endpoints."""
  6. @pytest.fixture
  7. async def project_factory(self, db_session):
  8. """Factory to create test projects."""
  9. _counter = [0]
  10. async def _create_project(**kwargs):
  11. from backend.app.models.project import Project
  12. _counter[0] += 1
  13. counter = _counter[0]
  14. defaults = {
  15. "name": f"Test Project {counter}",
  16. "description": "Test project description",
  17. "color": "#FF0000",
  18. }
  19. defaults.update(kwargs)
  20. project = Project(**defaults)
  21. db_session.add(project)
  22. await db_session.commit()
  23. await db_session.refresh(project)
  24. return project
  25. return _create_project
  26. @pytest.mark.asyncio
  27. @pytest.mark.integration
  28. async def test_list_projects_empty(self, async_client: AsyncClient):
  29. """Verify empty list when no projects exist."""
  30. response = await async_client.get("/api/v1/projects/")
  31. assert response.status_code == 200
  32. assert isinstance(response.json(), list)
  33. @pytest.mark.asyncio
  34. @pytest.mark.integration
  35. async def test_list_projects_with_data(
  36. self, async_client: AsyncClient, project_factory, db_session
  37. ):
  38. """Verify list returns existing projects."""
  39. await project_factory(name="My Project")
  40. response = await async_client.get("/api/v1/projects/")
  41. assert response.status_code == 200
  42. data = response.json()
  43. assert any(p["name"] == "My Project" for p in data)
  44. @pytest.mark.asyncio
  45. @pytest.mark.integration
  46. async def test_create_project(self, async_client: AsyncClient):
  47. """Verify project can be created."""
  48. data = {
  49. "name": "New Project",
  50. "description": "A new project",
  51. "color": "#00FF00",
  52. }
  53. response = await async_client.post("/api/v1/projects/", json=data)
  54. assert response.status_code == 200
  55. result = response.json()
  56. assert result["name"] == "New Project"
  57. assert result["color"] == "#00FF00"
  58. @pytest.mark.asyncio
  59. @pytest.mark.integration
  60. async def test_get_project(
  61. self, async_client: AsyncClient, project_factory, db_session
  62. ):
  63. """Verify single project can be retrieved."""
  64. project = await project_factory(name="Get Test Project")
  65. response = await async_client.get(f"/api/v1/projects/{project.id}")
  66. assert response.status_code == 200
  67. assert response.json()["name"] == "Get Test Project"
  68. @pytest.mark.asyncio
  69. @pytest.mark.integration
  70. async def test_get_project_not_found(self, async_client: AsyncClient):
  71. """Verify 404 for non-existent project."""
  72. response = await async_client.get("/api/v1/projects/9999")
  73. assert response.status_code == 404
  74. @pytest.mark.asyncio
  75. @pytest.mark.integration
  76. async def test_update_project(
  77. self, async_client: AsyncClient, project_factory, db_session
  78. ):
  79. """Verify project can be updated."""
  80. project = await project_factory(name="Original")
  81. response = await async_client.patch(
  82. f"/api/v1/projects/{project.id}",
  83. json={"name": "Updated", "description": "Updated description"}
  84. )
  85. assert response.status_code == 200
  86. result = response.json()
  87. assert result["name"] == "Updated"
  88. assert result["description"] == "Updated description"
  89. @pytest.mark.asyncio
  90. @pytest.mark.integration
  91. async def test_delete_project(
  92. self, async_client: AsyncClient, project_factory, db_session
  93. ):
  94. """Verify project can be deleted."""
  95. project = await project_factory()
  96. response = await async_client.delete(f"/api/v1/projects/{project.id}")
  97. assert response.status_code == 200
  98. data = response.json()
  99. assert data["message"] == "Project deleted"
  100. @pytest.mark.asyncio
  101. @pytest.mark.integration
  102. async def test_delete_project_not_found(self, async_client: AsyncClient):
  103. """Verify 404 for deleting non-existent project."""
  104. response = await async_client.delete("/api/v1/projects/9999")
  105. assert response.status_code == 404
  106. class TestProjectArchivesAPI:
  107. """Tests for project-archive relationships."""
  108. @pytest.fixture
  109. async def project_factory(self, db_session):
  110. """Factory to create test projects."""
  111. async def _create_project(**kwargs):
  112. from backend.app.models.project import Project
  113. defaults = {
  114. "name": "Archive Test Project",
  115. "description": "Test project",
  116. "color": "#0000FF",
  117. }
  118. defaults.update(kwargs)
  119. project = Project(**defaults)
  120. db_session.add(project)
  121. await db_session.commit()
  122. await db_session.refresh(project)
  123. return project
  124. return _create_project
  125. @pytest.mark.asyncio
  126. @pytest.mark.integration
  127. async def test_get_project_with_archives(
  128. self, async_client: AsyncClient, project_factory, db_session
  129. ):
  130. """Verify project can be retrieved with archive count."""
  131. project = await project_factory()
  132. response = await async_client.get(f"/api/v1/projects/{project.id}")
  133. assert response.status_code == 200
  134. # Project should have an archive count (may be 0)
  135. data = response.json()
  136. assert "name" in data