Merge pull request 'pkgs/cli: Fix dynamic shell completions' (#5599) from ke-cli-completion-fix into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5599
This commit is contained in:
@@ -23,6 +23,9 @@ from .secrets.users import list_users
|
|||||||
This module provides dynamic completions.
|
This module provides dynamic completions.
|
||||||
The completions should feel fast.
|
The completions should feel fast.
|
||||||
We target a maximum of 1second on our average machine.
|
We target a maximum of 1second on our average machine.
|
||||||
|
|
||||||
|
Note: All completion functions have 'prefix' as their first parameter (required
|
||||||
|
by argcomplete's API) but don't use it internally.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -44,7 +47,7 @@ def clan_dir(flake: str | None) -> str | None:
|
|||||||
|
|
||||||
|
|
||||||
def complete_machines(
|
def complete_machines(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -77,7 +80,7 @@ def complete_machines(
|
|||||||
|
|
||||||
|
|
||||||
def complete_services_for_machine(
|
def complete_services_for_machine(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -121,7 +124,7 @@ def complete_services_for_machine(
|
|||||||
|
|
||||||
|
|
||||||
def complete_backup_providers_for_machine(
|
def complete_backup_providers_for_machine(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -164,7 +167,7 @@ def complete_backup_providers_for_machine(
|
|||||||
|
|
||||||
|
|
||||||
def complete_state_services_for_machine(
|
def complete_state_services_for_machine(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -207,7 +210,7 @@ def complete_state_services_for_machine(
|
|||||||
|
|
||||||
|
|
||||||
def complete_secrets(
|
def complete_secrets(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -225,7 +228,7 @@ def complete_secrets(
|
|||||||
|
|
||||||
|
|
||||||
def complete_users(
|
def complete_users(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -243,7 +246,7 @@ def complete_users(
|
|||||||
|
|
||||||
|
|
||||||
def complete_groups(
|
def complete_groups(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -262,7 +265,7 @@ def complete_groups(
|
|||||||
|
|
||||||
|
|
||||||
def complete_templates_disko(
|
def complete_templates_disko(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -283,7 +286,7 @@ def complete_templates_disko(
|
|||||||
|
|
||||||
|
|
||||||
def complete_templates_clan(
|
def complete_templates_clan(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -304,7 +307,7 @@ def complete_templates_clan(
|
|||||||
|
|
||||||
|
|
||||||
def complete_templates_machine(
|
def complete_templates_machine(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -325,7 +328,7 @@ def complete_templates_machine(
|
|||||||
|
|
||||||
|
|
||||||
def complete_vars_for_machine(
|
def complete_vars_for_machine(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -366,7 +369,7 @@ def complete_vars_for_machine(
|
|||||||
|
|
||||||
|
|
||||||
def complete_target_host(
|
def complete_target_host(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
@@ -407,7 +410,7 @@ def complete_target_host(
|
|||||||
|
|
||||||
|
|
||||||
def complete_tags(
|
def complete_tags(
|
||||||
_prefix: str,
|
prefix: str, # noqa: ARG001
|
||||||
parsed_args: argparse.Namespace,
|
parsed_args: argparse.Namespace,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
) -> Iterable[str]:
|
) -> Iterable[str]:
|
||||||
|
|||||||
113
pkgs/clan-cli/clan_cli/completions_test.py
Normal file
113
pkgs/clan-cli/clan_cli/completions_test.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
"""Tests for dynamic completion functionality.
|
||||||
|
|
||||||
|
These tests verify that dynamic completions work correctly for various commands.
|
||||||
|
The tests directly call completion functions rather than simulating shell completion.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from clan_lib.flake import Flake
|
||||||
|
|
||||||
|
from .completions import complete_machines
|
||||||
|
from .tests import fixtures_flakes
|
||||||
|
from .tests.helpers import cli
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.with_core
|
||||||
|
def test_complete_machines(
|
||||||
|
test_flake_with_core: fixtures_flakes.FlakeForTest,
|
||||||
|
) -> None:
|
||||||
|
"""Test that complete_machines returns the correct machine names."""
|
||||||
|
parsed_args = argparse.Namespace(flake=Flake(str(test_flake_with_core.path)))
|
||||||
|
|
||||||
|
completions = complete_machines(prefix="", parsed_args=parsed_args)
|
||||||
|
|
||||||
|
completion_list = (
|
||||||
|
list(completions.keys()) if isinstance(completions, dict) else list(completions)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "vm1" in completion_list, (
|
||||||
|
f"Expected 'vm1' in completions, got: {completion_list}"
|
||||||
|
)
|
||||||
|
assert "vm2" in completion_list, (
|
||||||
|
f"Expected 'vm2' in completions, got: {completion_list}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.with_core
|
||||||
|
def test_complete_machines_with_prefix(
|
||||||
|
test_flake_with_core: fixtures_flakes.FlakeForTest,
|
||||||
|
) -> None:
|
||||||
|
parsed_args = argparse.Namespace(flake=Flake(str(test_flake_with_core.path)))
|
||||||
|
|
||||||
|
completions = complete_machines(prefix="vm", parsed_args=parsed_args)
|
||||||
|
completion_list = (
|
||||||
|
list(completions.keys()) if isinstance(completions, dict) else list(completions)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(completion_list) > 0, "Should return machine names"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.with_core
|
||||||
|
def test_complete_machines_without_flake(
|
||||||
|
test_flake_with_core: fixtures_flakes.FlakeForTest,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""Test that complete_machines works when flake is not explicitly set.
|
||||||
|
|
||||||
|
When no --flake argument is provided, completions should use the current
|
||||||
|
directory or CLAN_DIR environment variable.
|
||||||
|
"""
|
||||||
|
monkeypatch.chdir(test_flake_with_core.path)
|
||||||
|
|
||||||
|
parsed_args = argparse.Namespace()
|
||||||
|
|
||||||
|
completions = complete_machines(prefix="", parsed_args=parsed_args)
|
||||||
|
completion_list = (
|
||||||
|
list(completions.keys()) if isinstance(completions, dict) else list(completions)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "vm1" in completion_list
|
||||||
|
assert "vm2" in completion_list
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.with_core
|
||||||
|
def test_complete_machines_with_created_machine(
|
||||||
|
test_flake_with_core: fixtures_flakes.FlakeForTest,
|
||||||
|
) -> None:
|
||||||
|
"""Test that completions include dynamically created machines."""
|
||||||
|
# Create a new machine
|
||||||
|
cli.run(
|
||||||
|
[
|
||||||
|
"machines",
|
||||||
|
"create",
|
||||||
|
"--flake",
|
||||||
|
str(test_flake_with_core.path),
|
||||||
|
"test-machine",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
parsed_args = argparse.Namespace(flake=Flake(str(test_flake_with_core.path)))
|
||||||
|
completions = complete_machines(prefix="", parsed_args=parsed_args)
|
||||||
|
completion_list = (
|
||||||
|
list(completions.keys()) if isinstance(completions, dict) else list(completions)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should include the newly created machine
|
||||||
|
assert "test-machine" in completion_list, (
|
||||||
|
f"Expected 'test-machine' in {completion_list}"
|
||||||
|
)
|
||||||
|
assert "vm1" in completion_list
|
||||||
|
assert "vm2" in completion_list
|
||||||
|
|
||||||
|
|
||||||
|
def test_complete_machines_signature() -> None:
|
||||||
|
"""Test that complete_machines has the correct signature for argcomplete."""
|
||||||
|
sig = inspect.signature(complete_machines)
|
||||||
|
params = list(sig.parameters.keys())
|
||||||
|
|
||||||
|
assert params[0] == "prefix", (
|
||||||
|
f"First parameter should be 'prefix' for argcomplete compatibility, got: {params[0]}"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user