RemoteRF 2.2.1__tar.gz → 2.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. {remoterf-2.2.1/src/remoterf.egg-info → remoterf-2.2.3}/PKG-INFO +1 -1
  2. {remoterf-2.2.1 → remoterf-2.2.3}/pyproject.toml +1 -1
  3. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/core/acc_login.py +2 -1
  4. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/core/app.py +8 -3
  5. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/deployment/homes.py +21 -0
  6. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/remoterf_cli.py +45 -8
  7. {remoterf-2.2.1 → remoterf-2.2.3/src/remoterf.egg-info}/PKG-INFO +1 -1
  8. remoterf-2.2.3/tests/test_cli_login_fresh.py +162 -0
  9. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_homes.py +1 -6
  10. remoterf-2.2.1/tests/test_cli_login_fresh.py +0 -82
  11. {remoterf-2.2.1 → remoterf-2.2.3}/LICENSE +0 -0
  12. {remoterf-2.2.1 → remoterf-2.2.3}/README.md +0 -0
  13. {remoterf-2.2.1 → remoterf-2.2.3}/setup.cfg +0 -0
  14. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/__init__.py +0 -0
  15. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/__init__.py +0 -0
  16. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/grpc/__init__.py +0 -0
  17. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/grpc/deployment_capabilities.proto +0 -0
  18. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/grpc/deployment_capabilities_pb2.py +0 -0
  19. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/grpc/deployment_capabilities_pb2_grpc.py +0 -0
  20. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/grpc/grpc.proto +0 -0
  21. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/grpc/grpc_pb2.py +0 -0
  22. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/grpc/grpc_pb2_grpc.py +0 -0
  23. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/grpc/v2_codec.py +0 -0
  24. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/tos_notice.txt +0 -0
  25. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/utils/__init__.py +0 -0
  26. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/utils/ansi_codes.py +0 -0
  27. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/utils/api_token.py +0 -0
  28. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/utils/banner.py +0 -0
  29. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/utils/list_string.py +0 -0
  30. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/common/utils/process_arg.py +0 -0
  31. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/config/__init__.py +0 -0
  32. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/config/cert_fetcher.py +0 -0
  33. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/config/config.py +0 -0
  34. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/core/__init__.py +0 -0
  35. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/core/banner_note.txt +0 -0
  36. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/core/direct_path.py +0 -0
  37. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/core/dynamic_v2_transport.py +0 -0
  38. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/core/grpc_acc.py +0 -0
  39. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/core/grpc_client.py +0 -0
  40. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/core/secure_channel.py +0 -0
  41. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/core/v2_errors.py +0 -0
  42. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/deployment/__init__.py +0 -0
  43. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/deployment/backend.py +0 -0
  44. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/deployment/direct.py +0 -0
  45. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/deployment/http.py +0 -0
  46. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/deployment/state.py +0 -0
  47. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/__init__.py +0 -0
  48. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/_virtual.py +0 -0
  49. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/adalm_pluto/__init__.py +0 -0
  50. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/adalm_pluto/adalm_pluto_remote.py +0 -0
  51. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/dynamic_device.py +0 -0
  52. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/dynamic_v2.py +0 -0
  53. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/hackrf/__init__.py +0 -0
  54. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/hackrf/hackrf_remote.py +0 -0
  55. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/rtl_sdr/__init__.py +0 -0
  56. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/rtl_sdr/rtl_sdr_remote.py +0 -0
  57. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/sim_sdr/__init__.py +0 -0
  58. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/sim_sdr/sim_sdr_remote.py +0 -0
  59. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/support/__init__.py +0 -0
  60. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/support/ti_mmwave.py +0 -0
  61. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/support/uhd.py +0 -0
  62. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/support/uhd_dsp.py +0 -0
  63. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/support/uhd_v2.py +0 -0
  64. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/ti_mmwave/__init__.py +0 -0
  65. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/ti_mmwave/ti_mmwave_remote.py +0 -0
  66. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/usrp/__init__.py +0 -0
  67. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/usrp/usrp_remote.py +0 -0
  68. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/drivers/usrp/usrp_remote.pyi +0 -0
  69. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf/version.py +0 -0
  70. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf.egg-info/SOURCES.txt +0 -0
  71. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf.egg-info/dependency_links.txt +0 -0
  72. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf.egg-info/entry_points.txt +0 -0
  73. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf.egg-info/requires.txt +0 -0
  74. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf.egg-info/top_level.txt +0 -0
  75. {remoterf-2.2.1 → remoterf-2.2.3}/src/remoterf.py +0 -0
  76. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_banner.py +0 -0
  77. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_config_address.py +0 -0
  78. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_deployment_accounts.py +0 -0
  79. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_direct_path_fixed.py +0 -0
  80. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_direct_path_partner.py +0 -0
  81. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_direct_path_race.py +0 -0
  82. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_dynamic_device_codegen.py +0 -0
  83. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_dynamic_v2.py +0 -0
  84. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_federated_devices.py +0 -0
  85. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_fetch_idl_fallback.py +0 -0
  86. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_finish_prompt.py +0 -0
  87. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_gate_i_direct_path.py +0 -0
  88. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_grpc_client_target.py +0 -0
  89. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_native_account_credentials.py +0 -0
  90. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_native_devices.py +0 -0
  91. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_partner_dynamic_v2.py +0 -0
  92. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_pluto_virtual.py +0 -0
  93. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_process_arg.py +0 -0
  94. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_remoterf_cli.py +0 -0
  95. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_ti_mmwave.py +0 -0
  96. {remoterf-2.2.1 → remoterf-2.2.3}/tests/test_upgrade_rescue.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 2.2.1
