feat: sync missing features from v0.49.x line to master

The v0.49.x tag line diverged from origin/master, leaving many
features only accessible via tags but not on the master branch.

New modules:
- ci/cancel_superseded_runs.py — cancel superseded CI runs
- ci/check_workflow_artifact_deps.py — validate artifact deps
- ci/check_workflow_tofu_init.py — validate tofu init steps
- tools/check_alert_rules.py — validate Prometheus alert rules
- tools/check_ansible_set_fact_to_json.py — lint set_fact usage
- tools/check_docker_init.py — validate Docker init scripts
- utils/jinja.py — Jinja2 template utilities
- utils/ui.py — UI/console utilities

Modified modules:
- distribute_molecule.py: add --include-roles/--exclude-roles
- utils/api.py: add container.credentials for private registry auth
- install_tools.py: retry ansible-galaxy on transient timeouts
- setup_image.py: skip dep resolution with --no-deps
- cli.py: register new commands
- i18n.py: add new translation keys

Also removes accidentally committed .vale/styles/Google/ files.

Test results: 2195 passed, 100% coverage.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
emil
2026-08-09 03:07:28 +02:00
co-authored by Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent d2aa4c6298
commit bbb14c71e9
85 changed files with 4828 additions and 551 deletions
+171
View File
@@ -0,0 +1,171 @@
"""Unit tests for devx.utils.api.APIClient."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
import pytest
import requests
from devx.utils.api import APIClient
class TestAPIClient:
@patch("devx.utils.api.requests.request")
def test_get(self, mock_req):
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_req.return_value = mock_resp
client = APIClient("https://api.example.com", {"Authorization": "Bearer token"})
result = client.get("/users")
assert result is mock_resp
mock_req.assert_called_once_with(
"GET",
"https://api.example.com/users",
headers={"Authorization": "Bearer token"},
timeout=30,
verify=True,
)
@patch("devx.utils.api.requests.request")
def test_post(self, mock_req):
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_req.return_value = mock_resp
client = APIClient("https://api.example.com", {})
client.post("/users", json={"name": "alice"})
mock_req.assert_called_once_with(
"POST",
"https://api.example.com/users",
headers={},
timeout=30,
verify=True,
json={"name": "alice"},
)
@patch("devx.utils.api.requests.request")
def test_put(self, mock_req):
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_req.return_value = mock_resp
client = APIClient("https://api.example.com", {})
client.put("/users/1", json={"name": "bob"})
mock_req.assert_called_once_with(
"PUT",
"https://api.example.com/users/1",
headers={},
timeout=30,
verify=True,
json={"name": "bob"},
)
@patch("devx.utils.api.requests.request")
def test_delete(self, mock_req):
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_req.return_value = mock_resp
client = APIClient("https://api.example.com", {})
client.delete("/users/1")
mock_req.assert_called_once_with(
"DELETE",
"https://api.example.com/users/1",
headers={},
timeout=30,
verify=True,
)
@patch("devx.utils.api.requests.request")
def test_patch(self, mock_req):
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_req.return_value = mock_resp
client = APIClient("https://api.example.com", {})
client.patch("/users/1", json={"name": "carol"})
mock_req.assert_called_once_with(
"PATCH",
"https://api.example.com/users/1",
headers={},
timeout=30,
verify=True,
json={"name": "carol"},
)
@patch("devx.utils.api.requests.request")
def test_auth_tuple(self, mock_req):
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_req.return_value = mock_resp
client = APIClient("https://api.example.com", {}, auth=("admin", "pass"))
client.get("/data")
mock_req.assert_called_once_with(
"GET",
"https://api.example.com/data",
headers={},
timeout=30,
verify=True,
auth=("admin", "pass"),
)
@patch("devx.utils.api.requests.request")
def test_custom_timeout_and_verify(self, mock_req):
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_req.return_value = mock_resp
client = APIClient("https://api.example.com", {}, timeout=60, verify=False)
client.get("/data")
mock_req.assert_called_once_with(
"GET",
"https://api.example.com/data",
headers={},
timeout=60,
verify=False,
)
@patch("devx.utils.api.requests.request")
def test_raises_on_error(self, mock_req):
mock_resp = MagicMock()
mock_resp.raise_for_status.side_effect = requests.HTTPError("500")
mock_req.return_value = mock_resp
client = APIClient("https://api.example.com", {})
with pytest.raises(requests.HTTPError):
client.get("/fail")
@patch("devx.utils.api.requests.request")
def test_strips_trailing_slash(self, mock_req):
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_req.return_value = mock_resp
client = APIClient("https://api.example.com/", {})
client.get("/users")
mock_req.assert_called_once_with(
"GET",
"https://api.example.com/users",
headers={},
timeout=30,
verify=True,
)
@patch("devx.utils.api.requests.request")
def test_kwargs_override_defaults(self, mock_req):
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_req.return_value = mock_resp
client = APIClient("https://api.example.com", {}, timeout=30)
client.get("/slow", timeout=120)
mock_req.assert_called_once_with(
"GET",
"https://api.example.com/slow",
headers={},
timeout=120,
verify=True,
)
@patch("devx.utils.api.requests.request")
def test_no_auth_when_not_set(self, mock_req):
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_req.return_value = mock_resp
client = APIClient("https://api.example.com", {})
client.get("/data")
call_kwargs = mock_req.call_args.kwargs
assert "auth" not in call_kwargs