Merge pull request #123 from taigaio/random-fixes

A collection minor fixes + hotfixes
remotes/origin/enhancement/email-actions
David Barragán Merino 2014-10-15 23:29:01 +02:00
commit 154e13cc79
16 changed files with 260 additions and 176 deletions

View File

@ -16,13 +16,15 @@
import json import json
from rest_framework.utils import encoders from rest_framework.utils import encoders
from django.utils.encoding import force_text
def dumps(data, ensure_ascii=True, encoder_class=encoders.JSONEncoder): def dumps(data, ensure_ascii=True, encoder_class=encoders.JSONEncoder):
return json.dumps(data, cls=encoder_class, indent=None, ensure_ascii=ensure_ascii) return json.dumps(data, cls=encoder_class, indent=None, ensure_ascii=ensure_ascii)
def loads(data): def loads(data):
if isinstance(data, bytes):
data = force_text(data)
return json.loads(data) return json.loads(data)
# Some backward compatibility that should # Some backward compatibility that should

View File

@ -256,20 +256,14 @@ class RolesViewSet(ModelCrudViewSet):
filter_backends = (filters.CanViewProjectFilterBackend,) filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ('project',) filter_fields = ('project',)
@tx.atomic def pre_delete(self, obj):
def destroy(self, request, *args, **kwargs): move_to = self.request.QUERY_PARAMS.get('moveTo', None)
moveTo = self.request.QUERY_PARAMS.get('moveTo', None) if move_to:
if moveTo is None: role_dest = get_object_or_404(self.model, project=obj.project, id=move_to)
return super().destroy(request, *args, **kwargs) qs = models.Membership.objects.filter(project_id=obj.project.pk, role=obj)
qs.update(role=role_dest)
obj = self.get_object_or_none() super().pre_delete(obj)
moveItem = get_object_or_404(self.model, project=obj.project, id=moveTo)
self.check_permissions(request, 'destroy', obj)
models.Membership.objects.filter(project=obj.project, role=obj).update(role=moveItem)
return super().destroy(request, *args, **kwargs)
# User Stories commin ViewSets # User Stories commin ViewSets
@ -317,19 +311,19 @@ class PointsViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
class MoveOnDestroyMixin(object): class MoveOnDestroyMixin(object):
@tx.atomic @tx.atomic
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
moveTo = self.request.QUERY_PARAMS.get('moveTo', None) move_to = self.request.QUERY_PARAMS.get('moveTo', None)
if moveTo is None: if move_to is None:
return super().destroy(request, *args, **kwargs) return super().destroy(request, *args, **kwargs)
obj = self.get_object_or_none() obj = self.get_object_or_none()
moveItem = get_object_or_404(self.model, project=obj.project, id=moveTo) move_item = get_object_or_404(self.model, project=obj.project, id=move_to)
self.check_permissions(request, 'destroy', obj) self.check_permissions(request, 'destroy', obj)
kwargs = {self.move_on_destroy_related_field: moveItem} kwargs = {self.move_on_destroy_related_field: move_item}
self.move_on_destroy_related_class.objects.filter(project=obj.project, **{self.move_on_destroy_related_field: obj}).update(**kwargs) self.move_on_destroy_related_class.objects.filter(project=obj.project, **{self.move_on_destroy_related_field: obj}).update(**kwargs)
if getattr(obj.project, self.move_on_destroy_project_default_field) == obj: if getattr(obj.project, self.move_on_destroy_project_default_field) == obj:
setattr(obj.project, self.move_on_destroy_project_default_field, moveItem) setattr(obj.project, self.move_on_destroy_project_default_field, move_item)
obj.project.save() obj.project.save()
return super().destroy(request, *args, **kwargs) return super().destroy(request, *args, **kwargs)

View File

@ -171,8 +171,8 @@ class HistoryEntry(models.Model):
if changes: if changes:
change = { change = {
"filename": newattachs[aid]["filename"], "filename": newattachs.get(aid, {}).get("filename", ""),
"url": newattachs[aid]["url"], "url": newattachs.get(aid, {}).get("url", ""),
"changes": changes "changes": changes
} }
attachments["changed"].append(change) attachments["changed"].append(change)

View File

