From fb9033f0c589393873ba8e1cef6b7e18585584a4 Mon Sep 17 00:00:00 2001 From: mynk8 <166798988+mynk8@users.noreply.github.com> Date: Thu, 7 Aug 2025 22:17:48 +0530 Subject: [PATCH 1/3] Implement Forgejo event handling in Packit Service --- packit_service/events/__init__.py | 2 + packit_service/events/forgejo/__init__.py | 6 + packit_service/events/forgejo/abstract.py | 17 ++ packit_service/events/forgejo/issue.py | 92 +++++++++ packit_service/events/forgejo/pr.py | 121 ++++++++++++ packit_service/events/forgejo/push.py | 29 +++ packit_service/service/api/webhooks.py | 138 +++++++++++++ packit_service/worker/parser.py | 182 ++++++++++++++++++ .../worker/reporting/reporters/base.py | 4 + .../worker/reporting/reporters/forgejo.py | 46 +++++ 10 files changed, 637 insertions(+) create mode 100644 packit_service/events/forgejo/__init__.py create mode 100644 packit_service/events/forgejo/abstract.py create mode 100644 packit_service/events/forgejo/issue.py create mode 100644 packit_service/events/forgejo/pr.py create mode 100644 packit_service/events/forgejo/push.py create mode 100644 packit_service/worker/reporting/reporters/forgejo.py diff --git a/packit_service/events/__init__.py b/packit_service/events/__init__.py index 58382dc16..472ac9c94 100644 --- a/packit_service/events/__init__.py +++ b/packit_service/events/__init__.py @@ -7,6 +7,7 @@ copr, enums, event, + forgejo, github, gitlab, koji, @@ -22,6 +23,7 @@ anitya.__name__, github.__name__, gitlab.__name__, + forgejo.__name__, koji.__name__, openscanhub.__name__, pagure.__name__, diff --git a/packit_service/events/forgejo/__init__.py b/packit_service/events/forgejo/__init__.py new file mode 100644 index 000000000..5197842b6 --- /dev/null +++ b/packit_service/events/forgejo/__init__.py @@ -0,0 +1,6 @@ +# Copyright Contributors to the Packit project. +# SPDX-License-Identifier: MIT + +from . import abstract, issue, pr, push + +__all__ = [abstract.__name__, push.__name__, issue.__name__, pr.__name__] diff --git a/packit_service/events/forgejo/abstract.py b/packit_service/events/forgejo/abstract.py new file mode 100644 index 000000000..8debe6a51 --- /dev/null +++ b/packit_service/events/forgejo/abstract.py @@ -0,0 +1,17 @@ +# Copyright Contributors to the Packit project. +# SPDX-License-Identifier: MIT + +from typing import Optional + +from ..abstract.base import ForgeIndependent + + +class ForgejoEvent(ForgeIndependent): + def __init__(self, project_url: str, pr_id: Optional[int] = None, **kwargs): + super().__init__(pr_id=pr_id) + self.project_url: str = project_url + # git ref that can be 'git checkout'-ed + self.git_ref: Optional[str] = None + self.identifier: Optional[str] = ( + None # will be shown to users -- e.g. in logs or in the copr-project name + ) diff --git a/packit_service/events/forgejo/issue.py b/packit_service/events/forgejo/issue.py new file mode 100644 index 000000000..13415a896 --- /dev/null +++ b/packit_service/events/forgejo/issue.py @@ -0,0 +1,92 @@ +# Copyright Contributors to the Packit project. +# SPDX-License-Identifier: MIT + +# SPDX-License-Identifier: MIT + +from typing import Optional + +from ogr.abstract import Comment as OgrComment + +from ..abstract.comment import Issue as AbstractIssueCommentEvent +from ..enums import IssueCommentAction +from .abstract import ForgejoEvent + + +class Comment(AbstractIssueCommentEvent, ForgejoEvent): + def __init__( + self, + action: IssueCommentAction, + issue_id: int, + repo_namespace: str, + repo_name: str, + target_repo: str, + project_url: str, + actor: str, + comment: str, + comment_id: int, + tag_name: str = "", + base_ref: Optional[str] = "main", + comment_object: Optional[OgrComment] = None, + dist_git_project_url=None, + ) -> None: + super().__init__( + issue_id=issue_id, + repo_namespace=repo_namespace, + repo_name=repo_name, + project_url=project_url, + comment=comment, + comment_id=comment_id, + tag_name=tag_name, + comment_object=comment_object, + dist_git_project_url=dist_git_project_url, + ) + self.action = action + self.actor = actor + self.base_ref = base_ref + self.target_repo = target_repo + self.identifier = str(issue_id) + + @classmethod + def event_type(cls) -> str: + return "forgejo.issue.Comment" + + @property + def tag_name(self): + """ + For Forgejo issue comments, return the tag_name passed in constructor + without making API calls to avoid authentication issues. + """ + return self._tag_name + + @tag_name.setter + def tag_name(self, value: str) -> None: + self._tag_name = value + + @property + def commit_sha(self) -> Optional[str]: + """ + For Forgejo issue comments, return the commit_sha passed in constructor + without making API calls to avoid authentication issues. + """ + return self._commit_sha + + @commit_sha.setter + def commit_sha(self, value: Optional[str]) -> None: + self._commit_sha = value + + def get_dict(self, default_dict: Optional[dict] = None) -> dict: + """ + Override get_dict to avoid accessing properties that make API calls. + """ + # Get the basic dict from CommentEvent, not from Issue to avoid tag_name access + from ..abstract.comment import CommentEvent + + result = CommentEvent.get_dict(self, default_dict=default_dict) + + # Add the specific fields we need without triggering API calls + result["action"] = self.action.value + result["issue_id"] = self.issue_id + result["tag_name"] = self._tag_name # Use the private attribute directly + result["commit_sha"] = self._commit_sha # Use the private attribute directly + + return result diff --git a/packit_service/events/forgejo/pr.py b/packit_service/events/forgejo/pr.py new file mode 100644 index 000000000..a2138998b --- /dev/null +++ b/packit_service/events/forgejo/pr.py @@ -0,0 +1,121 @@ +# Copyright Contributors to the Packit project. +# SPDX-License-Identifier: MIT + +from typing import Optional + +from ogr.abstract import Comment as OgrComment +from ogr.abstract import GitProject + +from packit_service.service.db_project_events import AddPullRequestEventToDb + +from ..abstract.comment import PullRequest as AbstractPRCommentEvent +from ..enums import PullRequestAction, PullRequestCommentAction +from .abstract import ForgejoEvent + + +class Action(AddPullRequestEventToDb, ForgejoEvent): + def __init__( + self, + action: PullRequestAction, + pr_id: int, + base_repo_namespace: str, + base_repo_name: str, + base_ref: str, + target_repo_namespace: str, + target_repo_name: str, + project_url: str, + commit_sha: str, + commit_sha_before: str, + actor: str, + body: str, + ): + super().__init__(project_url=project_url, pr_id=pr_id) + self.action = action + self.base_repo_namespace = base_repo_namespace + self.base_repo_name = base_repo_name + self.base_ref = base_ref + self.target_repo_namespace = target_repo_namespace + self.target_repo_name = target_repo_name + self.commit_sha = commit_sha + self.commit_sha_before = commit_sha_before + self.actor = actor + self.identifier = str(pr_id) + self._pr_id = pr_id + self.git_ref = None # use pr_id for checkout + self.body = body + + def get_dict(self, default_dict: Optional[dict] = None) -> dict: + result = super().get_dict() + result["action"] = result["action"].value + return result + + @classmethod + def event_type(cls) -> str: + return "forgejo.pr.Action" + + def get_base_project(self) -> GitProject: + return self.project.service.get_project( + namespace=self.target_repo_namespace, + repo=self.target_repo_name, + ) + + +class Comment(AbstractPRCommentEvent, ForgejoEvent): + def __init__( + self, + action: PullRequestCommentAction, + pr_id: int, + base_repo_namespace: str, + base_repo_name: Optional[str], + base_ref: Optional[str], + target_repo_namespace: str, + target_repo_name: str, + project_url: str, + actor: str, + comment: str, + comment_id: int, + commit_sha: Optional[str] = None, + comment_object: Optional[OgrComment] = None, + ) -> None: + super().__init__( + pr_id=pr_id, + project_url=project_url, + comment=comment, + comment_id=comment_id, + commit_sha=commit_sha, + comment_object=comment_object, + ) + self.action = action + self.base_repo_namespace = base_repo_namespace + self.base_repo_name = base_repo_name + self.base_ref = base_ref + self.target_repo_namespace = target_repo_namespace + self.target_repo_name = target_repo_name + self.actor = actor + self.identifier = str(pr_id) + self.git_ref = None + self.pr_id = pr_id + + @classmethod + def event_type(cls) -> str: + return "forgejo.pr.Comment" + + def get_dict(self, default_dict: Optional[dict] = None) -> dict: + """ + Override get_dict to avoid accessing properties that make API calls. + Use private attributes directly, similar to forgejo/issue.py. + """ + from ..abstract.comment import CommentEvent + + result = CommentEvent.get_dict(self, default_dict=default_dict) + result.pop("_comment_object") + result["action"] = self.action.value + result["pr_id"] = self.pr_id + result["commit_sha"] = self._commit_sha + return result + + def get_base_project(self) -> GitProject: + return self.project.service.get_project( + namespace=self.target_repo_namespace, + repo=self.target_repo_name, + ) diff --git a/packit_service/events/forgejo/push.py b/packit_service/events/forgejo/push.py new file mode 100644 index 000000000..cc88b21ba --- /dev/null +++ b/packit_service/events/forgejo/push.py @@ -0,0 +1,29 @@ +# Copyright Contributors to the Packit project. +# SPDX-License-Identifier: MIT + +from packit_service.service.db_project_events import AddBranchPushEventToDb + +from .abstract import ForgejoEvent + + +class Commit(AddBranchPushEventToDb, ForgejoEvent): + def __init__( + self, + repo_namespace: str, + repo_name: str, + git_ref: str, + project_url: str, + commit_sha: str, + commit_sha_before: str, + ): + super().__init__(project_url=project_url) + self.repo_namespace = repo_namespace + self.repo_name = repo_name + self.git_ref = git_ref + self.commit_sha = commit_sha + self.commit_sha_before = commit_sha_before + self.identifier = git_ref + + @classmethod + def event_type(cls) -> str: + return "forgejo.push.Commit" diff --git a/packit_service/service/api/webhooks.py b/packit_service/service/api/webhooks.py index 1e792f4de..01fbc80a9 100644 --- a/packit_service/service/api/webhooks.py +++ b/packit_service/service/api/webhooks.py @@ -45,6 +45,15 @@ }, ) +ping_payload_forgejo = ns.model( + "Forgejo webhook ping", + { + "zen": fields.String(required=False), + "hook_id": fields.String(required=False), + "hook": fields.String(required=False), + }, +) + github_webhook_calls = Counter( "github_webhook_calls", "Number of times the GitHub webhook is called", @@ -344,3 +353,132 @@ def interested(): logger.debug(f"{event_type} {' (not interested)' if not _interested else ''}") return _interested + + +forgejo_webhook_calls = Counter( + "forgejo_webhook_calls", + "Number of times the Forgejo webhook is called", + ["result", "process_id"], +) + + +@ns.route("/forgejo") +class ForgejoWebhook(Resource): + @ns.response(HTTPStatus.OK.value, "Webhook accepted, returning reply") + @ns.response( + HTTPStatus.ACCEPTED.value, + "Webhook accepted, request is being processed", + ) + @ns.response(HTTPStatus.BAD_REQUEST.value, "Bad request data") + @ns.response(HTTPStatus.UNAUTHORIZED.value, "X-Forgejo-Signature validation failed") + @ns.expect(ping_payload_forgejo) + def post(self): + msg = request.json + + if not msg: + logger.debug("/webhooks/forgejo: no JSON data received.") + forgejo_webhook_calls.labels(result="no_data", process_id=os.getpid()).inc() + + return "No JSON data.", HTTPStatus.BAD_REQUEST + + if all([msg.get("zen"), msg.get("hook_id"), msg.get("hook")]): + logger.debug(f"/webhooks/forgejo received ping event: {msg['hook']}") + forgejo_webhook_calls.labels(result="pong", process_id=os.getpid()).inc() + return "Pong!", HTTPStatus.OK + # TODO + # try: + # self.validate_token() + # except ValidationFailed as exc: + # logger.info(f"/webhooks/forgejo {exc}") + # forgejo_webhook_calls.labels( + # result="invalid_signature", + # process_id=os.getpid(), + # ).inc() + # return str(exc), HTTPStatus.UNAUTHORIZED + # + if not self.interested(msg): + forgejo_webhook_calls.labels( + result="not_interested", + process_id=os.getpid(), + ).inc() + return "Thanks but we don't care about this event", HTTPStatus.ACCEPTED + + celery_app.send_task( + name=getenv("CELERY_MAIN_TASK_NAME") or CELERY_DEFAULT_MAIN_TASK_NAME, + kwargs={ + "event": msg, + "source": "forgejo", + "event_type": request.headers.get("X-Forgejo-Event"), + }, + ) + forgejo_webhook_calls.labels(result="accepted", process_id=os.getpid()).inc() + + return "Webhook accepted. We thank you, Forgejo.", HTTPStatus.ACCEPTED + + def validate_token(self): + """ + Validate the Forgejo webhook signature. + The signature is a direct SHA256 HMAC hex digest of the raw request body + using the webhook secret as the key, in a similar fashion to Github. + + """ + if "X-Forgejo-Signature" not in request.headers: + logger.debug("Ain't validating signatures.") + return + + if not (webhook_secret := getenv("WEBHOOK_SECRET")): + msg = "'webhook_secret' not specified in the config." + logger.error(msg) + raise ValidationFailed(msg) + + # Get raw payload + + payload = request.get_data() + if not payload: + msg = "No payload received." + logger.error(msg) + raise ValidationFailed(msg) + + data_hmac = hmac.new(webhook_secret.encode(), msg=payload, digestmod=sha256) + payload_signature = data_hmac.hexdigest() + header_sig = request.headers["X-Forgejo-Signature"] + + if header_sig != payload_signature: + msg = "Payload signature validation failed." + + logger.warning(msg) + logger.debug( + f"X-Forgejo-Signature: {header_sig!r} != computed: {payload_signature}", + ) + raise ValidationFailed(msg) + + @staticmethod + def interested(msg): + """ + + Check whether we want to process this event. + + + Args: + msg: The webhook payload as a dictionary + + Returns: + bool: False if we are not interested in this kind of event + """ + event = request.headers.get("X-Forgejo-Event") + uuid = request.headers.get("X-Forgejo-Delivery") + action = msg.get("action") if msg else None + deleted = msg.get("deleted") if msg else None + + interests = { + "push": not deleted, + "release": action == "published", + "issues": action in {"opened", "edited", "closed", "reopened"}, + "issue_comment": action in {"created", "edited"}, + "pull_request": action in {"opened", "edited", "closed", "reopened", "synchronize"}, + } + + _interested = interests.get(event or "", False) + + logger.debug(f"{event} {uuid}{'' if _interested else ' (not interested)'}") + return _interested diff --git a/packit_service/worker/parser.py b/packit_service/worker/parser.py index 7128e5057..f5f68c3b0 100644 --- a/packit_service/worker/parser.py +++ b/packit_service/worker/parser.py @@ -26,6 +26,7 @@ abstract, anitya, copr, + forgejo, github, gitlab, koji, @@ -1924,6 +1925,182 @@ def parse_logdetective_analysis_event(event) -> Optional[logdetective.Result]: pr_id=pr_id, ) + @staticmethod + def parse_forgejo_push_event(event: dict) -> Optional[forgejo.push.Commit]: + raw_ref = event.get("ref") + before = event.get("before") + after = event.get("after") + pusher = nested_get(event, "pusher", "login") or nested_get(event, "pusher", "name") + + if not (raw_ref and after and before and pusher): + return None + + # Forgejo sets `deleted` identically to GitHub + if event.get("deleted"): + logger.info(f"Forgejo push event on '{raw_ref}' by {pusher} to delete ref") + + return None + + # Number of commits introduced by this push + commits = event.get("commits") or [] + num_commits = len(commits) + + # Strip the ref prefix to get the branch/tag name + _, ref_type, ref_name = raw_ref.split("/", 2) + if ref_type != "heads": + logger.debug(f"Forgejo push event ignored – not a branch push ('{raw_ref}')") + return None + + logger.info( + f"Forgejo push event on '{ref_name}': " + f"{before[:8]} → {after[:8]} by {pusher} " + f"({num_commits} {'commit' if num_commits == 1 else 'commits'})" + ) + + repo_namespace = nested_get(event, "repository", "owner", "login") + repo_name = nested_get(event, "repository", "name") + repo_url = nested_get(event, "repository", "html_url") + + if not (repo_namespace and repo_name): + logger.warning("Forgejo push event missing repository namespace/name") + return None + + return forgejo.push.Commit( + repo_namespace=repo_namespace, + repo_name=repo_name, + git_ref=ref_name, + project_url=repo_url, + commit_sha=after, + commit_sha_before=before, + ) + + @staticmethod + def parse_forgejo_pr_event(event: dict) -> Optional[forgejo.pr.Action]: + """ + Parse Forgejo PR action events, only triggering for relevant actions. + Supported actions: 'opened', 'reopened', 'synchronize'. + Skips others like 'closed'. + + """ + action_str = event.get("action") + # Only trigger for these actions + supported_actions = {"opened", "reopened", "synchronize"} + if action_str not in supported_actions: + logger.info(f"Skipping PR action: {action_str}") + return None + + pr = event.get("pull_request") + if not pr: + logger.warning("No pull_request in event.") + return None + + pr_id = pr.get("number") + actor = event.get("sender", {}).get("login") + repo = event.get("repository", {}) + base = pr.get("base") + head = pr.get("head") + body = pr.get("body") + + # Check all required nested fields + try: + base_repo_namespace = base["repo"]["owner"]["login"] + base_repo_name = base["repo"]["name"] + base_ref = base["ref"] + target_repo_namespace = head["repo"]["owner"]["login"] + target_repo_name = head["repo"]["name"] + project_url = repo["html_url"] + commit_sha = head["sha"] + except (TypeError, KeyError): + logger.warning("Missing required nested fields in PR event.") + return None + + return forgejo.pr.Action( + action=PullRequestAction[action_str], + pr_id=pr_id, + base_repo_namespace=base_repo_namespace, + base_repo_name=base_repo_name, + base_ref=base_ref, + target_repo_namespace=target_repo_namespace, + target_repo_name=target_repo_name, + project_url=project_url, + commit_sha=commit_sha, + commit_sha_before=event.get("before", ""), # Optional, might be empty + actor=actor, + body=body, + ) + + @staticmethod + def parse_forgejo_comment_event( + event: dict, + ) -> Optional[Union[forgejo.pr.Comment, forgejo.issue.Comment]]: + """Since Forgejo treats PR as special issues the comments are basically on issues, + we need to distinguish between Forgejo issue and PR comments and parse accordingly.""" + + issue_id = nested_get(event, "issue", "number") + action = event.get("action") + if action not in {"created", "edited"} or not issue_id: + return None + + # Only treat as PR if 'pull_request' is present and not None + issue_dict = event.get("issue", {}) + is_pr = "pull_request" in issue_dict and issue_dict["pull_request"] is not None + + comment = nested_get(event, "comment", "body") + comment_id = nested_get(event, "comment", "id") + logger.info( + f"Forgejo {'PR' if is_pr else 'issue'}#{issue_id} " + f"comment: {comment!r} id#{comment_id} {action!r} event." + ) + + base_repo_namespace = nested_get(event, "issue", "user", "login") + base_repo_name = nested_get(event, "repository", "name") + + user_login = nested_get(event, "comment", "user", "login") + target_repo_namespace = nested_get(event, "repository", "owner", "login") + + target_repo_name = nested_get(event, "repository", "name") + https_url = nested_get(event, "repository", "html_url") + + if not ( + base_repo_name and base_repo_namespace and target_repo_name and target_repo_namespace + ): + logger.warning("Missing repo info in Forgejo event.") + return None + + if not user_login: + logger.warning("No user login in comment.") + return None + + if is_pr: + return forgejo.pr.Comment( + action=PullRequestCommentAction[action], + pr_id=issue_id, + base_ref="", + base_repo_namespace=base_repo_namespace, + base_repo_name=base_repo_name, + target_repo_namespace=target_repo_namespace, + target_repo_name=target_repo_name, + project_url=https_url, + actor=user_login, + comment=comment, + comment_id=comment_id, + commit_sha=None, + ) + return forgejo.issue.Comment( + action=IssueCommentAction[action], + issue_id=issue_id, + repo_namespace=base_repo_namespace, + repo_name=base_repo_name, + target_repo=f"{target_repo_namespace}/{target_repo_name}", + project_url=https_url, + actor=user_login, + comment=comment, + comment_id=comment_id, + tag_name="", + base_ref="", + dist_git_project_url=None, + ) + # The .__func__ are needed for Python < 3.10 MAPPING: ClassVar[dict[str, dict[str, Callable]]] = { "github": { @@ -1944,6 +2121,11 @@ def parse_logdetective_analysis_event(event) -> Optional[logdetective.Result]: "Pipeline Hook": parse_pipeline_event.__func__, # type: ignore "Release Hook": parse_gitlab_release_event.__func__, # type: ignore }, + "forgejo": { + "push": parse_forgejo_push_event.__func__, # type: ignore + "issue_comment": parse_forgejo_comment_event.__func__, # type: ignore + "pull_request": parse_forgejo_pr_event.__func__, # type: ignore + }, "fedora-messaging": { "pagure.pull-request.flag.added": parse_pagure_pr_flag_event.__func__, # type: ignore "pagure.pull-request.flag.updated": parse_pagure_pr_flag_event.__func__, # type: ignore diff --git a/packit_service/worker/reporting/reporters/base.py b/packit_service/worker/reporting/reporters/base.py index 7c9905c92..c34eaa873 100644 --- a/packit_service/worker/reporting/reporters/base.py +++ b/packit_service/worker/reporting/reporters/base.py @@ -6,6 +6,7 @@ from typing import Callable, Optional, Union from ogr.abstract import GitProject, PullRequest +from ogr.services.forgejo import ForgejoProject from ogr.services.github import GithubProject from ogr.services.gitlab import GitlabProject from ogr.services.pagure import PagureProject @@ -56,6 +57,7 @@ def get_instance( The `project` determines type of the reporter returned. All other parameters are passed to the initializer of the chosen reporter. """ + from .forgejo import StatusReporterForgejo from .github import StatusReporterGithubChecks from .gitlab import StatusReporterGitlab from .pagure import StatusReporterPagure @@ -67,6 +69,8 @@ def get_instance( reporter = StatusReporterGitlab elif isinstance(project, PagureProject): reporter = StatusReporterPagure + elif isinstance(project, ForgejoProject): + reporter = StatusReporterForgejo return reporter(project, commit_sha, packit_user, project_event_id, pr_id) @property diff --git a/packit_service/worker/reporting/reporters/forgejo.py b/packit_service/worker/reporting/reporters/forgejo.py new file mode 100644 index 000000000..76a706320 --- /dev/null +++ b/packit_service/worker/reporting/reporters/forgejo.py @@ -0,0 +1,46 @@ +# Copyright Contributors to the Packit project. +# SPDX-License-Identifier: MIT + +import logging +from typing import Optional + +from ogr.abstract import CommitStatus +from ogr.exceptions import ForgejoAPIException + +from packit_service.worker.reporting import BaseCommitStatus +from packit_service.worker.reporting.reporters.base import StatusReporter + +logger = logging.getLogger(__name__) + + +class StatusReporterForgejo(StatusReporter): + @staticmethod + def get_commit_status(state: BaseCommitStatus): + mapped_state = StatusReporter.get_commit_status(state) + + if mapped_state == CommitStatus.error: + mapped_state = CommitStatus.failure + return mapped_state + + def set_status( + self, + state: BaseCommitStatus, + description: str, + check_name: str, + url: str = "", + links_to_external_services: Optional[dict[str, str]] = None, + markdown_content: Optional[str] = None, + target_branch: Optional[str] = None, + ): + state_to_set = self.get_commit_status(state) + logger.debug(f"Setting Forgejo status '{state_to_set.name}'") + + try: + self.project_with_commit.set_commit_status( + self.commit_sha, state_to_set, url, description, check_name, trim=True + ) + + except ForgejoAPIException as e: + logger.debug(f"Failed to set status: {e}") + + self._add_commit_comment_with_status(state, description, check_name, url) From c975387d655c0e6a63ef8bf76c820853bb90a8e7 Mon Sep 17 00:00:00 2001 From: Matej Focko Date: Tue, 2 Sep 2025 13:07:10 +0200 Subject: [PATCH 2/3] test(data): add Forgejo webhooks for tests --- .../data/webhooks/forgejo/issue_comment.json | 221 +++++++ tests/data/webhooks/forgejo/pr_comment.json | 581 ++++++++++++++++++ tests/data/webhooks/forgejo/pr_opened.json | 484 +++++++++++++++ .../webhooks/forgejo/push_new_branch.json | 197 ++++++ .../forgejo/push_with_many_commits.json | 277 +++++++++ .../forgejo/push_with_one_commit.json | 197 ++++++ tests/data/webhooks/forgejo/tag_push.json | 176 ++++++ 7 files changed, 2133 insertions(+) create mode 100644 tests/data/webhooks/forgejo/issue_comment.json create mode 100644 tests/data/webhooks/forgejo/pr_comment.json create mode 100644 tests/data/webhooks/forgejo/pr_opened.json create mode 100644 tests/data/webhooks/forgejo/push_new_branch.json create mode 100644 tests/data/webhooks/forgejo/push_with_many_commits.json create mode 100644 tests/data/webhooks/forgejo/push_with_one_commit.json create mode 100644 tests/data/webhooks/forgejo/tag_push.json diff --git a/tests/data/webhooks/forgejo/issue_comment.json b/tests/data/webhooks/forgejo/issue_comment.json new file mode 100644 index 000000000..f3c945c18 --- /dev/null +++ b/tests/data/webhooks/forgejo/issue_comment.json @@ -0,0 +1,221 @@ +{ + "action": "created", + "issue": { + "id": 1118, + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/issues/1", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/issues/1", + "number": 1, + "user": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@example.com", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "en-US", + "is_admin": false, + "last_login": "2025-08-18T17:54:40Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": true, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "original_author": "", + "original_author_id": 0, + "title": "new issue", + "body": "", + "ref": "", + "assets": [], + "labels": [], + "milestone": null, + "assignee": null, + "assignees": null, + "state": "open", + "is_locked": false, + "comments": 0, + "created_at": "2025-08-19T08:34:42Z", + "updated_at": "2025-08-19T08:36:14Z", + "closed_at": null, + "due_date": null, + "pull_request": null, + "repository": { + "id": 681, + "name": "test-repo-to-generate-events", + "owner": "packit", + "full_name": "packit/test-repo-to-generate-events" + }, + "pin_order": 0 + }, + "comment": { + "id": 3144, + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/issues/1#issuecomment-3144", + "pull_request_url": "", + "issue_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/issues/1", + "user": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "original_author": "", + "original_author_id": 0, + "body": "issue comment", + "assets": [], + "created_at": "2025-08-19T08:36:14Z", + "updated_at": "2025-08-19T08:36:14Z" + }, + "repository": { + "id": 681, + "owner": { + "id": 450, + "login": "packit", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://v10.next.forgejo.org/avatars/ccef19de5d28148945dcca5aa2249c00", + "html_url": "https://v10.next.forgejo.org/packit", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-01-29T09:25:44Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packit" + }, + "name": "test-repo-to-generate-events", + "full_name": "packit/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 27, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/packit/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 1, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:58:42Z", + "updated_at": "2025-08-19T08:33:58Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + }, + "sender": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "is_pull": false +} diff --git a/tests/data/webhooks/forgejo/pr_comment.json b/tests/data/webhooks/forgejo/pr_comment.json new file mode 100644 index 000000000..1afcb99d9 --- /dev/null +++ b/tests/data/webhooks/forgejo/pr_comment.json @@ -0,0 +1,581 @@ +{ + "action": "created", + "issue": { + "id": 1119, + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/issues/2", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2", + "number": 2, + "user": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@example.com", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "en-US", + "is_admin": false, + "last_login": "2025-08-18T17:54:40Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": true, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "original_author": "", + "original_author_id": 0, + "title": "feature-branch-to-be-merged-via-pr", + "body": "", + "ref": "", + "assets": [], + "labels": [], + "milestone": null, + "assignee": null, + "assignees": null, + "state": "open", + "is_locked": false, + "comments": 0, + "created_at": "2025-08-19T08:44:27Z", + "updated_at": "2025-08-19T08:45:58Z", + "closed_at": null, + "due_date": null, + "pull_request": { + "merged": false, + "merged_at": null, + "draft": false, + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2" + }, + "repository": { + "id": 681, + "name": "test-repo-to-generate-events", + "owner": "packit", + "full_name": "packit/test-repo-to-generate-events" + }, + "pin_order": 0 + }, + "pull_request": { + "id": 505, + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2", + "number": 2, + "user": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@example.com", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "en-US", + "is_admin": false, + "last_login": "2025-08-18T17:54:40Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": true, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "title": "feature-branch-to-be-merged-via-pr", + "body": "", + "labels": [], + "milestone": null, + "assignee": null, + "assignees": null, + "requested_reviewers": null, + "requested_reviewers_teams": null, + "state": "open", + "draft": false, + "is_locked": false, + "comments": 0, + "review_comments": 0, + "additions": 0, + "deletions": 0, + "changed_files": 0, + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2", + "diff_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2.diff", + "patch_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2.patch", + "mergeable": true, + "merged": false, + "merged_at": null, + "merge_commit_sha": null, + "merged_by": null, + "allow_maintainer_edit": false, + "base": { + "label": "main", + "ref": "main", + "sha": "fa9bbf46c6ae89b755716683814b03b6a2c82263", + "repo_id": 681, + "repo": { + "id": 681, + "owner": { + "id": 450, + "login": "packit", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://v10.next.forgejo.org/avatars/ccef19de5d28148945dcca5aa2249c00", + "html_url": "https://v10.next.forgejo.org/packit", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-01-29T09:25:44Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packit" + }, + "name": "test-repo-to-generate-events", + "full_name": "packit/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 28, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/packit/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 1, + "open_pr_counter": 1, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:58:42Z", + "updated_at": "2025-08-19T08:37:25Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + } + }, + "head": { + "label": "feature-branch-to-be-merged-via-pr", + "ref": "feature-branch-to-be-merged-via-pr", + "sha": "37182f59ccaaa21584e7b580442fd33b60fe3f80", + "repo_id": 682, + "repo": { + "id": 682, + "owner": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "name": "test-repo-to-generate-events", + "full_name": "mfocko/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": true, + "template": false, + "parent": { + "id": 681, + "owner": { + "id": 450, + "login": "packit", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://v10.next.forgejo.org/avatars/ccef19de5d28148945dcca5aa2249c00", + "html_url": "https://v10.next.forgejo.org/packit", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-01-29T09:25:44Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packit" + }, + "name": "test-repo-to-generate-events", + "full_name": "packit/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 28, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/packit/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 1, + "open_pr_counter": 1, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:58:42Z", + "updated_at": "2025-08-19T08:37:25Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + }, + "mirror": false, + "size": 28, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/mfocko/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/mfocko/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/mfocko/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/mfocko/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/mfocko/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 1, + "open_issues_count": 0, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:59:49Z", + "updated_at": "2025-08-19T08:43:17Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": false, + "has_wiki": false, + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "has_releases": false, + "has_packages": false, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + } + }, + "merge_base": "fa9bbf46c6ae89b755716683814b03b6a2c82263", + "due_date": null, + "created_at": "2025-08-19T08:44:27Z", + "updated_at": "2025-08-19T08:45:58Z", + "closed_at": null, + "pin_order": 0 + }, + "comment": { + "id": 3146, + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2#issuecomment-3146", + "pull_request_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2", + "issue_url": "", + "user": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "original_author": "", + "original_author_id": 0, + "body": "PR comment", + "assets": [], + "created_at": "2025-08-19T08:45:58Z", + "updated_at": "2025-08-19T08:45:58Z" + }, + "repository": { + "id": 681, + "owner": { + "id": 450, + "login": "packit", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://v10.next.forgejo.org/avatars/ccef19de5d28148945dcca5aa2249c00", + "html_url": "https://v10.next.forgejo.org/packit", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-01-29T09:25:44Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packit" + }, + "name": "test-repo-to-generate-events", + "full_name": "packit/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 28, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/packit/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 1, + "open_pr_counter": 1, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:58:42Z", + "updated_at": "2025-08-19T08:37:25Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + }, + "sender": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "is_pull": true +} diff --git a/tests/data/webhooks/forgejo/pr_opened.json b/tests/data/webhooks/forgejo/pr_opened.json new file mode 100644 index 000000000..32f6304f2 --- /dev/null +++ b/tests/data/webhooks/forgejo/pr_opened.json @@ -0,0 +1,484 @@ +{ + "action": "opened", + "number": 2, + "pull_request": { + "id": 505, + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2", + "number": 2, + "user": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@example.com", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "en-US", + "is_admin": false, + "last_login": "2025-08-18T17:54:40Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": true, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "title": "feature-branch-to-be-merged-via-pr", + "body": "", + "labels": [], + "milestone": null, + "assignee": null, + "assignees": null, + "requested_reviewers": null, + "requested_reviewers_teams": null, + "state": "open", + "draft": false, + "is_locked": false, + "comments": 0, + "review_comments": 0, + "additions": 0, + "deletions": 0, + "changed_files": 0, + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2", + "diff_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2.diff", + "patch_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/pulls/2.patch", + "mergeable": true, + "merged": false, + "merged_at": null, + "merge_commit_sha": null, + "merged_by": null, + "allow_maintainer_edit": false, + "base": { + "label": "main", + "ref": "main", + "sha": "fa9bbf46c6ae89b755716683814b03b6a2c82263", + "repo_id": 681, + "repo": { + "id": 681, + "owner": { + "id": 450, + "login": "packit", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://v10.next.forgejo.org/avatars/ccef19de5d28148945dcca5aa2249c00", + "html_url": "https://v10.next.forgejo.org/packit", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-01-29T09:25:44Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packit" + }, + "name": "test-repo-to-generate-events", + "full_name": "packit/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 28, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/packit/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 1, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:58:42Z", + "updated_at": "2025-08-19T08:37:25Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + } + }, + "head": { + "label": "feature-branch-to-be-merged-via-pr", + "ref": "feature-branch-to-be-merged-via-pr", + "sha": "37182f59ccaaa21584e7b580442fd33b60fe3f80", + "repo_id": 682, + "repo": { + "id": 682, + "owner": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "name": "test-repo-to-generate-events", + "full_name": "mfocko/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": true, + "template": false, + "parent": { + "id": 681, + "owner": { + "id": 450, + "login": "packit", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://v10.next.forgejo.org/avatars/ccef19de5d28148945dcca5aa2249c00", + "html_url": "https://v10.next.forgejo.org/packit", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-01-29T09:25:44Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packit" + }, + "name": "test-repo-to-generate-events", + "full_name": "packit/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 28, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/packit/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 1, + "open_pr_counter": 1, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:58:42Z", + "updated_at": "2025-08-19T08:37:25Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + }, + "mirror": false, + "size": 28, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/mfocko/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/mfocko/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/mfocko/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/mfocko/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/mfocko/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 0, + "watchers_count": 1, + "open_issues_count": 0, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:59:49Z", + "updated_at": "2025-08-19T08:43:17Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": false, + "has_wiki": false, + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": false, + "has_releases": false, + "has_packages": false, + "has_actions": false, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + } + }, + "merge_base": "fa9bbf46c6ae89b755716683814b03b6a2c82263", + "due_date": null, + "created_at": "2025-08-19T08:44:27Z", + "updated_at": "2025-08-19T08:44:34Z", + "closed_at": null, + "pin_order": 0 + }, + "requested_reviewer": null, + "repository": { + "id": 681, + "owner": { + "id": 450, + "login": "packit", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://v10.next.forgejo.org/avatars/ccef19de5d28148945dcca5aa2249c00", + "html_url": "https://v10.next.forgejo.org/packit", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-01-29T09:25:44Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packit" + }, + "name": "test-repo-to-generate-events", + "full_name": "packit/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 28, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/packit/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 1, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:58:42Z", + "updated_at": "2025-08-19T08:37:25Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + }, + "sender": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "commit_id": "", + "review": null +} diff --git a/tests/data/webhooks/forgejo/push_new_branch.json b/tests/data/webhooks/forgejo/push_new_branch.json new file mode 100644 index 000000000..43c223a3e --- /dev/null +++ b/tests/data/webhooks/forgejo/push_new_branch.json @@ -0,0 +1,197 @@ +{ + "ref": "refs/heads/new-branch", + "before": "0000000000000000000000000000000000000000", + "after": "24f660b69e4608f63ddd55d5c5b459f348e5f272", + "compare_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/compare/fa9bbf46c6ae89b755716683814b03b6a2c82263...24f660b69e4608f63ddd55d5c5b459f348e5f272", + "commits": [ + { + "id": "24f660b69e4608f63ddd55d5c5b459f348e5f272", + "message": "huh\n\nSigned-off-by: Matej Focko \u003cmf@example.com\u003e\n", + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/commit/24f660b69e4608f63ddd55d5c5b459f348e5f272", + "author": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "committer": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "verification": null, + "timestamp": "2025-08-19T10:36:37+02:00", + "added": [], + "removed": [], + "modified": [] + } + ], + "total_commits": 1, + "head_commit": { + "id": "24f660b69e4608f63ddd55d5c5b459f348e5f272", + "message": "huh\n\nSigned-off-by: Matej Focko \u003cmf@example.com\u003e\n", + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/commit/24f660b69e4608f63ddd55d5c5b459f348e5f272", + "author": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "committer": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "verification": null, + "timestamp": "2025-08-19T10:36:37+02:00", + "added": [], + "removed": [], + "modified": [] + }, + "repository": { + "id": 681, + "owner": { + "id": 450, + "login": "packit", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://v10.next.forgejo.org/avatars/ccef19de5d28148945dcca5aa2249c00", + "html_url": "https://v10.next.forgejo.org/packit", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-01-29T09:25:44Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packit" + }, + "name": "test-repo-to-generate-events", + "full_name": "packit/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 27, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/packit/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 1, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:58:42Z", + "updated_at": "2025-08-19T08:33:58Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + }, + "pusher": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "sender": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + } +} diff --git a/tests/data/webhooks/forgejo/push_with_many_commits.json b/tests/data/webhooks/forgejo/push_with_many_commits.json new file mode 100644 index 000000000..02d8f0514 --- /dev/null +++ b/tests/data/webhooks/forgejo/push_with_many_commits.json @@ -0,0 +1,277 @@ +{ + "ref": "refs/heads/main", + "before": "c85008a0b44a60370e48a45a9f9d39da5b472e11", + "after": "fa9bbf46c6ae89b755716683814b03b6a2c82263", + "compare_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/compare/c85008a0b44a60370e48a45a9f9d39da5b472e11...fa9bbf46c6ae89b755716683814b03b6a2c82263", + "commits": [ + { + "id": "fa9bbf46c6ae89b755716683814b03b6a2c82263", + "message": "seventh of many commits\n\nSigned-off-by: Matej Focko \u003cmf@example.com\u003e\n", + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/commit/fa9bbf46c6ae89b755716683814b03b6a2c82263", + "author": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "committer": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "verification": null, + "timestamp": "2025-08-19T10:31:23+02:00", + "added": [], + "removed": [], + "modified": [] + }, + { + "id": "da5184eb5c8a16247ce3025af1b72da8086c1337", + "message": "sixth of many commits\n\nSigned-off-by: Matej Focko \u003cmf@example.com\u003e\n", + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/commit/da5184eb5c8a16247ce3025af1b72da8086c1337", + "author": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "committer": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "verification": null, + "timestamp": "2025-08-19T10:31:19+02:00", + "added": [], + "removed": [], + "modified": [] + }, + { + "id": "b8a8746d7b91bcff18e9f59a9a1b440089fc2dd5", + "message": "fifth of many commits\n\nSigned-off-by: Matej Focko \u003cmf@example.com\u003e\n", + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/commit/b8a8746d7b91bcff18e9f59a9a1b440089fc2dd5", + "author": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "committer": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "verification": null, + "timestamp": "2025-08-19T10:31:15+02:00", + "added": [], + "removed": [], + "modified": [] + }, + { + "id": "8d6033f1165c66acf8a4e2d0dffaf3ab10182a93", + "message": "fourth of many commits\n\nSigned-off-by: Matej Focko \u003cmf@example.com\u003e\n", + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/commit/8d6033f1165c66acf8a4e2d0dffaf3ab10182a93", + "author": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "committer": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "verification": null, + "timestamp": "2025-08-19T10:31:10+02:00", + "added": [], + "removed": [], + "modified": [] + }, + { + "id": "28c86b1224488c6fbf2b8018ebdee9b7251e804a", + "message": "third of many commits\n\nSigned-off-by: Matej Focko \u003cmf@example.com\u003e\n", + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/commit/28c86b1224488c6fbf2b8018ebdee9b7251e804a", + "author": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "committer": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "verification": null, + "timestamp": "2025-08-19T10:31:05+02:00", + "added": [], + "removed": [], + "modified": [] + } + ], + "total_commits": 7, + "head_commit": { + "id": "fa9bbf46c6ae89b755716683814b03b6a2c82263", + "message": "seventh of many commits\n\nSigned-off-by: Matej Focko \u003cmf@example.com\u003e\n", + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/commit/fa9bbf46c6ae89b755716683814b03b6a2c82263", + "author": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "committer": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "verification": null, + "timestamp": "2025-08-19T10:31:23+02:00", + "added": [], + "removed": [], + "modified": [] + }, + "repository": { + "id": 681, + "owner": { + "id": 450, + "login": "packit", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://v10.next.forgejo.org/avatars/ccef19de5d28148945dcca5aa2249c00", + "html_url": "https://v10.next.forgejo.org/packit", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-01-29T09:25:44Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packit" + }, + "name": "test-repo-to-generate-events", + "full_name": "packit/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 23, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/packit/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 0, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:58:42Z", + "updated_at": "2025-08-19T08:29:32Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + }, + "pusher": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "sender": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + } +} diff --git a/tests/data/webhooks/forgejo/push_with_one_commit.json b/tests/data/webhooks/forgejo/push_with_one_commit.json new file mode 100644 index 000000000..93aed4420 --- /dev/null +++ b/tests/data/webhooks/forgejo/push_with_one_commit.json @@ -0,0 +1,197 @@ +{ + "ref": "refs/heads/main", + "before": "b5827a36fbab61567da4376f7494a0e519d38a6e", + "after": "c85008a0b44a60370e48a45a9f9d39da5b472e11", + "compare_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/compare/b5827a36fbab61567da4376f7494a0e519d38a6e...c85008a0b44a60370e48a45a9f9d39da5b472e11", + "commits": [ + { + "id": "c85008a0b44a60370e48a45a9f9d39da5b472e11", + "message": "pushing one empty commit\n\nSigned-off-by: Matej Focko \u003cme@mfocko.xyz\u003e\n", + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/commit/c85008a0b44a60370e48a45a9f9d39da5b472e11", + "author": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "committer": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "verification": null, + "timestamp": "2025-08-19T10:28:44+02:00", + "added": [], + "removed": [], + "modified": [] + } + ], + "total_commits": 1, + "head_commit": { + "id": "c85008a0b44a60370e48a45a9f9d39da5b472e11", + "message": "pushing one empty commit\n\nSigned-off-by: Matej Focko \u003cme@mfocko.xyz\u003e\n", + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/commit/c85008a0b44a60370e48a45a9f9d39da5b472e11", + "author": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "committer": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "verification": null, + "timestamp": "2025-08-19T10:28:44+02:00", + "added": [], + "removed": [], + "modified": [] + }, + "repository": { + "id": 681, + "owner": { + "id": 450, + "login": "packit", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://v10.next.forgejo.org/avatars/ccef19de5d28148945dcca5aa2249c00", + "html_url": "https://v10.next.forgejo.org/packit", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-01-29T09:25:44Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packit" + }, + "name": "test-repo-to-generate-events", + "full_name": "packit/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 23, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/packit/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 0, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:58:42Z", + "updated_at": "2025-08-18T17:59:07Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + }, + "pusher": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "sender": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + } +} diff --git a/tests/data/webhooks/forgejo/tag_push.json b/tests/data/webhooks/forgejo/tag_push.json new file mode 100644 index 000000000..b73447e9b --- /dev/null +++ b/tests/data/webhooks/forgejo/tag_push.json @@ -0,0 +1,176 @@ +{ + "ref": "refs/tags/fifth", + "before": "0000000000000000000000000000000000000000", + "after": "9abc2644c3b9828db4bbe30c795b140c6c55089f", + "compare_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/compare/0000000000000000000000000000000000000000...9abc2644c3b9828db4bbe30c795b140c6c55089f", + "commits": [], + "total_commits": 0, + "head_commit": { + "id": "b8a8746d7b91bcff18e9f59a9a1b440089fc2dd5", + "message": "fifth of many commits\n\nSigned-off-by: Matej Focko \u003cmf@example.com\u003e\n", + "url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events/commit/b8a8746d7b91bcff18e9f59a9a1b440089fc2dd5", + "author": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "committer": { + "name": "Matej Focko", + "email": "mf@example.com", + "username": "" + }, + "verification": null, + "timestamp": "2025-08-19T10:31:15+02:00", + "added": [], + "removed": [], + "modified": [] + }, + "repository": { + "id": 681, + "owner": { + "id": 450, + "login": "packit", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "", + "avatar_url": "https://v10.next.forgejo.org/avatars/ccef19de5d28148945dcca5aa2249c00", + "html_url": "https://v10.next.forgejo.org/packit", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-01-29T09:25:44Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "packit" + }, + "name": "test-repo-to-generate-events", + "full_name": "packit/test-repo-to-generate-events", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 27, + "language": "", + "languages_url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events/languages", + "html_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events", + "url": "https://v10.next.forgejo.org/api/v1/repos/packit/test-repo-to-generate-events", + "link": "", + "ssh_url": "ssh://git@v10.next.forgejo.org:2100/packit/test-repo-to-generate-events.git", + "clone_url": "https://v10.next.forgejo.org/packit/test-repo-to-generate-events.git", + "original_url": "", + "website": "", + "stars_count": 0, + "forks_count": 1, + "watchers_count": 3, + "open_issues_count": 0, + "open_pr_counter": 0, + "release_counter": 0, + "default_branch": "main", + "archived": false, + "created_at": "2025-08-18T17:58:42Z", + "updated_at": "2025-08-19T08:32:16Z", + "archived_at": "1970-01-01T00:00:00Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": true, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "wiki_branch": "main", + "globally_editable_wiki": false, + "has_pull_requests": true, + "has_projects": true, + "has_releases": true, + "has_packages": true, + "has_actions": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_fast_forward_only_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "default_allow_maintainer_edit": false, + "default_update_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "object_format_name": "sha1", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null, + "topics": null + }, + "pusher": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + }, + "sender": { + "id": 601, + "login": "mfocko", + "login_name": "", + "source_id": 0, + "full_name": "", + "email": "mfocko@noreply.v10.next.forgejo.org", + "avatar_url": "https://v10.next.forgejo.org/avatars/ee44221e4bd2b983751872d3cde92cf4", + "html_url": "https://v10.next.forgejo.org/mfocko", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2025-02-12T13:52:32Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "pronouns": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "mfocko" + } +} From 71820656a5698d05324038002a5a2164e62d7fd7 Mon Sep 17 00:00:00 2001 From: mynk8 Date: Wed, 24 Sep 2025 00:21:00 +0530 Subject: [PATCH 3/3] include unit tests for forgejo event; fixes Update packit_service/worker/reporting/reporters/base.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Update packit_service/service/api/webhooks.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Update packit_service/worker/parser.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Update packit_service/events/forgejo/pr.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> pre-commit and mypy fixes test(data): add Forgejo webhooks for tests include unit tests for forgejo event; fixes add check for prioritising Forgejo before parsing events; fixes pre-commit remove redundant entries in parser list; fix bug Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> implement validate_token for ForgejoWebhooks; correct the pr.Action impl for Forgejo; fix a problem in parser that could have caused keynotfound errors. implement validate_token for ForgejoWebhooks; correct the pr.Action impl for Forgejo; fix a problem in parser that could have caused keynotfound errors. Implement Forgejo event handling in Packit Service. --- packit_service/events/__init__.py | 1 + packit_service/events/abstract/comment.py | 3 +- packit_service/events/forgejo/__init__.py | 2 +- packit_service/events/forgejo/issue.py | 40 +-- packit_service/events/forgejo/pr.py | 15 +- packit_service/service/api/webhooks.py | 50 +-- packit_service/worker/parser.py | 121 +++++-- tests/conftest.py | 3 +- tests/unit/events/test_forgejo.py | 389 ++++++++++++++++++++++ tests/unit/test_webhooks.py | 42 +++ 10 files changed, 571 insertions(+), 95 deletions(-) create mode 100644 tests/unit/events/test_forgejo.py diff --git a/packit_service/events/__init__.py b/packit_service/events/__init__.py index 472ac9c94..8b275a005 100644 --- a/packit_service/events/__init__.py +++ b/packit_service/events/__init__.py @@ -21,6 +21,7 @@ __all__ = [ abstract.__name__, anitya.__name__, + forgejo.__name__, github.__name__, gitlab.__name__, forgejo.__name__, diff --git a/packit_service/events/abstract/comment.py b/packit_service/events/abstract/comment.py index 943dba20b..e3c6bd872 100644 --- a/packit_service/events/abstract/comment.py +++ b/packit_service/events/abstract/comment.py @@ -152,6 +152,7 @@ def __init__( tag_name: str = "", comment_object: Optional[Comment] = None, dist_git_project_url=None, + commit_sha: Optional[str] = None, ) -> None: super().__init__( project_url=project_url, @@ -168,7 +169,7 @@ def __init__( # Lazy properties self._tag_name = tag_name - self._commit_sha: Optional[str] = None + self._commit_sha: Optional[str] = commit_sha self._comment_object = comment_object self._issue_object: Optional[OgrIssue] = None diff --git a/packit_service/events/forgejo/__init__.py b/packit_service/events/forgejo/__init__.py index 5197842b6..111d7935e 100644 --- a/packit_service/events/forgejo/__init__.py +++ b/packit_service/events/forgejo/__init__.py @@ -3,4 +3,4 @@ from . import abstract, issue, pr, push -__all__ = [abstract.__name__, push.__name__, issue.__name__, pr.__name__] +__all__ = [abstract.__name__, issue.__name__, pr.__name__, push.__name__] diff --git a/packit_service/events/forgejo/issue.py b/packit_service/events/forgejo/issue.py index 13415a896..d2b23fec8 100644 --- a/packit_service/events/forgejo/issue.py +++ b/packit_service/events/forgejo/issue.py @@ -1,8 +1,6 @@ # Copyright Contributors to the Packit project. # SPDX-License-Identifier: MIT -# SPDX-License-Identifier: MIT - from typing import Optional from ogr.abstract import Comment as OgrComment @@ -50,43 +48,7 @@ def __init__( def event_type(cls) -> str: return "forgejo.issue.Comment" - @property - def tag_name(self): - """ - For Forgejo issue comments, return the tag_name passed in constructor - without making API calls to avoid authentication issues. - """ - return self._tag_name - - @tag_name.setter - def tag_name(self, value: str) -> None: - self._tag_name = value - - @property - def commit_sha(self) -> Optional[str]: - """ - For Forgejo issue comments, return the commit_sha passed in constructor - without making API calls to avoid authentication issues. - """ - return self._commit_sha - - @commit_sha.setter - def commit_sha(self, value: Optional[str]) -> None: - self._commit_sha = value - def get_dict(self, default_dict: Optional[dict] = None) -> dict: - """ - Override get_dict to avoid accessing properties that make API calls. - """ - # Get the basic dict from CommentEvent, not from Issue to avoid tag_name access - from ..abstract.comment import CommentEvent - - result = CommentEvent.get_dict(self, default_dict=default_dict) - - # Add the specific fields we need without triggering API calls + result = super().get_dict() result["action"] = self.action.value - result["issue_id"] = self.issue_id - result["tag_name"] = self._tag_name # Use the private attribute directly - result["commit_sha"] = self._commit_sha # Use the private attribute directly - return result diff --git a/packit_service/events/forgejo/pr.py b/packit_service/events/forgejo/pr.py index a2138998b..a2a531eea 100644 --- a/packit_service/events/forgejo/pr.py +++ b/packit_service/events/forgejo/pr.py @@ -36,12 +36,10 @@ def __init__( self.base_ref = base_ref self.target_repo_namespace = target_repo_namespace self.target_repo_name = target_repo_name - self.commit_sha = commit_sha - self.commit_sha_before = commit_sha_before self.actor = actor self.identifier = str(pr_id) - self._pr_id = pr_id - self.git_ref = None # use pr_id for checkout + self.commit_sha = commit_sha + self.commit_sha_before = commit_sha_before self.body = body def get_dict(self, default_dict: Optional[dict] = None) -> dict: @@ -55,8 +53,8 @@ def event_type(cls) -> str: def get_base_project(self) -> GitProject: return self.project.service.get_project( - namespace=self.target_repo_namespace, - repo=self.target_repo_name, + namespace=self.base_repo_namespace, + repo=self.base_repo_name, ) @@ -94,7 +92,6 @@ def __init__( self.actor = actor self.identifier = str(pr_id) self.git_ref = None - self.pr_id = pr_id @classmethod def event_type(cls) -> str: @@ -116,6 +113,6 @@ def get_dict(self, default_dict: Optional[dict] = None) -> dict: def get_base_project(self) -> GitProject: return self.project.service.get_project( - namespace=self.target_repo_namespace, - repo=self.target_repo_name, + namespace=self.base_repo_namespace, + repo=self.base_repo_name, ) diff --git a/packit_service/service/api/webhooks.py b/packit_service/service/api/webhooks.py index 01fbc80a9..01057fe95 100644 --- a/packit_service/service/api/webhooks.py +++ b/packit_service/service/api/webhooks.py @@ -373,6 +373,9 @@ class ForgejoWebhook(Resource): @ns.response(HTTPStatus.UNAUTHORIZED.value, "X-Forgejo-Signature validation failed") @ns.expect(ping_payload_forgejo) def post(self): + """ + A webhook used by Packit-as-a-Service Forgejo hook. + """ msg = request.json if not msg: @@ -385,17 +388,17 @@ def post(self): logger.debug(f"/webhooks/forgejo received ping event: {msg['hook']}") forgejo_webhook_calls.labels(result="pong", process_id=os.getpid()).inc() return "Pong!", HTTPStatus.OK - # TODO - # try: - # self.validate_token() - # except ValidationFailed as exc: - # logger.info(f"/webhooks/forgejo {exc}") - # forgejo_webhook_calls.labels( - # result="invalid_signature", - # process_id=os.getpid(), - # ).inc() - # return str(exc), HTTPStatus.UNAUTHORIZED - # + + try: + self.validate_token() + except ValidationFailed as exc: + logger.info(f"/webhooks/forgejo {exc}") + forgejo_webhook_calls.labels( + result="invalid_signature", + process_id=os.getpid(), + ).inc() + return str(exc), HTTPStatus.UNAUTHORIZED + if not self.interested(msg): forgejo_webhook_calls.labels( result="not_interested", @@ -419,39 +422,44 @@ def validate_token(self): """ Validate the Forgejo webhook signature. The signature is a direct SHA256 HMAC hex digest of the raw request body - using the webhook secret as the key, in a similar fashion to Github. - + using the webhook secret as the key, in a similar fashion to GitHub. """ if "X-Forgejo-Signature" not in request.headers: + if config.validate_webhooks: + msg = "X-Forgejo-Signature not in request.headers" + logger.warning(msg) + raise ValidationFailed(msg) + + # don't validate signatures when testing locally logger.debug("Ain't validating signatures.") return - if not (webhook_secret := getenv("WEBHOOK_SECRET")): + if not (webhook_secret := config.webhook_secret): msg = "'webhook_secret' not specified in the config." logger.error(msg) raise ValidationFailed(msg) - # Get raw payload - payload = request.get_data() if not payload: msg = "No payload received." logger.error(msg) raise ValidationFailed(msg) + # Calculate payload signature using HMAC-SHA256 data_hmac = hmac.new(webhook_secret.encode(), msg=payload, digestmod=sha256) payload_signature = data_hmac.hexdigest() - header_sig = request.headers["X-Forgejo-Signature"] + header_signature = request.headers["X-Forgejo-Signature"] - if header_sig != payload_signature: + if not hmac.compare_digest(header_signature, payload_signature): msg = "Payload signature validation failed." - logger.warning(msg) logger.debug( - f"X-Forgejo-Signature: {header_sig!r} != computed: {payload_signature}", + f"X-Forgejo-Signature: {header_signature!r} != computed: {payload_signature}", ) raise ValidationFailed(msg) + logger.debug("Payload signature is OK.") + @staticmethod def interested(msg): """ @@ -475,7 +483,7 @@ def interested(msg): "release": action == "published", "issues": action in {"opened", "edited", "closed", "reopened"}, "issue_comment": action in {"created", "edited"}, - "pull_request": action in {"opened", "edited", "closed", "reopened", "synchronize"}, + "pull_request": action in {"opened", "reopened", "synchronize"}, } _interested = interests.get(event or "", False) diff --git a/packit_service/worker/parser.py b/packit_service/worker/parser.py index f5f68c3b0..d37098628 100644 --- a/packit_service/worker/parser.py +++ b/packit_service/worker/parser.py @@ -105,6 +105,48 @@ class Parser: we need to have method inside the `Parser` class to create objects defined in `event.py`. """ + @staticmethod + def is_forgejo_event(event: dict) -> bool: + """ + Detect if an event is from Forgejo based on platform-specific fields. + Forgejo events have additional fields that GitHub events don't have. + """ + + # Check for Forgejo-specific fields in user objects + def has_forgejo_user_fields(user_obj): + if not isinstance(user_obj, dict): + return False + forgejo_fields = { + "login_name", + "source_id", + "full_name", + "is_admin", + "last_login", + "created", + "restricted", + "active", + "prohibit_login", + "location", + "pronouns", + "website", + "description", + "visibility", + "followers_count", + "following_count", + "starred_repos_count", + "username", + } + return any(field in user_obj for field in forgejo_fields) + + return ( + has_forgejo_user_fields(event.get("user")) + or has_forgejo_user_fields(nested_get(event, "pull_request", "user")) + or has_forgejo_user_fields(nested_get(event, "comment", "user")) + or has_forgejo_user_fields(nested_get(event, "issue", "user")) + or has_forgejo_user_fields(event.get("pusher")) + or has_forgejo_user_fields(event.get("sender")) + ) + @staticmethod def parse_event( event: dict, @@ -130,6 +172,10 @@ def parse_event( gitlab.push.Commit, gitlab.push.Tag, gitlab.release.Release, + forgejo.pr.Comment, + forgejo.pr.Action, + forgejo.push.Commit, + forgejo.issue.Comment, koji.result.Build, koji.tag.Build, koji.result.Task, @@ -160,6 +206,23 @@ def parse_event( logger.warning("No event to process!") return None + # Check if this is a Forgejo event and prioritize Forgejo parsers + # We have to prioritize Forgejo events as they are similar in structure + # to github event payloads and hence can accidentally be parsed as + # Github objects. + is_forgejo = Parser.is_forgejo_event(event) + + if is_forgejo: + forgejo_parsers = ( + Parser.parse_forgejo_push_event, + Parser.parse_forgejo_pr_event, + Parser.parse_forgejo_comment_event, + ) + for parser in forgejo_parsers: + forgejo_response = parser(event) + if forgejo_response: + return forgejo_response + for response in ( parser(event) for parser in ( @@ -649,7 +712,6 @@ def parse_issue_comment_event(event) -> Optional[github.issue.Comment]: # but it's needed when called from parse_event(). if nested_get(event, "issue", "pull_request"): return None - issue_id = nested_get(event, "issue", "number") action = event.get("action") if action != "created" or not issue_id: @@ -1930,7 +1992,7 @@ def parse_forgejo_push_event(event: dict) -> Optional[forgejo.push.Commit]: raw_ref = event.get("ref") before = event.get("before") after = event.get("after") - pusher = nested_get(event, "pusher", "login") or nested_get(event, "pusher", "name") + pusher = nested_get(event, "pusher", "login") if not (raw_ref and after and before and pusher): return None @@ -1942,13 +2004,12 @@ def parse_forgejo_push_event(event: dict) -> Optional[forgejo.push.Commit]: return None # Number of commits introduced by this push - commits = event.get("commits") or [] - num_commits = len(commits) + num_commits = event.get("total_commits") # Strip the ref prefix to get the branch/tag name _, ref_type, ref_name = raw_ref.split("/", 2) - if ref_type != "heads": - logger.debug(f"Forgejo push event ignored – not a branch push ('{raw_ref}')") + if ref_type not in ("heads", "tags"): + logger.debug(f"Forgejo push event ignored – not a branch or tag push ('{raw_ref}')") return None logger.info( @@ -1980,7 +2041,6 @@ def parse_forgejo_pr_event(event: dict) -> Optional[forgejo.pr.Action]: Parse Forgejo PR action events, only triggering for relevant actions. Supported actions: 'opened', 'reopened', 'synchronize'. Skips others like 'closed'. - """ action_str = event.get("action") # Only trigger for these actions @@ -1995,7 +2055,7 @@ def parse_forgejo_pr_event(event: dict) -> Optional[forgejo.pr.Action]: return None pr_id = pr.get("number") - actor = event.get("sender", {}).get("login") + actor = nested_get(event, "pull_request", "user", "login") repo = event.get("repository", {}) base = pr.get("base") head = pr.get("head") @@ -2003,11 +2063,11 @@ def parse_forgejo_pr_event(event: dict) -> Optional[forgejo.pr.Action]: # Check all required nested fields try: - base_repo_namespace = base["repo"]["owner"]["login"] - base_repo_name = base["repo"]["name"] - base_ref = base["ref"] - target_repo_namespace = head["repo"]["owner"]["login"] - target_repo_name = head["repo"]["name"] + target_repo_namespace = base["repo"]["owner"]["login"] + target_repo_name = base["repo"]["name"] + base_ref = head["sha"] + base_repo_namespace = head["repo"]["owner"]["login"] + base_repo_name = head["repo"]["name"] project_url = repo["html_url"] commit_sha = head["sha"] except (TypeError, KeyError): @@ -2024,7 +2084,7 @@ def parse_forgejo_pr_event(event: dict) -> Optional[forgejo.pr.Action]: target_repo_name=target_repo_name, project_url=project_url, commit_sha=commit_sha, - commit_sha_before=event.get("before", ""), # Optional, might be empty + commit_sha_before=event.get("before", ""), actor=actor, body=body, ) @@ -2043,8 +2103,7 @@ def parse_forgejo_comment_event( # Only treat as PR if 'pull_request' is present and not None issue_dict = event.get("issue", {}) - is_pr = "pull_request" in issue_dict and issue_dict["pull_request"] is not None - + is_pr = issue_dict.get("pull_request") is not None comment = nested_get(event, "comment", "body") comment_id = nested_get(event, "comment", "id") logger.info( @@ -2052,11 +2111,22 @@ def parse_forgejo_comment_event( f"comment: {comment!r} id#{comment_id} {action!r} event." ) - base_repo_namespace = nested_get(event, "issue", "user", "login") - base_repo_name = nested_get(event, "repository", "name") - user_login = nested_get(event, "comment", "user", "login") - target_repo_namespace = nested_get(event, "repository", "owner", "login") + + if is_pr: + # For PR comments, extract repo info from pull_request section + base_repo_namespace = nested_get( + event, "pull_request", "head", "repo", "owner", "login" + ) + base_repo_name = nested_get(event, "pull_request", "head", "repo", "name") + target_repo_namespace = nested_get( + event, "pull_request", "base", "repo", "owner", "login" + ) + else: + # For issue comments, extract from repository section + base_repo_namespace = nested_get(event, "repository", "owner", "login") + base_repo_name = nested_get(event, "repository", "name") + target_repo_namespace = nested_get(event, "repository", "owner", "login") target_repo_name = nested_get(event, "repository", "name") https_url = nested_get(event, "repository", "html_url") @@ -2072,10 +2142,12 @@ def parse_forgejo_comment_event( return None if is_pr: + base_ref = nested_get(event, "pull_request", "head", "ref") + commit_sha = nested_get(event, "pull_request", "head", "sha") return forgejo.pr.Comment( action=PullRequestCommentAction[action], pr_id=issue_id, - base_ref="", + base_ref=base_ref, base_repo_namespace=base_repo_namespace, base_repo_name=base_repo_name, target_repo_namespace=target_repo_namespace, @@ -2084,8 +2156,11 @@ def parse_forgejo_comment_event( actor=user_login, comment=comment, comment_id=comment_id, - commit_sha=None, + commit_sha=commit_sha, ) + # For issue comments, get the default branch + default_branch = nested_get(event, "repository", "default_branch") or "main" + return forgejo.issue.Comment( action=IssueCommentAction[action], issue_id=issue_id, @@ -2097,7 +2172,7 @@ def parse_forgejo_comment_event( comment=comment, comment_id=comment_id, tag_name="", - base_ref="", + base_ref=default_branch, dist_git_project_url=None, ) diff --git a/tests/conftest.py b/tests/conftest.py index f1a8fe891..54a6351bc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,7 @@ import pytest from deepdiff import DeepDiff from flexmock import flexmock -from ogr import GithubService, GitlabService, PagureService +from ogr import ForgejoService, GithubService, GitlabService, PagureService from packit.config import JobConfig, JobConfigTriggerType, PackageConfig from packit.config.common_package_config import Deployment @@ -43,6 +43,7 @@ def global_service_config(): GitlabService(token="token"), PagureService(instance_url="https://src.fedoraproject.org", token="token"), PagureService(instance_url="https://git.stg.centos.org", token="6789"), + ForgejoService(instance_url="https://codeberg.org", token="token"), } service_config.server_name = "localhost" service_config.github_requests_log_path = "/path" diff --git a/tests/unit/events/test_forgejo.py b/tests/unit/events/test_forgejo.py new file mode 100644 index 000000000..97973443b --- /dev/null +++ b/tests/unit/events/test_forgejo.py @@ -0,0 +1,389 @@ +# Copyright Contributors to the Packit project. +# SPDX-License-Identifier: MIT + +import json + +import pytest +from flexmock import flexmock +from ogr.services.forgejo import ForgejoProject + +from packit_service.config import ServiceConfig +from packit_service.events.enums import ( + IssueCommentAction, + PullRequestAction, + PullRequestCommentAction, +) +from packit_service.events.forgejo import issue, pr, push +from packit_service.package_config_getter import PackageConfigGetter +from packit_service.worker.parser import Parser +from tests.spellbook import DATA_DIR + +PATH_TO_FORGEJO_WEBHOOKS = DATA_DIR / "webhooks" / "forgejo" + + +@pytest.fixture() +def pull_request_opened(): + with open(PATH_TO_FORGEJO_WEBHOOKS / "pr_opened.json") as outfile: + return json.load(outfile) + + +@pytest.fixture() +def pull_request_comment(): + with open(PATH_TO_FORGEJO_WEBHOOKS / "pr_comment.json") as outfile: + return json.load(outfile) + + +@pytest.fixture() +def issue_comment(): + with open(PATH_TO_FORGEJO_WEBHOOKS / "issue_comment.json") as outfile: + return json.load(outfile) + + +@pytest.fixture() +def push_new_branch(): + with open(PATH_TO_FORGEJO_WEBHOOKS / "push_new_branch.json") as outfile: + return json.load(outfile) + + +@pytest.fixture() +def tag_push(): + with open(PATH_TO_FORGEJO_WEBHOOKS / "tag_push.json") as outfile: + return json.load(outfile) + + +@pytest.fixture() +def push_with_many_commit(): + with open(PATH_TO_FORGEJO_WEBHOOKS / "push_with_many_commits.json") as outfile: + return json.load(outfile) + + +@pytest.fixture() +def push_with_one_commit(): + with open(PATH_TO_FORGEJO_WEBHOOKS / "push_with_one_commit.json") as outfile: + return json.load(outfile) + + +def test_parse_forgejo_tag_push(tag_push): + mock_project = flexmock(full_repo_name="packit/test-repo-to-generate-events") + flexmock(ServiceConfig).should_receive("get_project").and_return(mock_project) + + event_object = Parser.parse_event(tag_push) + + assert isinstance(event_object, push.Commit) + assert event_object.repo_namespace == "packit" + assert event_object.repo_name == "test-repo-to-generate-events" + assert event_object.commit_sha == "9abc2644c3b9828db4bbe30c795b140c6c55089f" + assert ( + event_object.project_url + == "https://v10.next.forgejo.org/packit/test-repo-to-generate-events" + ) + assert event_object.git_ref == "fifth" + + assert event_object.project.full_repo_name == "packit/test-repo-to-generate-events" + assert not event_object.base_project + + flexmock(PackageConfigGetter).should_receive( + "get_package_config_from_repo", + ).with_args( + base_project=event_object.base_project, + project=event_object.project, + pr_id=None, + reference="9abc2644c3b9828db4bbe30c795b140c6c55089f", + fail_when_missing=False, + ).and_return( + flexmock(get_package_config_views=dict), + ).once() + assert event_object.packages_config + + +def test_parse_pull_request(pull_request_opened): + mock_service = flexmock(get_project=lambda namespace, repo: flexmock()) + mock_project = flexmock( + full_repo_name="packit/test-repo-to-generate-events", service=mock_service + ) + flexmock(ServiceConfig).should_receive("get_project").and_return(mock_project) + + event_object = Parser.parse_event(pull_request_opened) + + assert isinstance(event_object, pr.Action) + assert event_object.action == PullRequestAction.opened + assert event_object.pr_id == 2 + assert event_object.base_repo_namespace == "mfocko" + assert event_object.base_repo_name == "test-repo-to-generate-events" + assert event_object.base_ref == "37182f59ccaaa21584e7b580442fd33b60fe3f80" + assert event_object.target_repo_namespace == "packit" + assert event_object.target_repo_name == "test-repo-to-generate-events" + assert ( + event_object.project_url + == "https://v10.next.forgejo.org/packit/test-repo-to-generate-events" + ) + assert event_object.commit_sha == "37182f59ccaaa21584e7b580442fd33b60fe3f80" + assert event_object.actor == "mfocko" + + assert event_object.project.full_repo_name == "packit/test-repo-to-generate-events" + assert event_object.base_project + + flexmock(PackageConfigGetter).should_receive( + "get_package_config_from_repo", + ).with_args( + base_project=event_object.base_project, + project=event_object.project, + pr_id=2, + reference="37182f59ccaaa21584e7b580442fd33b60fe3f80", + fail_when_missing=False, + ).and_return( + flexmock(get_package_config_views=dict), + ).once() + assert event_object.packages_config + + +def test_parse_pull_request_comment(pull_request_comment): + mock_project = flexmock( + full_repo_name="packit/test-repo-to-generate-events", + get_pr=lambda pr_id: flexmock(head_commit="37182f59ccaaa21584e7b580442fd33b60fe3f80"), + service=flexmock(get_project=lambda namespace, repo: flexmock()), + ) + flexmock(ServiceConfig).should_receive("get_project").and_return(mock_project) + + event_object = Parser.parse_event(pull_request_comment) + + assert isinstance(event_object, pr.Comment) + assert event_object.action == PullRequestCommentAction.created + assert event_object.pr_id == 2 + assert event_object.base_repo_namespace == "mfocko" + assert event_object.base_repo_name == "test-repo-to-generate-events" + assert event_object.target_repo_namespace == "packit" + assert event_object.target_repo_name == "test-repo-to-generate-events" + assert ( + event_object.project_url + == "https://v10.next.forgejo.org/packit/test-repo-to-generate-events" + ) + assert event_object.actor == "mfocko" + assert event_object.comment == "PR comment" + + assert event_object.project.full_repo_name == "packit/test-repo-to-generate-events" + assert event_object.base_project + + flexmock(PackageConfigGetter).should_receive( + "get_package_config_from_repo", + ).with_args( + base_project=event_object.base_project, + project=event_object.project, + pr_id=2, + reference="37182f59ccaaa21584e7b580442fd33b60fe3f80", + fail_when_missing=False, + ).and_return( + flexmock(get_package_config_views=dict), + ).once() + assert event_object.packages_config + + +def test_parse_issue_comment(issue_comment): + mock_project = flexmock(full_repo_name="packit/test-repo-to-generate-events") + flexmock(ServiceConfig).should_receive("get_project").and_return(mock_project) + + event_object = Parser.parse_event(issue_comment) + + assert isinstance(event_object, issue.Comment) + assert event_object.action == IssueCommentAction.created + assert event_object.issue_id == 1 + assert event_object.repo_namespace == "packit" + assert event_object.repo_name == "test-repo-to-generate-events" + assert event_object.target_repo == "packit/test-repo-to-generate-events" + assert event_object.base_ref == "main" + assert ( + event_object.project_url + == "https://v10.next.forgejo.org/packit/test-repo-to-generate-events" + ) + assert event_object.actor == "mfocko" + assert event_object.comment == "issue comment" + + assert event_object.project.full_repo_name == "packit/test-repo-to-generate-events" + assert not event_object.base_project + + flexmock(event_object.project).should_receive("get_releases").and_return([]) + flexmock(ForgejoProject).should_receive("get_sha_from_tag").never() + flexmock(PackageConfigGetter).should_receive( + "get_package_config_from_repo", + ).with_args( + base_project=event_object.base_project, + project=event_object.project, + pr_id=None, + reference=None, + fail_when_missing=False, + ).and_return( + flexmock(get_package_config_views=dict), + ).once() + assert event_object.packages_config + assert event_object.commit_sha is None + assert event_object.tag_name == "" + + +def test_parse_forgejo_push_many_commits(push_with_many_commit): + mock_project = flexmock(full_repo_name="packit/test-repo-to-generate-events") + flexmock(ServiceConfig).should_receive("get_project").and_return(mock_project) + + event_object = Parser.parse_event(push_with_many_commit) + + assert isinstance(event_object, push.Commit) + assert event_object.repo_namespace == "packit" + assert event_object.repo_name == "test-repo-to-generate-events" + assert event_object.commit_sha == "fa9bbf46c6ae89b755716683814b03b6a2c82263" + assert ( + event_object.project_url + == "https://v10.next.forgejo.org/packit/test-repo-to-generate-events" + ) + assert event_object.git_ref == "main" + + assert event_object.project.full_repo_name == "packit/test-repo-to-generate-events" + assert not event_object.base_project + + flexmock(PackageConfigGetter).should_receive( + "get_package_config_from_repo", + ).with_args( + base_project=event_object.base_project, + project=event_object.project, + pr_id=None, + reference="fa9bbf46c6ae89b755716683814b03b6a2c82263", + fail_when_missing=False, + ).and_return( + flexmock(get_package_config_views=dict), + ).once() + assert event_object.packages_config + + +def test_parse_forgejo_push_new_branch(push_new_branch): + mock_project = flexmock(full_repo_name="packit/test-repo-to-generate-events") + flexmock(ServiceConfig).should_receive("get_project").and_return(mock_project) + + event_object = Parser.parse_event(push_new_branch) + + assert isinstance(event_object, push.Commit) + assert event_object.repo_namespace == "packit" + assert event_object.repo_name == "test-repo-to-generate-events" + assert event_object.commit_sha == "24f660b69e4608f63ddd55d5c5b459f348e5f272" + assert ( + event_object.project_url + == "https://v10.next.forgejo.org/packit/test-repo-to-generate-events" + ) + assert event_object.git_ref == "new-branch" + + assert event_object.project.full_repo_name == "packit/test-repo-to-generate-events" + assert not event_object.base_project + + flexmock(PackageConfigGetter).should_receive( + "get_package_config_from_repo", + ).with_args( + base_project=event_object.base_project, + project=event_object.project, + pr_id=None, + reference="24f660b69e4608f63ddd55d5c5b459f348e5f272", + fail_when_missing=False, + ).and_return( + flexmock(get_package_config_views=dict), + ).once() + assert event_object.packages_config + + +def test_parse_forgejo_push_one_commit(push_with_one_commit): + mock_project = flexmock(full_repo_name="packit/test-repo-to-generate-events") + flexmock(ServiceConfig).should_receive("get_project").and_return(mock_project) + + event_object = Parser.parse_event(push_with_one_commit) + + assert isinstance(event_object, push.Commit) + assert event_object.repo_namespace == "packit" + assert event_object.repo_name == "test-repo-to-generate-events" + assert event_object.commit_sha == "c85008a0b44a60370e48a45a9f9d39da5b472e11" + assert ( + event_object.project_url + == "https://v10.next.forgejo.org/packit/test-repo-to-generate-events" + ) + assert event_object.git_ref == "main" + + assert event_object.project.full_repo_name == "packit/test-repo-to-generate-events" + assert not event_object.base_project + + flexmock(PackageConfigGetter).should_receive( + "get_package_config_from_repo", + ).with_args( + base_project=event_object.base_project, + project=event_object.project, + pr_id=None, + reference="c85008a0b44a60370e48a45a9f9d39da5b472e11", + fail_when_missing=False, + ).and_return( + flexmock(get_package_config_views=dict), + ).once() + assert event_object.packages_config + + +def test_parse_forgejo_pr_vs_issue_comment_discrimination(pull_request_comment, issue_comment): + """ + Test that the parser correctly discriminates between PR comments and issue comments. + In Forgejo, PRs are treated as special issues, so the parser needs to distinguish + between them based on the webhook payload structure. + """ + mock_project = flexmock( + full_repo_name="packit/test-repo-to-generate-events", + get_pr=lambda pr_id: flexmock(head_commit="37182f59ccaaa21584e7b580442fd33b60fe3f80"), + get_releases=list, + service=flexmock(get_project=lambda namespace, repo: flexmock()), + ) + flexmock(ServiceConfig).should_receive("get_project").and_return(mock_project) + + # Test PR comment parsing + pr_comment_event = Parser.parse_event(pull_request_comment) + + assert isinstance(pr_comment_event, pr.Comment) + assert pr_comment_event.action == PullRequestCommentAction.created + assert pr_comment_event.pr_id == 2 + assert pr_comment_event.comment == "PR comment" + assert pr_comment_event.actor == "mfocko" + + # Test issue comment parsing + issue_comment_event = Parser.parse_event(issue_comment) + + assert isinstance(issue_comment_event, issue.Comment) + assert issue_comment_event.action == IssueCommentAction.created + assert issue_comment_event.issue_id == 1 + assert issue_comment_event.comment == "issue comment" + assert issue_comment_event.actor == "mfocko" + + assert pr_comment_event.event_type() == "forgejo.pr.Comment" + assert issue_comment_event.event_type() == "forgejo.issue.Comment" + + assert pr_comment_event.project_url == issue_comment_event.project_url + assert ( + pr_comment_event.project_url + == "https://v10.next.forgejo.org/packit/test-repo-to-generate-events" + ) + + flexmock(ForgejoProject).should_receive("get_sha_from_tag").never() + + flexmock(PackageConfigGetter).should_receive( + "get_package_config_from_repo", + ).with_args( + base_project=pr_comment_event.base_project, + project=pr_comment_event.project, + pr_id=2, + reference="37182f59ccaaa21584e7b580442fd33b60fe3f80", + fail_when_missing=False, + ).and_return( + flexmock(get_package_config_views=dict), + ).once() + + flexmock(PackageConfigGetter).should_receive( + "get_package_config_from_repo", + ).with_args( + base_project=issue_comment_event.base_project, + project=issue_comment_event.project, + pr_id=None, + reference=None, + fail_when_missing=False, + ).and_return( + flexmock(get_package_config_views=dict), + ).once() + + assert pr_comment_event.packages_config + assert issue_comment_event.packages_config diff --git a/tests/unit/test_webhooks.py b/tests/unit/test_webhooks.py index b8372eead..b503aea1c 100644 --- a/tests/unit/test_webhooks.py +++ b/tests/unit/test_webhooks.py @@ -246,3 +246,45 @@ def test_interested(mock_config, headers, payload, interested): headers=headers, ): assert webhooks.GithubWebhook.interested() == interested + + +@pytest.mark.parametrize( + "headers, is_good", + [ + ( + { + "X-Forgejo-Signature": ( + "7884c9fc5f880c17920b2066e85aae7b57489505a16aa9b56806a924df78f846" + ), + }, + True, + ), + ( + { + "X-Forgejo-Signature": ( + "feedfacecafebeef920b2066e85aae7b57489505a16aa9b56806a924df78f666" + ), + }, + False, + ), + ({}, False), + ], +) +def test_validate_forgejo_token(mock_config, headers, is_good): + flexmock(ServiceConfig).should_receive("get_service_config").and_return( + flexmock(validate_webhooks=True), + ) + from packit_service.service.api import webhooks + + webhooks.config = mock_config + + with Flask(__name__).test_request_context(): + payload = {"zen": "Keep it logically awesome."} + + request._cached_data = request.data = dumps(payload).encode() + request.headers = headers + if not is_good: + with pytest.raises(ValidationFailed): + webhooks.ForgejoWebhook.validate_token(webhooks.ForgejoWebhook()) + else: + webhooks.ForgejoWebhook.validate_token(webhooks.ForgejoWebhook())