intuned-runtime 1.3.1__py3-none-any.whl → 1.3.2__py3-none-any.whl

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 (45) hide show
  1. intuned_cli/__init__.py +15 -24
  2. intuned_cli/commands/__init__.py +6 -1
  3. intuned_cli/commands/attempt_api_command.py +8 -0
  4. intuned_cli/commands/attempt_authsession_check_command.py +8 -0
  5. intuned_cli/commands/attempt_authsession_command.py +4 -4
  6. intuned_cli/commands/attempt_authsession_create_command.py +9 -1
  7. intuned_cli/commands/attempt_command.py +4 -4
  8. intuned_cli/commands/authsession_command.py +12 -0
  9. intuned_cli/commands/authsession_record_command.py +54 -0
  10. intuned_cli/commands/command.py +6 -4
  11. intuned_cli/commands/deploy_command.py +2 -0
  12. intuned_cli/commands/init_command.py +2 -0
  13. intuned_cli/commands/run_api_command.py +9 -1
  14. intuned_cli/commands/run_authsession_command.py +4 -4
  15. intuned_cli/commands/run_authsession_create_command.py +34 -4
  16. intuned_cli/commands/run_authsession_update_command.py +33 -4
  17. intuned_cli/commands/run_authsession_validate_command.py +32 -3
  18. intuned_cli/commands/run_command.py +4 -4
  19. intuned_cli/commands/save_command.py +2 -0
  20. intuned_cli/controller/__test__/test_api.py +159 -18
  21. intuned_cli/controller/__test__/test_authsession.py +497 -6
  22. intuned_cli/controller/api.py +40 -39
  23. intuned_cli/controller/authsession.py +213 -110
  24. intuned_cli/controller/deploy.py +3 -3
  25. intuned_cli/controller/save.py +47 -48
  26. intuned_cli/types.py +14 -0
  27. intuned_cli/utils/__test__/test_browser.py +132 -0
  28. intuned_cli/utils/__test__/test_traces.py +27 -0
  29. intuned_cli/utils/api_helpers.py +54 -5
  30. intuned_cli/utils/auth_session_helpers.py +42 -7
  31. intuned_cli/utils/backend.py +4 -1
  32. intuned_cli/utils/browser.py +63 -0
  33. intuned_cli/utils/error.py +14 -0
  34. intuned_cli/utils/exclusions.py +1 -0
  35. intuned_cli/utils/help.py +9 -0
  36. intuned_cli/utils/traces.py +31 -0
  37. intuned_cli/utils/wrapper.py +58 -0
  38. intuned_internal_cli/__init__.py +7 -0
  39. {intuned_runtime-1.3.1.dist-info → intuned_runtime-1.3.2.dist-info}/METADATA +4 -2
  40. {intuned_runtime-1.3.1.dist-info → intuned_runtime-1.3.2.dist-info}/RECORD +45 -37
  41. runtime/browser/launch_chromium.py +4 -0
  42. runtime/types/settings_types.py +13 -4
  43. {intuned_runtime-1.3.1.dist-info → intuned_runtime-1.3.2.dist-info}/WHEEL +0 -0
  44. {intuned_runtime-1.3.1.dist-info → intuned_runtime-1.3.2.dist-info}/entry_points.txt +0 -0
  45. {intuned_runtime-1.3.1.dist-info → intuned_runtime-1.3.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,14 +1,21 @@
1
+ import asyncio
1
2
  from dataclasses import dataclass
2
3
  from typing import Any
4
+ from typing import Awaitable
5
+ from typing import Callable
6
+ from typing import cast
3
7
  from typing import Generator
4
8
  from unittest.mock import AsyncMock
9
+ from unittest.mock import MagicMock
5
10
  from unittest.mock import Mock
6
11
  from unittest.mock import patch
7
12
 
8
13
  import pytest
9
14
 
15
+ from intuned_cli.controller.api import get_cli_run_options
10
16
  from intuned_cli.controller.authsession import execute_attempt_check_auth_session_cli
11
17
  from intuned_cli.controller.authsession import execute_attempt_create_auth_session_cli
18
+ from intuned_cli.controller.authsession import execute_record_auth_session_cli
12
19
  from intuned_cli.controller.authsession import execute_run_create_auth_session_cli
13
20
  from intuned_cli.controller.authsession import execute_run_update_auth_session_cli
14
21
  from intuned_cli.controller.authsession import execute_run_validate_auth_session_cli
@@ -20,8 +27,7 @@ from intuned_cli.utils.error import CLIError
20
27
  from runtime.errors.run_api_errors import AutomationError
21
28
  from runtime.types.run_types import ProxyConfig
22
29
  from runtime.types.run_types import StorageState
23
-
24
- from .test_api import AsyncContextManagerMock
30
+ from runtime.types.run_types import TracingDisabled
25
31
 
26
32
 
27
33
  def get_mock_console():
@@ -88,6 +94,8 @@ def shared_mocks() -> Generator[SharedMocks, Any, None]:
88
94
  class AttemptApiMocks:
89
95
  extendable_timeout: AsyncMock
90
96
  run_api: AsyncMock
97
+ cli_trace: AsyncMock
98
+ get_cli_run_options: AsyncMock
91
99
 
92
100
 
93
101
  @pytest.fixture
@@ -95,13 +103,19 @@ def attempt_api_mocks() -> Generator[AttemptApiMocks, Any, None]:
95
103
  """Mock dependencies for attempt_api tests."""
96
104
  _mock_timeout_patch = patch("intuned_cli.controller.authsession.extendable_timeout")
97
105
  _mock_run_api_patch = patch("intuned_cli.controller.authsession.run_api", new_callable=AsyncMock)
106
+ _mock_cli_trace_patch = patch("intuned_cli.controller.authsession.cli_trace")
107
+ _mock_get_cli_run_options_patch = patch(
108
+ "intuned_cli.controller.authsession.get_cli_run_options", new_callable=AsyncMock
109
+ )
98
110
 
99
111
  with (
100
112
  _mock_timeout_patch as mock_timeout,
101
113
  _mock_run_api_patch as mock_run_api,
114
+ _mock_cli_trace_patch as mock_cli_trace,
115
+ _mock_get_cli_run_options_patch as mock_get_cli_run_options,
102
116
  ):
103
117
  # Setup default return values
104
- mock_timeout.return_value = AsyncContextManagerMock()
118
+ mock_timeout.return_value = MagicMock()
105
119
 
106
120
  # Mock run_api to return success by default
107
121
  mock_result = Mock()
@@ -109,9 +123,21 @@ def attempt_api_mocks() -> Generator[AttemptApiMocks, Any, None]:
109
123
  mock_result.payload_to_append = []
110
124
  mock_run_api.return_value = mock_result
111
125
 
126
+ mock_cli_trace_return_value = MagicMock()
127
+ mock_cli_trace_return_value.__enter__.return_value = TracingDisabled()
128
+ mock_cli_trace.return_value = mock_cli_trace_return_value
129
+
130
+ async def get_cli_run_options_side_effect(*args: Any, **kwargs: Any):
131
+ kwargs["keep_browser_open"] = False
132
+ return await get_cli_run_options(*args, **kwargs)
133
+
134
+ mock_get_cli_run_options.side_effect = get_cli_run_options_side_effect
135
+
112
136
  yield AttemptApiMocks(
113
137
  extendable_timeout=mock_timeout,
114
138
  run_api=mock_run_api,
139
+ cli_trace=mock_cli_trace,
140
+ get_cli_run_options=mock_get_cli_run_options,
115
141
  )
116
142
 
117
143
 
@@ -184,6 +210,70 @@ def execute_cli_mocks() -> Generator[ExecuteCLIMocks, Any, None]:
184
210
  )
