test_external_links_api.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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(self, async_client: AsyncClient, link_factory, db_session):
  37. """Verify list returns existing links."""
  38. await link_factory(name="My Link")
  39. response = await async_client.get("/api/v1/external-links/")
  40. assert response.status_code == 200
  41. data = response.json()
  42. assert any(link["name"] == "My Link" for link in data)
  43. @pytest.mark.asyncio
  44. @pytest.mark.integration
  45. async def test_create_external_link(self, async_client: AsyncClient):
  46. """Verify external link can be created."""
  47. data = {
  48. "name": "New Link",
  49. "url": "https://new-link.example.com",
  50. "icon": "ExternalLink",
  51. }
  52. response = await async_client.post("/api/v1/external-links/", json=data)
  53. assert response.status_code == 200
  54. result = response.json()
  55. assert result["name"] == "New Link"
  56. assert result["url"] == "https://new-link.example.com"
  57. @pytest.mark.asyncio
  58. @pytest.mark.integration
  59. async def test_get_external_link(self, async_client: AsyncClient, link_factory, db_session):
  60. """Verify single link can be retrieved."""
  61. link = await link_factory(name="Get Test Link")
  62. response = await async_client.get(f"/api/v1/external-links/{link.id}")
  63. assert response.status_code == 200
  64. assert response.json()["name"] == "Get Test Link"
  65. @pytest.mark.asyncio
  66. @pytest.mark.integration
  67. async def test_get_external_link_not_found(self, async_client: AsyncClient):
  68. """Verify 404 for non-existent link."""
  69. response = await async_client.get("/api/v1/external-links/9999")
  70. assert response.status_code == 404
  71. @pytest.mark.asyncio
  72. @pytest.mark.integration
  73. async def test_update_external_link(self, async_client: AsyncClient, link_factory, db_session):
  74. """Verify link can be updated."""
  75. link = await link_factory(name="Original")
  76. response = await async_client.patch(
  77. f"/api/v1/external-links/{link.id}", json={"name": "Updated", "url": "https://updated.example.com"}
  78. )
  79. assert response.status_code == 200
  80. result = response.json()
  81. assert result["name"] == "Updated"
  82. assert result["url"] == "https://updated.example.com"
  83. @pytest.mark.asyncio
  84. @pytest.mark.integration
  85. async def test_delete_external_link(self, async_client: AsyncClient, link_factory, db_session):
  86. """Verify link can be deleted."""
  87. link = await link_factory()
  88. response = await async_client.delete(f"/api/v1/external-links/{link.id}")
  89. assert response.status_code == 200
  90. # Verify deleted
  91. response = await async_client.get(f"/api/v1/external-links/{link.id}")
  92. assert response.status_code == 404
  93. @pytest.mark.asyncio
  94. @pytest.mark.integration
  95. async def test_reorder_external_links(self, async_client: AsyncClient, link_factory, db_session):
  96. """Verify links can be reordered."""
  97. link1 = await link_factory(name="Link 1")
  98. link2 = await link_factory(name="Link 2")
  99. link3 = await link_factory(name="Link 3")
  100. # Reorder: 3, 1, 2
  101. response = await async_client.put(
  102. "/api/v1/external-links/reorder", json={"ids": [link3.id, link1.id, link2.id]}
  103. )
  104. assert response.status_code == 200
  105. data = response.json()
  106. # First link should be link3
  107. assert data[0]["id"] == link3.id
  108. assert data[0]["sort_order"] == 0
  109. class TestExternalLinksIconAPI:
  110. """Tests for external link icon upload/delete."""
  111. @pytest.fixture
  112. async def link_factory(self, db_session):
  113. """Factory to create test external links."""
  114. async def _create_link(**kwargs):
  115. from backend.app.models.external_link import ExternalLink
  116. defaults = {
  117. "name": "Icon Test Link",
  118. "url": "https://example.com",
  119. "icon": "Link",
  120. "sort_order": 0,
  121. }
  122. defaults.update(kwargs)
  123. link = ExternalLink(**defaults)
  124. db_session.add(link)
  125. await db_session.commit()
  126. await db_session.refresh(link)
  127. return link
  128. return _create_link
  129. @pytest.mark.asyncio
  130. @pytest.mark.integration
  131. async def test_get_icon_not_set(self, async_client: AsyncClient, link_factory, db_session):
  132. """Verify 404 when no custom icon is set."""
  133. link = await link_factory()
  134. response = await async_client.get(f"/api/v1/external-links/{link.id}/icon")
  135. assert response.status_code == 404
  136. @pytest.mark.asyncio
  137. @pytest.mark.integration
  138. async def test_delete_icon_when_none(self, async_client: AsyncClient, link_factory, db_session):
  139. """Verify deleting non-existent icon succeeds silently."""
  140. link = await link_factory()
  141. response = await async_client.delete(f"/api/v1/external-links/{link.id}/icon")
  142. assert response.status_code == 200