@ -14,9 +14,13 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from contextlib import suppress
from django.apps import apps
from django.db import transaction from django.db import transaction
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status from rest_framework import status
@ -62,6 +66,35 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
qs = qs.select_related("milestone", "project") qs = qs.select_related("milestone", "project")
return qs return qs
def pre_save(self, obj):
# This is very ugly hack, but having
# restframework is the only way to do it.
# NOTE: code moved as is from serializer
# to api because is not serializer logic.
related_data = getattr(obj, "_related_data", {})
self._role_points = related_data.pop("role_points", None)
if not obj.id:
obj.owner = self.request.user
super().pre_save(obj)
def post_save(self, obj, created=False):
# Code related to the hack of pre_save method. Rather,
# this is the continuation of it.
Points = apps.get_model("projects", "Points")
RolePoints = apps.get_model("userstories", "RolePoints")
if self._role_points:
with suppress(ObjectDoesNotExist):
for role_id, points_id in self._role_points.items():
role_points = RolePoints.objects.get(role__id=role_id, user_story_id=obj.pk)
role_points.points = Points.objects.get(id=points_id, project_id=obj.project_id)
role_points.save()
super().post_save(obj, created)
@list_route(methods=["POST"]) @list_route(methods=["POST"])
def bulk_create(self, request, **kwargs): def bulk_create(self, request, **kwargs):
serializer = serializers.UserStoriesBulkSerializer(data=request.DATA) serializer = serializers.UserStoriesBulkSerializer(data=request.DATA)
@ -145,8 +178,3 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
return response return response
def pre_save(self, obj):
if not obj.id:
obj.owner = self.request.user
super().pre_save(obj)

View File

@ -52,19 +52,6 @@ class UserStorySerializer(serializers.ModelSerializer):
depth = 0 depth = 0
read_only_fields = ('created_date', 'modified_date') read_only_fields = ('created_date', 'modified_date')
def save_object(self, obj, **kwargs):
role_points = obj._related_data.pop("role_points", None)
super().save_object(obj, **kwargs)
points_modelcls = apps.get_model("projects", "Points")
if role_points:
for role_id, points_id in role_points.items():
role_points = obj.role_points.get(role__id=role_id)
role_points.points = points_modelcls.objects.get(id=points_id,
project=obj.project)
role_points.save()
def get_total_points(self, obj): def get_total_points(self, obj):
return obj.get_total_points() return obj.get_total_points()

View File

@ -1,54 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014 Anler Hernández <hello@anler.me>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.core.urlresolvers import reverse
from tempfile import NamedTemporaryFile
import pytest
from .. import factories as f
pytestmark = pytest.mark.django_db
DUMMY_BMP_DATA = b'BM:\x00\x00\x00\x00\x00\x00\x006\x00\x00\x00(\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x18\x00\x00\x00\x00\x00\x04\x00\x00\x00\x13\x0b\x00\x00\x13\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
def test_change_avatar(client):
url = reverse('users-change-avatar')
user = f.UserFactory()
client.login(user)
with NamedTemporaryFile() as avatar:
# Test no avatar send
post_data = {}
response = client.post(url, post_data)
assert response.status_code == 400
# Test invalid file send
post_data = {
'avatar': avatar
}
response = client.post(url, post_data)
assert response.status_code == 400
# Test empty valid avatar send
avatar.write(DUMMY_BMP_DATA)
avatar.seek(0)
response = client.post(url, post_data)
assert response.status_code == 200

View File

@ -29,8 +29,7 @@ from .. import factories as f
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
def test_update_milestone_with_userstories_list(client):
def test_api_update_milestone(client):
user = f.UserFactory.create() user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user) project = f.ProjectFactory.create(owner=user)
role = f.RoleFactory.create(project=project) role = f.RoleFactory.create(project=project)
@ -39,7 +38,6 @@ def test_api_update_milestone(client):
points = f.PointsFactory.create(project=project, value=None) points = f.PointsFactory.create(project=project, value=None)
us = f.UserStoryFactory.create(project=project, owner=user) us = f.UserStoryFactory.create(project=project, owner=user)
# role_points = f.RolePointsFactory.create(points=points, user_story=us, role=role)
url = reverse("milestones-detail", args=[sprint.pk]) url = reverse("milestones-detail", args=[sprint.pk])

View File