185
211
 
186
212
 
213
+ @dataclass
214
+ class RecordAuthSessionMocks:
215
+ launch_chromium: AsyncMock
216
+ page: Mock
217
+ browser_context: Mock
218
+ # asyncio_sleep: AsyncMock
219
+ resolve_sleep: Callable[[], Awaitable[None]]
220
+
221
+
222
+ @pytest.fixture
223
+ def record_auth_session_mocks() -> Generator[RecordAuthSessionMocks, Any, None]:
224
+ """Mock dependencies for execute_record_auth_session_cli tests."""
225
+ from asyncio import sleep as original_sleep
226
+
227
+ _mock_launch_chromium = patch("runtime.browser.launch_chromium")
228
+ _mock_asyncio_sleep = patch("asyncio.sleep", new_callable=AsyncMock)
229
+ _mock_asyncio_timeout = patch("asyncio.timeout")
230
+ _mock_get_storage_state = patch("intuned_cli.controller.authsession.get_storage_state", new_callable=AsyncMock)
231
+
232
+ with (
233
+ _mock_launch_chromium as mock_launch_chromium,
234
+ _mock_asyncio_sleep as _mock_asyncio_sleep,
235
+ _mock_asyncio_timeout as _mock_asyncio_timeout,
236
+ _mock_get_storage_state as _mock_get_storage_state,
237
+ ):
238
+ mock_launch_chromium_return_value = Mock()
239
+ mock_page = Mock()
240
+ mock_page.url = ""
241
+ mock_page.wait_for_load_state = AsyncMock(return_value=None)
242
+ mock_page.goto = AsyncMock(return_value=None)
243
+
244
+ mock_browser_context = Mock()
245
+ mock_browser_context.pages = [mock_page]
246
+
247
+ mock_launch_chromium_return_value.__aenter__ = AsyncMock(return_value=(mock_browser_context, mock_page))
248
+ mock_launch_chromium_return_value.__aexit__ = AsyncMock(return_value=None)
249
+ mock_launch_chromium.return_value = mock_launch_chromium_return_value
250
+
251
+ mock_asyncio_timeout_return_value = MagicMock()
252
+ mock_asyncio_timeout_return_value.__aenter__.return_value = MagicMock()
253
+ _mock_asyncio_timeout.__aenter__.return_value = mock_asyncio_timeout_return_value
254
+
255
+ sleep_future: asyncio.Future[Any] = cast(asyncio.Future[Any], None)
256
+
257
+ async def asyncio_sleep_side_effect(_):
258
+ nonlocal sleep_future
259
+ await original_sleep(0)
260
+ sleep_future = asyncio.Future()
261
+
262
+ _mock_asyncio_sleep.side_effect = asyncio_sleep_side_effect
263
+
264
+ async def resolve_sleep_future():
265
+ await original_sleep(0)
266
+ if sleep_future:
267
+ sleep_future.set_result(None)
268
+
269
+ yield RecordAuthSessionMocks(
270
+ launch_chromium=mock_launch_chromium,
271
+ page=mock_page,
272
+ browser_context=mock_browser_context,
273
+ resolve_sleep=resolve_sleep_future,
274
+ )
275
+
276
+
187
277
  class TestRunCheck:
