Added branches to storyboard

Added three new migrations:
autocreate_branches column added to projects;
added branches table;
branch_id column added to tasks.

To db api added branches.
Added branches controller.
Added tests for patch.

Change-Id: I50008dc8a2dcb07bea4015d27b702994fccff0a8
This commit is contained in:
Aleksey Ripinen 2015-01-27 12:07:45 +03:00
parent def7fa9f75
commit eaa59426d5
15 changed files with 757 additions and 7 deletions

View File

@ -0,0 +1,156 @@
# 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.db.api import branches as branches_api
from storyboard.openstack.common.gettextutils import _ # noqa
CONF = cfg.CONF
SEARCH_ENGINE = search_engine.get_engine()
class BranchesController(rest.RestController):
"""REST controller for branches.
"""
_custom_actions = {"search": ["GET"]}
validation_post_schema = validations.BRANCHES_POST_SCHEMA
validation_put_schema = validations.BRANCHES_PUT_SCHEMA
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose(wmodels.Branch, int)
def get_one(self, branch_id):
"""Retrieve information about the given branch.
:param branch_id: branch ID.
"""
branch = branches_api.branch_get(branch_id)
if branch:
return wmodels.Branch.from_db_model(branch)
else:
abort(404, _("Branch %s not found") % branch_id)
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Branch], int, int, wtypes.text, int, int,
wtypes.text, wtypes.text)
def get_all(self, marker=None, limit=None, name=None, project_id=None,
project_group_id=None, sort_field='id', sort_dir='asc'):
"""Retrieve a list of branches.
:param marker: The resource id where the page should begin.
:param limit: The number of branches to retrieve.
:param name: Filter branches based on name.
:param project_id: Filter branches based on project.
:param project_group_id: Filter branches based on project group.
: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_branch = branches_api.branch_get(marker)
branches = \
branches_api.branch_get_all(marker=marker_branch,
limit=limit,
name=name,
project_id=project_id,
project_group_id=project_group_id,
sort_field=sort_field,
sort_dir=sort_dir)
branches_count = \
branches_api.branch_get_count(name=name,
project_id=project_id,
project_group_id=project_group_id)
# Apply the query response headers.
response.headers['X-Limit'] = str(limit)
response.headers['X-Total'] = str(branches_count)
if marker_branch:
response.headers['X-Marker'] = str(marker_branch.id)
return [wmodels.Branch.from_db_model(b) for b in branches]
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.Branch, body=wmodels.Branch)
def post(self, branch):
"""Create a new branch.
:param branch: a branch within the request body.
"""
branch_dict = branch.as_dict()
# we can't create expired branch
if branch.expiration_date or branch.expired:
abort(400, _("Can't create expired branch."))
result = branches_api.branch_create(branch_dict)
return wmodels.Branch.from_db_model(result)
@decorators.db_exceptions
@secure(checks.superuser)
@wsme_pecan.wsexpose(wmodels.Branch, int, body=wmodels.Branch)
def put(self, branch_id, branch):
"""Modify this branch.
:param branch_id: An ID of the branch.
:param branch: a branch within the request body.
"""
branch_dict = branch.as_dict(omit_unset=True)
if "expiration_date" in six.iterkeys(branch_dict):
abort(400, _("Can't change expiration date."))
if "expired" in six.iterkeys(branch_dict):
if branch_dict["expired"]:
branch_dict["expiration_date"] = datetime.now(tz=pytz.utc)
else:
branch_dict["expiration_date"] = None
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)

View File

@ -62,13 +62,13 @@ class TasksController(rest.RestController):
@decorators.db_exceptions
@secure(checks.guest)
@wsme_pecan.wsexpose([wmodels.Task], wtypes.text, int, int, int, int,
@wsme_pecan.wsexpose([wmodels.Task], wtypes.text, int, int, int, int, 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, status=None,
priority=None, marker=None, limit=None, sort_field='id',
sort_dir='asc'):
project_id=None, project_group_id=None, branch_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.
@ -76,6 +76,7 @@ class TasksController(rest.RestController):
:param assignee_id: filter tasks by who they are assigned to.
: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 status: filter tasks by status.
:param priority: filter tasks by priority.
:param marker: The resource id where the page should begin.
@ -98,6 +99,7 @@ class TasksController(rest.RestController):
assignee_id=assignee_id,
project_id=project_id,
project_group_id=project_group_id,
branch_id=branch_id,
status=status,
priority=priority,
sort_field=sort_field,
@ -110,6 +112,7 @@ class TasksController(rest.RestController):
assignee_id=assignee_id,
project_id=project_id,
project_group_id=project_group_id,
branch_id=branch_id,
status=status,
priority=priority)

View File

@ -14,6 +14,7 @@
# limitations under the License.
from storyboard.api.v1.auth import AuthController
from storyboard.api.v1.branches import BranchesController
from storyboard.api.v1.project_groups import ProjectGroupsController
from storyboard.api.v1.projects import ProjectsController
from storyboard.api.v1.stories import StoriesController
@ -33,6 +34,7 @@ class V1Controller(object):
projects = ProjectsController()
users = UsersController()
teams = TeamsController()
branches = BranchesController()
stories = StoriesController()
tags = TagsController()
tasks = TasksController()

View File

@ -166,6 +166,21 @@ TASKS_PUT_SCHEMA = {
TASKS_POST_SCHEMA = copy.deepcopy(TASKS_PUT_SCHEMA)
TASKS_POST_SCHEMA["required"] = ["title"]
BRANCHES_PUT_SCHEMA = {
"name": "branch_schema",
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": CommonLength.lower_middle_length,
"maxLength": CommonLength.top_middle_length
}
}
}
BRANCHES_POST_SCHEMA = copy.deepcopy(BRANCHES_PUT_SCHEMA)
BRANCHES_POST_SCHEMA["required"] = ["name"]
STORY_TAGS_PUT_SCHEMA = {
"name": "storyTag_schema",
"type": "object",

View File

@ -74,6 +74,11 @@ class Project(base.APIBase):
repo_url = wtypes.text
"""This is a repo link for this project"""
autocreate_branches = bool
"""This flag means that storyboard will try to create task branches
automatically from the branches declared in the code repository.
"""
@classmethod
def sample(cls):
return cls(
@ -195,6 +200,43 @@ class Task(base.APIBase):
priority = wtypes.text
"""The priority for this task, one of 'low', 'medium', 'high'"""
branch_id = int
"""The ID of corresponding Branch"""
class Branch(base.APIBase):
"""Represents a branch."""
name = wtypes.text
"""The branch unique name. This name will be displayed in the URL.
At least 3 alphanumeric symbols.
"""
project_id = int
"""The ID of the corresponding Project."""
expired = bool
"""A binary flag that marks branches that should no longer be
selectable in tasks."""
expiration_date = datetime
"""Last date the expired flag was switched to True."""
autocreated = bool
"""A flag that marks autocreated entries, so that they can
be auto-expired when the corresponding branch is deleted in the git repo.
"""
@classmethod
def sample(cls):
return cls(
name="Storyboard-branch",
project_id=1,
expired=True,
expiration_date=datetime(2015, 1, 1, 1, 1),
autocreated=False
)
class Team(base.APIBase):
"""The Team is a group od Users with a fixed set of permissions.

