From 4434b1f5f46fe4ec84582e57c265f11ff5c368b5 Mon Sep 17 00:00:00 2001
From: Chenxiong Qi <cqi@redhat.com>
Date: Jun 08 2018 09:02:18 +0000
Subject: [PATCH 1/3] Extend override by number of days or specific date


This patch adds a new subcommand extend to command override, which is
able to extend an override expiration date.

Fixes #67

Signed-off-by: Chenxiong Qi <cqi@redhat.com>

---

diff --git a/fedpkg/__init__.py b/fedpkg/__init__.py
index 7f09097..fa8e08b 100644
--- a/fedpkg/__init__.py
+++ b/fedpkg/__init__.py
@@ -15,12 +15,31 @@ import git
 import re
 import platform
 
-from datetime import datetime
+from datetime import datetime, timedelta
 
 from . import cli  # noqa
 from .lookaside import FedoraLookasideCache
 from pyrpkg.utils import cached_property
 
+try:
+    from bodhi.client.bindings import BodhiClient as _BodhiClient
+except ImportError:
+    _BodhiClient = None
+
+
+if _BodhiClient is not None:
+    class BodhiClient(_BodhiClient):
+        def extend_override(self, override, expiration_date):
+            data = dict(
+                nvr=override['nvr'],
+                notes=override['notes'],
+                expiration_date=expiration_date,
+                edited=override['nvr'],
+                csrf_token=self.csrf(),
+            )
+            return self.send_request(
+                'overrides/', verb='POST', auth=True, data=data)
+
 
 class Commands(pyrpkg.Commands):
 
@@ -238,8 +257,6 @@ class Commands(pyrpkg.Commands):
 
     def create_buildroot_override(self, bodhi_config, build, duration,
                                   notes=''):
-        from bodhi.client.bindings import BodhiClient
-
         bodhi = BodhiClient(staging=bodhi_config['staging'])
         result = bodhi.list_overrides(builds=build)
         if result['total'] == 0:
@@ -271,6 +288,64 @@ class Commands(pyrpkg.Commands):
                 self.log.info('Buildroot override for %s already exists and '
                               'not expired.', build)
 
+    def extend_buildroot_override(self, bodhi_config, build, duration):
+        bodhi = BodhiClient(username=self.user,
+                            staging=bodhi_config['staging'])
+        result = bodhi.list_overrides(builds=build)
+
+        if result['total'] == 0:
+            self.log.info('No buildroot override for build %s', build)
+            return
+
+        override = result['overrides'][0]
+        expiration_date = datetime.strptime(override['expiration_date'],
+                                            '%Y-%m-%d %H:%M:%S')
+        utcnow = datetime.utcnow()
+
+        # bodhi-client binding API save_override calculates expiration
+        # date by adding duration to datetime.utcnow
+        # This comparison should use utcnow as well.
+        if expiration_date < utcnow:
+            self.log.debug('Buildroot override is expired on %s',
+                           override['expiration_date'])
+            self.log.debug('Extend expiration date from today in UTC.')
+            base_date = utcnow
+        else:
+            self.log.debug(
+                'Extend expiration date from future expiration date.')
+            base_date = expiration_date
+
+        if isinstance(duration, datetime):
+            if duration < utcnow:
+                raise pyrpkg.rpkgError(
+                    'At least, specified expiration date {0} should be '
+                    'future date.'.format(duration.strftime('%Y-%m-%d')))
+            if duration < base_date:
+                self.log.warning(
+                    'Expiration date %s to be set is before override current'
+                    ' expiration date %s',
+                    duration, base_date)
+            # Keep time unchanged
+            new_expiration_date = datetime(
+                year=duration.year,
+                month=duration.month,
+                day=duration.day,
+                hour=base_date.hour,
+                minute=base_date.minute,
+                second=base_date.second)
+        else:
+            new_expiration_date = base_date + timedelta(days=duration)
+
+        try:
+            self.log.debug('Extend override expiration date to %s',
+                           new_expiration_date)
+            override = bodhi.extend_override(override, new_expiration_date)
+        except Exception as e:
+            self.log.error('Cannot extend override expiration.')
+            raise pyrpkg.rpkgError(str(e))
+        else:
+            self.log.info(bodhi.override_str(override, minimal=False))
+
 
 if __name__ == "__main__":
     from fedpkg.__main__ import main