188
278
  """Test suite for Auth Session controller functions."""
189
279
 
@@ -194,10 +284,71 @@ class TestRunCheck:
194
284
  auth=_get_empty_auth_session(),
195
285
  headless=False,
196
286
  timeout=6000,
287
+ trace_id=None,
288
+ keep_browser_open=False,
197
289
  )
198
290
 
199
291
  attempt_api_mocks.extendable_timeout.assert_called_once_with(6000)
200
292
 
293
+ @pytest.mark.asyncio
294
+ async def test_uses_trace_context_manager(self, attempt_api_mocks: AttemptApiMocks):
295
+ """Test that attempt_api uses the trace context manager."""
296
+ # Import inside test to avoid circular import issues
297
+
298
+ await run_check(
299
+ auth=_get_empty_auth_session(),
300
+ headless=False,
301
+ timeout=6000,
302
+ trace_id=None,
303
+ keep_browser_open=False,
304
+ )
305
+
306
+ attempt_api_mocks.cli_trace.assert_called_once_with(None)
307
+
308
+ attempt_api_mocks.cli_trace.reset_mock()
309
+ await run_check(
310
+ auth=_get_empty_auth_session(),
311
+ headless=False,
312
+ timeout=6000,
313
+ trace_id="trace-id",
314
+ keep_browser_open=False,
315
+ )
316
+
317
+ attempt_api_mocks.cli_trace.assert_called_once_with("trace-id")
318
+
319
+ @pytest.mark.asyncio
320
+ async def test_uses_get_cli_run_options(self, attempt_api_mocks: AttemptApiMocks):
321
+ """Test that attempt_api calls get_cli_run_options with the correct parameters."""
322
+ # Import inside test to avoid circular import issues
323
+
324
+ await run_check(
325
+ auth=_get_empty_auth_session(),
326
+ headless=False,
327
+ timeout=6000,
328
+ trace_id=None,
329
+ keep_browser_open=False,
330
+ )
331
+
332
+ attempt_api_mocks.get_cli_run_options.assert_called_once_with(
333
+ headless=False,
334
+ proxy=None,
335
+ keep_browser_open=False,
336
+ )
337
+
338
+ attempt_api_mocks.get_cli_run_options.reset_mock()
339
+ await run_check(
340
+ auth=_get_empty_auth_session(),
341
+ headless=True,
342
+ proxy="proxy",
343
+ timeout=6000,
344
+ trace_id=None,
345
+ keep_browser_open=True,
346
+ )
347
+ call_args = attempt_api_mocks.get_cli_run_options.call_args[1]
348
+ assert call_args["headless"] is True
349
+ assert call_args["proxy"] is not None
350
+ assert call_args["keep_browser_open"] is True
351
+
201
352
  @pytest.mark.asyncio
202
353
  async def test_calls_run_api_with_correct_parameters_and_parses_proxy(self, attempt_api_mocks: AttemptApiMocks):
203
354
  """Test that run_check calls run_api with correct parameters and parses proxy."""
@@ -216,6 +367,8 @@ class TestRunCheck:
216
367
  headless=False,
217
368
  timeout=999999999,
218
369
  proxy="proxy",
370
+ trace_id=None,
371
+ keep_browser_open=False,
219
372
  )
220
373
 
221
374
  mock_parse_proxy.assert_called_once_with("proxy")
@@ -237,6 +390,8 @@ class TestRunCheck:
237
390
  auth=StorageState(cookies=[], origins=[], session_storage=[]),
238
391
  headless=False,
239
392
  timeout=999999999,
393
+ trace_id=None,
394
+ keep_browser_open=False,
240
395
  )
241
396
 
242
397
  assert result_true is True
@@ -247,6 +402,8 @@ class TestRunCheck:
247
402
  auth=_get_empty_auth_session(),
248
403
  headless=False,
249
404
  timeout=999999999,
405
+ trace_id=None,
406
+ keep_browser_open=False,
250
407
  )
251
408
 
252
409
  assert result_false is False
@@ -262,6 +419,8 @@ class TestRunCheck:
262
419
  auth=StorageState(cookies=[], origins=[], session_storage=[]),
