Added milestones
Added table for milestones, field milestone_id in tasks table. Added controller for milestones. Added models for milestones. To db api added milestones. Added tests for patch. Tasks controller was modified. Change-Id: I857d36a9d579d04f6938327e48243ddd5b4d0569
This commit is contained in:
committed by
Thierry Carrez
parent
ee7a86c03f
commit
24314240c3
153
storyboard/api/v1/milestones.py
Normal file
153
storyboard/api/v1/milestones.py
Normal file
@@ -0,0 +1,153 @@
|
||||
# Copyright (c) 2015 Mirantis Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
|
||||
from oslo.config import cfg
|
||||
from pecan import abort
|
||||
from pecan import response
|
||||
from pecan import rest
|
||||
from pecan.secure import secure
|
||||
import six
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from storyboard.api.auth import authorization_checks as checks
|
||||
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 milestones as milestones_api
|
||||
from storyboard.openstack.common.gettextutils import _ # noqa
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
SEARCH_ENGINE = search_engine.get_engine()
|
||||
|
||||
|
||||
class MilestonesController(rest.RestController):
|
||||
"""REST controller for milestones.
|
||||
"""
|
||||
|
||||
_custom_actions = {"search": ["GET"]}
|
||||
|
||||
validation_post_schema = validations.MILESTONES_POST_SCHEMA
|
||||
validation_put_schema = validations.MILESTONES_PUT_SCHEMA
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose(wmodels.Milestone, int)
|
||||
def get_one(self, milestone_id):
|
||||
"""Retrieve information about the given milestone.
|
||||
|
||||
:param milestone_id: milestone ID.
|
||||
"""
|
||||
|
||||
milestones = milestones_api.milestone_get(milestone_id)
|
||||
|
||||
if milestones:
|
||||
return wmodels.Milestone.from_db_model(milestones)
|
||||
else:
|
||||
raise exc.NotFound(_("Milestone %s not found") % milestone_id)
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose([wmodels.Milestone], int, int, wtypes.text, int,
|
||||
wtypes.text, wtypes.text)
|
||||
def get_all(self, marker=None, limit=None, name=None, branch_id=None,
|
||||
sort_field='id', sort_dir='asc'):
|
||||
"""Retrieve a list of milestones.
|
||||
|
||||
:param marker: The resource id where the page should begin.
|
||||
:param limit: The number of milestones to retrieve.
|
||||
:param name: Filter milestones based on name.
|
||||
:param branch_id: Filter milestones based on branch_id.
|
||||
:param sort_field: The name of the field to sort on.
|
||||
:param sort_dir: sort direction for results (asc, desc).
|
||||
"""
|
||||
# Boundary check on limit.
|
||||
if limit is None:
|
||||
limit = CONF.page_size_default
|
||||
limit = min(CONF.page_size_maximum, max(1, limit))
|
||||
|
||||
# Resolve the marker record.
|
||||
marker_milestone = milestones_api.milestone_get(marker)
|
||||
|
||||
milestones = \
|
||||
milestones_api.milestone_get_all(marker=marker_milestone,
|
||||
limit=limit,
|
||||
name=name,
|
||||
branch_id=branch_id,
|
||||
sort_field=sort_field,
|
||||
sort_dir=sort_dir)
|
||||
milestones_count = \
|
||||
milestones_api.milestone_get_count(
|
||||
name=name,
|
||||
branch_id=branch_id)
|
||||
|
||||
# Apply the query response headers.
|
||||
response.headers['X-Limit'] = str(limit)
|
||||
response.headers['X-Total'] = str(milestones_count)
|
||||
if marker_milestone:
|
||||
response.headers['X-Marker'] = str(marker_milestone.id)
|
||||
|
||||
return [wmodels.Milestone.from_db_model(m) for m in milestones]
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.superuser)
|
||||
@wsme_pecan.wsexpose(wmodels.Milestone, body=wmodels.Milestone)
|
||||
def post(self, milestone):
|
||||
"""Create a new milestone.
|
||||
|
||||
:param milestone: a milestone within the request body.
|
||||
"""
|
||||
|
||||
# we can't create expired milestones
|
||||
if milestone.expiration_date or milestone.expired:
|
||||
abort(400, _("Can't create expired milestone."))
|
||||
|
||||
result = milestones_api.milestone_create(milestone.as_dict())
|
||||
return wmodels.Milestone.from_db_model(result)
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.superuser)
|
||||
@wsme_pecan.wsexpose(wmodels.Milestone, int, body=wmodels.Milestone)
|
||||
def put(self, milestone_id, milestone):
|
||||
"""Modify this milestone.
|
||||
|
||||
:param milestone_id: An ID of the milestone.
|
||||
:param milestone: a milestone within the request body.
|
||||
"""
|
||||
|
||||
milestone_dict = milestone.as_dict(omit_unset=True)
|
||||
|
||||
if milestone.expiration_date:
|
||||
abort(400, _("Can't change expiration date."))
|
||||
|
||||
if "expired" in six.iterkeys(milestone_dict):
|
||||
if milestone_dict["expired"]:
|
||||
milestone_dict["expiration_date"] = datetime.now(tz=pytz.utc)
|
||||
else:
|
||||
milestone_dict["expiration_date"] = None
|
||||
|
||||
result = milestones_api.milestone_update(milestone_id, milestone_dict)
|
||||
|
||||
if result:
|
||||
return wmodels.Milestone.from_db_model(result)
|
||||
else:
|
||||
raise exc.NotFound(_("Milestone %s not found") % milestone_id)
|
||||
@@ -4,7 +4,7 @@
|
||||
# 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
|
||||
# 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,
|
||||
@@ -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 milestones as milestones_api
|
||||
from storyboard.db.api import tasks as tasks_api
|
||||
from storyboard.db.api import timeline_events as events_api
|
||||
from storyboard.openstack.common.gettextutils import _ # noqa
|
||||
@@ -63,12 +64,12 @@ class TasksController(rest.RestController):
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.guest)
|
||||
@wsme_pecan.wsexpose([wmodels.Task], wtypes.text, int, int, int, int, int,
|
||||
[wtypes.text], [wtypes.text], int, int, wtypes.text,
|
||||
wtypes.text)
|
||||
int, [wtypes.text], [wtypes.text], int, int,
|
||||
wtypes.text, wtypes.text)
|
||||
def get_all(self, title=None, story_id=None, assignee_id=None,
|
||||
project_id=None, project_group_id=None, branch_id=None,
|
||||
status=None, priority=None, marker=None, limit=None,
|
||||
sort_field='id', sort_dir='asc'):
|
||||
milestone_id=None, status=None, priority=None, marker=None,
|
||||
limit=None, sort_field='id', sort_dir='asc'):
|
||||
"""Retrieve definitions of all of the tasks.
|
||||
|
||||
:param title: search by task title.
|
||||
@@ -77,6 +78,7 @@ class TasksController(rest.RestController):
|
||||
:param project_id: filter the tasks based on project.
|
||||
:param project_group_id: filter tasks based on project group.
|
||||
:param branch_id: filter tasks based on branch_id.
|
||||
:param milestone_id: filter tasks based on milestone.
|
||||
:param status: filter tasks by status.
|
||||
:param priority: filter tasks by priority.
|
||||
:param marker: The resource id where the page should begin.
|
||||
@@ -100,6 +102,7 @@ class TasksController(rest.RestController):
|
||||
project_id=project_id,
|
||||
project_group_id=project_group_id,
|
||||
branch_id=branch_id,
|
||||
milestone_id=milestone_id,
|
||||
status=status,
|
||||
priority=priority,
|
||||
sort_field=sort_field,
|
||||
@@ -113,6 +116,7 @@ class TasksController(rest.RestController):
|
||||
project_id=project_id,
|
||||
project_group_id=project_group_id,
|
||||
branch_id=branch_id,
|
||||
milestone_id=milestone_id,
|
||||
status=status,
|
||||
priority=priority)
|
||||
|
||||
@@ -124,6 +128,16 @@ class TasksController(rest.RestController):
|
||||
|
||||
return [wmodels.Task.from_db_model(s) for s in tasks]
|
||||
|
||||
def _milestone_is_valid(self, milestone_id):
|
||||
milestone = milestones_api.milestone_get(milestone_id)
|
||||
|
||||
if not milestone:
|
||||
raise exc.NotFound(_("Milestone %d not found.") %
|
||||
milestone_id)
|
||||
|
||||
if milestone['expired']:
|
||||
abort(400, _("Can't associate task to expired milestone."))
|
||||
|
||||
@decorators.db_exceptions
|
||||
@secure(checks.authenticated)
|
||||
@wsme_pecan.wsexpose(wmodels.Task, body=wmodels.Task)
|
||||
@@ -136,6 +150,13 @@ class TasksController(rest.RestController):
|
||||
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"))
|
||||
|
||||
self._milestone_is_valid(task.milestone_id)
|
||||
|
||||
creator_id = request.current_user_id
|
||||
task.creator_id = creator_id
|
||||
|
||||
@@ -166,6 +187,25 @@ class TasksController(rest.RestController):
|
||||
updated_task = tasks_api.task_update(task_id,
|
||||
task.as_dict(omit_unset=True))
|
||||
|
||||
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"))
|
||||
|
||||
self._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:
|
||||
self._post_timeline_events(original_task, updated_task)
|
||||
return wmodels.Task.from_db_model(updated_task)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
from storyboard.api.v1.auth import AuthController
|
||||
from storyboard.api.v1.branches import BranchesController
|
||||
from storyboard.api.v1.milestones import MilestonesController
|
||||
from storyboard.api.v1.project_groups import ProjectGroupsController
|
||||
from storyboard.api.v1.projects import ProjectsController
|
||||
from storyboard.api.v1.stories import StoriesController
|
||||
@@ -35,6 +36,7 @@ class V1Controller(object):
|
||||
users = UsersController()
|
||||
teams = TeamsController()
|
||||
branches = BranchesController()
|
||||
milestones = MilestonesController()
|
||||
stories = StoriesController()
|
||||
tags = TagsController()
|
||||
tasks = TasksController()
|
||||
|
||||
@@ -181,6 +181,21 @@ BRANCHES_PUT_SCHEMA = {
|
||||
BRANCHES_POST_SCHEMA = copy.deepcopy(BRANCHES_PUT_SCHEMA)
|
||||
BRANCHES_POST_SCHEMA["required"] = ["name"]
|
||||
|
||||
MILESTONES_PUT_SCHEMA = {
|
||||
"name": "milestone_schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": CommonLength.lower_middle_length,
|
||||
"maxLength": CommonLength.top_middle_length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MILESTONES_POST_SCHEMA = copy.deepcopy(MILESTONES_PUT_SCHEMA)
|
||||
MILESTONES_POST_SCHEMA["required"] = ["name"]
|
||||
|
||||
STORY_TAGS_PUT_SCHEMA = {
|
||||
"name": "storyTag_schema",
|
||||
"type": "object",
|
||||
|
||||
@@ -203,6 +203,9 @@ class Task(base.APIBase):
|
||||
branch_id = int
|
||||
"""The ID of corresponding Branch"""
|
||||
|
||||
milestone_id = int
|
||||
"""The ID of corresponding Milestone"""
|
||||
|
||||
|
||||
class Branch(base.APIBase):
|
||||
"""Represents a branch."""
|
||||
@@ -238,6 +241,34 @@ class Branch(base.APIBase):
|
||||
)
|
||||
|
||||
|
||||
class Milestone(base.APIBase):
|
||||
"""Represents a milestone."""
|
||||
|
||||
name = wtypes.text
|
||||
"""The milestone unique name. This name will be displayed in the URL.
|
||||
At least 3 alphanumeric symbols.
|
||||
"""
|
||||
|
||||
branch_id = int
|
||||
"""The ID of the corresponding Branch."""
|
||||
|
||||
expired = bool
|
||||
"""a binary flag that marks milestones that should no longer be
|
||||
selectable in completed tasks."""
|
||||
|
||||
expiration_date = datetime
|
||||
"""Last date the expired flag was switched to True."""
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
return cls(
|
||||
name="Storyboard-milestone",
|
||||
branch_id=1,
|
||||
expired=True,
|
||||
expiration_date=datetime(2015, 1, 1, 1, 1)
|
||||
)
|
||||
|
||||
|
||||
class Team(base.APIBase):
|
||||
"""The Team is a group od Users with a fixed set of permissions.
|
||||
"""
|
||||
|
||||
43
storyboard/db/api/milestones.py
Normal file
43
storyboard/db/api/milestones.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Copyright (c) 2015 Mirantis Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from storyboard.db.api import base as api_base
|
||||
from storyboard.db import models
|
||||
|
||||
|
||||
def milestone_get(milestone_id):
|
||||
return api_base.entity_get(models.Milestone, milestone_id)
|
||||
|
||||
|
||||
def milestone_get_all(marker=None, limit=None, sort_field=None, sort_dir=None,
|
||||
**kwargs):
|
||||
return api_base.entity_get_all(models.Milestone,
|
||||
marker=marker,
|
||||
limit=limit,
|
||||
sort_field=sort_field,
|
||||
sort_dir=sort_dir,
|
||||
**kwargs)
|
||||
|
||||
|
||||
def milestone_get_count(**kwargs):
|
||||
return api_base.entity_get_count(models.Milestone, **kwargs)
|
||||
|
||||
|
||||
def milestone_create(values):
|
||||
return api_base.entity_create(models.Milestone, values)
|
||||
|
||||
|
||||
def milestone_update(milestone_id, values):
|
||||
return api_base.entity_update(models.Milestone, milestone_id, values)
|
||||
@@ -0,0 +1,54 @@
|
||||
# Copyright (c) 2015 Mirantis Inc.
|
||||
#
|
||||
# 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 adds milestones table.
|
||||
|
||||
Revision ID: 038
|
||||
Revises: 037
|
||||
Create Date: 2015-01-28 15:26:34.622503
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
|
||||
revision = '038'
|
||||
down_revision = '037'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
MYSQL_ENGINE = 'InnoDB'
|
||||
MYSQL_CHARSET = 'utf8'
|
||||
|
||||
|
||||
def upgrade(active_plugins=None, options=None):
|
||||
op.create_table(
|
||||
'milestones',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(100), nullable=True),
|
||||
sa.Column('branch_id', sa.Integer(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('expired', sa.Boolean(), default=False, nullable=True),
|
||||
sa.Column('expiration_date', sa.DateTime(), default=None),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('name', 'branch_id', name="milestone_un_constr"),
|
||||
mysql_engine=MYSQL_ENGINE,
|
||||
mysql_charset=MYSQL_CHARSET
|
||||
)
|
||||
|
||||
|
||||
def downgrade(active_plugins=None, options=None):
|
||||
op.drop_table('milestones')
|
||||
@@ -0,0 +1,41 @@
|
||||
# Copyright (c) 2015 Mirantis Inc.
|
||||
#
|
||||
# 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 adds new column for milestone id.
|
||||
|
||||
Revision ID: 039
|
||||
Revises: 038
|
||||
Create Date: 2015-01-27 13:17:34.622503
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
|
||||
revision = '039'
|
||||
down_revision = '038'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade(active_plugins=None, options=None):
|
||||
op.add_column(
|
||||
'tasks',
|
||||
sa.Column('milestone_id', sa.Integer(), nullable=True)
|
||||
)
|
||||
|
||||
|
||||
def downgrade(active_plugins=None, options=None):
|
||||
op.drop_column('tasks', 'milestone_id')
|
||||
@@ -304,10 +304,12 @@ class Task(FullText, ModelBuilder, Base):
|
||||
project_id = Column(Integer, ForeignKey('projects.id'))
|
||||
assignee_id = Column(Integer, ForeignKey('users.id'), nullable=True)
|
||||
branch_id = Column(Integer, ForeignKey('branches.id'), nullable=True)
|
||||
milestone_id = Column(Integer, ForeignKey('milestones.id'), nullable=True)
|
||||
priority = Column(Enum(*_TASK_PRIORITIES), default='medium')
|
||||
|
||||
_public_fields = ["id", "creator_id", "title", "status", "story_id",
|
||||
"project_id", "assignee_id", "priority", "branch_id"]
|
||||
"project_id", "assignee_id", "priority", "branch_id",
|
||||
"milestone_id"]
|
||||
|
||||
|
||||
class Branch(FullText, ModelBuilder, Base):
|
||||
@@ -329,6 +331,24 @@ class Branch(FullText, ModelBuilder, Base):
|
||||
"expiration_date", "autocreated"]
|
||||
|
||||
|
||||
class Milestone(FullText, ModelBuilder, Base):
|
||||
__tablename__ = 'milestones'
|
||||
|
||||
__table_args__ = (
|
||||
schema.UniqueConstraint('name', 'branch_id',
|
||||
name='milestone_un_constr'),
|
||||
)
|
||||
|
||||
__fulltext_columns__ = ['name']
|
||||
|
||||
name = Column(String(CommonLength.top_middle_length))
|
||||
branch_id = Column(Integer, ForeignKey('branches.id'))
|
||||
expired = Column(Boolean, default=False)
|
||||
expiration_date = Column(UTCDateTime, default=None)
|
||||
|
||||
_public_fields = ["id", "name", "branch_id", "expired", "expiration_date"]
|
||||
|
||||
|
||||
class StoryTag(ModelBuilder, Base):
|
||||
__tablename__ = 'storytags'
|
||||
__table_args__ = (
|
||||
|
||||
@@ -33,6 +33,7 @@ class_mappings = {'task': [models.Task, wmodels.Task],
|
||||
'team': [models.Team, wmodels.Team],
|
||||
'story': [models.Story, wmodels.Story],
|
||||
'branch': [models.Branch, wmodels.Branch],
|
||||
'milestone': [models.Milestone, wmodels.Milestone],
|
||||
'tag': [models.StoryTag, wmodels.Tag]}
|
||||
|
||||
|
||||
@@ -142,6 +143,7 @@ class NotificationHook(hooks.PecanHook):
|
||||
'project_groups': 'project_group',
|
||||
'tasks': 'task',
|
||||
'branches': 'branch',
|
||||
'milestones': 'milestone',
|
||||
'timeline_events': 'timeline_event',
|
||||
'users': 'user',
|
||||
'teams': 'team',
|
||||
|
||||
133
storyboard/tests/api/test_milestones.py
Normal file
133
storyboard/tests/api/test_milestones.py
Normal file
@@ -0,0 +1,133 @@
|
||||
# Copyright (c) 2015 Mirantis Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from storyboard.tests import base
|
||||
|
||||
|
||||
class TestMilestones(base.FunctionalTest):
|
||||
def setUp(self):
|
||||
super(TestMilestones, self).setUp()
|
||||
|
||||
self.resource = '/milestones'
|
||||
|
||||
self.milestone_01 = {
|
||||
'name': 'test_milestone_01',
|
||||
'branch_id': 1
|
||||
}
|
||||
|
||||
self.milestone_02 = {
|
||||
'name': 'test_milestone_02',
|
||||
'branch_id': 100
|
||||
}
|
||||
|
||||
self.milestone_03 = {
|
||||
'name': 'test_milestone_03',
|
||||
'branch_id': 1,
|
||||
'expiration_date': '2014-01-01T00:00:00+00:00'
|
||||
}
|
||||
|
||||
self.put_milestone_01 = {
|
||||
'branch_id': 2
|
||||
}
|
||||
|
||||
self.put_milestone_02 = {
|
||||
'expired': True
|
||||
}
|
||||
|
||||
self.put_milestone_03 = {
|
||||
'expired': False
|
||||
}
|
||||
|
||||
self.put_milestone_04 = {
|
||||
'expired': False,
|
||||
'expiration_date': '2014-01-01T00:00:00+00:00'
|
||||
}
|
||||
|
||||
self.default_headers['Authorization'] = 'Bearer valid_superuser_token'
|
||||
|
||||
def test_create(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'])
|
||||
self.assertEqual(milestone['expired'], False)
|
||||
self.assertIsNone(milestone['expiration_date'])
|
||||
|
||||
def test_create_invalid(self):
|
||||
response = self.post_json(self.resource, self.milestone_03,
|
||||
expect_errors=True)
|
||||
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_update(self):
|
||||
response = self.post_json(self.resource, self.milestone_01)
|
||||
milestone = response.json
|
||||
self.assertEqual(milestone['name'], self.milestone_01['name'])
|
||||
self.assertEqual(milestone['branch_id'],
|
||||
self.milestone_01['branch_id'])
|
||||
self.assertIn("id", milestone)
|
||||
resource = "".join([self.resource, ("/%d" % milestone['id'])])
|
||||
|
||||
response = self.put_json(resource, self.put_milestone_01)
|
||||
milestone = response.json
|
||||
self.assertEqual(milestone['name'], self.milestone_01['name'])
|
||||
self.assertEqual(milestone['branch_id'],
|
||||
self.put_milestone_01['branch_id'])
|
||||
|
||||
response = self.put_json(resource, self.put_milestone_02)
|
||||
milestone = response.json
|
||||
self.assertEqual(milestone['expired'], True)
|
||||
self.assertIsNotNone(milestone['expiration_date'])
|
||||
|
||||
response = self.put_json(resource, self.put_milestone_03)
|
||||
milestone = response.json
|
||||
self.assertEqual(milestone['expired'], False)
|
||||
self.assertIsNone(milestone['expiration_date'])
|
||||
|
||||
def test_update_expiration_date(self):
|
||||
response = self.post_json(self.resource, self.milestone_01)
|
||||
milestone = response.json
|
||||
self.assertEqual(milestone['name'], self.milestone_01['name'])
|
||||
self.assertEqual(milestone['branch_id'],
|
||||
self.milestone_01['branch_id'])
|
||||
self.assertIn("id", milestone)
|
||||
resource = "".join([self.resource, ("/%d" % milestone['id'])])
|
||||
|
||||
response = self.put_json(resource, self.put_milestone_02)
|
||||
milestone = response.json
|
||||
self.assertEqual(milestone['expired'], True)
|
||||
self.assertIsNotNone(milestone['expiration_date'])
|
||||
|
||||
response = self.put_json(resource, self.put_milestone_04,
|
||||
expect_errors=True)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_get_one(self):
|
||||
response = self.post_json(self.resource, self.milestone_01)
|
||||
milestone = response.json
|
||||
resource = "".join([self.resource, ("/%d" % milestone['id'])])
|
||||
|
||||
milestone = self.get_json(path=resource)
|
||||
self.assertEqual(milestone['name'], self.milestone_01['name'])
|
||||
self.assertEqual(milestone['branch_id'],
|
||||
self.milestone_01['branch_id'])
|
||||
self.assertEqual(milestone['expired'], False)
|
||||
self.assertIsNone(milestone['expiration_date'])
|
||||
|
||||
def test_get_invalid(self):
|
||||
resource = "".join([self.resource, "/1000"])
|
||||
response = self.get_json(path=resource, expect_errors=True)
|
||||
self.assertEqual(404, response.status_code)
|
||||
54
storyboard/tests/db/api/test_milestones.py
Normal file
54
storyboard/tests/db/api/test_milestones.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# Copyright (c) 2015 Mirantis Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from storyboard.db.api import branches
|
||||
from storyboard.db.api import milestones
|
||||
from storyboard.db.api import projects
|
||||
from storyboard.tests.db import base
|
||||
|
||||
|
||||
class MilestonesTest(base.BaseDbTestCase):
|
||||
def setUp(self):
|
||||
super(MilestonesTest, self).setUp()
|
||||
|
||||
self.milestone_01 = {
|
||||
'name': u'test_milestone',
|
||||
'branch_id': 1
|
||||
}
|
||||
|
||||
self.branch_01 = {
|
||||
'name': u'test_branch',
|
||||
'project_id': 1
|
||||
}
|
||||
|
||||
self.project_01 = {
|
||||
'name': u'TestProject',
|
||||
'description': u'TestDescription'
|
||||
}
|
||||
|
||||
projects.project_create(self.project_01)
|
||||
branches.branch_create(self.branch_01)
|
||||
|
||||
def test_create_branch(self):
|
||||
self._test_create(self.milestone_01, milestones.milestone_create)
|
||||
|
||||
def test_update_branch(self):
|
||||
delta = {
|
||||
'expired': True
|
||||
}
|
||||
|
||||
self._test_update(self.milestone_01, delta,
|
||||
milestones.milestone_create,
|
||||
milestones.milestone_update)
|
||||
@@ -18,6 +18,7 @@ import pytz
|
||||
import storyboard.common.event_types as event
|
||||
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 Project
|
||||
from storyboard.db.models import ProjectGroup
|
||||
@@ -239,6 +240,25 @@ def load():
|
||||
),
|
||||
])
|
||||
|
||||
# Load some branches
|
||||
load_data([
|
||||
Branch(
|
||||
id=1,
|
||||
project_id=1,
|
||||
name='master',
|
||||
),
|
||||
Branch(
|
||||
id=2,
|
||||
project_id=2,
|
||||
name='master'
|
||||
),
|
||||
Branch(
|
||||
id=3,
|
||||
project_id=3,
|
||||
name='master'
|
||||
)
|
||||
])
|
||||
|
||||
|
||||
def load_data(data):
|
||||
"""Pre load test data into the database.
|
||||
|
||||
Reference in New Issue
Block a user