diff --git a/fedpkg/cli.py b/fedpkg/cli.py
index d55259a..9442030 100644
--- a/fedpkg/cli.py
+++ b/fedpkg/cli.py
@@ -22,6 +22,8 @@ import pkg_resources
 import six
 import textwrap
 
+from datetime import datetime
+
 from six.moves.configparser import NoSectionError
 from six.moves.configparser import NoOptionError
 from six.moves.urllib_parse import urlparse
@@ -243,6 +245,16 @@ and created:
             raise argparse.ArgumentTypeError(
                 'override should have 1 day to exist at least.')
 
+        def validate_extend_duration(value):
+            if value.isdigit():
+                return validate_duration(value)
+            match = re.match(r'(\d{4})-(\d{1,2})-(\d{1,2})', value)
+            if not match:
+                raise argparse.ArgumentTypeError(
+                    'Invalid expiration date. Valid format: yyyy-mm-dd.')
+            y, m, d = match.groups()
+            return datetime(year=int(y), month=int(m), day=int(d))
+
         override_parser = self.subparsers.add_parser(
             'override',
             help='Manage buildroot overrides')
@@ -286,6 +298,48 @@ Create for a specified build:
                  ' guessed from current release branch.')
         create_parser.set_defaults(command=self.create_buildroot_override)
 
+        extend_parser = override_subparser.add_parser(
+            'extend',
+            help='Extend buildroot override expiration',
+            formatter_class=argparse.RawDescriptionHelpFormatter,
+            description='''\
+Extend buildroot override expiration.
+
+An override expiration date could be extended by number of days or a specific
+date. If override is expired, expiration date will be extended from the date
+of today, otherwise from the expiration date.
+
+Command extend accepts an optional build NVR to find out its override in
+advance. If there is no such an override created previously, please use
+`override create` to create one. If build NVR is omitted, command extend must
+run inside a package repository and build will be guessed from current release
+branch.
+
+Examples:
+
+1. To give 2 days to override for build somepkg-0.2-1.fc28
+
+    {0} override extend 2 somepkg-0.2-1.fc28
+
+2. To extend expiration date to 2018-7-1
+
+    cd /path/to/somepkg
+    {0} switch-branch f28
+    {0} override extend 2018-7-1
+'''.format(self.name))
+        extend_parser.add_argument(
+            'duration',
+            type=validate_extend_duration,
+            help='Number of days to extend the expiration date, or set the '
+                 'expiration date directly. Valid date format: yyyy-mm-dd.')
+        extend_parser.add_argument(
+            'NVR',
+            nargs='?',
+            help='Buildroot override expiration for this build will be '
+                 'extended. If omitted, build will be guessed from current '
+                 'release branch.')
+        extend_parser.set_defaults(command=self.extend_buildroot_override)
+
     # Target functions go here
     def retire(self):
         # Skip if package is already retired...
@@ -698,3 +752,15 @@ suggest_reboot=False
             build=self.args.NVR or self.cmd.nvr,
             duration=self.args.duration,
             notes=self.args.notes)
+
+    def extend_buildroot_override(self):
+        check_bodhi_version()
+        if self.args.NVR:
+            if not self.cmd.anon_kojisession.getBuild(self.args.NVR):
+                raise rpkgError(
+                    'Build {0} does not exist.'.format(self.args.NVR))
+        bodhi_config = self._get_bodhi_config()
+        self.cmd.extend_buildroot_override(
+            bodhi_config,
+            build=self.args.NVR or self.cmd.nvr,
+            duration=self.args.duration)
diff --git a/test/test_cli.py b/test/test_cli.py
index a71c6ba..4024505 100644
--- a/test/test_cli.py
+++ b/test/test_cli.py
@@ -29,6 +29,7 @@ except ImportError:
 from datetime import datetime, timedelta
 from tempfile import mkdtemp
 from os import rmdir
+from freezegun import freeze_time
 
 import six
 from six.moves.configparser import NoOptionError
@@ -1138,7 +1139,7 @@ class TestBodhiOverride(CliTestCase):
     """Test command override"""
 
     @patch('fedpkg.cli.check_bodhi_version')
-    @patch('bodhi.client.bindings.BodhiClient')
+    @patch('fedpkg.BodhiClient')
     def test_create_for_given_build(self, BodhiClient, check_bodhi_version):
         bodhi_client = BodhiClient.return_value
         bodhi_client.list_overrides.return_value = {'total': 0}