263
420
  headless=False,
264
421
  timeout=999999999,
422
+ trace_id=None,
423
+ keep_browser_open=False,
265
424
  )
266
425
 
267
426
  # Test with AutomationError - should return False, not throw
@@ -272,10 +431,43 @@ class TestRunCheck:
272
431
  auth=StorageState(cookies=[], origins=[], session_storage=[]),
273
432
  headless=False,
274
433
  timeout=999999999,
434
+ trace_id=None,
435
+ keep_browser_open=False,
275
436
  )
276
437
 
277
438
 
278
439
  class TestRunCheckWithRetries:
440
+ @pytest.mark.asyncio
441
+ async def test_calls_check_with_trace_id_correctly(self, with_retries_mocks: WithRetriesMocks):
442
+ """Test that run_check_with_retries calls run_check with the correct trace_id."""
443
+
444
+ await run_check_with_retries(
445
+ auth=_get_empty_auth_session(),
446
+ retries=10,
447
+ headless=False,
448
+ timeout=999999999,
449
+ trace=False,
450
+ keep_browser_open=False,
451
+ )
452
+
453
+ with_retries_mocks.run_check.assert_called()
454
+ call_args = with_retries_mocks.run_check.call_args
455
+ assert call_args[1]["trace_id"] is None
456
+
457
+ with_retries_mocks.run_check.reset_mock()
458
+
459
+ await run_check_with_retries(
460
+ auth=_get_empty_auth_session(),
461
+ retries=10,
462
+ headless=False,
463
+ timeout=999999999,
464
+ trace=True,
465
+ keep_browser_open=False,
466
+ )
467
+ with_retries_mocks.run_check.assert_called()
468
+ call_args = with_retries_mocks.run_check.call_args
469
+ assert isinstance(call_args[1]["trace_id"], str)
470
+
279
471
  @pytest.mark.asyncio
280
472
  async def test_retries_the_check_if_it_fails(self, with_retries_mocks: WithRetriesMocks):
281
473
  """Test that run_check_with_retries retries the check if it fails."""
@@ -286,6 +478,8 @@ class TestRunCheckWithRetries:
286
478
  retries=10,
287
479
  headless=False,
288
480
  timeout=999999999,
481
+ trace=False,
482
+ keep_browser_open=False,
289
483
  )
290
484
 
291
485
  assert result is True
@@ -301,6 +495,8 @@ class TestRunCheckWithRetries:
301
495
  retries=10,
302
496
  headless=False,
303
497
  timeout=999999999,
498
+ trace=False,
499
+ keep_browser_open=False,
304
500
  )
305
501
 
306
502
  assert result is False
@@ -318,6 +514,8 @@ class TestRunCheckWithRetries:
318
514
  retries=10,
319
515
  headless=False,
320
516
  timeout=999999999,
517
+ trace=False,
518
+ keep_browser_open=False,
321
519
  )
322
520
 
323
521
  assert result is True
@@ -335,6 +533,8 @@ class TestRunCheckWithRetries:
335
533
  retries=10,
336
534
  headless=False,
337
535
  timeout=999999999,
536
+ trace=False,
537
+ keep_browser_open=False,
338
538
  )
339
539
 
340
540
  with_retries_mocks.run_check.assert_called_once()
@@ -350,10 +550,71 @@ class TestRunCreate:
350
550
  auth_session_input={},
351
551
  headless=False,
352
552
  timeout=6000,
553
+ trace_id=None,
554
+ keep_browser_open=False,
353
555
  )
354
556
 
355
557
  attempt_api_mocks.extendable_timeout.assert_called_once_with(6000)
356
558
 
559
+ @pytest.mark.asyncio
560
+ async def test_uses_trace_context_manager(self, attempt_api_mocks: AttemptApiMocks):
561
+ """Test that run_create uses the trace context manager."""
562
+ attempt_api_mocks.run_api.return_value = _create_success_result("session")
563
+
564
+ await run_create(
565
+ auth_session_input={},
566
+ headless=False,
567
+ timeout=6000,
568
+ trace_id=None,
569
+ keep_browser_open=False,
570
+ )
571
+
572
+ attempt_api_mocks.cli_trace.assert_called_once_with(None)
573
+
574
+ attempt_api_mocks.cli_trace.reset_mock()
575
+ await run_create(
576
+ auth_session_input={},
577
+ headless=False,
578
+ timeout=6000,
579
+ trace_id="trace-id",
580
+ keep_browser_open=False,
581
+ )
582
+
583
+ attempt_api_mocks.cli_trace.assert_called_once_with("trace-id")
584
+
585
+ @pytest.mark.asyncio
586
+ async def test_uses_get_cli_run_options(self, attempt_api_mocks: AttemptApiMocks):
587
+ """Test that run_create calls get_cli_run_options with the correct parameters."""
588
+ attempt_api_mocks.run_api.return_value = _create_success_result("session")
589
+
590
+ await run_create(
591
+ auth_session_input={},
592
+ headless=False,
593
+ timeout=6000,
594
+ trace_id=None,
595
+ keep_browser_open=False,
596
+ )
597
+
598
+ attempt_api_mocks.get_cli_run_options.assert_called_once_with(
599
+ headless=False,
600
+ proxy=None,
601
+ keep_browser_open=False,
602
+ )
603
+
604
+ attempt_api_mocks.get_cli_run_options.reset_mock()
605
+ await run_create(
606
+ auth_session_input={},
607
+ headless=True,
608
+ proxy="proxy",
609
+ timeout=6000,
610
+ trace_id=None,
611
+ keep_browser_open=True,
612
+ )
613
+ call_args = attempt_api_mocks.get_cli_run_options.call_args[1]
614
+ assert call_args["headless"] is True
615
+ assert call_args["proxy"] is not None
616
+ assert call_args["keep_browser_open"] is True
617
+
357
618
  @pytest.mark.asyncio
