diff --git a/readthedocs/core/urls/__init__.py b/readthedocs/core/urls/__init__.py index 60e7fd32325..d0a9550e425 100644 --- a/readthedocs/core/urls/__init__.py +++ b/readthedocs/core/urls/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """URL configuration for core app.""" from __future__ import absolute_import @@ -7,7 +5,7 @@ from readthedocs.constants import pattern_opts from readthedocs.core import views -from readthedocs.core.views import hooks, serve +from readthedocs.core.views import serve from readthedocs.projects.feeds import LatestProjectsFeed, NewProjectsFeed docs_urls = [ @@ -43,18 +41,6 @@ ] core_urls = [ - # Hooks - url(r'^github', hooks.github_build, name='github_build'), - url(r'^gitlab', hooks.gitlab_build, name='gitlab_build'), - url(r'^bitbucket', hooks.bitbucket_build, name='bitbucket_build'), - url( - ( - r'^build/' - r'(?P{project_slug})'.format(**pattern_opts) - ), - hooks.generic_build, - name='generic_build', - ), # Random other stuff url( r'^random/(?P{project_slug})'.format(**pattern_opts), diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py index a6d106fca18..6b438866ae5 100644 --- a/readthedocs/core/views/__init__.py +++ b/readthedocs/core/views/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """ Core views, including the main homepage, @@ -87,7 +85,7 @@ def wipe_version(request, project_slug, version_slug): if request.method == 'POST': wipe_version_via_slugs( version_slug=version_slug, - project_slug=project_slug + project_slug=project_slug, ) return redirect('project_version_list', project_slug) return render( diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py index 1a77c99fa69..a3273a88871 100644 --- a/readthedocs/core/views/hooks.py +++ b/readthedocs/core/views/hooks.py @@ -1,31 +1,14 @@ """Views pertaining to builds.""" -import json import logging -import re -from django.http import HttpResponse, HttpResponseNotFound -from django.shortcuts import redirect -from django.views.decorators.csrf import csrf_exempt - -from readthedocs.builds.constants import LATEST from readthedocs.core.utils import trigger_build -from readthedocs.projects import constants -from readthedocs.projects.models import Feature, Project from readthedocs.projects.tasks import sync_repository_task log = logging.getLogger(__name__) -class NoProjectException(Exception): - pass - - -def _allow_deprecated_webhook(project): - return project.has_feature(Feature.ALLOW_DEPRECATED_WEBHOOKS) - - def _build_version(project, slug, already_built=()): """ Where we actually trigger builds for a project and slug. @@ -105,284 +88,3 @@ def sync_versions(project): except Exception: log.exception('Unknown sync versions exception') return None - - -def get_project_from_url(url): - if not url: - return Project.objects.none() - projects = ( - Project.objects.filter(repo__iendswith=url) | - Project.objects.filter(repo__iendswith=url + '.git') - ) - return projects - - -def log_info(project, msg): - log.info( - constants.LOG_TEMPLATE, - { - 'project': project, - 'version': '', - 'msg': msg, - } - ) - - -def _build_url(url, projects, branches): - """ - Map a URL onto specific projects to build that are linked to that URL. - - Check each of the ``branches`` to see if they are active and should be - built. - """ - ret = '' - all_built = {} - all_not_building = {} - - # This endpoint doesn't require authorization, we shouldn't allow builds to - # be triggered from this any longer. Deprecation plan is to selectively - # allow access to this endpoint for now. - if not any(_allow_deprecated_webhook(project) for project in projects): - return HttpResponse('This API endpoint is deprecated', status=403) - - for project in projects: - (built, not_building) = build_branches(project, branches) - if not built: - # Call sync_repository_task to update tag/branch info - version = project.versions.get(slug=LATEST) - sync_repository_task.delay(version.pk) - msg = '(URL Build) Syncing versions for %s' % project.slug - log.info(msg) - all_built[project.slug] = built - all_not_building[project.slug] = not_building - - for project_slug, built in list(all_built.items()): - if built: - msg = '(URL Build) Build Started: {} [{}]'.format( - url, - ' '.join(built), - ) - log_info(project_slug, msg=msg) - ret += msg - - for project_slug, not_building in list(all_not_building.items()): - if not_building: - msg = '(URL Build) Not Building: {} [{}]'.format( - url, - ' '.join(not_building), - ) - log_info(project_slug, msg=msg) - ret += msg - - if not ret: - ret = '(URL Build) No known branches were pushed to.' - - return HttpResponse(ret) - - -@csrf_exempt -def github_build(request): # noqa: D205 - """ - GitHub webhook consumer. - - .. warning:: **DEPRECATED** - Use :py:class:`readthedocs.api.v2.views.integrations.GitHubWebhookView` - instead of this view function - - This will search for projects matching either a stripped down HTTP or SSH - URL. The search is error prone, use the API v2 webhook for new webhooks. - - Old webhooks may not have specified the content type to POST with, and - therefore can use ``application/x-www-form-urlencoded`` to pass the JSON - payload. More information on the API docs here: - https://developer.github.com/webhooks/creating/#content-type - """ - if request.method == 'POST': - try: - if request.META['CONTENT_TYPE'] == 'application/x-www-form-urlencoded': - data = json.loads(request.POST.get('payload')) - else: - data = json.loads(request.body) - http_url = data['repository']['url'] - http_search_url = http_url.replace('http://', '').replace('https://', '') - ssh_url = data['repository']['ssh_url'] - ssh_search_url = ssh_url.replace('git@', '').replace('.git', '') - branches = [data['ref'].replace('refs/heads/', '')] - except (ValueError, TypeError, KeyError): - log.exception('Invalid GitHub webhook payload') - return HttpResponse('Invalid request', status=400) - try: - repo_projects = get_project_from_url(http_search_url) - if repo_projects: - log.info( - 'GitHub webhook search: url=%s branches=%s', - http_search_url, - branches, - ) - ssh_projects = get_project_from_url(ssh_search_url) - if ssh_projects: - log.info( - 'GitHub webhook search: url=%s branches=%s', - ssh_search_url, - branches, - ) - projects = repo_projects | ssh_projects - return _build_url(http_search_url, projects, branches) - except NoProjectException: - log.exception('Project match not found: url=%s', http_search_url) - return HttpResponseNotFound('Project not found') - else: - return HttpResponse('Method not allowed, POST is required', status=405) - - -@csrf_exempt -def gitlab_build(request): # noqa: D205 - """ - GitLab webhook consumer. - - .. warning:: **DEPRECATED** - Use :py:class:`readthedocs.api.v2.views.integrations.GitLabWebhookView` - instead of this view function - - Search project repository URLs using the site URL from GitLab webhook payload. - This search is error-prone, use the API v2 webhook view for new webhooks. - """ - if request.method == 'POST': - try: - data = json.loads(request.body) - url = data['project']['http_url'] - search_url = re.sub(r'^https?://(.*?)(?:\.git|)$', '\\1', url) - branches = [data['ref'].replace('refs/heads/', '')] - except (ValueError, TypeError, KeyError): - log.exception('Invalid GitLab webhook payload') - return HttpResponse('Invalid request', status=400) - log.info( - 'GitLab webhook search: url=%s branches=%s', - search_url, - branches, - ) - projects = get_project_from_url(search_url) - if projects: - return _build_url(search_url, projects, branches) - - log.info('Project match not found: url=%s', search_url) - return HttpResponseNotFound('Project match not found') - return HttpResponse('Method not allowed, POST is required', status=405) - - -@csrf_exempt -def bitbucket_build(request): - """ - Consume webhooks from multiple versions of Bitbucket's API. - - .. warning:: **DEPRECATED** - Use :py:class:`readthedocs.api.v2.views.integrations.BitbucketWebhookView` - instead of this view function - - New webhooks are set up with v2, but v1 webhooks will still point to this - endpoint. There are also "services" that point here and submit - ``application/x-www-form-urlencoded`` data. - - API v1 - https://confluence.atlassian.com/bitbucket/events-resources-296095220.html - - API v2 - https://confluence.atlassian.com/bitbucket/event-payloads-740262817.html#EventPayloads-Push - - Services - https://confluence.atlassian.com/bitbucket/post-service-management-223216518.html - """ - if request.method == 'POST': - try: - if request.META['CONTENT_TYPE'] == 'application/x-www-form-urlencoded': - data = json.loads(request.POST.get('payload')) - else: - data = json.loads(request.body) - - version = 2 if request.META.get('HTTP_USER_AGENT') == 'Bitbucket-Webhooks/2.0' else 1 # yapf: disabled # noqa - if version == 1: - branches = [ - commit.get('branch', '') for commit in data['commits'] - ] - repository = data['repository'] - if not repository['absolute_url']: - return HttpResponse('Invalid request', status=400) - search_url = 'bitbucket.org{}'.format( - repository['absolute_url'].rstrip('/'), - ) - elif version == 2: - changes = data['push']['changes'] - branches = [change['new']['name'] for change in changes] - if not data['repository']['full_name']: - return HttpResponse('Invalid request', status=400) - search_url = 'bitbucket.org/{}'.format( - data['repository']['full_name'], - ) - except (TypeError, ValueError, KeyError): - log.exception('Invalid Bitbucket webhook payload') - return HttpResponse('Invalid request', status=400) - - log.info( - 'Bitbucket webhook search: url=%s branches=%s', - search_url, - branches, - ) - log.debug('Bitbucket webhook payload:\n\n%s\n\n', data) - - projects = get_project_from_url(search_url) - if projects and branches: - return _build_url(search_url, projects, branches) - - if not branches: - log.info( - 'Commit/branch not found url=%s branches=%s', - search_url, - branches, - ) - return HttpResponseNotFound('Commit/branch not found') - - log.info('Project match not found: url=%s', search_url) - return HttpResponseNotFound('Project match not found') - return HttpResponse('Method not allowed, POST is required', status=405) - - -@csrf_exempt -def generic_build(request, project_id_or_slug=None): - """ - Generic webhook build endpoint. - - .. warning:: **DEPRECATED** - - Use :py:class:`readthedocs.api.v2.views.integrations.GenericWebhookView` - instead of this view function - """ - try: - project = Project.objects.get(pk=project_id_or_slug) - # Allow slugs too - except (Project.DoesNotExist, ValueError): - try: - project = Project.objects.get(slug=project_id_or_slug) - except (Project.DoesNotExist, ValueError): - log.exception( - '(Incoming Generic Build) Repo not found: %s', - project_id_or_slug, - ) - return HttpResponseNotFound( - 'Repo not found: %s' % project_id_or_slug, - ) - # This endpoint doesn't require authorization, we shouldn't allow builds to - # be triggered from this any longer. Deprecation plan is to selectively - # allow access to this endpoint for now. - if not _allow_deprecated_webhook(project): - return HttpResponse('This API endpoint is deprecated', status=403) - if request.method == 'POST': - slug = request.POST.get('version_slug', project.default_version) - log.info( - '(Incoming Generic Build) %s [%s]', - project.slug, - slug, - ) - _build_version(project, slug) - else: - return HttpResponse('You must POST to this resource.') - return redirect('builds_project_list', project.slug) diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py deleted file mode 100644 index 4dd40865e02..00000000000 --- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py +++ /dev/null @@ -1,655 +0,0 @@ -# -*- coding: utf-8 -*- -import json -import logging -from urllib.parse import urlencode - -import mock -from django.test import TestCase -from django_dynamic_fixture import get - -from readthedocs.builds.models import Version -from readthedocs.projects.models import Feature, Project - - -log = logging.getLogger(__name__) - - -class BasePostCommitTest(TestCase): - def _setup(self): - self.rtfd = get( - Project, repo='https://github.com/rtfd/readthedocs.org', slug='read-the-docs', - ) - self.rtfd_not_ok = get( - Version, project=self.rtfd, slug='not_ok', identifier='not_ok', active=False, - ) - self.rtfd_awesome = get( - Version, project=self.rtfd, slug='awesome', identifier='awesome', active=True, - ) - - self.pip = get(Project, repo='https://bitbucket.org/pip/pip', repo_type='hg') - self.pip_not_ok = get( - Version, project=self.pip, slug='not_ok', identifier='not_ok', active=False, - ) - self.sphinx = get(Project, repo='https://bitbucket.org/sphinx/sphinx', repo_type='git') - - self.mocks = [mock.patch('readthedocs.core.views.hooks.trigger_build')] - self.patches = [m.start() for m in self.mocks] - - # Remove all possible Features added by a migration - Feature.objects.all().delete() - - self.feature = get( - Feature, - feature_id=Feature.ALLOW_DEPRECATED_WEBHOOKS, - default_true=True, - ) - self.feature.projects.add(self.pip) - self.feature.projects.add(self.rtfd) - self.feature.projects.add(self.sphinx) - - self.client.login(username='eric', password='test') - - -class GitLabWebHookTest(BasePostCommitTest): - fixtures = ['eric'] - - def setUp(self): - self._setup() - - self.payload = { - 'object_kind': 'push', - 'before': '95790bf891e76fee5e1747ab589903a6a1f80f22', - 'after': 'da1560886d4f094c3e6c9ef40349f7d38b5d27d7', - 'ref': 'refs/heads/awesome', - 'checkout_sha': 'da1560886d4f094c3e6c9ef40349f7d38b5d27d7', - 'user_id': 4, - 'user_name': 'John Smith', - 'user_email': 'john@example.com', - 'project_id': 15, - 'project':{ - 'name':'readthedocs', - 'description':'', - 'web_url':'http://example.com/mike/diaspora', - 'avatar_url': None, - 'git_ssh_url':'git@github.com:rtfd/readthedocs.org.git', - 'git_http_url':'http://github.com/rtfd/readthedocs.org.git', - 'namespace':'Mike', - 'visibility_level':0, - 'path_with_namespace':'mike/diaspora', - 'default_branch':'master', - 'homepage':'http://example.com/mike/diaspora', - 'url':'git@github.com/rtfd/readthedocs.org.git', - 'ssh_url':'git@github.com/rtfd/readthedocs.org.git', - 'http_url':'http://github.com/rtfd/readthedocs.org.git', - }, - 'repository':{ - 'name': 'Diaspora', - 'url': 'git@github.com:rtfd/readthedocs.org.git', - 'description': '', - 'homepage': 'http://github.com/rtfd/readthedocs.org', - 'git_http_url': 'http://github.com/rtfd/readthedocs.org.git', - 'git_ssh_url': 'git@github.com:rtfd/readthedocs.org.git', - 'visibility_level': 0, - }, - 'commits': [ - { - 'id': 'b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327', - 'message': 'Update Catalan translation to e38cb41.', - 'timestamp': '2011-12-12T14:27:31+02:00', - 'url': 'http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327', - 'author': { - 'name': 'Jordi Mallach', - 'email': 'jordi@softcatala.org', - }, - 'added': ['CHANGELOG'], - 'modified': ['app/controller/application.rb'], - 'removed': [], - }, - { - 'id': 'da1560886d4f094c3e6c9ef40349f7d38b5d27d7', - 'message': 'fixed readme', - 'timestamp': '2012-01-03T23:36:29+02:00', - 'url': 'http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7', - 'author': { - 'name': 'GitLab dev user', - 'email': 'gitlabdev@dv6700.(none)', - }, - 'added': ['CHANGELOG'], - 'modified': ['app/controller/application.rb'], - 'removed': [], - }, - ], - 'total_commits_count': 4, - } - - def test_gitlab_post_commit_hook_builds_branch_docs_if_it_should(self): - """GitLab webhook should only build active versions.""" - r = self.client.post( - '/gitlab/', data=json.dumps(self.payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [awesome]') - - self.payload['ref'] = 'refs/heads/not_ok' - r = self.client.post( - '/gitlab/', data=json.dumps(self.payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Not Building: github.com/rtfd/readthedocs.org [not_ok]') - - self.payload['ref'] = 'refs/heads/unknown' - r = self.client.post( - '/gitlab/', data=json.dumps(self.payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) No known branches were pushed to.') - - def test_gitlab_post_commit_knows_default_branches(self): - """ - Test the gitlab post commit hook so that the default branch - will be respected and built as the latest version. - """ - rtd = Project.objects.get(slug='read-the-docs') - old_default = rtd.default_branch - rtd.default_branch = 'master' - rtd.save() - self.payload['ref'] = 'refs/heads/master' - - r = self.client.post( - '/gitlab/', data=json.dumps(self.payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [latest]') - - rtd.default_branch = old_default - rtd.save() - - def test_gitlab_request_empty_url(self): - """ - The gitlab hook shouldn't build any project - if the url, ssh_url or ref are empty. - """ - self.payload['project']['http_url'] = '' - r = self.client.post( - '/gitlab/', data=json.dumps(self.payload), - content_type='application/json', - ) - self.assertEqual(r.status_code, 404) - - def test_gitlab_webhook_is_deprecated(self): - # Project is created after feature, not included in historical allowance - url = 'https://github.com/rtfd/readthedocs-build' - payload = self.payload.copy() - payload['project']['http_url'] = url - project = get( - Project, - main_language_project=None, - repo=url, - ) - r = self.client.post( - '/gitlab/', - data=json.dumps(payload), - content_type='application/json', - ) - self.assertEqual(r.status_code, 403) - - -class GitHubWebHookTest(BasePostCommitTest): - fixtures = ['eric'] - - def setUp(self): - self._setup() - - self.payload = { - 'after': '5ad757394b926e5637ffeafe340f952ef48bd270', - 'base_ref': 'refs/heads/master', - 'before': '5b4e453dc913b08642b1d4fb10ed23c9d6e5b129', - 'commits': [ - { - 'added': [], - 'author': { - 'email': 'eric@ericholscher.com', - 'name': 'Eric Holscher', - 'username': 'ericholscher', - }, - 'distinct': False, - 'id': '11f229c6a78f5bc8cb173104a3f7a68cdb7eb15a', - 'message': 'Fix it on the front list as well.', - 'modified': [ - 'readthedocs/templates/core/project_list_detailed.html', - ], - 'removed': [], - 'timestamp': '2011-09-12T19:38:55-07:00', - 'url': ( - 'https://github.com/wraithan/readthedocs.org/' - 'commit/11f229c6a78f5bc8cb173104a3f7a68cdb7eb15a' - ), - }, - ], - 'compare': ( - 'https://github.com/wraithan/readthedocs.org/compare/' - '5b4e453...5ad7573' - ), - 'created': False, - 'deleted': False, - 'forced': False, - 'pusher': { - 'name': 'none', - }, - 'ref': 'refs/heads/awesome', - 'repository': { - 'created_at': '2011/09/09 14:20:13 -0700', - 'description': 'source code to readthedocs.org', - 'fork': True, - 'forks': 0, - 'has_downloads': True, - 'has_issues': False, - 'has_wiki': True, - 'homepage': 'http://rtfd.org/', - 'language': 'Python', - 'name': 'readthedocs.org', - 'open_issues': 0, - 'owner': { - 'email': 'XWraithanX@gmail.com', - 'name': 'wraithan', - }, - 'private': False, - 'pushed_at': '2011/09/12 22:33:34 -0700', - 'size': 140, - 'url': 'https://github.com/rtfd/readthedocs.org', - 'ssh_url': 'git@github.com:rtfd/readthedocs.org.git', - 'watchers': 1, - - }, - } - - def test_post_types(self): - """Ensure various POST formats.""" - r = self.client.post( - '/github/', - data=json.dumps(self.payload), - content_type='application/json', - ) - self.assertEqual(r.status_code, 200) - r = self.client.post( - '/github/', - data=urlencode({'payload': json.dumps(self.payload)}), - content_type='application/x-www-form-urlencoded', - ) - self.assertEqual(r.status_code, 200) - - def test_github_upper_case_repo(self): - """ - Test the github post commit hook will build properly with upper case - repository. - - This allows for capitalization differences in post-commit hook URL's. - """ - payload = self.payload.copy() - payload['repository']['url'] = payload['repository']['url'].upper() - r = self.client.post( - '/github/', data=json.dumps(payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Build Started: HTTPS://GITHUB.COM/RTFD/READTHEDOCS.ORG [awesome]') - self.payload['ref'] = 'refs/heads/not_ok' - - def test_400_on_no_ref(self): - """ - GitHub sometimes sends us a post-commit hook without a ref. - - This means we don't know what branch to build, so return a 400. - """ - payload = self.payload.copy() - del payload['ref'] - r = self.client.post( - '/github/', data=json.dumps(payload), - content_type='application/json', - ) - self.assertEqual(r.status_code, 400) - - def test_github_request_empty_url(self): - """ - The github hook shouldn't build any project - if the url, ssh_url or ref are empty. - """ - self.payload['repository']['url'] = '' - self.payload['repository']['ssh_url'] = '' - r = self.client.post( - '/github/', data=json.dumps(self.payload), - content_type='application/json', - ) - self.assertEqual(r.status_code, 403) - - def test_private_repo_mapping(self): - """ - Test for private GitHub repo mapping. - - Previously we were missing triggering post-commit hooks because we only - compared against the *public* ``github.com/user/repo`` URL. Users can - also enter a ``github.com:user/repo`` URL, which we should support. - """ - self.rtfd.repo = 'git@github.com:rtfd/readthedocs.org' - self.rtfd.save() - payload = self.payload.copy() - r = self.client.post( - '/github/', data=json.dumps(payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [awesome]') - - def test_github_post_commit_hook_builds_branch_docs_if_it_should(self): - """ - Test the github post commit hook to see if it will only build - versions that are set to be built if the branch they refer to - is updated. Otherwise it is no op. - """ - r = self.client.post( - '/github/', data=json.dumps(self.payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [awesome]') - - self.payload['ref'] = 'refs/heads/not_ok' - r = self.client.post( - '/github/', data=json.dumps(self.payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Not Building: github.com/rtfd/readthedocs.org [not_ok]') - - self.payload['ref'] = 'refs/heads/unknown' - r = self.client.post( - '/github/', data=json.dumps(self.payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) No known branches were pushed to.') - - def test_github_post_commit_knows_default_branches(self): - """ - Test the github post commit hook so that the default branch - will be respected and built as the latest version. - """ - rtd = Project.objects.get(slug='read-the-docs') - old_default = rtd.default_branch - rtd.default_branch = 'master' - rtd.save() - self.payload['ref'] = 'refs/heads/master' - - r = self.client.post( - '/github/', data=json.dumps(self.payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Build Started: github.com/rtfd/readthedocs.org [latest]') - - rtd.default_branch = old_default - rtd.save() - - def test_github_webhook_is_deprecated(self): - # Project is created after feature, not included in historical allowance - url = 'https://github.com/rtfd/readthedocs-build' - payload = self.payload.copy() - payload['repository']['url'] = url - project = get( - Project, - main_language_project=None, - repo=url, - ) - r = self.client.post( - '/github/', - data=json.dumps(payload), - content_type='application/json', - ) - self.assertEqual(r.status_code, 403) - - -class CorePostCommitTest(BasePostCommitTest): - fixtures = ['eric'] - - def setUp(self): - self._setup() - - def test_core_commit_hook(self): - rtd = Project.objects.get(slug='read-the-docs') - rtd.default_branch = 'master' - rtd.save() - r = self.client.post('/build/%s' % rtd.pk, {'version_slug': 'master'}) - self.assertEqual(r.status_code, 302) - self.assertEqual( - r._headers['location'][1], - '/projects/read-the-docs/builds/', - ) - - def test_hook_state_tracking(self): - rtd = Project.objects.get(slug='read-the-docs') - self.assertEqual(Project.objects.get(slug='read-the-docs').has_valid_webhook, False) - self.client.post('/build/%s' % rtd.pk, {'version_slug': 'latest'}) - # Need to re-query to get updated DB entry - self.assertEqual(Project.objects.get(slug='read-the-docs').has_valid_webhook, True) - - def test_generic_webhook_is_deprecated(self): - # Project is created after feature, not included in historical allowance - project = get(Project, main_language_project=None) - r = self.client.post('/build/%s' % project.pk, {'version_slug': 'master'}) - self.assertEqual(r.status_code, 403) - - -class BitBucketWebHookTest(BasePostCommitTest): - - def setUp(self): - self._setup() - - self.hg_payload = { - 'canon_url': 'https://bitbucket.org', - 'commits': [ - { - 'author': 'marcus', - 'branch': 'default', - 'files': [ - { - 'file': 'somefile.py', - 'type': 'modified', - }, - ], - 'message': 'Added some feature things', - 'node': 'd14d26a93fd2', - 'parents': [ - '1b458191f31a', - ], - 'raw_author': 'Marcus Bertrand ', - 'raw_node': 'd14d26a93fd28d3166fa81c0cd3b6f339bb95bfe', - 'revision': 3, - 'size': -1, - 'timestamp': '2012-05-30 06:07:03', - 'utctimestamp': '2012-05-30 04:07:03+00:00', - }, - ], - 'repository': { - 'absolute_url': '/pip/pip/', - 'fork': False, - 'is_private': True, - 'name': 'Project X', - 'owner': 'marcus', - 'scm': 'hg', - 'slug': 'project-x', - 'website': '', - }, - 'user': 'marcus', - } - - self.git_payload = { - 'canon_url': 'https://bitbucket.org', - 'commits': [ - { - 'author': 'marcus', - 'branch': 'master', - 'files': [ - { - 'file': 'somefile.py', - 'type': 'modified', - }, - ], - 'message': 'Added some more things to somefile.py\n', - 'node': '620ade18607a', - 'parents': [ - '702c70160afc', - ], - 'raw_author': 'Marcus Bertrand ', - 'raw_node': '620ade18607ac42d872b568bb92acaa9a28620e9', - 'revision': None, - 'size': -1, - 'timestamp': '2012-05-30 05:58:56', - 'utctimestamp': '2012-05-30 03:58:56+00:00', - }, - ], - 'repository': { - 'absolute_url': '/sphinx/sphinx/', - 'fork': False, - 'is_private': True, - 'name': 'Project X', - 'owner': 'marcus', - 'scm': 'git', - 'slug': 'project-x', - 'website': 'https://atlassian.com/', - }, - 'user': 'marcus', - } - - def test_post_types(self): - """Ensure various POST formats.""" - r = self.client.post( - '/bitbucket/', - data=json.dumps(self.hg_payload), - content_type='application/json', - ) - self.assertEqual(r.status_code, 200) - r = self.client.post( - '/bitbucket/', - data=urlencode({'payload': json.dumps(self.hg_payload)}), - content_type='application/x-www-form-urlencoded', - ) - self.assertEqual(r.status_code, 200) - - def test_bitbucket_post_commit(self): - r = self.client.post( - '/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Build Started: bitbucket.org/pip/pip [latest]') - - r = self.client.post( - '/bitbucket/', data=json.dumps(self.git_payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Build Started: bitbucket.org/sphinx/sphinx [latest]') - - def test_bitbucket_post_commit_empty_commit_list(self): - self.hg_payload['commits'] = [] - self.git_payload['commits'] = [] - - r = self.client.post( - '/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json', - ) - self.assertContains(r, 'Commit/branch not found', status_code=404) - - r = self.client.post( - '/bitbucket/', data=json.dumps(self.git_payload), - content_type='application/json', - ) - self.assertContains(r, 'Commit/branch not found', status_code=404) - - def test_bitbucket_post_commit_non_existent_url(self): - self.hg_payload['repository']['absolute_url'] = '/invalid/repository' - self.git_payload['repository']['absolute_url'] = '/invalid/repository' - - r = self.client.post( - '/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json', - ) - self.assertContains(r, 'Project match not found', status_code=404) - - r = self.client.post( - '/bitbucket/', data=json.dumps(self.git_payload), - content_type='application/json', - ) - self.assertContains(r, 'Project match not found', status_code=404) - - - def test_bitbucket_post_commit_hook_builds_branch_docs_if_it_should(self): - """ - Test the bitbucket post commit hook to see if it will only build - versions that are set to be built if the branch they refer to - is updated. Otherwise it is no op. - """ - r = self.client.post( - '/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Build Started: bitbucket.org/pip/pip [latest]') - - self.hg_payload['commits'] = [{ - "branch": "not_ok", - }] - r = self.client.post( - '/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Not Building: bitbucket.org/pip/pip [not_ok]') - - self.hg_payload['commits'] = [{ - "branch": "unknown", - }] - r = self.client.post( - '/bitbucket/', data=json.dumps(self.hg_payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) No known branches were pushed to.') - - def test_bitbucket_default_branch(self): - self.test_project = get( - Project, repo='HTTPS://bitbucket.org/test/project', slug='test-project', - default_branch='integration', repo_type='git', - ) - self.feature.projects.add(self.test_project) - - self.git_payload['commits'] = [{ - 'branch': 'integration', - }] - self.git_payload['repository'] = { - 'absolute_url': '/test/project/', - } - - r = self.client.post( - '/bitbucket/', data=json.dumps(self.git_payload), - content_type='application/json', - ) - self.assertContains(r, '(URL Build) Build Started: bitbucket.org/test/project [latest]') - - def test_bitbucket_request_empty_url(self): - """ - The bitbucket hook shouldn't build any project - if the url, ssh_url or ref are empty. - """ - self.git_payload['repository']['absolute_url'] = '' - r = self.client.post( - '/bitbucket/', data=json.dumps(self.git_payload), - content_type='application/json', - ) - self.assertEqual(r.status_code, 400) - - def test_bitbucket_webhook_is_deprecated(self): - # Project is created after feature, not included in historical allowance - url = 'https://bitbucket.org/rtfd/readthedocs-build' - payload = self.git_payload.copy() - payload['repository']['absolute_url'] = '/rtfd/readthedocs-build' - project = get( - Project, - main_language_project=None, - repo=url, - ) - r = self.client.post( - '/bitbucket/', - data=json.dumps(payload), - content_type='application/json', - ) - self.assertEqual(r.status_code, 403)