From 77072cce4cb6b8ceecd2f53e607deef532f85938 Mon Sep 17 00:00:00 2001 From: saadmk11 Date: Sun, 11 Aug 2019 21:25:32 +0600 Subject: [PATCH 1/9] PR Builder GitLab Integration added --- readthedocs/api/v2/views/integrations.py | 53 ++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/readthedocs/api/v2/views/integrations.py b/readthedocs/api/v2/views/integrations.py index fd3cdedeb98..3825c5752fc 100644 --- a/readthedocs/api/v2/views/integrations.py +++ b/readthedocs/api/v2/views/integrations.py @@ -42,6 +42,12 @@ GITHUB_PULL_REQUEST_SYNC = 'synchronize' GITHUB_CREATE = 'create' GITHUB_DELETE = 'delete' +GITLAB_MERGE_REQUEST = 'merge_request' +GITLAB_MERGE_REQUEST_CLOSE = 'close' +GITLAB_MERGE_REQUEST_MERGE = 'merge' +GITLAB_MERGE_REQUEST_OPEN = 'open' +GITLAB_MERGE_REQUEST_REOPEN = 'reopen' +GITLAB_MERGE_REQUEST_UPDATE = 'update' GITLAB_TOKEN_HEADER = 'HTTP_X_GITLAB_TOKEN' GITLAB_PUSH = 'push' GITLAB_NULL_HASH = '0' * 40 @@ -413,10 +419,26 @@ class GitLabWebhookView(WebhookMixin, APIView): ... } + For merge_request events: + + { + "object_kind": "merge_request", + "object_attributes": { + "iid": 2, + "last_commit": { + "id": "717abb9a6a0f3111dbd601ef6f58c70bdd165aef", + }, + "action": "open" + ... + }, + ... + } + See full payload here: - https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#push-events - https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#tag-events + - https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#merge-request-events """ integration_type = Integration.GITLAB_WEBHOOK @@ -441,6 +463,17 @@ def is_payload_valid(self): return False return token == secret + def get_external_version_data(self): + """Get Commit Sha and merge request number from payload.""" + try: + identifier = self.data['object_attributes']['last_commit']['id'] + verbose_name = str(self.data['object_attributes']['iid']) + + return identifier, verbose_name + + except KeyError: + raise ParseError('Parameters "id" and "iid" are required') + def handle_webhook(self): """ Handle GitLab events for push and tag_push. @@ -450,6 +483,7 @@ def handle_webhook(self): 0000000000000000000000000000000000000000 ('0' * 40) """ event = self.request.data.get('object_kind', GITLAB_PUSH) + action = self.data.get('object_attributes', {}).get('action', None) webhook_gitlab.send( Project, project=self.project, @@ -470,6 +504,25 @@ def handle_webhook(self): return self.get_response_push(self.project, branches) except KeyError: raise ParseError('Parameter "ref" is required') + + if ( + self.project.has_feature(Feature.EXTERNAL_VERSION_BUILD) and + event == GITLAB_MERGE_REQUEST and action + ): + if ( + action in + [ + GITLAB_MERGE_REQUEST_OPEN, + GITLAB_MERGE_REQUEST_REOPEN, + GITLAB_MERGE_REQUEST_UPDATE + ] + ): + # Handle open, update, reopen merge_request event. + return self.get_external_version_response(self.project) + + if action in [GITLAB_MERGE_REQUEST_CLOSE, GITLAB_MERGE_REQUEST_MERGE]: + # Handle merge and close merge_request event. + return self.get_delete_external_version_response(self.project) return None def _normalize_ref(self, ref): From 5e579032c14b381c02bac66bd69779ec6302a510 Mon Sep 17 00:00:00 2001 From: saadmk11 Date: Sun, 11 Aug 2019 21:48:14 +0600 Subject: [PATCH 2/9] provider name for gitlab added --- readthedocs/builds/constants.py | 1 + readthedocs/builds/models.py | 6 +++++- readthedocs/projects/constants.py | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/readthedocs/builds/constants.py b/readthedocs/builds/constants.py index d122c9065de..e506367344d 100644 --- a/readthedocs/builds/constants.py +++ b/readthedocs/builds/constants.py @@ -91,4 +91,5 @@ RTD_BUILD_STATUS_API_NAME = 'continuous-documentation/read-the-docs' GITHUB_EXTERNAL_VERSION_NAME = 'Pull Request' +GITLAB_EXTERNAL_VERSION_NAME = 'Merge Request' GENERIC_EXTERNAL_VERSION_NAME = 'External Version' diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index 1ddd93fcc57..88d6b0a7ed0 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -27,6 +27,7 @@ GITHUB_URL, GITHUB_PULL_REQUEST_URL, GITHUB_PULL_REQUEST_COMMIT_URL, + GITLAB_BRAND, GITLAB_COMMIT_URL, GITLAB_URL, PRIVACY_CHOICES, @@ -44,6 +45,7 @@ BUILD_TYPES, GENERIC_EXTERNAL_VERSION_NAME, GITHUB_EXTERNAL_VERSION_NAME, + GITLAB_EXTERNAL_VERSION_NAME, INTERNAL, LATEST, NON_REPOSITORY_VERSIONS, @@ -845,7 +847,9 @@ def external_version_name(self): if self.is_external: if self.project.git_provider_name == GITHUB_BRAND: return GITHUB_EXTERNAL_VERSION_NAME - # TODO: Add External Version Name for other Git Providers + + if self.project.git_provider_name == GITLAB_BRAND: + return GITLAB_EXTERNAL_VERSION_NAME return GENERIC_EXTERNAL_VERSION_NAME return None diff --git a/readthedocs/projects/constants.py b/readthedocs/projects/constants.py index 85458d348c8..2ae8733f6db 100644 --- a/readthedocs/projects/constants.py +++ b/readthedocs/projects/constants.py @@ -354,7 +354,10 @@ 'commit/{commit}' ) +# Patterns to pull merge/pull request from providers GITHUB_PR_PULL_PATTERN = 'pull/{id}/head:external-{id}' +GITLAB_MR_PULL_PATTERN = 'merge-requests/{id}/head:external-{id}' # Git provider names GITHUB_BRAND = 'GitHub' +GITLAB_BRAND = 'GitLab' From 66372f5c2310c1df26c2a6300466f5d841375b4e Mon Sep 17 00:00:00 2001 From: saadmk11 Date: Sun, 11 Aug 2019 21:52:29 +0600 Subject: [PATCH 3/9] checkout using gitlab pull pattern --- readthedocs/vcs_support/backends/git.py | 27 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/readthedocs/vcs_support/backends/git.py b/readthedocs/vcs_support/backends/git.py index 454f905daea..094e68e5b77 100644 --- a/readthedocs/vcs_support/backends/git.py +++ b/readthedocs/vcs_support/backends/git.py @@ -12,7 +12,13 @@ from readthedocs.builds.constants import EXTERNAL from readthedocs.config import ALL -from readthedocs.projects.constants import GITHUB_PR_PULL_PATTERN +from readthedocs.projects.constants import ( + GITHUB_BRAND, + GITHUB_PR_PULL_PATTERN, + GITLAB_BRAND, + GITLAB_MR_PULL_PATTERN, + +) from readthedocs.projects.exceptions import RepositoryError from readthedocs.projects.validators import validate_submodule_url from readthedocs.vcs_support.base import BaseVCS, VCSVersion @@ -149,14 +155,17 @@ def fetch(self): if self.use_shallow_clone(): cmd.extend(['--depth', str(self.repo_depth)]) - if ( - self.verbose_name and - self.version_type == EXTERNAL and - 'github.com' in self.repo_url - ): - cmd.append( - GITHUB_PR_PULL_PATTERN.format(id=self.verbose_name) - ) + if self.verbose_name and self.version_type == EXTERNAL: + + if self.project.git_provider_name == GITHUB_BRAND: + cmd.append( + GITHUB_PR_PULL_PATTERN.format(id=self.verbose_name) + ) + + if self.project.git_provider_name == GITLAB_BRAND: + cmd.append( + GITLAB_MR_PULL_PATTERN.format(id=self.verbose_name) + ) code, stdout, stderr = self.run(*cmd) if code != 0: From 9a9c718ee2b4fa0592f9a7b01ef33e9c850a59ce Mon Sep 17 00:00:00 2001 From: saadmk11 Date: Sun, 11 Aug 2019 22:48:35 +0600 Subject: [PATCH 4/9] commit url and external version name updated --- readthedocs/api/v2/views/integrations.py | 4 ++-- readthedocs/builds/models.py | 27 ++++++++++++++++++++++-- readthedocs/projects/constants.py | 8 +++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/readthedocs/api/v2/views/integrations.py b/readthedocs/api/v2/views/integrations.py index 3825c5752fc..bd49ad67ebc 100644 --- a/readthedocs/api/v2/views/integrations.py +++ b/readthedocs/api/v2/views/integrations.py @@ -407,7 +407,7 @@ class GitLabWebhookView(WebhookMixin, APIView): """ Webhook consumer for GitLab. - Accepts webhook events from GitLab, 'push' events trigger builds. + Accepts webhook events from GitLab, 'push' and 'merge_request' events trigger builds. Expects the following JSON:: @@ -438,7 +438,7 @@ class GitLabWebhookView(WebhookMixin, APIView): - https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#push-events - https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#tag-events - - https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#merge-request-events + - https://docs.gitlab.com/ce/user/project/integrations/webhooks.html#merge-request-events """ integration_type = Integration.GITLAB_WEBHOOK diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index 88d6b0a7ed0..67b199e5111 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -29,6 +29,8 @@ GITHUB_PULL_REQUEST_COMMIT_URL, GITLAB_BRAND, GITLAB_COMMIT_URL, + GITLAB_MERGE_REQUEST_URL, + GITLAB_MERGE_REQUEST_COMMIT_URL, GITLAB_URL, PRIVACY_CHOICES, PRIVATE, @@ -180,7 +182,14 @@ def vcs_url(self): repo=repo, number=self.verbose_name, ) - # TODO: Add VCS URL for other Git Providers + if 'gitlab' in self.project.repo: + user, repo = get_gitlab_username_repo(self.project.repo) + return GITLAB_MERGE_REQUEST_URL.format( + user=user, + repo=repo, + number=self.verbose_name, + ) + # TODO: Add VCS URL for BitBucket. return '' url = '' @@ -789,7 +798,19 @@ def get_commit_url(self): number=self.version.verbose_name, commit=self.commit ) - # TODO: Add External Version Commit URL for other Git Providers + if 'gitlab' in repo_url: + user, repo = get_gitlab_username_repo(repo_url) + if not user and not repo: + return '' + + repo = repo.rstrip('/') + return GITLAB_MERGE_REQUEST_COMMIT_URL.format( + user=user, + repo=repo, + number=self.version.verbose_name, + commit=self.commit + ) + # TODO: Add External Version Commit URL for BitBucket. else: if 'github' in repo_url: user, repo = get_github_username_repo(repo_url) @@ -851,6 +872,8 @@ def external_version_name(self): if self.project.git_provider_name == GITLAB_BRAND: return GITLAB_EXTERNAL_VERSION_NAME + # TODO: Add External Version Name for BitBucket. + return GENERIC_EXTERNAL_VERSION_NAME return None diff --git a/readthedocs/projects/constants.py b/readthedocs/projects/constants.py index 2ae8733f6db..fc190050a3e 100644 --- a/readthedocs/projects/constants.py +++ b/readthedocs/projects/constants.py @@ -353,6 +353,14 @@ 'https://gitlab.com/{user}/{repo}/' 'commit/{commit}' ) +GITLAB_MERGE_REQUEST_COMMIT_URL = ( + 'https://gitlab.com/{user}/{repo}/' + 'commit/{commit}?merge_request_iid={number}' +) +GITLAB_MERGE_REQUEST_URL = ( + 'https://gitlab.com/{user}/{repo}/' + 'merge_requests/{number}' +) # Patterns to pull merge/pull request from providers GITHUB_PR_PULL_PATTERN = 'pull/{id}/head:external-{id}' From 50a971d689653627fe7fa742515781032eba94bc Mon Sep 17 00:00:00 2001 From: saadmk11 Date: Sun, 11 Aug 2019 22:53:49 +0600 Subject: [PATCH 5/9] remove unnecessary blank lines --- readthedocs/builds/models.py | 1 - readthedocs/vcs_support/backends/git.py | 1 - 2 files changed, 2 deletions(-) diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index 67b199e5111..cf20de76657 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -873,7 +873,6 @@ def external_version_name(self): return GITLAB_EXTERNAL_VERSION_NAME # TODO: Add External Version Name for BitBucket. - return GENERIC_EXTERNAL_VERSION_NAME return None diff --git a/readthedocs/vcs_support/backends/git.py b/readthedocs/vcs_support/backends/git.py index 094e68e5b77..dda965bdb86 100644 --- a/readthedocs/vcs_support/backends/git.py +++ b/readthedocs/vcs_support/backends/git.py @@ -17,7 +17,6 @@ GITHUB_PR_PULL_PATTERN, GITLAB_BRAND, GITLAB_MR_PULL_PATTERN, - ) from readthedocs.projects.exceptions import RepositoryError from readthedocs.projects.validators import validate_submodule_url From f947a9287f9591e8091a76533759bf7db5fdb03a Mon Sep 17 00:00:00 2001 From: saadmk11 Date: Mon, 12 Aug 2019 18:34:25 +0600 Subject: [PATCH 6/9] tests added --- readthedocs/rtd_tests/tests/test_builds.py | 35 +++++++++++++++++++++ readthedocs/rtd_tests/tests/test_project.py | 17 ++++++++++ readthedocs/rtd_tests/tests/test_version.py | 9 +++++- 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/readthedocs/rtd_tests/tests/test_builds.py b/readthedocs/rtd_tests/tests/test_builds.py index 4da28561429..6f78c62c69a 100644 --- a/readthedocs/rtd_tests/tests/test_builds.py +++ b/readthedocs/rtd_tests/tests/test_builds.py @@ -14,6 +14,7 @@ BRANCH, EXTERNAL, GITHUB_EXTERNAL_VERSION_NAME, + GITLAB_EXTERNAL_VERSION_NAME, GENERIC_EXTERNAL_VERSION_NAME ) from readthedocs.builds.models import Build, Version @@ -669,6 +670,20 @@ def test_external_version_name_github(self): GITHUB_EXTERNAL_VERSION_NAME ) + def test_external_version_name_gitlab(self): + self.project.repo = 'https://gitlab.com/test/test/' + self.project.save() + + external_version = get(Version, project=self.project, type=EXTERNAL) + external_build = get( + Build, project=self.project, version=external_version + ) + + self.assertEqual( + external_build.external_version_name, + GITLAB_EXTERNAL_VERSION_NAME + ) + def test_external_version_name_generic(self): # Turn the build version to EXTERNAL type. self.version.type = EXTERNAL @@ -700,6 +715,26 @@ def test_get_commit_url_external_version_github(self): ) self.assertEqual(external_build.get_commit_url(), expected_url) + def test_get_commit_url_external_version_gitlab(self): + + self.pip.repo = 'https://gitlab.com/pypa/pip' + self.pip.save() + + external_build = get( + Build, + project=self.pip, + version=self.external_version, + config={'version': 1}, + ) + expected_url = ( + 'https://gitlab.com/pypa/pip/commit/' + '{commit}?merge_request_iid={number}' + ).format( + number=self.external_version.verbose_name, + commit=external_build.commit + ) + self.assertEqual(external_build.get_commit_url(), expected_url) + def test_get_commit_url_internal_version(self): build = get( Build, diff --git a/readthedocs/rtd_tests/tests/test_project.py b/readthedocs/rtd_tests/tests/test_project.py index f345d710519..e278ff0bca2 100644 --- a/readthedocs/rtd_tests/tests/test_project.py +++ b/readthedocs/rtd_tests/tests/test_project.py @@ -17,6 +17,8 @@ EXTERNAL, ) from readthedocs.builds.models import Build, Version +from readthedocs.oauth.services import GitHubService, GitLabService +from readthedocs.projects.constants import GITHUB_BRAND, GITLAB_BRAND from readthedocs.projects.exceptions import ProjectConfigurationError from readthedocs.projects.models import Project from readthedocs.projects.tasks import finish_inactive_builds @@ -193,6 +195,21 @@ def test_get_latest_build_excludes_external_versions(self): # Test that External Version is not considered for get_latest_build. self.assertEqual(self.pip.get_latest_build(), None) + def test_git_provider_name_github(self): + self.assertEqual(self.pip.git_provider_name, GITHUB_BRAND) + + def test_git_service_class_github(self): + self.assertEqual(self.pip.git_service_class(), GitHubService) + + def test_git_provider_name_gitlab(self): + self.pip.repo = 'https://gitlab.com/pypa/pip' + self.pip.save() + self.assertEqual(self.pip.git_provider_name, GITLAB_BRAND) + + def test_git_service_class_gitlab(self): + self.pip.repo = 'https://gitlab.com/pypa/pip' + self.pip.save() + self.assertEqual(self.pip.git_service_class(), GitLabService) class TestProjectTranslations(ProjectMixin, TestCase): diff --git a/readthedocs/rtd_tests/tests/test_version.py b/readthedocs/rtd_tests/tests/test_version.py index 0ca9cc124da..4e3768b885b 100644 --- a/readthedocs/rtd_tests/tests/test_version.py +++ b/readthedocs/rtd_tests/tests/test_version.py @@ -45,10 +45,17 @@ def setUp(self): class TestVersionModel(VersionMixin, TestCase): - def test_vcs_url_for_external_version(self): + def test_vcs_url_for_external_version_github(self): expected_url = f'https://github.com/pypa/pip/pull/{self.external_version.verbose_name}' self.assertEqual(self.external_version.vcs_url, expected_url) + def test_vcs_url_for_external_version_gitlab(self): + self.pip.repo = 'https://gitlab.com/pypa/pip' + self.pip.save() + + expected_url = f'https://gitlab.com/pypa/pip/merge_requests/{self.external_version.verbose_name}' + self.assertEqual(self.external_version.vcs_url, expected_url) + def test_vcs_url_for_latest_version(self): slug = self.pip.default_branch or self.pip.vcs_repo().fallback_branch expected_url = f'https://github.com/pypa/pip/tree/{slug}/' From 73c55f3595ee573d8acb85eae9a086c912170daa Mon Sep 17 00:00:00 2001 From: saadmk11 Date: Mon, 12 Aug 2019 20:38:55 +0600 Subject: [PATCH 7/9] API tests added --- readthedocs/rtd_tests/tests/test_api.py | 301 ++++++++++++++++++++++++ 1 file changed, 301 insertions(+) diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py index ac525abb7a8..cfd1b66c815 100644 --- a/readthedocs/rtd_tests/tests/test_api.py +++ b/readthedocs/rtd_tests/tests/test_api.py @@ -23,6 +23,12 @@ GITHUB_PULL_REQUEST_REOPENED, GITHUB_PULL_REQUEST_CLOSED, GITHUB_PULL_REQUEST_SYNC, + GITLAB_MERGE_REQUEST, + GITLAB_MERGE_REQUEST_CLOSE, + GITLAB_MERGE_REQUEST_MERGE, + GITLAB_MERGE_REQUEST_OPEN, + GITLAB_MERGE_REQUEST_REOPEN, + GITLAB_MERGE_REQUEST_UPDATE, GITLAB_NULL_HASH, GITLAB_PUSH, GITLAB_TAG_PUSH, @@ -794,6 +800,16 @@ def setUp(self): } } } + self.gitlab_merge_request_payload = { + "object_kind": GITLAB_MERGE_REQUEST, + "object_attributes": { + "iid": '2', + "last_commit": { + "id": self.commit + }, + "action": "open" + }, + } self.gitlab_payload = { 'object_kind': GITLAB_PUSH, 'ref': 'master', @@ -1536,6 +1552,291 @@ def test_gitlab_skip_token_validation(self, trigger_build): ) self.assertEqual(resp.status_code, 200) + @mock.patch('readthedocs.core.utils.trigger_build') + def test_gitlab_merge_request_open_event(self, trigger_build, core_trigger_build): + client = APIClient() + + resp = client.post( + reverse( + 'api_webhook_gitlab', + kwargs={'project_slug': self.project.slug} + ), + self.gitlab_merge_request_payload, + format='json', + ) + # get the created external version + external_version = self.project.versions( + manager=EXTERNAL + ).get(verbose_name='2') + + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertTrue(resp.data['build_triggered']) + self.assertEqual(resp.data['project'], self.project.slug) + self.assertEqual(resp.data['versions'], [external_version.verbose_name]) + core_trigger_build.assert_called_once_with( + force=True, project=self.project, + version=external_version, commit=self.commit + ) + self.assertTrue(external_version) + + @mock.patch('readthedocs.core.utils.trigger_build') + def test_gitlab_merge_request_reopen_event(self, trigger_build, core_trigger_build): + client = APIClient() + + # Update the payload for `reopen` webhook event + merge_request_number = '5' + payload = self.gitlab_merge_request_payload + payload["object_attributes"]["action"] = GITLAB_MERGE_REQUEST_REOPEN + payload["object_attributes"]["iid"] = merge_request_number + + resp = client.post( + reverse( + 'api_webhook_gitlab', + kwargs={'project_slug': self.project.slug} + ), + payload, + format='json', + ) + # get the created external version + external_version = self.project.versions( + manager=EXTERNAL + ).get(verbose_name=merge_request_number) + + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertTrue(resp.data['build_triggered']) + self.assertEqual(resp.data['project'], self.project.slug) + self.assertEqual(resp.data['versions'], [external_version.verbose_name]) + core_trigger_build.assert_called_once_with( + force=True, project=self.project, + version=external_version, commit=self.commit + ) + self.assertTrue(external_version) + + @mock.patch('readthedocs.core.utils.trigger_build') + def test_gitlab_merge_request_update_event(self, trigger_build, core_trigger_build): + client = APIClient() + + merge_request_number = '6' + prev_identifier = '95790bf891e76fee5e1747ab589903a6a1f80f23' + # create an existing external version for merge request + version = get( + Version, + project=self.project, + type=EXTERNAL, + built=True, + uploaded=True, + active=True, + verbose_name=merge_request_number, + identifier=prev_identifier + ) + + # Update the payload for merge request `update` webhook event + payload = self.gitlab_merge_request_payload + payload["object_attributes"]["action"] = GITLAB_MERGE_REQUEST_UPDATE + payload["object_attributes"]["iid"] = merge_request_number + + resp = client.post( + reverse( + 'api_webhook_gitlab', + kwargs={'project_slug': self.project.slug} + ), + payload, + format='json', + ) + # get updated external version + external_version = self.project.versions( + manager=EXTERNAL + ).get(verbose_name=merge_request_number) + + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertTrue(resp.data['build_triggered']) + self.assertEqual(resp.data['project'], self.project.slug) + self.assertEqual(resp.data['versions'], [external_version.verbose_name]) + core_trigger_build.assert_called_once_with( + force=True, project=self.project, + version=external_version, commit=self.commit + ) + # `update` webhook event updated the identifier (commit hash) + self.assertNotEqual(prev_identifier, external_version.identifier) + + @mock.patch('readthedocs.core.utils.trigger_build') + def test_gitlab_merge_request_close_event(self, trigger_build, core_trigger_build): + client = APIClient() + + merge_request_number = '7' + identifier = '95790bf891e76fee5e1747ab589903a6a1f80f23' + # create an existing external version for merge request + version = get( + Version, + project=self.project, + type=EXTERNAL, + built=True, + uploaded=True, + active=True, + verbose_name=merge_request_number, + identifier=identifier + ) + + # Update the payload for `closed` webhook event + payload = self.gitlab_merge_request_payload + payload["object_attributes"]["action"] = GITLAB_MERGE_REQUEST_CLOSE + payload["object_attributes"]["iid"] = merge_request_number + payload["object_attributes"]["last_commit"]["id"] = identifier + + resp = client.post( + reverse( + 'api_webhook_gitlab', + kwargs={'project_slug': self.project.slug} + ), + payload, + format='json', + ) + external_version = self.project.versions( + manager=EXTERNAL + ).filter(verbose_name=merge_request_number) + + # external version should be deleted + self.assertFalse(external_version.exists()) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertTrue(resp.data['version_deleted']) + self.assertEqual(resp.data['project'], self.project.slug) + self.assertEqual(resp.data['versions'], [version.verbose_name]) + core_trigger_build.assert_not_called() + + @mock.patch('readthedocs.core.utils.trigger_build') + def test_gitlab_merge_request_merge_event(self, trigger_build, core_trigger_build): + client = APIClient() + + merge_request_number = '8' + identifier = '95790bf891e76fee5e1747ab589903a6a1f80f23' + # create an existing external version for merge request + version = get( + Version, + project=self.project, + type=EXTERNAL, + built=True, + uploaded=True, + active=True, + verbose_name=merge_request_number, + identifier=identifier + ) + + # Update the payload for `merge` webhook event + payload = self.gitlab_merge_request_payload + payload["object_attributes"]["action"] = GITLAB_MERGE_REQUEST_MERGE + payload["object_attributes"]["iid"] = merge_request_number + payload["object_attributes"]["last_commit"]["id"] = identifier + + resp = client.post( + reverse( + 'api_webhook_gitlab', + kwargs={'project_slug': self.project.slug} + ), + payload, + format='json', + ) + external_version = self.project.versions( + manager=EXTERNAL + ).filter(verbose_name=merge_request_number) + + # external version should be deleted + self.assertFalse(external_version.exists()) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertTrue(resp.data['version_deleted']) + self.assertEqual(resp.data['project'], self.project.slug) + self.assertEqual(resp.data['versions'], [version.verbose_name]) + core_trigger_build.assert_not_called() + + def test_gitlab_merge_request_no_action(self, trigger_build): + client = APIClient() + + payload = { + "object_kind": GITLAB_MERGE_REQUEST, + "object_attributes": { + "iid": 2, + "last_commit": { + "id": self.commit + }, + }, + } + + resp = client.post( + reverse( + 'api_webhook_gitlab', + kwargs={'project_slug': self.project.slug} + ), + payload, + format='json', + ) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data['detail'], 'Unhandled webhook event') + + def test_gitlab_merge_request_open_event_invalid_payload(self, trigger_build): + client = APIClient() + + payload = { + "object_kind": GITLAB_MERGE_REQUEST, + "object_attributes": { + "action": GITLAB_MERGE_REQUEST_CLOSE + }, + } + resp = client.post( + reverse( + 'api_webhook_gitlab', + kwargs={'project_slug': self.project.slug} + ), + payload, + format='json', + ) + + self.assertEqual(resp.status_code, 400) + + def test_gitlab_merge_request_close_event_invalid_payload(self, trigger_build): + client = APIClient() + + payload = { + "object_kind": GITLAB_MERGE_REQUEST, + "object_attributes": { + "action": GITLAB_MERGE_REQUEST_CLOSE + }, + } + + resp = client.post( + reverse( + 'api_webhook_gitlab', + kwargs={'project_slug': self.project.slug} + ), + payload, + format='json', + ) + + self.assertEqual(resp.status_code, 400) + + @mock.patch('readthedocs.core.utils.trigger_build') + def test_gitlab_merge_request_event_no_feature_flag(self, trigger_build, core_trigger_build): + # delete feature flag + self.feature_flag.delete() + + client = APIClient() + + resp = client.post( + reverse( + 'api_webhook_gitlab', + kwargs={'project_slug': self.project.slug} + ), + self.gitlab_merge_request_payload, + format='json', + ) + # get external version + external_version = self.project.versions( + manager=EXTERNAL + ).filter(verbose_name='2').first() + + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.data['detail'], 'Unhandled webhook event') + core_trigger_build.assert_not_called() + self.assertFalse(external_version) + def test_bitbucket_webhook(self, trigger_build): """Bitbucket webhook API.""" client = APIClient() From 52c6ef967057d134677fb9aadcbc1098c6652ffe Mon Sep 17 00:00:00 2001 From: Maksudul Haque Date: Tue, 13 Aug 2019 09:18:14 +0600 Subject: [PATCH 8/9] Update readthedocs/api/v2/views/integrations.py Co-Authored-By: Eric Holscher <25510+ericholscher@users.noreply.github.com> --- readthedocs/api/v2/views/integrations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/api/v2/views/integrations.py b/readthedocs/api/v2/views/integrations.py index bd49ad67ebc..3ca08fa859d 100644 --- a/readthedocs/api/v2/views/integrations.py +++ b/readthedocs/api/v2/views/integrations.py @@ -464,7 +464,7 @@ def is_payload_valid(self): return token == secret def get_external_version_data(self): - """Get Commit Sha and merge request number from payload.""" + """Get commit SHA and merge request number from payload.""" try: identifier = self.data['object_attributes']['last_commit']['id'] verbose_name = str(self.data['object_attributes']['iid']) From 419cdb317e364a66c45137e93763246e415ff832 Mon Sep 17 00:00:00 2001 From: saadmk11 Date: Tue, 13 Aug 2019 16:36:35 +0600 Subject: [PATCH 9/9] Tests updated --- readthedocs/rtd_tests/tests/test_builds.py | 3 ++- readthedocs/rtd_tests/tests/test_project.py | 4 ++++ readthedocs/rtd_tests/tests/test_version.py | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/readthedocs/rtd_tests/tests/test_builds.py b/readthedocs/rtd_tests/tests/test_builds.py index 6f78c62c69a..0f458f90240 100644 --- a/readthedocs/rtd_tests/tests/test_builds.py +++ b/readthedocs/rtd_tests/tests/test_builds.py @@ -702,6 +702,8 @@ def test_external_version_name_generic(self): ) def test_get_commit_url_external_version_github(self): + self.pip.repo = 'https://github.com/pypa/pip' + self.pip.save() external_build = get( Build, @@ -716,7 +718,6 @@ def test_get_commit_url_external_version_github(self): self.assertEqual(external_build.get_commit_url(), expected_url) def test_get_commit_url_external_version_gitlab(self): - self.pip.repo = 'https://gitlab.com/pypa/pip' self.pip.save() diff --git a/readthedocs/rtd_tests/tests/test_project.py b/readthedocs/rtd_tests/tests/test_project.py index e278ff0bca2..f4aa5b0a3b1 100644 --- a/readthedocs/rtd_tests/tests/test_project.py +++ b/readthedocs/rtd_tests/tests/test_project.py @@ -196,9 +196,13 @@ def test_get_latest_build_excludes_external_versions(self): self.assertEqual(self.pip.get_latest_build(), None) def test_git_provider_name_github(self): + self.pip.repo = 'https://github.com/pypa/pip' + self.pip.save() self.assertEqual(self.pip.git_provider_name, GITHUB_BRAND) def test_git_service_class_github(self): + self.pip.repo = 'https://github.com/pypa/pip' + self.pip.save() self.assertEqual(self.pip.git_service_class(), GitHubService) def test_git_provider_name_gitlab(self): diff --git a/readthedocs/rtd_tests/tests/test_version.py b/readthedocs/rtd_tests/tests/test_version.py index 4e3768b885b..d9782c11da6 100644 --- a/readthedocs/rtd_tests/tests/test_version.py +++ b/readthedocs/rtd_tests/tests/test_version.py @@ -46,6 +46,9 @@ def setUp(self): class TestVersionModel(VersionMixin, TestCase): def test_vcs_url_for_external_version_github(self): + self.pip.repo = 'https://github.com/pypa/pip' + self.pip.save() + expected_url = f'https://github.com/pypa/pip/pull/{self.external_version.verbose_name}' self.assertEqual(self.external_version.vcs_url, expected_url)