@ -15,11 +15,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest import pytest
import json
from unittest.mock import patch from unittest.mock import patch
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from taiga.base.utils import json
from taiga.projects.issues.models import Issue from taiga.projects.issues.models import Issue
from taiga.projects.wiki.models import WikiPage from taiga.projects.wiki.models import WikiPage
from taiga.projects.userstories.models import UserStory from taiga.projects.userstories.models import UserStory
@ -58,7 +58,7 @@ def test_valid_concurrent_save_for_issue(client):
url = reverse("issues-detail", args=(issue.id,)) url = reverse("issues-detail", args=(issue.id,))
data = {"version": 10} data = {"version": 10}
response = client.patch(url, json.dumps(data), content_type="application/json") response = client.patch(url, json.dumps(data), content_type="application/json")
assert json.loads(response.content.decode('utf-8'))['version'] == 11 assert json.loads(response.content)['version'] == 11
assert response.status_code == 200 assert response.status_code == 200
issue = Issue.objects.get(id=issue.id) issue = Issue.objects.get(id=issue.id)
assert issue.version == 11 assert issue.version == 11
@ -85,7 +85,7 @@ def test_valid_concurrent_save_for_wiki_page(client):
url = reverse("wiki-detail", args=(wiki_page.id,)) url = reverse("wiki-detail", args=(wiki_page.id,))
data = {"version": 10} data = {"version": 10}
response = client.patch(url, json.dumps(data), content_type="application/json") response = client.patch(url, json.dumps(data), content_type="application/json")
assert json.loads(response.content.decode('utf-8'))['version'] == 11 assert json.loads(response.content)['version'] == 11
assert response.status_code == 200 assert response.status_code == 200
wiki_page = WikiPage.objects.get(id=wiki_page.id) wiki_page = WikiPage.objects.get(id=wiki_page.id)
assert wiki_page.version == 11 assert wiki_page.version == 11
@ -128,7 +128,7 @@ def test_valid_concurrent_save_for_us(client):
url = reverse("userstories-detail", args=(userstory.id,)) url = reverse("userstories-detail", args=(userstory.id,))
data = {"version": 10} data = {"version": 10}
response = client.patch(url, json.dumps(data), content_type="application/json") response = client.patch(url, json.dumps(data), content_type="application/json")
assert json.loads(response.content.decode('utf-8'))['version'] == 11 assert json.loads(response.content)['version'] == 11
assert response.status_code == 200 assert response.status_code == 200
userstory = UserStory.objects.get(id=userstory.id) userstory = UserStory.objects.get(id=userstory.id)
assert userstory.version == 11 assert userstory.version == 11
@ -159,7 +159,7 @@ def test_valid_concurrent_save_for_task(client):
url = reverse("tasks-detail", args=(task.id,)) url = reverse("tasks-detail", args=(task.id,))
data = {"version": 10} data = {"version": 10}
response = client.patch(url, json.dumps(data), content_type="application/json") response = client.patch(url, json.dumps(data), content_type="application/json")
assert json.loads(response.content.decode('utf-8'))['version'] == 11 assert json.loads(response.content)['version'] == 11
assert response.status_code == 200 assert response.status_code == 200
task = Task.objects.get(id=task.id) task = Task.objects.get(id=task.id)
assert task.version == 11 assert task.version == 11

View File

