diff --git a/taiga/projects/issues/api.py b/taiga/projects/issues/api.py index d2582223..6f59a93a 100644 --- a/taiga/projects/issues/api.py +++ b/taiga/projects/issues/api.py @@ -31,7 +31,8 @@ from taiga.projects.notifications.mixins import WatchedResourceMixin from taiga.projects.occ import OCCResourceMixin from taiga.projects.history.mixins import HistoryResourceMixin -from taiga.projects.models import Project +from taiga.projects.models import Project, IssueStatus, Severity, Priority, IssueType +from taiga.projects.milestones.models import Milestone from taiga.projects.votes.utils import attach_votescount_to_queryset from taiga.projects.votes import services as votes_service from taiga.projects.votes import serializers as votes_serializers @@ -121,6 +122,60 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, "assigned_to", "subject") + def update(self, request, *args, **kwargs): + self.object = self.get_object_or_none() + project_id = request.DATA.get('project', None) + if project_id and self.object and self.object.project.id != project_id: + try: + new_project = Project.objects.get(pk=project_id) + self.check_permissions(request, "destroy", self.object) + self.check_permissions(request, "create", new_project) + + sprint_id = request.DATA.get('milestone', None) + if sprint_id is not None and new_project.milestones.filter(pk=sprint_id).count() == 0: + request.DATA['milestone'] = None + + status_id = request.DATA.get('status', None) + if status_id is not None: + try: + old_status = self.object.project.issue_statuses.get(pk=status_id) + new_status = new_project.issue_statuses.get(slug=old_status.slug) + request.DATA['status'] = new_status.id + except IssueStatus.DoesNotExist: + request.DATA['status'] = new_project.default_issue_status.id + + priority_id = request.DATA.get('priority', None) + if priority_id is not None: + try: + old_priority = self.object.project.priorities.get(pk=priority_id) + new_priority = new_project.priorities.get(name=old_priority.name) + request.DATA['priority'] = new_priority.id + except Priority.DoesNotExist: + request.DATA['priority'] = new_project.default_priority.id + + severity_id = request.DATA.get('severity', None) + if severity_id is not None: + try: + old_severity = self.object.project.severities.get(pk=severity_id) + new_severity = new_project.severities.get(name=old_severity.name) + request.DATA['severity'] = new_severity.id + except Severity.DoesNotExist: + request.DATA['severity'] = new_project.default_severity.id + + type_id = request.DATA.get('type', None) + if type_id is not None: + try: + old_type = self.object.project.issue_types.get(pk=type_id) + new_type = new_project.issue_types.get(name=old_type.name) + request.DATA['type'] = new_type.id + except IssueType.DoesNotExist: + request.DATA['type'] = new_project.default_issue_type.id + + except Project.DoesNotExist: + return response.BadRequest(_("The project doesn't exist")) + + return super().update(request, *args, **kwargs) + def get_queryset(self): qs = models.Issue.objects.all() qs = qs.prefetch_related("attachments") @@ -130,6 +185,7 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, def pre_save(self, obj): if not obj.id: obj.owner = self.request.user + super().pre_save(obj) def pre_conditions_on_save(self, obj): diff --git a/taiga/projects/references/models.py b/taiga/projects/references/models.py index 8832034c..583c69c8 100644 --- a/taiga/projects/references/models.py +++ b/taiga/projects/references/models.py @@ -80,19 +80,31 @@ def delete_sequence(sender, instance, **kwargs): seq.delete(seqname) -def attach_sequence(sender, instance, created, **kwargs): - if created and not instance._importing: - # Create a reference object. This operation should be - # used in transaction context, otherwise it can - # create a lot of phantom reference objects. - refval, _ = make_reference(instance, instance.project) +def store_previous_project(sender, instance, **kwargs): + try: + prev_instance = sender.objects.get(pk=instance.pk) + instance.prev_project = prev_instance.project + except sender.DoesNotExist: + instance.prev_project = None - # Additionally, attach sequence number to instance as ref - instance.ref = refval - instance.save(update_fields=['ref']) + +def attach_sequence(sender, instance, created, **kwargs): + if not instance._importing: + if created or instance.prev_project != instance.project: + # Create a reference object. This operation should be + # used in transaction context, otherwise it can + # create a lot of phantom reference objects. + refval, _ = make_reference(instance, instance.project) + + # Additionally, attach sequence number to instance as ref + instance.ref = refval + instance.save(update_fields=['ref']) models.signals.post_save.connect(create_sequence, sender=Project, dispatch_uid="refproj") +models.signals.pre_save.connect(store_previous_project, sender=UserStory, dispatch_uid="refus") +models.signals.pre_save.connect(store_previous_project, sender=Issue, dispatch_uid="refissue") +models.signals.pre_save.connect(store_previous_project, sender=Task, dispatch_uid="reftask") models.signals.post_save.connect(attach_sequence, sender=UserStory, dispatch_uid="refus") models.signals.post_save.connect(attach_sequence, sender=Issue, dispatch_uid="refissue") models.signals.post_save.connect(attach_sequence, sender=Task, dispatch_uid="reftask") diff --git a/taiga/projects/tasks/api.py b/taiga/projects/tasks/api.py index 2f1c008b..5d5581cd 100644 --- a/taiga/projects/tasks/api.py +++ b/taiga/projects/tasks/api.py @@ -21,7 +21,7 @@ from taiga.base import filters, response from taiga.base import exceptions as exc from taiga.base.decorators import list_route from taiga.base.api import ModelCrudViewSet -from taiga.projects.models import Project +from taiga.projects.models import Project, TaskStatus from django.http import HttpResponse from taiga.projects.notifications.mixins import WatchedResourceMixin @@ -44,6 +44,38 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, filter_fields = ["user_story", "milestone", "project", "assigned_to", "status__is_closed", "watchers"] + def update(self, request, *args, **kwargs): + self.object = self.get_object_or_none() + project_id = request.DATA.get('project', None) + if project_id and self.object and self.object.project.id != project_id: + try: + new_project = Project.objects.get(pk=project_id) + self.check_permissions(request, "destroy", self.object) + self.check_permissions(request, "create", new_project) + + sprint_id = request.DATA.get('milestone', None) + if sprint_id is not None and new_project.milestones.filter(pk=sprint_id).count() == 0: + request.DATA['milestone'] = None + + us_id = request.DATA.get('user_story', None) + if us_id is not None and new_project.user_stories.filter(pk=us_id).count() == 0: + request.DATA['user_story'] = None + + status_id = request.DATA.get('status', None) + if status_id is not None: + try: + old_status = self.object.project.task_statuses.get(pk=status_id) + new_status = new_project.task_statuses.get(slug=old_status.slug) + request.DATA['status'] = new_status.id + except TaskStatus.DoesNotExist: + request.DATA['status'] = new_project.default_task_status.id + + except Project.DoesNotExist: + return response.BadRequest(_("The project doesn't exist")) + + return super().update(request, *args, **kwargs) + + def pre_save(self, obj): if obj.user_story: obj.milestone = obj.user_story.milestone @@ -55,16 +87,16 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, super().pre_conditions_on_save(obj) if obj.milestone and obj.milestone.project != obj.project: - raise exc.WrongArguments(_("You don't have permissions for add/modify this task.")) + raise exc.WrongArguments(_("You don't have permissions to set this sprint to this task.")) if obj.user_story and obj.user_story.project != obj.project: - raise exc.WrongArguments(_("You don't have permissions for add/modify this task.")) + raise exc.WrongArguments(_("You don't have permissions to set this user story to this task.")) if obj.status and obj.status.project != obj.project: - raise exc.WrongArguments(_("You don't have permissions for add/modify this task.")) + raise exc.WrongArguments(_("You don't have permissions to set this status to this task.")) if obj.milestone and obj.user_story and obj.milestone != obj.user_story.milestone: - raise exc.WrongArguments(_("You don't have permissions for add/modify this task.")) + raise exc.WrongArguments(_("You don't have permissions to set this sprint to this task.")) @list_route(methods=["GET"]) def by_ref(self, request): diff --git a/taiga/projects/userstories/api.py b/taiga/projects/userstories/api.py index b3e6ed05..9820f589 100644 --- a/taiga/projects/userstories/api.py +++ b/taiga/projects/userstories/api.py @@ -62,6 +62,33 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi # Specific filter used for filtering neighbor user stories _neighbor_tags_filter = filters.TagsFilter('neighbor_tags') + def update(self, request, *args, **kwargs): + self.object = self.get_object_or_none() + project_id = request.DATA.get('project', None) + if project_id and self.object and self.object.project.id != project_id: + try: + new_project = Project.objects.get(pk=project_id) + self.check_permissions(request, "destroy", self.object) + self.check_permissions(request, "create", new_project) + + sprint_id = request.DATA.get('milestone', None) + if sprint_id is not None and new_project.milestones.filter(pk=sprint_id).count() == 0: + request.DATA['milestone'] = None + + status_id = request.DATA.get('status', None) + if status_id is not None: + try: + old_status = self.object.project.us_statuses.get(pk=status_id) + new_status = new_project.us_statuses.get(slug=old_status.slug) + request.DATA['status'] = new_status.id + except UserStoryStatus.DoesNotExist: + request.DATA['status'] = new_project.default_us_status.id + except Project.DoesNotExist: + return response.BadRequest(_("The project doesn't exist")) + + return super().update(request, *args, **kwargs) + + def get_queryset(self): qs = self.model.objects.all() qs = qs.prefetch_related("role_points", @@ -100,6 +127,17 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi super().post_save(obj, created) + def pre_conditions_on_save(self, obj): + super().pre_conditions_on_save(obj) + + if obj.milestone and obj.milestone.project != obj.project: + raise exc.PermissionDenied(_("You don't have permissions to set this sprint " + "to this user story.")) + + if obj.status and obj.status.project != obj.project: + raise exc.PermissionDenied(_("You don't have permissions to set this status " + "to this user story.")) + @list_route(methods=["GET"]) def by_ref(self, request): ref = request.QUERY_PARAMS.get("ref", None) diff --git a/tests/factories.py b/tests/factories.py index 4e9b9d0c..8a351d49 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -231,6 +231,7 @@ class UserStoryFactory(Factory): subject = factory.Sequence(lambda n: "User Story {}".format(n)) description = factory.Sequence(lambda n: "User Story {} description".format(n)) status = factory.SubFactory("tests.factories.UserStoryStatusFactory") + milestone = factory.SubFactory("tests.factories.MilestoneFactory") class UserStoryStatusFactory(Factory): diff --git a/tests/integration/resources_permissions/test_issues_resources.py b/tests/integration/resources_permissions/test_issues_resources.py index c6c99f2d..43abdeac 100644 --- a/tests/integration/resources_permissions/test_issues_resources.py +++ b/tests/integration/resources_permissions/test_issues_resources.py @@ -160,6 +160,119 @@ def test_issue_update(client, data): assert results == [401, 403, 403, 200, 200] +def test_issue_update_with_project_change(client): + user1 = f.UserFactory.create() + user2 = f.UserFactory.create() + user3 = f.UserFactory.create() + user4 = f.UserFactory.create() + project1 = f.ProjectFactory() + project2 = f.ProjectFactory() + + issue_status1 = f.IssueStatusFactory.create(project=project1) + issue_status2 = f.IssueStatusFactory.create(project=project2) + + priority1 = f.PriorityFactory.create(project=project1) + priority2 = f.PriorityFactory.create(project=project2) + + severity1 = f.SeverityFactory.create(project=project1) + severity2 = f.SeverityFactory.create(project=project2) + + issue_type1 = f.IssueTypeFactory.create(project=project1) + issue_type2 = f.IssueTypeFactory.create(project=project2) + + project1.default_issue_status = issue_status1 + project2.default_issue_status = issue_status2 + + project1.default_priority = priority1 + project2.default_priority = priority2 + + project1.default_severity = severity1 + project2.default_severity = severity2 + + project1.default_issue_type = issue_type1 + project2.default_issue_type = issue_type2 + + project1.save() + project2.save() + + membership1 = f.MembershipFactory(project=project1, + user=user1, + role__project=project1, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + membership2 = f.MembershipFactory(project=project2, + user=user1, + role__project=project2, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + membership3 = f.MembershipFactory(project=project1, + user=user2, + role__project=project1, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + membership4 = f.MembershipFactory(project=project2, + user=user3, + role__project=project2, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + + issue = f.IssueFactory.create(project=project1) + + url = reverse('issues-detail', kwargs={"pk": issue.pk}) + + # Test user with permissions in both projects + client.login(user1) + + issue_data = IssueSerializer(issue).data + issue_data["project"] = project2.id + issue_data = json.dumps(issue_data) + + response = client.put(url, data=issue_data, content_type="application/json") + + assert response.status_code == 200 + + issue.project = project1 + issue.save() + + # Test user with permissions in only origin project + client.login(user2) + + issue_data = IssueSerializer(issue).data + issue_data["project"] = project2.id + issue_data = json.dumps(issue_data) + + response = client.put(url, data=issue_data, content_type="application/json") + + assert response.status_code == 403 + + issue.project = project1 + issue.save() + + # Test user with permissions in only destionation project + client.login(user3) + + issue_data = IssueSerializer(issue).data + issue_data["project"] = project2.id + issue_data = json.dumps(issue_data) + + response = client.put(url, data=issue_data, content_type="application/json") + + assert response.status_code == 403 + + issue.project = project1 + issue.save() + + # Test user without permissions in the projects + client.login(user4) + + issue_data = IssueSerializer(issue).data + issue_data["project"] = project2.id + issue_data = json.dumps(issue_data) + + response = client.put(url, data=issue_data, content_type="application/json") + + assert response.status_code == 403 + + issue.project = project1 + issue.save() + + def test_issue_delete(client, data): public_url = reverse('issues-detail', kwargs={"pk": data.public_issue.pk}) private_url1 = reverse('issues-detail', kwargs={"pk": data.private_issue1.pk}) diff --git a/tests/integration/resources_permissions/test_tasks_resources.py b/tests/integration/resources_permissions/test_tasks_resources.py index 22bb719f..2259cfa4 100644 --- a/tests/integration/resources_permissions/test_tasks_resources.py +++ b/tests/integration/resources_permissions/test_tasks_resources.py @@ -83,18 +83,25 @@ def data(): user=m.project_owner, is_owner=True) + milestone_public_task = f.MilestoneFactory(project=m.public_project) + milestone_private_task1 = f.MilestoneFactory(project=m.private_project1) + milestone_private_task2 = f.MilestoneFactory(project=m.private_project2) + m.public_task = f.TaskFactory(project=m.public_project, status__project=m.public_project, - milestone__project=m.public_project, - user_story__project=m.public_project) + milestone=milestone_public_task, + user_story__project=m.public_project, + user_story__milestone=milestone_public_task) m.private_task1 = f.TaskFactory(project=m.private_project1, status__project=m.private_project1, - milestone__project=m.private_project1, - user_story__project=m.private_project1) + milestone=milestone_private_task1, + user_story__project=m.private_project1, + user_story__milestone=milestone_private_task1) m.private_task2 = f.TaskFactory(project=m.private_project2, status__project=m.private_project2, - milestone__project=m.private_project2, - user_story__project=m.private_project2) + milestone=milestone_private_task2, + user_story__project=m.private_project2, + user_story__milestone=milestone_private_task2) m.public_project.default_task_status = m.public_task.status m.public_project.save() @@ -160,6 +167,101 @@ def test_task_update(client, data): assert results == [401, 403, 403, 200, 200] +def test_task_update_with_project_change(client): + user1 = f.UserFactory.create() + user2 = f.UserFactory.create() + user3 = f.UserFactory.create() + user4 = f.UserFactory.create() + project1 = f.ProjectFactory() + project2 = f.ProjectFactory() + + task_status1 = f.TaskStatusFactory.create(project=project1) + task_status2 = f.TaskStatusFactory.create(project=project2) + + project1.default_task_status = task_status1 + project2.default_task_status = task_status2 + + project1.save() + project2.save() + + membership1 = f.MembershipFactory(project=project1, + user=user1, + role__project=project1, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + membership2 = f.MembershipFactory(project=project2, + user=user1, + role__project=project2, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + membership3 = f.MembershipFactory(project=project1, + user=user2, + role__project=project1, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + membership4 = f.MembershipFactory(project=project2, + user=user3, + role__project=project2, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + + task = f.TaskFactory.create(project=project1) + + url = reverse('tasks-detail', kwargs={"pk": task.pk}) + + # Test user with permissions in both projects + client.login(user1) + + task_data = TaskSerializer(task).data + task_data["project"] = project2.id + task_data = json.dumps(task_data) + + response = client.put(url, data=task_data, content_type="application/json") + + assert response.status_code == 200 + + task.project = project1 + task.save() + + # Test user with permissions in only origin project + client.login(user2) + + task_data = TaskSerializer(task).data + task_data["project"] = project2.id + task_data = json.dumps(task_data) + + response = client.put(url, data=task_data, content_type="application/json") + + assert response.status_code == 403 + + task.project = project1 + task.save() + + # Test user with permissions in only destionation project + client.login(user3) + + task_data = TaskSerializer(task).data + task_data["project"] = project2.id + task_data = json.dumps(task_data) + + response = client.put(url, data=task_data, content_type="application/json") + + assert response.status_code == 403 + + task.project = project1 + task.save() + + # Test user without permissions in the projects + client.login(user4) + + task_data = TaskSerializer(task).data + task_data["project"] = project2.id + task_data = json.dumps(task_data) + + response = client.put(url, data=task_data, content_type="application/json") + + assert response.status_code == 403 + + task.project = project1 + task.save() + + def test_task_delete(client, data): public_url = reverse('tasks-detail', kwargs={"pk": data.public_task.pk}) private_url1 = reverse('tasks-detail', kwargs={"pk": data.private_task1.pk}) diff --git a/tests/integration/resources_permissions/test_userstories_resources.py b/tests/integration/resources_permissions/test_userstories_resources.py index 3a718cc7..a3e09808 100644 --- a/tests/integration/resources_permissions/test_userstories_resources.py +++ b/tests/integration/resources_permissions/test_userstories_resources.py @@ -89,13 +89,19 @@ def data(): m.public_role_points = f.RolePointsFactory(role=m.public_project.roles.all()[0], points=m.public_points, - user_story__project=m.public_project) + user_story__project=m.public_project, + user_story__milestone__project=m.public_project, + user_story__status__project=m.public_project) m.private_role_points1 = f.RolePointsFactory(role=m.private_project1.roles.all()[0], points=m.private_points1, - user_story__project=m.private_project1) + user_story__project=m.private_project1, + user_story__milestone__project=m.private_project1, + user_story__status__project=m.private_project1) m.private_role_points2 = f.RolePointsFactory(role=m.private_project2.roles.all()[0], points=m.private_points2, - user_story__project=m.private_project2) + user_story__project=m.private_project2, + user_story__milestone__project=m.private_project2, + user_story__status__project=m.private_project2) m.public_user_story = m.public_role_points.user_story m.private_user_story1 = m.private_role_points1.user_story @@ -158,6 +164,101 @@ def test_user_story_update(client, data): assert results == [401, 403, 403, 200, 200] +def test_user_story_update_with_project_change(client): + user1 = f.UserFactory.create() + user2 = f.UserFactory.create() + user3 = f.UserFactory.create() + user4 = f.UserFactory.create() + project1 = f.ProjectFactory() + project2 = f.ProjectFactory() + + us_status1 = f.UserStoryStatusFactory.create(project=project1) + us_status2 = f.UserStoryStatusFactory.create(project=project2) + + project1.default_us_status = us_status1 + project2.default_us_status = us_status2 + + project1.save() + project2.save() + + membership1 = f.MembershipFactory(project=project1, + user=user1, + role__project=project1, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + membership2 = f.MembershipFactory(project=project2, + user=user1, + role__project=project2, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + membership3 = f.MembershipFactory(project=project1, + user=user2, + role__project=project1, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + membership4 = f.MembershipFactory(project=project2, + user=user3, + role__project=project2, + role__permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) + + us = f.UserStoryFactory.create(project=project1) + + url = reverse('userstories-detail', kwargs={"pk": us.pk}) + + # Test user with permissions in both projects + client.login(user1) + + us_data = UserStorySerializer(us).data + us_data["project"] = project2.id + us_data = json.dumps(us_data) + + response = client.put(url, data=us_data, content_type="application/json") + + assert response.status_code == 200 + + us.project = project1 + us.save() + + # Test user with permissions in only origin project + client.login(user2) + + us_data = UserStorySerializer(us).data + us_data["project"] = project2.id + us_data = json.dumps(us_data) + + response = client.put(url, data=us_data, content_type="application/json") + + assert response.status_code == 403 + + us.project = project1 + us.save() + + # Test user with permissions in only destionation project + client.login(user3) + + us_data = UserStorySerializer(us).data + us_data["project"] = project2.id + us_data = json.dumps(us_data) + + response = client.put(url, data=us_data, content_type="application/json") + + assert response.status_code == 403 + + us.project = project1 + us.save() + + # Test user without permissions in the projects + client.login(user4) + + us_data = UserStorySerializer(us).data + us_data["project"] = project2.id + us_data = json.dumps(us_data) + + response = client.put(url, data=us_data, content_type="application/json") + + assert response.status_code == 403 + + us.project = project1 + us.save() + + def test_user_story_delete(client, data): public_url = reverse('userstories-detail', kwargs={"pk": data.public_user_story.pk}) private_url1 = reverse('userstories-detail', kwargs={"pk": data.private_user_story1.pk}) diff --git a/tests/integration/test_history.py b/tests/integration/test_history.py index 020eadcd..4ea8efbd 100644 --- a/tests/integration/test_history.py +++ b/tests/integration/test_history.py @@ -199,7 +199,7 @@ def test_take_hidden_snapshot(): def test_history_with_only_comment_shouldnot_be_hidden(client): project = f.create_project() - us = f.create_userstory(project=project) + us = f.create_userstory(project=project, status__project=project) f.MembershipFactory.create(project=project, user=project.owner, is_owner=True) qs_all = HistoryEntry.objects.all() @@ -213,7 +213,7 @@ def test_history_with_only_comment_shouldnot_be_hidden(client): client.login(project.owner) response = client.patch(url, data, content_type="application/json") - assert response.status_code == 200, response.content + assert response.status_code == 200, str(response.content) assert qs_all.count() == 1 assert qs_hidden.count() == 0 diff --git a/tests/integration/test_notifications.py b/tests/integration/test_notifications.py index d3d64ddb..13087254 100644 --- a/tests/integration/test_notifications.py +++ b/tests/integration/test_notifications.py @@ -32,6 +32,7 @@ from taiga.projects.history.services import take_snapshot from taiga.projects.issues.serializers import IssueSerializer from taiga.projects.userstories.serializers import UserStorySerializer from taiga.projects.tasks.serializers import TaskSerializer +from taiga.permissions.permissions import MEMBERS_PERMISSIONS pytestmark = pytest.mark.django_db @@ -317,7 +318,7 @@ def test_watchers_assignation_for_issue(client): url = reverse("issues-detail", args=[issue.pk]) response = client.json.patch(url, json.dumps(data)) - assert response.status_code == 200, response.content + assert response.status_code == 200, str(response.content) issue = f.create_issue(project=project1, owner=user1) data = {"version": issue.version, @@ -356,22 +357,22 @@ def test_watchers_assignation_for_task(client): user2 = f.UserFactory.create() project1 = f.ProjectFactory.create(owner=user1) project2 = f.ProjectFactory.create(owner=user2) - role1 = f.RoleFactory.create(project=project1) + role1 = f.RoleFactory.create(project=project1, permissions=list(map(lambda x: x[0], MEMBERS_PERMISSIONS))) role2 = f.RoleFactory.create(project=project2) f.MembershipFactory.create(project=project1, user=user1, role=role1, is_owner=True) f.MembershipFactory.create(project=project2, user=user2, role=role2) client.login(user1) - task = f.create_task(project=project1, owner=user1) + task = f.create_task(project=project1, owner=user1, status__project=project1, milestone__project=project1, user_story=None) data = {"version": task.version, "watchers": [user1.pk]} url = reverse("tasks-detail", args=[task.pk]) response = client.json.patch(url, json.dumps(data)) - assert response.status_code == 200, response.content + assert response.status_code == 200, str(response.content) - task = f.create_task(project=project1, owner=user1) + task = f.create_task(project=project1, owner=user1, status__project=project1, milestone__project=project1) data = {"version": task.version, "watchers": [user1.pk, user2.pk]} @@ -379,7 +380,7 @@ def test_watchers_assignation_for_task(client): response = client.json.patch(url, json.dumps(data)) assert response.status_code == 400 - task = f.create_task(project=project1, owner=user1) + task = f.create_task(project=project1, owner=user1, status__project=project1, milestone__project=project1) data = dict(TaskSerializer(task).data) data["id"] = None data["version"] = None @@ -391,7 +392,7 @@ def test_watchers_assignation_for_task(client): # Test the impossible case when project is not # exists in create request, and validator works as expected - task = f.create_task(project=project1, owner=user1) + task = f.create_task(project=project1, owner=user1, status__project=project1, milestone__project=project1) data = dict(TaskSerializer(task).data) data["id"] = None @@ -415,15 +416,15 @@ def test_watchers_assignation_for_us(client): client.login(user1) - us = f.create_userstory(project=project1, owner=user1) + us = f.create_userstory(project=project1, owner=user1, status__project=project1) data = {"version": us.version, "watchers": [user1.pk]} url = reverse("userstories-detail", args=[us.pk]) response = client.json.patch(url, json.dumps(data)) - assert response.status_code == 200 + assert response.status_code == 200, str(response.content) - us = f.create_userstory(project=project1, owner=user1) + us = f.create_userstory(project=project1, owner=user1, status__project=project1) data = {"version": us.version, "watchers": [user1.pk, user2.pk]} @@ -431,7 +432,7 @@ def test_watchers_assignation_for_us(client): response = client.json.patch(url, json.dumps(data)) assert response.status_code == 400 - us = f.create_userstory(project=project1, owner=user1) + us = f.create_userstory(project=project1, owner=user1, status__project=project1) data = dict(UserStorySerializer(us).data) data["id"] = None data["version"] = None @@ -443,7 +444,7 @@ def test_watchers_assignation_for_us(client): # Test the impossible case when project is not # exists in create request, and validator works as expected - us = f.create_userstory(project=project1, owner=user1) + us = f.create_userstory(project=project1, owner=user1, status__project=project1) data = dict(UserStorySerializer(us).data) data["id"] = None @@ -463,4 +464,4 @@ def test_retrieve_notify_policies_by_anonymous_user(client): url = reverse("notifications-detail", args=[policy.pk]) response = client.get(url, content_type="application/json") assert response.status_code == 404, response.status_code - assert response.data["_error_message"] == "No NotifyPolicy matches the given query.", response.content + assert response.data["_error_message"] == "No NotifyPolicy matches the given query.", str(response.content) diff --git a/tests/integration/test_references_sequences.py b/tests/integration/test_references_sequences.py index 10653cea..f384791e 100644 --- a/tests/integration/test_references_sequences.py +++ b/tests/integration/test_references_sequences.py @@ -74,3 +74,70 @@ def test_unique_reference_per_project(seq, refmodels): project.delete() assert not seq.exists(seqname) + + +@pytest.mark.django_db +def test_regenerate_us_reference_on_project_change(seq, refmodels): + project1 = factories.ProjectFactory.create() + seqname1 = refmodels.make_sequence_name(project1) + project2 = factories.ProjectFactory.create() + seqname2 = refmodels.make_sequence_name(project2) + + seq.alter(seqname1, 100) + seq.alter(seqname2, 200) + + user_story = factories.UserStoryFactory.create(project=project1) + assert user_story.ref == 101 + + user_story.subject = "other" + user_story.save() + assert user_story.ref == 101 + + user_story.project = project2 + user_story.save() + + assert user_story.ref == 201 + +@pytest.mark.django_db +def test_regenerate_task_reference_on_project_change(seq, refmodels): + project1 = factories.ProjectFactory.create() + seqname1 = refmodels.make_sequence_name(project1) + project2 = factories.ProjectFactory.create() + seqname2 = refmodels.make_sequence_name(project2) + + seq.alter(seqname1, 100) + seq.alter(seqname2, 200) + + task = factories.TaskFactory.create(project=project1) + assert task.ref == 101 + + task.subject = "other" + task.save() + assert task.ref == 101 + + task.project = project2 + task.save() + + assert task.ref == 201 + +@pytest.mark.django_db +def test_regenerate_issue_reference_on_project_change(seq, refmodels): + project1 = factories.ProjectFactory.create() + seqname1 = refmodels.make_sequence_name(project1) + project2 = factories.ProjectFactory.create() + seqname2 = refmodels.make_sequence_name(project2) + + seq.alter(seqname1, 100) + seq.alter(seqname2, 200) + + issue = factories.IssueFactory.create(project=project1) + assert issue.ref == 101 + + issue.subject = "other" + issue.save() + assert issue.ref == 101 + + issue.project = project2 + issue.save() + + assert issue.ref == 201 diff --git a/tests/integration/test_stats.py b/tests/integration/test_stats.py index 97bce3ff..4dfab9e4 100644 --- a/tests/integration/test_stats.py +++ b/tests/integration/test_stats.py @@ -37,19 +37,23 @@ def data(): m.role_points1 = f.RolePointsFactory(role=m.role1, points=m.points1, user_story__project=m.project, - user_story__status=m.open_status) + user_story__status=m.open_status, + user_story__milestone=None) m.role_points2 = f.RolePointsFactory(role=m.role1, points=m.points2, user_story__project=m.project, - user_story__status=m.open_status) + user_story__status=m.open_status, + user_story__milestone=None) m.role_points3 = f.RolePointsFactory(role=m.role1, points=m.points3, user_story__project=m.project, - user_story__status=m.open_status) + user_story__status=m.open_status, + user_story__milestone=None) m.role_points4 = f.RolePointsFactory(role=m.project.roles.all()[0], points=m.points4, user_story__project=m.project, - user_story__status=m.open_status) + user_story__status=m.open_status, + user_story__milestone=None) m.user_story1 = m.role_points1.user_story m.user_story2 = m.role_points2.user_story diff --git a/tests/integration/test_tasks.py b/tests/integration/test_tasks.py index 61d1954a..382bfc6f 100644 --- a/tests/integration/test_tasks.py +++ b/tests/integration/test_tasks.py @@ -54,8 +54,9 @@ def test_create_task_without_status(client): def test_api_update_task_tags(client): - task = f.create_task() - f.MembershipFactory.create(project=task.project, user=task.owner, is_owner=True) + project = f.ProjectFactory.create() + task = f.create_task(project=project, status__project=project, milestone=None, user_story=None) + f.MembershipFactory.create(project=project, user=task.owner, is_owner=True) url = reverse("tasks-detail", kwargs={"pk": task.pk}) data = {"tags": ["back", "front"], "version": task.version} diff --git a/tests/integration/test_userstories.py b/tests/integration/test_userstories.py index e86f1dad..90b85444 100644 --- a/tests/integration/test_userstories.py +++ b/tests/integration/test_userstories.py @@ -152,7 +152,7 @@ def test_update_userstory_points(client): f.PointsFactory.create(project=project, value=1) points3 = f.PointsFactory.create(project=project, value=2) - us = f.UserStoryFactory.create(project=project, owner=user1) + us = f.UserStoryFactory.create(project=project, owner=user1, status__project=project, milestone__project=project) usdata = UserStorySerializer(us).data url = reverse("userstories-detail", args=[us.pk]) @@ -166,7 +166,7 @@ def test_update_userstory_points(client): data["points"].update({'2000': points3.pk}) response = client.json.patch(url, json.dumps(data)) - assert response.status_code == 200 + assert response.status_code == 200, str(response.content) assert response.data["points"] == usdata['points'] # Api should save successful