diff --git a/taiga/base/utils/json.py b/taiga/base/utils/json.py
index dcb818a3..bb8dde78 100644
--- a/taiga/base/utils/json.py
+++ b/taiga/base/utils/json.py
@@ -16,13 +16,15 @@
import json
from rest_framework.utils import encoders
+from django.utils.encoding import force_text
def dumps(data, ensure_ascii=True, encoder_class=encoders.JSONEncoder):
return json.dumps(data, cls=encoder_class, indent=None, ensure_ascii=ensure_ascii)
-
def loads(data):
+ if isinstance(data, bytes):
+ data = force_text(data)
return json.loads(data)
# Some backward compatibility that should
diff --git a/taiga/projects/api.py b/taiga/projects/api.py
index 1b18e4c3..914c4f77 100644
--- a/taiga/projects/api.py
+++ b/taiga/projects/api.py
@@ -256,20 +256,14 @@ class RolesViewSet(ModelCrudViewSet):
filter_backends = (filters.CanViewProjectFilterBackend,)
filter_fields = ('project',)
- @tx.atomic
- def destroy(self, request, *args, **kwargs):
- moveTo = self.request.QUERY_PARAMS.get('moveTo', None)
- if moveTo is None:
- return super().destroy(request, *args, **kwargs)
+ def pre_delete(self, obj):
+ move_to = self.request.QUERY_PARAMS.get('moveTo', None)
+ if move_to:
+ role_dest = get_object_or_404(self.model, project=obj.project, id=move_to)
+ qs = models.Membership.objects.filter(project_id=obj.project.pk, role=obj)
+ qs.update(role=role_dest)
- obj = self.get_object_or_none()
-
- 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)
+ super().pre_delete(obj)
# User Stories commin ViewSets
@@ -317,19 +311,19 @@ class PointsViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
class MoveOnDestroyMixin(object):
@tx.atomic
def destroy(self, request, *args, **kwargs):
- moveTo = self.request.QUERY_PARAMS.get('moveTo', None)
- if moveTo is None:
+ move_to = self.request.QUERY_PARAMS.get('moveTo', None)
+ if move_to is None:
return super().destroy(request, *args, **kwargs)
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)
- 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)
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()
return super().destroy(request, *args, **kwargs)
diff --git a/taiga/projects/history/models.py b/taiga/projects/history/models.py
index 809cae1c..ab58b312 100644
--- a/taiga/projects/history/models.py
+++ b/taiga/projects/history/models.py
@@ -171,8 +171,8 @@ class HistoryEntry(models.Model):
if changes:
change = {
- "filename": newattachs[aid]["filename"],
- "url": newattachs[aid]["url"],
+ "filename": newattachs.get(aid, {}).get("filename", ""),
+ "url": newattachs.get(aid, {}).get("url", ""),
"changes": changes
}
attachments["changed"].append(change)
diff --git a/taiga/projects/userstories/api.py b/taiga/projects/userstories/api.py
index f4b604ed..f1a87c9a 100644
--- a/taiga/projects/userstories/api.py
+++ b/taiga/projects/userstories/api.py
@@ -14,9 +14,13 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from contextlib import suppress
+
+from django.apps import apps
from django.db import transaction
from django.utils.translation import ugettext as _
from django.shortcuts import get_object_or_404
+from django.core.exceptions import ObjectDoesNotExist
from rest_framework.response import Response
from rest_framework import status
@@ -62,6 +66,35 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
qs = qs.select_related("milestone", "project")
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"])
def bulk_create(self, request, **kwargs):
serializer = serializers.UserStoriesBulkSerializer(data=request.DATA)
@@ -145,8 +178,3 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
return response
- def pre_save(self, obj):
- if not obj.id:
- obj.owner = self.request.user
-
- super().pre_save(obj)
diff --git a/taiga/projects/userstories/serializers.py b/taiga/projects/userstories/serializers.py
index ef2607e2..cc7d1638 100644
--- a/taiga/projects/userstories/serializers.py
+++ b/taiga/projects/userstories/serializers.py
@@ -52,19 +52,6 @@ class UserStorySerializer(serializers.ModelSerializer):
depth = 0
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):
return obj.get_total_points()
diff --git a/tests/integration/test_change_avatar.py b/tests/integration/test_change_avatar.py
deleted file mode 100644
index a2202f32..00000000
--- a/tests/integration/test_change_avatar.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright (C) 2014 Andrey Antukh
-# Copyright (C) 2014 Jesús Espino
-# Copyright (C) 2014 David Barragán
-# Copyright (C) 2014 Anler Hernández
-# 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 .
-
-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
diff --git a/tests/integration/test_project_history.py b/tests/integration/test_history.py
similarity index 100%
rename from tests/integration/test_project_history.py
rename to tests/integration/test_history.py
diff --git a/tests/integration/test_milestones.py b/tests/integration/test_milestones.py
index 7af07411..e32f23ac 100644
--- a/tests/integration/test_milestones.py
+++ b/tests/integration/test_milestones.py
@@ -29,8 +29,7 @@ from .. import factories as f
pytestmark = pytest.mark.django_db
-
-def test_api_update_milestone(client):
+def test_update_milestone_with_userstories_list(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
role = f.RoleFactory.create(project=project)
@@ -39,7 +38,6 @@ def test_api_update_milestone(client):
points = f.PointsFactory.create(project=project, value=None)
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])
diff --git a/tests/integration/test_project_notifications.py b/tests/integration/test_notifications.py
similarity index 100%
rename from tests/integration/test_project_notifications.py
rename to tests/integration/test_notifications.py
diff --git a/tests/integration/test_occ.py b/tests/integration/test_occ.py
index 97626290..eb25eef8 100644
--- a/tests/integration/test_occ.py
+++ b/tests/integration/test_occ.py
@@ -15,11 +15,11 @@
# along with this program. If not, see .
import pytest
-import json
from unittest.mock import patch
from django.core.urlresolvers import reverse
+from taiga.base.utils import json
from taiga.projects.issues.models import Issue
from taiga.projects.wiki.models import WikiPage
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,))
data = {"version": 10}
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
issue = Issue.objects.get(id=issue.id)
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,))
data = {"version": 10}
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
wiki_page = WikiPage.objects.get(id=wiki_page.id)
assert wiki_page.version == 11
@@ -128,7 +128,7 @@ def test_valid_concurrent_save_for_us(client):
url = reverse("userstories-detail", args=(userstory.id,))
data = {"version": 10}
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
userstory = UserStory.objects.get(id=userstory.id)
assert userstory.version == 11
@@ -159,7 +159,7 @@ def test_valid_concurrent_save_for_task(client):
url = reverse("tasks-detail", args=(task.id,))
data = {"version": 10}
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
task = Task.objects.get(id=task.id)
assert task.version == 11
diff --git a/tests/integration/test_project_us.py b/tests/integration/test_project_us.py
deleted file mode 100644
index c369daa7..00000000
--- a/tests/integration/test_project_us.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright (C) 2014 Andrey Antukh
-# Copyright (C) 2014 Jesús Espino
-# Copyright (C) 2014 David Barragán
-# Copyright (C) 2014 Anler Hernández
-# 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 .
-
-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
diff --git a/tests/integration/test_projects.py b/tests/integration/test_projects.py
index 20762415..f2a6c249 100644
--- a/tests/integration/test_projects.py
+++ b/tests/integration/test_projects.py
@@ -7,7 +7,7 @@ import pytest
pytestmark = pytest.mark.django_db
-def test_api_create_project(client):
+def test_create_project(client):
user = f.create_user()
url = reverse("projects-list")
data = {"name": "project name", "description": "project description"}
@@ -18,7 +18,7 @@ def test_api_create_project(client):
assert response.status_code == 201
-def test_api_partially_update_project(client):
+def test_partially_update_project(client):
project = f.create_project()
url = reverse("projects-detail", kwargs={"pk": project.pk})
data = {"name": ""}
diff --git a/tests/integration/test_project_references_sequences.py b/tests/integration/test_references_sequences.py
similarity index 100%
rename from tests/integration/test_project_references_sequences.py
rename to tests/integration/test_references_sequences.py
diff --git a/tests/integration/test_roles.py b/tests/integration/test_roles.py
new file mode 100644
index 00000000..e8bd89fa
--- /dev/null
+++ b/tests/integration/test_roles.py
@@ -0,0 +1,82 @@
+# Copyright (C) 2014 Andrey Antukh
+# Copyright (C) 2014 Jesús Espino
+# Copyright (C) 2014 David Barragán
+# Copyright (C) 2014 Anler Hernández
+# 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 .
+
+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
diff --git a/tests/integration/test_users.py b/tests/integration/test_users.py
index 2beb3762..7c749cdb 100644
--- a/tests/integration/test_users.py
+++ b/tests/integration/test_users.py
@@ -1,5 +1,6 @@
import pytest
import json
+from tempfile import NamedTemporaryFile
from django.core.urlresolvers import reverse
@@ -11,7 +12,7 @@ from taiga.auth.tokens import get_token_for_user
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)
url = reverse('users-list')
@@ -26,7 +27,7 @@ def test_api_user_normal_user(client):
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")
url = reverse('users-detail', kwargs={"pk": user.pk})
data = {"email": "same@email.com"}
@@ -38,7 +39,7 @@ def test_api_user_patch_same_email(client):
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")
user = f.UserFactory.create(email="two@email.com")
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'
-def test_api_user_patch_invalid_email(client):
+def test_update_user_with_invalid_email(client):
user = f.UserFactory.create(email="my@email.com")
url = reverse('users-detail', kwargs={"pk": user.pk})
data = {"email": "my@email"}
@@ -63,7 +64,7 @@ def test_api_user_patch_invalid_email(client):
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")
url = reverse('users-detail', kwargs={"pk": user.pk})
data = {"email": "new@email.com"}
@@ -77,7 +78,7 @@ def test_api_user_patch_valid_email(client):
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")
url = reverse('users-change-email')
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"
-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")
url = reverse('users-change-email')
data = {}
client.login(user)
response = client.post(url, json.dumps(data), content_type="application/json")
-
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")
url = reverse('users-change-email')
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")
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()
url = reverse('users-detail', kwargs={"pk": user.pk})
@@ -128,7 +126,7 @@ def test_api_user_delete(client):
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()
url = reverse('users-cancel')
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"
-def test_api_user_cancel_invalid_token(client):
+def test_cancel_self_user_with_invalid_token(client):
user = f.UserFactory.create()
url = reverse('users-cancel')
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")
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
diff --git a/tests/integration/test_userstories.py b/tests/integration/test_userstories.py
index 63721eca..fc1d31e5 100644
--- a/tests/integration/test_userstories.py
+++ b/tests/integration/test_userstories.py
@@ -1,3 +1,4 @@
+import copy
from unittest import mock
from django.core.urlresolvers import reverse
@@ -11,10 +12,7 @@ pytestmark = pytest.mark.django_db
def test_get_userstories_from_bulk():
- data = """
-User Story #1
-User Story #2
-"""
+ data = "User Story #1\nUser Story #2\n"
userstories = services.get_userstories_from_bulk(data)
assert len(userstories) == 2
@@ -23,10 +21,7 @@ User Story #2
def test_create_userstories_in_bulk():
- data = """
-User Story #1
-User Story #2
-"""
+ data = "User Story #1\nUser Story #2\n"
with mock.patch("taiga.projects.userstories.services.db") as db:
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:
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)
@@ -108,3 +105,76 @@ def test_api_update_backlog_order_in_bulk(client):
assert response1.status_code == 204, response.data
assert response2.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
+