test_external_links_api.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. """Integration tests for External Links API endpoints."""
  2. import pytest
  3. from httpx import AsyncClient
  4. class TestExternalLinksAPI:
  5. """Integration tests for /api/v1/external-links endpoints."""
  6. @pytest.fixture
  7. async def link_factory(self, db_session):
  8. """Factory to create test external links."""
  9. _counter = [0]
  10. async def _create_link(**kwargs):
  11. from backend.app.models.external_link import ExternalLink
  12. _counter[0] += 1
  13. counter = _counter[0]
  14. defaults = {
  15. "name": f"Test Link {counter}",
  16. "url": f"https://example.com/{counter}",
  17. "icon": "Link",
  18. "sort_order": counter,
  19. }
  20. defaults.update(kwargs)
  21. link = ExternalLink(**defaults)
  22. db_session.add(link)
  23. await db_session.commit()
  24. await db_session.refresh(link)
  25. return link
  26. return _create_link
  27. @pytest.mark.asyncio
  28. @pytest.mark.integration
  29. async def test_list_external_links_empty(self, async_client: AsyncClient):
  30. """Verify empty list when no links exist."""
  31. response = await async_client.get("/api/v1/external-links/")
  32. assert response.status_code == 200
  33. assert isinstance(response.json(), list)
  34. @pytest.mark.asyncio
  35. @pytest.mark.integration
  36. async def test_list_external_links_with_data(
  37. self, async_client: AsyncClient, link_factory, db_session
  38. ):
  39. """Verify list returns existing links."""
  40. await link_factory(name="My Link")
  41. response = await async_client.get("/api/v1/external-links/")
  42. assert response.status_code == 200
  43. data = response.json()
  44. assert any(link["name"] == "My Link" for link in data)
  45. @pytest.mark.asyncio
  46. @pytest.mark.integration
  47. async def test_create_external_link(self, async_client: AsyncClient):
  48. """Verify external link can be created."""
  49. data = {
  50. "name": "New Link",
  51. "url": "https://new-link.example.com",
  52. "icon": "ExternalLink",
  53. }
  54. response = await async_client.post("/api/v1/external-links/", json=data)
  55. assert response.status_code == 200
  56. result = response.json()
  57. assert result["name"] == "New Link"
  58. assert result["url"] == "https://new-link.example.com"
  59. @pytest.mark.asyncio
  60. @pytest.mark.integration
  61. async def test_get_external_link(
  62. self, async_client: AsyncClient, link_factory, db_session
  63. ):
  64. """Verify single link can be retrieved."""
  65. link = await link_factory(name="Get Test Link")
  66. response = await async_client.get(f"/api/v1/external-links/{link.id}")
  67. assert response.status_code == 200
  68. assert response.json()["name"] == "Get Test Link"
  69. @pytest.mark.asyncio
  70. @pytest.mark.integration
  71. async def test_get_external_link_not_found(self, async_client: AsyncClient):
  72. """Verify 404 for non-existent link."""
  73. response = await async_client.get("/api/v1/external-links/9999")
  74. assert response.status_code == 404
  75. @pytest.mark.asyncio
  76. @pytest.mark.integration
  77. async def test_update_external_link(
  78. self, async_client: AsyncClient, link_factory, db_session
  79. ):
  80. """Verify link can be updated."""
  81. link = await link_factory(name="Original")
  82. response = await async_client.patch(
  83. f"/api/v1/external-links/{link.id}",
  84. json={"name": "Updated", "url": "https://updated.example.com"}
  85. )
  86. assert response.status_code == 200
  87. result = response.json()
  88. assert result["name"] == "Updated"
  89. assert result["url"] == "https://updated.example.com"
  90. @pytest.mark.asyncio
  91. @pytest.mark.integration
  92. async def test_delete_external_link(
  93. self, async_client: AsyncClient, link_factory, db_session
  94. ):
  95. """Verify link can be deleted."""
  96. link = await link_factory()
  97. response = await async_client.delete(f"/api/v1/external-links/{link.id}")
  98. assert response.status_code == 200
  99. # Verify deleted
  100. response = await async_client.get(f"/api/v1/external-links/{link.id}")
  101. assert response.status_code == 404
  102. @pytest.mark.asyncio
  103. @pytest.mark.integration
  104. async def test_reorder_external_links(
  105. self, async_client: AsyncClient, link_factory, db_session
  106. ):
  107. """Verify links can be reordered."""
  108. link1 = await link_factory(name="Link 1")
  109. link2 = await link_factory(name="Link 2")
  110. link3 = await link_factory(name="Link 3")
  111. # Reorder: 3, 1, 2
  112. response = await async_client.put(
  113. "/api/v1/external-links/reorder",
  114. json={"ids": [link3.id, link1.id, link2.id]}
  115. )
  116. assert response.status_code == 200
  117. data = response.json()
  118. # First link should be link3
  119. assert data[0]["id"] == link3.id
  120. assert data[0]["sort_order"] == 0
  121. class TestExternalLinksIconAPI:
  122. """Tests for external link icon upload/delete."""
  123. @pytest.fixture
  124. async def link_factory(self, db_session):
  125. """Factory to create test external links."""
  126. async def _create_link(**kwargs):
  127. from backend.app.models.external_link import ExternalLink
  128. defaults = {
  129. "name": "Icon Test Link",
  130. "url": "https://example.com",
  131. "icon": "Link",
  132. "sort_order": 0,
  133. }
  134. defaults.update(kwargs)
  135. link = ExternalLink(**defaults)
  136. db_session.add(link)
  137. await db_session.commit()
  138. await db_session.refresh(link)
  139. return link
  140. return _create_link
  141. @pytest.mark.asyncio
  142. @pytest.mark.integration
  143. async def test_get_icon_not_set(
  144. self, async_client: AsyncClient, link_factory, db_session
  145. ):
  146. """Verify 404 when no custom icon is set."""
  147. link = await link_factory()
  148. response = await async_client.get(f"/api/v1/external-links/{link.id}/icon")
  149. assert response.status_code == 404
  150. @pytest.mark.asyncio
  151. @pytest.mark.integration
  152. async def test_delete_icon_when_none(
  153. self, async_client: AsyncClient, link_factory, db_session
  154. ):
  155. """Verify deleting non-existent icon succeeds silently."""
  156. link = await link_factory()
  157. response = await async_client.delete(f"/api/v1/external-links/{link.id}/icon")
  158. assert response.status_code == 200