Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F7123843
D8800.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
77 KB
Subscribers
None
D8800.diff
View Options
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
Details
Attached
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
Attached To
D8800: save_origin_webhooks, class edition
Event Timeline
Log In to Comment