GRM-59: fix: post-merge workflow failures (4 jobs)

This commit is contained in:
emil
2026-06-22 10:26:28 +00:00
parent 7d3cf999d6
commit f649120172
7 changed files with 42 additions and 25 deletions
+3 -2
View File
@@ -54,8 +54,9 @@ jobs:
env: env:
PYTHONPATH: .:src PYTHONPATH: .:src
run: | run: |
git log -1 --format=%B > /tmp/commit-msg.txt git log -1 --format=%B > commit-msg.txt
python3 scripts/ci/validate_commit_msg.py /tmp/commit-msg.txt --branch master python3 scripts/ci/validate_commit_msg.py commit-msg.txt --branch master
rm -f commit-msg.txt
release: release:
needs: [detect-type] needs: [detect-type]
+14 -10
View File
@@ -64,11 +64,11 @@ def extract_conventional_msg(commit_msg: str) -> str:
return re.sub(r"^GRM-\d+[:\s]\s*", "", first_line) return re.sub(r"^GRM-\d+[:\s]\s*", "", first_line)
def resolve_task_id(client: VikunjaClient, task_id: str) -> int: def resolve_task_id(client: VikunjaClient, task_id: str) -> int | None:
"""Resolve GRM-N identifier to Vikunja numeric task ID. """Resolve GRM-N identifier to Vikunja numeric task ID.
Paginates through the project's tasks to handle projects with more Paginates through the project's tasks to handle projects with more
than 50 tasks. than 50 tasks. Returns None if the task is not found.
""" """
page = 1 page = 1
while True: while True:
@@ -81,13 +81,7 @@ def resolve_task_id(client: VikunjaClient, task_id: str) -> int:
if len(tasks) < DEFAULT_PER_PAGE: if len(tasks) < DEFAULT_PER_PAGE:
break break
page += 1 page += 1
raise click.ClickException( return None
_(
"Could not find Vikunja task for {task_id} in project {project_id}.",
task_id=task_id,
project_id=VIKUNJA_PROJECT_ID,
)
)
def build_comment(task_id: str, conv_msg: str, commit_sha: str) -> str: def build_comment(task_id: str, conv_msg: str, commit_sha: str) -> str:
@@ -162,7 +156,17 @@ def main(commit_msg: str | None, commit_sha: str, from_git: bool, git_sha: str)
client = VikunjaClient(VIKUNJA_API_URL, token) client = VikunjaClient(VIKUNJA_API_URL, token)
vikunja_task_id = 0 vikunja_task_id = 0
try: try:
vikunja_task_id = resolve_task_id(client, task_id) vikunja_task_id = resolve_task_id(client, task_id) or 0
if not vikunja_task_id:
click.echo(
_(
"Warning: Could not find Vikunja task {task_id} in project {project_id}. "
"The merge succeeded — please update the Vikunja task manually.",
task_id=task_id,
project_id=VIKUNJA_PROJECT_ID,
)
)
return
conv_msg = extract_conventional_msg(commit_msg) conv_msg = extract_conventional_msg(commit_msg)
sha = commit_sha or "unknown" sha = commit_sha or "unknown"
html = build_comment(task_id, conv_msg, sha) html = build_comment(task_id, conv_msg, sha)
+3 -2
View File
@@ -50,6 +50,7 @@ def _ensure_label_via_tea(tea: TeaCLI, repo: str, name: str, color: str, descrip
"""Create a label via tea if it doesn't already exist. """Create a label via tea if it doesn't already exist.
Returns True if created, False if it already existed. Returns True if created, False if it already existed.
Falls back to GiteaClient if tea is not installed or fails.
""" """
try: try:
existing = tea.list_labels(repo) existing = tea.list_labels(repo)
@@ -57,8 +58,8 @@ def _ensure_label_via_tea(tea: TeaCLI, repo: str, name: str, color: str, descrip
return False return False
tea.create_label(repo, name=name, color=color, description=description) tea.create_label(repo, name=name, color=color, description=description)
return True return True
except TeaCLIError: except (TeaCLIError, FileNotFoundError):
# Fall back to GiteaClient if tea fails # Fall back to GiteaClient if tea is not installed or fails
return _ensure_label_via_client(name, color, description) return _ensure_label_via_client(name, color, description)
+3 -1
View File
@@ -21,7 +21,9 @@ DEFAULT_PER_PAGE = 50
BRANCH_PROTECTION_CONFIG: dict[str, object] = { BRANCH_PROTECTION_CONFIG: dict[str, object] = {
"branch_name": "master", "branch_name": "master",
"enable_push": False, "enable_push": True,
"enable_push_whitelist": True,
"push_whitelist_usernames": ["emil"],
"enable_status_check": True, "enable_status_check": True,
"status_check_contexts": [ "status_check_contexts": [
"CI / quality (pull_request)", "CI / quality (pull_request)",
+3 -1
View File
@@ -46,7 +46,9 @@ class TestConfigConstants:
def test_branch_protection_config(self) -> None: def test_branch_protection_config(self) -> None:
assert BRANCH_PROTECTION_CONFIG["branch_name"] == "master" assert BRANCH_PROTECTION_CONFIG["branch_name"] == "master"
assert BRANCH_PROTECTION_CONFIG["enable_push"] is False assert BRANCH_PROTECTION_CONFIG["enable_push"] is True
assert BRANCH_PROTECTION_CONFIG["enable_push_whitelist"] is True
assert "emil" in BRANCH_PROTECTION_CONFIG["push_whitelist_usernames"]
assert BRANCH_PROTECTION_CONFIG["required_approvals"] == 0 assert BRANCH_PROTECTION_CONFIG["required_approvals"] == 0
contexts = BRANCH_PROTECTION_CONFIG["status_check_contexts"] contexts = BRANCH_PROTECTION_CONFIG["status_check_contexts"]
assert isinstance(contexts, list) assert isinstance(contexts, list)
+9
View File
@@ -62,6 +62,15 @@ class TestEnsureLabelViaTea:
assert result is True assert result is True
mock_fallback.assert_called_once_with("ready-to-merge", "2ecc71", "desc") mock_fallback.assert_called_once_with("ready-to-merge", "2ecc71", "desc")
def test_tea_not_installed_falls_back_to_client(self) -> None:
"""When tea CLI is not installed (FileNotFoundError), fall back to GiteaClient."""
mock_tea = MagicMock()
mock_tea.list_labels.side_effect = FileNotFoundError("[Errno 2] No such file or directory: 'tea'")
with patch("scripts.configure_repo._ensure_label_via_client", return_value=True) as mock_fallback:
result = _ensure_label_via_tea(mock_tea, "owner/repo", "ready-to-merge", "2ecc71", "desc")
assert result is True
mock_fallback.assert_called_once_with("ready-to-merge", "2ecc71", "desc")
class TestEnsureLabelViaClient: class TestEnsureLabelViaClient:
@patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True) @patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True)
+7 -9
View File
@@ -58,12 +58,10 @@ class TestResolveTaskId:
assert resolve_task_id(mock_client, "GRM-19") == 42 assert resolve_task_id(mock_client, "GRM-19") == 42
mock_client.list_project_tasks.assert_called_once() mock_client.list_project_tasks.assert_called_once()
def test_not_found_raises(self) -> None: def test_not_found_returns_none(self) -> None:
mock_client = MagicMock() mock_client = MagicMock()
mock_client.list_project_tasks.return_value = [] mock_client.list_project_tasks.return_value = []
with pytest.raises(click.ClickException) as exc: assert resolve_task_id(mock_client, "GRM-99") is None
resolve_task_id(mock_client, "GRM-99")
assert "Could not find" in str(exc.value)
def test_found_on_second_page(self) -> None: def test_found_on_second_page(self) -> None:
"""Task is on page 2 when project has more than 50 tasks.""" """Task is on page 2 when project has more than 50 tasks."""
@@ -79,9 +77,7 @@ class TestResolveTaskId:
mock_client = MagicMock() mock_client = MagicMock()
page1 = [{"id": i, "identifier": f"GRM-{i}"} for i in range(10)] page1 = [{"id": i, "identifier": f"GRM-{i}"} for i in range(10)]
mock_client.list_project_tasks.return_value = page1 mock_client.list_project_tasks.return_value = page1
with pytest.raises(click.ClickException) as exc: assert resolve_task_id(mock_client, "GRM-99") is None
resolve_task_id(mock_client, "GRM-99")
assert "Could not find" in str(exc.value)
assert mock_client.list_project_tasks.call_count == 1 assert mock_client.list_project_tasks.call_count == 1
def test_http_error_propagates(self) -> None: def test_http_error_propagates(self) -> None:
@@ -168,14 +164,16 @@ class TestMain:
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"}) @patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
@patch("scripts.ci.post_merge.VikunjaClient") @patch("scripts.ci.post_merge.VikunjaClient")
def test_resolve_failure_propagates(self, mock_client_cls: MagicMock) -> None: def test_resolve_failure_warns(self, mock_client_cls: MagicMock) -> None:
"""Missing Vikunja task should warn, not fail — the merge already succeeded."""
mock_client = MagicMock() mock_client = MagicMock()
mock_client.list_project_tasks.return_value = [] mock_client.list_project_tasks.return_value = []
mock_client_cls.return_value = mock_client mock_client_cls.return_value = mock_client
runner = CliRunner() runner = CliRunner()
result = runner.invoke(main, ["GRM-20: fix: bug"]) result = runner.invoke(main, ["GRM-20: fix: bug"])
assert result.exit_code == 1 assert result.exit_code == 0
assert "Could not find" in result.output assert "Could not find" in result.output
assert "Warning" in result.output
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"}) @patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
@patch("scripts.ci.post_merge.VikunjaClient") @patch("scripts.ci.post_merge.VikunjaClient")