3
+ Version: 2.2.3
4
4
  Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
5
5
  Author-email: Ethan Ge <ethoGalaxy@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remoterf"
7
- version = "2.2.1"
7
+ version = "2.2.3"
8
8
  description = "A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts."
9
9
  readme = "README.md"
10
10
  license = "GPL-3.0-or-later"
@@ -14,7 +14,7 @@
14
14
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
15
 
16
16
  def main(*, register=False, enrollment_code=None, server_label=None, show_banner=True,
17
- home=None, route=None):
17
+ home=None, route=None, username=None):
18
18
  from .app import run
19
19
  return run(
20
20
  register=register,
@@ -23,4 +23,5 @@ def main(*, register=False, enrollment_code=None, server_label=None, show_banner
23
23
  show_banner=show_banner,
24
24
  home=home,
25
25
  route=route,
26
+ username=username,
26
27
  )
@@ -116,6 +116,8 @@ def _password_confirmation(*, enforce_policy=False):
116
116
 
117
117
  # The code typed at `remoterf -r`, used once for this registration.
118
118
  pending_enrollment_code = None
119
+ # A username already given (found on Global): the login asks only the password.
120
+ pending_username = None
119
121
  # The saved home this shell is attached to (None on a bare direct profile).
120
122
  home_name = None
121
123
  # The route it was reached over (None on a bare direct profile or an HTTPS home).
@@ -249,7 +251,9 @@ def _account_command(command):
249
251
  return False
250
252
  if command in ('l', 'login'):
251
253
  printf(f"Login to {_home_name()}", (Sty.BOLD, Sty.BLUE))
252
- account.username = _ask('Username or Email' if account.is_https_home else 'Username')
254
+ global pending_username
255
+ account.username = pending_username or _ask('Username or Email' if account.is_https_home else 'Username')
256
+ pending_username = None
253
257
  account.password = _ask('Password (Hidden)', hidden=True)
254
258
  ok = bool(account.login_user(remember=not account.is_https_home))
255
259
  if ok:
@@ -1585,8 +1589,9 @@ def interactive_reserve_next_days_auto():
1585
1589
 
1586
1590
 
1587
1591
  def run(backend=None, *, register=False, enrollment_code=None, server_label=None,
1588
- show_banner=True, home=None, route=None):
1589
- global account, session, server_addr, pending_enrollment_code, home_name, current_route
1592
+ show_banner=True, home=None, route=None, username=None):
1593
+ global account, session, server_addr, pending_enrollment_code, home_name, current_route, pending_username
1594
+ pending_username = username
1590
1595
  home_name = home