358
619
  async def test_calls_run_api_with_correct_parameters_and_parses_proxy(self, attempt_api_mocks: AttemptApiMocks):
359
620
  """Test that run_create calls run_api with correct parameters and parses proxy."""
@@ -372,6 +633,8 @@ class TestRunCreate:
372
633
  headless=False,
373
634
  timeout=999999999,
374
635
  proxy="proxy",
636
+ trace_id=None,
637
+ keep_browser_open=False,
375
638
  )
376
639
 
377
640
  mock_parse_proxy.assert_called_once_with("proxy")
@@ -394,6 +657,8 @@ class TestRunCreate:
394
657
  auth_session_input={},
395
658
  headless=False,
396
659
  timeout=999999999,
660
+ trace_id=None,
661
+ keep_browser_open=False,
397
662
  )
398
663
 
399
664
  assert storage_state == "session"
@@ -409,10 +674,45 @@ class TestRunCreate:
409
674
  auth_session_input={},
410
675
  headless=False,
411
676
  timeout=999999999,
677
+ trace_id=None,
678
+ keep_browser_open=False,
412
679
  )
413
680
 
414
681
 
415
682
  class TestRunCreateWithRetries:
683
+ @pytest.mark.asyncio
684
+ async def test_calls_create_with_trace_id_correctly(self, with_retries_mocks: WithRetriesMocks):
685
+ """Test that run_create_with_retries calls run_create with the correct trace_id."""
686
+
687
+ await run_create_with_retries(
688
+ auth_session_id="testId",
689
+ auth_session_input={},
690
+ retries=10,
691
+ headless=False,
692
+ timeout=999999999,
693
+ trace=False,
694
+ keep_browser_open=False,
695
+ )
696
+
697
+ with_retries_mocks.run_create.assert_called()
698
+ call_args = with_retries_mocks.run_create.call_args
699
+ assert call_args[1]["trace_id"] is None
700
+
701
+ with_retries_mocks.run_create.reset_mock()
702
+
703
+ await run_create_with_retries(
704
+ auth_session_id="testId",
705
+ auth_session_input={},
706
+ retries=10,
707
+ headless=False,
708
+ timeout=999999999,
709
+ trace=True,
710
+ keep_browser_open=False,
711
+ )
712
+ with_retries_mocks.run_create.assert_called()
713
+ call_args = with_retries_mocks.run_create.call_args
714
+ assert isinstance(call_args[1]["trace_id"], str)
715
+
416
716
  @pytest.mark.asyncio
417
717
  async def test_saves_the_auth_session_instance(self, with_retries_mocks: WithRetriesMocks):
418
718
  """Test that run_create_with_retries saves the auth session instance."""
@@ -427,6 +727,8 @@ class TestRunCreateWithRetries:
427
727
  retries=1,
428
728
  headless=False,
429
729
  timeout=30,
730
+ trace=False,
731
+ keep_browser_open=False,
430
732
  )
431
733
 
432
734
  assert with_retries_mocks.run_create.call_count == 1
@@ -449,6 +751,8 @@ class TestRunCreateWithRetries:
449
751
  retries=10,
450
752
  headless=False,
451
753
  timeout=30,
754
+ trace=False,
755
+ keep_browser_open=False,
452
756
  )
453
757
 
454
758
  assert with_retries_mocks.run_create.call_count == 2
@@ -465,6 +769,8 @@ class TestRunCreateWithRetries:
465
769
  retries=3,
466
770
  headless=False,
467
771
  timeout=30,
772
+ trace=False,
773
+ keep_browser_open=False,
468
774
  )
469
775
 
470
776
  assert with_retries_mocks.run_create.call_count == 3
@@ -482,6 +788,8 @@ class TestRunCreateWithRetries:
482
788
  retries=10,
483
789
  headless=False,
484
790
  timeout=30,
791
+ trace=False,
792
+ keep_browser_open=False,
485
793
  )
486
794
 
487
795
  with_retries_mocks.run_create.assert_called_once()
@@ -498,6 +806,8 @@ class TestExecuteAuthSessionValidateCLI:
498
806
  create_retries=1,
