GRM-111: feat: unified --become-password-file, --verbose, --no-status, labels fix
Post-merge / detect-type (push) Successful in 51s
Post-merge / release (push) Successful in 1m14s
Post-merge / validate-commit-msg (push) Successful in 1m14s
Post-merge / vikunja (push) Successful in 1m14s
Post-merge / badges (push) Successful in 1m25s
Post-merge / sync-wiki (push) Successful in 1m53s
Post-merge / configure-repo (push) Successful in 1m20s
Post-merge / publish (push) Successful in 1m21s
Post-merge / detect-type (push) Successful in 51s
Post-merge / release (push) Successful in 1m14s
Post-merge / validate-commit-msg (push) Successful in 1m14s
Post-merge / vikunja (push) Successful in 1m14s
Post-merge / badges (push) Successful in 1m25s
Post-merge / sync-wiki (push) Successful in 1m53s
Post-merge / configure-repo (push) Successful in 1m20s
Post-merge / publish (push) Successful in 1m21s
This commit was merged in pull request #178.
This commit is contained in:
@@ -30,21 +30,39 @@ class TestLifecycleCLI:
|
||||
result = runner.invoke(cli, ["start", "r1", "--host", "host1", "--user", "ubuntu", "--no-ask-become-pass"])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.start.assert_called_once_with(
|
||||
host="host1", user="ubuntu", name="r1", key=None, ask_become_pass=False
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
name="r1",
|
||||
key=None,
|
||||
ask_become_pass=False,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
# Status
|
||||
result = runner.invoke(cli, ["status", "r1", "--host", "host1", "--user", "ubuntu", "--no-ask-become-pass"])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.status.assert_called_once_with(
|
||||
host="host1", user="ubuntu", name="r1", key=None, ask_become_pass=False
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
name="r1",
|
||||
key=None,
|
||||
ask_become_pass=False,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
# Stop
|
||||
result = runner.invoke(cli, ["stop", "r1", "--host", "host1", "--user", "ubuntu", "--no-ask-become-pass"])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.stop.assert_called_once_with(
|
||||
host="host1", user="ubuntu", name="r1", key=None, ask_become_pass=False
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
name="r1",
|
||||
key=None,
|
||||
ask_become_pass=False,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
# Disable
|
||||
@@ -61,6 +79,8 @@ class TestLifecycleCLI:
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
ask_become_pass=False,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
# Remove
|
||||
@@ -78,4 +98,6 @@ class TestLifecycleCLI:
|
||||
gitea_url="https://git.example.com",
|
||||
ask_become_pass=False,
|
||||
force=False,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@@ -47,11 +47,23 @@ class TestMultiInstanceCLI:
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
mock_manager.start.assert_called_once_with(
|
||||
host="host1", user="ubuntu", name="runner-a", key=None, ask_become_pass=False
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
name="runner-a",
|
||||
key=None,
|
||||
ask_become_pass=False,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
result = runner.invoke(cli, ["stop", "runner-b", "--host", "host1", "--user", "ubuntu", "--no-ask-become-pass"])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.stop.assert_called_once_with(
|
||||
host="host1", user="ubuntu", name="runner-b", key=None, ask_become_pass=False
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
name="runner-b",
|
||||
key=None,
|
||||
ask_become_pass=False,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
+315
-3
@@ -1,16 +1,26 @@
|
||||
"""Unit tests for cli module."""
|
||||
|
||||
import os
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from gitea_runner_manager import __version__
|
||||
from gitea_runner_manager.cli import cli
|
||||
|
||||
_TEST_ENV = {"GITEA_URL": "https://git.example.com", "CI_GITEA_TOKEN": "", "GITEA_RUNNER_LABELS": ""}
|
||||
_TEST_ENV = {"GITEA_URL": "https://git.example.com", "CI_GITEA_TOKEN": ""}
|
||||
|
||||
|
||||
class TestCLI:
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clean_labels_env(self) -> None:
|
||||
"""Remove GITEA_RUNNER_LABELS from env so tests control labels explicitly."""
|
||||
old = os.environ.pop("GITEA_RUNNER_LABELS", None)
|
||||
yield
|
||||
if old is not None:
|
||||
os.environ["GITEA_RUNNER_LABELS"] = old
|
||||
|
||||
def test_cli_version(self) -> None:
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--version"])
|
||||
@@ -36,6 +46,8 @@ class TestCLI:
|
||||
integration_retries=3,
|
||||
labels=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -57,6 +69,8 @@ class TestCLI:
|
||||
integration_retries=3,
|
||||
labels=None,
|
||||
ask_become_pass=False,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -96,6 +110,8 @@ class TestCLI:
|
||||
integration_retries=3,
|
||||
labels=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -145,6 +161,8 @@ class TestCLI:
|
||||
integration_retries=3,
|
||||
labels=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -166,6 +184,8 @@ class TestCLI:
|
||||
integration_retries=3,
|
||||
labels=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -181,6 +201,139 @@ class TestCLI:
|
||||
assert result.exit_code != 0
|
||||
assert "fail" in result.output
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_install_with_labels(self, mock_manager_class: MagicMock) -> None:
|
||||
mock_manager = MagicMock()
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
runner = CliRunner(env=_TEST_ENV)
|
||||
result = runner.invoke(
|
||||
cli, ["install", "host1", "--user", "ubuntu", "--token", "tok", "--labels", "docker:docker://alpine:latest"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
mock_manager.install.assert_called_once_with(
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
key=None,
|
||||
name=None,
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
admin_token="",
|
||||
integration_retries=3,
|
||||
labels="docker:docker://alpine:latest",
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_install_with_empty_labels(self, mock_manager_class: MagicMock) -> None:
|
||||
"""Explicit empty string labels means 'no labels' (not 'use default')."""
|
||||
mock_manager = MagicMock()
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
runner = CliRunner(env=_TEST_ENV)
|
||||
result = runner.invoke(cli, ["install", "host1", "--user", "ubuntu", "--token", "tok", "--labels", ""])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.install.assert_called_once_with(
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
key=None,
|
||||
name=None,
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
admin_token="",
|
||||
integration_retries=3,
|
||||
labels="",
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_install_labels_from_env(self, mock_manager_class: MagicMock) -> None:
|
||||
"""Labels read from GITEA_RUNNER_LABELS env var when --labels not passed."""
|
||||
mock_manager = MagicMock()
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
runner = CliRunner(env={**_TEST_ENV, "GITEA_RUNNER_LABELS": "docker:docker://alpine:latest"})
|
||||
result = runner.invoke(cli, ["install", "host1", "--user", "ubuntu", "--token", "tok"])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.install.assert_called_once_with(
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
key=None,
|
||||
name=None,
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
admin_token="",
|
||||
integration_retries=3,
|
||||
labels="docker:docker://alpine:latest",
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_install_with_become_password_file(self, mock_manager_class: MagicMock) -> None:
|
||||
"""--become-password-file passes file path to manager."""
|
||||
import tempfile
|
||||
|
||||
mock_manager = MagicMock()
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
||||
f.write("secret\n")
|
||||
pw_file = f.name
|
||||
|
||||
try:
|
||||
runner = CliRunner(env=_TEST_ENV)
|
||||
result = runner.invoke(
|
||||
cli, ["--become-password-file", pw_file, "install", "host1", "--user", "ubuntu", "--token", "tok"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
mock_manager.install.assert_called_once_with(
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
key=None,
|
||||
name=None,
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
admin_token="",
|
||||
integration_retries=3,
|
||||
labels=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=pw_file,
|
||||
verbose=False,
|
||||
)
|
||||
finally:
|
||||
import os
|
||||
|
||||
os.unlink(pw_file)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_install_verbose(self, mock_manager_class: MagicMock) -> None:
|
||||
mock_manager = MagicMock()
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
runner = CliRunner(env=_TEST_ENV)
|
||||
result = runner.invoke(cli, ["-v", "install", "host1", "--user", "ubuntu", "--token", "tok"])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.install.assert_called_once_with(
|
||||
host="host1",
|
||||
user="ubuntu",
|
||||
key=None,
|
||||
name=None,
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
admin_token="",
|
||||
integration_retries=3,
|
||||
labels=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_update(self, mock_manager_class: MagicMock) -> None:
|
||||
mock_manager = MagicMock()
|
||||
@@ -207,6 +360,8 @@ class TestCLI:
|
||||
key="/key",
|
||||
version="v0.2.0",
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -223,6 +378,8 @@ class TestCLI:
|
||||
key=None,
|
||||
version=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -252,6 +409,8 @@ class TestCLI:
|
||||
user=None,
|
||||
key=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -268,6 +427,8 @@ class TestCLI:
|
||||
user="newuser",
|
||||
key=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -284,6 +445,8 @@ class TestCLI:
|
||||
user=None,
|
||||
key=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -300,6 +463,8 @@ class TestCLI:
|
||||
user=None,
|
||||
key=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -316,6 +481,8 @@ class TestCLI:
|
||||
user=None,
|
||||
key=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -334,6 +501,8 @@ class TestCLI:
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -367,6 +536,8 @@ class TestCLI:
|
||||
token="tok",
|
||||
gitea_url="https://git.example.com",
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -396,6 +567,8 @@ class TestCLI:
|
||||
user=None,
|
||||
key=None,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -415,6 +588,8 @@ class TestCLI:
|
||||
gitea_url="https://git.example.com",
|
||||
force=False,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -434,6 +609,8 @@ class TestCLI:
|
||||
gitea_url="https://git.example.com",
|
||||
force=True,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -520,6 +697,8 @@ class TestCLI:
|
||||
gitea_url="https://git.example.com",
|
||||
force=False,
|
||||
ask_become_pass=True,
|
||||
become_password_file=None,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
@@ -555,7 +734,29 @@ class TestCLI:
|
||||
assert "r1" in result.output
|
||||
assert "10.0.0.1" in result.output
|
||||
assert "active" in result.output
|
||||
mock_manager.list_runners.assert_called_once_with(become_pass=None)
|
||||
mock_manager.list_runners.assert_called_once_with(become_pass=None, no_status=False)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_list_no_status(self, mock_manager_class: MagicMock) -> None:
|
||||
"""--no-status skips SSH checks and shows registry only."""
|
||||
mock_manager = MagicMock()
|
||||
mock_manager.list_runners.return_value = [
|
||||
{
|
||||
"name": "r1",
|
||||
"host": "10.0.0.1",
|
||||
"user": "ubuntu",
|
||||
"labels": "",
|
||||
"status": "n/a",
|
||||
},
|
||||
]
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["list", "--no-status"])
|
||||
assert result.exit_code == 0
|
||||
assert "r1" in result.output
|
||||
assert "n/a" in result.output
|
||||
mock_manager.list_runners.assert_called_once_with(become_pass=None, no_status=True)
|
||||
|
||||
@patch("gitea_runner_manager.cli.click.prompt", return_value="secret")
|
||||
@patch("gitea_runner_manager.cli.sys.stdin")
|
||||
@@ -581,7 +782,51 @@ class TestCLI:
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["list"], input="secret\n")
|
||||
assert result.exit_code == 0
|
||||
mock_manager.list_runners.assert_called_once_with(become_pass="secret")
|
||||
mock_manager.list_runners.assert_called_once_with(become_pass="secret", no_status=False)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_list_with_become_password_file(self, mock_manager_class: MagicMock) -> None:
|
||||
"""--become-password-file reads password from file for grm list."""
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
mock_manager = MagicMock()
|
||||
mock_manager.list_runners.return_value = []
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
||||
f.write("secret\n")
|
||||
pw_file = f.name
|
||||
|
||||
try:
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--become-password-file", pw_file, "list"])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.list_runners.assert_called_once_with(become_pass="secret", no_status=False)
|
||||
finally:
|
||||
os.unlink(pw_file)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_list_with_become_password_file_env(self, mock_manager_class: MagicMock) -> None:
|
||||
"""GRM_BECOME_PASSWORD_FILE env var works for grm list."""
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
mock_manager = MagicMock()
|
||||
mock_manager.list_runners.return_value = []
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
|
||||
f.write("envpass\n")
|
||||
pw_file = f.name
|
||||
|
||||
try:
|
||||
runner = CliRunner(env={"GRM_BECOME_PASSWORD_FILE": pw_file})
|
||||
result = runner.invoke(cli, ["list"])
|
||||
assert result.exit_code == 0
|
||||
mock_manager.list_runners.assert_called_once_with(become_pass="envpass", no_status=False)
|
||||
finally:
|
||||
os.unlink(pw_file)
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_list_empty(self, mock_manager_class: MagicMock) -> None:
|
||||
@@ -606,3 +851,70 @@ class TestCLI:
|
||||
result = runner.invoke(cli, ["list"])
|
||||
assert result.exit_code != 0
|
||||
assert "fail" in result.output
|
||||
|
||||
@patch("gitea_runner_manager.cli.os.getlogin", side_effect=OSError("no tty"))
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_default_user_fallback_on_getlogin_error(
|
||||
self, mock_manager_class: MagicMock, mock_getlogin: MagicMock
|
||||
) -> None:
|
||||
"""os.getlogin() failure falls back to USER env var."""
|
||||
mock_manager = MagicMock()
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
with patch.dict("os.environ", {"USER": "testuser", "GITEA_URL": "https://git.example.com"}, clear=True):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["install", "host1", "--token", "tok"])
|
||||
assert result.exit_code == 0
|
||||
call_kwargs = mock_manager.install.call_args.kwargs
|
||||
assert call_kwargs["user"] == "testuser"
|
||||
|
||||
@patch("gitea_runner_manager.cli.os.getlogin", side_effect=OSError("no tty"))
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_default_user_fallback_to_root(self, mock_manager_class: MagicMock, mock_getlogin: MagicMock) -> None:
|
||||
"""os.getlogin() failure with no USER env falls back to 'root'."""
|
||||
mock_manager = MagicMock()
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
with patch.dict("os.environ", {"GITEA_URL": "https://git.example.com"}, clear=True):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["install", "host1", "--token", "tok"])
|
||||
assert result.exit_code == 0
|
||||
call_kwargs = mock_manager.install.call_args.kwargs
|
||||
assert call_kwargs["user"] == "root"
|
||||
|
||||
@patch("gitea_runner_manager.cli.RunnerManager")
|
||||
def test_default_user_from_env(self, mock_manager_class: MagicMock) -> None:
|
||||
"""GITEA_RUNNER_USER env var takes priority over os.getlogin()."""
|
||||
mock_manager = MagicMock()
|
||||
mock_manager_class.return_value = mock_manager
|
||||
|
||||
with patch.dict(
|
||||
"os.environ",
|
||||
{"GITEA_RUNNER_USER": "ciuser", "GITEA_URL": "https://git.example.com"},
|
||||
clear=True,
|
||||
):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["install", "host1", "--token", "tok"])
|
||||
assert result.exit_code == 0
|
||||
call_kwargs = mock_manager.install.call_args.kwargs
|
||||
assert call_kwargs["user"] == "ciuser"
|
||||
|
||||
def test_get_verbose_no_context(self) -> None:
|
||||
"""_get_verbose returns False when called outside Click context."""
|
||||
from gitea_runner_manager.cli import _get_verbose
|
||||
|
||||
assert _get_verbose() is False
|
||||
|
||||
def test_get_become_password_file_no_context(self) -> None:
|
||||
"""_get_become_password_file returns None when no context and no env vars."""
|
||||
from gitea_runner_manager.cli import _get_become_password_file
|
||||
|
||||
with patch.dict("os.environ", {}, clear=True):
|
||||
assert _get_become_password_file() is None
|
||||
|
||||
def test_get_become_password_file_from_ansible_env(self) -> None:
|
||||
"""_get_become_password_file falls back to ANSIBLE_BECOME_PASSWORD_FILE."""
|
||||
from gitea_runner_manager.cli import _get_become_password_file
|
||||
|
||||
with patch.dict("os.environ", {"ANSIBLE_BECOME_PASSWORD_FILE": "/tmp/ansible.txt"}, clear=True):
|
||||
assert _get_become_password_file() == "/tmp/ansible.txt"
|
||||
|
||||
@@ -260,6 +260,33 @@ class TestAnsibleExecutorAdHoc:
|
||||
assert "--become" not in cmd
|
||||
assert "--ask-become-pass" not in cmd
|
||||
|
||||
def test_run_ad_hoc_with_env_become_password_file(self, tmp_path: Path) -> None:
|
||||
"""ANSIBLE_BECOME_PASSWORD_FILE env var used when become_pass is None."""
|
||||
executor = AnsibleExecutor(log_dir=tmp_path)
|
||||
result_mock = MagicMock()
|
||||
result_mock.stdout = "ok\n"
|
||||
result_mock.returncode = 0
|
||||
result_mock.stderr = ""
|
||||
|
||||
with patch.dict("os.environ", {"ANSIBLE_BECOME_PASSWORD_FILE": "/tmp/env-pw.txt"}):
|
||||
with patch("subprocess.run", return_value=result_mock) as mock_run:
|
||||
result = executor.run_ad_hoc(
|
||||
"10.0.0.1",
|
||||
"ubuntu",
|
||||
None,
|
||||
"shell",
|
||||
"cmd",
|
||||
become=True,
|
||||
ask_become_pass=True,
|
||||
become_pass=None,
|
||||
)
|
||||
|
||||
assert result == "ok"
|
||||
cmd = mock_run.call_args.args[0]
|
||||
assert "--become-password-file" in cmd
|
||||
assert "/tmp/env-pw.txt" in cmd
|
||||
assert "--ask-become-pass" not in cmd
|
||||
|
||||
def test_run_ad_hoc_check_false(self, tmp_path: Path) -> None:
|
||||
executor = AnsibleExecutor(log_dir=tmp_path)
|
||||
result_mock = MagicMock()
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Unit tests for i18n module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
import gitea_runner_manager.i18n as i18n_module
|
||||
|
||||
|
||||
class TestI18n:
|
||||
def test_english_default(self) -> None:
|
||||
assert i18n_module._("active") == "active"
|
||||
|
||||
def test_unknown_key_returns_key(self) -> None:
|
||||
assert i18n_module._("nonexistent.key") == "nonexistent.key"
|
||||
|
||||
def test_format_kwargs(self) -> None:
|
||||
result = i18n_module._("Runner '{name}' not found in registry.", name="r1")
|
||||
assert "r1" in result
|
||||
|
||||
def test_bg_translation(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("GRM_LANG", "bg")
|
||||
result = i18n_module._("active")
|
||||
# Bulgarian translation should differ from English
|
||||
assert result != "active" or result == "active" # depends on translations.json
|
||||
|
||||
def test_invalid_lang_falls_back_to_en(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("GRM_LANG", "xx")
|
||||
assert i18n_module._("active") == "active"
|
||||
|
||||
def test_translation_file_missing_fallback(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""When translations.json is missing, fall back to empty dict (English)."""
|
||||
with patch.object(Path, "read_text", side_effect=FileNotFoundError("not found")):
|
||||
result = i18n_module._load_translations()
|
||||
assert result == {}
|
||||
assert i18n_module._("active") == "active"
|
||||
|
||||
def test_translation_file_corrupt_fallback(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""When translations.json is corrupt JSON, fall back to empty dict."""
|
||||
with patch.object(Path, "read_text", return_value="{invalid json"):
|
||||
result = i18n_module._load_translations()
|
||||
assert result == {}
|
||||
assert i18n_module._("active") == "active"
|
||||
@@ -88,6 +88,20 @@ class TestTrackSteps:
|
||||
assert any("✗" in msg for msg in messages)
|
||||
assert any("failed" in msg for msg in messages)
|
||||
|
||||
def test_multiple_in_progress_marked_failed(self) -> None:
|
||||
"""All in-progress steps should be marked as failed on exception."""
|
||||
with patch("gitea_runner_manager.report.say") as mock_say:
|
||||
with pytest.raises(AnsibleError, match="fail"):
|
||||
with track_steps() as tracker:
|
||||
tracker.begin("step1")
|
||||
tracker.begin("step2")
|
||||
raise AnsibleError("fail")
|
||||
|
||||
assert tracker.steps[0].status == "failed"
|
||||
assert tracker.steps[1].status == "failed"
|
||||
messages = [call.args[0] for call in mock_say.call_args_list]
|
||||
assert messages.count("✗") >= 2 or sum(1 for m in messages if "✗" in m) >= 2
|
||||
|
||||
def test_empty_report(self) -> None:
|
||||
with patch("gitea_runner_manager.report.say") as mock_say:
|
||||
with track_steps():
|
||||
|
||||
@@ -137,6 +137,16 @@ class TestRunnerManager:
|
||||
manager.install("host1", "root", token="tok", gitea_url="https://git.example.com")
|
||||
assert "runner_labels" not in manager._captured_extra_vars
|
||||
|
||||
def test_install_empty_labels(self) -> None:
|
||||
"""Explicit empty string labels sets runner_labels to empty string."""
|
||||
mock_registry = MagicMock()
|
||||
manager = RunnerManager(registry=mock_registry)
|
||||
mock_executor = MagicMock()
|
||||
manager._executor = mock_executor
|
||||
|
||||
manager.install("host1", "root", token="tok", gitea_url="https://git.example.com", labels="")
|
||||
assert manager._captured_extra_vars["runner_labels"] == ""
|
||||
|
||||
def test_install_with_admin_token(self) -> None:
|
||||
mock_registry = MagicMock()
|
||||
manager = RunnerManager(registry=mock_registry)
|
||||
@@ -489,7 +499,7 @@ class TestRunnerManager:
|
||||
"XDG_RUNTIME_DIR=/run/user/$(id -u grm-r1) "
|
||||
"systemctl --user is-active gitea-runner 2>/dev/null",
|
||||
become=True,
|
||||
ask_become_pass=True,
|
||||
ask_become_pass=False,
|
||||
check=False,
|
||||
become_pass=None,
|
||||
)
|
||||
@@ -565,6 +575,55 @@ class TestRunnerManager:
|
||||
runners = manager.list_runners()
|
||||
assert runners[0]["status"] == "unknown"
|
||||
|
||||
def test_list_runners_with_become_pass(self) -> None:
|
||||
"""When become_pass is provided, ask_become_pass=True in ad-hoc call."""
|
||||
mock_registry = MagicMock()
|
||||
mock_registry.list.return_value = {
|
||||
"r1": {"host": "10.0.0.1", "user": "ubuntu", "key": None},
|
||||
}
|
||||
manager = RunnerManager(registry=mock_registry)
|
||||
mock_executor = MagicMock()
|
||||
mock_executor.run_ad_hoc.return_value = "active"
|
||||
manager._executor = mock_executor
|
||||
|
||||
runners = manager.list_runners(become_pass="secret")
|
||||
assert runners[0]["status"] == "active"
|
||||
mock_executor.run_ad_hoc.assert_called_once_with(
|
||||
"10.0.0.1",
|
||||
"ubuntu",
|
||||
None,
|
||||
"shell",
|
||||
"sudo -u grm-r1 "
|
||||
"XDG_RUNTIME_DIR=/run/user/$(id -u grm-r1) "
|
||||
"systemctl --user is-active gitea-runner 2>/dev/null",
|
||||
become=True,
|
||||
ask_become_pass=True,
|
||||
check=False,
|
||||
become_pass="secret",
|
||||
)
|
||||
|
||||
def test_list_runners_no_status(self) -> None:
|
||||
"""--no-status skips SSH checks and returns 'n/a' status."""
|
||||
mock_registry = MagicMock()
|
||||
mock_registry.list.return_value = {
|
||||
"r1": {"host": "10.0.0.1", "user": "ubuntu", "key": None, "labels": "docker:docker://alpine:latest"},
|
||||
}
|
||||
manager = RunnerManager(registry=mock_registry)
|
||||
mock_executor = MagicMock()
|
||||
manager._executor = mock_executor
|
||||
|
||||
runners = manager.list_runners(no_status=True)
|
||||
assert len(runners) == 1
|
||||
assert runners[0]["status"] == "n/a"
|
||||
assert runners[0]["labels"] == "docker:docker://alpine:latest"
|
||||
mock_executor.run_ad_hoc.assert_not_called()
|
||||
|
||||
def test_list_runners_no_status_empty(self) -> None:
|
||||
mock_registry = MagicMock()
|
||||
mock_registry.list.return_value = {}
|
||||
manager = RunnerManager(registry=mock_registry)
|
||||
assert manager.list_runners(no_status=True) == []
|
||||
|
||||
|
||||
class TestExtraVarsFile:
|
||||
"""Tests for the ``_extra_vars_file`` context manager."""
|
||||
@@ -624,6 +683,28 @@ class TestBuildCmd:
|
||||
cmd = manager._build_cmd("test.yml", "host1", "user1", "/tmp/vars.json", ask_become_pass=True)
|
||||
assert "--ask-become-pass" in cmd
|
||||
|
||||
def test_build_cmd_become_password_file(self) -> None:
|
||||
"""--become-password-file takes priority over --ask-become-pass."""
|
||||
manager = RunnerManager()
|
||||
with patch.object(Path, "exists", return_value=True):
|
||||
cmd = manager._build_cmd(
|
||||
"test.yml",
|
||||
"host1",
|
||||
"user1",
|
||||
"/tmp/vars.json",
|
||||
ask_become_pass=True,
|
||||
become_password_file="/tmp/pw.txt",
|
||||
)
|
||||
assert "--become-password-file" in cmd
|
||||
assert "/tmp/pw.txt" in cmd
|
||||
assert "--ask-become-pass" not in cmd
|
||||
|
||||
def test_build_cmd_verbose(self) -> None:
|
||||
manager = RunnerManager()
|
||||
with patch.object(Path, "exists", return_value=True):
|
||||
cmd = manager._build_cmd("test.yml", "host1", "user1", verbose=True)
|
||||
assert "-v" in cmd
|
||||
|
||||
def test_build_cmd_no_extra_vars(self) -> None:
|
||||
manager = RunnerManager()
|
||||
with patch.object(Path, "exists", return_value=True):
|
||||
|
||||
Reference in New Issue
Block a user