@@ -1171,7 +1172,7 @@ class TestBodhiOverride(CliTestCase):
             notes='build for fedpkg')
 
     @patch('fedpkg.cli.check_bodhi_version')
-    @patch('bodhi.client.bindings.BodhiClient')
+    @patch('fedpkg.BodhiClient')
     @patch('fedpkg.Commands.nvr', new_callable=PropertyMock)
     def test_create_from_current_branch(
             self, nvr, BodhiClient, check_bodhi_version):
@@ -1195,7 +1196,7 @@ class TestBodhiOverride(CliTestCase):
             notes='build for fedpkg')
 
     @patch('fedpkg.cli.check_bodhi_version')
-    @patch('bodhi.client.bindings.BodhiClient')
+    @patch('fedpkg.BodhiClient')
     @patch('fedpkg.Commands.nvr', new_callable=PropertyMock)
     def test_override_already_exists_but_expired(
             self, nvr, BodhiClient, check_bodhi_version):
@@ -1226,7 +1227,7 @@ class TestBodhiOverride(CliTestCase):
                     'rpkg-1.54-2.fc28')
 
     @patch('fedpkg.cli.check_bodhi_version')
-    @patch('bodhi.client.bindings.BodhiClient')
+    @patch('fedpkg.BodhiClient')
     @patch('fedpkg.Commands.nvr', new_callable=PropertyMock)
     def test_override_already_exists_but_not_expired(
             self, nvr, BodhiClient, check_bodhi_version):
@@ -1254,3 +1255,264 @@ class TestBodhiOverride(CliTestCase):
                 log.info.assert_any_call(
                     'Buildroot override for %s already exists and not '
                     'expired.', 'rpkg-1.54-2.fc28')
