Add test for build redirect

ghowardsit
Eric Holscher 2018-11-02 15:55:37 -05:00
parent 51196b14f5
commit c972fb253d
3 changed files with 142 additions and 62 deletions

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Views for builds app.""" """Views for builds app."""
from __future__ import ( from __future__ import (
@ -35,8 +36,13 @@ class BuildBase(object):
def get_queryset(self): def get_queryset(self):
self.project_slug = self.kwargs.get('project_slug', None) self.project_slug = self.kwargs.get('project_slug', None)
self.project = get_object_or_404(Project.objects.protected(self.request.user),slug=self.project_slug,) self.project = get_object_or_404(
queryset = Build.objects.public(user=self.request.user, project=self.project) Project.objects.protected(self.request.user),
slug=self.project_slug,
)
queryset = Build.objects.public(
user=self.request.user, project=self.project
)
return queryset return queryset
@ -57,10 +63,10 @@ class BuildTriggerMixin(object):
slug=version_slug, slug=version_slug,
) )
_, signature = trigger_build(project=project, version=version) _, build = trigger_build(project=project, version=version)
build_pk = signature.get('kwargs', {}).get('build_pk')
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('builds_detail', args=[project.slug, build_pk])) reverse('builds_detail', args=[project.slug, build.pk]),
)
class BuildList(BuildBase, BuildTriggerMixin, ListView): class BuildList(BuildBase, BuildTriggerMixin, ListView):
@ -68,11 +74,14 @@ class BuildList(BuildBase, BuildTriggerMixin, ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(BuildList, self).get_context_data(**kwargs) context = super(BuildList, self).get_context_data(**kwargs)
active_builds = self.get_queryset().exclude(state='finished').values('id') active_builds = self.get_queryset().exclude(state='finished'
).values('id')
context['project'] = self.project context['project'] = self.project
context['active_builds'] = active_builds context['active_builds'] = active_builds
context['versions'] = Version.objects.public(user=self.request.user, project=self.project) context['versions'] = Version.objects.public(
user=self.request.user, project=self.project
)
context['build_qs'] = self.get_queryset() context['build_qs'] = self.get_queryset()
return context return context
@ -91,8 +100,12 @@ class BuildDetail(BuildBase, DetailView):
def builds_redirect_list(request, project_slug): # pylint: disable=unused-argument def builds_redirect_list(request, project_slug): # pylint: disable=unused-argument
return HttpResponsePermanentRedirect(reverse('builds_project_list', args=[project_slug])) return HttpResponsePermanentRedirect(
reverse('builds_project_list', args=[project_slug])
)
def builds_redirect_detail(request, project_slug, pk): # pylint: disable=unused-argument def builds_redirect_detail(request, project_slug, pk): # pylint: disable=unused-argument
return HttpResponsePermanentRedirect(reverse('builds_detail', args=[project_slug, pk])) return HttpResponsePermanentRedirect(
reverse('builds_detail', args=[project_slug, pk])
)

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Common utilty functions.""" """Common utilty functions."""
from __future__ import absolute_import from __future__ import absolute_import
@ -14,13 +15,11 @@ from django.utils import six
from django.utils.functional import allow_lazy from django.utils.functional import allow_lazy
from django.utils.safestring import SafeText, mark_safe from django.utils.safestring import SafeText, mark_safe
from django.utils.text import slugify as slugify_base from django.utils.text import slugify as slugify_base
from future.backports.urllib.parse import urlparse
from celery import group, chord from celery import group, chord
from readthedocs.builds.constants import LATEST, BUILD_STATE_TRIGGERED from readthedocs.builds.constants import LATEST, BUILD_STATE_TRIGGERED
from readthedocs.doc_builder.constants import DOCKER_LIMITS from readthedocs.doc_builder.constants import DOCKER_LIMITS
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
SYNC_USER = getattr(settings, 'SYNC_USER', getpass.getuser()) SYNC_USER = getattr(settings, 'SYNC_USER', getpass.getuser())
@ -40,9 +39,9 @@ def broadcast(type, task, args, kwargs=None, callback=None): # pylint: disable=
kwargs = {} kwargs = {}
default_queue = getattr(settings, 'CELERY_DEFAULT_QUEUE', 'celery') default_queue = getattr(settings, 'CELERY_DEFAULT_QUEUE', 'celery')
if type in ['web', 'app']: if type in ['web', 'app']:
servers = getattr(settings, "MULTIPLE_APP_SERVERS", [default_queue]) servers = getattr(settings, 'MULTIPLE_APP_SERVERS', [default_queue])
elif type in ['build']: elif type in ['build']:
servers = getattr(settings, "MULTIPLE_BUILD_SERVERS", [default_queue]) servers = getattr(settings, 'MULTIPLE_BUILD_SERVERS', [default_queue])
tasks = [] tasks = []
for server in servers: for server in servers:
@ -71,7 +70,12 @@ def cname_to_slug(host):
def prepare_build( def prepare_build(
project, version=None, record=True, force=False, immutable=True): project,
version=None,
record=True,
force=False,
immutable=True,
):
""" """
Prepare a build in a Celery task for project and version. Prepare a build in a Celery task for project and version.
@ -132,11 +136,14 @@ def prepare_build(
options['soft_time_limit'] = time_limit options['soft_time_limit'] = time_limit
options['time_limit'] = int(time_limit * 1.2) options['time_limit'] = int(time_limit * 1.2)
return update_docs_task.signature( return (
args=(project.pk,), update_docs_task.signature(
kwargs=kwargs, args=(project.pk,),
options=options, kwargs=kwargs,
immutable=True, options=options,
immutable=True,
),
build,
) )
@ -153,7 +160,7 @@ def trigger_build(project, version=None, record=True, force=False):
:param force: build the HTML documentation even if the files haven't changed :param force: build the HTML documentation even if the files haven't changed
:returns: A tuple (Celery AsyncResult promise, Task Signature from ``prepare_build``) :returns: A tuple (Celery AsyncResult promise, Task Signature from ``prepare_build``)
""" """
update_docs_task = prepare_build( update_docs_task, build = prepare_build(
project, project,
version, version,
record, record,
@ -165,11 +172,13 @@ def trigger_build(project, version=None, record=True, force=False):
# Current project is skipped # Current project is skipped
return None return None
return (update_docs_task.apply_async(), update_docs_task) return (update_docs_task.apply_async(), build)
def send_email(recipient, subject, template, template_html, context=None, def send_email(
request=None, from_email=None, **kwargs): # pylint: disable=unused-argument recipient, subject, template, template_html, context=None, request=None,
from_email=None, **kwargs
): # pylint: disable=unused-argument
""" """
Alter context passed in and call email send task. Alter context passed in and call email send task.
@ -183,10 +192,14 @@ def send_email(recipient, subject, template, template_html, context=None,
if context is None: if context is None:
context = {} context = {}
context['uri'] = '{scheme}://{host}'.format( context['uri'] = '{scheme}://{host}'.format(
scheme='https', host=settings.PRODUCTION_DOMAIN) scheme='https',
send_email_task.delay(recipient=recipient, subject=subject, template=template, host=settings.PRODUCTION_DOMAIN,
template_html=template_html, context=context, from_email=from_email, )
**kwargs) send_email_task.delay(
recipient=recipient, subject=subject, template=template,
template_html=template_html, context=context, from_email=from_email,
**kwargs
)
def slugify(value, *args, **kwargs): def slugify(value, *args, **kwargs):

View File

@ -1,19 +1,26 @@
from __future__ import absolute_import # -*- coding: utf-8 -*-
from __future__ import (
absolute_import,
division,
print_function,
unicode_literals,
)
import mock
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import TestCase from django.test import TestCase
from django.utils.six.moves.urllib.parse import urlsplit from django.utils.six.moves.urllib.parse import urlsplit
from django_dynamic_fixture import get from django_dynamic_fixture import get, new
from django_dynamic_fixture import new
from readthedocs.builds.constants import LATEST from readthedocs.builds.constants import LATEST
from readthedocs.builds.models import Build
from readthedocs.core.permissions import AdminPermission from readthedocs.core.permissions import AdminPermission
from readthedocs.projects.models import ImportedFile
from readthedocs.projects.models import Project
from readthedocs.projects.forms import UpdateProjectForm from readthedocs.projects.forms import UpdateProjectForm
from readthedocs.projects.models import ImportedFile, Project
class Testmaker(TestCase): class Testmaker(TestCase):
def setUp(self): def setUp(self):
self.eric = User(username='eric') self.eric = User(username='eric')
self.eric.set_password('test') self.eric.set_password('test')
@ -27,21 +34,24 @@ class Testmaker(TestCase):
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
r = self.client.get('/dashboard/import/manual/', {}) r = self.client.get('/dashboard/import/manual/', {})
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
form = UpdateProjectForm(data={ form = UpdateProjectForm(
'name': 'Django Kong', data={
'repo': 'https://github.com/ericholscher/django-kong', 'name': 'Django Kong',
'repo_type': 'git', 'repo': 'https://github.com/ericholscher/django-kong',
'description': 'OOHHH AH AH AH KONG SMASH', 'repo_type': 'git',
'language': 'en', 'description': 'OOHHH AH AH AH KONG SMASH',
'default_branch': '', 'language': 'en',
'project_url': 'http://django-kong.rtfd.org', 'default_branch': '',
'default_version': LATEST, 'project_url': 'http://django-kong.rtfd.org',
'privacy_level': 'public', 'default_version': LATEST,
'version_privacy_level': 'public', 'privacy_level': 'public',
'python_interpreter': 'python', 'version_privacy_level': 'public',
'documentation_type': 'sphinx', 'python_interpreter': 'python',
'csrfmiddlewaretoken': '34af7c8a5ba84b84564403a280d9a9be', 'documentation_type': 'sphinx',
}, user=user) 'csrfmiddlewaretoken': '34af7c8a5ba84b84564403a280d9a9be',
},
user=user,
)
_ = form.save() _ = form.save()
_ = Project.objects.get(slug='django-kong') _ = Project.objects.get(slug='django-kong')
@ -111,11 +121,13 @@ class PrivateViewsAreProtectedTests(TestCase):
def test_subprojects_delete(self): def test_subprojects_delete(self):
# This URL doesn't exist anymore, 404 # This URL doesn't exist anymore, 404
response = self.client.get( response = self.client.get(
'/dashboard/pip/subprojects/delete/a-subproject/') '/dashboard/pip/subprojects/delete/a-subproject/',
)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
# New URL # New URL
response = self.client.get( response = self.client.get(
'/dashboard/pip/subprojects/a-subproject/delete/') '/dashboard/pip/subprojects/a-subproject/delete/',
)
self.assertRedirectToLogin(response) self.assertRedirectToLogin(response)
def test_subprojects(self): def test_subprojects(self):
@ -143,7 +155,9 @@ class PrivateViewsAreProtectedTests(TestCase):
self.assertRedirectToLogin(response) self.assertRedirectToLogin(response)
def test_project_translations_delete(self): def test_project_translations_delete(self):
response = self.client.get('/dashboard/pip/translations/delete/a-translation/') response = self.client.get(
'/dashboard/pip/translations/delete/a-translation/'
)
self.assertRedirectToLogin(response) self.assertRedirectToLogin(response)
def test_project_redirects(self): def test_project_redirects(self):
@ -168,7 +182,8 @@ class RandomPageTests(TestCase):
slug='file', slug='file',
path='file.html', path='file.html',
md5='abcdef', md5='abcdef',
commit='1234567890abcdef') commit='1234567890abcdef',
)
def test_random_page_view_redirects(self): def test_random_page_view_redirects(self):
response = self.client.get('/random/') response = self.client.get('/random/')
@ -188,7 +203,9 @@ class RandomPageTests(TestCase):
response = self.client.get('/random/pip/') response = self.client.get('/random/pip/')
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
class SubprojectViewTests(TestCase): class SubprojectViewTests(TestCase):
def setUp(self): def setUp(self):
self.user = new(User, username='test') self.user = new(User, username='test')
self.user.set_password('test') self.user.set_password('test')
@ -201,10 +218,15 @@ class SubprojectViewTests(TestCase):
self.client.login(username='test', password='test') self.client.login(username='test', password='test')
def test_deny_delete_for_non_project_admins(self): def test_deny_delete_for_non_project_admins(self):
response = self.client.get('/dashboard/my-mainproject/subprojects/delete/my-subproject/') response = self.client.get(
'/dashboard/my-mainproject/subprojects/delete/my-subproject/'
)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
self.assertTrue(self.subproject in [r.child for r in self.project.subprojects.all()]) self.assertTrue(
self.subproject in
[r.child for r in self.project.subprojects.all()]
)
def test_admins_can_delete_subprojects(self): def test_admins_can_delete_subprojects(self):
self.project.users.add(self.user) self.project.users.add(self.user)
@ -212,24 +234,56 @@ class SubprojectViewTests(TestCase):
# URL doesn't exist anymore, 404 # URL doesn't exist anymore, 404
response = self.client.get( response = self.client.get(
'/dashboard/my-mainproject/subprojects/delete/my-subproject/') '/dashboard/my-mainproject/subprojects/delete/my-subproject/',
)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
# This URL still doesn't accept GET, 405 # This URL still doesn't accept GET, 405
response = self.client.get( response = self.client.get(
'/dashboard/my-mainproject/subprojects/my-subproject/delete/') '/dashboard/my-mainproject/subprojects/my-subproject/delete/',
)
self.assertEqual(response.status_code, 405) self.assertEqual(response.status_code, 405)
self.assertTrue(self.subproject in [r.child for r in self.project.subprojects.all()]) self.assertTrue(
self.subproject in
[r.child for r in self.project.subprojects.all()]
)
# Test POST # Test POST
response = self.client.post( response = self.client.post(
'/dashboard/my-mainproject/subprojects/my-subproject/delete/') '/dashboard/my-mainproject/subprojects/my-subproject/delete/',
)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertTrue(self.subproject not in [r.child for r in self.project.subprojects.all()]) self.assertTrue(
self.subproject not in
[r.child for r in self.project.subprojects.all()]
)
def test_project_admins_can_delete_subprojects_that_they_are_not_admin_of(self): def test_project_admins_can_delete_subprojects_that_they_are_not_admin_of(
self
):
self.project.users.add(self.user) self.project.users.add(self.user)
self.assertFalse(AdminPermission.is_admin(self.user, self.subproject)) self.assertFalse(AdminPermission.is_admin(self.user, self.subproject))
response = self.client.post( response = self.client.post(
'/dashboard/my-mainproject/subprojects/my-subproject/delete/') '/dashboard/my-mainproject/subprojects/my-subproject/delete/',
)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertTrue(self.subproject not in [r.child for r in self.project.subprojects.all()]) self.assertTrue(
self.subproject not in
[r.child for r in self.project.subprojects.all()]
)
class BuildViewTests(TestCase):
fixtures = ['eric', 'test_data']
def setUp(self):
self.client.login(username='eric', password='test')
@mock.patch('readthedocs.projects.tasks.update_docs_task')
def test_build_redirect(self, mock):
r = self.client.post('/projects/pip/builds/', {'version_slug': '0.8.1'})
build = Build.objects.filter(project__slug='pip').latest()
self.assertEqual(r.status_code, 302)
self.assertEqual(
r._headers['location'][1],
'/projects/pip/builds/%s/' % build.pk,
)