1591
1596
  current_route = route
1592
1597
  from ..deployment.backend import selected_backend
@@ -268,6 +268,27 @@ def connect(name: str) -> dict:
268
268
  return route
269
269
 
270
270
 
271
+ # Global's own account home, by its name on Global's broker.
272
+ GLOBAL_HOME = "home"
273
+
274
+
275
+ def lookup_username(global_origin: str, username: str) -> str | None:
276
+ """The deployment that holds this username on Global, or None."""
277
+ from .http import AccountBackendError, JsonTransport
278
+
279
+ transport = JsonTransport(global_origin)
280
+ try:
281
+ return transport.request(
282
+ "POST", "/v2/broker/usernames/lookup", data={"username": username}
283
+ )["deployment"]
284
+ except AccountBackendError as exc:
285
+ if exc.code == "not_found":
286
+ return None
287
+ raise
288
+ finally:
289
+ transport.close()
290
+
291
+
271
292
  def register_home(host: str) -> tuple[str, dict, dict]:
272
293
  """Learn a deployment from its code, save it, and connect to it."""
273
294
  document = discover(host)
@@ -180,7 +180,8 @@ def print_help() -> None:
180
180
 
181
181
  def _account_shell(*, register: bool, enrollment_code: str | None = None,
182
182
  server_label: str | None = None, show_banner: bool = True,
183
- home: str | None = None, route: dict | None = None) -> int:
183
+ home: str | None = None, route: dict | None = None,
184
+ username: str | None = None) -> int:
184
185
  ok, _ = _ensure_config_present()
185
186
  if not ok:
186
187
  _print_server_unavailable()
@@ -198,6 +199,7 @@ def _account_shell(*, register: bool, enrollment_code: str | None = None,
198
199
  show_banner=show_banner,
199
200
  home=home,
200
201
  route=route,
202
+ username=username,
201
203
  )
202
204
  or 0
203
205
  )
@@ -212,7 +214,7 @@ def _global_origin() -> str:
212
214
  return os.getenv('REMOTERF_GLOBAL_ORIGIN', 'https://global.remoterf.net')
213
215
 
214
216
 
