GRM-35: fix: release push permission and notify_failure label IDs
Fix two issues found during release workflow testing. Closes GRM-35
This commit is contained in:
@@ -51,15 +51,17 @@ def main(repo: str, run_id: str, workflow: str, commit: str) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
issue = client.create_issue(title=title, body=body, labels=["bug"])
|
# Look up label IDs by name (Gitea API expects integer IDs, not strings)
|
||||||
|
label_ids: list[int] = []
|
||||||
|
for label in client.list_labels():
|
||||||
|
if label.get("name") == "bug":
|
||||||
|
label_ids.append(int(label["id"]))
|
||||||
|
break
|
||||||
|
issue = client.create_issue(title=title, body=body, labels=label_ids if label_ids else None)
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
# If labels don't exist, retry without labels
|
raise click.ClickException(
|
||||||
if e.status == 404:
|
_("Failed to create issue: HTTP {status} — {message}", status=e.status, message=e.message)
|
||||||
issue = client.create_issue(title=title, body=body)
|
) from None
|
||||||
else:
|
|
||||||
raise click.ClickException(
|
|
||||||
_("Failed to create issue: HTTP {status} — {message}", status=e.status, message=e.message)
|
|
||||||
) from None
|
|
||||||
|
|
||||||
click.echo(
|
click.echo(
|
||||||
_(
|
_(
|
||||||
|
|||||||
@@ -105,8 +105,12 @@ class GiteaClient:
|
|||||||
return None
|
return None
|
||||||
return self.create_label(name, color, description)
|
return self.create_label(name, color, description)
|
||||||
|
|
||||||
def create_issue(self, title: str, body: str = "", labels: list[str] | None = None) -> dict[str, Any]:
|
def create_issue(self, title: str, body: str = "", labels: list[int] | None = None) -> dict[str, Any]:
|
||||||
"""Create a new issue in the repository."""
|
"""Create a new issue in the repository.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
labels: List of label IDs (integers, not names).
|
||||||
|
"""
|
||||||
payload: dict[str, Any] = {"title": title, "body": body}
|
payload: dict[str, Any] = {"title": title, "body": body}
|
||||||
if labels:
|
if labels:
|
||||||
payload["labels"] = labels
|
payload["labels"] = labels
|
||||||
|
|||||||
@@ -275,12 +275,12 @@ class TestGiteaClient:
|
|||||||
client = GiteaClient("https://git.example.com", "tok", "owner", "repo")
|
client = GiteaClient("https://git.example.com", "tok", "owner", "repo")
|
||||||
client._session.request = MagicMock(return_value=_mock_response({"id": 42, "title": "bug"}))
|
client._session.request = MagicMock(return_value=_mock_response({"id": 42, "title": "bug"}))
|
||||||
|
|
||||||
result = client.create_issue(title="bug", body="description", labels=["bug"])
|
result = client.create_issue(title="bug", body="description", labels=[1])
|
||||||
assert result["id"] == 42
|
assert result["id"] == 42
|
||||||
client._session.request.assert_called_once_with(
|
client._session.request.assert_called_once_with(
|
||||||
"POST",
|
"POST",
|
||||||
"https://git.example.com/repos/owner/repo/issues",
|
"https://git.example.com/repos/owner/repo/issues",
|
||||||
json={"title": "bug", "body": "description", "labels": ["bug"]},
|
json={"title": "bug", "body": "description", "labels": [1]},
|
||||||
timeout=DEFAULT_TIMEOUT,
|
timeout=DEFAULT_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""Unit tests for scripts/notify_failure.py."""
|
"""Unit tests for scripts/notify_failure.py."""
|
||||||
|
|
||||||
import http
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
@@ -14,6 +13,7 @@ class TestNotifyFailure:
|
|||||||
@patch("scripts.notify_failure.GiteaClient")
|
@patch("scripts.notify_failure.GiteaClient")
|
||||||
def test_creates_issue_with_labels(self, mock_client_cls: MagicMock) -> None:
|
def test_creates_issue_with_labels(self, mock_client_cls: MagicMock) -> None:
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
|
mock_client.list_labels.return_value = [{"id": 5, "name": "bug"}]
|
||||||
mock_client.create_issue.return_value = {"id": 42}
|
mock_client.create_issue.return_value = {"id": 42}
|
||||||
mock_client_cls.return_value = mock_client
|
mock_client_cls.return_value = mock_client
|
||||||
|
|
||||||
@@ -35,17 +35,15 @@ class TestNotifyFailure:
|
|||||||
assert "issue #42" in result.output
|
assert "issue #42" in result.output
|
||||||
mock_client.create_issue.assert_called_once()
|
mock_client.create_issue.assert_called_once()
|
||||||
call_kwargs = mock_client.create_issue.call_args
|
call_kwargs = mock_client.create_issue.call_args
|
||||||
assert "release" in call_kwargs.kwargs["title"]
|
assert call_kwargs.kwargs["labels"] == [5]
|
||||||
assert call_kwargs.kwargs["labels"] == ["bug"]
|
|
||||||
|
|
||||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||||
@patch("scripts.notify_failure.GiteaClient")
|
@patch("scripts.notify_failure.GiteaClient")
|
||||||
def test_creates_issue_without_labels_on_404(self, mock_client_cls: MagicMock) -> None:
|
def test_creates_issue_without_bug_label(self, mock_client_cls: MagicMock) -> None:
|
||||||
|
"""When 'bug' label doesn't exist, create issue without labels."""
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_client.create_issue.side_effect = [
|
mock_client.list_labels.return_value = [{"id": 1, "name": "enhancement"}]
|
||||||
APIError(http.HTTPStatus.NOT_FOUND, "label not found"),
|
mock_client.create_issue.return_value = {"id": 43}
|
||||||
{"id": 43},
|
|
||||||
]
|
|
||||||
mock_client_cls.return_value = mock_client
|
mock_client_cls.return_value = mock_client
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
@@ -64,16 +62,16 @@ class TestNotifyFailure:
|
|||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "issue #43" in result.output
|
assert "issue #43" in result.output
|
||||||
assert mock_client.create_issue.call_count == 2
|
mock_client.create_issue.assert_called_once()
|
||||||
# Second call should not have labels
|
call_kwargs = mock_client.create_issue.call_args
|
||||||
second_call = mock_client.create_issue.call_args_list[1]
|
assert call_kwargs.kwargs.get("labels") is None
|
||||||
assert "labels" not in second_call.kwargs or second_call.kwargs.get("labels") is None
|
|
||||||
|
|
||||||
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok"})
|
||||||
@patch("scripts.notify_failure.GiteaClient")
|
@patch("scripts.notify_failure.GiteaClient")
|
||||||
def test_api_error_raises(self, mock_client_cls: MagicMock) -> None:
|
def test_api_error_raises(self, mock_client_cls: MagicMock) -> None:
|
||||||
mock_client = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_client.create_issue.side_effect = APIError(http.HTTPStatus.FORBIDDEN, "forbidden")
|
mock_client.list_labels.return_value = []
|
||||||
|
mock_client.create_issue.side_effect = APIError(403, "forbidden")
|
||||||
mock_client_cls.return_value = mock_client
|
mock_client_cls.return_value = mock_client
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
|
|||||||
Reference in New Issue
Block a user