@ -1,49 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014 Anler Hernández <hello@anler.me>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
import json
from django.core.urlresolvers import reverse
from .. import factories as f
pytestmark = pytest.mark.django_db
def test_archived_filter(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
f.MembershipFactory.create(project=project, user=user)
f.UserStoryFactory.create(project=project)
f.UserStoryFactory.create(is_archived=True, project=project)
client.login(user)
url = reverse("userstories-list")
data = {}
response = client.get(url, data)
assert len(json.loads(response.content.decode('utf-8'))) == 2
data = {"is_archived": 0}
response = client.get(url, data)
assert len(json.loads(response.content.decode('utf-8'))) == 1
data = {"is_archived": 1}
response = client.get(url, data)
assert len(json.loads(response.content.decode('utf-8'))) == 1

View File

@ -7,7 +7,7 @@ import pytest
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
def test_api_create_project(client): def test_create_project(client):
user = f.create_user() user = f.create_user()
url = reverse("projects-list") url = reverse("projects-list")
data = {"name": "project name", "description": "project description"} data = {"name": "project name", "description": "project description"}
@ -18,7 +18,7 @@ def test_api_create_project(client):
assert response.status_code == 201 assert response.status_code == 201
def test_api_partially_update_project(client): def test_partially_update_project(client):
project = f.create_project() project = f.create_project()
url = reverse("projects-detail", kwargs={"pk": project.pk}) url = reverse("projects-detail", kwargs={"pk": project.pk})
data = {"name": ""} data = {"name": ""}

View File

@ -0,0 +1,82 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014 Anler Hernández <hello@anler.me>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from unittest.mock import patch, Mock
from django.apps import apps
from django.core.urlresolvers import reverse
from taiga.base.utils import json
from taiga.users.models import Role
from taiga.projects.models import Membership
from taiga.projects.models import Project
from taiga.projects.userstories.serializers import UserStorySerializer
from .. import factories as f
pytestmark = pytest.mark.django_db
def test_destroy_role_and_reassign_members(client):
user1 = f.UserFactory.create()
user2 = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user1)
role1 = f.RoleFactory.create(project=project)
role2 = f.RoleFactory.create(project=project)
member = f.MembershipFactory.create(project=project, user=user1, role=role1)
member = f.MembershipFactory.create(project=project, user=user2, role=role2)
url = reverse("roles-detail", args=[role2.pk]) + "?moveTo={}".format(role1.pk)
client.login(user1)
response = client.delete(url)
assert response.status_code == 204
qs = Role.objects.filter(project=project)
assert qs.count() == 1
qs = Membership.objects.filter(project=project, role_id=role2.pk)
assert qs.count() == 0
qs = Membership.objects.filter(project=project, role_id=role1.pk)
assert qs.count() == 2
def test_destroy_role_and_reassign_members_with_deleted_project(client):
"""
Regression test, that fixes some 500 errors on production
"""
user1 = f.UserFactory.create()
user2 = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user1)
role1 = f.RoleFactory.create(project=project)
role2 = f.RoleFactory.create(project=project)
member = f.MembershipFactory.create(project=project, user=user1, role=role1)
member = f.MembershipFactory.create(project=project, user=user2, role=role2)
Project.objects.filter(pk=project.id).delete()
url = reverse("roles-detail", args=[role2.pk]) + "?moveTo={}".format(role1.pk)
client.login(user1)
response = client.delete(url)
# FIXME: really should return 403? I think it should be 404
assert response.status_code == 403, response.content

View File

