DEVX-119: feat: add GiteaClient repo variable methods and parallelize pytest-cov
This commit is contained in:
@@ -55,8 +55,7 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate 2>/dev/null || true
|
. .venv/bin/activate 2>/dev/null || true
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
make devx-vale
|
||||||
vale --minAlertLevel=error docs/ AGENTS.md README.md
|
|
||||||
- name: Translation completeness check
|
- name: Translation completeness check
|
||||||
env:
|
env:
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ RUN pip install --no-cache-dir /tmp/devx[lint] \
|
|||||||
&& rm -rf /tmp/devx
|
&& rm -rf /tmp/devx
|
||||||
|
|
||||||
# Install CI/CD binary tools
|
# Install CI/CD binary tools
|
||||||
RUN python3 -m devx.tools.install_tools --tool actionlint \
|
RUN python3 -m devx.tools.install_tools --tool actionlint --tool vale \
|
||||||
&& python3 -m devx.tools.install_checkmake
|
&& python3 -m devx.tools.install_checkmake
|
||||||
|
|
||||||
# Install hadolint (Dockerfile linter)
|
# Install hadolint (Dockerfile linter)
|
||||||
|
|||||||
@@ -122,7 +122,9 @@ exponential backoff (2s, 4s, 8s).
|
|||||||
- Labels (list, create, add to issues)
|
- Labels (list, create, add to issues)
|
||||||
- Issues (create, list)
|
- Issues (create, list)
|
||||||
- Pull requests (get commits, merge, create review)
|
- Pull requests (get commits, merge, create review)
|
||||||
- Releases (list)
|
- Releases (list, create idempotent)
|
||||||
|
- Actions (list runs, list jobs, get job logs)
|
||||||
|
- Actions variables (get, set idempotent)
|
||||||
- Wiki pages (list, fetch, create, update, delete)
|
- Wiki pages (list, fetch, create, update, delete)
|
||||||
|
|
||||||
**`VikunjaClient`** — Vikunja REST API wrapper:
|
**`VikunjaClient`** — Vikunja REST API wrapper:
|
||||||
|
|||||||
@@ -372,6 +372,35 @@ class GiteaClient:
|
|||||||
r = self._request("GET", f"/actions/jobs/{job_id}/logs")
|
r = self._request("GET", f"/actions/jobs/{job_id}/logs")
|
||||||
return r.text
|
return r.text
|
||||||
|
|
||||||
|
# -- actions variables (repo-level) --
|
||||||
|
|
||||||
|
def get_repo_variable(self, name: str) -> str | None:
|
||||||
|
"""Read a Gitea Actions repository variable.
|
||||||
|
|
||||||
|
Returns the variable value, or ``None`` if the variable is not set.
|
||||||
|
Raises :class:`APIError` on other HTTP errors.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
r = self._request("GET", f"/actions/variables/{name}")
|
||||||
|
return r.json().get("value")
|
||||||
|
except APIError as e:
|
||||||
|
if e.status == 404:
|
||||||
|
return None
|
||||||
|
raise
|
||||||
|
|
||||||
|
def set_repo_variable(self, name: str, value: str) -> None:
|
||||||
|
"""Create or update a Gitea Actions repository variable (idempotent).
|
||||||
|
|
||||||
|
Tries PATCH first; if the variable doesn't exist (404), creates it
|
||||||
|
via POST.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._request("PATCH", f"/actions/variables/{name}", json={"value": value})
|
||||||
|
except APIError as e:
|
||||||
|
if e.status != 404:
|
||||||
|
raise
|
||||||
|
self._request("POST", "/actions/variables", json={"name": name, "value": value})
|
||||||
|
|
||||||
|
|
||||||
class VikunjaClient:
|
class VikunjaClient:
|
||||||
"""Low-level Vikunja REST API client with connection pooling."""
|
"""Low-level Vikunja REST API client with connection pooling."""
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ devx-test-unit:
|
|||||||
@$(DEVX_BIN)/pytest $(DEVX_TEST_PATHS) -q --no-cov
|
@$(DEVX_BIN)/pytest $(DEVX_TEST_PATHS) -q --no-cov
|
||||||
|
|
||||||
devx-pytest-cov:
|
devx-pytest-cov:
|
||||||
@$(DEVX_BIN)/pytest $(DEVX_TEST_PATHS) -v --cov=$(DEVX_COV_PKG) --cov-report=term-missing --cov-fail-under=100
|
@$(DEVX_BIN)/pytest $(DEVX_TEST_PATHS) -n auto --cov=$(DEVX_COV_PKG) --cov-report=term-missing --cov-fail-under=100
|
||||||
|
|
||||||
# ── Quality checks ────────────────────────────────────────────────────────────
|
# ── Quality checks ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -328,10 +328,14 @@ devx-check-docs:
|
|||||||
devx-check-doc-versions:
|
devx-check-doc-versions:
|
||||||
@$(DEVX_PYTHON) -m devx.tools.check_doc_versions --root .
|
@$(DEVX_PYTHON) -m devx.tools.check_doc_versions --root .
|
||||||
|
|
||||||
# Run Vale prose linter on docs and README
|
# Run Vale prose linter on docs and README (skips if vale not installed)
|
||||||
devx-vale:
|
devx-vale:
|
||||||
@export PATH="$$HOME/.local/bin:$$PATH" && \
|
@export PATH="$$HOME/.local/bin:$$PATH" && \
|
||||||
vale --minAlertLevel=error docs/ AGENTS.md README.md
|
if ! command -v vale >/dev/null 2>&1; then \
|
||||||
|
echo "[devx-vale] vale not installed — skipping (install with 'make install-tools')"; \
|
||||||
|
else \
|
||||||
|
vale --minAlertLevel=error docs/ AGENTS.md README.md; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Verify test suite timing
|
# Verify test suite timing
|
||||||
devx-check-test-speed:
|
devx-check-test-speed:
|
||||||
|
|||||||
+816
-616
File diff suppressed because it is too large
Load Diff
@@ -909,3 +909,65 @@ class TestGiteaClientActions:
|
|||||||
"https://git.example.com/repos/owner/repo/actions/jobs/10026/logs",
|
"https://git.example.com/repos/owner/repo/actions/jobs/10026/logs",
|
||||||
timeout=DEFAULT_TIMEOUT,
|
timeout=DEFAULT_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_get_repo_variable_returns_value(self) -> None:
|
||||||
|
client = GiteaClient("https://git.example.com", "tok", "owner", "repo")
|
||||||
|
client._session.request = MagicMock(return_value=_mock_response({"value": "v0.28.1"}))
|
||||||
|
result = client.get_repo_variable("PRODUCTION_DEPLOY_TAG")
|
||||||
|
assert result == "v0.28.1"
|
||||||
|
client._session.request.assert_called_once_with(
|
||||||
|
"GET",
|
||||||
|
"https://git.example.com/repos/owner/repo/actions/variables/PRODUCTION_DEPLOY_TAG",
|
||||||
|
timeout=DEFAULT_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_repo_variable_returns_none_on_404(self) -> None:
|
||||||
|
client = GiteaClient("https://git.example.com", "tok", "owner", "repo")
|
||||||
|
not_found = MagicMock()
|
||||||
|
not_found.raise_for_status.side_effect = _mock_http_error(404, "not found")
|
||||||
|
client._session.request = MagicMock(return_value=not_found)
|
||||||
|
result = client.get_repo_variable("MISSING_VAR")
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
@patch("time.sleep")
|
||||||
|
def test_get_repo_variable_reraises_non_404(self, mock_sleep: MagicMock) -> None:
|
||||||
|
client = GiteaClient("https://git.example.com", "tok", "owner", "repo")
|
||||||
|
server_error = MagicMock()
|
||||||
|
server_error.raise_for_status.side_effect = _mock_http_error(500, "server error")
|
||||||
|
client._session.request = MagicMock(return_value=server_error)
|
||||||
|
with pytest.raises(APIError) as exc_info:
|
||||||
|
client.get_repo_variable("SOME_VAR")
|
||||||
|
assert exc_info.value.status == 500
|
||||||
|
|
||||||
|
def test_set_repo_variable_updates_existing(self) -> None:
|
||||||
|
client = GiteaClient("https://git.example.com", "tok", "owner", "repo")
|
||||||
|
client._session.request = MagicMock(return_value=_mock_response({}))
|
||||||
|
client.set_repo_variable("PRODUCTION_DEPLOY_TAG", "v0.28.2")
|
||||||
|
client._session.request.assert_called_once_with(
|
||||||
|
"PATCH",
|
||||||
|
"https://git.example.com/repos/owner/repo/actions/variables/PRODUCTION_DEPLOY_TAG",
|
||||||
|
timeout=DEFAULT_TIMEOUT,
|
||||||
|
json={"value": "v0.28.2"},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_set_repo_variable_creates_on_404(self) -> None:
|
||||||
|
client = GiteaClient("https://git.example.com", "tok", "owner", "repo")
|
||||||
|
not_found = MagicMock()
|
||||||
|
not_found.raise_for_status.side_effect = _mock_http_error(404, "not found")
|
||||||
|
created = _mock_response({})
|
||||||
|
client._session.request = MagicMock(side_effect=[not_found, created])
|
||||||
|
client.set_repo_variable("NEW_VAR", "v0.29.0")
|
||||||
|
assert client._session.request.call_count == 2
|
||||||
|
second_call = client._session.request.call_args_list[1]
|
||||||
|
assert second_call.args[0] == "POST"
|
||||||
|
assert second_call.args[1] == "https://git.example.com/repos/owner/repo/actions/variables"
|
||||||
|
assert second_call.kwargs["json"] == {"name": "NEW_VAR", "value": "v0.29.0"}
|
||||||
|
|
||||||
|
def test_set_repo_variable_reraises_non_404(self) -> None:
|
||||||
|
client = GiteaClient("https://git.example.com", "tok", "owner", "repo")
|
||||||
|
forbidden = MagicMock()
|
||||||
|
forbidden.raise_for_status.side_effect = _mock_http_error(403, "forbidden")
|
||||||
|
client._session.request = MagicMock(return_value=forbidden)
|
||||||
|
with pytest.raises(APIError) as exc_info:
|
||||||
|
client.set_repo_variable("SOME_VAR", "val")
|
||||||
|
assert exc_info.value.status == 403
|
||||||
|
|||||||
Reference in New Issue
Block a user