RemoteRF 2.2.2__tar.gz → 2.2.4__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.2/src/remoterf.egg-info → remoterf-2.2.4}/PKG-INFO +1 -1
  2. {remoterf-2.2.2 → remoterf-2.2.4}/pyproject.toml +1 -1
  3. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/core/acc_login.py +2 -1
  4. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/core/app.py +16 -3
  5. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/deployment/homes.py +14 -0
  6. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/remoterf_cli.py +48 -6
  7. {remoterf-2.2.2 → remoterf-2.2.4/src/remoterf.egg-info}/PKG-INFO +1 -1
  8. remoterf-2.2.4/tests/test_cli_login_fresh.py +203 -0
  9. remoterf-2.2.2/tests/test_cli_login_fresh.py +0 -93
  10. {remoterf-2.2.2 → remoterf-2.2.4}/LICENSE +0 -0
  11. {remoterf-2.2.2 → remoterf-2.2.4}/README.md +0 -0
  12. {remoterf-2.2.2 → remoterf-2.2.4}/setup.cfg +0 -0
  13. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/__init__.py +0 -0
  14. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/__init__.py +0 -0
  15. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/grpc/__init__.py +0 -0
  16. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/grpc/deployment_capabilities.proto +0 -0
  17. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/grpc/deployment_capabilities_pb2.py +0 -0
  18. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/grpc/deployment_capabilities_pb2_grpc.py +0 -0
  19. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/grpc/grpc.proto +0 -0
  20. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/grpc/grpc_pb2.py +0 -0
  21. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/grpc/grpc_pb2_grpc.py +0 -0
  22. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/grpc/v2_codec.py +0 -0
  23. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/tos_notice.txt +0 -0
  24. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/utils/__init__.py +0 -0
  25. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/utils/ansi_codes.py +0 -0
  26. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/utils/api_token.py +0 -0
  27. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/utils/banner.py +0 -0
  28. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/utils/list_string.py +0 -0
  29. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/common/utils/process_arg.py +0 -0
  30. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/config/__init__.py +0 -0
  31. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/config/cert_fetcher.py +0 -0
  32. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/config/config.py +0 -0
  33. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/core/__init__.py +0 -0
  34. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/core/banner_note.txt +0 -0
  35. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/core/direct_path.py +0 -0
  36. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/core/dynamic_v2_transport.py +0 -0
  37. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/core/grpc_acc.py +0 -0
  38. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/core/grpc_client.py +0 -0
  39. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/core/secure_channel.py +0 -0
  40. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/core/v2_errors.py +0 -0
  41. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/deployment/__init__.py +0 -0
  42. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/deployment/backend.py +0 -0
  43. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/deployment/direct.py +0 -0
  44. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/deployment/http.py +0 -0
  45. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/deployment/state.py +0 -0
  46. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/__init__.py +0 -0
  47. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/_virtual.py +0 -0
  48. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/adalm_pluto/__init__.py +0 -0
  49. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/adalm_pluto/adalm_pluto_remote.py +0 -0
  50. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/dynamic_device.py +0 -0
  51. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/dynamic_v2.py +0 -0
  52. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/hackrf/__init__.py +0 -0
  53. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/hackrf/hackrf_remote.py +0 -0
  54. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/rtl_sdr/__init__.py +0 -0
  55. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/rtl_sdr/rtl_sdr_remote.py +0 -0
  56. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/sim_sdr/__init__.py +0 -0
  57. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/sim_sdr/sim_sdr_remote.py +0 -0
  58. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/support/__init__.py +0 -0
  59. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/support/ti_mmwave.py +0 -0
  60. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/support/uhd.py +0 -0
  61. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/support/uhd_dsp.py +0 -0
  62. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/support/uhd_v2.py +0 -0
  63. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/ti_mmwave/__init__.py +0 -0
  64. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/ti_mmwave/ti_mmwave_remote.py +0 -0
  65. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/usrp/__init__.py +0 -0
  66. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/usrp/usrp_remote.py +0 -0
  67. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/drivers/usrp/usrp_remote.pyi +0 -0
  68. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf/version.py +0 -0
  69. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf.egg-info/SOURCES.txt +0 -0
  70. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf.egg-info/dependency_links.txt +0 -0
  71. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf.egg-info/entry_points.txt +0 -0
  72. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf.egg-info/requires.txt +0 -0
  73. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf.egg-info/top_level.txt +0 -0
  74. {remoterf-2.2.2 → remoterf-2.2.4}/src/remoterf.py +0 -0
  75. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_banner.py +0 -0
  76. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_config_address.py +0 -0
  77. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_deployment_accounts.py +0 -0
  78. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_direct_path_fixed.py +0 -0
  79. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_direct_path_partner.py +0 -0
  80. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_direct_path_race.py +0 -0
  81. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_dynamic_device_codegen.py +0 -0
  82. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_dynamic_v2.py +0 -0
  83. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_federated_devices.py +0 -0
  84. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_fetch_idl_fallback.py +0 -0
  85. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_finish_prompt.py +0 -0
  86. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_gate_i_direct_path.py +0 -0
  87. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_grpc_client_target.py +0 -0
  88. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_homes.py +0 -0
  89. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_native_account_credentials.py +0 -0
  90. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_native_devices.py +0 -0
  91. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_partner_dynamic_v2.py +0 -0
  92. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_pluto_virtual.py +0 -0
  93. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_process_arg.py +0 -0
  94. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_remoterf_cli.py +0 -0
  95. {remoterf-2.2.2 → remoterf-2.2.4}/tests/test_ti_mmwave.py +0 -0
  96. {remoterf-2.2.2 → remoterf-2.2.4}/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.2