499
807
  headless=False,
500
808
  timeout=30,
809
+ trace=False,
810
+ keep_browser_open=False,
501
811
  )
502
812
 
503
813
  shared_mocks.assert_api_file_exists.assert_called_once_with("auth-sessions", "check")
@@ -516,6 +826,8 @@ class TestExecuteAuthSessionValidateCLI:
516
826
  create_retries=1,
517
827
  headless=False,
518
828
  timeout=30,
829
+ trace=False,
830
+ keep_browser_open=False,
519
831
  )
520
832
 
521
833
  assert execute_cli_mocks.run_check_with_retries.call_count == 1
@@ -535,6 +847,8 @@ class TestExecuteAuthSessionValidateCLI:
535
847
  create_retries=1,
536
848
  headless=False,
537
849
  timeout=30,
850
+ trace=False,
851
+ keep_browser_open=False,
538
852
  )
539
853
 
540
854
  @pytest.mark.asyncio
@@ -554,6 +868,8 @@ class TestExecuteAuthSessionValidateCLI:
554
868
  create_retries=1,
555
869
  headless=False,
556
870
  timeout=30,
871
+ trace=False,
872
+ keep_browser_open=False,
557
873
  )
558
874
 
559
875
  # Should call assert for both check and create APIs
@@ -578,6 +894,8 @@ class TestExecuteAuthSessionValidateCLI:
578
894
  create_retries=1,
579
895
  headless=False,
580
896
  timeout=30,
897
+ trace=False,
898
+ keep_browser_open=False,
581
899
  )
582
900
 
583
901
  @pytest.mark.asyncio
@@ -598,6 +916,8 @@ class TestExecuteAuthSessionValidateCLI:
598
916
  create_retries=1,
599
917
  headless=False,
600
918
  timeout=30,
919
+ trace=False,
920
+ keep_browser_open=False,
601
921
  )
602
922
 
603
923
  @pytest.mark.asyncio
@@ -618,6 +938,8 @@ class TestExecuteAuthSessionValidateCLI:
618
938
  create_retries=1,
619
939
  headless=False,
620
940
  timeout=30,
941
+ trace=False,
942
+ keep_browser_open=False,
621
943
  )
622
944
 
623
945
  @pytest.mark.asyncio
@@ -636,6 +958,8 @@ class TestExecuteAuthSessionValidateCLI:
636
958
  create_retries=1,
637
959
  headless=False,
638
960
  timeout=30,
961
+ trace=False,
962
+ keep_browser_open=False,
639
963
  )
640
964
 
641
965
  assert result == auth
@@ -655,6 +979,8 @@ class TestExecuteAuthSessionCreateCLI:
655
979
  create_retries=1,
656
980
  headless=False,
657
981
  timeout=30,
982
+ trace=False,
983
+ keep_browser_open=False,
658
984
  )
659
985
 
660
986
  # Should assert both create and check API files exist
@@ -674,6 +1000,8 @@ class TestExecuteAuthSessionCreateCLI:
674
1000
  create_retries=1,
675
1001
  headless=False,
676
1002
  timeout=30,
1003
+ trace=False,
1004
+ keep_browser_open=False,
677
1005
  )
678
1006
 
679
1007
  @pytest.mark.asyncio
@@ -692,6 +1020,8 @@ class TestExecuteAuthSessionCreateCLI:
692
1020
  create_retries=1,
693
1021
  headless=False,
694
1022
  timeout=30,
1023
+ trace=False,
1024
+ keep_browser_open=False,
695
1025
  )
696
1026
 
697
1027
  @pytest.mark.asyncio
@@ -709,6 +1039,8 @@ class TestExecuteAuthSessionCreateCLI:
709
1039
  create_retries=1,
710
1040
  headless=False,
711
1041
  timeout=30,
1042
+ trace=False,
1043
+ keep_browser_open=False,
712
1044
  )
713
1045
 
714
1046
  # run_create_with_retries is tested to save in its own tests, so just verify it was called here
@@ -731,6 +1063,8 @@ class TestExecuteAuthSessionCreateCLI:
731
1063
  create_retries=1,
732
1064
  headless=False,
733
1065
  timeout=30,
1066
+ trace=False,
1067
+ keep_browser_open=False,
734
1068
  )
735
1069
 
736
1070
  # Verify that store was called with the provided id
@@ -754,6 +1088,8 @@ class TestExecuteAuthSessionUpdateCLI:
754
1088
  create_retries=1,
755
1089
  headless=False,
756
1090
  timeout=30,
1091
+ trace=False,
1092
+ keep_browser_open=False,
757
1093
  )
758
1094
 
759
1095
  @pytest.mark.asyncio
@@ -770,6 +1106,8 @@ class TestExecuteAuthSessionUpdateCLI:
770
1106
  create_retries=1,
771
1107
  headless=False,
772
1108
  timeout=30,
1109
+ trace=False,
1110
+ keep_browser_open=False,
773
1111
  )
774
1112
 
775
1113
  # Should use the provided input data