View File

@ -0,0 +1,36 @@
# 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.
class MasterBranchHelper:
name = "master"
project_id = None
expired = False
expiration_date = None
autocreated = False
def __init__(self, project_id):
self.project_id = project_id
def as_dict(self):
master_branch_dict = {
"name": self.name,
"project_id": self.project_id,
"expired": self.expired,
"expiration_date": self.expiration_date,
"autocreated": self.autocreated
}
return master_branch_dict

View File

@ -0,0 +1,69 @@
# 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 branch_get(branch_id):
return api_base.entity_get(models.Branch, branch_id)
def branch_get_all(marker=None, limit=None, sort_field=None, sort_dir=None,
project_group_id=None, **kwargs):
if not sort_field:
sort_field = 'id'
if not sort_dir:
sort_dir = 'asc'
query = branch_build_query(project_group_id=project_group_id,
**kwargs)
query = api_base.paginate_query(query=query,
model=models.Branch,
limit=limit,
sort_key=sort_field,
marker=marker,
sort_dir=sort_dir)
return query.all()
def branch_get_count(project_group_id=None, **kwargs):
query = branch_build_query(project_group_id=project_group_id,
**kwargs)
return query.count()
def branch_create(values):
return api_base.entity_create(models.Branch, values)
def branch_update(branch_id, values):
return api_base.entity_update(models.Branch, branch_id, values)
def branch_build_query(project_group_id, **kwargs):
query = api_base.model_query(models.Branch)
if project_group_id:
query = query.join(models.Project.project_groups) \
.filter(models.ProjectGroup.id == project_group_id)
query = api_base.apply_query_filters(query=query, model=models.Branch,
**kwargs)
return query

