diff --git a/mistralclient/api/v2/actions.py b/mistralclient/api/v2/actions.py index 0c2eefff..ea5e12ef 100644 --- a/mistralclient/api/v2/actions.py +++ b/mistralclient/api/v2/actions.py @@ -12,9 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import six + from mistralclient.api import base +urlparse = six.moves.urllib.parse + + class Action(base.Resource): resource_name = 'Action' @@ -52,8 +57,28 @@ class ActionManager(base.ResourceManager): return [self.resource_class(self, resource_data) for resource_data in base.extract_json(resp, 'actions')] - def list(self): - return self._list('/actions', response_key='actions') + def list(self, marker='', limit=None, sort_keys='', sort_dirs=''): + qparams = {} + + if marker: + qparams['marker'] = marker + + if limit: + qparams['limit'] = limit + + if sort_keys: + qparams['sort_keys'] = sort_keys + + if sort_dirs: + qparams['sort_dirs'] = sort_dirs + + query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) + if qparams else "") + + return self._list( + '/actions%s' % query_string, + response_key='actions', + ) def get(self, name): self._ensure_not_empty(name=name) diff --git a/mistralclient/api/v2/executions.py b/mistralclient/api/v2/executions.py index bea94f11..d158964b 100644 --- a/mistralclient/api/v2/executions.py +++ b/mistralclient/api/v2/executions.py @@ -19,6 +19,9 @@ import six from mistralclient.api import base +urlparse = six.moves.urllib.parse + + class Execution(base.Resource): resource_name = 'Execution' @@ -64,8 +67,28 @@ class ExecutionManager(base.ResourceManager): return self._update('/executions/%s' % id, data) - def list(self): - return self._list('/executions', response_key='executions') + def list(self, marker='', limit=None, sort_keys='', sort_dirs=''): + qparams = {} + + if marker: + qparams['marker'] = marker + + if limit: + qparams['limit'] = limit + + if sort_keys: + qparams['sort_keys'] = sort_keys + + if sort_dirs: + qparams['sort_dirs'] = sort_dirs + + query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) + if qparams else "") + + return self._list( + '/executions%s' % query_string, + response_key='executions', + ) def get(self, id): self._ensure_not_empty(id=id) diff --git a/mistralclient/api/v2/workflows.py b/mistralclient/api/v2/workflows.py index 6a530fb3..273c2b0c 100644 --- a/mistralclient/api/v2/workflows.py +++ b/mistralclient/api/v2/workflows.py @@ -13,9 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import six + from mistralclient.api import base +urlparse = six.moves.urllib.parse + + class Workflow(base.Resource): resource_name = 'Workflow' @@ -53,8 +58,28 @@ class WorkflowManager(base.ResourceManager): return [self.resource_class(self, resource_data) for resource_data in base.extract_json(resp, 'workflows')] - def list(self): - return self._list('/workflows', response_key='workflows') + def list(self, marker='', limit=None, sort_keys='', sort_dirs=''): + qparams = {} + + if marker: + qparams['marker'] = marker + + if limit: + qparams['limit'] = limit + + if sort_keys: + qparams['sort_keys'] = sort_keys + + if sort_dirs: + qparams['sort_dirs'] = sort_dirs + + query_string = ("?%s" % urlparse.urlencode(list(qparams.items())) + if qparams else "") + + return self._list( + '/workflows%s' % query_string, + response_key='workflows', + ) def get(self, name): self._ensure_not_empty(name=name) diff --git a/mistralclient/tests/unit/v2/base.py b/mistralclient/tests/unit/v2/base.py index 2eac15ba..b72c968f 100644 --- a/mistralclient/tests/unit/v2/base.py +++ b/mistralclient/tests/unit/v2/base.py @@ -29,4 +29,5 @@ class BaseClientV2Test(base.BaseClientTest): self.workflows = self._client.workflows self.environments = self._client.environments self.action_executions = self._client.action_executions + self.actions = self._client.actions self.services = self._client.services diff --git a/mistralclient/tests/unit/v2/test_actions.py b/mistralclient/tests/unit/v2/test_actions.py new file mode 100644 index 00000000..5a538b38 --- /dev/null +++ b/mistralclient/tests/unit/v2/test_actions.py @@ -0,0 +1,123 @@ +# Copyright 2015 Huawei Technologies Co., Ltd. +# +# 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 mistralclient.api.v2 import actions +from mistralclient.tests.unit.v2 import base + + +ACTION_DEF = """ +--- +version: 2.0 + +my_action: + base: std.echo + base-input: + output: 'Bye!' + output: + info: <% $.output %> +""" + +ACTION = { + 'id': '123', + 'name': 'my_action', + 'input': '', + 'definition': ACTION_DEF +} + +URL_TEMPLATE = '/actions' +URL_TEMPLATE_SCOPE = '/actions?scope=private' +URL_TEMPLATE_NAME = '/actions/%s' + + +class TestActionsV2(base.BaseClientV2Test): + def test_create(self): + mock = self.mock_http_post(content={'actions': [ACTION]}) + + actions = self.actions.create(ACTION_DEF) + + self.assertIsNotNone(actions) + self.assertEqual(ACTION_DEF, actions[0].definition) + + mock.assert_called_once_with( + URL_TEMPLATE_SCOPE, + ACTION_DEF, + headers={'content-type': 'text/plain'} + ) + + def test_update(self): + mock = self.mock_http_put(content={'actions': [ACTION]}) + + actions = self.actions.update(ACTION_DEF) + + self.assertIsNotNone(actions) + self.assertEqual(ACTION_DEF, actions[0].definition) + + mock.assert_called_once_with( + URL_TEMPLATE_SCOPE, + ACTION_DEF, + headers={'content-type': 'text/plain'} + ) + + def test_list(self): + mock = self.mock_http_get(content={'actions': [ACTION]}) + + action_list = self.actions.list() + + self.assertEqual(1, len(action_list)) + + action = action_list[0] + + self.assertEqual( + actions.Action(self.actions, ACTION).to_dict(), + action.to_dict() + ) + + mock.assert_called_once_with(URL_TEMPLATE) + + def test_list_with_pagination(self): + mock = self.mock_http_get( + content={'actions': [ACTION], 'next': '/actions?fake'} + ) + + action_list = self.actions.list( + limit=1, + sort_keys='created_at', + sort_dirs='asc' + ) + + self.assertEqual(1, len(action_list)) + + # The url param order is unpredictable. + self.assertIn('limit=1', mock.call_args[0][0]) + self.assertIn('sort_keys=created_at', mock.call_args[0][0]) + self.assertIn('sort_dirs=asc', mock.call_args[0][0]) + + def test_get(self): + mock = self.mock_http_get(content=ACTION) + + action = self.actions.get('action') + + self.assertIsNotNone(action) + self.assertEqual( + actions.Action(self.actions, ACTION).to_dict(), + action.to_dict() + ) + + mock.assert_called_once_with(URL_TEMPLATE_NAME % 'action') + + def test_delete(self): + mock = self.mock_http_delete(status_code=204) + + self.actions.delete('action') + + mock.assert_called_once_with(URL_TEMPLATE_NAME % 'action') diff --git a/mistralclient/tests/unit/v2/test_executions.py b/mistralclient/tests/unit/v2/test_executions.py index 71677e33..02089293 100644 --- a/mistralclient/tests/unit/v2/test_executions.py +++ b/mistralclient/tests/unit/v2/test_executions.py @@ -93,6 +93,24 @@ class TestExecutionsV2(base.BaseClientV2Test): ex.to_dict()) mock.assert_called_once_with(URL_TEMPLATE) + def test_list_with_pagination(self): + mock = self.mock_http_get( + content={'executions': [EXEC], 'next': '/executions?fake'} + ) + + execution_list = self.executions.list( + limit=1, + sort_keys='created_at', + sort_dirs='asc' + ) + + self.assertEqual(1, len(execution_list)) + + # The url param order is unpredictable. + self.assertIn('limit=1', mock.call_args[0][0]) + self.assertIn('sort_keys=created_at', mock.call_args[0][0]) + self.assertIn('sort_dirs=asc', mock.call_args[0][0]) + def test_get(self): mock = self.mock_http_get(content=EXEC) diff --git a/mistralclient/tests/unit/v2/test_workflows.py b/mistralclient/tests/unit/v2/test_workflows.py new file mode 100644 index 00000000..bc592b1b --- /dev/null +++ b/mistralclient/tests/unit/v2/test_workflows.py @@ -0,0 +1,123 @@ +# Copyright 2015 Huawei Technologies Co., Ltd. +# +# 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 mistralclient.api.v2 import workflows +from mistralclient.tests.unit.v2 import base + + +WF_DEF = """ +--- +version: 2.0 + +my_wf: + type: direct + + tasks: + task1: + action: std.echo output="hello, world" +""" + +WORKFLOW = { + 'id': '123', + 'name': 'my_wf', + 'input': '', + 'definition': WF_DEF +} + +URL_TEMPLATE = '/workflows' +URL_TEMPLATE_SCOPE = '/workflows?scope=private' +URL_TEMPLATE_NAME = '/workflows/%s' + + +class TestWorkflowsV2(base.BaseClientV2Test): + def test_create(self): + mock = self.mock_http_post(content={'workflows': [WORKFLOW]}) + + wfs = self.workflows.create(WF_DEF) + + self.assertIsNotNone(wfs) + self.assertEqual(WF_DEF, wfs[0].definition) + + mock.assert_called_once_with( + URL_TEMPLATE_SCOPE, + WF_DEF, + headers={'content-type': 'text/plain'} + ) + + def test_update(self): + mock = self.mock_http_put(content={'workflows': [WORKFLOW]}) + + wfs = self.workflows.update(WF_DEF) + + self.assertIsNotNone(wfs) + self.assertEqual(WF_DEF, wfs[0].definition) + + mock.assert_called_once_with( + URL_TEMPLATE_SCOPE, + WF_DEF, + headers={'content-type': 'text/plain'} + ) + + def test_list(self): + mock = self.mock_http_get(content={'workflows': [WORKFLOW]}) + + workflows_list = self.workflows.list() + + self.assertEqual(1, len(workflows_list)) + + wf = workflows_list[0] + + self.assertEqual( + workflows.Workflow(self.workflows, WORKFLOW).to_dict(), + wf.to_dict() + ) + + mock.assert_called_once_with(URL_TEMPLATE) + + def test_list_with_pagination(self): + mock = self.mock_http_get( + content={'workflows': [WORKFLOW], 'next': '/workflows?fake'} + ) + + workflows_list = self.workflows.list( + limit=1, + sort_keys='created_at', + sort_dirs='asc' + ) + + self.assertEqual(1, len(workflows_list)) + + # The url param order is unpredictable. + self.assertIn('limit=1', mock.call_args[0][0]) + self.assertIn('sort_keys=created_at', mock.call_args[0][0]) + self.assertIn('sort_dirs=asc', mock.call_args[0][0]) + + def test_get(self): + mock = self.mock_http_get(content=WORKFLOW) + + wf = self.workflows.get('wf') + + self.assertIsNotNone(wf) + self.assertEqual( + workflows.Workflow(self.workflows, WORKFLOW).to_dict(), + wf.to_dict() + ) + + mock.assert_called_once_with(URL_TEMPLATE_NAME % 'wf') + + def test_delete(self): + mock = self.mock_http_delete(status_code=204) + + self.workflows.delete('wf') + + mock.assert_called_once_with(URL_TEMPLATE_NAME % 'wf')