@@ -780,9 +1118,10 @@ class TestExecuteAuthSessionUpdateCLI:
780
1118
  create_retries=1,
781
1119
  headless=False,
782
1120
  timeout=30,
783
- proxy=None,
784
1121
  metadata=mock_metadata,
785
1122
  log=False,
1123
+ trace=False,
1124
+ keep_browser_open=False,
786
1125
  )
787
1126
 
788
1127
  @pytest.mark.asyncio
@@ -798,6 +1137,8 @@ class TestExecuteAuthSessionUpdateCLI:
798
1137
  create_retries=1,
799
1138
  headless=False,
800
1139
  timeout=30,
1140
+ trace=False,
1141
+ keep_browser_open=False,
801
1142
  )
802
1143
 
803
1144
  # Should use the provided input data
@@ -814,6 +1155,8 @@ class TestExecuteAttemptAuthSessionCLI:
814
1155
  id="testId",
815
1156
  headless=False,
816
1157
  timeout=30,
1158
+ trace=False,
1159
+ keep_browser_open=False,
817
1160
  )
818
1161
 
819
1162
  shared_mocks.assert_api_file_exists.assert_called_once_with("auth-sessions", "check")
@@ -827,6 +1170,8 @@ class TestExecuteAttemptAuthSessionCLI:
827
1170
  id="testId",
828
1171
  headless=False,
829
1172
  timeout=30,
1173
+ trace=False,
1174
+ keep_browser_open=False,
830
1175
  )
831
1176
 
832
1177
  @pytest.mark.asyncio
@@ -839,6 +1184,8 @@ class TestExecuteAttemptAuthSessionCLI:
839
1184
  id="testId",
840
1185
  headless=False,
841
1186
  timeout=30,
1187
+ trace=False,
1188
+ keep_browser_open=False,
842
1189
  )
843
1190
 
844
1191
 
@@ -851,6 +1198,8 @@ class TestExecuteAttemptCreateAuthSessionCLI:
851
1198
  input_data={},
852
1199
  headless=False,
853
1200
  timeout=30,
1201
+ trace=False,
1202
+ keep_browser_open=False,
854
1203
  )
855
1204
 
856
1205
  shared_mocks.assert_api_file_exists.assert_called_once_with("auth-sessions", "create")
@@ -868,6 +1217,8 @@ class TestExecuteAttemptCreateAuthSessionCLI:
868
1217
  input_data={},
869
1218
  headless=False,
870
1219
  timeout=30,
1220
+ trace=False,
1221
+ keep_browser_open=False,
871
1222
  )
872
1223
 
873
1224
  @pytest.mark.asyncio
@@ -879,15 +1230,17 @@ class TestExecuteAttemptCreateAuthSessionCLI:
879
1230
  input_data={},
880
1231
  headless=False,
881
1232
  timeout=30,
1233
+ trace=False,
1234
+ keep_browser_open=False,
882
1235
  )
883
1236
 
884
1237
  execute_cli_mocks.run_create_with_retries.assert_called_once()
885
1238
  call_args = execute_cli_mocks.run_create_with_retries.call_args.kwargs
886
1239
  assert call_args["auth_session_input"] == {}
887
- assert call_args["retries"] == 1
1240
+ assert call_args["retries"] is None
888
1241
  assert call_args["headless"] is False
889
1242
  assert call_args["timeout"] == 30
890
- assert call_args["proxy"] is None
1243
+ assert call_args.get("proxy") is None
891
1244
 
892
1245
  @pytest.mark.asyncio
893
1246
  async def test_uses_auth_session_id_to_save_if_provided(self, execute_cli_mocks: ExecuteCLIMocks):
@@ -899,9 +1252,147 @@ class TestExecuteAttemptCreateAuthSessionCLI:
899
1252
  input_data={},
900
1253
  headless=False,
901
1254
  timeout=30,
1255
+ trace=False,
1256
+ keep_browser_open=False,
902
1257
  )
903
1258
 
904
1259
  # Verify that store was called with the provided id
905
1260
  execute_cli_mocks.run_create_with_retries.assert_called_once()
906
1261
  call_args = execute_cli_mocks.run_create_with_retries.call_args.kwargs
907
1262
  assert call_args["auth_session_id"] == "customId"
