diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 3e2fe6d..60713d4 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,6 +1,7 @@ name: pytest on: + workflow_dispatch: push: branches: - main diff --git a/python/gitea.py b/python/gitea.py index 8b57fdf..54c7ff0 100644 --- a/python/gitea.py +++ b/python/gitea.py @@ -4,10 +4,12 @@ from __future__ import annotations from dataclasses import dataclass from typing import Self +from urllib.parse import quote import httpx DEFAULT_PAGE_SIZE = 100 +EXPECTED_NO_CONTENT = 204 EXPECTED_CREATED = 201 EXPECTED_OK = 200 @@ -222,6 +224,16 @@ class GiteaClient: json=payload, ) + def dispatch_workflow(self, *, owner: str, repo: str, workflow_id: str, ref: str) -> None: + """Trigger a workflow_dispatch run.""" + workflow_path = quote(workflow_id, safe="") + self._request( + "POST", + f"/api/v1/repos/{owner}/{repo}/actions/workflows/{workflow_path}/dispatches", + expected_statuses={EXPECTED_OK, EXPECTED_NO_CONTENT}, + json={"ref": ref}, + ) + def list_run_jobs(self, *, owner: str, repo: str, run_id: str | int) -> list[WorkflowJob]: """List workflow jobs for a specific run.""" jobs: list[WorkflowJob] = [] diff --git a/python/gitea_flake_lock.py b/python/gitea_flake_lock.py index 581438a..c95cbc3 100644 --- a/python/gitea_flake_lock.py +++ b/python/gitea_flake_lock.py @@ -14,6 +14,7 @@ DEFAULT_BASE_BRANCH = "main" DEFAULT_BRANCH = "automation/update-flake-lock" DEFAULT_GITEA_URL = "https://gitea.tmmworkshop.com" PR_LABELS = ["dependencies", "automated", "flake_lock_update"] +PR_CHECK_WORKFLOWS = ["build_systems.yml", "treefmt.yml", "pytest.yml"] PR_TITLE = "Update flake.lock" PR_BODY = "Automated flake.lock update." @@ -57,6 +58,12 @@ def find_flake_lock_pull_request(client: GiteaClient, *, owner: str, repo: str) return pull_requests[0] +def dispatch_pull_request_checks(client: GiteaClient, *, owner: str, repo: str, branch: str) -> None: + """Dispatch the workflows that normally run for pull requests.""" + for workflow in PR_CHECK_WORKFLOWS: + client.dispatch_workflow(owner=owner, repo=repo, workflow_id=workflow, ref=branch) + + def has_worktree_changes() -> bool: """Return whether `flake.lock` has worktree changes.""" result = run_cmd(["git", "diff", "--quiet", "--", "flake.lock"], check=False) @@ -113,6 +120,9 @@ def update( branch=branch, base=base, ) + # We can remove this if Gitea fixes the following issue: + # https://github.com/go-gitea/gitea/issues/33963 + dispatch_pull_request_checks(client, owner=owner, repo=repo_name, branch=branch) typer.echo(pull_request.html_url or f"Pull request #{pull_request.number}") diff --git a/tests/test_gitea_flake_lock.py b/tests/test_gitea_flake_lock.py new file mode 100644 index 0000000..0e75682 --- /dev/null +++ b/tests/test_gitea_flake_lock.py @@ -0,0 +1,113 @@ +"""Tests for Gitea flake.lock automation.""" + +from __future__ import annotations + +from python.gitea import PullRequest +from python.gitea_flake_lock import ( + PR_CHECK_WORKFLOWS, + PR_LABELS, + dispatch_pull_request_checks, + ensure_flake_lock_pull_request, + find_flake_lock_pull_request, +) + + +def _pull_request(number=1, head_branch="automation/update-flake-lock"): + return PullRequest( + number=number, + title="Update flake.lock", + html_url=f"https://gitea.example.test/pulls/{number}", + labels=(), + head_branch=head_branch, + base_branch="main", + ) + + +class FakeGiteaClient: + def __init__(self, pull_requests=None): + self.pull_requests = pull_requests or [] + self.dispatch_calls = [] + self.list_calls = [] + self.create_calls = [] + + def list_open_pull_requests(self, **kwargs): + self.list_calls.append(kwargs) + return self.pull_requests + + def create_pull_request(self, **kwargs): + self.create_calls.append(kwargs) + return _pull_request() + + def dispatch_workflow(self, **kwargs): + self.dispatch_calls.append(kwargs) + + +def test_ensure_flake_lock_pull_request_finds_by_branch(): + pull_request = _pull_request() + client = FakeGiteaClient([pull_request]) + + result = ensure_flake_lock_pull_request( + client, + owner="Richie", + repo="dotfiles", + branch="automation/update-flake-lock", + base="main", + ) + + assert result == pull_request + assert client.list_calls == [ + {"owner": "Richie", "repo": "dotfiles", "head": "automation/update-flake-lock"}, + ] + assert client.create_calls == [] + + +def test_ensure_flake_lock_pull_request_creates_with_labels(): + client = FakeGiteaClient() + + ensure_flake_lock_pull_request( + client, + owner="Richie", + repo="dotfiles", + branch="automation/update-flake-lock", + base="main", + ) + + assert client.create_calls == [ + { + "owner": "Richie", + "repo": "dotfiles", + "title": "Update flake.lock", + "body": "Automated flake.lock update.", + "head": "automation/update-flake-lock", + "base": "main", + "labels": PR_LABELS, + }, + ] + + +def test_find_flake_lock_pull_request_finds_by_label(): + pull_request = _pull_request() + client = FakeGiteaClient([pull_request]) + + result = find_flake_lock_pull_request(client, owner="Richie", repo="dotfiles") + + assert result == pull_request + assert client.list_calls == [ + {"owner": "Richie", "repo": "dotfiles", "labels": ["flake_lock_update"]}, + ] + + +def test_dispatch_pull_request_checks_runs_each_workflow(): + client = FakeGiteaClient() + + dispatch_pull_request_checks(client, owner="Richie", repo="dotfiles", branch="automation/update-flake-lock") + + assert client.dispatch_calls == [ + { + "owner": "Richie", + "repo": "dotfiles", + "workflow_id": workflow, + "ref": "automation/update-flake-lock", + } + for workflow in PR_CHECK_WORKFLOWS + ]