Repository: fedora-infra/bodhi

Issue:
Add support for editing updates to the CLI



Discussion:

Resolving patch:
diff --git a/bodhi/client/__init__.py b/bodhi/client/__init__.py
--- a/bodhi/client/__init__.py
+++ b/bodhi/client/__init__.py
@@ -22,6 +22,40 @@
 
 from bodhi.client import bindings
 
+new_edit_options = [
+    click.option('--user', envvar='USERNAME'),
+    click.option('--password', prompt=True, hide_input=True),
+    click.option('--type', default='bugfix', help='Update type', required=True,
+                 type=click.Choice(['security', 'bugfix', 'enhancement', 'newpackage'])),
+    click.option('--notes', help='Update description'),
+    click.option('--bugs', help='Comma-seperated list of bug numbers', default=''),
+    click.option('--close-bugs', default=True, is_flag=True, help='Automatically close bugs'),
+    click.option('--request', help='Requested repository',
+                 type=click.Choice(['testing', 'stable', 'unpush'])),
+    click.option('--autokarma', default=True, is_flag=True, help='Enable karma automatism'),
+    click.option('--stable-karma', type=click.INT, help='Stable karma threshold'),
+    click.option('--unstable-karma', type=click.INT, help='Unstable karma threshold'),
+    click.option('--suggest', help='Post-update user suggestion',
+                 type=click.Choice(['logout', 'reboot'])),
+    click.option('--staging', help='Use the staging bodhi instance',
+                 is_flag=True, default=False)]
+
+
+def add_options(options):
+    """ Given a list of click options this creates a decorator that
+    will return a function used to add the options to a click command.
+    :param options: a list of click.options decorator.
+    """
+    def _add_options(func):
+        """ Given a click command and a list of click options this will
+        return the click command decorated with all the options in the list.
+        :param func: a click command function.
+        """
+        for option in reversed(options):
+            func = option(func)
+        return func
+    return _add_options
+
 
 def _warn_if_url_and_staging_set(ctx, param, value):
     """
@@ -66,26 +100,10 @@ def updates():
 
 
 @updates.command()
+@add_options(new_edit_options)
 @click.argument('builds')
-@click.option('--user', envvar='USERNAME')
-@click.option('--password', prompt=True, hide_input=True)
-@click.option('--type', default='bugfix', help='Update type', required=True,
-              type=click.Choice(['security', 'bugfix',
-                                 'enhancement', 'newpackage']))
-@click.option('--notes', help='Update description')
 @click.option('--notes-file', help='Update description from a file')
-@click.option('--bugs', help='Comma-seperated list of bug numbers', default='')
-@click.option('--close-bugs', default=True, is_flag=True, help='Automatically close bugs')
-@click.option('--request', help='Requested repository',
-              type=click.Choice(['testing', 'stable', 'unpush']))
-@click.option('--autokarma', default=True, is_flag=True, help='Enable karma automatism')
-@click.option('--stable-karma', type=click.INT, help='Stable karma threshold')
-@click.option('--unstable-karma', type=click.INT, help='Unstable karma threshold')
-@click.option('--suggest', help='Post-update user suggestion',
-              type=click.Choice(['logout', 'reboot']))
 @click.option('--file', help='A text file containing all the update details')
-@click.option('--staging', help='Use the staging bodhi instance',
-              is_flag=True, default=False)
 @url_option
 def new(user, password, url, **kwargs):
     """
@@ -126,6 +144,38 @@ def new(user, password, url, **kwargs):
             traceback.print_exc()
 
 
+@updates.command()
+@add_options(new_edit_options)
+@click.argument('updateid')
+@url_option
+def edit(user, password, url, **kwargs):
+    """
+    Edit an existing update.
+
+    Args:
+        user (unicode): The username to authenticate as.
+        password (unicode): The user's password.
+        url (unicode): The URL of a Bodhi server to create the update on. Ignored if staging is
+                       True.
+        kwargs (dict): Other keyword arguments passed to us by click.
+    """
+    client = bindings.BodhiClient(base_url=url, username=user, password=password,
+                                  staging=kwargs['staging'])
+
+    try:
+        query_param = {'updateid': kwargs['updateid']}
+        del(kwargs['updateid'])
+        resp = client.query(**query_param)
+        title = resp['updates'][0]['title']
+        kwargs['builds'] = title
+        kwargs['edited'] = title
+
+        resp = client.save(**kwargs)
+        print_resp(resp, client)
+    except bindings.BodhiClientException as e:
+        click.echo(str(e))
+
+
 @updates.command()
 @click.option('--updateid', help='Query by update ID (eg: FEDORA-2015-0001)')
 @click.option('--approved-since', help='Approved after a specific timestamp')


Test patch:
diff --git a/bodhi/tests/client/test___init__.py b/bodhi/tests/client/test___init__.py
--- a/bodhi/tests/client/test___init__.py
+++ b/bodhi/tests/client/test___init__.py
@@ -203,7 +203,7 @@ class TestRequest(unittest.TestCase):
                 return_value=client_test_data.EXAMPLE_UPDATE_MUNCH)
     def test_successful_operation(self, send_request, __init__):
         """
-        Assert that a successful operation is handled properly.
+        Assert that a successful updates request is handled properly.
         """
         runner = testing.CliRunner()
 
@@ -358,3 +358,40 @@ def test_staging_true(self, echo):
         self.assertEqual(result, 'http://localhost:6543')
         echo.assert_called_once_with(
             '\nWarning: url and staging flags are both set. url will be ignored.\n', err=True)
+
+
+class TestEdit(unittest.TestCase):
+    """
+    This class tests the edit() function.
+    """
+    @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
+                mock.MagicMock(return_value='a_csrf_token'))
+    @mock.patch('bodhi.client.bindings.BodhiClient.query',
+                return_value=client_test_data.EXAMPLE_QUERY_MUNCH, autospec=True)
+    @mock.patch('bodhi.client.bindings.BodhiClient.send_request',
+                return_value=client_test_data.EXAMPLE_UPDATE_MUNCH, autospec=True)
+    def test_url_flag(self, send_request, query):
+        """
+        Assert that a successful updates edit request is handled properly.
+        """
+        runner = testing.CliRunner()
+
+        result = runner.invoke(
+            client.edit, ['FEDORA-2017-cc8582d738', '--user', 'bowlofeggs',
+                          '--password', 's3kr3t', '--notes', 'this is an edited note',
+                          '--url', 'http://localhost:6543'])
+
+        self.assertEqual(result.exit_code, 0)
+        bindings_client = query.mock_calls[0][1][0]
+        query.assert_called_with(
+            bindings_client, updateid=u'FEDORA-2017-cc8582d738')
+        bindings_client = send_request.mock_calls[0][1][0]
+        send_request.assert_called_with(
+            bindings_client, 'updates/', auth=True, verb='POST',
+            data={
+                'close_bugs': True, 'stable_karma': None, 'csrf_token': 'a_csrf_token',
+                'staging': False, 'builds': u'nodejs-grunt-wrap-0.3.0-2.fc25', 'autokarma': True,
+                'edited': u'nodejs-grunt-wrap-0.3.0-2.fc25', 'suggest': None,
+                'notes': u'this is an edited note', 'request': None, 'bugs': u'',
+                'unstable_karma': None, 'type': 'bugfix'})
+        self.assertEqual(bindings_client.base_url, 'http://localhost:6543/')