1263
+
1264
+
1265
+ class TestExecuteRecordAuthSessionCLI:
1266
+ @pytest.mark.asyncio
1267
+ async def test_launches_browser_with_app_mode(
1268
+ self,
1269
+ record_auth_session_mocks: RecordAuthSessionMocks,
1270
+ with_retries_mocks: WithRetriesMocks,
1271
+ execute_cli_mocks: ExecuteCLIMocks,
1272
+ ):
1273
+ """Test that execute_record_auth_session_cli launches browser with app mode."""
1274
+ record_auth_session_mocks.page.url = "url"
1275
+ await execute_record_auth_session_cli(
1276
+ start_url="url",
1277
+ finish_url="url",
1278
+ headless=False,
1279
+ timeout=30,
1280
+ trace=False,
1281
+ keep_browser_open=False,
1282
+ )
1283
+
1284
+ record_auth_session_mocks.launch_chromium.assert_called_once()
1285
+ call_args = record_auth_session_mocks.launch_chromium.call_args.kwargs
1286
+ assert call_args["app_mode_initial_url"] == "url"
1287
+ assert call_args["headless"] is False
1288
+
1289
+ @pytest.mark.asyncio
1290
+ async def test_navigates_to_start_url_if_pages_does_not_load_with_it(
1291
+ self,
1292
+ record_auth_session_mocks: RecordAuthSessionMocks,
1293
+ with_retries_mocks: WithRetriesMocks,
1294
+ execute_cli_mocks: ExecuteCLIMocks,
1295
+ ):
1296
+ record_auth_session_mocks.page.url = "finish_url"
1297
+
1298
+ """Test that execute_record_auth_session_cli navigates to start_url if page does not load with it."""
1299
+ await execute_record_auth_session_cli(
1300
+ start_url="start_url",
1301
+ finish_url="finish_url",
1302
+ headless=False,
1303
+ timeout=30,
1304
+ trace=False,
1305
+ keep_browser_open=False,
1306
+ )
1307
+
1308
+ record_auth_session_mocks.page.goto.assert_called_once_with("start_url")
1309
+
1310
+ @pytest.mark.asyncio
1311
+ async def test_waits_for_finish_url(
1312
+ self,
1313
+ record_auth_session_mocks: RecordAuthSessionMocks,
1314
+ with_retries_mocks: WithRetriesMocks,
1315
+ execute_cli_mocks: ExecuteCLIMocks,
1316
+ ):
1317
+ """Test that execute_record_auth_session_cli waits for finish_url."""
1318
+ import asyncio
1319
+
1320
+ record_auth_session_mocks.page.url = "start_url"
1321
+
1322
+ record_task = asyncio.create_task(
1323
+ execute_record_auth_session_cli(
1324
+ start_url="start_url",
1325
+ finish_url="finish_url",
1326
+ headless=False,
1327
+ timeout=30,
1328
+ trace=False,
1329
+ keep_browser_open=False,
1330
+ )
1331
+ )
1332
+
1333
+ # simulate time passing without URL changing
1334
+ await record_auth_session_mocks.resolve_sleep()
1335
+ await record_auth_session_mocks.resolve_sleep()
1336
+ await record_auth_session_mocks.resolve_sleep()
1337
+ assert record_task.done() is False
1338
+
1339
+ record_auth_session_mocks.page.url = "finish_url"
1340
+
1341
+ await record_auth_session_mocks.resolve_sleep()
1342
+
1343
+ await record_task
1344
+
1345
+ @pytest.mark.asyncio
1346
+ async def test_throws_if_timeout(
1347
+ self,
1348
+ record_auth_session_mocks: RecordAuthSessionMocks,
1349
+ with_retries_mocks: WithRetriesMocks,
1350
+ execute_cli_mocks: ExecuteCLIMocks,
1351
+ ):
1352
+ """Test that execute_record_auth_session_cli throws if timeout."""
1353
+ import asyncio as mocked_asyncio
1354
+
1355
+ record_task = asyncio.create_task(
1356
+ execute_record_auth_session_cli(
1357
+ start_url="start_url",
1358
+ finish_url="finish_url",
1359
+ headless=False,
1360
+ timeout=30,
1361
+ trace=False,
1362
+ keep_browser_open=False,
1363
+ )
1364
+ )
1365
+
1366
+ # simulate time passing without URL changing
1367
+ await record_auth_session_mocks.resolve_sleep()
1368
+ await record_auth_session_mocks.resolve_sleep()
1369
+ await record_auth_session_mocks.resolve_sleep()
1370
+ assert record_task.done() is False
1371
+
1372
+ cast(AsyncMock, mocked_asyncio.sleep).side_effect = asyncio.TimeoutError()
1373
+
1374
+ await record_auth_session_mocks.resolve_sleep()
1375
+
1376
+ with pytest.raises(CLIError, match="Timeout"):
1377
+ await record_task
1378
+
1379
+ @pytest.mark.asyncio
1380
+ async def test_stores_auth_session_and_validates_if_successful(
1381
+ self,
1382
+ record_auth_session_mocks: RecordAuthSessionMocks,
1383
+ with_retries_mocks: WithRetriesMocks,
1384
+ execute_cli_mocks: ExecuteCLIMocks,
1385
+ ):
1386
+ """Test that execute_record_auth_session_cli stores auth session and validates it if successful."""
1387
+ record_auth_session_mocks.page.url = "url"
1388
+ await execute_record_auth_session_cli(
1389
+ start_url="url",
1390
+ finish_url="url",
1391
+ headless=False,
1392
+ timeout=30,
1393
+ trace=False,
1394
+ keep_browser_open=False,
1395
+ )
1396
+
1397
+ with_retries_mocks.store_auth_session_instance.assert_called_once()
1398
+ execute_cli_mocks.run_check_with_retries.assert_called_once()