@ -1,5 +1,6 @@
import pytest import pytest
import json import json
from tempfile import NamedTemporaryFile
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@ -11,7 +12,7 @@ from taiga.auth.tokens import get_token_for_user
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
def test_api_user_normal_user(client): def test_users_create_through_standard_api(client):
user = f.UserFactory.create(is_superuser=True) user = f.UserFactory.create(is_superuser=True)
url = reverse('users-list') url = reverse('users-list')
@ -26,7 +27,7 @@ def test_api_user_normal_user(client):
assert response.status_code == 405 assert response.status_code == 405
def test_api_user_patch_same_email(client): def test_update_user_with_same_email(client):
user = f.UserFactory.create(email="same@email.com") user = f.UserFactory.create(email="same@email.com")
url = reverse('users-detail', kwargs={"pk": user.pk}) url = reverse('users-detail', kwargs={"pk": user.pk})
data = {"email": "same@email.com"} data = {"email": "same@email.com"}
@ -38,7 +39,7 @@ def test_api_user_patch_same_email(client):
assert response.data['_error_message'] == 'Duplicated email' assert response.data['_error_message'] == 'Duplicated email'
def test_api_user_patch_duplicated_email(client): def test_update_user_with_duplicated_email(client):
f.UserFactory.create(email="one@email.com") f.UserFactory.create(email="one@email.com")
user = f.UserFactory.create(email="two@email.com") user = f.UserFactory.create(email="two@email.com")
url = reverse('users-detail', kwargs={"pk": user.pk}) url = reverse('users-detail', kwargs={"pk": user.pk})
@ -51,7 +52,7 @@ def test_api_user_patch_duplicated_email(client):
assert response.data['_error_message'] == 'Duplicated email' assert response.data['_error_message'] == 'Duplicated email'
def test_api_user_patch_invalid_email(client): def test_update_user_with_invalid_email(client):
user = f.UserFactory.create(email="my@email.com") user = f.UserFactory.create(email="my@email.com")
url = reverse('users-detail', kwargs={"pk": user.pk}) url = reverse('users-detail', kwargs={"pk": user.pk})
data = {"email": "my@email"} data = {"email": "my@email"}
@ -63,7 +64,7 @@ def test_api_user_patch_invalid_email(client):
assert response.data['_error_message'] == 'Not valid email' assert response.data['_error_message'] == 'Not valid email'
def test_api_user_patch_valid_email(client): def test_update_user_with_valid_email(client):
user = f.UserFactory.create(email="old@email.com") user = f.UserFactory.create(email="old@email.com")
url = reverse('users-detail', kwargs={"pk": user.pk}) url = reverse('users-detail', kwargs={"pk": user.pk})
data = {"email": "new@email.com"} data = {"email": "new@email.com"}
@ -77,7 +78,7 @@ def test_api_user_patch_valid_email(client):
assert user.new_email == "new@email.com" assert user.new_email == "new@email.com"
def test_api_user_action_change_email_ok(client): def test_validate_requested_email_change(client):
user = f.UserFactory.create(email_token="change_email_token", new_email="new@email.com") user = f.UserFactory.create(email_token="change_email_token", new_email="new@email.com")
url = reverse('users-change-email') url = reverse('users-change-email')
data = {"email_token": "change_email_token"} data = {"email_token": "change_email_token"}
@ -92,19 +93,17 @@ def test_api_user_action_change_email_ok(client):
assert user.email == "new@email.com" assert user.email == "new@email.com"
def test_api_user_action_change_email_no_token(client): def test_validate_requested_email_change_without_token(client):
user = f.UserFactory.create(email_token="change_email_token", new_email="new@email.com") user = f.UserFactory.create(email_token="change_email_token", new_email="new@email.com")
url = reverse('users-change-email') url = reverse('users-change-email')
data = {} data = {}
client.login(user) client.login(user)
response = client.post(url, json.dumps(data), content_type="application/json") response = client.post(url, json.dumps(data), content_type="application/json")
assert response.status_code == 400 assert response.status_code == 400
assert response.data['_error_message'] == 'Invalid, are you sure the token is correct and you didn\'t use it before?'
def test_api_user_action_change_email_invalid_token(client): def test_validate_requested_email_change_with_invalid_token(client):
user = f.UserFactory.create(email_token="change_email_token", new_email="new@email.com") user = f.UserFactory.create(email_token="change_email_token", new_email="new@email.com")
url = reverse('users-change-email') url = reverse('users-change-email')
data = {"email_token": "invalid_email_token"} data = {"email_token": "invalid_email_token"}
@ -113,10 +112,9 @@ def test_api_user_action_change_email_invalid_token(client):
response = client.post(url, json.dumps(data), content_type="application/json") response = client.post(url, json.dumps(data), content_type="application/json")
assert response.status_code == 400 assert response.status_code == 400
assert response.data['_error_message'] == 'Invalid, are you sure the token is correct and you didn\'t use it before?'
def test_api_user_delete(client): def test_delete_self_user(client):
user = f.UserFactory.create() user = f.UserFactory.create()
url = reverse('users-detail', kwargs={"pk": user.pk}) url = reverse('users-detail', kwargs={"pk": user.pk})
@ -128,7 +126,7 @@ def test_api_user_delete(client):
assert user.full_name == "Deleted user" assert user.full_name == "Deleted user"
def test_api_user_cancel_valid_token(client): def test_cancel_self_user_with_valid_token(client):
user = f.UserFactory.create() user = f.UserFactory.create()
url = reverse('users-cancel') url = reverse('users-cancel')
cancel_token = get_token_for_user(user, "cancel_account") cancel_token = get_token_for_user(user, "cancel_account")
@ -141,7 +139,7 @@ def test_api_user_cancel_valid_token(client):
assert user.full_name == "Deleted user" assert user.full_name == "Deleted user"
def test_api_user_cancel_invalid_token(client): def test_cancel_self_user_with_invalid_token(client):
user = f.UserFactory.create() user = f.UserFactory.create()
url = reverse('users-cancel') url = reverse('users-cancel')
data = {"cancel_token": "invalid_cancel_token"} data = {"cancel_token": "invalid_cancel_token"}
@ -149,4 +147,32 @@ def test_api_user_cancel_invalid_token(client):
response = client.post(url, json.dumps(data), content_type="application/json") response = client.post(url, json.dumps(data), content_type="application/json")
assert response.status_code == 400 assert response.status_code == 400
assert response.data['_error_message'] == "Invalid, are you sure the token is correct?"
DUMMY_BMP_DATA = b'BM:\x00\x00\x00\x00\x00\x00\x006\x00\x00\x00(\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x18\x00\x00\x00\x00\x00\x04\x00\x00\x00\x13\x0b\x00\x00\x13\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
def test_change_avatar(client):
url = reverse('users-change-avatar')
user = f.UserFactory()
client.login(user)
with NamedTemporaryFile() as avatar:
# Test no avatar send
post_data = {}
response = client.post(url, post_data)
assert response.status_code == 400
# Test invalid file send
post_data = {
'avatar': avatar
}
response = client.post(url, post_data)
assert response.status_code == 400
# Test empty valid avatar send
avatar.write(DUMMY_BMP_DATA)
avatar.seek(0)
response = client.post(url, post_data)
assert response.status_code == 200