+
+
+@unittest.skipUnless(bodhi, 'Skip if no supported bodhi-client is available')
+class TestBodhiOverrideExtend(CliTestCase):
+    """Test command `override extend`"""
+
+    def setUp(self):
+        super(TestBodhiOverrideExtend, self).setUp()
+
+        # No need to check bodhi version for tests
+        self.cbv_p = patch('fedpkg.cli.check_bodhi_version')
+        self.cbv_p.start()
+
+        self.anon_kojisession_p = patch('fedpkg.Commands.anon_kojisession',
+                                        new_callable=PropertyMock)
+        self.anon_kojisession_m = self.anon_kojisession_p.start()
+        self.kojisession = self.anon_kojisession_m.return_value
+
+        # Fake build returned from Koji for the specified build NVR in tests
+        self.kojisession.getBuild.return_value = {'build_id': 1}
+
+    def tearDown(self):
+        self.anon_kojisession_p.stop()
+        self.cbv_p.stop()
+        super(TestBodhiOverrideExtend, self).tearDown()
+
+    def test_specified_build_not_exist(self):
+        self.kojisession.getBuild.return_value = None
+
+        cli_cmd = [
+            'fedpkg', '--path', self.cloned_repo_path,
+            'override', 'extend', '7', 'somepkg-1.54-2.fc28'
+        ]
+
+        with patch('sys.argv', new=cli_cmd):
+            cli = self.new_cli()
+            six.assertRaisesRegex(
+                self, rpkgError, 'Build somepkg-1.54-2.fc28 does not exist.',
+                cli.extend_buildroot_override)
+
+    @patch('fedpkg.BodhiClient.list_overrides')
+    def test_no_override_for_build(self, list_overrides):
+        list_overrides.return_value = {'total': 0}
+
+        cli_cmd = [
+            'fedpkg', '--path', self.cloned_repo_path,
+            'override', 'extend', '7', 'somepkg-1.54-2.fc28'
+        ]
+
+        with patch('sys.argv', new=cli_cmd):
+            cli = self.new_cli()
+            with patch.object(cli.cmd, 'log') as log:
+                cli.extend_buildroot_override()
+                log.info.assert_any_call('No buildroot override for build %s',
+                                         'somepkg-1.54-2.fc28')
+
+    @patch('fedpkg.BodhiClient.list_overrides')
+    @patch('fedpkg.BodhiClient.csrf')
+    @patch('fedpkg.BodhiClient.send_request')
+    def test_extend_override_by_days(
+            self, send_request, csrf, list_overrides):
+        utcnow = datetime.utcnow()
+        override_expiration_date = utcnow + timedelta(days=7)
+        build_nvr = 'somepkg-1.54-2.fc28'
+        build_override = {
+            'expiration_date':
+                override_expiration_date.strftime('%Y-%m-%d %H:%M:%S'),
+            'nvr': build_nvr,
+            'notes': 'build for other package',
+            'build': {'nvr': build_nvr},
+            'submitter': {'name': 'someone'},
+            'expired_date': utcnow - timedelta(days=20)
+        }
+
+        list_overrides.return_value = {
+            'total': 1,
+            'overrides': [build_override]
+        }
+        edited_override = build_override.copy()
+        expected_expiration_date = override_expiration_date + timedelta(days=2)
+        edited_override['expiration_date'] = \
+            expected_expiration_date.strftime('%Y-%m-%d %H:%M:%S')
+        send_request.return_value = edited_override
+
+        cli_cmd = [
+            'fedpkg', '--path', self.cloned_repo_path,
+            'override', 'extend', '2', build_nvr
+        ]
+
+        with patch('sys.argv', new=cli_cmd):
+            cli = self.new_cli()
+            cli.extend_buildroot_override()
+
+        # Ensure no microsecond is included in the expected expiration data
+        new_date = override_expiration_date + timedelta(days=2)
+        expected_expiration_date = datetime(year=new_date.year,
+                                            month=new_date.month,
+                                            day=new_date.day,
+                                            hour=new_date.hour,
+                                            minute=new_date.minute,
+                                            second=new_date.second)
+        request_data = {
+            'expiration_date': expected_expiration_date,
+            'nvr': build_nvr,
+            'notes': build_override['notes'],
+            'edited': build_nvr,
+            'csrf_token': csrf.return_value,
+        }
+        send_request.assert_called_once_with(
+            'overrides/', verb='POST', auth=True, data=request_data)
+
+    @patch('fedpkg.BodhiClient.list_overrides')
+    @patch('fedpkg.BodhiClient.csrf')
+    @patch('fedpkg.BodhiClient.send_request')
+    def test_extend_override_by_specific_date(
+            self, send_request, csrf, list_overrides):
+        utcnow = datetime.utcnow()
+        override_expiration_date = utcnow + timedelta(days=7)
+        build_nvr = 'somepkg-1.54-2.fc28'
+        build_override = {
+            'expiration_date':
+                override_expiration_date.strftime('%Y-%m-%d %H:%M:%S'),
+            'nvr': build_nvr,
+            'notes': 'build for other package',
+            'build': {'nvr': build_nvr},
+            'submitter': {'name': 'someone'},
+            'expired_date': utcnow - timedelta(days=20)
+        }
+
+        list_overrides.return_value = {
+            'total': 1,
+            'overrides': [build_override]
+        }
+
+        new_expiration_date = override_expiration_date + timedelta(days=4)
+        # Ensure no microsecond is included in the expected expiration data
+        expected_expiration_date = datetime(
+            year=new_expiration_date.year,
+            month=new_expiration_date.month,
+            day=new_expiration_date.day,
+            hour=override_expiration_date.hour,
+            minute=override_expiration_date.minute,
+            second=override_expiration_date.second)
+
+        edited_override = build_override.copy()
+        edited_override['expiration_date'] = \
+            expected_expiration_date.strftime('%Y-%m-%d %H:%M:%S')
+        send_request.return_value = edited_override
+
+        cli_cmd = [
+            'fedpkg', '--path', self.cloned_repo_path,
+            'override', 'extend', new_expiration_date.strftime('%Y-%m-%d'),
+            build_nvr
+        ]
+
+        with patch('sys.argv', new=cli_cmd):
+            cli = self.new_cli()
+            cli.extend_buildroot_override()
+
+        request_data = {
+            'expiration_date': expected_expiration_date,
+            'nvr': build_nvr,
+            'notes': build_override['notes'],
+            'edited': build_nvr,
+            'csrf_token': csrf.return_value,
+        }
+        send_request.assert_called_once_with(
+            'overrides/', verb='POST', auth=True, data=request_data)
+
+    @patch('fedpkg.BodhiClient.list_overrides')
+    @patch('fedpkg.BodhiClient.csrf')
+    @patch('fedpkg.BodhiClient.send_request')
+    @freeze_time('2018-06-08 16:17:30')
+    def test_extend_for_expired_override(
+            self, send_request, csrf, list_overrides):
+        utcnow = datetime.utcnow()
+        # Make override expired
+        override_expiration_date = utcnow - timedelta(days=7)
+        build_nvr = 'somepkg-1.54-2.fc28'
+        build_override = {
+            'expiration_date':
+                override_expiration_date.strftime('%Y-%m-%d %H:%M:%S'),
+            'nvr': build_nvr,
+            'notes': 'build for other package',
+            'build': {'nvr': build_nvr},
+            'submitter': {'name': 'someone'},
+            'expired_date': utcnow - timedelta(days=20)
+        }
+
+        list_overrides.return_value = {
+            'total': 1,
+            'overrides': [build_override]
+        }
+
+        # Ensure no microsecond is included in the expected expiration data
+        new_date = utcnow + timedelta(days=2)
+        expected_expiration_date = datetime(
+            year=new_date.year,
+            month=new_date.month,
+            day=new_date.day,
+            hour=new_date.hour,
+            minute=new_date.minute,
+            second=new_date.second)
+
+        edited_override = build_override.copy()
+        edited_override['expiration_date'] = \
+            expected_expiration_date.strftime('%Y-%m-%d %H:%M:%S')
+        send_request.return_value = edited_override
+
+        cli_cmd = [
+            'fedpkg', '--path', self.cloned_repo_path,
+            'override', 'extend', '2', build_nvr
+        ]
+
+        with patch('sys.argv', new=cli_cmd):
+            cli = self.new_cli()
+            cli.extend_buildroot_override()
+
+        request_data = {
+            'expiration_date': expected_expiration_date,
+            'nvr': build_nvr,
+            'notes': build_override['notes'],
+            'edited': build_nvr,
+            'csrf_token': csrf.return_value,
+        }
+        send_request.assert_called_once_with(
+            'overrides/', verb='POST', auth=True, data=request_data)
+
+    @patch('fedpkg.BodhiClient.list_overrides')
+    @patch('fedpkg.BodhiClient.csrf')
+    @patch('fedpkg.BodhiClient.extend_override')
+    def test_error_handled_properly_when_fail_to_request(
+            self, extend_override, csrf, list_overrides):
+        extend_override.side_effect = Exception
+
+        utcnow = datetime.utcnow()
+        override_expiration_date = utcnow + timedelta(days=7)
+        build_nvr = 'somepkg-1.54-2.fc28'
+
+        list_overrides.return_value = {
+            'total': 1,
+            'overrides': [{
+                'expiration_date':
+                    override_expiration_date.strftime('%Y-%m-%d %H:%M:%S'),
+                'nvr': build_nvr,
+                'notes': 'build for other package',
+                'build': {'nvr': build_nvr},
+                'submitter': {'name': 'someone'},
+                'expired_date': utcnow - timedelta(days=20)
+            }]
+        }
+
+        cli_cmd = [
+            'fedpkg', '--path', self.cloned_repo_path,
+            'override', 'extend', '2', build_nvr
+        ]
+
+        with patch('sys.argv', new=cli_cmd):
+            cli = self.new_cli()
+            six.assertRaisesRegex(self, rpkgError, '',
+                                  cli.extend_buildroot_override)
diff --git a/tests-requirements.txt b/tests-requirements.txt
index cdfe964..81997f0 100644
--- a/tests-requirements.txt
+++ b/tests-requirements.txt
@@ -1,3 +1,4 @@
 mock == 1.0.1
 nose == 1.3.7
 nose-cov