3
+ Version: 2.2.4
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.2"
7
+ version = "2.2.4"
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 (`name@lab` at `-l`): 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).
@@ -207,6 +209,9 @@ def _account_command(command):
207
209
  account.email = _ask('Email') if policy['email_required'] else ''
208
210
  if not account.create_user():
209
211
  raise AccountBackendError('Registration failed')
212
+ if account.is_https_home:
213
+ from ..deployment import homes
214
+ printf(f"Log in from anywhere as {account.username}@{homes.GLOBAL_ACCOUNTS}", Sty.GRAY)
210
215
  except BaseException:
211
216
  # The code was fine; only the details failed. Keep it for the retry.
212
217
  pending_enrollment_code = account.enrollment_code or None
@@ -249,7 +254,14 @@ def _account_command(command):
249
254
  return False
250
255
  if command in ('l', 'login'):
251
256
  printf(f"Login to {_home_name()}", (Sty.BOLD, Sty.BLUE))
252
- account.username = _ask('Username or Email' if account.is_https_home else 'Username')
257
+ global pending_username
258
+ from ..deployment import homes
259
+ typed = pending_username or _ask('Username or Email' if account.is_https_home else 'Username')
260
+ pending_username = None
261
+ # `ethan@ucla` typed at ucla is `ethan` there.
262
+ name, where = homes.split_account(typed)
263
+ here = homes.GLOBAL_ACCOUNTS if account.is_https_home else home_name
264
+ account.username = name if where == here else typed
253
265
  account.password = _ask('Password (Hidden)', hidden=True)
254
266
  ok = bool(account.login_user(remember=not account.is_https_home))
255
267
  if ok:
@@ -1585,8 +1597,9 @@ def interactive_reserve_next_days_auto():
1585
1597
 
1586
1598
 
1587
1599
  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
1600
+ show_banner=True, home=None, route=None, username=None):
1601
+ global account, session, server_addr, pending_enrollment_code, home_name, current_route, pending_username
1602
+ pending_username = username
1590
1603
  home_name = home
1591
1604
  current_route = route
1592
1605
  from ..deployment.backend import selected_backend
@@ -268,6 +268,20 @@ def connect(name: str) -> dict:
268
268
  return route
269
269
 
270
270
 
