Page MenuHomeSoftware Heritage

D8800.diff
No OneTemporary

D8800.diff

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 <john.doe@example.org>",
+ "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": "<p>Initial commit</p>"
+ },
+ "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 <john.doe@example.org>",
+ "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": "<p>Add new line in README to trigger webhook when pushing commit</p>"
+ },
+ "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 <john.doe@example.org>",
+ "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": "<p>Add new line in README to trigger webhook when pushing commit</p>"
+ },
+ "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."
+ ),
+ }

File Metadata

Mime Type
text/plain
Expires
Dec 20 2024, 3:58 AM (11 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3218498

Event Timeline