View File

@ -13,7 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from storyboard.common.master_branch_helper import MasterBranchHelper
from storyboard.db.api import base as api_base
from storyboard.db.api import branches as branches_api
from storyboard.db import models
@ -58,7 +60,11 @@ def project_get_count(project_group_id=None, **kwargs):
def project_create(values):
return api_base.entity_create(models.Project, values)
# Create project and 'master' branch for him
project = api_base.entity_create(models.Project, values)
master_branch = MasterBranchHelper(project["id"])
branches_api.branch_create(master_branch.as_dict())
return project
def project_update(project_id, values):

View File

@ -0,0 +1,51 @@
# 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 autocreate branches and makes it 'false'
in all projects in database.
Revision ID: 035
Revises: 034
Create Date: 2015-01-26 13:00:02.622503
"""
# revision identifiers, used by Alembic.
revision = '035'
down_revision = '034'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql.expression import table
def upgrade(active_plugins=None, options=None):
op.add_column('projects', sa.Column('autocreate_branches',
sa.Boolean(),
default=False))
projects_table = table(
'projects',
sa.Column('autocreate_branches', sa.Boolean(), nullable=True)
)
bind = op.get_bind()
bind.execute(projects_table.update().
values(autocreate_branches=False))
def downgrade(active_plugins=None, options=None):
op.drop_column('projects', 'autocreate_branches')

View File

@ -0,0 +1,83 @@
# 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 table for branches and for all projects in database
adds branch with name 'master'.
Revision ID: 036
Revises: 035
Create Date: 2015-01-26 13:03:34.622503
"""
# revision identifiers, used by Alembic.
revision = '036'
down_revision = '035'
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):
op.create_table(
'branches',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(100), nullable=True),
sa.Column('project_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,
nullable=True),
sa.Column('autocreated', sa.Boolean(), default=False, nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name', 'project_id', name="branch_un_constr"),
mysql_engine=MYSQL_ENGINE,
mysql_charset=MYSQL_CHARSET
)
bind = op.get_bind()
projects = list(bind.execute(
sa.select(columns=['id', 'created_at', 'updated_at'],
from_obj=sa.Table('projects', sa.MetaData()))))
branches_table = table(
'branches',
sa.Column('name', sa.String(100), nullable=True),
sa.Column('project_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),
sa.Column('expiration_date', sa.DateTime(), default=None),
sa.Column('autocreated', sa.Boolean(), default=False),
)
for project in projects:
bind.execute(branches_table.insert().values(
name="master",
project_id=project['id'],
created_at=project['created_at'],
updated_at=project['updated_at']
))
def downgrade(active_plugins=None, options=None):
op.drop_table('branches')

View File

@ -0,0 +1,73 @@
# 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 branch id and merge all tasks to branch
'master' in corresponding project.
Revision ID: 037
Revises: 036
Create Date: 2015-01-27 13:17:34.622503
"""
# revision identifiers, used by Alembic.
revision = '037'
down_revision = '036'
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):
op.add_column(
'tasks',
sa.Column('branch_id', sa.Integer(), nullable=True)
)
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()))))
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)
)
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):
op.drop_column('tasks', 'branch_id')

View File

@ -242,8 +242,10 @@ class Project(FullText, ModelBuilder, Base):
is_active = Column(Boolean, default=True)
project_groups = relationship("ProjectGroup",
secondary="project_group_mapping")
autocreate_branches = Column(Boolean, default=False)
_public_fields = ["id", "name", "description", "tasks", "repo_url"]
_public_fields = ["id", "name", "description", "tasks", "repo_url",
"autocreate_branches"]
class ProjectGroup(ModelBuilder, Base):
@ -301,10 +303,30 @@ class Task(FullText, ModelBuilder, Base):
story_id = Column(Integer, ForeignKey('stories.id'))
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)
priority = Column(Enum(*_TASK_PRIORITIES), default='medium')
_public_fields = ["id", "creator_id", "title", "status", "story_id",
"project_id", "assignee_id", "priority"]
"project_id", "assignee_id", "priority", "branch_id"]
class Branch(FullText, ModelBuilder, Base):
__tablename__ = 'branches'
__table_args__ = (
schema.UniqueConstraint('name', 'project_id', name='branch_un_constr'),
)
__fulltext_columns__ = ['name']
name = Column(String(CommonLength.top_middle_length))
project_id = Column(Integer, ForeignKey('projects.id'))
expired = Column(Boolean, default=False)
expiration_date = Column(UTCDateTime, default=None)
autocreated = Column(Boolean, default=False)
_public_fields = ["id", "name", "project_id", "expired",
"expiration_date", "autocreated"]
class StoryTag(ModelBuilder, Base):

View File

@ -32,6 +32,7 @@ class_mappings = {'task': [models.Task, wmodels.Task],
'user': [models.User, wmodels.User],
'team': [models.Team, wmodels.Team],
'story': [models.Story, wmodels.Story],
'branch': [models.Branch, wmodels.Branch],
'tag': [models.StoryTag, wmodels.Tag]}
@ -140,6 +141,7 @@ class NotificationHook(hooks.PecanHook):
'projects': 'project',
'project_groups': 'project_group',
'tasks': 'task',
'branches': 'branch',
'timeline_events': 'timeline_event',
'users': 'user',
'teams': 'team',

View File

@ -0,0 +1,144 @@
# 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 TestBranches(base.FunctionalTest):
def setUp(self):
super(TestBranches, self).setUp()
self.resource = '/branches'
self.branch_01 = {
'name': 'test_branch_01',
'project_id': 1
}
self.branch_02 = {
'name': 'test_branch_02',
'project_id': 100
}
self.branch_03 = {
'name': 'test_branch_03',
'project_id': 1,
'expiration_date': '2014-01-01T00:00:00+00:00'
}
self.put_branch_01 = {
'project_id': 2
}
self.put_branch_02 = {
'expired': True
}
self.put_branch_03 = {
'expired': False
}
self.put_branch_04 = {
'expired': False,
'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):
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'])
self.assertEqual(branch['expired'], False)
self.assertIsNone(branch['expiration_date'])
self.assertEqual(branch['autocreated'], False)
def test_create_invalid(self):
response = self.post_json(self.resource, self.branch_03,
expect_errors=True)
self.assertEqual(response.status_code, 400)
def test_update(self):
response = self.post_json(self.resource, self.branch_01)
branch = response.json
self.assertEqual(branch['name'], self.branch_01['name'])
self.assertEqual(branch['project_id'], self.branch_01['project_id'])
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['project_id'],
self.put_branch_01['project_id'])
response = self.put_json(resource, self.put_branch_02)
branch = response.json
self.assertEqual(branch['expired'], True)
self.assertIsNotNone(branch['expiration_date'])
response = self.put_json(resource, self.put_branch_03)
branch = response.json
self.assertEqual(branch['expired'], False)
self.assertIsNone(branch['expiration_date'])
def test_update_expiration_date(self):
response = self.post_json(self.resource, self.branch_01)
branch = response.json
self.assertEqual(branch['name'], self.branch_01['name'])
self.assertEqual(branch['project_id'], self.branch_01['project_id'])
self.assertIn("id", branch)
resource = "".join([self.resource, ("/%d" % branch['id'])])
response = self.put_json(resource, self.put_branch_02)
branch = response.json
self.assertEqual(branch['expired'], True)
self.assertIsNotNone(branch['expiration_date'])
response = self.put_json(resource, self.put_branch_04,
expect_errors=True)
self.assertEqual(response.status_code, 400)
def test_get_one(self):
response = self.post_json(self.resource, self.branch_01)
branch = response.json
resource = "".join([self.resource, ("/%d" % branch['id'])])
branch = self.get_json(path=resource)
self.assertEqual(branch['name'], self.branch_01['name'])
self.assertEqual(branch['project_id'], self.branch_01['project_id'])
self.assertEqual(branch['expired'], False)
self.assertIsNone(branch['expiration_date'])
self.assertEqual(branch['autocreated'], False)
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)

View File

@ -0,0 +1,46 @@
# 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 projects
from storyboard.tests.db import base
class BranchesTest(base.BaseDbTestCase):
def setUp(self):
super(BranchesTest, self).setUp()
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)
def test_create_branch(self):
self._test_create(self.branch_01, branches.branch_create)
def test_update_branch(self):
delta = {
'expired': True
}
self._test_update(self.branch_01, delta, branches.branch_create,
branches.branch_update)