GRM-59: fix: post-merge workflow failures (4 jobs)
This commit is contained in:
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user