diff --git a/swh/web/api/apidoc.py b/swh/web/api/apidoc.py --- a/swh/web/api/apidoc.py +++ b/swh/web/api/apidoc.py @@ -485,3 +485,11 @@ return f return decorator + + +def set_docstring(docstring): + def decorator(f): + f.__doc__ = docstring + return f + + return decorator diff --git a/swh/web/config.py b/swh/web/config.py --- a/swh/web/config.py +++ b/swh/web/config.py @@ -168,6 +168,7 @@ "swh.web.mailmap", "swh.web.metrics", "swh.web.save_code_now", + "swh.web.save_origin_webhooks", "swh.web.vault", ], ), diff --git a/swh/web/save_origin_webhooks/__init__.py b/swh/web/save_origin_webhooks/__init__.py new file mode 100644 diff --git a/swh/web/save_origin_webhooks/bitbucket.py b/swh/web/save_origin_webhooks/bitbucket.py new file mode 100644 --- /dev/null +++ b/swh/web/save_origin_webhooks/bitbucket.py @@ -0,0 +1,44 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from typing import Tuple + +from rest_framework.request import Request + +from swh.web.save_origin_webhooks.generic_receiver import OriginSaveWebhookReceiver + + +class BitbucketOriginSaveWebhookReceiver(OriginSaveWebhookReceiver): + FORGE_TYPE = "Bitbucket" + WEBHOOK_GUIDE_URL = ( + "https://support.atlassian.com/bitbucket-cloud/docs/manage-webhooks/" + ) + REPO_TYPES = "git" + + def is_forge_request(self, request: Request) -> bool: + return ( + request.headers.get("User-Agent", "").startswith( + f"{self.FORGE_TYPE}-Webhooks/" + ) + and "X-Event-Key" in request.headers + ) + + def is_push_event(self, request: Request) -> bool: + return request.headers["X-Event-Key"] == "repo:push" + + def extract_repo_url_and_visit_type(self, request: Request) -> Tuple[str, str]: + repo_url = ( + request.data.get("repository", {}) + .get("links", {}) + .get("html", {}) + .get("href", "") + ) + if repo_url: + repo_url += ".git" + + return repo_url, "git" + + +api_origin_save_webhook_bitbucket = BitbucketOriginSaveWebhookReceiver() diff --git a/swh/web/save_origin_webhooks/generic_receiver.py b/swh/web/save_origin_webhooks/generic_receiver.py new file mode 100644 --- /dev/null +++ b/swh/web/save_origin_webhooks/generic_receiver.py @@ -0,0 +1,114 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import abc +from typing import Any, Dict, Tuple + +from rest_framework.request import Request + +from swh.web.api.apidoc import api_doc +from swh.web.api.apiurls import api_route +from swh.web.save_code_now.origin_save import create_save_origin_request +from swh.web.utils.exc import BadInputExc + + +class OriginSaveWebhookReceiver(abc.ABC): + FORGE_TYPE: str + WEBHOOK_GUIDE_URL: str + REPO_TYPES: str + + @abc.abstractmethod + def is_forge_request(self, request: Request) -> bool: + ... + + @abc.abstractmethod + def is_push_event(self, request: Request) -> bool: + ... + + @abc.abstractmethod + def extract_repo_url_and_visit_type(self, request: Request) -> Tuple[str, str]: + ... + + def __init__(self): + self.__doc__ = f""" + .. http:post:: /api/1/origin/save/webhook/{self.FORGE_TYPE.lower()}/ + + Webhook receiver for {self.FORGE_TYPE} to request or update the archival of + a repository when new commits are pushed to it. + + To add such webhook to one of your {self.REPO_TYPES} repository hosted on + {self.FORGE_TYPE}, please follow that `guide <{self.WEBHOOK_GUIDE_URL}>`_. + + Please note that the expected content type for the webhook payload should be + ``application/json``. + + :>json string origin_url: the url of the origin to save + :>json string visit_type: the type of visit to perform + :>json string save_request_date: the date (in iso format) the save + request was issued + :>json string save_request_status: the status of the save request, + either **accepted**, **rejected** or **pending** + + :statuscode 200: save request for repository has been successfully created + from the webhook payload. + :statuscode 400: no save request has been created due to invalid POST + request or missing data in webhook payload + """ + self.__name__ = "api_origin_save_webhook_{self.FORGE_TYPE.lower()}" + api_doc( + f"/origin/save/webhook/{self.FORGE_TYPE.lower()}/", + category="Request archival", + )(self) + api_route( + f"/origin/save/webhook/{self.FORGE_TYPE.lower()}/", + f"api-1-origin-save-webhook-{self.FORGE_TYPE.lower()}", + methods=["POST"], + )(self) + + def __call__( + self, + request: Request, + ) -> Dict[str, Any]: + + if not self.is_forge_request(request): + raise BadInputExc( + f"POST request has not been sent by a {self.FORGE_TYPE} webhook and " + "has not been processed." + ) + + if not self.is_push_event(request): + raise BadInputExc( + f"Event sent by {self.FORGE_TYPE} webhook is not a push one, request " + "has not been processed." + ) + + content_type = request.headers.get("Content-Type") + if content_type != "application/json": + raise BadInputExc( + f"Invalid content type '{content_type}' for the POST request sent by " + f"{self.FORGE_TYPE} webhook, it should be 'application/json'." + ) + + repo_url, visit_type = self.extract_repo_url_and_visit_type(request) + if not repo_url: + raise BadInputExc( + f"Repository URL could not be extracted from {self.FORGE_TYPE} webhook " + f"payload." + ) + if not visit_type: + raise BadInputExc( + f"Visit type could not be determined for repository {repo_url}." + ) + + save_request = create_save_origin_request( + visit_type=visit_type, origin_url=repo_url + ) + + return { + "origin_url": save_request["origin_url"], + "visit_type": save_request["visit_type"], + "save_request_date": save_request["save_request_date"], + "save_request_status": save_request["save_request_status"], + } diff --git a/swh/web/save_origin_webhooks/gitea.py b/swh/web/save_origin_webhooks/gitea.py new file mode 100644 --- /dev/null +++ b/swh/web/save_origin_webhooks/gitea.py @@ -0,0 +1,28 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from typing import Tuple + +from rest_framework.request import Request + +from swh.web.save_origin_webhooks.generic_receiver import OriginSaveWebhookReceiver + + +class GiteaOriginSaveWebhookReceiver(OriginSaveWebhookReceiver): + FORGE_TYPE = "Gitea" + WEBHOOK_GUIDE_URL = "https://docs.gitea.io/en-us/webhooks/" + REPO_TYPES = "git" + + def is_forge_request(self, request: Request) -> bool: + return f"X-{self.FORGE_TYPE}-Event" in request.headers + + def is_push_event(self, request: Request) -> bool: + return request.headers[f"X-{self.FORGE_TYPE}-Event"] == "push" + + def extract_repo_url_and_visit_type(self, request: Request) -> Tuple[str, str]: + return request.data.get("repository", {}).get("clone_url", ""), "git" + + +api_origin_save_webhook_gitea = GiteaOriginSaveWebhookReceiver() diff --git a/swh/web/save_origin_webhooks/github.py b/swh/web/save_origin_webhooks/github.py new file mode 100644 --- /dev/null +++ b/swh/web/save_origin_webhooks/github.py @@ -0,0 +1,36 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from typing import Tuple + +from rest_framework.request import Request + +from swh.web.save_origin_webhooks.generic_receiver import OriginSaveWebhookReceiver + + +class GitHubOriginSaveWebhookReceiver(OriginSaveWebhookReceiver): + FORGE_TYPE = "GitHub" + WEBHOOK_GUIDE_URL = ( + "https://docs.github.com/en/developers/webhooks-and-events/" + "webhooks/creating-webhooks#setting-up-a-webhook" + ) + REPO_TYPES = "git" + + def is_forge_request(self, request: Request) -> bool: + return ( + request.headers.get("User-Agent", "").startswith( + f"{self.FORGE_TYPE}-Hookshot/" + ) + and f"X-{self.FORGE_TYPE}-Event" in request.headers + ) + + def is_push_event(self, request: Request) -> bool: + return request.headers[f"X-{self.FORGE_TYPE}-Event"] == "push" + + def extract_repo_url_and_visit_type(self, request: Request) -> Tuple[str, str]: + return request.data.get("repository", {}).get("html_url", ""), "git" + + +api_origin_save_webhook_github = GitHubOriginSaveWebhookReceiver() diff --git a/swh/web/save_origin_webhooks/gitlab.py b/swh/web/save_origin_webhooks/gitlab.py new file mode 100644 --- /dev/null +++ b/swh/web/save_origin_webhooks/gitlab.py @@ -0,0 +1,34 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from typing import Tuple + +from rest_framework.request import Request + +from swh.web.save_origin_webhooks.generic_receiver import OriginSaveWebhookReceiver + + +class GitlabOriginSaveWebhookReceiver(OriginSaveWebhookReceiver): + FORGE_TYPE = "GitLab" + WEBHOOK_GUIDE_URL = ( + "https://docs.gitlab.com/ee/user/project/integrations/" + "webhooks.html#configure-a-webhook-in-gitlab" + ) + REPO_TYPES = "git" + + def is_forge_request(self, request: Request) -> bool: + return ( + request.headers.get("User-Agent", "").startswith(f"{self.FORGE_TYPE}/") + and "X-Gitlab-Event" in request.headers + ) + + def is_push_event(self, request: Request) -> bool: + return request.headers["X-Gitlab-Event"] == "Push Hook" + + def extract_repo_url_and_visit_type(self, request: Request) -> Tuple[str, str]: + return request.data.get("repository", {}).get("git_http_url", ""), "git" + + +api_origin_save_webhook_gitlab = GitlabOriginSaveWebhookReceiver() diff --git a/swh/web/save_origin_webhooks/sourceforge.py b/swh/web/save_origin_webhooks/sourceforge.py new file mode 100644 --- /dev/null +++ b/swh/web/save_origin_webhooks/sourceforge.py @@ -0,0 +1,61 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from typing import Tuple + +import requests + +from rest_framework.request import Request + +from swh.web.save_origin_webhooks.generic_receiver import OriginSaveWebhookReceiver + + +class SourceforgeOriginSaveWebhookReceiver(OriginSaveWebhookReceiver): + FORGE_TYPE = "SourceForge" + WEBHOOK_GUIDE_URL = ( + "https://sourceforge.net/blog/" + "how-to-use-webhooks-for-git-mercurial-and-svn-repositories/" + ) + REPO_TYPES = "git, hg or svn" + + SOURCE_FORGE_API_PROJECT_URL_PATTERN = ( + "https://sourceforge.net/rest/p/{project_name}" + ) + + def is_forge_request(self, request: Request) -> bool: + return ( + request.headers.get("User-Agent", "") + == "Allura Webhook (https://allura.apache.org/)" + ) + + def is_push_event(self, request: Request) -> bool: + # SourceForge only support webhooks for push events + return True + + def extract_repo_url_and_visit_type(self, request: Request) -> Tuple[str, str]: + repo_url = "" + visit_type = "" + project_full_name = request.data.get("repository", {}).get("full_name") + if project_full_name: + project_name = project_full_name.split("/")[2] + project_api_url = self.SOURCE_FORGE_API_PROJECT_URL_PATTERN.format( + project_name=project_name + ) + response = requests.get(project_api_url) + if response.ok: + project_data = response.json() + for tool in project_data.get("tools", []): + if tool.get("mount_point") == "code" and tool.get( + "url", "" + ).endswith(project_full_name): + repo_url = tool.get( + "clone_url_https_anon", tool.get("clone_url_ro", "") + ) + visit_type = tool.get("name", "") + + return repo_url, visit_type + + +api_origin_save_webhook_sourceforge = SourceforgeOriginSaveWebhookReceiver() diff --git a/swh/web/save_origin_webhooks/urls.py b/swh/web/save_origin_webhooks/urls.py new file mode 100644 --- /dev/null +++ b/swh/web/save_origin_webhooks/urls.py @@ -0,0 +1,17 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from typing import List, Union + +from django.urls import URLPattern, URLResolver + +# register Web API endpoints +import swh.web.save_origin_webhooks.bitbucket # noqa +import swh.web.save_origin_webhooks.gitea # noqa +import swh.web.save_origin_webhooks.github # noqa +import swh.web.save_origin_webhooks.gitlab # noqa +import swh.web.save_origin_webhooks.sourceforge # noqa + +urlpatterns: List[Union[URLPattern, URLResolver]] = [] diff --git a/swh/web/settings/tests.py b/swh/web/settings/tests.py --- a/swh/web/settings/tests.py +++ b/swh/web/settings/tests.py @@ -96,6 +96,7 @@ "swh.web.mailmap", "swh.web.metrics", "swh.web.save_code_now", + "swh.web.save_origin_webhooks", "swh.web.vault", ], } diff --git a/swh/web/tests/conftest.py b/swh/web/tests/conftest.py --- a/swh/web/tests/conftest.py +++ b/swh/web/tests/conftest.py @@ -822,12 +822,6 @@ return _IndexerData(tests_data) -# Custom data directory for requests_mock -@pytest.fixture -def datadir(): - return os.path.join(os.path.abspath(os.path.dirname(__file__)), "resources") - - class _ArchiveData: """ Helper class to manage data from a sample test archive. diff --git a/swh/web/tests/helpers.py b/swh/web/tests/helpers.py --- a/swh/web/tests/helpers.py +++ b/swh/web/tests/helpers.py @@ -140,6 +140,7 @@ status_code: int, content_type: str = "*/*", data: Optional[Dict[str, Any]] = None, + **headers, ) -> HttpResponseBase: """Helper function to check Web API response for a POST request for all accepted content types. @@ -158,6 +159,7 @@ data=data, format="json", HTTP_ACCEPT=content_type, + **headers, ), status_code=status_code, content_type=content_type, @@ -169,6 +171,7 @@ url: str, status_code: int, data: Optional[Dict[str, Any]] = None, + **headers, ) -> Response: """Helper function to check Web API responses for POST requests for all accepted content types (JSON, YAML). @@ -183,12 +186,22 @@ """ # check JSON response response_json = check_api_post_response( - api_client, url, status_code, content_type="application/json", data=data + api_client, + url, + status_code, + content_type="application/json", + data=data, + **headers, ) # check YAML response check_api_post_response( - api_client, url, status_code, content_type="application/yaml", data=data + api_client, + url, + status_code, + content_type="application/yaml", + data=data, + **headers, ) return cast(Response, response_json) diff --git a/swh/web/tests/resources/http_esnode1.internal.softwareheritage.org/swh_workers-*__search b/swh/web/tests/save_code_now/data/http_esnode1.internal.softwareheritage.org/swh_workers-*__search rename from swh/web/tests/resources/http_esnode1.internal.softwareheritage.org/swh_workers-*__search rename to swh/web/tests/save_code_now/data/http_esnode1.internal.softwareheritage.org/swh_workers-*__search diff --git a/swh/web/tests/save_origin_webhooks/__init__.py b/swh/web/tests/save_origin_webhooks/__init__.py new file mode 100644 diff --git a/swh/web/tests/save_origin_webhooks/data/bitbucket_webhook_payload.json b/swh/web/tests/save_origin_webhooks/data/bitbucket_webhook_payload.json new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/data/bitbucket_webhook_payload.json @@ -0,0 +1,333 @@ +{ + "push": { + "changes": [ + { + "old": { + "name": "main", + "target": { + "type": "commit", + "hash": "f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72", + "date": "2022-10-26T09:57:22+00:00", + "author": { + "type": "author", + "raw": "John Doe ", + "user": { + "display_name": "John Doe", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7B65f18e6c-2e1c-4a89-99c1-f7bad58d5a39%7D" + }, + "avatar": { + "href": "https://secure.gravatar.com/avatar/d522e4a403af0605784d0f7936a506a3?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FAL-1.png" + }, + "html": { + "href": "https://bitbucket.org/%7B65f18e6c-2e1c-4a89-99c1-f7bad58d5a39%7D/" + } + }, + "type": "user", + "uuid": "{65f18e6c-2e1c-4a89-99c1-f7bad58d5a39}", + "account_id": "5d1483f6f46aa30c271c968e", + "nickname": "John Doe" + } + }, + "message": "Initial commit", + "summary": { + "type": "rendered", + "raw": "Initial commit", + "markup": "markdown", + "html": "

