Skip to content

Commit

Permalink
Add end-to-end test
Browse files Browse the repository at this point in the history
refactor factories to use python3 syntax
change js to properly react
  • Loading branch information
fdambrine authored and artragis committed Feb 23, 2018
1 parent c4f9f2d commit e51c37a
Show file tree
Hide file tree
Showing 16 changed files with 239 additions and 61 deletions.
10 changes: 4 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ install:
&& mkdir geckodriver \
&& tar -xzf geckodriver-v0.19.1-linux64.tar.gz -C geckodriver \
&& export PATH=$PATH:$PWD/geckodriver \
&& export DISPLAY=:99.0 \
&& sh -e /etc/init.d/xvfb start \
&& sleep 3 # give xvfb some time to start
&& export DISPLAY=:99.0
fi
- |
Expand Down Expand Up @@ -163,11 +161,11 @@ script:
# selenium tests
if [[ "$ZDS_TEST_JOB" == *"selenium"* ]]; then
yarn build
python manage.py \
xvfb-run --server-args="-screen 0 1280x720x8" python manage.py \
test \
--keepdb \
--settings zds.settings.ci_test \
--tag=front
--tag=front \
--keepdb
fi
- |
Expand Down
19 changes: 9 additions & 10 deletions assets/js/content-publication-readiness.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
(function ($, undefined) {
$("li").on("click", ".readiness", function (e) {
(function ($) {
$(".readiness").on("click", function (e) {
var url = $(e.target).data("url");
var readiness = $(e.target).data("is-ready") === "true";
var readiness = $(e.target).data("is-ready").toString() === "true";
var csrf = $("input[name=csrfmiddlewaretoken]").val();

var toggledReadiness = !readiness;
$.ajax(url, {method: "PUT", data: {
"ready_to_publish": readiness,
"ready_to_publish": toggledReadiness,
"container_slug": $(e.target).data("container-slug"),
"parent_container_slug": $(e.target).data("parent-container-slug") || ""
}, success: function () {
$(e.target).data("is-ready", readiness?"false":"true")
.children("span")
.removeClass("glyphicon-remove-sign")
.removeClass("glyphicon-ok-sign")
.addClass(readiness?"glyphicon-remove-sign":"glyphicon-ok-sign");
var readinessAsString = String(toggledReadiness);
var newDisplayedText = $(e.target).data("is-ready-" + readinessAsString);
$(e.target).attr("data-is-ready", readinessAsString)
.text(newDisplayedText);
}, headers: {
"X-CSRFToken": csrf
}});
Expand Down
6 changes: 3 additions & 3 deletions doc/source/back-end/contents_manifest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ Les 0 non significatifs sont optionnels ainsi ``{version: "1"}`` est strictement
Version 2.1
-----------

La version 2.1 est la version actuelement utilisée.
Le manifest voit l'arrivée d'un nouvel élément non obligatoire ``ready_to_publish`` qui sera utilisé sur tous les éléments de type `Container`.
La version 2.1 est la version actuellement utilisée.
Le manifest voit l'arrivée d'un nouvel élément non obligatoire ``ready_to_publish`` qui sera utilisé sur tous les éléments de type ``Container``.
Cet élément permet de marquer qu'une partie ou un chapitre est prêt à être publié. Lorsque la valeur est à ``False``, la partie ou le chapitre
sont simplement ignoré du processus de publication.
sont simplement ignorés du processus de publication.

Lorsque l'attribut n'est pas renseigné, il est supposé *truthy*.

Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ flake8==3.4.1
flake8_quotes==0.11.0
autopep8==1.3.2
sphinx==1.6.3
selenium==3.6.0
selenium==3.8.1
sphinx_rtd_theme==0.2.4
faker==0.8.3
mock==2.0.0
Expand Down
18 changes: 9 additions & 9 deletions templates/tutorialv2/view/container.html
Original file line number Diff line number Diff line change
Expand Up @@ -243,17 +243,17 @@ <h1>
</form>
</li>
{% endif %}
{% if container.ready_to_publish %}
<li>
<a data-url="{% url 'api:content:readiness' content.pk %}" class="readiness" data-is-ready="true" data-container-slug="{{ container.slug }}"
{% if container.parent.parent %}data-parent-container-slug="{{ container.parent.slug }}"{% endif %}>
<span class="glyphicon-remove-sign"></span>{% trans "Marquer comme à ne pas valider." %}
<a data-url="{% url 'api:content:readiness' content.pk %}" class="readiness" data-is-ready="{{ container.ready_to_publish|yesno:"true,false" }}"
data-container-slug="{{ container.slug }}"
{% if container.parent.parent %}data-parent-container-slug="{{ container.parent.slug }}"{% endif %}
data-is-ready-false="{% trans "Marquer comme prêt à valider." %}" data-is-ready-true="{% trans "Marquer comme à ne pas valider." %}">
{% if container.ready_to_publish %}
{% trans "Marquer comme à ne pas valider." %}
{% else %}
{% trans "Marquer comme prêt à valider." %}
{% endif %}
</a></li>
{% else %}
<li><a data-url="{% url 'api:content:readiness' content.pk %}" class="readiness" data-is-ready="false"
{% if container.parent.parent %}data-parent-container-slug="{{ container.parent.slug }}"{% endif %}>
<span class="glyphicon-ok-sign"></span>{% trans "Marquer comme prêt à valider." %}</a></li>
{% endif %}
{% endif %}
{% endblock %}

Expand Down
19 changes: 14 additions & 5 deletions zds/member/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,25 @@ def has_object_permission(self, request, view, obj):


class IsOwner(permissions.BasePermission):

def has_permission(self, request, view):
return request.user and self.has_object_permission(request, view, view.get_object())

def has_object_permission(self, request, view, obj):
# Write permissions are not allowed to the owner of the snippet
if hasattr(obj, 'user'):
owners = [obj.user]
owners = [obj.user.pk]
elif hasattr(obj, 'author'):
owners = [obj.author]
owners = [obj.author.pk]
elif hasattr(obj, 'authors'):
owners = list(obj.authors)
owners = list(obj.authors.values_list('pk', flat=True))

return request.user in owners
return request.user.pk in owners


class IsAuthorOrStaff(permissions.BasePermission):
def has_permission(self, request, view):
return IsStaffUser().has_permission(request, view) or IsOwner().has_object_permission(request, view,
view.get_object())


class IsNotOwnerOrReadOnly(permissions.BasePermission):
Expand Down
2 changes: 1 addition & 1 deletion zds/settings/abstract_base/django.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@
CRISPY_TEMPLATE_PACK = 'bootstrap'

INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
Expand Down
7 changes: 4 additions & 3 deletions zds/tutorialv2/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
from rest_framework.generics import UpdateAPIView
from rest_framework.serializers import Serializer, CharField, BooleanField
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from zds.member.api.permissions import CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly, IsStaffUser, IsOwner
from zds.member.api.permissions import CanReadAndWriteNowOrReadOnly, IsNotOwnerOrReadOnly, IsAuthorOrStaff
from zds.tutorialv2.utils import search_container_or_404
from zds.utils.api.views import KarmaView
from zds.tutorialv2.models.database import ContentReaction, PublishableContent


class ContainerReadinessSerializer(Serializer):
parent_container_slug = CharField()
parent_container_slug = CharField(allow_blank=True, allow_null=True, required=False)
container_slug = CharField(required=True)
ready_to_publish = BooleanField(required=True)

Expand Down Expand Up @@ -45,7 +45,7 @@ class ContentReactionKarmaView(KarmaView):


class ContainerPublicationReadinessView(UpdateAPIView):
permission_classes = (IsStaffUser, IsOwner)
permission_classes = (IsAuthorOrStaff, )
serializer_class = ContainerReadinessSerializer

def get_object(self):
Expand All @@ -54,4 +54,5 @@ def get_object(self):
.first()
if not content:
raise Http404()
self.check_object_permissions(self.request, object)
return content
19 changes: 4 additions & 15 deletions zds/tutorialv2/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Meta:
pubdate = datetime.now()

@classmethod
def _prepare(cls, create, **kwargs):
def _prepare(cls, create, *, light=True, **kwargs):
auths = []
if 'author_list' in kwargs:
auths = kwargs.pop('author_list')
Expand All @@ -52,9 +52,6 @@ def _prepare(cls, create, **kwargs):
given_licence = Licence.objects.filter(title=given_licence).first() or Licence.objects.first()
licence = given_licence or LicenceFactory()

light = True
if 'light' in kwargs:
light = kwargs.pop('light')
text = text_content
if not light:
text = tricky_text_content
Expand Down Expand Up @@ -82,13 +79,9 @@ class Meta:
title = factory.Sequence(lambda n: 'Mon container No{0}'.format(n + 1))

@classmethod
def _prepare(cls, create, **kwargs):
db_object = kwargs.pop('db_object', None)
def _prepare(cls, create, *, db_object=None, light=True, **kwargs):
parent = kwargs.pop('parent', None)

light = True
if 'light' in kwargs:
light = kwargs.pop('light')
text = text_content
if not light:
text = tricky_text_content
Expand All @@ -109,13 +102,9 @@ class Meta:
title = factory.Sequence(lambda n: 'Mon extrait No{0}'.format(n + 1))

@classmethod
def _prepare(cls, create, **kwargs):
def _prepare(cls, create, *, light=True, container=None, **kwargs):
db_object = kwargs.pop('db_object', None)
parent = kwargs.pop('container', None)

light = True
if 'light' in kwargs:
light = kwargs.pop('light')
parent = container
text = text_content
if not light:
text = tricky_text_content
Expand Down
8 changes: 8 additions & 0 deletions zds/tutorialv2/models/versioned.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,14 @@ def requires_validation(self):
"""
return self.type in CONTENT_TYPES_REQUIRING_VALIDATION

def remove_children(self, children_slugs):
for slug in children_slugs:
if slug not in self.children_dict:
continue
to_be_remove = self.children_dict[slug]
self.children.remove(to_be_remove)
del self.children_dict[slug]

def _dump_html(self, file_path, content, db_object):
try:
with file_path.open('w', encoding='utf-8') as f:
Expand Down
1 change: 0 additions & 1 deletion zds/tutorialv2/publication_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,6 @@ def publish(self, md_file_path, base_name, silently_pass=True, **kwargs):
class FailureDuringPublication(Exception):
"""Exception raised if something goes wrong during publication process
"""

def __init__(self, *args, **kwargs):
super(FailureDuringPublication, self).__init__(*args, **kwargs)

Expand Down
13 changes: 8 additions & 5 deletions zds/tutorialv2/publish_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,16 @@ def publish_container(db_object, base_dir, container, template='tutorialv2/expor
parsed = emarkdown(container.get_introduction(), db_object.js_support)
container.introduction = str(part_path)
write_chapter_file(base_dir, container, part_path, parsed, path_to_title_dict, image_callback)

for i, child in enumerate(copy.copy(container.children)):
children = copy.copy(container.children)
container.children = []
container.children_dict = {}
for child in filter(lambda c: c.ready_to_publish, children):
altered_version = copy.copy(child)
container.children[i] = altered_version
container.children.append(altered_version)
container.children_dict[altered_version.slug] = altered_version
path_to_title_dict.update(publish_container(db_object, base_dir, altered_version, file_ext=file_ext,
image_callback=image_callback))
result = publish_container(db_object, base_dir, altered_version, file_ext=file_ext,
image_callback=image_callback)
path_to_title_dict.update(result)
if container.conclusion and container.get_conclusion():
part_path = Path(container.get_prod_path(relative=True), 'conclusion.' + file_ext)
parsed = emarkdown(container.get_conclusion(), db_object.js_support)
Expand Down
75 changes: 75 additions & 0 deletions zds/tutorialv2/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from django.conf import settings
import shutil

from django.urls import reverse
from selenium.webdriver.support.wait import WebDriverWait

from zds.tutorialv2.models.database import Validation


class TutorialTestMixin:
def clean_media_dir(self):
Expand All @@ -11,3 +16,73 @@ def clean_media_dir(self):

def tearDown(self):
self.clean_media_dir()


class TutorialFrontMixin:
def login(self, profile):
"""
TODO: This is definitely way too slow. Fasten this.
"""
selenium = self.selenium
find_element = selenium.find_element_by_css_selector

selenium.get(self.live_server_url + reverse('member-login'))

username = find_element('.content-container input#id_username')
password = find_element('.content-container input#id_password')
username.send_keys(profile.user.username)
password.send_keys('hostel77')

find_element('.content-container button[type=submit]').click()

# Wait until the user is logged in (this raises if the element
# is not found).

find_element('.header-container .logbox .my-account .username')

def login_author(self):
self.login(self.user_author.profile)

def login_staff(self):
self.login(self.user_staff.profile)

def logout(self):
find_element = self.selenium.find_element_by_css_selector
find_element('#my-account').click()
find_element('form[action="/membres/deconnexion/"] button').click()

def ask_validation(self):
find_element = self.selenium.find_element_by_css_selector
self.selenium.get(self.live_server_url + self.content.get_absolute_url())
find_element('a[href="#ask-validation"]').click()
find_element('#id_text').send_keys('Coucou.')
find_element('#ask-validation button[type="submit"]').click()

def take_reservation(self):
find_element = self.selenium.find_element_by_css_selector
self.selenium.get(self.live_server_url + self.content.get_absolute_url())
validation = Validation.objects.filter(content=self.content).first()
find_element('form[action="/validations/reserver/{}/"] button'.format(validation.pk)).click()

def validate(self):
find_element = self.selenium.find_element_by_css_selector
self.selenium.get(self.live_server_url + self.content.get_absolute_url())
validation = Validation.objects.filter(content=self.content).first()
find_element('a[href="#valid-publish"]').click()
find_element('form#valid-publish #id_text').send_keys('Coucou.')
find_element('form[action="/validations/accepter/{}/"] button'.format(validation.pk)).click()

def wait_element_attribute_change(self, locator, attribute, initial_value, time):
return WebDriverWait(self.selenium, time) \
.until(AttributeHasChanged(locator, attribute, initial_value))


class AttributeHasChanged:
def __init__(self, locator, attribute_name, initial_value):
self.locator = locator
self.attribute_name = attribute_name
self.initial_value = initial_value

def __call__(self, driver):
element = driver.find_element(*self.locator)
return element.get_attribute(self.attribute_name) != self.initial_value
Loading

0 comments on commit e51c37a

Please sign in to comment.