215
- def _use_global(*, register: bool, show_banner: bool = True) -> int:
217
+ def _use_global(*, register: bool, show_banner: bool = True, username: str | None = None) -> int:
216
218
  """Register or log in with the RemoteRF Global identity itself.
217
219
 
218
220
  A Global-native account is complete on its own: it needs no deployment
@@ -238,7 +240,7 @@ def _use_global(*, register: bool, show_banner: bool = True) -> int:
238
240
  except (RuntimeError, ValueError) as exc:
239
241
  print(f'Could not reach RemoteRF Global: {exc}')
240
242
  return 2
241
- return _account_shell(register=register, show_banner=show_banner)
243
+ return _account_shell(register=register, show_banner=show_banner, username=username)
242
244
 
243
245
 
244
246
  @contextmanager
@@ -397,13 +399,11 @@ def _login(target: str | None) -> int:
397
399
  # Nothing saved. A legacy address in .env still logs in there;
398
400
  # a fresh install has nothing to log in to, so `-l` is `-r`.
399
401
  if not _ensure_config_present()[0]:
400
- return _register()
402
+ return _login_by_username()
401
403
  return _account_shell(register=False)
402
404
  if target == 'global':
403
- if not homes.has_global_account():
404
- printf('No RemoteRF Global account is configured here.', Sty.WARNING)
405
- printf('Run: ', Sty.GRAY, 'remoterf --register', Sty.CYAN)
406
- return 2
405
+ # Global's address is built in, so a fresh install can log in to an
406
+ # existing Global account without registering again.
407
407
  return _use_global(register=False)
408
408
  if target not in homes.homes():
409
409
  printf(f'No saved home named {target!r}.', Sty.WARNING)
@@ -411,6 +411,43 @@ def _login(target: str | None) -> int:
411
411
  return _use_home(target, register=False)
412
412
 
413
413
 
414
+ def _login_by_username() -> int:
415
+ """A fresh install: Global knows which lab holds a username, so the
416
+ account is found by name. Enter registers, as `-l` here always did."""
417
+ from remoterf.deployment import homes
418
+
419
+ try:
420
+ name = input('Username (Enter to register): ').strip()
421
+ except (EOFError, KeyboardInterrupt):
422
+ print()
423
+ return 1
424
+ if not name:
425
+ return _register()
426
+ try:
427
+ with _working("Looking up your account…"):
428
+ deployment = homes.lookup_username(_global_origin(), name)
429
+ except (RuntimeError, ValueError) as exc:
430
+ printf(f'Could not reach RemoteRF Global: {_reason(exc)}', Sty.WARNING)
431
+ return 2
432
+ if deployment is None:
433
+ printf(f'No RemoteRF account named {name!r} was found.', Sty.WARNING)
434
+ printf('Run: ', Sty.GRAY, 'remoterf --register', Sty.CYAN)
435
+ return 2
436
+ if deployment == homes.GLOBAL_HOME:
437
+ return _use_global(register=False, username=name)
438
+ host = f'{deployment}.{homes.GLOBAL_DOMAIN}'
439
+ try:
440
+ with _working(f"Looking up {host}…"):
441
+ lab, home, route = homes.register_home(host)
442
+ except (RuntimeError, ValueError) as exc:
443
+ printf(f'Could not reach {host}: {_reason(exc)}', Sty.WARNING)
444
+ return 2
445
+ return _account_shell(
446
+ register=False, server_label=f'{lab} ({_route_label(route)})',
447
+ home=lab, route={**route, 'home': home}, username=name,
448
+ )
449
+
450
+
414
451
  def _print_pick_a_target() -> int:
415
452
  _print_homes()
416
453
  printf('Choose one: ', Sty.GRAY, 'remoterf --login <name>', Sty.CYAN)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 2.2.1
3
+ Version: 2.2.3
4
4
  Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
5
5
  Author-email: Ethan Ge <ethoGalaxy@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -0,0 +1,162 @@
1
+ """`remoterf -l` on a fresh install is `remoterf -r`; a configured client logs in."""
2
+
3
+ import os
4
+ import tempfile
5
+ from unittest import mock
6
+
7
+
8
+ def _fresh_home():
9
+ tmp = tempfile.TemporaryDirectory()
10
+ env = {k: v for k, v in os.environ.items() if not k.startswith("REMOTERF_")}
11
+ env["HOME"] = tmp.name
12
+ return tmp, mock.patch.dict(os.environ, env, clear=True)
13
+
14
+
15
+ def test_login_on_a_fresh_install_registers_on_enter():
16
+ from remoterf import remoterf_cli
17
+
18
+ tmp, env = _fresh_home()
19
+ with tmp, env, \
20
+ mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
21
+ mock.patch("builtins.input", return_value=""), \
22
+ mock.patch("remoterf.deployment.homes.lookup_username") as lookup, \
23
+ mock.patch.object(remoterf_cli, "_register", return_value=0) as register, \
24
+ mock.patch.object(remoterf_cli, "_account_shell") as shell:
25
+ assert remoterf_cli._login(None) == 0
26
+ register.assert_called_once_with()
27
+ shell.assert_not_called()
28
+ lookup.assert_not_called()
29
+
30
+
31
+ def test_login_on_a_fresh_install_finds_the_account_by_username():
32
+ from remoterf import remoterf_cli
33
+
34
+ tmp, env = _fresh_home()
35
+ route = {"kind": "relay", "host": "ucla.global.remoterf.net", "port": 12321}
36
+ with tmp, env, \
37
+ mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
38
+ mock.patch("builtins.input", return_value="ian"), \
39
+ mock.patch("remoterf.deployment.homes.lookup_username", return_value="ucla") as lookup, \
40
+ mock.patch("remoterf.deployment.homes.register_home",
41
+ return_value=("ucla", {"display_name": "UCLA"}, route)) as register_home, \
42
+ mock.patch.object(remoterf_cli, "_account_shell", return_value=0) as shell:
43
+ assert remoterf_cli._login(None) == 0
44
+ assert lookup.call_args.args[1] == "ian"
45
+ register_home.assert_called_once_with("ucla.global.remoterf.net")
46
+ assert shell.call_args.kwargs["register"] is False
47
+ assert shell.call_args.kwargs["home"] == "ucla"
48
+ assert shell.call_args.kwargs["username"] == "ian"
49
+
50
+
51
+ def test_login_on_a_fresh_install_finds_a_global_account():
52
+ from remoterf import remoterf_cli
53
+
54
+ tmp, env = _fresh_home()
55
+ with tmp, env, \
56
+ mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
57
+ mock.patch("builtins.input", return_value="ian"), \
58
+ mock.patch("remoterf.deployment.homes.lookup_username", return_value="home"), \
59
+ mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global:
60
+ assert remoterf_cli._login(None) == 0
61
+ use_global.assert_called_once_with(register=False, username="ian")
62
+
63
+
64
+ def test_login_on_a_fresh_install_with_an_unknown_username():
65
+ from remoterf import remoterf_cli
66
+
67
+ tmp, env = _fresh_home()
68
+ with tmp, env, \
69
+ mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
70
+ mock.patch("builtins.input", return_value="nobody"), \
71
+ mock.patch("remoterf.deployment.homes.lookup_username", return_value=None), \
72
+ mock.patch.object(remoterf_cli, "_register") as register, \
73
+ mock.patch.object(remoterf_cli, "_account_shell") as shell:
74
+ assert remoterf_cli._login(None) == 2
75
+ register.assert_not_called()
76
+ shell.assert_not_called()
77
+
78
+
79
+ def test_login_global_on_a_fresh_install_logs_in_to_global():
80
+ from remoterf import remoterf_cli
81
+
82
+ tmp, env = _fresh_home()
83
+ with tmp, env, \
84
+ mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
85
+ mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global:
86
+ assert remoterf_cli._login("global") == 0
87
+ use_global.assert_called_once_with(register=False)
88
+
89
+
90
+ def test_login_with_a_legacy_address_still_logs_in_there():
91
+ from remoterf import remoterf_cli
92
+
93
+ tmp, env = _fresh_home()
94
+ with tmp, env, \
95
+ mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
96
+ mock.patch.object(remoterf_cli, "_ensure_config_present", return_value=(True, "")), \
97
+ mock.patch.object(remoterf_cli, "_register") as register, \
98
+ mock.patch.object(remoterf_cli, "_account_shell", return_value=0) as shell:
99
+ assert remoterf_cli._login(None) == 0
100
+ shell.assert_called_once_with(register=False)
101
+ register.assert_not_called()
102
+
103
+
104
+ def test_register_without_a_code_makes_a_global_account(monkeypatch):
105
+ from remoterf import remoterf_cli
106
+
107
+ monkeypatch.setattr("builtins.input", lambda prompt="": "")
108
+ with mock.patch("remoterf.config.config._confirm_tos", return_value=True), \
109
+ mock.patch("remoterf.config.config.forget_tos_agreement"), \
110
+ mock.patch("remoterf.config.config.remember_tos_agreement"), \
111
+ mock.patch.object(remoterf_cli, "print_client_banner"), \
112
+ mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global, \
113
+ mock.patch.object(remoterf_cli, "_account_shell") as shell:
114
+ assert remoterf_cli._register() == 0
115
+ use_global.assert_called_once_with(register=True, show_banner=False)
116
+ shell.assert_not_called()
117
+
118
+
119
+ def test_register_global_asks_for_the_tos_like_register(monkeypatch):
120
+ from remoterf import remoterf_cli
121
+
122
+ for arg in ("global", "-global"):
123
+ monkeypatch.setattr("sys.argv", ["remoterf", "-r", arg])
124
+ with mock.patch("remoterf.config.config._confirm_tos", return_value=True) as confirm, \
125
+ mock.patch("remoterf.config.config.forget_tos_agreement") as forget, \
126
+ mock.patch("remoterf.config.config.remember_tos_agreement") as remember, \
127
+ mock.patch.object(remoterf_cli, "print_client_banner"), \
128
+ mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global:
129
+ assert remoterf_cli.main() == 0
130
+ forget.assert_called_once_with()
131
+ confirm.assert_called_once()
132
+ remember.assert_called_once_with()
133
+ use_global.assert_called_once_with(register=True, show_banner=False)
134
+
135
+ # Declining stops before any account is made.
136
+ monkeypatch.setattr("sys.argv", ["remoterf", "-r", "global"])
137
+ with mock.patch("remoterf.config.config._confirm_tos", return_value=False), \
138
+ mock.patch("remoterf.config.config.forget_tos_agreement"), \
139
+ mock.patch("remoterf.config.config.remember_tos_agreement") as remember, \
140
+ mock.patch.object(remoterf_cli, "print_client_banner"), \
141
+ mock.patch.object(remoterf_cli, "_use_global") as use_global:
142
+ assert remoterf_cli.main() == 1
143
+ remember.assert_not_called()
144
+ use_global.assert_not_called()
145
+
146
+
147
+ def test_a_found_username_is_not_asked_again():
148
+ from remoterf.core import app
149
+
150
+ account = mock.Mock(is_https_home=False, backend=None)
151
+ account.login_user.return_value = True
152
+ with mock.patch.object(app, "account", account), \
153
+ mock.patch.object(app, "pending_username", "ian"), \
154
+ mock.patch.object(app, "local_capabilities",
155
+ return_value={"registration_policy": {}}), \
156
+ mock.patch.object(app, "_home_name", return_value="ucla"), \
157
+ mock.patch.object(app, "_remember_login"), \
158
+ mock.patch.object(app, "_ask", return_value="secret") as ask:
159
+ assert app._account_command("login") is True
160
+ assert app.pending_username is None
161
+ assert account.username == "ian"
162
+ ask.assert_called_once_with("Password (Hidden)", hidden=True)
@@ -366,13 +366,8 @@ def test_login_names_a_home_explicitly(monkeypatch, reachable):
366
366
  assert run_cli(["-l", "nope"], monkeypatch, _account_shell=shell) == 2
367
367
 
368
368
 
369
- def test_login_global_requires_a_global_account(monkeypatch):
369
+ def test_login_global_works_without_a_saved_global_account(monkeypatch):
370
370
  homes.save_home("ucla", document())
371
- assert run_cli(["-l", "global"], monkeypatch, _account_shell=mock.Mock()) == 2
372
-
373
- homes.remember_global_account(
374
- "https://global.remoterf.net", "897541e5-b61f-45f7-a814-8ffeaad2ae69"
375
- )
376
371
  used_global = mock.Mock(return_value=0)
377
372
  assert run_cli(["-l", "global"], monkeypatch, _use_global=used_global) == 0
378
373
  used_global.assert_called_once_with(register=False)
@@ -1,82 +0,0 @@
1
- """`remoterf -l` on a fresh install is `remoterf -r`; a configured client logs in."""
2
-
3
- import os
4
- import tempfile
5
- from unittest import mock
6
-
7
-
8
- def _fresh_home():
9
- tmp = tempfile.TemporaryDirectory()
10
- env = {k: v for k, v in os.environ.items() if not k.startswith("REMOTERF_")}
11
- env["HOME"] = tmp.name
12
- return tmp, mock.patch.dict(os.environ, env, clear=True)
13
-
14
-
15
- def test_login_on_a_fresh_install_registers():
16
- from remoterf import remoterf_cli
17
-
18
- tmp, env = _fresh_home()
19
- with tmp, env, \
20
- mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
21
- mock.patch.object(remoterf_cli, "_register", return_value=0) as register, \
22
- mock.patch.object(remoterf_cli, "_account_shell") as shell:
23
- assert remoterf_cli._login(None) == 0
24
- register.assert_called_once_with()
25
- shell.assert_not_called()
26
-
27
-
28
- def test_login_with_a_legacy_address_still_logs_in_there():
29
- from remoterf import remoterf_cli
30
-
31
- tmp, env = _fresh_home()
32
- with tmp, env, \
33
- mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
34
- mock.patch.object(remoterf_cli, "_ensure_config_present", return_value=(True, "")), \
35
- mock.patch.object(remoterf_cli, "_register") as register, \
36
- mock.patch.object(remoterf_cli, "_account_shell", return_value=0) as shell:
37
- assert remoterf_cli._login(None) == 0
38
- shell.assert_called_once_with(register=False)
39
- register.assert_not_called()
40
-
41
-
42
- def test_register_without_a_code_makes_a_global_account(monkeypatch):
43
- from remoterf import remoterf_cli
44
-
45
- monkeypatch.setattr("builtins.input", lambda prompt="": "")
46
- with mock.patch("remoterf.config.config._confirm_tos", return_value=True), \
47
- mock.patch("remoterf.config.config.forget_tos_agreement"), \
48
- mock.patch("remoterf.config.config.remember_tos_agreement"), \
49
- mock.patch.object(remoterf_cli, "print_client_banner"), \
50
- mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global, \
51
- mock.patch.object(remoterf_cli, "_account_shell") as shell:
52
- assert remoterf_cli._register() == 0
53
- use_global.assert_called_once_with(register=True, show_banner=False)
54
- shell.assert_not_called()
55
-
56
-
57
- def test_register_global_asks_for_the_tos_like_register(monkeypatch):
58
- from remoterf import remoterf_cli
59
-
60
- for arg in ("global", "-global"):
61
- monkeypatch.setattr("sys.argv", ["remoterf", "-r", arg])
62
- with mock.patch("remoterf.config.config._confirm_tos", return_value=True) as confirm, \
63
- mock.patch("remoterf.config.config.forget_tos_agreement") as forget, \
64
- mock.patch("remoterf.config.config.remember_tos_agreement") as remember, \
65
- mock.patch.object(remoterf_cli, "print_client_banner"), \
66
- mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global:
67
- assert remoterf_cli.main() == 0
68
- forget.assert_called_once_with()
69
- confirm.assert_called_once()
70
- remember.assert_called_once_with()
71
- use_global.assert_called_once_with(register=True, show_banner=False)
72
-
73
- # Declining stops before any account is made.
74
- monkeypatch.setattr("sys.argv", ["remoterf", "-r", "global"])
75
- with mock.patch("remoterf.config.config._confirm_tos", return_value=False), \
76
- mock.patch("remoterf.config.config.forget_tos_agreement"), \
77
- mock.patch("remoterf.config.config.remember_tos_agreement") as remember, \
78
- mock.patch.object(remoterf_cli, "print_client_banner"), \
79
- mock.patch.object(remoterf_cli, "_use_global") as use_global:
80
- assert remoterf_cli.main() == 1
81
- remember.assert_not_called()
82
- use_global.assert_not_called()
File without changes
File without changes
File without changes
File without changes
File without changes