271
+ # An account is `name@<deployment>`; Global's own accounts are `name@global`.
272
+ GLOBAL_ACCOUNTS = "global"
273
+
274
+
275
+ def split_account(text: str) -> tuple[str, str | None]:
276
+ """`ethan@ucla` -> ("ethan", "ucla"). A bare name, or an email (a dot
277
+ after the @; deployment names never have one), names no deployment."""
278
+ text = text.strip()
279
+ name, at, where = text.rpartition("@")
280
+ if not at or not name or not where or "." in where:
281
+ return text, None
282
+ return name, where.lower()
283
+
284
+
271
285
  def register_home(host: str) -> tuple[str, dict, dict]:
272
286
  """Learn a deployment from its code, save it, and connect to it."""
273
287
  document = discover(host)
@@ -150,7 +150,7 @@ def print_help() -> None:
150
150
  printf(" remoterf -h | --help", Sty.CYAN, " Show this help", Sty.DEFAULT)
151
151
  print()
152
152
  printf("Commands:", (Sty.BOLD, Sty.MAGENTA))
153
- printf(" remoterf -l | --login [target]", Sty.CYAN, " Login: last used, 'global', or a home name", Sty.DEFAULT)
153
+ printf(" remoterf -l | --login [target]", Sty.CYAN, " Login: last used, 'global', a home name, or an account (ethan@ucla)", Sty.DEFAULT)
154
154
  printf(" remoterf -r | --register [global]", Sty.CYAN, " Register with an enrollment code (or a RemoteRF Global account)", Sty.DEFAULT)
155
155
  printf(" remoterf homes", Sty.CYAN, " List saved deployment homes", Sty.DEFAULT)
156
156
  printf(" remoterf -v | --version", Sty.CYAN, " Print version", Sty.DEFAULT)
@@ -171,6 +171,7 @@ def print_help() -> None:
171
171
  printf(" remoterf --login", Sty.GREEN)
172
172
  printf(" remoterf --login ucla", Sty.GREEN)
173
173
  printf(" remoterf --login global", Sty.GREEN)
174
+ printf(" remoterf --login ethan@ucla", Sty.GREEN)
174
175
  printf(" remoterf homes", Sty.GREEN)
175
176
  printf(" remoterf --version", Sty.GREEN)
176
177
  printf(" remoterf --config --addr 203.0.113.10:12321", Sty.GREEN)
@@ -180,7 +181,8 @@ def print_help() -> None:
180
181
 
181
182
  def _account_shell(*, register: bool, enrollment_code: str | None = None,
182
183
  server_label: str | None = None, show_banner: bool = True,
183
- home: str | None = None, route: dict | None = None) -> int:
184
+ home: str | None = None, route: dict | None = None,
185
+ username: str | None = None) -> int:
184
186
  ok, _ = _ensure_config_present()
185
187
  if not ok:
186
188
  _print_server_unavailable()
@@ -198,6 +200,7 @@ def _account_shell(*, register: bool, enrollment_code: str | None = None,
198
200
  show_banner=show_banner,
199
201
  home=home,
200
202
  route=route,
203
+ username=username,
201
204
  )
202
205
  or 0
203
206
  )
@@ -212,7 +215,7 @@ def _global_origin() -> str:
212
215
  return os.getenv('REMOTERF_GLOBAL_ORIGIN', 'https://global.remoterf.net')
213
216
 
214
217
 
