From d8be665b37f17d04c2acc58c9a292bfe49a056b8 Mon Sep 17 00:00:00 2001 From: Aleksey Ripinen Date: Tue, 23 Dec 2014 17:37:09 +0300 Subject: [PATCH] Team and project groups delete methods Added delete methods for teams and project groups. Now delete methods check project groups and teams for empty status. DB api now raises exceptions if operation is no allowed. Change-Id: Ic34d42d07c72683aa9624f45dcdf4c033af74e28 --- storyboard/api/v1/project_groups.py | 11 +++- storyboard/api/v1/teams.py | 18 +++++++ storyboard/common/exception.py | 8 +++ storyboard/db/api/project_groups.py | 9 +++- storyboard/db/api/teams.py | 12 +++++ storyboard/tests/api/test_project_groups.py | 30 +++++++++++ storyboard/tests/api/test_teams.py | 58 +++++++++++++++++++++ 7 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 storyboard/tests/api/test_teams.py diff --git a/storyboard/api/v1/project_groups.py b/storyboard/api/v1/project_groups.py index bdd3e144..70d22e70 100644 --- a/storyboard/api/v1/project_groups.py +++ b/storyboard/api/v1/project_groups.py @@ -14,6 +14,7 @@ # limitations under the License. from oslo.config import cfg +from pecan import abort from pecan import response from pecan import rest from pecan.secure import secure @@ -22,6 +23,7 @@ import wsmeext.pecan as wsme_pecan import storyboard.api.auth.authorization_checks as checks from storyboard.api.v1 import wmodels +import storyboard.common.exception as exc from storyboard.db.api import project_groups from storyboard.db.api import projects from storyboard.openstack.common.gettextutils import _ # noqa @@ -165,13 +167,18 @@ class ProjectGroupsController(rest.RestController): return wmodels.ProjectGroup.from_db_model(updated_group) @secure(checks.superuser) - @wsme_pecan.wsexpose(wmodels.ProjectGroup, int) + @wsme_pecan.wsexpose(None, int) def delete(self, project_group_id): """Delete this project group. :param project_group_id: An ID of the project group. """ - project_groups.project_group_delete(project_group_id) + try: + project_groups.project_group_delete(project_group_id) + except exc.NotFound as not_found_exc: + abort(404, not_found_exc.message) + except exc.NotEmpty as not_empty_exc: + abort(400, not_empty_exc.message) response.status_code = 204 diff --git a/storyboard/api/v1/teams.py b/storyboard/api/v1/teams.py index 21d60d50..52885f71 100644 --- a/storyboard/api/v1/teams.py +++ b/storyboard/api/v1/teams.py @@ -14,6 +14,7 @@ # limitations under the License. from oslo.config import cfg +from pecan import abort from pecan.decorators import expose from pecan import response from pecan import rest @@ -23,6 +24,7 @@ import wsmeext.pecan as wsme_pecan from storyboard.api.auth import authorization_checks as checks from storyboard.api.v1 import wmodels +from storyboard.common import exception as exc from storyboard.db.api import teams as teams_api from storyboard.db.api import users as users_api from storyboard.openstack.common.gettextutils import _ # noqa @@ -197,3 +199,19 @@ class TeamsController(rest.RestController): # Use default routing for all other requests return super(TeamsController, self)._route(args, request) + + @secure(checks.superuser) + @wsme_pecan.wsexpose(None, int) + def delete(self, team_id): + """Delete this team. + + :param team_id: An ID of the team. + """ + try: + teams_api.team_delete(team_id) + except exc.NotFound as not_found_exc: + abort(404, not_found_exc.message) + except exc.NotEmpty as not_empty_exc: + abort(400, not_empty_exc.message) + + response.status_code = 204 diff --git a/storyboard/common/exception.py b/storyboard/common/exception.py index 3be756cc..9765a607 100644 --- a/storyboard/common/exception.py +++ b/storyboard/common/exception.py @@ -46,3 +46,11 @@ class DuplicateEntry(StoryboardException): def __init__(self, message=None): if message: self.message = message + + +class NotEmpty(StoryboardException): + message = _("Database object must be empty") + + def __init__(self, message=None): + if message: + self.message = message diff --git a/storyboard/db/api/project_groups.py b/storyboard/db/api/project_groups.py index 74088a66..15383029 100644 --- a/storyboard/db/api/project_groups.py +++ b/storyboard/db/api/project_groups.py @@ -120,5 +120,10 @@ def project_group_delete_project(project_group_id, project_id): def project_group_delete(project_group_id): project_group = project_group_get(project_group_id) - if project_group: - api_base.entity_hard_delete(models.ProjectGroup, project_group_id) + if not project_group: + raise exc.NotFound(_('Project group not found.')) + + if len(project_group.projects) > 0: + raise exc.NotEmpty(_('Project group must be empty.')) + + api_base.entity_hard_delete(models.ProjectGroup, project_group_id) diff --git a/storyboard/db/api/teams.py b/storyboard/db/api/teams.py index 5615b6ae..1887746f 100644 --- a/storyboard/db/api/teams.py +++ b/storyboard/db/api/teams.py @@ -105,3 +105,15 @@ def team_delete_user(team_id, user_id): session.add(team) return team + + +def team_delete(team_id): + team = team_get(team_id) + + if not team: + raise exc.NotFound(_('Team not found.')) + + if len(team.users) > 0: + raise exc.NotEmpty(_('Team must be empty.')) + + api_base.entity_hard_delete(models.Team, team_id) diff --git a/storyboard/tests/api/test_project_groups.py b/storyboard/tests/api/test_project_groups.py index 1714c335..c91f6fc8 100644 --- a/storyboard/tests/api/test_project_groups.py +++ b/storyboard/tests/api/test_project_groups.py @@ -99,6 +99,36 @@ class TestProjectGroups(base.FunctionalTest): # check for a too short name self.assertRaises(AppError, self.put_json, url, delta) + def test_delete_invalid(self): + # try to delete project group with projects + # she can't be deleted, because + # only empty project groups can be deleted + + response = self.delete(self.resource + '/2', expect_errors=True) + self.assertEqual(400, response.status_code) + + def test_delete(self): + # create new empty project group with name 'testProjectGroup' + response = self.post_json(self.resource, + {'name': 'testProjectGroup', + 'title': 'testProjectGroupTitle'}) + body = json.loads(response.body) + self.assertEqual('testProjectGroup', body['name']) + self.assertEqual('testProjectGroupTitle', body['title']) + + # delete project group with name 'testProjectGroup' + # project group with name 'testProjectGroup' can be deleted, because + # she is empty + # only empty project groups can be deleted + resource = (self.resource + '/%d') % body['id'] + response = self.delete(resource) + self.assertEqual(204, response.status_code) + + # check that project group with name 'testProjectGroup' + # doesn't exist now + response = self.get_json(resource, expect_errors=True) + self.assertEqual(404, response.status_code) + class TestProjectGroupSearch(base.FunctionalTest): def setUp(self): diff --git a/storyboard/tests/api/test_teams.py b/storyboard/tests/api/test_teams.py new file mode 100644 index 00000000..e8398dd6 --- /dev/null +++ b/storyboard/tests/api/test_teams.py @@ -0,0 +1,58 @@ +# 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. + +import json + +from storyboard.tests import base + + +class TestTeams(base.FunctionalTest): + def setUp(self): + super(TestTeams, self).setUp() + self.resource = '/teams' + self.default_headers['Authorization'] = 'Bearer valid_superuser_token' + + def test_delete_invalid(self): + # create new empty team with name 'testTeam' + response = self.post_json(self.resource, {'name': 'testTeam'}) + body = json.loads(response.body) + self.assertEqual('testTeam', body['name']) + + # add user with id = 2 to team with name 'testTeam' + resource = (self.resource + '/%d') % body['id'] + response = self.put_json(resource + '/users', {'user_id': '2'}) + self.assertEqual(200, response.status_code) + + # try to delete team with name 'testTeam' + # team with name 'testTeam' can't be deleted, because she isn't empty + # only empty teams can be deleted + response = self.delete(resource, expect_errors=True) + self.assertEqual(400, response.status_code) + + def test_delete(self): + # create new team with name 'testTeam' + response = self.post_json(self.resource, {'name': 'testTeam'}) + body = json.loads(response.body) + self.assertEqual('testTeam', body['name']) + resource = (self.resource + '/%d') % body['id'] + + # delete team with name 'testTeam' + # team with name 'testTeam' can be deleted, because she is empty + # only empty teams can be deleted + response = self.delete(resource) + self.assertEqual(204, response.status_code) + + # check that team with name 'testTeam' doesn't exist now + response = self.get_json(resource, expect_errors=True) + self.assertEqual(404, response.status_code)