test_graphs_cli.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. """
  2. Tests for the graphs commands in the CLI.
  3. - list
  4. - retrieve
  5. - reset
  6. - update
  7. - list-entities
  8. - get-entity
  9. x remove-entity
  10. - list-relationships
  11. - get-relationship
  12. x remove-relationship
  13. - build
  14. - list-communities
  15. - get-community
  16. x update-community
  17. x delete-community
  18. - pull
  19. - remove-document
  20. """
  21. import json
  22. import uuid
  23. import pytest
  24. from click.testing import CliRunner
  25. from cli.commands.collections import create as create_collection
  26. from cli.commands.graphs import (
  27. build,
  28. delete_community,
  29. get_community,
  30. get_entity,
  31. get_relationship,
  32. list,
  33. list_communities,
  34. list_entities,
  35. list_relationships,
  36. pull,
  37. remove_document,
  38. remove_entity,
  39. remove_relationship,
  40. reset,
  41. retrieve,
  42. update,
  43. update_community,
  44. )
  45. from r2r import R2RAsyncClient
  46. from tests.cli.async_invoke import async_invoke
  47. def extract_json_block(output: str) -> dict:
  48. """Extract and parse the first valid JSON object found in the output."""
  49. start = output.find("{")
  50. if start == -1:
  51. raise ValueError("No JSON object start found in output")
  52. brace_count = 0
  53. for i, char in enumerate(output[start:], start=start):
  54. if char == "{":
  55. brace_count += 1
  56. elif char == "}":
  57. brace_count -= 1
  58. if brace_count == 0:
  59. json_str = output[start : i + 1].strip()
  60. return json.loads(json_str)
  61. raise ValueError("No complete JSON object found in output")
  62. async def create_test_collection(
  63. runner: CliRunner, client: R2RAsyncClient
  64. ) -> str:
  65. """Helper function to create a test collection and return its ID."""
  66. collection_name = f"test-collection-{uuid.uuid4()}"
  67. create_result = await async_invoke(
  68. runner, create_collection, collection_name, obj=client
  69. )
  70. response = extract_json_block(create_result.stdout_bytes.decode())
  71. return response["results"]["id"]
  72. @pytest.mark.asyncio
  73. async def test_graph_basic_operations():
  74. """Test basic graph operations: retrieve, reset, update."""
  75. client = R2RAsyncClient(base_url="http://localhost:7272")
  76. runner = CliRunner(mix_stderr=False)
  77. collection_id = await create_test_collection(runner, client)
  78. try:
  79. # Retrieve graph
  80. retrieve_result = await async_invoke(
  81. runner, retrieve, collection_id, obj=client
  82. )
  83. assert retrieve_result.exit_code == 0
  84. assert collection_id in retrieve_result.stdout_bytes.decode()
  85. # Update graph
  86. new_name = "Updated Graph Name"
  87. new_description = "Updated description"
  88. update_result = await async_invoke(
  89. runner,
  90. update,
  91. collection_id,
  92. "--name",
  93. new_name,
  94. "--description",
  95. new_description,
  96. obj=client,
  97. )
  98. assert update_result.exit_code == 0
  99. # Reset graph
  100. reset_result = await async_invoke(
  101. runner, reset, collection_id, obj=client
  102. )
  103. assert reset_result.exit_code == 0
  104. finally:
  105. # Cleanup will be handled by collection deletion
  106. pass
  107. @pytest.mark.asyncio
  108. async def test_graph_entity_operations():
  109. """Test entity-related operations in a graph."""
  110. client = R2RAsyncClient(base_url="http://localhost:7272")
  111. runner = CliRunner(mix_stderr=False)
  112. collection_id = await create_test_collection(runner, client)
  113. try:
  114. # List entities (empty initially)
  115. list_entities_result = await async_invoke(
  116. runner, list_entities, collection_id, obj=client
  117. )
  118. assert list_entities_result.exit_code == 0
  119. # Test with pagination
  120. paginated_result = await async_invoke(
  121. runner,
  122. list_entities,
  123. collection_id,
  124. "--offset",
  125. "0",
  126. "--limit",
  127. "2",
  128. obj=client,
  129. )
  130. assert paginated_result.exit_code == 0
  131. # Test nonexistent entity operations
  132. nonexistent_entity_id = str(uuid.uuid4())
  133. get_entity_result = await async_invoke(
  134. runner,
  135. get_entity,
  136. collection_id,
  137. nonexistent_entity_id,
  138. obj=client,
  139. )
  140. assert "not found" in get_entity_result.stderr_bytes.decode().lower()
  141. finally:
  142. # Cleanup will be handled by collection deletion
  143. pass
  144. @pytest.mark.asyncio
  145. async def test_graph_relationship_operations():
  146. """Test relationship-related operations in a graph."""
  147. client = R2RAsyncClient(base_url="http://localhost:7272")
  148. runner = CliRunner(mix_stderr=False)
  149. collection_id = await create_test_collection(runner, client)
  150. try:
  151. # List relationships
  152. list_rel_result = await async_invoke(
  153. runner, list_relationships, collection_id, obj=client
  154. )
  155. assert list_rel_result.exit_code == 0
  156. # Test with pagination
  157. paginated_result = await async_invoke(
  158. runner,
  159. list_relationships,
  160. collection_id,
  161. "--offset",
  162. "0",
  163. "--limit",
  164. "2",
  165. obj=client,
  166. )
  167. assert paginated_result.exit_code == 0
  168. # Test nonexistent relationship operations
  169. nonexistent_rel_id = str(uuid.uuid4())
  170. get_rel_result = await async_invoke(
  171. runner,
  172. get_relationship,
  173. collection_id,
  174. nonexistent_rel_id,
  175. obj=client,
  176. )
  177. assert "not found" in get_rel_result.stderr_bytes.decode().lower()
  178. finally:
  179. # Cleanup will be handled by collection deletion
  180. pass
  181. @pytest.mark.asyncio
  182. async def test_graph_community_operations():
  183. """Test community-related operations in a graph."""
  184. client = R2RAsyncClient(base_url="http://localhost:7272")
  185. runner = CliRunner(mix_stderr=False)
  186. collection_id = await create_test_collection(runner, client)
  187. try:
  188. # List communities
  189. list_comm_result = await async_invoke(
  190. runner, list_communities, collection_id, obj=client
  191. )
  192. assert list_comm_result.exit_code == 0
  193. # Test with pagination
  194. paginated_result = await async_invoke(
  195. runner,
  196. list_communities,
  197. collection_id,
  198. "--offset",
  199. "0",
  200. "--limit",
  201. "2",
  202. obj=client,
  203. )
  204. assert paginated_result.exit_code == 0
  205. # Test nonexistent community operations
  206. nonexistent_comm_id = str(uuid.uuid4())
  207. get_comm_result = await async_invoke(
  208. runner,
  209. get_community,
  210. collection_id,
  211. nonexistent_comm_id,
  212. obj=client,
  213. )
  214. assert "not found" in get_comm_result.stderr_bytes.decode().lower()
  215. finally:
  216. # Cleanup will be handled by collection deletion
  217. pass
  218. @pytest.mark.asyncio
  219. async def test_graph_build_and_pull():
  220. """Test graph building and document pull operations."""
  221. client = R2RAsyncClient(base_url="http://localhost:7272")
  222. runner = CliRunner(mix_stderr=False)
  223. collection_id = await create_test_collection(runner, client)
  224. try:
  225. # Test build with minimal settings
  226. settings = {"some_setting": "value"}
  227. build_result = await async_invoke(
  228. runner,
  229. build,
  230. collection_id,
  231. "--settings",
  232. json.dumps(settings),
  233. obj=client,
  234. )
  235. assert build_result.exit_code == 0
  236. # Test pull documents
  237. pull_result = await async_invoke(
  238. runner, pull, collection_id, obj=client
  239. )
  240. assert pull_result.exit_code == 0
  241. # Test remove document (with nonexistent document)
  242. nonexistent_doc_id = str(uuid.uuid4())
  243. remove_doc_result = await async_invoke(
  244. runner,
  245. remove_document,
  246. collection_id,
  247. nonexistent_doc_id,
  248. obj=client,
  249. )
  250. assert "not found" in remove_doc_result.stderr_bytes.decode().lower()
  251. finally:
  252. # Cleanup will be handled by collection deletion
  253. pass
  254. @pytest.mark.asyncio
  255. async def test_list_graphs():
  256. """Test listing graphs."""
  257. client = R2RAsyncClient(base_url="http://localhost:7272")
  258. runner = CliRunner(mix_stderr=False)
  259. try:
  260. # Test basic list
  261. list_result = await async_invoke(runner, list, obj=client)
  262. assert list_result.exit_code == 0
  263. finally:
  264. # Cleanup will be handled by collection deletion
  265. pass