215
- def _use_global(*, register: bool, show_banner: bool = True) -> int:
218
+ def _use_global(*, register: bool, show_banner: bool = True, username: str | None = None) -> int:
216
219
  """Register or log in with the RemoteRF Global identity itself.
217
220
 
218
221
  A Global-native account is complete on its own: it needs no deployment
@@ -238,7 +241,7 @@ def _use_global(*, register: bool, show_banner: bool = True) -> int:
238
241
  except (RuntimeError, ValueError) as exc:
239
242
  print(f'Could not reach RemoteRF Global: {exc}')
240
243
  return 2
241
- return _account_shell(register=register, show_banner=show_banner)
244
+ return _account_shell(register=register, show_banner=show_banner, username=username)
242
245
 
243
246
 
244
247
  @contextmanager
@@ -293,7 +296,8 @@ def _reason(exc: BaseException) -> str:
293
296
  return str(exc)
294
297
 
295
298
 
296
- def _use_home(name: str, *, register: bool, enrollment_code: str | None = None) -> int:
299
+ def _use_home(name: str, *, register: bool, enrollment_code: str | None = None,
300
+ username: str | None = None) -> int:
297
301
  from remoterf.deployment import homes
298
302
 
299
303
  try:
@@ -309,6 +313,7 @@ def _use_home(name: str, *, register: bool, enrollment_code: str | None = None)
309
313
  server_label=f'{name} ({_route_label(route)})',
310
314
  home=name,
311
315
  route={**route, 'home': home},
316
+ username=username,
312
317
  )
313
318
 
314
319
 
@@ -382,6 +387,8 @@ def _login(target: str | None) -> int:
382
387
  if not require_tos_for_login():
383
388
  return 1
384
389
  homes.migrate_legacy_target()
390
+ if target is not None and '@' in target:
391
+ return _login_as(target)
385
392
  if target is None:
386
393
  last = homes.last_target()
387
394
  saved = sorted(homes.homes())
@@ -397,7 +404,7 @@ def _login(target: str | None) -> int:
397
404
  # Nothing saved. A legacy address in .env still logs in there;
398
405
  # a fresh install has nothing to log in to, so `-l` is `-r`.
399
406
  if not _ensure_config_present()[0]:
400
- return _register()
407
+ return _login_by_username()
401
408
  return _account_shell(register=False)
402
409
  if target == 'global':
403
410
  # Global's address is built in, so a fresh install can log in to an
@@ -409,6 +416,41 @@ def _login(target: str | None) -> int:
409
416
  return _use_home(target, register=False)
410
417
 
411
418
 
419
+ def _login_by_username() -> int:
420
+ """A fresh install: an account says where it lives (`ethan@ucla`), so
421
+ asking for it is enough. Enter registers, as `-l` here always did."""
422
+ try:
423
+ name = input('Account, e.g. ethan@ucla (Enter to register): ').strip()
424
+ except (EOFError, KeyboardInterrupt):
425
+ print()
426
+ return 1
427
+ if not name:
428
+ return _register()
429
+ return _login_as(name)
430
+
431
+
432
+ def _login_as(account: str) -> int:
433
+ """Log in to `name@lab` at that lab; a bare name or an email is Global."""
434
+ from remoterf.deployment import homes
435
+
436
+ name, lab = homes.split_account(account)
437
+ if lab is None or lab == homes.GLOBAL_ACCOUNTS:
438
+ return _use_global(register=False, username=name)
439
+ if lab in homes.homes():
440
+ return _use_home(lab, register=False, username=name)
441
+ host = f'{lab}.{homes.GLOBAL_DOMAIN}'
442
+ try:
443
+ with _working(f"Looking up {host}…"):
444
+ lab, home, route = homes.register_home(host)
445
+ except (RuntimeError, ValueError) as exc:
446
+ printf(f'Could not reach {host}: {_reason(exc)}', Sty.WARNING)
447
+ return 2
448
+ return _account_shell(
449
+ register=False, server_label=f'{lab} ({_route_label(route)})',
450
+ home=lab, route={**route, 'home': home}, username=name,
451
+ )
452
+
453
+
412
454
  def _print_pick_a_target() -> int:
413
455
  _print_homes()
414
456
  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.2
3
+ Version: 2.2.4
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,203 @@
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.object(remoterf_cli, "_register", return_value=0) as register, \
23
+ mock.patch.object(remoterf_cli, "_account_shell") as shell:
24
+ assert remoterf_cli._login(None) == 0
25
+ register.assert_called_once_with()
26
+ shell.assert_not_called()
27
+
28
+
29
+ def test_an_account_name_says_where_it_lives():
30
+ from remoterf.deployment.homes import split_account
31
+
32
+ assert split_account(" ian@ucla ") == ("ian", "ucla")
33
+ assert split_account("Ian@UCLA") == ("Ian", "ucla")
34
+ assert split_account("ian@global") == ("ian", "global")
35
+ assert split_account("ian") == ("ian", None)
36
+ assert split_account("ian@ucla.edu") == ("ian@ucla.edu", None) # an email
37
+ assert split_account("a@b@ucla") == ("a@b", "ucla")
38
+ assert split_account("@ucla") == ("@ucla", None)
39
+ assert split_account("ian@") == ("ian@", None)
40
+
41
+
42
+ def test_login_on_a_fresh_install_goes_to_the_lab_in_the_account_name():
43
+ from remoterf import remoterf_cli
44
+
45
+ tmp, env = _fresh_home()
46
+ route = {"kind": "relay", "host": "ucla.global.remoterf.net", "port": 12321}
47
+ with tmp, env, \
48
+ mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
49
+ mock.patch("builtins.input", return_value="ian@ucla"), \
50
+ mock.patch("remoterf.deployment.homes.register_home",
51
+ return_value=("ucla", {"display_name": "UCLA"}, route)) as register_home, \
52
+ mock.patch.object(remoterf_cli, "_account_shell", return_value=0) as shell:
53
+ assert remoterf_cli._login(None) == 0
54
+ register_home.assert_called_once_with("ucla.global.remoterf.net")
55
+ assert shell.call_args.kwargs["register"] is False
56
+ assert shell.call_args.kwargs["home"] == "ucla"
57
+ assert shell.call_args.kwargs["username"] == "ian"
58
+
59
+
60
+ def test_login_on_a_fresh_install_global_bare_names_and_emails_go_to_global():
61
+ from remoterf import remoterf_cli
62
+
63
+ for typed, username in (("ian@global", "ian"), ("ian", "ian"), ("ian@ucla.edu", "ian@ucla.edu")):
64
+ tmp, env = _fresh_home()
65
+ with tmp, env, \
66
+ mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
67
+ mock.patch("builtins.input", return_value=typed), \
68
+ mock.patch("remoterf.deployment.homes.register_home") as register_home, \
69
+ mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global:
70
+ assert remoterf_cli._login(None) == 0
71
+ use_global.assert_called_once_with(register=False, username=username)
72
+ register_home.assert_not_called()
73
+
74
+
75
+ def test_login_to_a_lab_that_does_not_answer():
76
+ from remoterf import remoterf_cli
77
+
78
+ tmp, env = _fresh_home()
79
+ with tmp, env, \
80
+ mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
81
+ mock.patch("builtins.input", return_value="ian@nolab"), \
82
+ mock.patch("remoterf.deployment.homes.register_home",
83
+ side_effect=RuntimeError("no such deployment")) as register_home, \
84
+ mock.patch.object(remoterf_cli, "_account_shell") as shell:
85
+ assert remoterf_cli._login(None) == 2
86
+ register_home.assert_called_once_with("nolab.global.remoterf.net")
87
+ shell.assert_not_called()
88
+
89
+
90
+ def test_login_with_an_account_uses_the_saved_home():
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("remoterf.deployment.homes.homes", return_value={"ucla": {}}), \
97
+ mock.patch("remoterf.deployment.homes.register_home") as register_home, \
98
+ mock.patch.object(remoterf_cli, "_use_home", return_value=0) as use_home:
99
+ assert remoterf_cli._login("ian@ucla") == 0
100
+ use_home.assert_called_once_with("ucla", register=False, username="ian")
101
+ register_home.assert_not_called()
102
+
103
+
104
+ def test_login_global_on_a_fresh_install_logs_in_to_global():
105
+ from remoterf import remoterf_cli
106
+
107
+ tmp, env = _fresh_home()
108
+ with tmp, env, \
109
+ mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
110
+ mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global:
111
+ assert remoterf_cli._login("global") == 0
112
+ use_global.assert_called_once_with(register=False)
113
+
114
+
115
+ def test_login_with_a_legacy_address_still_logs_in_there():
116
+ from remoterf import remoterf_cli
117
+
118
+ tmp, env = _fresh_home()
119
+ with tmp, env, \
120
+ mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
121
+ mock.patch.object(remoterf_cli, "_ensure_config_present", return_value=(True, "")), \
122
+ mock.patch.object(remoterf_cli, "_register") as register, \
123
+ mock.patch.object(remoterf_cli, "_account_shell", return_value=0) as shell:
124
+ assert remoterf_cli._login(None) == 0
125
+ shell.assert_called_once_with(register=False)
126
+ register.assert_not_called()
127
+
128
+
129
+ def test_register_without_a_code_makes_a_global_account(monkeypatch):
130
+ from remoterf import remoterf_cli
131
+
132
+ monkeypatch.setattr("builtins.input", lambda prompt="": "")
133
+ with mock.patch("remoterf.config.config._confirm_tos", return_value=True), \
134
+ mock.patch("remoterf.config.config.forget_tos_agreement"), \
135
+ mock.patch("remoterf.config.config.remember_tos_agreement"), \
136
+ mock.patch.object(remoterf_cli, "print_client_banner"), \
137
+ mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global, \
138
+ mock.patch.object(remoterf_cli, "_account_shell") as shell:
139
+ assert remoterf_cli._register() == 0
140
+ use_global.assert_called_once_with(register=True, show_banner=False)
141
+ shell.assert_not_called()
142
+
143
+
144
+ def test_register_global_asks_for_the_tos_like_register(monkeypatch):
145
+ from remoterf import remoterf_cli
146
+
147
+ for arg in ("global", "-global"):
148
+ monkeypatch.setattr("sys.argv", ["remoterf", "-r", arg])
149
+ with mock.patch("remoterf.config.config._confirm_tos", return_value=True) as confirm, \
150
+ mock.patch("remoterf.config.config.forget_tos_agreement") as forget, \
151
+ mock.patch("remoterf.config.config.remember_tos_agreement") as remember, \
152
+ mock.patch.object(remoterf_cli, "print_client_banner"), \
153
+ mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global:
154
+ assert remoterf_cli.main() == 0
155
+ forget.assert_called_once_with()
156
+ confirm.assert_called_once()
157
+ remember.assert_called_once_with()
158
+ use_global.assert_called_once_with(register=True, show_banner=False)
159
+
160
+ # Declining stops before any account is made.
161
+ monkeypatch.setattr("sys.argv", ["remoterf", "-r", "global"])
162
+ with mock.patch("remoterf.config.config._confirm_tos", return_value=False), \
163
+ mock.patch("remoterf.config.config.forget_tos_agreement"), \
164
+ mock.patch("remoterf.config.config.remember_tos_agreement") as remember, \
165
+ mock.patch.object(remoterf_cli, "print_client_banner"), \
166
+ mock.patch.object(remoterf_cli, "_use_global") as use_global:
167
+ assert remoterf_cli.main() == 1
168
+ remember.assert_not_called()
169
+ use_global.assert_not_called()
170
+
171
+
172
+ def _login_at(home, typed, *, https=False, pending=None):
173
+ from remoterf.core import app
174
+
175
+ account = mock.Mock(is_https_home=https, backend=None)
176
+ account.login_user.return_value = True
177
+ with mock.patch.object(app, "account", account), \
178
+ mock.patch.object(app, "home_name", home), \
179
+ mock.patch.object(app, "pending_username", pending), \
180
+ mock.patch.object(app, "local_capabilities",
181
+ return_value={"registration_policy": {}}), \
182
+ mock.patch.object(app, "_home_name", return_value=home), \
183
+ mock.patch.object(app, "_remember_login"), \
184
+ mock.patch.object(app, "_ask", side_effect=[typed, "secret"] if typed else ["secret"]) as ask:
185
+ if https:
186
+ account.backend = mock.Mock(capabilities={"registration_policy": {}})
187
+ assert app._account_command("login") is True
188
+ assert app.pending_username is None
189
+ return account.username, ask
190
+
191
+
192
+ def test_a_found_username_is_not_asked_again():
193
+ username, ask = _login_at("ucla", None, pending="ian")
194
+ assert username == "ian"
195
+ ask.assert_called_once_with("Password (Hidden)", hidden=True)
196
+
197
+
198
+ def test_the_full_account_name_works_at_its_own_home_only():
199
+ assert _login_at("ucla", "ian@ucla")[0] == "ian"
200
+ assert _login_at("ucla", "ian")[0] == "ian"
201
+ assert _login_at("otheruni", "ian@ucla")[0] == "ian@ucla" # not this lab's: no such name here
202
+ assert _login_at(None, "ian@global", https=True)[0] == "ian"
203
+ assert _login_at(None, "ian@ucla.edu", https=True)[0] == "ian@ucla.edu" # an email
@@ -1,93 +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_global_on_a_fresh_install_logs_in_to_global():
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, "_use_global", return_value=0) as use_global:
35
- assert remoterf_cli._login("global") == 0
36
- use_global.assert_called_once_with(register=False)
37
-
38
-
39
- def test_login_with_a_legacy_address_still_logs_in_there():
40
- from remoterf import remoterf_cli
41
-
42
- tmp, env = _fresh_home()
43
- with tmp, env, \
44
- mock.patch("remoterf.config.config.require_tos_for_login", return_value=True), \
45
- mock.patch.object(remoterf_cli, "_ensure_config_present", return_value=(True, "")), \
46
- mock.patch.object(remoterf_cli, "_register") as register, \
47
- mock.patch.object(remoterf_cli, "_account_shell", return_value=0) as shell:
48
- assert remoterf_cli._login(None) == 0
49
- shell.assert_called_once_with(register=False)
50
- register.assert_not_called()
51
-
52
-
53
- def test_register_without_a_code_makes_a_global_account(monkeypatch):
54
- from remoterf import remoterf_cli
55
-
56
- monkeypatch.setattr("builtins.input", lambda prompt="": "")
57
- with mock.patch("remoterf.config.config._confirm_tos", return_value=True), \
58
- mock.patch("remoterf.config.config.forget_tos_agreement"), \
59
- mock.patch("remoterf.config.config.remember_tos_agreement"), \
60
- mock.patch.object(remoterf_cli, "print_client_banner"), \
61
- mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global, \
62
- mock.patch.object(remoterf_cli, "_account_shell") as shell:
63
- assert remoterf_cli._register() == 0
64
- use_global.assert_called_once_with(register=True, show_banner=False)
65
- shell.assert_not_called()
66
-
67
-
68
- def test_register_global_asks_for_the_tos_like_register(monkeypatch):
69
- from remoterf import remoterf_cli
70
-
71
- for arg in ("global", "-global"):
72
- monkeypatch.setattr("sys.argv", ["remoterf", "-r", arg])
73
- with mock.patch("remoterf.config.config._confirm_tos", return_value=True) as confirm, \
74
- mock.patch("remoterf.config.config.forget_tos_agreement") as forget, \
75
- mock.patch("remoterf.config.config.remember_tos_agreement") as remember, \
76
- mock.patch.object(remoterf_cli, "print_client_banner"), \
77
- mock.patch.object(remoterf_cli, "_use_global", return_value=0) as use_global:
78
- assert remoterf_cli.main() == 0
79
- forget.assert_called_once_with()
80
- confirm.assert_called_once()
81
- remember.assert_called_once_with()
82
- use_global.assert_called_once_with(register=True, show_banner=False)
83
-
84
- # Declining stops before any account is made.
85
- monkeypatch.setattr("sys.argv", ["remoterf", "-r", "global"])
86
- with mock.patch("remoterf.config.config._confirm_tos", return_value=False), \
87
- mock.patch("remoterf.config.config.forget_tos_agreement"), \
88
- mock.patch("remoterf.config.config.remember_tos_agreement") as remember, \
89
- mock.patch.object(remoterf_cli, "print_client_banner"), \
90
- mock.patch.object(remoterf_cli, "_use_global") as use_global:
91
- assert remoterf_cli.main() == 1
92
- remember.assert_not_called()
93
- use_global.assert_not_called()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes