test_system_cli.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. """
  2. Tests for the system commands in the CLI.
  3. - health
  4. - settings
  5. - status
  6. x serve
  7. x image-exists
  8. x docker-down
  9. x generate-report
  10. x update
  11. - version
  12. """
  13. import json
  14. from importlib.metadata import version as get_version
  15. import pytest
  16. from click.testing import CliRunner
  17. from cli.commands.system import health, settings, status, version
  18. from r2r import R2RAsyncClient
  19. from tests.cli.async_invoke import async_invoke
  20. @pytest.mark.asyncio
  21. async def test_health_against_server():
  22. """Test health check against a real server."""
  23. # Create real client
  24. client = R2RAsyncClient(base_url="http://localhost:7272")
  25. # Run command
  26. runner = CliRunner(mix_stderr=False)
  27. result = await async_invoke(runner, health, obj=client)
  28. # Extract just the JSON part (everything after the "Time taken" line)
  29. output = result.stdout_bytes.decode()
  30. json_str = output.split("\n", 1)[1]
  31. # Basic validation
  32. response_data = json.loads(json_str)
  33. assert "results" in response_data
  34. assert "message" in response_data["results"]
  35. assert response_data["results"]["message"] == "ok"
  36. assert result.exit_code == 0
  37. @pytest.mark.asyncio
  38. async def test_health_server_down():
  39. """Test health check when server is unreachable."""
  40. client = R2RAsyncClient(base_url="http://localhost:54321") # Invalid port
  41. runner = CliRunner(mix_stderr=False)
  42. result = await async_invoke(runner, health, obj=client)
  43. assert result.exit_code != 0
  44. assert (
  45. "Request failed: All connection attempts failed"
  46. in result.stderr_bytes.decode()
  47. )
  48. @pytest.mark.asyncio
  49. async def test_health_invalid_url():
  50. """Test health check with invalid URL."""
  51. client = R2RAsyncClient(base_url="http://invalid.localhost")
  52. runner = CliRunner(mix_stderr=False)
  53. result = await async_invoke(runner, health, obj=client)
  54. assert result.exit_code != 0
  55. assert "Request failed" in result.stderr_bytes.decode()
  56. @pytest.mark.asyncio
  57. async def test_settings_against_server():
  58. """Test settings retrieval against a real server."""
  59. client = R2RAsyncClient(base_url="http://localhost:7272")
  60. runner = CliRunner(mix_stderr=False)
  61. result = await async_invoke(runner, settings, obj=client)
  62. # Extract JSON part after "Time taken" line
  63. output = result.stdout_bytes.decode()
  64. json_str = output.split("\n", 1)[1]
  65. # Validate response structure
  66. response_data = json.loads(json_str)
  67. assert "results" in response_data
  68. assert "config" in response_data["results"]
  69. assert "prompts" in response_data["results"]
  70. # Validate key configuration sections
  71. config = response_data["results"]["config"]
  72. assert "completion" in config
  73. assert "database" in config
  74. assert "embedding" in config
  75. assert "ingestion" in config
  76. assert result.exit_code == 0
  77. @pytest.mark.asyncio
  78. async def test_settings_server_down():
  79. """Test settings retrieval when server is unreachable."""
  80. client = R2RAsyncClient(base_url="http://localhost:54321") # Invalid port
  81. runner = CliRunner(mix_stderr=False)
  82. result = await async_invoke(runner, settings, obj=client)
  83. assert result.exit_code != 0
  84. assert (
  85. "Request failed: All connection attempts failed"
  86. in result.stderr_bytes.decode()
  87. )
  88. @pytest.mark.asyncio
  89. async def test_settings_invalid_url():
  90. """Test settings retrieval with invalid URL."""
  91. client = R2RAsyncClient(base_url="http://invalid.localhost")
  92. runner = CliRunner(mix_stderr=False)
  93. result = await async_invoke(runner, settings, obj=client)
  94. assert result.exit_code != 0
  95. assert "Request failed" in result.stderr_bytes.decode()
  96. @pytest.mark.asyncio
  97. async def test_settings_response_structure():
  98. """Test detailed structure of settings response."""
  99. client = R2RAsyncClient(base_url="http://localhost:7272")
  100. runner = CliRunner(mix_stderr=False)
  101. result = await async_invoke(runner, settings, obj=client)
  102. output = result.stdout_bytes.decode()
  103. json_str = output.split("\n", 1)[1]
  104. response_data = json.loads(json_str)
  105. # Validate prompts structure
  106. prompts = response_data["results"]["prompts"]
  107. assert "results" in prompts
  108. assert "total_entries" in prompts
  109. assert isinstance(prompts["results"], list)
  110. # Validate prompt entries
  111. for prompt in prompts["results"]:
  112. assert "name" in prompt
  113. assert "id" in prompt
  114. assert "template" in prompt
  115. assert "input_types" in prompt
  116. assert "created_at" in prompt
  117. assert "updated_at" in prompt
  118. assert result.exit_code == 0
  119. @pytest.mark.asyncio
  120. async def test_settings_config_validation():
  121. """Test specific configuration values in settings response."""
  122. client = R2RAsyncClient(base_url="http://localhost:7272")
  123. runner = CliRunner(mix_stderr=False)
  124. result = await async_invoke(runner, settings, obj=client)
  125. output = result.stdout_bytes.decode()
  126. json_str = output.split("\n", 1)[1]
  127. response_data = json.loads(json_str)
  128. config = response_data["results"]["config"]
  129. # Validate completion config
  130. completion = config["completion"]
  131. assert "provider" in completion
  132. assert "concurrent_request_limit" in completion
  133. assert "generation_config" in completion
  134. # Validate database config
  135. database = config["database"]
  136. assert "provider" in database
  137. assert "default_collection_name" in database
  138. assert "limits" in database
  139. assert result.exit_code == 0
  140. @pytest.mark.asyncio
  141. async def test_status_against_server():
  142. """Test status check against a real server."""
  143. client = R2RAsyncClient(base_url="http://localhost:7272")
  144. runner = CliRunner(mix_stderr=False)
  145. result = await async_invoke(runner, status, obj=client)
  146. # Extract JSON part after "Time taken" line
  147. output = result.stdout_bytes.decode()
  148. json_str = output.split("\n", 1)[1]
  149. # Validate response structure
  150. response_data = json.loads(json_str)
  151. assert "results" in response_data
  152. # Validate specific fields
  153. results = response_data["results"]
  154. assert "start_time" in results
  155. assert "uptime_seconds" in results
  156. assert "cpu_usage" in results
  157. assert "memory_usage" in results
  158. # Validate data types
  159. assert isinstance(results["uptime_seconds"], (int, float))
  160. assert isinstance(results["cpu_usage"], (int, float))
  161. assert isinstance(results["memory_usage"], (int, float))
  162. assert result.exit_code == 0
  163. @pytest.mark.asyncio
  164. async def test_status_server_down():
  165. """Test status check when server is unreachable."""
  166. client = R2RAsyncClient(base_url="http://localhost:54321") # Invalid port
  167. runner = CliRunner(mix_stderr=False)
  168. result = await async_invoke(runner, status, obj=client)
  169. assert result.exit_code != 0
  170. assert (
  171. "Request failed: All connection attempts failed"
  172. in result.stderr_bytes.decode()
  173. )
  174. @pytest.mark.asyncio
  175. async def test_status_invalid_url():
  176. """Test status check with invalid URL."""
  177. client = R2RAsyncClient(base_url="http://invalid.localhost")
  178. runner = CliRunner(mix_stderr=False)
  179. result = await async_invoke(runner, status, obj=client)
  180. assert result.exit_code != 0
  181. assert "Request failed" in result.stderr_bytes.decode()
  182. @pytest.mark.asyncio
  183. async def test_status_value_ranges():
  184. """Test that status values are within expected ranges."""
  185. client = R2RAsyncClient(base_url="http://localhost:7272")
  186. runner = CliRunner(mix_stderr=False)
  187. result = await async_invoke(runner, status, obj=client)
  188. output = result.stdout_bytes.decode()
  189. json_str = output.split("\n", 1)[1]
  190. response_data = json.loads(json_str)
  191. results = response_data["results"]
  192. # CPU usage should be between 0 and 100
  193. assert 0 <= results["cpu_usage"] <= 100
  194. # Memory usage should be between 0 and 100
  195. assert 0 <= results["memory_usage"] <= 100
  196. # Uptime should be positive
  197. assert results["uptime_seconds"] > 0
  198. assert result.exit_code == 0
  199. @pytest.mark.asyncio
  200. async def test_status_start_time_format():
  201. """Test that start_time is in correct ISO format."""
  202. client = R2RAsyncClient(base_url="http://localhost:7272")
  203. runner = CliRunner(mix_stderr=False)
  204. result = await async_invoke(runner, status, obj=client)
  205. output = result.stdout_bytes.decode()
  206. json_str = output.split("\n", 1)[1]
  207. response_data = json.loads(json_str)
  208. from datetime import datetime
  209. # Verify start_time is valid ISO format
  210. start_time = response_data["results"]["start_time"]
  211. try:
  212. datetime.fromisoformat(start_time.replace("Z", "+00:00"))
  213. except ValueError:
  214. pytest.fail("start_time is not in valid ISO format")
  215. assert result.exit_code == 0
  216. @pytest.mark.asyncio
  217. async def test_version_command():
  218. """Test basic version command functionality."""
  219. runner = CliRunner()
  220. result = await async_invoke(runner, version)
  221. # Verify command succeeded
  222. assert result.exit_code == 0
  223. # Verify output is valid JSON and matches actual package version
  224. expected_version = get_version("r2r")
  225. actual_version = json.loads(result.stdout_bytes.decode())
  226. assert actual_version == expected_version
  227. @pytest.mark.asyncio
  228. async def test_version_output_format():
  229. """Test that version output is properly formatted JSON."""
  230. runner = CliRunner()
  231. result = await async_invoke(runner, version)
  232. # Verify output is valid JSON
  233. try:
  234. output = result.stdout_bytes.decode()
  235. parsed = json.loads(output)
  236. assert isinstance(parsed, str) # Version should be a string
  237. except json.JSONDecodeError:
  238. pytest.fail("Version output is not valid JSON")
  239. # Should be non-empty output ending with newline
  240. assert output.strip()
  241. assert output.endswith("\n")
  242. @pytest.mark.asyncio
  243. async def test_version_error_handling(monkeypatch):
  244. """Test error handling when version import fails."""
  245. def mock_version(_):
  246. raise ImportError("Package not found")
  247. # Mock the version function to raise an error
  248. monkeypatch.setattr("importlib.metadata.version", mock_version)
  249. runner = CliRunner(mix_stderr=False)
  250. result = await async_invoke(runner, version)
  251. # Verify command failed with exception
  252. assert result.exit_code == 1
  253. error_output = result.stderr_bytes.decode()
  254. assert "An unexpected error occurred" in error_output
  255. assert "Package not found" in error_output