test_reflex.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. from functools import reduce
  2. from unittest.mock import Mock
  3. import pytest
  4. from typer.testing import CliRunner
  5. from reflex.reflex import cli
  6. from reflex.utils.hosting import DeploymentPrepInfo
  7. runner = CliRunner()
  8. def test_login_success_existing_token(mocker):
  9. mocker.patch(
  10. "reflex.utils.hosting.authenticated_token",
  11. return_value=("fake-token", "fake-code"),
  12. )
  13. result = runner.invoke(cli, ["login"])
  14. assert result.exit_code == 0
  15. def test_login_success_on_browser(mocker):
  16. mocker.patch(
  17. "reflex.utils.hosting.authenticated_token",
  18. return_value=("", "fake-code"),
  19. )
  20. mock_authenticate_on_browser = mocker.patch(
  21. "reflex.utils.hosting.authenticate_on_browser", return_value="fake-token"
  22. )
  23. result = runner.invoke(cli, ["login"])
  24. assert result.exit_code == 0
  25. mock_authenticate_on_browser.assert_called_once_with("fake-code")
  26. def test_login_fail(mocker):
  27. # Access token does not exist, but user authenticates successfully on browser.
  28. mocker.patch(
  29. "reflex.utils.hosting.get_existing_access_token", return_value=("", "")
  30. )
  31. mocker.patch("reflex.utils.hosting.authenticate_on_browser", return_value="")
  32. result = runner.invoke(cli, ["login"])
  33. assert result.exit_code == 1
  34. @pytest.mark.parametrize(
  35. "args",
  36. [
  37. ["--no-interactive", "-k", "chatroom"],
  38. ["--no-interactive", "--deployment-key", "chatroom"],
  39. ["--no-interactive", "-r", "sjc"],
  40. ["--no-interactive", "--region", "sjc"],
  41. ["--no-interactive", "-r", "sjc", "-r", "lax"],
  42. ["--no-interactive", "-r", "sjc", "--region", "lax"],
  43. ],
  44. )
  45. def test_deploy_required_args_missing(args):
  46. result = runner.invoke(cli, ["deploy", *args])
  47. assert result.exit_code == 1
  48. @pytest.fixture
  49. def setup_env_authentication(mocker):
  50. mocker.patch("reflex.utils.prerequisites.check_initialized")
  51. mocker.patch("reflex.utils.hosting.authenticated_token", return_value="fake-token")
  52. mocker.patch("time.sleep")
  53. def test_deploy_non_interactive_prepare_failed(
  54. mocker,
  55. setup_env_authentication,
  56. ):
  57. mocker.patch(
  58. "reflex.utils.hosting.prepare_deploy",
  59. side_effect=Exception("server did not like params in prepare"),
  60. )
  61. result = runner.invoke(
  62. cli, ["deploy", "--no-interactive", "-k", "chatroom", "-r", "sjc"]
  63. )
  64. assert result.exit_code == 1
  65. @pytest.mark.parametrize(
  66. "optional_args,values",
  67. [
  68. ([], None),
  69. (["--env", "k1=v1"], {"envs": {"k1": "v1"}}),
  70. (["--cpus", 2], {"cpus": 2}),
  71. (["--memory-mb", 2048], {"memory_mb": 2048}),
  72. (["--no-auto-start"], {"auto_start": False}),
  73. (["--no-auto-stop"], {"auto_stop": False}),
  74. (
  75. ["--frontend-hostname", "myfrontend.com"],
  76. {"frontend_hostname": "myfrontend.com"},
  77. ),
  78. ],
  79. )
  80. def test_deploy_non_interactive_success(
  81. mocker, setup_env_authentication, optional_args, values
  82. ):
  83. mocker.patch("reflex.utils.console.ask")
  84. app_prefix = "fake-prefix"
  85. mocker.patch(
  86. "reflex.utils.hosting.prepare_deploy",
  87. return_value=Mock(
  88. app_prefix=app_prefix,
  89. reply=Mock(
  90. api_url="fake-api-url", deploy_url="fake-deploy-url", key="fake-key"
  91. ),
  92. ),
  93. )
  94. fake_export_dir = "fake-export-dir"
  95. mocker.patch("tempfile.mkdtemp", return_value=fake_export_dir)
  96. mocker.patch("reflex.reflex.export")
  97. mock_deploy = mocker.patch(
  98. "reflex.utils.hosting.deploy",
  99. return_value=Mock(
  100. frontend_url="fake-frontend-url", backend_url="fake-backend-url"
  101. ),
  102. )
  103. mocker.patch("reflex.utils.hosting.wait_for_server_to_pick_up_request")
  104. mocker.patch("reflex.utils.hosting.display_deploy_milestones")
  105. mocker.patch("reflex.utils.hosting.poll_backend", return_value=True)
  106. mocker.patch("reflex.utils.hosting.poll_frontend", return_value=True)
  107. # TODO: typer option default not working in test for app name
  108. deployment_key = "chatroom-0"
  109. app_name = "chatroom"
  110. regions = ["sjc"]
  111. result = runner.invoke(
  112. cli,
  113. [
  114. "deploy",
  115. "--no-interactive",
  116. "-k",
  117. deployment_key,
  118. *reduce(lambda x, y: x + ["-r", y], regions, []),
  119. "--app-name",
  120. app_name,
  121. *optional_args,
  122. ],
  123. )
  124. assert result.exit_code == 0
  125. expected_call_args = dict(
  126. frontend_file_name="frontend.zip",
  127. backend_file_name="backend.zip",
  128. export_dir=fake_export_dir,
  129. key=deployment_key,
  130. app_name=app_name,
  131. regions=regions,
  132. app_prefix=app_prefix,
  133. cpus=None,
  134. memory_mb=None,
  135. auto_start=None,
  136. auto_stop=None,
  137. frontend_hostname=None,
  138. envs=None,
  139. with_metrics=None,
  140. with_tracing=None,
  141. )
  142. expected_call_args.update(values or {})
  143. assert mock_deploy.call_args.kwargs == expected_call_args
  144. def get_app_prefix():
  145. return "fake-prefix"
  146. def get_deployment_key():
  147. return "i-want-this-site"
  148. def get_suggested_key():
  149. return "suggested-key"
  150. def test_deploy_interactive_prepare_failed(
  151. mocker,
  152. setup_env_authentication,
  153. ):
  154. mocker.patch(
  155. "reflex.utils.hosting.prepare_deploy",
  156. side_effect=Exception("server did not like params in prepare"),
  157. )
  158. result = runner.invoke(cli, ["deploy"])
  159. assert result.exit_code == 1
  160. @pytest.mark.parametrize(
  161. "app_prefix,deployment_key,prepare_responses,user_input_region,user_input_envs,expected_key,args_patch",
  162. [
  163. # CLI provides suggestion and but user enters a different key
  164. (
  165. get_app_prefix(),
  166. get_deployment_key(),
  167. Mock(
  168. app_prefix=get_app_prefix(),
  169. reply=None,
  170. suggestion=Mock(
  171. api_url="fake-api-url",
  172. deploy_url="fake-deploy-url",
  173. key=get_suggested_key(),
  174. ),
  175. existing=None,
  176. enabled_regions=["sjc"],
  177. ),
  178. ["sjc"],
  179. [],
  180. get_deployment_key(),
  181. None,
  182. ),
  183. # CLI provides suggestion and but user enters a different key and enters envs
  184. (
  185. get_app_prefix(),
  186. get_deployment_key(),
  187. Mock(
  188. app_prefix=get_app_prefix(),
  189. reply=None,
  190. suggestion=Mock(
  191. api_url="fake-api-url",
  192. deploy_url="fake-deploy-url",
  193. key=get_suggested_key(),
  194. ),
  195. existing=None,
  196. enabled_regions=["sjc"],
  197. ),
  198. ["sjc"],
  199. ["k1=v1", "k2=v2"],
  200. get_deployment_key(),
  201. {"envs": {"k1": "v1", "k2": "v2"}},
  202. ),
  203. # CLI provides suggestion and but user takes it
  204. (
  205. get_app_prefix(),
  206. get_deployment_key(),
  207. Mock(
  208. app_prefix=get_app_prefix(),
  209. reply=None,
  210. suggestion=Mock(
  211. api_url="fake-api-url",
  212. deploy_url="fake-deploy-url",
  213. key=get_suggested_key(),
  214. ),
  215. existing=None,
  216. enabled_regions=["sjc"],
  217. ),
  218. ["sjc"],
  219. [],
  220. get_suggested_key(),
  221. None,
  222. ),
  223. # CLI provides suggestion and but user takes it and enters envs
  224. (
  225. get_app_prefix(),
  226. get_deployment_key(),
  227. Mock(
  228. app_prefix=get_app_prefix(),
  229. reply=None,
  230. suggestion=Mock(
  231. api_url="fake-api-url",
  232. deploy_url="fake-deploy-url",
  233. key=get_suggested_key(),
  234. ),
  235. existing=None,
  236. enabled_regions=["sjc"],
  237. ),
  238. ["sjc"],
  239. ["k1=v1", "k3=v3"],
  240. get_suggested_key(),
  241. {"envs": {"k1": "v1", "k3": "v3"}},
  242. ),
  243. # User has an existing deployment
  244. (
  245. get_app_prefix(),
  246. get_deployment_key(),
  247. Mock(
  248. app_prefix=get_app_prefix(),
  249. reply=None,
  250. existing=Mock(
  251. __getitem__=lambda _, __: DeploymentPrepInfo(
  252. api_url="fake-api-url",
  253. deploy_url="fake-deploy-url",
  254. key=get_deployment_key(),
  255. )
  256. ),
  257. suggestion=None,
  258. enabled_regions=["sjc"],
  259. ),
  260. ["sjc"],
  261. [],
  262. get_deployment_key(),
  263. None,
  264. ),
  265. # User has an existing deployment then updates the envs
  266. (
  267. get_app_prefix(),
  268. get_deployment_key(),
  269. Mock(
  270. app_prefix=get_app_prefix(),
  271. reply=None,
  272. existing=Mock(
  273. __getitem__=lambda _, __: DeploymentPrepInfo(
  274. api_url="fake-api-url",
  275. deploy_url="fake-deploy-url",
  276. key=get_deployment_key(),
  277. )
  278. ),
  279. suggestion=None,
  280. enabled_regions=["sjc"],
  281. ),
  282. ["sjc"],
  283. ["k4=v4"],
  284. get_deployment_key(),
  285. {"envs": {"k4": "v4"}},
  286. ),
  287. ],
  288. )
  289. def test_deploy_interactive(
  290. mocker,
  291. setup_env_authentication,
  292. app_prefix,
  293. deployment_key,
  294. prepare_responses,
  295. user_input_region,
  296. user_input_envs,
  297. expected_key,
  298. args_patch,
  299. ):
  300. mocker.patch(
  301. "reflex.utils.hosting.check_requirements_for_non_reflex_packages",
  302. return_value=True,
  303. )
  304. mocker.patch(
  305. "reflex.utils.hosting.prepare_deploy",
  306. return_value=prepare_responses,
  307. )
  308. mocker.patch(
  309. "reflex.utils.hosting.interactive_get_deployment_key_from_user_input",
  310. return_value=(expected_key, "fake-api-url", "fake-deploy-url"),
  311. )
  312. mocker.patch("reflex.utils.console.ask", side_effect=user_input_region)
  313. mocker.patch(
  314. "reflex.utils.hosting.interactive_prompt_for_envs", return_value=user_input_envs
  315. )
  316. fake_export_dir = "fake-export-dir"
  317. mocker.patch("tempfile.mkdtemp", return_value=fake_export_dir)
  318. mocker.patch("reflex.reflex.export")
  319. mock_deploy = mocker.patch(
  320. "reflex.utils.hosting.deploy",
  321. return_value=Mock(
  322. frontend_url="fake-frontend-url", backend_url="fake-backend-url"
  323. ),
  324. )
  325. mocker.patch("reflex.utils.hosting.wait_for_server_to_pick_up_request")
  326. mocker.patch("reflex.utils.hosting.display_deploy_milestones")
  327. mocker.patch("reflex.utils.hosting.poll_backend", return_value=True)
  328. mocker.patch("reflex.utils.hosting.poll_frontend", return_value=True)
  329. # TODO: typer option default not working in test for app name
  330. app_name = "fake-app-workaround"
  331. regions = ["sjc"]
  332. result = runner.invoke(
  333. cli,
  334. ["deploy", "--app-name", app_name],
  335. )
  336. assert result.exit_code == 0
  337. expected_call_args = dict(
  338. frontend_file_name="frontend.zip",
  339. backend_file_name="backend.zip",
  340. export_dir=fake_export_dir,
  341. key=expected_key,
  342. app_name=app_name,
  343. regions=regions,
  344. app_prefix=app_prefix,
  345. cpus=None,
  346. memory_mb=None,
  347. auto_start=None,
  348. auto_stop=None,
  349. frontend_hostname=None,
  350. envs=None,
  351. with_metrics=None,
  352. with_tracing=None,
  353. )
  354. expected_call_args.update(args_patch or {})
  355. assert mock_deploy.call_args.kwargs == expected_call_args