Add test for build redirect
parent
51196b14f5
commit
c972fb253d
|
@ -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])
|
||||||
|
)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue