diff --git a/python/packages/jumpstarter-cli-common/pyproject.toml b/python/packages/jumpstarter-cli-common/pyproject.toml index 4b6512e92..4dbc07244 100644 --- a/python/packages/jumpstarter-cli-common/pyproject.toml +++ b/python/packages/jumpstarter-cli-common/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "pydantic>=2.8.2", "click>=8.1.7.2", "authlib>=1.4.1", + "certifi>=2024.2.2", "truststore>=0.10.1", "joserfc>=1.0.3", "yarl>=1.18.3", diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/create_test.py b/python/packages/jumpstarter-cli/jumpstarter_cli/create_test.py index b9d3837e7..a0b760944 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/create_test.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/create_test.py @@ -15,6 +15,7 @@ def test_create_lease_passes_exporter_name_to_config(): with patch("jumpstarter_cli.create.model_print") as model_print: # Skip Click config loading wrapper and call the command body directly. + assert create_lease.callback is not None inspect.unwrap(create_lease.callback)( config=config, selector=None, @@ -38,6 +39,7 @@ def test_create_lease_passes_exporter_name_to_config(): def test_create_lease_requires_selector_or_name(): + assert create_lease.callback is not None with pytest.raises(click.UsageError, match="one of --selector/-l or --name/-n is required"): inspect.unwrap(create_lease.callback)( config=Mock(), @@ -57,6 +59,7 @@ def test_create_lease_passes_tags_to_config(): config.create_lease.return_value = lease with patch("jumpstarter_cli.create.model_print"): + assert create_lease.callback is not None inspect.unwrap(create_lease.callback)( config=config, selector="board=rpi4", @@ -84,6 +87,7 @@ def test_create_lease_empty_tags_passes_none(): config.create_lease.return_value = lease with patch("jumpstarter_cli.create.model_print"): + assert create_lease.callback is not None inspect.unwrap(create_lease.callback)( config=config, selector="board=rpi4", @@ -106,6 +110,7 @@ def test_create_lease_empty_tags_passes_none(): def test_create_lease_invalid_tag_format(): + assert create_lease.callback is not None with pytest.raises(click.UsageError, match="Invalid tag format"): inspect.unwrap(create_lease.callback)( config=Mock(), diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/delete_batch_test.py b/python/packages/jumpstarter-cli/jumpstarter_cli/delete_batch_test.py index edfcdf8fa..3c7900fd2 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/delete_batch_test.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/delete_batch_test.py @@ -9,6 +9,7 @@ class TestBatchDeleteLeases: def test_delete_multiple_leases(self): config = Mock() + assert delete_leases.callback is not None delete_leases.callback.__wrapped__.__wrapped__( config=config, names=("lease1", "lease2", "lease3"), @@ -24,6 +25,7 @@ def test_delete_multiple_leases(self): def test_delete_zero_names_no_flags_raises_error(self): config = Mock() + assert delete_leases.callback is not None with pytest.raises(click.ClickException, match="must be specified"): delete_leases.callback.__wrapped__.__wrapped__( config=config, @@ -38,6 +40,7 @@ def test_delete_with_output_name(self): from jumpstarter_cli_common.opt import OutputMode config = Mock() + assert delete_leases.callback is not None delete_leases.callback.__wrapped__.__wrapped__( config=config, names=("lease1", "lease2"), diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/delete_test.py b/python/packages/jumpstarter-cli/jumpstarter_cli/delete_test.py index 6c0261afc..66701b3ef 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/delete_test.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/delete_test.py @@ -34,6 +34,7 @@ def _make_config(leases): return config +assert delete_leases.callback is not None _delete_leases = delete_leases.callback.__wrapped__.__wrapped__ diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/get_test.py b/python/packages/jumpstarter-cli/jumpstarter_cli/get_test.py index 396c49198..7f28674ae 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/get_test.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/get_test.py @@ -220,6 +220,7 @@ def test_get_exporters_calls_list_exporters(self): from jumpstarter_cli.get import get_exporters + assert get_exporters.callback is not None with patch("jumpstarter_cli.get.model_print"): get_exporters.callback.__wrapped__.__wrapped__( config=config, selector=None, output="text", with_options=[] @@ -239,6 +240,7 @@ def test_get_leases_calls_list_leases(self): from jumpstarter_cli.get import get_leases + assert get_leases.callback is not None with patch("jumpstarter_cli.get.model_print"): get_leases.callback.__wrapped__.__wrapped__( config=config, selector=None, output="text", show_all=False, all_clients=False, tag_filter=None, @@ -398,6 +400,7 @@ def test_get_leases_accepts_short_a_flag(self): assert "-a" in all_option.opts +assert get_leases.callback is not None _unwrapped_get_leases = get_leases.callback.__wrapped__.__wrapped__ diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py b/python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py index 78819415e..a2d9db013 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/shell_test.py @@ -106,7 +106,7 @@ async def test_shell_warns_when_expired_token_prevents_cleanup_on_normal_exit(): async def lease_async(selector, exporter_name, lease_name, duration, portal, acquisition_timeout): yield lease - config.lease_async = lease_async + config.lease_async = lease_async # ty: ignore[invalid-assignment] async def fake_monitor(_config, _lease, _cancel_scope, token_state=None): if token_state is not None: @@ -140,6 +140,7 @@ def test_shell_requires_selector_or_name_when_no_leases(): config = Mock(spec=ClientConfigV1Alpha1) config.metadata = type("Metadata", (), {"name": "test-client"})() config.list_leases = AsyncMock(return_value=_make_lease_list([])) + assert shell.callback is not None with pytest.raises(click.UsageError, match="no active leases found"): shell.callback.__wrapped__.__wrapped__( config=config, @@ -157,6 +158,7 @@ def test_shell_requires_selector_or_name_when_no_leases(): def test_shell_allows_existing_lease_name_without_selector_or_name(): + assert shell.callback is not None with ( patch("jumpstarter_cli.shell.anyio.run", return_value=0), patch("jumpstarter_cli.shell.sys.exit") as mock_exit, @@ -181,6 +183,7 @@ def test_shell_allows_existing_lease_name_without_selector_or_name(): def test_shell_auto_connects_single_lease(): config = Mock(spec=ClientConfigV1Alpha1) config.metadata = type("Metadata", (), {"name": "test-client"})() + assert shell.callback is not None with ( patch("jumpstarter_cli.shell.anyio.run", side_effect=["my-only-lease", 0]) as mock_run, patch("jumpstarter_cli.shell.sys.exit") as mock_exit, @@ -211,6 +214,7 @@ def test_shell_no_leases_shows_guidance(): config = Mock(spec=ClientConfigV1Alpha1) config.metadata = type("Metadata", (), {"name": "test-client"})() config.list_leases = AsyncMock(return_value=_make_lease_list([])) + assert shell.callback is not None with pytest.raises(click.UsageError, match="no active leases found"): shell.callback.__wrapped__.__wrapped__( config=config, @@ -247,6 +251,7 @@ def test_shell_multi_lease_no_tty_error(): config = Mock(spec=ClientConfigV1Alpha1) config.metadata = type("Metadata", (), {"name": "test-client"})() config.list_leases = AsyncMock(return_value=_make_lease_list(["lease-a", "lease-b"])) + assert shell.callback is not None with ( patch("jumpstarter_cli.shell.sys.stdin") as mock_stdin, pytest.raises(click.UsageError, match="lease-a"), @@ -286,6 +291,7 @@ def test_shell_no_own_leases_among_others(): config = Mock(spec=ClientConfigV1Alpha1) config.metadata = type("Metadata", (), {"name": "test-client"})() config.list_leases = AsyncMock(return_value=lease_list) + assert shell.callback is not None with pytest.raises(click.UsageError, match="no active leases found"): shell.callback.__wrapped__.__wrapped__( config=config, @@ -303,6 +309,7 @@ def test_shell_no_own_leases_among_others(): def test_shell_allows_env_lease_without_selector_or_name(): + assert shell.callback is not None with ( patch("jumpstarter_cli.shell.anyio.run", return_value=0), patch("jumpstarter_cli.shell.sys.exit") as mock_exit, @@ -937,7 +944,7 @@ async def test_exits_gracefully_when_lease_ended_and_exception_group(self): async def lease_async(selector, exporter_name, lease_name, duration, portal, acquisition_timeout): yield lease - config.lease_async = lease_async + config.lease_async = lease_async # ty: ignore[invalid-assignment] async def fake_run_shell(*_args): raise BaseExceptionGroup("test", [RuntimeError("simulated cancellation")]) @@ -966,7 +973,7 @@ async def test_raises_offline_error_when_lease_not_ended_and_exception_group(sel async def lease_async(selector, exporter_name, lease_name, duration, portal, acquisition_timeout): yield lease - config.lease_async = lease_async + config.lease_async = lease_async # ty: ignore[invalid-assignment] async def fake_run_shell(*_args): raise BaseExceptionGroup("test", [RuntimeError("connection broken")]) diff --git a/python/packages/jumpstarter-cli/jumpstarter_cli/update_test.py b/python/packages/jumpstarter-cli/jumpstarter_cli/update_test.py index ba42f40bf..1bbdae8b4 100644 --- a/python/packages/jumpstarter-cli/jumpstarter_cli/update_test.py +++ b/python/packages/jumpstarter-cli/jumpstarter_cli/update_test.py @@ -15,6 +15,7 @@ def test_update_lease_with_to_client(): config.update_lease.return_value = lease with patch("jumpstarter_cli.update.model_print") as model_print: + assert update_lease.callback is not None inspect.unwrap(update_lease.callback)( config=config, name="my-lease", @@ -40,6 +41,7 @@ def test_update_lease_with_duration_and_to_client(): config.update_lease.return_value = lease with patch("jumpstarter_cli.update.model_print") as model_print: + assert update_lease.callback is not None inspect.unwrap(update_lease.callback)( config=config, name="my-lease", @@ -64,6 +66,7 @@ def test_update_lease_without_to_client(): config.update_lease.return_value = lease with patch("jumpstarter_cli.update.model_print") as model_print: + assert update_lease.callback is not None inspect.unwrap(update_lease.callback)( config=config, name="my-lease", @@ -83,6 +86,7 @@ def test_update_lease_without_to_client(): def test_update_lease_requires_at_least_one_option(): + assert update_lease.callback is not None with pytest.raises(click.UsageError, match="At least one of"): inspect.unwrap(update_lease.callback)( config=Mock(), diff --git a/python/pyproject.toml b/python/pyproject.toml index 78623a341..59a07b500 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -58,25 +58,25 @@ hatch-pin-jumpstarter = { workspace = true } [dependency-groups] docs = [ - "sphinx<8.1.0", - "myst-parser>=5.0.0", - "sphinxcontrib-mermaid>=2.0.1", - "furo>=2024.8.6", - "esbonio>=0.16.4", - "sphinx-autobuild>=2024.4.16", - "sphinx-click>=6.0.0", - "sphinx-substitution-extensions>=2024.10.17", - "requests>=2.33.1", + "sphinx<9.2.0", + "myst-parser>=5.1.0", + "sphinxcontrib-mermaid>=2.0.2", + "furo>=2025.12.19", + "esbonio>=2.0.0", + "sphinx-autobuild>=2025.8.25", + "sphinx-click>=6.2.0", + "sphinx-substitution-extensions>=2026.1.12", + "requests>=2.34.0", "sphinxcontrib-programoutput>=0.19", "sphinx-copybutton>=0.5.2", - "sphinx-inline-tabs>=2023.4.21", + "sphinx-inline-tabs>=2025.12.21.14", ] dev = [ - "ruff==0.15.10", - "typos>=1.23.6", - "pre-commit>=3.8.0", - "esbonio>=0.16.5", - "ty>=0.0.1a8", + "ruff==0.15.12", + "typos>=1.46.1", + "pre-commit>=4.6.0", + "esbonio>=2.0.0", + "ty>=0.0.35", "diff-cover>=10.2.0", ]