Initial commit

" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/commit/f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72" + }, + "html": { + "href": "https://bitbucket.org/johndoe/webhook-test/commits/f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72" + } + }, + "parents": [], + "rendered": {}, + "properties": {} + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/refs/branches/main" + }, + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/commits/main" + }, + "html": { + "href": "https://bitbucket.org/johndoe/webhook-test/branch/main" + } + }, + "type": "branch", + "merge_strategies": [ + "merge_commit", + "squash", + "fast_forward" + ], + "default_merge_strategy": "merge_commit" + }, + "new": { + "name": "main", + "target": { + "type": "commit", + "hash": "45ed1aeeb43008b0ec5666ef67242f66639920d7", + "date": "2022-10-26T10:03:24+00:00", + "author": { + "type": "author", + "raw": "John Doe ", + "user": { + "display_name": "John Doe", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7B65f18e6c-2e1c-4a89-99c1-f7bad58d5a39%7D" + }, + "avatar": { + "href": "https://secure.gravatar.com/avatar/d522e4a403af0605784d0f7936a506a3?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FAL-1.png" + }, + "html": { + "href": "https://bitbucket.org/%7B65f18e6c-2e1c-4a89-99c1-f7bad58d5a39%7D/" + } + }, + "type": "user", + "uuid": "{65f18e6c-2e1c-4a89-99c1-f7bad58d5a39}", + "account_id": "5d1483f6f46aa30c271c968e", + "nickname": "John Doe" + } + }, + "message": "Add new line in README to trigger webhook when pushing commit\n", + "summary": { + "type": "rendered", + "raw": "Add new line in README to trigger webhook when pushing commit\n", + "markup": "markdown", + "html": "

Add new line in README to trigger webhook when pushing commit

" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/commit/45ed1aeeb43008b0ec5666ef67242f66639920d7" + }, + "html": { + "href": "https://bitbucket.org/johndoe/webhook-test/commits/45ed1aeeb43008b0ec5666ef67242f66639920d7" + } + }, + "parents": [ + { + "type": "commit", + "hash": "f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/commit/f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72" + }, + "html": { + "href": "https://bitbucket.org/johndoe/webhook-test/commits/f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72" + } + } + } + ], + "rendered": {}, + "properties": {} + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/refs/branches/main" + }, + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/commits/main" + }, + "html": { + "href": "https://bitbucket.org/johndoe/webhook-test/branch/main" + } + }, + "type": "branch", + "merge_strategies": [ + "merge_commit", + "squash", + "fast_forward" + ], + "default_merge_strategy": "merge_commit" + }, + "truncated": false, + "created": false, + "forced": false, + "closed": false, + "links": { + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/commits?include=45ed1aeeb43008b0ec5666ef67242f66639920d7&exclude=f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72" + }, + "diff": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/diff/45ed1aeeb43008b0ec5666ef67242f66639920d7..f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72" + }, + "html": { + "href": "https://bitbucket.org/johndoe/webhook-test/branches/compare/45ed1aeeb43008b0ec5666ef67242f66639920d7..f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72" + } + }, + "commits": [ + { + "type": "commit", + "hash": "45ed1aeeb43008b0ec5666ef67242f66639920d7", + "date": "2022-10-26T10:03:24+00:00", + "author": { + "type": "author", + "raw": "John Doe ", + "user": { + "display_name": "John Doe", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7B65f18e6c-2e1c-4a89-99c1-f7bad58d5a39%7D" + }, + "avatar": { + "href": "https://secure.gravatar.com/avatar/d522e4a403af0605784d0f7936a506a3?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FAL-1.png" + }, + "html": { + "href": "https://bitbucket.org/%7B65f18e6c-2e1c-4a89-99c1-f7bad58d5a39%7D/" + } + }, + "type": "user", + "uuid": "{65f18e6c-2e1c-4a89-99c1-f7bad58d5a39}", + "account_id": "5d1483f6f46aa30c271c968e", + "nickname": "John Doe" + } + }, + "message": "Add new line in README to trigger webhook when pushing commit\n", + "summary": { + "type": "rendered", + "raw": "Add new line in README to trigger webhook when pushing commit\n", + "markup": "markdown", + "html": "

Add new line in README to trigger webhook when pushing commit

" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/commit/45ed1aeeb43008b0ec5666ef67242f66639920d7" + }, + "html": { + "href": "https://bitbucket.org/johndoe/webhook-test/commits/45ed1aeeb43008b0ec5666ef67242f66639920d7" + }, + "diff": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/diff/45ed1aeeb43008b0ec5666ef67242f66639920d7" + }, + "approve": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/commit/45ed1aeeb43008b0ec5666ef67242f66639920d7/approve" + }, + "comments": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/commit/45ed1aeeb43008b0ec5666ef67242f66639920d7/comments" + }, + "statuses": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/commit/45ed1aeeb43008b0ec5666ef67242f66639920d7/statuses" + }, + "patch": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/patch/45ed1aeeb43008b0ec5666ef67242f66639920d7" + } + }, + "parents": [ + { + "type": "commit", + "hash": "f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test/commit/f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72" + }, + "html": { + "href": "https://bitbucket.org/johndoe/webhook-test/commits/f069b8b3a68b043cdab1cabd2b3415eaf1b0ef72" + } + } + } + ], + "rendered": {}, + "properties": {} + } + ] + } + ] + }, + "repository": { + "type": "repository", + "full_name": "johndoe/webhook-test", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/johndoe/webhook-test" + }, + "html": { + "href": "https://bitbucket.org/johndoe/webhook-test" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7Bb5d2882b-effe-4cf8-bb5a-8a757bdbfe28%7D?ts=default" + } + }, + "name": "webhook-test", + "scm": "git", + "website": null, + "owner": { + "display_name": "John Doe", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7B65f18e6c-2e1c-4a89-99c1-f7bad58d5a39%7D" + }, + "avatar": { + "href": "https://secure.gravatar.com/avatar/d522e4a403af0605784d0f7936a506a3?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FAL-1.png" + }, + "html": { + "href": "https://bitbucket.org/%7B65f18e6c-2e1c-4a89-99c1-f7bad58d5a39%7D/" + } + }, + "type": "user", + "uuid": "{65f18e6c-2e1c-4a89-99c1-f7bad58d5a39}", + "account_id": "5d1483f6f46aa30c271c968e", + "nickname": "John Doe" + }, + "workspace": { + "type": "workspace", + "uuid": "{65f18e6c-2e1c-4a89-99c1-f7bad58d5a39}", + "name": "John Doe", + "slug": "johndoe", + "links": { + "avatar": { + "href": "https://bitbucket.org/workspaces/johndoe/avatar/?ts=1561625644" + }, + "html": { + "href": "https://bitbucket.org/johndoe/" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/johndoe" + } + } + }, + "is_private": false, + "project": { + "type": "project", + "key": "WEB", + "uuid": "{4c11da16-a545-45c4-84c2-d0ba937a2e23}", + "name": "webhook-test", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/johndoe/projects/WEB" + }, + "html": { + "href": "https://bitbucket.org/johndoe/workspace/projects/WEB" + }, + "avatar": { + "href": "https://bitbucket.org/account/user/johndoe/projects/WEB/avatar/32?ts=1666778241" + } + } + }, + "uuid": "{b5d2882b-effe-4cf8-bb5a-8a757bdbfe28}" + }, + "actor": { + "display_name": "John Doe", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7B65f18e6c-2e1c-4a89-99c1-f7bad58d5a39%7D" + }, + "avatar": { + "href": "https://secure.gravatar.com/avatar/d522e4a403af0605784d0f7936a506a3?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FAL-1.png" + }, + "html": { + "href": "https://bitbucket.org/%7B65f18e6c-2e1c-4a89-99c1-f7bad58d5a39%7D/" + } + }, + "type": "user", + "uuid": "{65f18e6c-2e1c-4a89-99c1-f7bad58d5a39}", + "account_id": "5d1483f6f46aa30c271c968e", + "nickname": "John Doe" + } +} \ No newline at end of file diff --git a/swh/web/tests/save_origin_webhooks/data/gitea_webhook_payload.json b/swh/web/tests/save_origin_webhooks/data/gitea_webhook_payload.json new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/data/gitea_webhook_payload.json @@ -0,0 +1,179 @@ +{ + "ref": "refs/heads/main", + "before": "d74a0d9989b1642e82e5f7ab3ba395f4034b1fcd", + "after": "fc0cf34aac1443223c9dd55f3a901e6b38bd25b0", + "compare_url": "https://try.gitea.io/johndoe/webhook-test/compare/d74a0d9989b1642e82e5f7ab3ba395f4034b1fcd...fc0cf34aac1443223c9dd55f3a901e6b38bd25b0", + "commits": [ + { + "id": "fc0cf34aac1443223c9dd55f3a901e6b38bd25b0", + "message": "Add a new line to trigger webhook when pushing commit\n", + "url": "https://try.gitea.io/johndoe/webhook-test/commit/fc0cf34aac1443223c9dd55f3a901e6b38bd25b0", + "author": { + "name": "John Doe", + "email": "john.doe@example.org", + "username": "" + }, + "committer": { + "name": "John Doe", + "email": "john.doe@example.org", + "username": "" + }, + "verification": null, + "timestamp": "2022-10-25T17:34:08+02:00", + "added": [], + "removed": [], + "modified": [ + "README.md" + ] + } + ], + "total_commits": 1, + "head_commit": { + "id": "fc0cf34aac1443223c9dd55f3a901e6b38bd25b0", + "message": "Add a new line to trigger webhook when pushing commit\n", + "url": "https://try.gitea.io/johndoe/webhook-test/commit/fc0cf34aac1443223c9dd55f3a901e6b38bd25b0", + "author": { + "name": "John Doe", + "email": "john.doe@example.org", + "username": "" + }, + "committer": { + "name": "John Doe", + "email": "john.doe@example.org", + "username": "" + }, + "verification": null, + "timestamp": "2022-10-25T17:34:08+02:00", + "added": [], + "removed": [], + "modified": [ + "README.md" + ] + }, + "repository": { + "id": 38171, + "owner": { + "id": 520163, + "login": "johndoe", + "login_name": "", + "full_name": "", + "email": "johndoe@noreply.try.gitea.io", + "avatar_url": "https://try.gitea.io/avatars/1c41d79af82e53f20334d1cf9a3dea7e", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-10-25T15:30:24Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "johndoe" + }, + "name": "webhook-test", + "full_name": "johndoe/webhook-test", + "description": "", + "empty": false, + "private": false, + "fork": false, + "template": false, + "parent": null, + "mirror": false, + "size": 88, + "language": "", + "languages_url": "https://try.gitea.io/api/v1/repos/johndoe/webhook-test/languages", + "html_url": "https://try.gitea.io/johndoe/webhook-test", + "ssh_url": "git@try.gitea.io:johndoe/webhook-test.git", + "clone_url": "https://try.gitea.io/johndoe/webhook-test.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": "2022-10-25T15:31:27Z", + "updated_at": "2022-10-25T15:31:28Z", + "permissions": { + "admin": true, + "push": true, + "pull": true + }, + "has_issues": true, + "internal_tracker": { + "enable_time_tracker": false, + "allow_only_contributors_to_track_time": true, + "enable_issue_dependencies": true + }, + "has_wiki": true, + "has_pull_requests": true, + "has_projects": true, + "ignore_whitespace_conflicts": false, + "allow_merge_commits": true, + "allow_rebase": true, + "allow_rebase_explicit": true, + "allow_squash_merge": true, + "allow_rebase_update": true, + "default_delete_branch_after_merge": false, + "default_merge_style": "merge", + "avatar_url": "", + "internal": false, + "mirror_interval": "", + "mirror_updated": "0001-01-01T00:00:00Z", + "repo_transfer": null + }, + "pusher": { + "id": 520163, + "login": "johndoe", + "login_name": "", + "full_name": "", + "email": "johndoe@noreply.try.gitea.io", + "avatar_url": "https://try.gitea.io/avatars/1c41d79af82e53f20334d1cf9a3dea7e", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-10-25T15:30:24Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "johndoe" + }, + "sender": { + "id": 520163, + "login": "johndoe", + "login_name": "", + "full_name": "", + "email": "johndoe@noreply.try.gitea.io", + "avatar_url": "https://try.gitea.io/avatars/1c41d79af82e53f20334d1cf9a3dea7e", + "language": "", + "is_admin": false, + "last_login": "0001-01-01T00:00:00Z", + "created": "2022-10-25T15:30:24Z", + "restricted": false, + "active": false, + "prohibit_login": false, + "location": "", + "website": "", + "description": "", + "visibility": "public", + "followers_count": 0, + "following_count": 0, + "starred_repos_count": 0, + "username": "johndoe" + } +} \ No newline at end of file diff --git a/swh/web/tests/save_origin_webhooks/data/github_webhook_payload.json b/swh/web/tests/save_origin_webhooks/data/github_webhook_payload.json new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/data/github_webhook_payload.json @@ -0,0 +1,185 @@ +{ + "ref": "refs/heads/main", + "before": "63c72ea620a8d93aa14f0d60b9320881631c83ca", + "after": "366675b3a4017eb3ace4452d8288ae664c4f0af4", + "repository": { + "id": 557370172, + "node_id": "R_kgDOITjLPA", + "name": "webhook-test", + "full_name": "johndoe/webhook-test", + "private": false, + "owner": { + "name": "johndoe", + "email": "john.doe@example.org", + "login": "johndoe", + "id": 5493543, + "node_id": "MDQ6VXNlcjU0OTM1NDM=", + "avatar_url": "", + "gravatar_id": "", + "url": "https://api.github.com/users/johndoe", + "html_url": "https://github.com/johndoe", + "followers_url": "https://api.github.com/users/johndoe/followers", + "following_url": "https://api.github.com/users/johndoe/following{/other_user}", + "gists_url": "https://api.github.com/users/johndoe/gists{/gist_id}", + "starred_url": "https://api.github.com/users/johndoe/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/johndoe/subscriptions", + "organizations_url": "https://api.github.com/users/johndoe/orgs", + "repos_url": "https://api.github.com/users/johndoe/repos", + "events_url": "https://api.github.com/users/johndoe/events{/privacy}", + "received_events_url": "https://api.github.com/users/johndoe/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/johndoe/webhook-test", + "description": "Sample repository to test webhooks delivery", + "fork": false, + "url": "https://github.com/johndoe/webhook-test", + "forks_url": "https://api.github.com/repos/johndoe/webhook-test/forks", + "keys_url": "https://api.github.com/repos/johndoe/webhook-test/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/johndoe/webhook-test/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/johndoe/webhook-test/teams", + "hooks_url": "https://api.github.com/repos/johndoe/webhook-test/hooks", + "issue_events_url": "https://api.github.com/repos/johndoe/webhook-test/issues/events{/number}", + "events_url": "https://api.github.com/repos/johndoe/webhook-test/events", + "assignees_url": "https://api.github.com/repos/johndoe/webhook-test/assignees{/user}", + "branches_url": "https://api.github.com/repos/johndoe/webhook-test/branches{/branch}", + "tags_url": "https://api.github.com/repos/johndoe/webhook-test/tags", + "blobs_url": "https://api.github.com/repos/johndoe/webhook-test/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/johndoe/webhook-test/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/johndoe/webhook-test/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/johndoe/webhook-test/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/johndoe/webhook-test/statuses/{sha}", + "languages_url": "https://api.github.com/repos/johndoe/webhook-test/languages", + "stargazers_url": "https://api.github.com/repos/johndoe/webhook-test/stargazers", + "contributors_url": "https://api.github.com/repos/johndoe/webhook-test/contributors", + "subscribers_url": "https://api.github.com/repos/johndoe/webhook-test/subscribers", + "subscription_url": "https://api.github.com/repos/johndoe/webhook-test/subscription", + "commits_url": "https://api.github.com/repos/johndoe/webhook-test/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/johndoe/webhook-test/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/johndoe/webhook-test/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/johndoe/webhook-test/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/johndoe/webhook-test/contents/{+path}", + "compare_url": "https://api.github.com/repos/johndoe/webhook-test/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/johndoe/webhook-test/merges", + "archive_url": "https://api.github.com/repos/johndoe/webhook-test/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/johndoe/webhook-test/downloads", + "issues_url": "https://api.github.com/repos/johndoe/webhook-test/issues{/number}", + "pulls_url": "https://api.github.com/repos/johndoe/webhook-test/pulls{/number}", + "milestones_url": "https://api.github.com/repos/johndoe/webhook-test/milestones{/number}", + "notifications_url": "https://api.github.com/repos/johndoe/webhook-test/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/johndoe/webhook-test/labels{/name}", + "releases_url": "https://api.github.com/repos/johndoe/webhook-test/releases{/id}", + "deployments_url": "https://api.github.com/repos/johndoe/webhook-test/deployments", + "created_at": 1666710385, + "updated_at": "2022-10-25T15:06:25Z", + "pushed_at": 1666710739, + "git_url": "git://github.com/johndoe/webhook-test.git", + "ssh_url": "git@github.com:johndoe/webhook-test.git", + "clone_url": "https://github.com/johndoe/webhook-test.git", + "svn_url": "https://github.com/johndoe/webhook-test", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": null, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "stargazers": 0, + "master_branch": "main" + }, + "pusher": { + "name": "johndoe", + "email": "john.doe@example.org" + }, + "sender": { + "login": "johndoe", + "id": 5493543, + "node_id": "MDQ6VXNlcjU0OTM1NDM=", + "avatar_url": "", + "gravatar_id": "", + "url": "https://api.github.com/users/johndoe", + "html_url": "https://github.com/johndoe", + "followers_url": "https://api.github.com/users/johndoe/followers", + "following_url": "https://api.github.com/users/johndoe/following{/other_user}", + "gists_url": "https://api.github.com/users/johndoe/gists{/gist_id}", + "starred_url": "https://api.github.com/users/johndoe/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/johndoe/subscriptions", + "organizations_url": "https://api.github.com/users/johndoe/orgs", + "repos_url": "https://api.github.com/users/johndoe/repos", + "events_url": "https://api.github.com/users/johndoe/events{/privacy}", + "received_events_url": "https://api.github.com/users/johndoe/received_events", + "type": "User", + "site_admin": false + }, + "created": false, + "deleted": false, + "forced": false, + "base_ref": null, + "compare": "https://github.com/johndoe/webhook-test/compare/63c72ea620a8...366675b3a401", + "commits": [ + { + "id": "366675b3a4017eb3ace4452d8288ae664c4f0af4", + "tree_id": "e824f22f6528dfae5489e6a08888f460eb74f975", + "distinct": true, + "message": "Add a new line to trigger webhook when pushing commit", + "timestamp": "2022-10-25T17:12:09+02:00", + "url": "https://github.com/johndoe/webhook-test/commit/366675b3a4017eb3ace4452d8288ae664c4f0af4", + "author": { + "name": "John Doe", + "email": "johndoe@softwareheritage.org", + "username": "johndoe" + }, + "committer": { + "name": "John Doe", + "email": "johndoe@softwareheritage.org", + "username": "johndoe" + }, + "added": [], + "removed": [], + "modified": [ + "README.md" + ] + } + ], + "head_commit": { + "id": "366675b3a4017eb3ace4452d8288ae664c4f0af4", + "tree_id": "e824f22f6528dfae5489e6a08888f460eb74f975", + "distinct": true, + "message": "Add a new line to trigger webhook when pushing commit", + "timestamp": "2022-10-25T17:12:09+02:00", + "url": "https://github.com/johndoe/webhook-test/commit/366675b3a4017eb3ace4452d8288ae664c4f0af4", + "author": { + "name": "John Doe", + "email": "johndoe@softwareheritage.org", + "username": "johndoe" + }, + "committer": { + "name": "John Doe", + "email": "johndoe@softwareheritage.org", + "username": "johndoe" + }, + "added": [], + "removed": [], + "modified": [ + "README.md" + ] + } +} \ No newline at end of file diff --git a/swh/web/tests/save_origin_webhooks/data/gitlab_webhook_payload.json b/swh/web/tests/save_origin_webhooks/data/gitlab_webhook_payload.json new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/data/gitlab_webhook_payload.json @@ -0,0 +1,62 @@ +{ + "object_kind": "push", + "event_name": "push", + "before": "1b0392a66f1439bac26c0845a083fe64f01a00fe", + "after": "87b5530bd21f10330bc1437296295205112088c5", + "ref": "refs/heads/main", + "checkout_sha": "87b5530bd21f10330bc1437296295205112088c5", + "message": null, + "user_id": 4775591, + "user_name": "John Doe", + "user_username": "johndoe", + "user_email": "", + "user_avatar": "", + "project_id": 39639957, + "project": { + "id": 39639957, + "name": "webhook-test", + "description": "", + "web_url": "https://gitlab.com/johndoe/test", + "avatar_url": null, + "git_ssh_url": "git@gitlab.com:johndoe/test.git", + "git_http_url": "https://gitlab.com/johndoe/test.git", + "namespace": "John Doe", + "visibility_level": 20, + "path_with_namespace": "johndoe/test", + "default_branch": "main", + "ci_config_path": "", + "homepage": "https://gitlab.com/johndoe/test", + "url": "git@gitlab.com:johndoe/test.git", + "ssh_url": "git@gitlab.com:johndoe/test.git", + "http_url": "https://gitlab.com/johndoe/test.git" + }, + "commits": [ + { + "id": "87b5530bd21f10330bc1437296295205112088c5", + "message": "Remove line to trigger webhook when pushing commit\n", + "title": "Remove line to trigger webhook when pushing commit", + "timestamp": "2022-10-25T17:24:50+02:00", + "url": "https://gitlab.com/johndoe/test/-/commit/87b5530bd21f10330bc1437296295205112088c5", + "author": { + "name": "John Doe", + "email": "[REDACTED]" + }, + "added": [], + "modified": [ + "README.md" + ], + "removed": [] + } + ], + "total_commits_count": 1, + "push_options": {}, + "repository": { + "name": "webhook-test", + "url": "git@gitlab.com:johndoe/test.git", + "description": "", + "homepage": "https://gitlab.com/johndoe/test", + "git_http_url": "https://gitlab.com/johndoe/test.git", + "git_ssh_url": "git@gitlab.com:johndoe/test.git", + "visibility_level": 20 + } +} \ No newline at end of file diff --git a/swh/web/tests/save_origin_webhooks/data/https_sourceforge.net/rest_p_webhook-test-git b/swh/web/tests/save_origin_webhooks/data/https_sourceforge.net/rest_p_webhook-test-git new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/data/https_sourceforge.net/rest_p_webhook-test-git @@ -0,0 +1,81 @@ +{ + "shortname": "webhook-test-git", + "name": "webhook-test-git", + "_id": "635802e25fe6a2c790df6731", + "url": "https://sourceforge.net/p/webhook-test-git/", + "private": false, + "short_description": "", + "creation_date": "2022-10-25", + "summary": "", + "external_homepage": "https://webhook-test-git.sourceforge.io", + "video_url": "", + "socialnetworks": [], + "status": "active", + "moved_to_url": "", + "preferred_support_tool": "", + "preferred_support_url": "", + "developers": [ + { + "username": "johndoe", + "name": "John Doe", + "url": "https://sourceforge.net/u/johndoe/" + } + ], + "tools": [ + { + "name": "activity", + "mount_point": "activity", + "url": "https://sourceforge.net/p/webhook-test-git/activity/", + "mount_label": "Activity", + "api_url": "https://sourceforge.net/rest/p/webhook-test-git/activity/" + }, + { + "name": "summary", + "mount_point": "summary", + "url": "https://sourceforge.net/p/webhook-test-git/summary/", + "mount_label": "Summary", + "sourceforge_group_id": 3568118 + }, + { + "name": "files-sf", + "mount_point": "files-sf", + "url": "https://sourceforge.net/p/webhook-test-git/files-sf/", + "mount_label": "Files" + }, + { + "name": "reviews", + "mount_point": "reviews", + "url": "https://sourceforge.net/p/webhook-test-git/reviews/", + "mount_label": "Reviews" + }, + { + "name": "support", + "mount_point": "support", + "url": "https://sourceforge.net/p/webhook-test-git/support/", + "mount_label": "Support" + }, + { + "name": "git", + "mount_point": "code", + "url": "https://sourceforge.net/p/webhook-test-git/code/", + "mount_label": "Code", + "api_url": "https://sourceforge.net/rest/p/webhook-test-git/code/", + "clone_url_https_anon": "https://git.code.sf.net/p/webhook-test-git/code", + "clone_url_ro": "git://git.code.sf.net/p/webhook-test-git/code" + } + ], + "labels": [], + "categories": { + "audience": [], + "developmentstatus": [], + "environment": [], + "language": [], + "license": [], + "translation": [], + "os": [], + "database": [], + "topic": [] + }, + "icon_url": null, + "screenshots": [] +} \ No newline at end of file diff --git a/swh/web/tests/save_origin_webhooks/data/https_sourceforge.net/rest_p_webhook-test-hg b/swh/web/tests/save_origin_webhooks/data/https_sourceforge.net/rest_p_webhook-test-hg new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/data/https_sourceforge.net/rest_p_webhook-test-hg @@ -0,0 +1,80 @@ +{ + "shortname": "webhook-test-hg", + "name": "webhook-test-hg", + "_id": "63580743079e46f00071122b", + "url": "https://sourceforge.net/p/webhook-test-hg/", + "private": false, + "short_description": "", + "creation_date": "2022-10-25", + "summary": "", + "external_homepage": "https://webhook-test-hg.sourceforge.io", + "video_url": "", + "socialnetworks": [], + "status": "active", + "moved_to_url": "", + "preferred_support_tool": "", + "preferred_support_url": "", + "developers": [ + { + "username": "johndoe", + "name": "John Doe", + "url": "https://sourceforge.net/u/johndoe/" + } + ], + "tools": [ + { + "name": "activity", + "mount_point": "activity", + "url": "https://sourceforge.net/p/webhook-test-hg/activity/", + "mount_label": "Activity", + "api_url": "https://sourceforge.net/rest/p/webhook-test-hg/activity/" + }, + { + "name": "summary", + "mount_point": "summary", + "url": "https://sourceforge.net/p/webhook-test-hg/summary/", + "mount_label": "Summary", + "sourceforge_group_id": 3568128 + }, + { + "name": "files-sf", + "mount_point": "files-sf", + "url": "https://sourceforge.net/p/webhook-test-hg/files-sf/", + "mount_label": "Files" + }, + { + "name": "reviews", + "mount_point": "reviews", + "url": "https://sourceforge.net/p/webhook-test-hg/reviews/", + "mount_label": "Reviews" + }, + { + "name": "support", + "mount_point": "support", + "url": "https://sourceforge.net/p/webhook-test-hg/support/", + "mount_label": "Support" + }, + { + "name": "hg", + "mount_point": "code", + "url": "https://sourceforge.net/p/webhook-test-hg/code/", + "mount_label": "Code", + "api_url": "https://sourceforge.net/rest/p/webhook-test-hg/code/", + "clone_url_ro": "http://hg.code.sf.net/p/webhook-test-hg/code" + } + ], + "labels": [], + "categories": { + "audience": [], + "developmentstatus": [], + "environment": [], + "language": [], + "license": [], + "translation": [], + "os": [], + "database": [], + "topic": [] + }, + "icon_url": null, + "screenshots": [] +} \ No newline at end of file diff --git a/swh/web/tests/save_origin_webhooks/data/https_sourceforge.net/rest_p_webhook-test-svn b/swh/web/tests/save_origin_webhooks/data/https_sourceforge.net/rest_p_webhook-test-svn new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/data/https_sourceforge.net/rest_p_webhook-test-svn @@ -0,0 +1,81 @@ +{ + "shortname": "webhook-test-svn", + "name": "webhook-test-svn", + "_id": "635805ebe3c1ec1e7d0eab36", + "url": "https://sourceforge.net/p/webhook-test-svn/", + "private": false, + "short_description": "", + "creation_date": "2022-10-25", + "summary": "", + "external_homepage": "https://webhook-test-svn.sourceforge.io", + "video_url": "", + "socialnetworks": [], + "status": "active", + "moved_to_url": "", + "preferred_support_tool": "", + "preferred_support_url": "", + "developers": [ + { + "username": "johndoe", + "name": "John Doe", + "url": "https://sourceforge.net/u/johndoe/" + } + ], + "tools": [ + { + "name": "activity", + "mount_point": "activity", + "url": "https://sourceforge.net/p/webhook-test-svn/activity/", + "mount_label": "Activity", + "api_url": "https://sourceforge.net/rest/p/webhook-test-svn/activity/" + }, + { + "name": "summary", + "mount_point": "summary", + "url": "https://sourceforge.net/p/webhook-test-svn/summary/", + "mount_label": "Summary", + "sourceforge_group_id": 3568124 + }, + { + "name": "files-sf", + "mount_point": "files-sf", + "url": "https://sourceforge.net/p/webhook-test-svn/files-sf/", + "mount_label": "Files" + }, + { + "name": "reviews", + "mount_point": "reviews", + "url": "https://sourceforge.net/p/webhook-test-svn/reviews/", + "mount_label": "Reviews" + }, + { + "name": "support", + "mount_point": "support", + "url": "https://sourceforge.net/p/webhook-test-svn/support/", + "mount_label": "Support" + }, + { + "name": "svn", + "mount_point": "code", + "url": "https://sourceforge.net/p/webhook-test-svn/code/", + "mount_label": "Code", + "api_url": "https://sourceforge.net/rest/p/webhook-test-svn/code/", + "clone_url_https_anon": "https://svn.code.sf.net/p/webhook-test-svn/code/", + "clone_url_ro": "svn://svn.code.sf.net/p/webhook-test-svn/code/" + } + ], + "labels": [], + "categories": { + "audience": [], + "developmentstatus": [], + "environment": [], + "language": [], + "license": [], + "translation": [], + "os": [], + "database": [], + "topic": [] + }, + "icon_url": null, + "screenshots": [] +} \ No newline at end of file diff --git a/swh/web/tests/save_origin_webhooks/data/sourceforge_webhook_payload_git.json b/swh/web/tests/save_origin_webhooks/data/sourceforge_webhook_payload_git.json new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/data/sourceforge_webhook_payload_git.json @@ -0,0 +1,36 @@ +{ + "size": 1, + "commits": [ + { + "id": "42157ad6b5d5dee256ba450c9d7a1e70a31bb701", + "url": "https://sourceforge.net/p/webhook-test-git/code/ci/42157ad6b5d5dee256ba450c9d7a1e70a31bb701/", + "timestamp": "2022-10-25T15:43:40Z", + "message": "Add REAME.md", + "author": { + "name": "John Doe", + "email": "john.doe@example.org", + "username": "" + }, + "committer": { + "name": "John Doe", + "email": "john.doe@example.org", + "username": "" + }, + "added": [ + "README.md" + ], + "removed": [], + "modified": [], + "copied": [], + "renamed": [] + } + ], + "before": "", + "after": "42157ad6b5d5dee256ba450c9d7a1e70a31bb701", + "repository": { + "name": "Code", + "full_name": "/p/webhook-test-git/code/", + "url": "https://sourceforge.net/p/webhook-test-git/code/" + }, + "ref": "refs/heads/master" +} \ No newline at end of file diff --git a/swh/web/tests/save_origin_webhooks/data/sourceforge_webhook_payload_hg.json b/swh/web/tests/save_origin_webhooks/data/sourceforge_webhook_payload_hg.json new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/data/sourceforge_webhook_payload_hg.json @@ -0,0 +1,36 @@ +{ + "size": 1, + "commits": [ + { + "id": "c4e59883381e38e79e9c0418e759392da565629d", + "url": "https://sourceforge.net/p/webhook-test-hg/code/ci/c4e59883381e38e79e9c0418e759392da565629d/", + "timestamp": "2022-10-25T15:59:02Z", + "message": "Initial commit", + "author": { + "name": "johndoe", + "email": "john.doe@example.org", + "username": "" + }, + "committer": { + "name": "johndoe", + "email": "john.doe@example.org", + "username": "" + }, + "added": [ + "README" + ], + "removed": [], + "modified": [], + "copied": [], + "renamed": [] + } + ], + "before": "", + "after": "c4e59883381e38e79e9c0418e759392da565629d", + "repository": { + "name": "Code", + "full_name": "/p/webhook-test-hg/code/", + "url": "https://sourceforge.net/p/webhook-test-hg/code/" + }, + "ref": "refs/heads/default" +} \ No newline at end of file diff --git a/swh/web/tests/save_origin_webhooks/data/sourceforge_webhook_payload_svn.json b/swh/web/tests/save_origin_webhooks/data/sourceforge_webhook_payload_svn.json new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/data/sourceforge_webhook_payload_svn.json @@ -0,0 +1,37 @@ +{ + "size": 1, + "commits": [ + { + "id": "r1", + "url": "https://sourceforge.net/p/webhook-test-svn/code/1/", + "timestamp": "2022-10-25T15:53:04Z", + "message": "Add initial directories", + "author": { + "name": "johndoe", + "email": "", + "username": "" + }, + "committer": { + "name": "johndoe", + "email": "", + "username": "" + }, + "added": [ + "/branches", + "/tags", + "/trunk" + ], + "removed": [], + "modified": [], + "copied": [], + "renamed": [] + } + ], + "before": "", + "after": "r1", + "repository": { + "name": "Code", + "full_name": "/p/webhook-test-svn/code/", + "url": "https://sourceforge.net/p/webhook-test-svn/code/" + } +} \ No newline at end of file diff --git a/swh/web/tests/save_origin_webhooks/test_bitbucket.py b/swh/web/tests/save_origin_webhooks/test_bitbucket.py new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/test_bitbucket.py @@ -0,0 +1,88 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import json +import os + +import pytest + +from .utils import ( + origin_save_webhook_receiver_invalid_content_type_test, + origin_save_webhook_receiver_invalid_event_test, + origin_save_webhook_receiver_invalid_request_test, + origin_save_webhook_receiver_no_repo_url_test, + origin_save_webhook_receiver_test, +) + + +@pytest.mark.django_db +def test_origin_save_bitbucket_webhook_receiver(api_client, swh_scheduler, datadir): + with open(os.path.join(datadir, "bitbucket_webhook_payload.json"), "rb") as payload: + origin_save_webhook_receiver_test( + forge_type="Bitbucket", + http_headers={ + "User-Agent": "Bitbucket-Webhooks/2.0", + "X-Event-Key": "repo:push", + }, + payload=json.load(payload), + expected_origin_url="https://bitbucket.org/johndoe/webhook-test.git", + expected_visit_type="git", + api_client=api_client, + swh_scheduler=swh_scheduler, + ) + + +def test_origin_save_bitbucket_webhook_receiver_invalid_request( + api_client, +): + origin_save_webhook_receiver_invalid_request_test( + forge_type="Bitbucket", + http_headers={}, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_bitbucket_webhook_receiver_invalid_event( + api_client, +): + origin_save_webhook_receiver_invalid_event_test( + forge_type="Bitbucket", + http_headers={ + "User-Agent": "Bitbucket-Webhooks/2.0", + "X-Event-Key": "repo:fork", + }, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_bitbucket_webhook_receiver_invalid_content_type( + api_client, +): + origin_save_webhook_receiver_invalid_content_type_test( + forge_type="Bitbucket", + http_headers={ + "User-Agent": "Bitbucket-Webhooks/2.0", + "X-Event-Key": "repo:push", + }, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_bitbucket_webhook_receiver_no_repo_url(api_client, datadir): + with open(os.path.join(datadir, "bitbucket_webhook_payload.json"), "rb") as payload: + payload = json.load(payload) + del payload["repository"] + origin_save_webhook_receiver_no_repo_url_test( + forge_type="Bitbucket", + http_headers={ + "User-Agent": "Bitbucket-Webhooks/2.0", + "X-Event-Key": "repo:push", + }, + payload=payload, + api_client=api_client, + ) diff --git a/swh/web/tests/save_origin_webhooks/test_gitea.py b/swh/web/tests/save_origin_webhooks/test_gitea.py new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/test_gitea.py @@ -0,0 +1,87 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import json +import os + +import pytest + +from swh.web.save_code_now.models import SaveAuthorizedOrigin + +from .utils import ( + origin_save_webhook_receiver_invalid_content_type_test, + origin_save_webhook_receiver_invalid_event_test, + origin_save_webhook_receiver_invalid_request_test, + origin_save_webhook_receiver_no_repo_url_test, + origin_save_webhook_receiver_test, +) + + +@pytest.mark.django_db +def test_origin_save_gitea_webhook_receiver(api_client, swh_scheduler, datadir): + SaveAuthorizedOrigin.objects.create(url="https://try.gitea.io/") + with open(os.path.join(datadir, "gitea_webhook_payload.json"), "rb") as payload: + origin_save_webhook_receiver_test( + forge_type="Gitea", + http_headers={ + "X-Gitea-Event": "push", + }, + payload=json.load(payload), + expected_origin_url="https://try.gitea.io/johndoe/webhook-test.git", + expected_visit_type="git", + api_client=api_client, + swh_scheduler=swh_scheduler, + ) + + +def test_origin_save_gitea_webhook_receiver_invalid_request( + api_client, +): + origin_save_webhook_receiver_invalid_request_test( + forge_type="Gitea", + http_headers={}, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_gitea_webhook_receiver_invalid_event( + api_client, +): + origin_save_webhook_receiver_invalid_event_test( + forge_type="Gitea", + http_headers={ + "X-Gitea-Event": "issues", + }, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_gitea_webhook_receiver_invalid_content_type( + api_client, +): + origin_save_webhook_receiver_invalid_content_type_test( + forge_type="Gitea", + http_headers={ + "X-Gitea-Event": "push", + }, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_gitea_webhook_receiver_no_repo_url(api_client, datadir): + with open(os.path.join(datadir, "gitea_webhook_payload.json"), "rb") as payload: + payload = json.load(payload) + del payload["repository"] + origin_save_webhook_receiver_no_repo_url_test( + forge_type="Gitea", + http_headers={ + "X-Gitea-Event": "push", + }, + payload=payload, + api_client=api_client, + ) diff --git a/swh/web/tests/save_origin_webhooks/test_github.py b/swh/web/tests/save_origin_webhooks/test_github.py new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/test_github.py @@ -0,0 +1,88 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import json +import os + +import pytest + +from .utils import ( + origin_save_webhook_receiver_invalid_content_type_test, + origin_save_webhook_receiver_invalid_event_test, + origin_save_webhook_receiver_invalid_request_test, + origin_save_webhook_receiver_no_repo_url_test, + origin_save_webhook_receiver_test, +) + + +@pytest.mark.django_db +def test_origin_save_github_webhook_receiver(api_client, swh_scheduler, datadir): + with open(os.path.join(datadir, "github_webhook_payload.json"), "rb") as payload: + origin_save_webhook_receiver_test( + forge_type="GitHub", + http_headers={ + "User-Agent": "GitHub-Hookshot/ede37db", + "X-GitHub-Event": "push", + }, + payload=json.load(payload), + expected_origin_url="https://github.com/johndoe/webhook-test", + expected_visit_type="git", + api_client=api_client, + swh_scheduler=swh_scheduler, + ) + + +def test_origin_save_github_webhook_receiver_invalid_request( + api_client, +): + origin_save_webhook_receiver_invalid_request_test( + forge_type="GitHub", + http_headers={}, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_github_webhook_receiver_invalid_event( + api_client, +): + origin_save_webhook_receiver_invalid_event_test( + forge_type="GitHub", + http_headers={ + "User-Agent": "GitHub-Hookshot/ede37db", + "X-GitHub-Event": "issues", + }, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_github_webhook_receiver_invalid_content_type( + api_client, +): + origin_save_webhook_receiver_invalid_content_type_test( + forge_type="GitHub", + http_headers={ + "User-Agent": "GitHub-Hookshot/ede37db", + "X-GitHub-Event": "push", + }, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_github_webhook_receiver_no_repo_url(api_client, datadir): + with open(os.path.join(datadir, "github_webhook_payload.json"), "rb") as payload: + payload = json.load(payload) + del payload["repository"] + origin_save_webhook_receiver_no_repo_url_test( + forge_type="GitHub", + http_headers={ + "User-Agent": "GitHub-Hookshot/ede37db", + "X-GitHub-Event": "push", + }, + payload=payload, + api_client=api_client, + ) diff --git a/swh/web/tests/save_origin_webhooks/test_gitlab.py b/swh/web/tests/save_origin_webhooks/test_gitlab.py new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/test_gitlab.py @@ -0,0 +1,88 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import json +import os + +import pytest + +from .utils import ( + origin_save_webhook_receiver_invalid_content_type_test, + origin_save_webhook_receiver_invalid_event_test, + origin_save_webhook_receiver_invalid_request_test, + origin_save_webhook_receiver_no_repo_url_test, + origin_save_webhook_receiver_test, +) + + +@pytest.mark.django_db +def test_origin_save_gitlab_webhook_receiver(api_client, swh_scheduler, datadir): + with open(os.path.join(datadir, "gitlab_webhook_payload.json"), "rb") as payload: + origin_save_webhook_receiver_test( + forge_type="GitLab", + http_headers={ + "User-Agent": "GitLab/15.6.0-pre", + "X-Gitlab-Event": "Push Hook", + }, + payload=json.load(payload), + expected_origin_url="https://gitlab.com/johndoe/test.git", + expected_visit_type="git", + api_client=api_client, + swh_scheduler=swh_scheduler, + ) + + +def test_origin_save_gitlab_webhook_receiver_invalid_request( + api_client, +): + origin_save_webhook_receiver_invalid_request_test( + forge_type="GitLab", + http_headers={}, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_gitlab_webhook_receiver_invalid_event( + api_client, +): + origin_save_webhook_receiver_invalid_event_test( + forge_type="GitLab", + http_headers={ + "User-Agent": "GitLab/15.6.0-pre", + "X-Gitlab-Event": "Issue Hook", + }, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_gitlab_webhook_receiver_invalid_content_type( + api_client, +): + origin_save_webhook_receiver_invalid_content_type_test( + forge_type="GitLab", + http_headers={ + "User-Agent": "GitLab/15.6.0-pre", + "X-Gitlab-Event": "Push Hook", + }, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_gitlab_webhook_receiver_no_repo_url(api_client, datadir): + with open(os.path.join(datadir, "gitlab_webhook_payload.json"), "rb") as payload: + payload = json.load(payload) + del payload["repository"] + origin_save_webhook_receiver_no_repo_url_test( + forge_type="GitLab", + http_headers={ + "User-Agent": "GitLab/15.6.0-pre", + "X-Gitlab-Event": "Push Hook", + }, + payload=payload, + api_client=api_client, + ) diff --git a/swh/web/tests/save_origin_webhooks/test_sourceforge.py b/swh/web/tests/save_origin_webhooks/test_sourceforge.py new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/test_sourceforge.py @@ -0,0 +1,100 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import json +import os + +import pytest + +from .utils import ( + origin_save_webhook_receiver_invalid_content_type_test, + origin_save_webhook_receiver_invalid_request_test, + origin_save_webhook_receiver_no_repo_url_test, + origin_save_webhook_receiver_test, +) + + +@pytest.mark.django_db +@pytest.mark.parametrize( + "payload_file,expected_origin_url,expected_visit_type", + [ + ( + "sourceforge_webhook_payload_hg.json", + "http://hg.code.sf.net/p/webhook-test-hg/code", + "hg", + ), + ( + "sourceforge_webhook_payload_git.json", + "https://git.code.sf.net/p/webhook-test-git/code", + "git", + ), + ( + "sourceforge_webhook_payload_svn.json", + "https://svn.code.sf.net/p/webhook-test-svn/code/", + "svn", + ), + ], +) +def test_origin_save_sourceforge_webhook_receiver( + api_client, + swh_scheduler, + datadir, + requests_mock_datadir, + payload_file, + expected_origin_url, + expected_visit_type, +): + with open(os.path.join(datadir, payload_file), "rb") as payload: + origin_save_webhook_receiver_test( + forge_type="SourceForge", + http_headers={ + "User-Agent": "Allura Webhook (https://allura.apache.org/)", + }, + payload=json.load(payload), + expected_origin_url=expected_origin_url, + expected_visit_type=expected_visit_type, + api_client=api_client, + swh_scheduler=swh_scheduler, + ) + + +def test_origin_save_sourceforge_webhook_receiver_invalid_request( + api_client, +): + origin_save_webhook_receiver_invalid_request_test( + forge_type="SourceForge", + http_headers={}, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_sourceforge_webhook_receiver_invalid_content_type( + api_client, +): + origin_save_webhook_receiver_invalid_content_type_test( + forge_type="SourceForge", + http_headers={ + "User-Agent": "Allura Webhook (https://allura.apache.org/)", + }, + payload={}, + api_client=api_client, + ) + + +def test_origin_save_sourceforge_webhook_receiver_no_repo_url(api_client, datadir): + with open( + os.path.join(datadir, "sourceforge_webhook_payload_git.json"), "rb" + ) as payload: + payload = json.load(payload) + del payload["repository"] + origin_save_webhook_receiver_no_repo_url_test( + forge_type="SourceForge", + http_headers={ + "User-Agent": "Allura Webhook (https://allura.apache.org/)", + }, + payload=payload, + api_client=api_client, + ) diff --git a/swh/web/tests/save_origin_webhooks/utils.py b/swh/web/tests/save_origin_webhooks/utils.py new file mode 100644 --- /dev/null +++ b/swh/web/tests/save_origin_webhooks/utils.py @@ -0,0 +1,143 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from typing import Any, Dict + +from swh.web.tests.helpers import check_api_post_responses +from swh.web.utils import reverse + + +def _django_http_headers(http_headers: Dict[str, Any]): + return {f"HTTP_{k.upper().replace('-', '_')}": v for k, v in http_headers.items()} + + +def origin_save_webhook_receiver_test( + forge_type: str, + http_headers: Dict[str, Any], + payload: Dict[str, Any], + expected_origin_url: str, + expected_visit_type: str, + api_client, + swh_scheduler, +): + url = reverse(f"api-1-origin-save-webhook-{forge_type.lower()}") + + resp = check_api_post_responses( + api_client, + url, + status_code=200, + data=payload, + **_django_http_headers(http_headers), + ) + + assert resp.data["origin_url"] == expected_origin_url + assert resp.data["visit_type"] == expected_visit_type + + tasks = swh_scheduler.search_tasks(task_type=f"load-{expected_visit_type}") + assert tasks + task = dict(tasks[0].items()) + assert task["arguments"]["kwargs"]["url"] == expected_origin_url + + +def origin_save_webhook_receiver_invalid_request_test( + forge_type: str, + http_headers: Dict[str, Any], + payload: Dict[str, Any], + api_client, +): + url = reverse(f"api-1-origin-save-webhook-{forge_type.lower()}") + + resp = check_api_post_responses( + api_client, + url, + status_code=400, + data=payload, + **_django_http_headers(http_headers), + ) + + assert resp.data == { + "exception": "BadInputExc", + "reason": ( + f"POST request has not been sent by a {forge_type} webhook " + "and has not been processed." + ), + } + + +def origin_save_webhook_receiver_invalid_event_test( + forge_type: str, + http_headers: Dict[str, Any], + payload: Dict[str, Any], + api_client, +): + url = reverse(f"api-1-origin-save-webhook-{forge_type.lower()}") + + resp = check_api_post_responses( + api_client, + url, + status_code=400, + data=payload, + **_django_http_headers(http_headers), + ) + + assert resp.data == { + "exception": "BadInputExc", + "reason": ( + f"Event sent by {forge_type} webhook is not a push one, request has " + "not been processed." + ), + } + + +def origin_save_webhook_receiver_invalid_content_type_test( + forge_type: str, + http_headers: Dict[str, Any], + payload: Dict[str, Any], + api_client, +): + url = reverse(f"api-1-origin-save-webhook-{forge_type.lower()}") + + bad_content_type = "application/x-www-form-urlencoded" + http_headers["Content-Type"] = bad_content_type + + resp = check_api_post_responses( + api_client, + url, + status_code=400, + data=payload, + **_django_http_headers(http_headers), + ) + + assert resp.data == { + "exception": "BadInputExc", + "reason": ( + f"Invalid content type '{bad_content_type}' for the POST request sent by " + f"{forge_type} webhook, it should be 'application/json'." + ), + } + + +def origin_save_webhook_receiver_no_repo_url_test( + forge_type: str, + http_headers: Dict[str, Any], + payload: Dict[str, Any], + api_client, +): + url = reverse(f"api-1-origin-save-webhook-{forge_type.lower()}") + + resp = check_api_post_responses( + api_client, + url, + status_code=400, + data=payload, + **_django_http_headers(http_headers), + ) + + assert resp.data == { + "exception": "BadInputExc", + "reason": ( + f"Repository URL could not be extracted from {forge_type} webhook payload." + ), + }