+freezegun

From ec096d2992a7ef3cb57305352db215832497628f Mon Sep 17 00:00:00 2001
From: Chenxiong Qi <cqi@redhat.com>
Date: Jun 11 2018 03:39:45 +0000
Subject: [PATCH 2/3] Refine command override create


* Pass username to BodhiClient class
* Use datetime.utcnow instead of datetime.now, which is same as
  what bodhi-client API does
* Print created override by call to BodhiClient.override_str rather than
  printing by fedpkg itself
* Show fedpkg or fedpkg-stage in description accordingly
* Update tests.

Signed-off-by: Chenxiong Qi <cqi@redhat.com>

---

diff --git a/fedpkg/__init__.py b/fedpkg/__init__.py
index fa8e08b..77e500e 100644
--- a/fedpkg/__init__.py
+++ b/fedpkg/__init__.py
@@ -257,7 +257,8 @@ class Commands(pyrpkg.Commands):
 
     def create_buildroot_override(self, bodhi_config, build, duration,
                                   notes=''):
-        bodhi = BodhiClient(staging=bodhi_config['staging'])
+        bodhi = BodhiClient(username=self.user,
+                            staging=bodhi_config['staging'])
         result = bodhi.list_overrides(builds=build)
         if result['total'] == 0:
             try:
