diff options
7 files changed, 259 insertions, 10 deletions
diff --git a/utils/test/testapi/opnfv_testapi/common/raises.py b/utils/test/testapi/opnfv_testapi/common/raises.py index ec6b8a564..55c58c9e2 100644 --- a/utils/test/testapi/opnfv_testapi/common/raises.py +++ b/utils/test/testapi/opnfv_testapi/common/raises.py @@ -26,6 +26,10 @@ class Forbidden(Raiser): code = httplib.FORBIDDEN +class Conflict(Raiser): + code = httplib.CONFLICT + + class NotFound(Raiser): code = httplib.NOT_FOUND diff --git a/utils/test/testapi/opnfv_testapi/resources/handlers.py b/utils/test/testapi/opnfv_testapi/resources/handlers.py index aa77da25d..240fa0a39 100644 --- a/utils/test/testapi/opnfv_testapi/resources/handlers.py +++ b/utils/test/testapi/opnfv_testapi/resources/handlers.py @@ -194,6 +194,7 @@ class GenericApiHandler(web.RequestHandler): data = self.table_cls.from_dict(data) update_req = self._update_requests(data) yield dbapi.db_update(self.table, query, update_req) + self.finish_request() def _update_requests(self, data): request = dict() diff --git a/utils/test/testapi/opnfv_testapi/resources/models.py b/utils/test/testapi/opnfv_testapi/resources/models.py index e8fc532b7..6f04cc236 100644 --- a/utils/test/testapi/opnfv_testapi/resources/models.py +++ b/utils/test/testapi/opnfv_testapi/resources/models.py @@ -48,6 +48,29 @@ class ModelBase(object): return t + @classmethod + def from_dict_with_raise(cls, a_dict): + if a_dict is None: + return None + + attr_parser = cls.attr_parser() + t = cls() + for k, v in a_dict.iteritems(): + if k not in t.__dict__: + raise AttributeError( + '{} has no attribute {}'.format(cls.__name__, k)) + value = v + if isinstance(v, dict) and k in attr_parser: + value = attr_parser[k].from_dict(v) + elif isinstance(v, list) and k in attr_parser: + value = [] + for item in v: + value.append(attr_parser[k].from_dict(item)) + + t.__setattr__(k, value) + + return t + @staticmethod def attr_parser(): return {} diff --git a/utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py b/utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py index c3d471c57..09cce7bc5 100644 --- a/utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py +++ b/utils/test/testapi/opnfv_testapi/resources/scenario_handlers.py @@ -1,5 +1,7 @@ import functools +from opnfv_testapi.common import message +from opnfv_testapi.common import raises from opnfv_testapi.resources import handlers import opnfv_testapi.resources.scenario_models as models from opnfv_testapi.tornado_swagger import swagger @@ -141,6 +143,9 @@ class ScenarioUpdater(object): ('customs', 'post'): self._update_requests_add_customs, ('customs', 'put'): self._update_requests_update_customs, ('customs', 'delete'): self._update_requests_delete_customs, + ('projects', 'post'): self._update_requests_add_projects, + ('projects', 'put'): self._update_requests_update_projects, + ('projects', 'delete'): self._update_requests_delete_projects, } updates[(item, action)](self.data) @@ -201,6 +206,51 @@ class ScenarioUpdater(object): lambda f: f not in self.body, project.customs) + @iter_installers + @iter_versions + def _update_requests_add_projects(self, version): + exists = list() + malformat = list() + for n in self.body: + try: + f_n = models.ScenarioProject.from_dict_with_raise(n) + if not any(o.project == f_n.project for o in version.projects): + version.projects.append(f_n) + else: + exists.append(n['project']) + except Exception as e: + malformat.append(e.message) + if malformat: + raises.BadRequest(message.bad_format(malformat)) + elif exists: + raises.Conflict(message.exist('projects', exists)) + + @iter_installers + @iter_versions + def _update_requests_update_projects(self, version): + exists = list() + malformat = list() + projects = list() + for n in self.body: + try: + f_n = models.ScenarioProject.from_dict_with_raise(n) + if not any(o.project == f_n.project for o in projects): + projects.append(models.ScenarioProject.from_dict(n)) + else: + exists.append(n['project']) + except: + malformat.append(n) + if malformat: + raises.BadRequest(message.bad_format(malformat)) + elif exists: + raises.Forbidden(message.exist('projects', exists)) + version.projects = projects + + @iter_installers + @iter_versions + def _update_requests_delete_projects(self, version): + version.projects = self._remove_projects(version.projects) + def _filter_installers(self, installers): return self._filter('installer', installers) @@ -210,11 +260,19 @@ class ScenarioUpdater(object): def _filter_projects(self, projects): return self._filter('project', projects) + def _remove_projects(self, projects): + return self._remove('project', projects) + def _filter(self, item, items): return filter( lambda f: getattr(f, item) == getattr(self, item), items) + def _remove(self, field, fields): + return filter( + lambda f: getattr(f, field) not in self.body, + fields) + class GenericScenarioUpdateHandler(GenericScenarioHandler): def __init__(self, application, request, **kwargs): @@ -236,7 +294,6 @@ class GenericScenarioUpdateHandler(GenericScenarioHandler): setattr(self, k, v) locators[k] = v self.pure_update(query=self.set_query(locators=locators)) - self.finish_request() def _update_requests(self, data): return ScenarioUpdater(data, @@ -420,3 +477,92 @@ class ScenarioCustomsHandler(GenericScenarioUpdateHandler): 'installer': None, 'version': None, 'project': None}) + + +class ScenarioProjectsHandler(GenericScenarioUpdateHandler): + @swagger.operation(nickname="addProjectsUnderScenario") + def post(self, scenario): + """ + @description: add projects to scenario + @notes: add one or multiple projects + POST /api/v1/scenarios/<scenario_name>/projects? \ + installer=<installer_name>& \ + version=<version_name> + @param body: projects to be added + @type body: C{list} of L{ScenarioProject} + @in body: body + @param installer: installer type + @type installer: L{string} + @in installer: query + @required installer: True + @param version: version + @type version: L{string} + @in version: query + @required version: True + @return 200: projects are added. + @raise 400: bad schema + @raise 409: conflict, project already exists + @raise 404: scenario/installer/version not existed + """ + self.do_update('projects', + 'post', + locators={'scenario': scenario, + 'installer': None, + 'version': None}) + + @swagger.operation(nickname="updateScenarioProjects") + def put(self, scenario): + """ + @description: replace all projects + @notes: substitute all projects, delete existed ones with new provides + PUT /api/v1/scenarios/<scenario_name>/projects? \ + installer=<installer_name>& \ + version=<version_name> + @param body: new projects + @type body: C{list} of L{ScenarioProject} + @in body: body + @param installer: installer type + @type installer: L{string} + @in installer: query + @required installer: True + @param version: version + @type version: L{string} + @in version: query + @required version: True + @return 200: replace projects success. + @raise 400: bad schema + @raise 404: scenario/installer/version not existed + """ + self.do_update('projects', + 'put', + locators={'scenario': scenario, + 'installer': None, + 'version': None}) + + @swagger.operation(nickname="deleteProjectsUnderScenario") + def delete(self, scenario): + """ + @description: delete one or multiple projects + @notes: delete one or multiple projects + DELETE /api/v1/scenarios/<scenario_name>/projects? \ + installer=<installer_name>& \ + version=<version_name> + @param body: projects(names) to be deleted + @type body: C{list} of L{string} + @in body: body + @param installer: installer type + @type installer: L{string} + @in installer: query + @required installer: True + @param version: version + @type version: L{string} + @in version: query + @required version: True + @return 200: delete project(s) success. + @raise 404: scenario/installer/version not existed + """ + self.do_update('projects', + 'delete', + locators={'scenario': scenario, + 'installer': None, + 'version': None}) diff --git a/utils/test/testapi/opnfv_testapi/router/url_mappings.py b/utils/test/testapi/opnfv_testapi/router/url_mappings.py index 4c30eb573..dc3b656a5 100644 --- a/utils/test/testapi/opnfv_testapi/router/url_mappings.py +++ b/utils/test/testapi/opnfv_testapi/router/url_mappings.py @@ -61,6 +61,9 @@ mappings = [ (r"/api/v1/scenarios/([^/]+)/customs", scenario_handlers.ScenarioCustomsHandler), + (r"/api/v1/scenarios/([^/]+)/projects", + scenario_handlers.ScenarioProjectsHandler), + # static path (r'/(.*\.(css|png|gif|js|html|json|map|woff2|woff|ttf))', tornado.web.StaticFileHandler, diff --git a/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_scenario.py b/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_scenario.py index ba45f4bf8..8c54e7d74 100644 --- a/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_scenario.py +++ b/utils/test/testapi/opnfv_testapi/tests/unit/resources/test_scenario.py @@ -168,15 +168,30 @@ class TestScenarioUpdate(TestScenarioBase): self.version, 'functest') + def update_url_fixture(item): + def _update_url_fixture(xstep): + def wrapper(self, *args, **kwargs): + locator = None + if item == 'projects': + locator = 'installer={}&version={}'.format( + self.installer, + self.version) + self.update_url = '{}/{}?{}'.format(self.scenario_url, + item, + locator) + xstep(self, *args, **kwargs) + return wrapper + return _update_url_fixture + def update_partial(operate, expected): - def _update(set_update): + def _update_partial(set_update): @functools.wraps(set_update) - def wrap(self): + def wrapper(self): update, scenario = set_update(self, deepcopy(self.req_d)) code, body = getattr(self, operate)(update, self.scenario) getattr(self, expected)(code, scenario) - return wrap - return _update + return wrapper + return _update_partial @update_partial('_add', '_success') def test_addScore(self, scenario): @@ -232,6 +247,53 @@ class TestScenarioUpdate(TestScenarioBase): return obsoletes, scenario + @update_url_fixture('projects') + @update_partial('_add', '_success') + def test_addProjects_succ(self, scenario): + add = models.ScenarioProject(project='qtip').format() + scenario['installers'][0]['versions'][0]['projects'].append(add) + return [add], scenario + + @update_url_fixture('projects') + @update_partial('_add', '_conflict') + def test_addProjects_already_exist(self, scenario): + add = models.ScenarioProject(project='functest').format() + scenario['installers'][0]['versions'][0]['projects'].append(add) + return [add], scenario + + @update_url_fixture('projects') + @update_partial('_add', '_bad_request') + def test_addProjects_bad_schema(self, scenario): + add = models.ScenarioProject(project='functest').format() + add['score'] = None + scenario['installers'][0]['versions'][0]['projects'].append(add) + return [add], scenario + + @update_url_fixture('projects') + @update_partial('_update', '_success') + def test_updateProjects_succ(self, scenario): + update = models.ScenarioProject(project='qtip').format() + scenario['installers'][0]['versions'][0]['projects'] = [update] + return [update], scenario + + @update_url_fixture('projects') + @update_partial('_update', '_bad_request') + def test_updateProjects_bad_schema(self, scenario): + update = models.ScenarioProject(project='functest').format() + update['score'] = None + scenario['installers'][0]['versions'][0]['projects'] = [update] + return [update], scenario + + @update_url_fixture('projects') + @update_partial('_delete', '_success') + def test_deleteProjects(self, scenario): + deletes = ['functest'] + projects = scenario['installers'][0]['versions'][0]['projects'] + scenario['installers'][0]['versions'][0]['projects'] = filter( + lambda f: f['project'] != 'functest', + projects) + return deletes, scenario + def _add(self, update_req, new_scenario): return self.post_direct_url(self.update_url, update_req) @@ -250,3 +312,6 @@ class TestScenarioUpdate(TestScenarioBase): def _bad_request(self, status, new_scenario): self.assertEqual(status, httplib.BAD_REQUEST) + + def _conflict(self, status, new_scenario): + self.assertEqual(status, httplib.CONFLICT) diff --git a/utils/test/testapi/opnfv_testapi/tornado_swagger/swagger.py b/utils/test/testapi/opnfv_testapi/tornado_swagger/swagger.py index 83f389a6b..6125c9554 100644 --- a/utils/test/testapi/opnfv_testapi/tornado_swagger/swagger.py +++ b/utils/test/testapi/opnfv_testapi/tornado_swagger/swagger.py @@ -94,11 +94,18 @@ class DocParser(object): def _parse_type(self, **kwargs): arg = kwargs.get('arg', None) - body = self._get_body(**kwargs) - self.params.setdefault(arg, {}).update({ - 'name': arg, - 'dataType': body - }) + code = self._parse_epytext_para('code', **kwargs) + link = self._parse_epytext_para('link', **kwargs) + if code is None: + self.params.setdefault(arg, {}).update({ + 'name': arg, + 'type': link + }) + elif code == 'list': + self.params.setdefault(arg, {}).update({ + 'type': 'array', + 'items': {'type': link} + }) def _parse_in(self, **kwargs): arg = kwargs.get('arg', None) |