Added REST API for tasks
There are several major changes in this commit: * db api refactored to extract common code to common methods * added REST API layer for tasks * creation of story now triggers creation of default associated task * it is possible to filter stories by project.id * it is possible to filter tasks by story.id * all new features have functional test coverage * updated rest api docs to include only functioning endpoints Change-Id: Ice2ebfa174e35944bebd71e3cf4b809a3e825761
This commit is contained in:
parent
b56316a4bb
commit
c45a36175a
@ -11,11 +11,6 @@ Projects
|
|||||||
.. rest-controller:: storyboard.api.v1.projects:ProjectsController
|
.. rest-controller:: storyboard.api.v1.projects:ProjectsController
|
||||||
:webprefix: /v1/projects
|
:webprefix: /v1/projects
|
||||||
|
|
||||||
Project Groups
|
|
||||||
==============
|
|
||||||
.. rest-controller:: storyboard.api.v1.project_groups:ProjectGroupsController
|
|
||||||
:webprefix: /v1/projects
|
|
||||||
|
|
||||||
Stories
|
Stories
|
||||||
=======
|
=======
|
||||||
.. rest-controller:: storyboard.api.v1.stories:StoriesController
|
.. rest-controller:: storyboard.api.v1.stories:StoriesController
|
||||||
@ -26,62 +21,24 @@ Tasks
|
|||||||
.. rest-controller:: storyboard.api.v1.tasks:TasksController
|
.. rest-controller:: storyboard.api.v1.tasks:TasksController
|
||||||
:webprefix: /v1/projects
|
:webprefix: /v1/projects
|
||||||
|
|
||||||
Teams
|
|
||||||
=====
|
|
||||||
.. rest-controller:: storyboard.api.v1.teams:TeamsController
|
|
||||||
:webprefix: /v1/projects
|
|
||||||
|
|
||||||
Users
|
|
||||||
=====
|
|
||||||
.. rest-controller:: storyboard.api.v1.users:UsersController
|
|
||||||
:webprefix: /v1/projects
|
|
||||||
|
|
||||||
|
|
||||||
############
|
############
|
||||||
Object model
|
Object model
|
||||||
############
|
############
|
||||||
|
|
||||||
Comment
|
|
||||||
=======
|
|
||||||
.. autotype:: storyboard.api.v1.wsme_models.Comment
|
|
||||||
:members:
|
|
||||||
|
|
||||||
Permission
|
|
||||||
==========
|
|
||||||
.. autotype:: storyboard.api.v1.wsme_models.Permission
|
|
||||||
:members:
|
|
||||||
|
|
||||||
Project
|
Project
|
||||||
=======
|
=======
|
||||||
.. autotype:: storyboard.api.v1.projects.Project
|
.. autotype:: storyboard.api.v1.projects.Project
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
ProjectGroup
|
|
||||||
============
|
|
||||||
.. autotype:: storyboard.api.v1.wsme_models.ProjectGroup
|
|
||||||
:members:
|
|
||||||
|
|
||||||
Story
|
Story
|
||||||
=====
|
=====
|
||||||
.. autotype:: storyboard.api.v1.stories.Story
|
.. autotype:: storyboard.api.v1.stories.Story
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
StoryTag
|
|
||||||
========
|
|
||||||
.. autotype:: storyboard.api.v1.wsme_models.StoryTag
|
|
||||||
:members:
|
|
||||||
|
|
||||||
Task
|
Task
|
||||||
====
|
====
|
||||||
.. autotype:: storyboard.api.v1.wsme_models.Task
|
.. autotype:: storyboard.api.v1.tasks.Task
|
||||||
:members:
|
|
||||||
|
|
||||||
Team
|
|
||||||
====
|
|
||||||
.. autotype:: storyboard.api.v1.wsme_models.Team
|
|
||||||
:members:
|
|
||||||
|
|
||||||
User
|
|
||||||
====
|
|
||||||
.. autotype:: storyboard.api.v1.wsme_models.User
|
|
||||||
:members:
|
:members:
|
||||||
|
@ -39,6 +39,9 @@ class Story(base.APIBase):
|
|||||||
Allowed values: ['Undefined', 'Low', 'Medium', 'High', 'Critical'].
|
Allowed values: ['Undefined', 'Low', 'Medium', 'High', 'Critical'].
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
project_id = int
|
||||||
|
"""Optional parameter"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def sample(cls):
|
def sample(cls):
|
||||||
return cls(
|
return cls(
|
||||||
@ -51,7 +54,7 @@ class Story(base.APIBase):
|
|||||||
class StoriesController(rest.RestController):
|
class StoriesController(rest.RestController):
|
||||||
"""Manages operations on stories."""
|
"""Manages operations on stories."""
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(Story, unicode)
|
@wsme_pecan.wsexpose(Story, int)
|
||||||
def get_one(self, story_id):
|
def get_one(self, story_id):
|
||||||
"""Retrieve details about one story.
|
"""Retrieve details about one story.
|
||||||
|
|
||||||
@ -60,10 +63,16 @@ class StoriesController(rest.RestController):
|
|||||||
story = dbapi.story_get(story_id)
|
story = dbapi.story_get(story_id)
|
||||||
return Story.from_db_model(story)
|
return Story.from_db_model(story)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose([Story])
|
@wsme_pecan.wsexpose([Story], int)
|
||||||
def get(self):
|
def get_all(self, project_id=None):
|
||||||
"""Retrieve definitions of all of the stories."""
|
"""Retrieve definitions of all of the stories.
|
||||||
stories = dbapi.story_get_all()
|
|
||||||
|
:param project_id: filter stories by project ID
|
||||||
|
"""
|
||||||
|
if project_id:
|
||||||
|
stories = dbapi.story_get_all_in_project(project_id)
|
||||||
|
else:
|
||||||
|
stories = dbapi.story_get_all()
|
||||||
return [Story.from_db_model(s) for s in stories]
|
return [Story.from_db_model(s) for s in stories]
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(Story, body=Story)
|
@wsme_pecan.wsexpose(Story, body=Story)
|
||||||
@ -72,7 +81,18 @@ class StoriesController(rest.RestController):
|
|||||||
|
|
||||||
:param story: a story within the request body.
|
:param story: a story within the request body.
|
||||||
"""
|
"""
|
||||||
created_story = dbapi.story_create(story.as_dict())
|
args = story.as_dict()
|
||||||
|
project_id = args.pop('project_id', None)
|
||||||
|
created_story = dbapi.story_create(args)
|
||||||
|
# Create default task for this story
|
||||||
|
task = {
|
||||||
|
'title': created_story['title'],
|
||||||
|
'status': 'Todo',
|
||||||
|
'story_id': created_story['id'],
|
||||||
|
'project_id': project_id
|
||||||
|
}
|
||||||
|
dbapi.task_create(task)
|
||||||
|
|
||||||
return Story.from_db_model(created_story)
|
return Story.from_db_model(created_story)
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(Story, int, body=Story)
|
@wsme_pecan.wsexpose(Story, int, body=Story)
|
||||||
|
@ -14,41 +14,69 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from pecan import rest
|
from pecan import rest
|
||||||
from wsme.exc import ClientSideError
|
from wsme import types as wtypes
|
||||||
import wsmeext.pecan as wsme_pecan
|
import wsmeext.pecan as wsme_pecan
|
||||||
|
|
||||||
import storyboard.api.v1.wsme_models as wsme_models
|
from storyboard.api.v1 import base
|
||||||
|
from storyboard.db import api as dbapi
|
||||||
|
|
||||||
|
|
||||||
|
class Task(base.APIBase):
|
||||||
|
"""Represents a task within a story."""
|
||||||
|
|
||||||
|
title = wtypes.text
|
||||||
|
"""A descriptive label for this tracker to show in listings."""
|
||||||
|
|
||||||
|
# TODO(ruhe): replace with enum
|
||||||
|
status = wtypes.text
|
||||||
|
"""Status.
|
||||||
|
Allowed values: ['Todo', 'In review', 'Landed'].
|
||||||
|
"""
|
||||||
|
|
||||||
|
story_id = int
|
||||||
|
"""An ID of corresponding user story"""
|
||||||
|
|
||||||
|
project_id = int
|
||||||
|
"""An ID of project this task is assigned to"""
|
||||||
|
|
||||||
|
|
||||||
class TasksController(rest.RestController):
|
class TasksController(rest.RestController):
|
||||||
"""Manages tasks."""
|
"""Manages tasks."""
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(wsme_models.Task, unicode)
|
@wsme_pecan.wsexpose(Task, int)
|
||||||
def get_one(self, id):
|
def get_one(self, task_id):
|
||||||
"""Retrieve details about one task.
|
"""Retrieve details about one task.
|
||||||
|
|
||||||
:param id: An ID of the task.
|
:param task_id: An ID of the task.
|
||||||
"""
|
"""
|
||||||
task = wsme_models.Task.get(id=id)
|
task = dbapi.task_get(task_id)
|
||||||
if not task:
|
return Task.from_db_model(task)
|
||||||
raise ClientSideError("Task %s not found" % id,
|
|
||||||
status_code=404)
|
|
||||||
return task
|
|
||||||
|
|
||||||
@wsme_pecan.wsexpose([wsme_models.Task])
|
@wsme_pecan.wsexpose([Task], int)
|
||||||
def get(self):
|
def get_all(self, story_id=None):
|
||||||
"""Retrieve definitions of all of the tasks."""
|
"""Retrieve definitions of all of the tasks.
|
||||||
tasks = wsme_models.Task.get_all()
|
|
||||||
return tasks
|
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(wsme_models.Task, unicode, wsme_models.Task)
|
:param story_id: filter tasks by story ID
|
||||||
|
"""
|
||||||
|
tasks = dbapi.task_get_all(story_id=story_id)
|
||||||
|
return [Task.from_db_model(s) for s in tasks]
|
||||||
|
|
||||||
|
@wsme_pecan.wsexpose(Task, body=Task)
|
||||||
|
def post(self, task):
|
||||||
|
"""Create a new task.
|
||||||
|
|
||||||
|
:param task: a task within the request body.
|
||||||
|
"""
|
||||||
|
created_task = dbapi.task_create(task.as_dict())
|
||||||
|
return Task.from_db_model(created_task)
|
||||||
|
|
||||||
|
@wsme_pecan.wsexpose(Task, int, body=Task)
|
||||||
def put(self, task_id, task):
|
def put(self, task_id, task):
|
||||||
"""Modify this task.
|
"""Modify this task.
|
||||||
|
|
||||||
:param task_id: An ID of the task.
|
:param task_id: An ID of the task.
|
||||||
:param task: a task within the request body.
|
:param task: a task within the request body.
|
||||||
"""
|
"""
|
||||||
updated_task = wsme_models.Task.update("id", task_id, task)
|
updated_task = dbapi.task_update(task_id,
|
||||||
if not updated_task:
|
task.as_dict(omit_unset=True))
|
||||||
raise ClientSideError("Could not update story %s" % task_id)
|
return Task.from_db_model(updated_task)
|
||||||
return updated_task
|
|
||||||
|
@ -215,11 +215,6 @@ class Permission(_Base):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Task(_Base):
|
|
||||||
"""Represents a task within a story."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class StoryTag(_Base):
|
class StoryTag(_Base):
|
||||||
"""Tags are used classifying user-stories."""
|
"""Tags are used classifying user-stories."""
|
||||||
pass
|
pass
|
||||||
@ -326,7 +321,6 @@ SQLALCHEMY_TO_WSME = {
|
|||||||
sqlalchemy_models.User: User,
|
sqlalchemy_models.User: User,
|
||||||
sqlalchemy_models.ProjectGroup: ProjectGroup,
|
sqlalchemy_models.ProjectGroup: ProjectGroup,
|
||||||
sqlalchemy_models.Permission: Permission,
|
sqlalchemy_models.Permission: Permission,
|
||||||
sqlalchemy_models.Task: Task,
|
|
||||||
sqlalchemy_models.Comment: Comment,
|
sqlalchemy_models.Comment: Comment,
|
||||||
sqlalchemy_models.StoryTag: StoryTag
|
sqlalchemy_models.StoryTag: StoryTag
|
||||||
}
|
}
|
||||||
|
@ -37,89 +37,110 @@ def model_query(model, session=None):
|
|||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
## BEGIN Projects
|
def __entity_get(kls, entity_id, session):
|
||||||
|
query = model_query(kls, session)
|
||||||
def _project_get(project_id, session):
|
return query.filter_by(id=entity_id).first()
|
||||||
query = model_query(models.Project, session)
|
|
||||||
return query.filter_by(id=project_id).first()
|
|
||||||
|
|
||||||
|
|
||||||
def project_get(project_id):
|
def _entity_get(kls, entity_id):
|
||||||
return _project_get(project_id, get_session())
|
return __entity_get(kls, entity_id, get_session())
|
||||||
|
|
||||||
|
|
||||||
def project_get_all(**kwargs):
|
def _entity_get_all(kls, **kwargs):
|
||||||
query = model_query(models.Project)
|
kwargs = dict((k, v) for k, v in kwargs.iteritems() if v)
|
||||||
|
|
||||||
|
query = model_query(kls)
|
||||||
return query.filter_by(**kwargs).all()
|
return query.filter_by(**kwargs).all()
|
||||||
|
|
||||||
|
|
||||||
def project_create(values):
|
def _entity_create(kls, values):
|
||||||
project = models.Project()
|
entity = kls()
|
||||||
project.update(values.copy())
|
entity.update(values.copy())
|
||||||
|
|
||||||
session = get_session()
|
session = get_session()
|
||||||
with session.begin():
|
with session.begin():
|
||||||
try:
|
try:
|
||||||
project.save(session=session)
|
entity.save(session=session)
|
||||||
except db_exc.DBDuplicateEntry as e:
|
except db_exc.DBDuplicateEntry as e:
|
||||||
raise exc.DuplicateEntry("Duplicate entry for Project: %s"
|
raise exc.DuplicateEntry("Duplicate etnry for : %s"
|
||||||
% e.columns)
|
% (kls.__name__, e.colums))
|
||||||
|
|
||||||
return project
|
return entity
|
||||||
|
|
||||||
|
|
||||||
def project_update(project_id, values):
|
def entity_update(kls, entity_id, values):
|
||||||
session = get_session()
|
session = get_session()
|
||||||
|
|
||||||
with session.begin():
|
with session.begin():
|
||||||
project = _project_get(project_id, session)
|
entity = __entity_get(kls, entity_id, session)
|
||||||
if project is None:
|
if entity is None:
|
||||||
raise exc.NotFound("Project %s not found" % project_id)
|
raise exc.NotFound("%s %s not found" % (kls.__name__, entity_id))
|
||||||
|
|
||||||
project.update(values.copy())
|
entity.update(values.copy())
|
||||||
|
|
||||||
return project
|
return entity
|
||||||
|
|
||||||
|
|
||||||
|
## BEGIN Projects
|
||||||
|
|
||||||
|
def project_get(project_id):
|
||||||
|
return _entity_get(models.Project, project_id)
|
||||||
|
|
||||||
|
|
||||||
|
def project_get_all(**kwargs):
|
||||||
|
return _entity_get_all(models.Project, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def project_create(values):
|
||||||
|
return _entity_create(models.Project, values)
|
||||||
|
|
||||||
|
|
||||||
|
def project_update(project_id, values):
|
||||||
|
return entity_update(models.Project, project_id, values)
|
||||||
|
|
||||||
|
|
||||||
## BEGIN Stories
|
## BEGIN Stories
|
||||||
|
|
||||||
def _story_get(story_id, session):
|
|
||||||
query = model_query(models.Story, session)
|
|
||||||
return query.filter_by(id=story_id).first()
|
|
||||||
|
|
||||||
|
|
||||||
def story_get_all(**kwargs):
|
|
||||||
query = model_query(models.Story)
|
|
||||||
return query.filter_by(**kwargs).all()
|
|
||||||
|
|
||||||
|
|
||||||
def story_get(story_id):
|
def story_get(story_id):
|
||||||
return _story_get(story_id, get_session())
|
return _entity_get(models.Story, story_id)
|
||||||
|
|
||||||
|
|
||||||
|
def story_get_all(project_id=None):
|
||||||
|
if project_id:
|
||||||
|
return story_get_all_in_project(project_id)
|
||||||
|
else:
|
||||||
|
return _entity_get_all(models.Story)
|
||||||
|
|
||||||
|
|
||||||
|
def story_get_all_in_project(project_id):
|
||||||
|
session = get_session()
|
||||||
|
|
||||||
|
query = model_query(models.Story, session).join(models.Task)
|
||||||
|
return query.filter_by(project_id=project_id)
|
||||||
|
|
||||||
|
|
||||||
def story_create(values):
|
def story_create(values):
|
||||||
story = models.Story()
|
return _entity_create(models.Story, values)
|
||||||
story.update(values.copy())
|
|
||||||
|
|
||||||
session = get_session()
|
|
||||||
with session.begin():
|
|
||||||
try:
|
|
||||||
story.save(session)
|
|
||||||
except db_exc.DBDuplicateEntry as e:
|
|
||||||
raise exc.DuplicateEntry("Duplicate etnry for Story: %s"
|
|
||||||
% e.colums)
|
|
||||||
|
|
||||||
return story
|
|
||||||
|
|
||||||
|
|
||||||
def story_update(story_id, values):
|
def story_update(story_id, values):
|
||||||
session = get_session()
|
return entity_update(models.Story, story_id, values)
|
||||||
|
|
||||||
with session.begin():
|
|
||||||
story = _story_get(story_id, session)
|
|
||||||
if story is None:
|
|
||||||
raise exc.NotFound("Story %s not found" % story_id)
|
|
||||||
|
|
||||||
story.update(values.copy())
|
# BEGIN Tasks
|
||||||
|
|
||||||
return story
|
def task_get(task_id):
|
||||||
|
return _entity_get(models.Task, task_id)
|
||||||
|
|
||||||
|
|
||||||
|
def task_get_all(story_id=None):
|
||||||
|
return _entity_get_all(models.Task, story_id=story_id)
|
||||||
|
|
||||||
|
|
||||||
|
def task_create(values):
|
||||||
|
return _entity_create(models.Task, values)
|
||||||
|
|
||||||
|
|
||||||
|
def task_update(task_id, values):
|
||||||
|
return entity_update(models.Task, task_id, values)
|
||||||
|
@ -30,7 +30,7 @@ class TestStories(base.FunctionalTest):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def test_stories_endpoint(self):
|
def test_stories_endpoint(self):
|
||||||
response = self.get_json(self.resource)
|
response = self.get_json(self.resource, project_id=1)
|
||||||
self.assertEqual([], response)
|
self.assertEqual([], response)
|
||||||
|
|
||||||
def test_create(self):
|
def test_create(self):
|
||||||
@ -65,3 +65,26 @@ class TestStories(base.FunctionalTest):
|
|||||||
self.assertNotEqual(updated['title'], original['title'])
|
self.assertNotEqual(updated['title'], original['title'])
|
||||||
self.assertNotEqual(updated['description'],
|
self.assertNotEqual(updated['description'],
|
||||||
original['description'])
|
original['description'])
|
||||||
|
|
||||||
|
def test_complete_workflow(self):
|
||||||
|
ref = self.story_01
|
||||||
|
ref['project_id'] = 2
|
||||||
|
resp = self.post_json('/stories', ref)
|
||||||
|
saved_story = json.loads(resp.body)
|
||||||
|
|
||||||
|
saved_task = self.get_json('/tasks', story_id=saved_story['id'])[0]
|
||||||
|
self.assertEqual(saved_story['id'], saved_task['story_id'])
|
||||||
|
self.assertEqual(ref['title'], saved_task['title'])
|
||||||
|
|
||||||
|
stories = self.get_json('/stories', project_id=ref['project_id'])
|
||||||
|
self.assertEqual(1, len(stories))
|
||||||
|
|
||||||
|
new_task = {
|
||||||
|
'title': 'StoryBoard',
|
||||||
|
'status': 'Todo',
|
||||||
|
'story_id': saved_story['id']
|
||||||
|
}
|
||||||
|
self.post_json('/tasks', new_task)
|
||||||
|
|
||||||
|
tasks = self.get_json('/tasks', story_id=saved_story['id'])
|
||||||
|
self.assertEqual(2, len(tasks))
|
||||||
|
46
storyboard/tests/api/test_tasks.py
Normal file
46
storyboard/tests/api/test_tasks.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Copyright (c) 2014 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 TestTasks(base.FunctionalTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestTasks, self).setUp()
|
||||||
|
self.resource = '/tasks'
|
||||||
|
|
||||||
|
self.task_01 = {
|
||||||
|
'title': 'StoryBoard',
|
||||||
|
'status': 'Todo',
|
||||||
|
'story_id': 10
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_tasks_endpoint(self):
|
||||||
|
response = self.get_json(self.resource)
|
||||||
|
self.assertEqual([], response)
|
||||||
|
|
||||||
|
def test_create(self):
|
||||||
|
self.post_json(self.resource, self.task_01)
|
||||||
|
self.task_01['story_id'] = 1000
|
||||||
|
self.post_json(self.resource, self.task_01)
|
||||||
|
|
||||||
|
# No filters here - we should receive both created tasks
|
||||||
|
all_tasks = self.get_json(self.resource)
|
||||||
|
self.assertEqual(2, len(all_tasks))
|
||||||
|
|
||||||
|
# filter by story_id - we should receive only the one task
|
||||||
|
tasks_story_10 = self.get_json(self.resource, story_id=10)
|
||||||
|
self.assertEqual(1, len(tasks_story_10))
|
||||||
|
self.assertEqual(self.task_01['title'], tasks_story_10[0]['title'])
|
@ -17,7 +17,29 @@ from storyboard.db import api as dbapi
|
|||||||
from storyboard.tests import base
|
from storyboard.tests import base
|
||||||
|
|
||||||
|
|
||||||
class ProjectsTest(base.DbTestCase):
|
class BaseDbTestCase(base.DbTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(BaseDbTestCase, self).setUp()
|
||||||
|
|
||||||
|
def _assert_saved_fields(self, expected, actual):
|
||||||
|
for k in expected.keys():
|
||||||
|
self.assertEqual(expected[k], actual[k])
|
||||||
|
|
||||||
|
def _test_create(self, ref, save_method):
|
||||||
|
saved = save_method(ref)
|
||||||
|
|
||||||
|
self.assertIsNotNone(saved.id)
|
||||||
|
self._assert_saved_fields(ref, saved)
|
||||||
|
|
||||||
|
def _test_update(self, ref, delta, create, update):
|
||||||
|
saved = create(ref)
|
||||||
|
updated = update(saved.id, delta)
|
||||||
|
|
||||||
|
self.assertEqual(saved.id, updated.id)
|
||||||
|
self._assert_saved_fields(delta, updated)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectsTest(BaseDbTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ProjectsTest, self).setUp()
|
super(ProjectsTest, self).setUp()
|
||||||
@ -28,27 +50,18 @@ class ProjectsTest(base.DbTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def test_save_project(self):
|
def test_save_project(self):
|
||||||
ref = self.project_01
|
self._test_create(self.project_01, dbapi.project_create)
|
||||||
saved = dbapi.project_create(ref)
|
|
||||||
|
|
||||||
self.assertIsNotNone(saved.id)
|
|
||||||
self.assertEqual(ref['name'], saved.name)
|
|
||||||
self.assertEqual(ref['description'], saved.description)
|
|
||||||
|
|
||||||
def test_update_project(self):
|
def test_update_project(self):
|
||||||
saved = dbapi.project_create(self.project_01)
|
|
||||||
delta = {
|
delta = {
|
||||||
'name': u'New Name',
|
'name': u'New Name',
|
||||||
'description': u'New Description'
|
'description': u'New Description'
|
||||||
}
|
}
|
||||||
updated = dbapi.project_update(saved.id, delta)
|
self._test_update(self.project_01, delta,
|
||||||
|
dbapi.project_create, dbapi.project_update)
|
||||||
self.assertEqual(saved.id, updated.id)
|
|
||||||
self.assertEqual(delta['name'], updated.name)
|
|
||||||
self.assertEqual(delta['description'], updated.description)
|
|
||||||
|
|
||||||
|
|
||||||
class StoriesTest(base.DbTestCase):
|
class StoriesTest(BaseDbTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(StoriesTest, self).setUp()
|
super(StoriesTest, self).setUp()
|
||||||
@ -59,22 +72,36 @@ class StoriesTest(base.DbTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def test_create_story(self):
|
def test_create_story(self):
|
||||||
ref = self.story_01
|
self._test_create(self.story_01, dbapi.story_create)
|
||||||
saved = dbapi.story_create(self.story_01)
|
|
||||||
|
|
||||||
self.assertIsNotNone(saved.id)
|
|
||||||
self.assertEqual(ref['title'], saved.title)
|
|
||||||
self.assertEqual(ref['description'], saved.description)
|
|
||||||
|
|
||||||
def test_update_story(self):
|
def test_update_story(self):
|
||||||
saved = dbapi.story_create(self.story_01)
|
|
||||||
delta = {
|
delta = {
|
||||||
'title': u'New Title',
|
'title': u'New Title',
|
||||||
'description': u'New Description'
|
'description': u'New Description'
|
||||||
}
|
}
|
||||||
|
self._test_update(self.story_01, delta,
|
||||||
|
dbapi.story_create, dbapi.story_update)
|
||||||
|
|
||||||
updated = dbapi.story_update(saved.id, delta)
|
|
||||||
|
|
||||||
self.assertEqual(saved.id, updated.id)
|
class TasksTest(BaseDbTestCase):
|
||||||
self.assertEqual(delta['title'], updated.title)
|
|
||||||
self.assertEqual(delta['description'], updated.description)
|
def setUp(self):
|
||||||
|
super(TasksTest, self).setUp()
|
||||||
|
|
||||||
|
self.task_01 = {
|
||||||
|
'title': u'Invent time machine',
|
||||||
|
'status': 'Todo',
|
||||||
|
'story_id': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_create_task(self):
|
||||||
|
self._test_create(self.task_01, dbapi.task_create)
|
||||||
|
|
||||||
|
def test_update_task(self):
|
||||||
|
delta = {
|
||||||
|
'status': 'In review',
|
||||||
|
'assignee_id': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
self._test_update(self.task_01, delta,
|
||||||
|
dbapi.task_create, dbapi.task_update)
|
||||||
|
Loading…
Reference in New Issue
Block a user