Add action execution client lib and CLI
Partially implements blueprint mistral-refactor-task-output Change-Id: I18c8e7e531ddf8164891bcf69e7497ef41cb21f8
This commit is contained in:
54
mistralclient/api/v2/action_executions.py
Normal file
54
mistralclient/api/v2/action_executions.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Copyright 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 mistralclient.api import base
|
||||||
|
|
||||||
|
|
||||||
|
class ActionExecution(base.Resource):
|
||||||
|
resource_name = 'ActionExecution'
|
||||||
|
|
||||||
|
|
||||||
|
class ActionExecutionManager(base.ResourceManager):
|
||||||
|
resource_class = ActionExecution
|
||||||
|
|
||||||
|
def update(self, id, state=None, output=None):
|
||||||
|
self._ensure_not_empty(id=id)
|
||||||
|
|
||||||
|
if not (state or output):
|
||||||
|
raise base.APIException(
|
||||||
|
400,
|
||||||
|
"Please provide either state or output for action execution."
|
||||||
|
)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
if state:
|
||||||
|
data['state'] = state
|
||||||
|
|
||||||
|
if output:
|
||||||
|
data['output'] = output
|
||||||
|
|
||||||
|
return self._update('/action_executions/%s' % id, data)
|
||||||
|
|
||||||
|
def list(self, task_execution_id=None):
|
||||||
|
url = '/action_executions'
|
||||||
|
|
||||||
|
if task_execution_id:
|
||||||
|
url = '/tasks/%s/action_executions' % task_execution_id
|
||||||
|
|
||||||
|
return self._list(url, response_key='action_executions')
|
||||||
|
|
||||||
|
def get(self, id):
|
||||||
|
self._ensure_not_empty(id=id)
|
||||||
|
|
||||||
|
return self._get('/action_executions/%s' % id)
|
@@ -16,6 +16,7 @@
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
from mistralclient.api import httpclient
|
from mistralclient.api import httpclient
|
||||||
|
from mistralclient.api.v2 import action_executions
|
||||||
from mistralclient.api.v2 import actions
|
from mistralclient.api.v2 import actions
|
||||||
from mistralclient.api.v2 import cron_triggers
|
from mistralclient.api.v2 import cron_triggers
|
||||||
from mistralclient.api.v2 import environments
|
from mistralclient.api.v2 import environments
|
||||||
@@ -56,6 +57,7 @@ class Client(object):
|
|||||||
self.workflows = workflows.WorkflowManager(self)
|
self.workflows = workflows.WorkflowManager(self)
|
||||||
self.cron_triggers = cron_triggers.CronTriggerManager(self)
|
self.cron_triggers = cron_triggers.CronTriggerManager(self)
|
||||||
self.environments = environments.EnvironmentManager(self)
|
self.environments = environments.EnvironmentManager(self)
|
||||||
|
self.action_executions = action_executions.ActionExecutionManager(self)
|
||||||
|
|
||||||
def authenticate(self, mistral_url=None, username=None, api_key=None,
|
def authenticate(self, mistral_url=None, username=None, api_key=None,
|
||||||
project_name=None, auth_url=None, project_id=None,
|
project_name=None, auth_url=None, project_id=None,
|
||||||
|
186
mistralclient/commands/v2/action_executions.py
Normal file
186
mistralclient/commands/v2/action_executions.py
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
# Copyright 2014 - Mirantis, Inc.
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from cliff import command
|
||||||
|
from cliff import show
|
||||||
|
|
||||||
|
from mistralclient.api.v2 import action_executions
|
||||||
|
from mistralclient.commands.v2 import base
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def format(action_ex=None):
|
||||||
|
columns = (
|
||||||
|
'ID',
|
||||||
|
'Name',
|
||||||
|
'Workflow name',
|
||||||
|
'State',
|
||||||
|
'State info',
|
||||||
|
'Is accepted',
|
||||||
|
)
|
||||||
|
|
||||||
|
if action_ex:
|
||||||
|
data = (
|
||||||
|
action_ex.id,
|
||||||
|
action_ex.name,
|
||||||
|
action_ex.workflow_name,
|
||||||
|
action_ex.state,
|
||||||
|
action_ex.state_info,
|
||||||
|
action_ex.accepted,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
data = (tuple('<none>' for _ in range(len(columns))),)
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
class List(base.MistralLister):
|
||||||
|
"""List all Action executions."""
|
||||||
|
|
||||||
|
def _get_format_function(self):
|
||||||
|
return format
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(List, self).get_parser(prog_name)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'task_execution_id',
|
||||||
|
nargs='?',
|
||||||
|
help='Task execution ID.')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def _get_resources(self, parsed_args):
|
||||||
|
return action_executions.ActionExecutionManager(
|
||||||
|
self.app.client
|
||||||
|
).list(parsed_args.task_execution_id)
|
||||||
|
|
||||||
|
|
||||||
|
class Get(show.ShowOne):
|
||||||
|
"""Show specific Action execution."""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(Get, self).get_parser(prog_name)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'id',
|
||||||
|
help='Action execution ID.')
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
execution = action_executions.ActionExecutionManager(
|
||||||
|
self.app.client
|
||||||
|
).get(parsed_args.id)
|
||||||
|
|
||||||
|
return format(execution)
|
||||||
|
|
||||||
|
|
||||||
|
class Update(show.ShowOne):
|
||||||
|
"""Update specific Action execution."""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(Update, self).get_parser(prog_name)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'id',
|
||||||
|
help='Action execution ID.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--state',
|
||||||
|
dest='state',
|
||||||
|
choices=['IDLE', 'RUNNING', 'SUCCESS', 'ERROR'],
|
||||||
|
help='Action execution state')
|
||||||
|
parser.add_argument(
|
||||||
|
'--output',
|
||||||
|
dest='output',
|
||||||
|
help='Action execution output')
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
output = None
|
||||||
|
if parsed_args.output:
|
||||||
|
try:
|
||||||
|
output = json.loads(parsed_args.output)
|
||||||
|
except:
|
||||||
|
output = json.load(open(parsed_args.output))
|
||||||
|
|
||||||
|
execution = action_executions.ActionExecutionManager(
|
||||||
|
self.app.client
|
||||||
|
).update(
|
||||||
|
parsed_args.id,
|
||||||
|
parsed_args.state,
|
||||||
|
output
|
||||||
|
)
|
||||||
|
|
||||||
|
return format(execution)
|
||||||
|
|
||||||
|
|
||||||
|
class GetOutput(command.Command):
|
||||||
|
"""Show Action execution output data."""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(GetOutput, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'id',
|
||||||
|
help='Action execution ID.')
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
output = action_executions.ActionExecutionManager(
|
||||||
|
self.app.client
|
||||||
|
).get(
|
||||||
|
parsed_args.id
|
||||||
|
).output
|
||||||
|
|
||||||
|
try:
|
||||||
|
output = json.loads(output)
|
||||||
|
output = json.dumps(output, indent=4) + "\n"
|
||||||
|
except:
|
||||||
|
LOG.debug("Task result is not JSON.")
|
||||||
|
|
||||||
|
self.app.stdout.write(output or "\n")
|
||||||
|
|
||||||
|
|
||||||
|
class GetInput(command.Command):
|
||||||
|
"""Show Action execution input data."""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(GetInput, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'id',
|
||||||
|
help='Action execution ID.'
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
result = action_executions.ActionExecutionManager(
|
||||||
|
self.app.client
|
||||||
|
).get(
|
||||||
|
parsed_args.id
|
||||||
|
).input
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = json.loads(result)
|
||||||
|
result = json.dumps(result, indent=4) + "\n"
|
||||||
|
except:
|
||||||
|
LOG.debug("Task result is not JSON.")
|
||||||
|
|
||||||
|
self.app.stdout.write(result or "\n")
|
@@ -25,6 +25,7 @@ from mistralclient.api import client
|
|||||||
import mistralclient.commands.v1.executions
|
import mistralclient.commands.v1.executions
|
||||||
import mistralclient.commands.v1.tasks
|
import mistralclient.commands.v1.tasks
|
||||||
import mistralclient.commands.v1.workbooks
|
import mistralclient.commands.v1.workbooks
|
||||||
|
import mistralclient.commands.v2.action_executions
|
||||||
import mistralclient.commands.v2.actions
|
import mistralclient.commands.v2.actions
|
||||||
import mistralclient.commands.v2.cron_triggers
|
import mistralclient.commands.v2.cron_triggers
|
||||||
import mistralclient.commands.v2.environments
|
import mistralclient.commands.v2.environments
|
||||||
@@ -261,6 +262,16 @@ class MistralShell(app.App):
|
|||||||
mistralclient.commands.v2.environments.Update,
|
mistralclient.commands.v2.environments.Update,
|
||||||
'environment-list': mistralclient.commands.v2.environments.List,
|
'environment-list': mistralclient.commands.v2.environments.List,
|
||||||
'environment-get': mistralclient.commands.v2.environments.Get,
|
'environment-get': mistralclient.commands.v2.environments.Get,
|
||||||
|
'action-execution-list':
|
||||||
|
mistralclient.commands.v2.action_executions.List,
|
||||||
|
'action-execution-get':
|
||||||
|
mistralclient.commands.v2.action_executions.Get,
|
||||||
|
'action-execution-get-input':
|
||||||
|
mistralclient.commands.v2.action_executions.GetInput,
|
||||||
|
'action-execution-get-output':
|
||||||
|
mistralclient.commands.v2.action_executions.GetOutput,
|
||||||
|
'action-execution-update':
|
||||||
|
mistralclient.commands.v2.action_executions.Update,
|
||||||
'execution-create': mistralclient.commands.v2.executions.Create,
|
'execution-create': mistralclient.commands.v2.executions.Create,
|
||||||
'execution-delete': mistralclient.commands.v2.executions.Delete,
|
'execution-delete': mistralclient.commands.v2.executions.Delete,
|
||||||
'execution-update': mistralclient.commands.v2.executions.Update,
|
'execution-update': mistralclient.commands.v2.executions.Update,
|
||||||
|
@@ -28,3 +28,4 @@ class BaseClientV2Test(base.BaseClientTest):
|
|||||||
self.tasks = self._client.tasks
|
self.tasks = self._client.tasks
|
||||||
self.workflows = self._client.workflows
|
self.workflows = self._client.workflows
|
||||||
self.environments = self._client.environments
|
self.environments = self._client.environments
|
||||||
|
self.action_executions = self._client.action_executions
|
||||||
|
80
mistralclient/tests/unit/v2/test_action_executions.py
Normal file
80
mistralclient/tests/unit/v2/test_action_executions.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Copyright 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 mistralclient.api.v2 import action_executions
|
||||||
|
from mistralclient.tests.unit.v2 import base
|
||||||
|
|
||||||
|
# TODO(everyone): later we need additional tests verifying all the errors etc.
|
||||||
|
|
||||||
|
ACTION_EXEC = {
|
||||||
|
'id': "1",
|
||||||
|
'name': 'my_action_execution',
|
||||||
|
'workflow_name': 'my_wf',
|
||||||
|
'state': 'RUNNING',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
URL_TEMPLATE = '/action_executions'
|
||||||
|
URL_TEMPLATE_ID = '/action_executions/%s'
|
||||||
|
|
||||||
|
|
||||||
|
class TestActionExecutions(base.BaseClientV2Test):
|
||||||
|
def test_update(self):
|
||||||
|
mock = self.mock_http_put(content=ACTION_EXEC)
|
||||||
|
body = {
|
||||||
|
'state': ACTION_EXEC['state']
|
||||||
|
}
|
||||||
|
|
||||||
|
action_execution = self.action_executions.update(
|
||||||
|
ACTION_EXEC['id'],
|
||||||
|
ACTION_EXEC['state']
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsNotNone(action_execution)
|
||||||
|
self.assertEqual(action_executions.ActionExecution(
|
||||||
|
self.action_executions, ACTION_EXEC
|
||||||
|
).__dict__, action_execution.__dict__)
|
||||||
|
|
||||||
|
mock.assert_called_once_with(
|
||||||
|
URL_TEMPLATE_ID % ACTION_EXEC['id'], json.dumps(body))
|
||||||
|
|
||||||
|
def test_list(self):
|
||||||
|
mock = self.mock_http_get(
|
||||||
|
content={'action_executions': [ACTION_EXEC]}
|
||||||
|
)
|
||||||
|
|
||||||
|
action_execution_list = self.action_executions.list()
|
||||||
|
|
||||||
|
self.assertEqual(1, len(action_execution_list))
|
||||||
|
action_execution = action_execution_list[0]
|
||||||
|
|
||||||
|
self.assertEqual(action_executions.ActionExecution(
|
||||||
|
self.action_executions, ACTION_EXEC
|
||||||
|
).__dict__, action_execution.__dict__)
|
||||||
|
|
||||||
|
mock.assert_called_once_with(URL_TEMPLATE)
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
mock = self.mock_http_get(content=ACTION_EXEC)
|
||||||
|
|
||||||
|
action_execution = self.action_executions.get(ACTION_EXEC['id'])
|
||||||
|
|
||||||
|
self.assertEqual(action_executions.ActionExecution(
|
||||||
|
self.action_executions, ACTION_EXEC
|
||||||
|
).__dict__, action_execution.__dict__)
|
||||||
|
|
||||||
|
mock.assert_called_once_with(
|
||||||
|
URL_TEMPLATE_ID % ACTION_EXEC['id'])
|
113
mistralclient/tests/unit/v2/test_cli_action_execs.py
Normal file
113
mistralclient/tests/unit/v2/test_cli_action_execs.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# Copyright 2014 Mirantis, Inc.
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from mistralclient.api.v2 import action_executions as action_ex
|
||||||
|
from mistralclient.commands.v2 import action_executions as action_ex_cmd
|
||||||
|
from mistralclient.tests.unit import base
|
||||||
|
|
||||||
|
ACTION_EX_DICT = {
|
||||||
|
'id': '123',
|
||||||
|
'name': 'some',
|
||||||
|
'workflow_name': 'thing',
|
||||||
|
'state': 'RUNNING',
|
||||||
|
'state_info': 'RUNNING somehow.',
|
||||||
|
'accepted': True
|
||||||
|
}
|
||||||
|
|
||||||
|
ACTION_EX_RESULT = {"test": "is", "passed": "successfully"}
|
||||||
|
ACTION_EX_INPUT = {"param1": "val1", "param2": 2}
|
||||||
|
|
||||||
|
ACTION_EX_WITH_OUTPUT_DICT = ACTION_EX_DICT.copy()
|
||||||
|
ACTION_EX_WITH_OUTPUT_DICT.update({'output': json.dumps(ACTION_EX_RESULT)})
|
||||||
|
ACTION_EX_WITH_INPUT_DICT = ACTION_EX_DICT.copy()
|
||||||
|
ACTION_EX_WITH_INPUT_DICT.update({'input': json.dumps(ACTION_EX_INPUT)})
|
||||||
|
|
||||||
|
ACTION_EX = action_ex.ActionExecution(mock, ACTION_EX_DICT)
|
||||||
|
ACTION_EX_WITH_OUTPUT = action_ex.ActionExecution(
|
||||||
|
mock, ACTION_EX_WITH_OUTPUT_DICT
|
||||||
|
)
|
||||||
|
ACTION_EX_WITH_INPUT = action_ex.ActionExecution(
|
||||||
|
mock, ACTION_EX_WITH_INPUT_DICT
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCLIActionExecutions(base.BaseCommandTest):
|
||||||
|
@mock.patch(
|
||||||
|
'mistralclient.api.v2.action_executions.ActionExecutionManager.update'
|
||||||
|
)
|
||||||
|
def test_update(self, mock):
|
||||||
|
mock.return_value = ACTION_EX
|
||||||
|
|
||||||
|
result = self.call(action_ex_cmd.Update,
|
||||||
|
app_args=['id', '--state', 'ERROR'])
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
('123', 'some', 'thing', 'RUNNING',
|
||||||
|
'RUNNING somehow.', True), result[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'mistralclient.api.v2.action_executions.ActionExecutionManager.list'
|
||||||
|
)
|
||||||
|
def test_list(self, mock):
|
||||||
|
mock.return_value = (ACTION_EX,)
|
||||||
|
|
||||||
|
result = self.call(action_ex_cmd.List)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
[('123', 'some', 'thing', 'RUNNING',
|
||||||
|
'RUNNING somehow.', True)], result[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'mistralclient.api.v2.action_executions.ActionExecutionManager.get'
|
||||||
|
)
|
||||||
|
def test_get(self, mock):
|
||||||
|
mock.return_value = ACTION_EX
|
||||||
|
|
||||||
|
result = self.call(action_ex_cmd.Get, app_args=['id'])
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
('123', 'some', 'thing', 'RUNNING',
|
||||||
|
'RUNNING somehow.', True), result[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'mistralclient.api.v2.action_executions.ActionExecutionManager.get'
|
||||||
|
)
|
||||||
|
def test_get_output(self, mock):
|
||||||
|
mock.return_value = ACTION_EX_WITH_OUTPUT
|
||||||
|
|
||||||
|
self.call(action_ex_cmd.GetOutput, app_args=['id'])
|
||||||
|
|
||||||
|
self.app.stdout.write.assert_called_with(
|
||||||
|
json.dumps(ACTION_EX_RESULT, indent=4) + "\n")
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'mistralclient.api.v2.action_executions.ActionExecutionManager.get'
|
||||||
|
)
|
||||||
|
def test_get_input(self, mock):
|
||||||
|
mock.return_value = ACTION_EX_WITH_INPUT
|
||||||
|
|
||||||
|
self.call(action_ex_cmd.GetInput, app_args=['id'])
|
||||||
|
|
||||||
|
self.app.stdout.write.assert_called_with(
|
||||||
|
json.dumps(ACTION_EX_INPUT, indent=4) + "\n"
|
||||||
|
)
|
@@ -43,7 +43,7 @@ TASK_WITH_RESULT = tasks.Task(mock, TASK_WITH_RESULT_DICT)
|
|||||||
TASK_WITH_INPUT = tasks.Task(mock, TASK_WITH_INPUT_DICT)
|
TASK_WITH_INPUT = tasks.Task(mock, TASK_WITH_INPUT_DICT)
|
||||||
|
|
||||||
|
|
||||||
class TestCLIT1asksV2(base.BaseCommandTest):
|
class TestCLITasksV2(base.BaseCommandTest):
|
||||||
@mock.patch('mistralclient.api.v2.tasks.TaskManager.update')
|
@mock.patch('mistralclient.api.v2.tasks.TaskManager.update')
|
||||||
def test_update(self, mock):
|
def test_update(self, mock):
|
||||||
mock.return_value = TASK
|
mock.return_value = TASK
|
||||||
|
Reference in New Issue
Block a user