@@ -271,15 +272,12 @@ class Commands(pyrpkg.Commands):
                 self.log.error(str(e))
                 raise pyrpkg.rpkgError('Cannot create override.')
             else:
-                self.log.info('Override is created.')
-                self.log.info('Expiration date: %s',
-                              override['expiration_date'])
-                self.log.info('Notes: %s', override['notes'])
+                self.log.info(bodhi.override_str(override, minimal=False))
         else:
             override = result['overrides'][0]
             expiration_date = datetime.strptime(override['expiration_date'],
                                                 '%Y-%m-%d %H:%M:%S')
-            if expiration_date < datetime.now():
+            if expiration_date < datetime.utcnow():
                 self.log.info(
                     'Buildroot override for %s exists and is expired. Consider'
                     ' using command `override extend` to extend duration.',
diff --git a/fedpkg/cli.py b/fedpkg/cli.py
index 9442030..a0e7123 100644
--- a/fedpkg/cli.py
+++ b/fedpkg/cli.py
@@ -274,13 +274,13 @@ Examples:
 Create a buildroot override from build guessed from release branch. Note that,
 command must run inside a package repository.
 
-    fedpkg switch-branch f28
-    fedpkg override create --duration 5
+    {0} switch-branch f28
+    {0} override create --duration 5
 
 Create for a specified build:
 
-    fedpkg override create --duration 5 package-1.0-1.fc28
-''')
+    {0} override create --duration 5 package-1.0-1.fc28
+'''.format(self.name))
         create_parser.add_argument(
             '--duration',
             type=validate_duration,
@@ -746,6 +746,10 @@ suggest_reboot=False
     def create_buildroot_override(self):
         """Create a buildroot override in Bodhi"""
         check_bodhi_version()
+        if self.args.NVR:
+            if not self.cmd.anon_kojisession.getBuild(self.args.NVR):
+                raise rpkgError(
+                    'Build {0} does not exist.'.format(self.args.NVR))
         bodhi_config = self._get_bodhi_config()
         self.cmd.create_buildroot_override(
             bodhi_config,
diff --git a/test/test_cli.py b/test/test_cli.py
index 4024505..aa922b4 100644
--- a/test/test_cli.py
+++ b/test/test_cli.py
@@ -1138,9 +1138,27 @@ class TestCheckBodhiVersion(unittest.TestCase):
 class TestBodhiOverride(CliTestCase):
     """Test command override"""
 
-    @patch('fedpkg.cli.check_bodhi_version')
+    def setUp(self):
+        super(TestBodhiOverride, self).setUp()
+
+        self.cbv_p = patch('fedpkg.cli.check_bodhi_version')
+        self.cbv_m = self.cbv_p.start()
+
+        self.anon_kojisession_p = patch('fedpkg.Commands.anon_kojisession',
+                                        new_callable=PropertyMock)
+        self.anon_kojisession_m = self.anon_kojisession_p.start()
+        self.kojisession = self.anon_kojisession_m.return_value
+
+        # Fake build returned from Koji for the specified build NVR in tests
+        self.kojisession.getBuild.return_value = {'build_id': 1}
+
+    def tearDown(self):
+        self.anon_kojisession_p.stop()
+        self.cbv_p.stop()
+        super(TestBodhiOverride, self).tearDown()
+
     @patch('fedpkg.BodhiClient')
-    def test_create_for_given_build(self, BodhiClient, check_bodhi_version):
+    def test_create_for_given_build(self, BodhiClient):
         bodhi_client = BodhiClient.return_value
         bodhi_client.list_overrides.return_value = {'total': 0}
         expiration_date = datetime.now() + timedelta(days=7)
@@ -1162,20 +1180,20 @@ class TestBodhiOverride(CliTestCase):
             with patch.object(cli.cmd, 'log') as log:
                 cli.create_buildroot_override()
 
-                log.info.assert_any_call('Expiration date: %s',
-                                         new_override['expiration_date'])
-                log.info.assert_any_call('Notes: %s', new_override['notes'])
+                bodhi_client.override_str.assert_called_once_with(
+                    bodhi_client.save_override.return_value,
+                    minimal=False)
+                log.info.assert_any_call(
+                    bodhi_client.override_str.return_value)
 
         bodhi_client.save_override.assert_called_once_with(
             nvr='rpkg-1.54-1.fc28',
             duration=7,
             notes='build for fedpkg')
 
-    @patch('fedpkg.cli.check_bodhi_version')
     @patch('fedpkg.BodhiClient')
     @patch('fedpkg.Commands.nvr', new_callable=PropertyMock)
-    def test_create_from_current_branch(
-            self, nvr, BodhiClient, check_bodhi_version):
+    def test_create_from_current_branch(self, nvr, BodhiClient):
         nvr.return_value = 'rpkg-1.54-2.fc28'
         bodhi_client = BodhiClient.return_value
         bodhi_client.list_overrides.return_value = {'total': 0}
@@ -1195,11 +1213,9 @@ class TestBodhiOverride(CliTestCase):
             duration=7,
             notes='build for fedpkg')
 
-    @patch('fedpkg.cli.check_bodhi_version')
     @patch('fedpkg.BodhiClient')
     @patch('fedpkg.Commands.nvr', new_callable=PropertyMock)
-    def test_override_already_exists_but_expired(
-            self, nvr, BodhiClient, check_bodhi_version):
+    def test_override_already_exists_but_expired(self, nvr, BodhiClient):
         nvr.return_value = 'rpkg-1.54-2.fc28'
         today = datetime.today()
         fake_expiration_date = today - timedelta(days=10)
@@ -1226,11 +1242,9 @@ class TestBodhiOverride(CliTestCase):
                     ' using command `override extend` to extend duration.',
                     'rpkg-1.54-2.fc28')
 
