diff --git a/storyboard/api/v1/branches.py b/storyboard/api/v1/branches.py index 179d3370..21465e56 100644 --- a/storyboard/api/v1/branches.py +++ b/storyboard/api/v1/branches.py @@ -30,6 +30,7 @@ from storyboard.api.v1.search import search_engine from storyboard.api.v1 import validations from storyboard.api.v1 import wmodels from storyboard.common import decorators +from storyboard.common import exception as exc from storyboard.db.api import branches as branches_api from storyboard.openstack.common.gettextutils import _ # noqa @@ -60,7 +61,7 @@ class BranchesController(rest.RestController): if branch: return wmodels.Branch.from_db_model(branch) else: - abort(404, _("Branch %s not found") % branch_id) + raise exc.NotFound(_("Branch %s not found") % branch_id) @decorators.db_exceptions @secure(checks.guest) @@ -146,9 +147,19 @@ class BranchesController(rest.RestController): else: branch_dict["expiration_date"] = None + if branch.project_id: + original_branch = branches_api.branch_get(branch_id) + + if not original_branch: + raise exc.NotFound(_("Branch %s not found") % branch_id) + + if branch.project_id != original_branch.project_id: + abort(400, _("You can't associate branch %s " + "with another project.") % branch_id) + result = branches_api.branch_update(branch_id, branch_dict) if result: return wmodels.Branch.from_db_model(result) else: - abort(404, _("Branch %s not found") % branch_id) + raise exc.NotFound(_("Branch %s not found") % branch_id) diff --git a/storyboard/api/v1/milestones.py b/storyboard/api/v1/milestones.py index 874629df..8bcbab12 100644 --- a/storyboard/api/v1/milestones.py +++ b/storyboard/api/v1/milestones.py @@ -143,6 +143,16 @@ class MilestonesController(rest.RestController): else: milestone_dict["expiration_date"] = None + if milestone.branch_id: + original_milestone = milestones_api.milestone_get(milestone_id) + + if not original_milestone: + raise exc.NotFound(_("Milestone %s not found") % milestone_id) + + if milestone.branch_id != original_milestone.branch_id: + abort(400, _("You can't associate milestone %s " + "with another branch.") % milestone_id) + result = milestones_api.milestone_update(milestone_id, milestone_dict) if result: diff --git a/storyboard/api/v1/tasks.py b/storyboard/api/v1/tasks.py index fb0c5b0b..75851156 100644 --- a/storyboard/api/v1/tasks.py +++ b/storyboard/api/v1/tasks.py @@ -28,6 +28,7 @@ from storyboard.api.v1 import validations from storyboard.api.v1 import wmodels from storyboard.common import decorators from storyboard.common import exception as exc +from storyboard.db.api import branches as branches_api from storyboard.db.api import milestones as milestones_api from storyboard.db.api import tasks as tasks_api from storyboard.db.api import timeline_events as events_api @@ -38,15 +39,133 @@ CONF = cfg.CONF SEARCH_ENGINE = search_engine.get_engine() -def milestone_is_valid(milestone_id): - milestone = milestones_api.milestone_get(milestone_id) +def milestone_is_valid(task): + """Check that milestone exists, milestone and task associated with the + same branch and milestone is not expired. + """ - if not milestone: - raise exc.NotFound(_("Milestone %d not found.") % - milestone_id) + milestone_id = task.milestone_id + milestone = milestones_api.milestone_get(milestone_id) - if milestone['expired']: - abort(400, _("Can't associate task to expired milestone.")) + if not milestone: + raise exc.NotFound(_("Milestone %s not found.") % + milestone_id) + + if milestone['expired']: + abort(400, _("You can't associate task to expired milestone %s.") + % milestone_id) + + if task.branch_id and milestone.branch_id != task.branch_id: + abort(400, _("Milestone %(m_id)s doesn't associate " + "with branch %(b_id)s.") + % {'m_id': milestone_id, 'b_id': task.branch_id}) + + +def branch_is_valid(task): + """Check that branch exists, branch and task associated with the same + project and branch is not expired. + """ + + branch = branches_api.branch_get(task.branch_id) + + if not branch: + raise exc.NotFound(_("Branch %s not found.") % task.branch_id) + + if branch.project_id != task.project_id: + abort(400, _("Branch %(b_id)s doesn't associate with " + "project %(p_id)s.") + % {'b_id': branch.id, 'p_id': task.project_id}) + + if branch["expired"]: + abort(400, _("You can't associate task with expired branch %s.") % + task.branch_id) + + +def task_is_valid_post(task): + """Check that task can be created. + """ + + # Check that task author didn't change creator_id. + if task.creator_id and task.creator_id != request.current_user_id: + abort(400, _("You can't select author of task.")) + + # Check that project_id is in request + if not task.project_id: + abort(400, _("You must select a project for task.")) + + # Set branch_id to 'master' branch defaults and check that + # branch is valid for this task. + if not task.branch_id: + task.branch_id = branches_api.branch_get_master_branch( + task.project_id + ).id + else: + branch_is_valid(task) + + # Check that task status is merged and milestone is valid for this task + # if milestone_id is in request. + if task.milestone_id: + if task.status != 'merged': + abort(400, + _("Milestones can only be associated with merged tasks")) + + milestone_is_valid(task) + + return task + + +def task_is_valid_put(task, original_task): + """Check that task can be update. + """ + + # Check that creator_id of task can't be changed. + if task.creator_id and task.creator_id != original_task.creator_id: + abort(400, _("You can't change author of task.")) + + # Set project_id if it isn't in request. + if not task.project_id: + task.project_id = original_task.project_id + + # Set branch_id if it isn't in request. + if not task.branch_id: + task.branch_id = original_task.branch_id + + # Check that branch is valid for this task. If project_id was changed, + # task will be associated with master branch of this project, because + # client doesn't support branches. + if task.project_id == original_task.project_id: + branch_is_valid(task) + else: + task.branch_id = branches_api.branch_get_master_branch( + task.project_id + ).id + + # Check that task ready to associate with milestone if milestone_id in + # request. + if task.milestone_id: + if original_task.status != 'merged' and task.status != 'merged': + abort(400, + _("Milestones can only be associated with merged tasks")) + + if (original_task.status == 'merged' and + task.status and task.status != 'merged'): + abort(400, + _("Milestones can only be associated with merged tasks")) + elif 'milestone_id' in task.as_dict(omit_unset=True): + return task + + # Set milestone id if task status isn't 'merged' or if original task + # has milestone_id. + if task.status and task.status != 'merged': + task.milestone_id = None + elif not task.milestone_id and original_task.milestone_id: + task.milestone_id = original_task.milestone_id + + # Check that milestone is valid for this task. + if task.milestone_id: + milestone_is_valid(task) + + return task def post_timeline_events(original_task, updated_task): @@ -190,18 +309,10 @@ class TasksPrimaryController(rest.RestController): def post(self, task): """Create a new task. - :param task: A task within the request body. + :param task: a task within the request body. """ - if task.creator_id and task.creator_id != request.current_user_id: - abort(400, _("You can't select author of task.")) - - if task.milestone_id: - if task.status != 'merged': - abort(400, - _("Milestones can only be associated with merged tasks")) - - milestone_is_valid(task.milestone_id) + task = task_is_valid_post(task) creator_id = request.current_user_id task.creator_id = creator_id @@ -227,33 +338,16 @@ class TasksPrimaryController(rest.RestController): original_task = tasks_api.task_get(task_id) - if task.creator_id and task.creator_id != original_task.creator_id: - abort(400, _("You can't change author of task.")) + if not original_task: + raise exc.NotFound(_("Task %s not found.") % task_id) - if task.milestone_id: - if original_task['status'] != 'merged' and task.status != 'merged': - abort(400, - _("Milestones can only be associated with merged tasks")) + task = task_is_valid_put(task, original_task) - if (original_task['status'] == 'merged' and - task.status and task.status != 'merged'): - abort(400, - _("Milestones can only be associated with merged tasks")) + updated_task = tasks_api.task_update(task_id, task.as_dict( + omit_unset=True)) - milestone_is_valid(task.milestone_id) - - task_dict = task.as_dict(omit_unset=True) - - if task.status and task.status != 'merged': - task_dict['milestone_id'] = None - - updated_task = tasks_api.task_update(task_id, task_dict) - - if updated_task: - post_timeline_events(original_task, updated_task) - return wmodels.Task.from_db_model(updated_task) - else: - raise exc.NotFound(_("Task %s not found") % task_id) + post_timeline_events(original_task, updated_task) + return wmodels.Task.from_db_model(updated_task) @decorators.db_exceptions @secure(checks.authenticated) @@ -394,24 +488,16 @@ class TasksNestedController(rest.RestController): :param task: a task within the request body. """ - if task.creator_id and task.creator_id != request.current_user_id: - abort(400, _("You can't select author of task.")) - - creator_id = request.current_user_id - task.creator_id = creator_id - if not task.story_id: task.story_id = story_id if task.story_id != story_id: abort(400, _("URL story_id and task.story_id do not match")) - if task.milestone_id: - if task.status != 'merged': - abort(400, - _("Milestones can only be associated with merged tasks")) + task = task_is_valid_post(task) - milestone_is_valid(task.milestone_id) + creator_id = request.current_user_id + task.creator_id = creator_id created_task = tasks_api.task_create(task.as_dict()) @@ -435,36 +521,19 @@ class TasksNestedController(rest.RestController): original_task = tasks_api.task_get(task_id) + if not original_task: + raise exc.NotFound(_("Task %s not found") % task_id) + if original_task.story_id != story_id: abort(400, _("URL story_id and task.story_id do not match")) - if task.creator_id and task.creator_id != original_task.creator_id: - abort(400, _("You can't change author of task.")) + task = task_is_valid_put(task, original_task) - if task.milestone_id: - if original_task['status'] != 'merged' and task.status != 'merged': - abort(400, - _("Milestones can only be associated with merged tasks")) + updated_task = tasks_api.task_update(task_id, task.as_dict( + omit_unset=True)) - if (original_task['status'] == 'merged' and - task.status and task.status != 'merged'): - abort(400, - _("Milestones can only be associated with merged tasks")) - - milestone_is_valid(task.milestone_id) - - task_dict = task.as_dict(omit_unset=True) - - if task.status and task.status != 'merged': - task_dict['milestone_id'] = None - - updated_task = tasks_api.task_update(task_id, task_dict) - - if updated_task: - post_timeline_events(original_task, updated_task) - return wmodels.Task.from_db_model(updated_task) - else: - raise exc.NotFound(_("Task %s not found") % task_id) + post_timeline_events(original_task, updated_task) + return wmodels.Task.from_db_model(updated_task) @decorators.db_exceptions @secure(checks.authenticated) diff --git a/storyboard/db/api/branches.py b/storyboard/db/api/branches.py index db2922b1..263eaf95 100644 --- a/storyboard/db/api/branches.py +++ b/storyboard/db/api/branches.py @@ -13,8 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from storyboard.common import exception as exc from storyboard.db.api import base as api_base from storyboard.db import models +from storyboard.openstack.common.gettextutils import _ # noqa def branch_get(branch_id): @@ -67,3 +69,14 @@ def branch_build_query(project_group_id, **kwargs): **kwargs) return query + + +def branch_get_master_branch(project_id): + query = api_base.model_query(models.Branch) + query = query.filter_by(project_id=project_id, name='master').first() + + if not query: + raise exc.NotFound(_("Master branch of project %d not found.") + % project_id) + + return query diff --git a/storyboard/db/migration/alembic_migrations/versions/043_fix_tasks.py b/storyboard/db/migration/alembic_migrations/versions/043_fix_tasks.py new file mode 100644 index 00000000..46801950 --- /dev/null +++ b/storyboard/db/migration/alembic_migrations/versions/043_fix_tasks.py @@ -0,0 +1,72 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""This migration fix project_id and branch_id fields in tasks. All tasks +without project id now are assigned to project with the smallest id. All tasks +without branch_id now assigned to masted branch of matching project. + +Revision ID: 043 +Revises: 042 +Create Date: 2015-03-24 13:11:22 + +""" + +# revision identifiers, used by Alembic. + +revision = '043' +down_revision = '042' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql.expression import table + +MYSQL_ENGINE = 'InnoDB' +MYSQL_CHARSET = 'utf8' + + +def upgrade(active_plugins=None, options=None): + bind = op.get_bind() + + branches = list(bind.execute( + sa.select(columns=['id', 'name', 'project_id'], + from_obj=sa.Table('branches', sa.MetaData())))) + + projects = list(bind.execute( + sa.select(columns=['id'], from_obj=sa.Table('projects', + sa.MetaData())))) + if len(projects) > 0: + branch_dict = {} + + for branch in branches: + branch_dict[(branch['project_id'], branch['name'])] = branch['id'] + + tasks_table = table( + 'tasks', + sa.Column('project_id', sa.Integer(), nullable=True), + sa.Column('branch_id', sa.Integer(), nullable=True), + ) + + bind.execute(tasks_table.update(). + where(tasks_table.c.project_id.is_(None)). + values(project_id=projects[0].id)) + + for project in projects: + bind.execute( + tasks_table.update(). + where(tasks_table.c.project_id == project['id']). + values(branch_id=branch_dict[(project['id'], "master")]) + ) + + +def downgrade(active_plugins=None, options=None): + pass diff --git a/storyboard/db/projects_loader.py b/storyboard/db/projects_loader.py index ed844157..0831a7bc 100644 --- a/storyboard/db/projects_loader.py +++ b/storyboard/db/projects_loader.py @@ -22,7 +22,9 @@ import six from sqlalchemy.exc import SADeprecationWarning from storyboard.common.custom_types import NameType +from storyboard.common.master_branch_helper import MasterBranchHelper from storyboard.db.api import base as db_api +from storyboard.db.models import Branch from storyboard.db.models import Project from storyboard.db.models import ProjectGroup from storyboard.openstack.common.gettextutils import _LW # noqa @@ -115,6 +117,14 @@ def _get_project(project, session): session.add(db_project) + master_branch = session.query(Branch).\ + filter_by(name='master', project_id=db_project.id).first() + + if not master_branch: + master_branch = Branch() + master_branch.update(MasterBranchHelper(db_project.id).as_dict()) + session.add(master_branch) + return db_project diff --git a/storyboard/tests/api/test_branches.py b/storyboard/tests/api/test_branches.py index bb6985eb..5f3a23c5 100644 --- a/storyboard/tests/api/test_branches.py +++ b/storyboard/tests/api/test_branches.py @@ -38,7 +38,7 @@ class TestBranches(base.FunctionalTest): } self.put_branch_01 = { - 'project_id': 2 + 'name': 'new_branch_name' } self.put_branch_02 = { @@ -54,11 +54,6 @@ class TestBranches(base.FunctionalTest): 'expiration_date': '2014-01-01T00:00:00+00:00' } - self.project_01 = { - 'name': 'project-for-put', - 'description': 'test_description' - } - self.default_headers['Authorization'] = 'Bearer valid_superuser_token' def test_create(self): @@ -84,20 +79,11 @@ class TestBranches(base.FunctionalTest): self.assertIn("id", branch) resource = "".join([self.resource, ("/%d" % branch['id'])]) - response_project = self.post_json('/projects', self.project_01) - project = response_project.json - - self.assertEqual(self.project_01['name'], project['name']) - self.assertEqual(self.project_01['description'], - project['description']) - self.assertIn("id", project) - self.put_branch_01["id"] = project["id"] - response = self.put_json(resource, self.put_branch_01) branch = response.json - self.assertEqual(branch['name'], self.branch_01['name']) + self.assertEqual(branch['name'], self.put_branch_01['name']) self.assertEqual(branch['project_id'], - self.put_branch_01['project_id']) + self.branch_01['project_id']) response = self.put_json(resource, self.put_branch_02) branch = response.json @@ -126,6 +112,18 @@ class TestBranches(base.FunctionalTest): expect_errors=True) self.assertEqual(response.status_code, 400) + def test_change_project(self): + response = self.post_json(self.resource, self.branch_01) + branch = response.json + self.assertIn("id", branch) + self.assertEqual(branch['name'], self.branch_01['name']) + self.assertEqual(branch['project_id'], self.branch_01['project_id']) + resource = "".join([self.resource, ("/%d" % branch['id'])]) + + response = self.put_json(resource, {'project_id': 2}, + expect_errors=True) + self.assertEqual(400, response.status_code) + def test_get_one(self): response = self.post_json(self.resource, self.branch_01) branch = response.json diff --git a/storyboard/tests/api/test_jsonschema.py b/storyboard/tests/api/test_jsonschema.py index 3eacfffb..dcb6e4b2 100644 --- a/storyboard/tests/api/test_jsonschema.py +++ b/storyboard/tests/api/test_jsonschema.py @@ -357,21 +357,25 @@ class TestTasks(base.FunctionalTest): self.task_01 = { 'title': 'jsonschema_task_01', - 'story_id': 1 + 'story_id': 1, + 'project_id': 1 } self.task_02 = { 'title': 'ts', - 'story_id': 1 + 'story_id': 1, + 'project_id': 1 } self.task_03 = { 'title': LONG_STRING, - 'story_id': 1 + 'story_id': 1, + 'project_id': 1 } self.task_04 = { - 'story_id': 1 + 'story_id': 1, + 'project_id': 1 } self.put_task_01 = { diff --git a/storyboard/tests/api/test_milestones.py b/storyboard/tests/api/test_milestones.py index 35ec7b42..2f3c8e64 100644 --- a/storyboard/tests/api/test_milestones.py +++ b/storyboard/tests/api/test_milestones.py @@ -22,7 +22,7 @@ class TestMilestones(base.FunctionalTest): self.resource = '/milestones' self.milestone_01 = { - 'name': 'test_milestone_01', + 'name': 'test_milestone_1', 'branch_id': 1 } @@ -38,7 +38,7 @@ class TestMilestones(base.FunctionalTest): } self.put_milestone_01 = { - 'branch_id': 2 + 'name': 'new_milestone_name' } self.put_milestone_02 = { @@ -83,9 +83,9 @@ class TestMilestones(base.FunctionalTest): response = self.put_json(resource, self.put_milestone_01) milestone = response.json - self.assertEqual(milestone['name'], self.milestone_01['name']) + self.assertEqual(milestone['name'], self.put_milestone_01['name']) self.assertEqual(milestone['branch_id'], - self.put_milestone_01['branch_id']) + self.milestone_01['branch_id']) response = self.put_json(resource, self.put_milestone_02) milestone = response.json @@ -115,6 +115,19 @@ class TestMilestones(base.FunctionalTest): expect_errors=True) self.assertEqual(response.status_code, 400) + def test_change_branch(self): + response = self.post_json(self.resource, self.milestone_01) + milestone = response.json + self.assertIn("id", milestone) + self.assertEqual(milestone['name'], self.milestone_01['name']) + self.assertEqual(milestone['branch_id'], + self.milestone_01['branch_id']) + resource = "".join([self.resource, ("/%d" % milestone['id'])]) + + response = self.put_json(resource, {'branch_id': 2}, + expect_errors=True) + self.assertEqual(400, response.status_code) + def test_get_one(self): response = self.post_json(self.resource, self.milestone_01) milestone = response.json diff --git a/storyboard/tests/api/test_tasks.py b/storyboard/tests/api/test_tasks.py index cfef9684..57e0fd77 100644 --- a/storyboard/tests/api/test_tasks.py +++ b/storyboard/tests/api/test_tasks.py @@ -21,32 +21,200 @@ class TestTasksPrimary(base.FunctionalTest): def setUp(self): super(TestTasksPrimary, self).setUp() self.resource = '/tasks' + self.default_headers['Authorization'] = 'Bearer valid_superuser_token' self.task_01 = { 'title': 'StoryBoard', 'status': 'todo', - 'story_id': 1 + 'story_id': 1, + 'project_id': 1 + } + + self.task_02 = { + 'title': 'StoryBoard', + 'status': 'merged', + 'story_id': 1, + 'project_id': 1, + 'branch_id': 1, + 'milestone_id': 1 + } + + self.task_03 = { + 'title': 'StoryBoard', + 'status': 'todo', + 'story_id': 1, + 'project_id': 1, + 'branch_id': 2 + } + + self.task_04 = { + 'title': 'StoryBoard', + 'status': 'merged', + 'story_id': 1, + 'project_id': 1, + 'branch_id': 1, + 'milestone_id': 2 + } + + self.task_05 = { + 'title': 'StoryBoard', + 'status': 'todo', + 'story_id': 1, + 'project_id': 1, + 'branch_id': 1, + 'milestone_id': 1 + } + + self.updated_task_01 = { + 'title': 'StoryBoardUpdated' + } + + self.updated_task_02 = { + 'project_id': 2, + 'branch_id': 2 + } + + self.updated_task_03 = { + 'project_id': 2 + } + + self.updated_task_04 = { + 'branch_id': 2 + } + + self.updated_task_05 = { + 'milestone_id': 2 + } + + self.updated_task_06 = { + 'milestone_id': 1 + } + + self.updated_task_07 = { + 'project_id': 1, + 'branch_id': 1 + } + + self.updated_task_08 = { + 'project_id': 2, + 'branch_id': 2, + } + + self.helper_branch_01 = { + 'name': 'test_branch_01', + 'project_id': 1 } - self.default_headers['Authorization'] = 'Bearer valid_superuser_token' def test_tasks_endpoint(self): response = self.get_json(self.resource) self.assertEqual(4, len(response)) def test_create(self): - result = self.post_json(self.resource, { - 'title': 'StoryBoard', - 'status': 'todo', - 'story_id': 1 - }) + result = self.post_json(self.resource, self.task_01) # Retrieve the created task created_task = self \ .get_json('%s/%d' % (self.resource, result.json['id'])) - self.assertEqual(result.json['id'], created_task['id']) - self.assertEqual('StoryBoard', created_task['title']) - self.assertEqual('todo', created_task['status']) - self.assertEqual(1, created_task['story_id']) + self.assertEqual(self.task_01['title'], created_task['title']) + self.assertEqual(self.task_01['status'], created_task['status']) + self.assertEqual(self.task_01['story_id'], created_task['story_id']) + self.assertEqual(self.task_01['project_id'], + created_task['project_id']) + self.assertEqual(1, created_task['branch_id']) + + def test_create_merged_task(self): + response = self.post_json(self.resource, self.task_02) + created_task = response.json + + self.assertEqual(self.task_02['title'], created_task['title']) + self.assertEqual(self.task_02['status'], created_task['status']) + self.assertEqual(self.task_02['story_id'], created_task['story_id']) + self.assertEqual(self.task_02['project_id'], + created_task['project_id']) + self.assertEqual(self.task_02['branch_id'], created_task['branch_id']) + self.assertEqual(self.task_02['milestone_id'], + created_task['milestone_id']) + + def test_create_invalid_associations(self): + response = self.post_json(self.resource, self.task_03, + expect_errors=True) + self.assertEqual(400, response.status_code) + + response = self.post_json(self.resource, self.task_04, + expect_errors=True) + self.assertEqual(400, response.status_code) + + def test_create_invalid_expired(self): + response = self.put_json('/branches/1', {'expired': True}) + branch = response.json + self.assertTrue(branch['expired']) + response = self.post_json(self.resource, self.task_02, + expect_errors=True) + self.assertEqual(400, response.status_code) + + response = self.post_json(self.resource, self.task_02, + expect_errors=True) + self.assertEqual(400, response.status_code) + + def test_create_invalid_with_milestone(self): + response = self.post_json(self.resource, self.task_05, + expect_errors=True) + self.assertEqual(400, response.status_code) + + def test_task_update(self): + response = self.put_json(self.resource + '/1', self.updated_task_01) + updated_task = response.json + self.assertEqual(self.updated_task_01['title'], updated_task['title']) + + def test_task_update_another_project(self): + response = self.put_json(self.resource + '/1', self.updated_task_02) + updated_task = response.json + self.assertEqual(self.updated_task_02['project_id'], + updated_task['project_id']) + self.assertEqual(self.updated_task_02['branch_id'], + updated_task['branch_id']) + + def test_task_update_invalid_associations(self): + response = self.put_json(self.resource + '/1', self.updated_task_04, + expect_errors=True) + self.assertEqual(400, response.status_code) + + response = self.put_json(self.resource + '/2', self.updated_task_05) + updated_task = response.json + self.assertEqual(self.updated_task_05['milestone_id'], + updated_task['milestone_id']) + + response = self.put_json(self.resource + '/2', self.updated_task_06, + expect_errors=True) + self.assertEqual(400, response.status_code) + + response = self.put_json(self.resource + '/2', self.updated_task_07, + expect_errors=True) + self.assertEqual(400, response.status_code) + + def test_task_update_invalid_expired(self): + response = self.post_json('/branches', {'name': 'expired_branch', + 'project_id': 1}) + branch = response.json + branch_id = branch['id'] + + response = self.put_json('/branches/%s' % branch_id, {'expired': True}) + branch = response.json + + self.assertEqual(True, branch['expired']) + + response = self.put_json(self.resource + '/1', + {'branch_id': branch_id}, + expect_errors=True) + self.assertEqual(400, response.status_code) + + response = self.put_json('/milestones/1', {'expired': True}) + milestone = response.json + self.assertTrue(milestone['expired']) + + response = self.put_json(self.resource + '/1', self.updated_task_06, + expect_errors=True) + self.assertEqual(400, response.status_code) class TestTasksNestedController(base.FunctionalTest): @@ -57,6 +225,7 @@ class TestTasksNestedController(base.FunctionalTest): self.task_01 = { 'title': 'StoryBoard', 'status': 'todo', + 'project_id': 1, 'story_id': 1 } self.default_headers['Authorization'] = 'Bearer valid_superuser_token' @@ -81,6 +250,7 @@ class TestTasksNestedController(base.FunctionalTest): result = self.post_json(self.resource, { 'title': 'StoryBoard', 'status': 'todo', + 'project_id': 1, 'story_id': 1 }) @@ -90,12 +260,14 @@ class TestTasksNestedController(base.FunctionalTest): self.assertEqual(result.json['id'], created_task['id']) self.assertEqual('StoryBoard', created_task['title']) self.assertEqual('todo', created_task['status']) + self.assertEqual(1, created_task['project_id']) self.assertEqual(1, created_task['story_id']) def test_create_id_in_url(self): result = self.post_json(self.resource, { 'title': 'StoryBoard', 'status': 'todo', + 'project_id': 1 }) # story_id is not set in the body. URL should handle that @@ -105,6 +277,7 @@ class TestTasksNestedController(base.FunctionalTest): self.assertEqual(result.json['id'], created_task['id']) self.assertEqual('StoryBoard', created_task['title']) self.assertEqual('todo', created_task['status']) + self.assertEqual(1, created_task['project_id']) self.assertEqual(1, created_task['story_id']) def test_create_error(self): diff --git a/storyboard/tests/mock_data.py b/storyboard/tests/mock_data.py index 3a3bb7cd..b633ee94 100644 --- a/storyboard/tests/mock_data.py +++ b/storyboard/tests/mock_data.py @@ -20,6 +20,7 @@ from storyboard.db.api import base as db from storyboard.db.models import AccessToken from storyboard.db.models import Branch from storyboard.db.models import Comment +from storyboard.db.models import Milestone from storyboard.db.models import Project from storyboard.db.models import ProjectGroup from storyboard.db.models import Story @@ -161,6 +162,7 @@ def load(): status='inprogress', story_id=1, project_id=1, + branch_id=1, assignee_id=2, priority='medium' ), @@ -171,6 +173,7 @@ def load(): status='merged', story_id=1, project_id=2, + branch_id=2, assignee_id=1, priority='high' ), @@ -181,6 +184,7 @@ def load(): status='invalid', story_id=1, project_id=3, + branch_ud=3, assignee_id=1, priority='low' ), @@ -191,6 +195,7 @@ def load(): status='merged', story_id=2, project_id=2, + branch_id=2, assignee_id=1, priority='medium' ) @@ -308,6 +313,18 @@ def load(): ) ]) + # Load some milestones + load_data([ + Milestone( + name='test_milestone_01', + branch_id=1 + ), + Milestone( + name='test_milestone_02', + branch_id=2 + ) + ]) + def load_data(data): """Pre load test data into the database.