View File

@ -1,3 +1,4 @@
import copy
from unittest import mock from unittest import mock
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
@ -11,10 +12,7 @@ pytestmark = pytest.mark.django_db
def test_get_userstories_from_bulk(): def test_get_userstories_from_bulk():
data = """ data = "User Story #1\nUser Story #2\n"
User Story #1
User Story #2
"""
userstories = services.get_userstories_from_bulk(data) userstories = services.get_userstories_from_bulk(data)
assert len(userstories) == 2 assert len(userstories) == 2
@ -23,10 +21,7 @@ User Story #2
def test_create_userstories_in_bulk(): def test_create_userstories_in_bulk():
data = """ data = "User Story #1\nUser Story #2\n"
User Story #1
User Story #2
"""
with mock.patch("taiga.projects.userstories.services.db") as db: with mock.patch("taiga.projects.userstories.services.db") as db:
userstories = services.create_userstories_in_bulk(data) userstories = services.create_userstories_in_bulk(data)
@ -41,7 +36,9 @@ def test_update_userstories_order_in_bulk():
with mock.patch("taiga.projects.userstories.services.db") as db: with mock.patch("taiga.projects.userstories.services.db") as db:
services.update_userstories_order_in_bulk(data, "backlog_order", project) services.update_userstories_order_in_bulk(data, "backlog_order", project)
db.update_in_bulk_with_ids.assert_called_once_with([1, 2], [{"backlog_order": 1}, {"backlog_order": 2}], db.update_in_bulk_with_ids.assert_called_once_with([1, 2],
[{"backlog_order": 1},
{"backlog_order": 2}],
model=models.UserStory) model=models.UserStory)
@ -108,3 +105,76 @@ def test_api_update_backlog_order_in_bulk(client):
assert response1.status_code == 204, response.data assert response1.status_code == 204, response.data
assert response2.status_code == 204, response.data assert response2.status_code == 204, response.data
assert response3.status_code == 204, response.data assert response3.status_code == 204, response.data
from taiga.projects.userstories.serializers import UserStorySerializer
def test_update_userstory_points(client):
user1 = f.UserFactory.create()
user2 = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user1)
role1 = f.RoleFactory.create(project=project)
role2 = f.RoleFactory.create(project=project)
member = f.MembershipFactory.create(project=project, user=user1, role=role1)
member = f.MembershipFactory.create(project=project, user=user2, role=role2)
points1 = f.PointsFactory.create(project=project, value=None)
points2 = f.PointsFactory.create(project=project, value=1)
points3 = f.PointsFactory.create(project=project, value=2)
us = f.UserStoryFactory.create(project=project, owner=user1)
url = reverse("userstories-detail", args=[us.pk])
usdata = UserStorySerializer(us).data
client.login(user1)
# Api should ignore invalid values
data = {}
data["version"] = usdata["version"]
data["points"] = copy.copy(usdata["points"])
data["points"].update({'2000':points3.pk})
response = client.json.patch(url, json.dumps(data))
assert response.status_code == 200, response.data
# Api should save successful
data = {}
data["version"] = usdata["version"]
data["points"] = copy.copy(usdata["points"])
data["points"].update({str(role1.pk):points3.pk})
response = client.json.patch(url, json.dumps(data))
assert response.status_code == 200, response.data
us = models.UserStory.objects.get(pk=us.pk)
rp = list(us.role_points.values_list("role_id", "points_id"))
assert rp == [(role1.pk, points3.pk), (role2.pk, points1.pk)]
def test_archived_filter(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
f.MembershipFactory.create(project=project, user=user)
f.UserStoryFactory.create(project=project)
f.UserStoryFactory.create(is_archived=True, project=project)
client.login(user)
url = reverse("userstories-list")
data = {}
response = client.get(url, data)
assert len(json.loads(response.content)) == 2
data = {"is_archived": 0}
response = client.get(url, data)
assert len(json.loads(response.content)) == 1
data = {"is_archived": 1}
response = client.get(url, data)
assert len(json.loads(response.content)) == 1