-    @patch('fedpkg.cli.check_bodhi_version')
     @patch('fedpkg.BodhiClient')
     @patch('fedpkg.Commands.nvr', new_callable=PropertyMock)
-    def test_override_already_exists_but_not_expired(
-            self, nvr, BodhiClient, check_bodhi_version):
+    def test_override_already_exists_but_not_expired(self, nvr, BodhiClient):
         nvr.return_value = 'rpkg-1.54-2.fc28'
         today = datetime.today()
         fake_expiration_date = today + timedelta(days=10)

From 251f3107febf7808938435f2429298d088039a7a Mon Sep 17 00:00:00 2001
From: Chenxiong Qi <cqi@redhat.com>
Date: Jun 11 2018 09:44:27 +0000
Subject: [PATCH 3/3] Handle Bodhi login automatically


This refers to what Bodhi CLI errorhandler does. The reason to handle
login automatically is, in fedpkg implementation, list_overrides is
called pior to save_overrides and send_request. That causes Bodhi
cookies is stored, and then it results in AuthError during subsequent
call, e.g. to save_overrides.

So, the result behavior is similar with bodhi CLI, that is password is
required to be entered only once until it is unavailable.

Signed-off-by: Chenxiong Qi <cqi@redhat.com>

---

diff --git a/fedpkg/__init__.py b/fedpkg/__init__.py
index 77e500e..7888a1a 100644
--- a/fedpkg/__init__.py
+++ b/fedpkg/__init__.py
@@ -28,7 +28,17 @@ except ImportError:
 
 
 if _BodhiClient is not None:
+    from fedora.client import AuthError
+
     class BodhiClient(_BodhiClient):
+        def save_override(self, *args, **kwargs):
+            try:
+                super(BodhiClient, self).save_override(*args, **kwargs)
+            except AuthError:
+                self._session.clear()
+                self.csrf_token = None
+                super(BodhiClient, self).save_override(*args, **kwargs)
+
         def extend_override(self, override, expiration_date):
             data = dict(
                 nvr=override['nvr'],
@@ -37,8 +47,14 @@ if _BodhiClient is not None:
                 edited=override['nvr'],
                 csrf_token=self.csrf(),
             )
-            return self.send_request(
-                'overrides/', verb='POST', auth=True, data=data)
+            try:
+                return self.send_request(
+                    'overrides/', verb='POST', auth=True, data=data)
+            except AuthError:
+                self._session.clear()
+                self.csrf_token = None
+                return self.send_request(
+                    'overrides/', verb='POST', auth=True, data=data)
 
 
 class Commands(pyrpkg.Commands):

