Repository: fedora-infra/bodhi

Issue:
Reset multi builds update that failed merging a side tag to pending
If a multi builds update fails to be merge in stable, the approve testing cron job will add a new comment to the update every times it run.
To avoid this we need to reset the update status to pending, but we also need to be able to move back the update to testing when the update is edited (build added or build removed).


Discussion:

Resolving patch:
diff --git a/bodhi/server/tasks/updates.py b/bodhi/server/tasks/updates.py
--- a/bodhi/server/tasks/updates.py
+++ b/bodhi/server/tasks/updates.py
@@ -36,10 +36,12 @@
 import logging
 import time
 
+from sqlalchemy import func
+
 from bodhi.server import util, bugs as bug_module
 from bodhi.server.config import config
 from bodhi.server.exceptions import BodhiException
-from bodhi.server.models import Bug, Update, UpdateType
+from bodhi.server.models import Bug, Update, UpdateType, UpdateStatus
 
 
 log = logging.getLogger('bodhi')
@@ -95,6 +97,19 @@ def run(self, api_version: int, data: dict):
 
             bugs = []
             if action == "edit":
+                # If editing a Pending update, all of whose builds are signed, for a release
+                # which isn't composed by Bodhi (i.e. Rawhide), move it directly to Testing.
+                if not update.release.composed_by_bodhi \
+                        and update.status == UpdateStatus.pending \
+                        and update.signed:
+                    log.info("Every build in the update is signed, set status to testing")
+
+                    update.status = UpdateStatus.testing
+                    update.date_testing = func.current_timestamp()
+                    update.request = None
+
+                    log.info(f"Update status of {update.display_name} has been set to testing")
+
                 for idx in data['new_bugs']:
                     bug = Bug.get(idx)
 


Test patch:
diff --git a/bodhi/server/scripts/approve_testing.py b/bodhi/server/scripts/approve_testing.py
--- a/bodhi/server/scripts/approve_testing.py
+++ b/bodhi/server/scripts/approve_testing.py
@@ -128,6 +128,9 @@ def main(argv=sys.argv):
                                 f"These builds {builds_str} have a more recent "
                                 f"build in koji's {update.release.stable_tag} tag.",
                                 author="bodhi")
+                            update.status = UpdateStatus.pending
+                            update.request = None
+                            update.remove_tag(update.release.get_testing_side_tag(update.from_tag))
                             db.commit()
                             continue
 
diff --git a/bodhi/tests/server/scripts/test_approve_testing.py b/bodhi/tests/server/scripts/test_approve_testing.py
--- a/bodhi/tests/server/scripts/test_approve_testing.py
+++ b/bodhi/tests/server/scripts/test_approve_testing.py
@@ -930,6 +930,7 @@ def test_update_conflicting_build_not_pushed(self, build_creation_time):
         update.date_testing = datetime.utcnow() - timedelta(days=8)
         update.status = models.UpdateStatus.testing
         update.release.composed_by_bodhi = False
+        update.from_tag = 'f17-build-side-1234'
 
         # Clear pending messages
         self.db.info['messages'] = []
@@ -940,7 +941,7 @@ def test_update_conflicting_build_not_pushed(self, build_creation_time):
                 with fml_testing.mock_sends(api.Message):
                     approve_testing.main(['nosetests', 'some_config.ini'])
 
-        assert update.status == models.UpdateStatus.testing
+        assert update.status == models.UpdateStatus.pending
 
         bodhi = self.db.query(models.User).filter_by(name='bodhi').one()
         cmnts = self.db.query(models.Comment).filter_by(update_id=update.id, user_id=bodhi.id)
diff --git a/bodhi/tests/server/tasks/test_updates.py b/bodhi/tests/server/tasks/test_updates.py
--- a/bodhi/tests/server/tasks/test_updates.py
+++ b/bodhi/tests/server/tasks/test_updates.py
@@ -23,7 +23,9 @@
 from urllib.error import URLError
 
 import sqlalchemy
+from fedora_messaging import testing as fml_testing
 
+from bodhi.messages.schemas import update as update_schemas
 from bodhi.server import config, exceptions, models, util
 from bodhi.server.tasks import handle_update, updates
 from bodhi.tests.server import base
@@ -304,6 +306,91 @@ def test_update_not_found(self, work_on_bugs, fetch_test_cases, sleep):
         self.assertEqual(fetch_test_cases.call_count, 0)
         sleep.assert_called_once_with(1)
 
+    @mock.patch('bodhi.server.tasks.updates.UpdatesHandler.fetch_test_cases')
+    @mock.patch('bodhi.server.tasks.updates.UpdatesHandler.work_on_bugs')
+    @mock.patch.dict(config.config, [('test_gating.required', True)])
+    def test_rawhide_update_edit_move_to_testing(self, work_on_bugs, fetch_test_cases, sleep):
+        """
+        Assert that a pending rawhide update that was edited gets moved to testing
+        if all the builds in the update are signed.
+        """
+        update = models.Build.query.filter_by(nvr='bodhi-2.0-1.fc17').one().update
+        update.status = models.UpdateStatus.pending
+        update.release.composed_by_bodhi = False
+        update.builds[0].signed = True
+
+        h = updates.UpdatesHandler()
+        h.db_factory = base.TransactionalSessionMaker(self.Session)
+        with fml_testing.mock_sends(update_schemas.UpdateReadyForTestingV1):
+            with mock.patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
+                greenwave_response = {
+                    'policies_satisfied': False,
+                    'summary': 'what have you done‽',
+                    'applicable_policies': ['taskotron_release_critical_tasks'],
+                    'unsatisfied_requirements': [
+                        {'testcase': 'dist.rpmdeplint',
+                         'item': {'item': 'bodhi-2.0-1.fc17', 'type': 'koji_build'},
+                         'type': 'test-result-missing', 'scenario': None},
+                        {'testcase': 'dist.rpmdeplint',
+                         'item': {'item': update.alias, 'type': 'bodhi_update'},
+                         'type': 'test-result-missing', 'scenario': None}]}
+                mock_greenwave.return_value = greenwave_response
+                h.run(api_version=1,
+                      data={
+                          'action': 'edit',
+                          'update': {'alias': update.alias, 'builds': [{'nvr': 'bodhi-2.0-1.fc17'}],
+                                     'user': {'name': 'bodhi'}, 'status': str(update.status),
+                                     'request': str(update.request)}, 'new_bugs': []})
+
+        self.assertEqual(update.status, models.UpdateStatus.testing)
+        self.assertEqual(update.test_gating_status, models.TestGatingStatus.failed)
+
+    @mock.patch('bodhi.server.tasks.updates.UpdatesHandler.fetch_test_cases')
+    @mock.patch('bodhi.server.tasks.updates.UpdatesHandler.work_on_bugs')
+    def test_rawhide_update_edit_stays_pending(self, work_on_bugs, fetch_test_cases, sleep):
+        """
+        Assert that a pending rawhide update that was edited does not get moved to testing
+        if not all the builds in the update are signed.
+        """
+        update = models.Build.query.filter_by(nvr='bodhi-2.0-1.fc17').one().update
+        update.status = models.UpdateStatus.pending
+        update.release.composed_by_bodhi = False
+        update.builds[0].signed = False
+
+        h = updates.UpdatesHandler()
+        h.db_factory = base.TransactionalSessionMaker(self.Session)
+        h.run(api_version=1,
+              data={
+                  'action': 'edit',
+                  'update': {'alias': update.alias, 'builds': [{'nvr': 'bodhi-2.0-1.fc17'}],
+                             'user': {'name': 'bodhi'}, 'status': str(update.status),
+                             'request': str(update.request)}, 'new_bugs': []})
+
+        self.assertEqual(update.status, models.UpdateStatus.pending)
+
+    @mock.patch('bodhi.server.tasks.updates.UpdatesHandler.fetch_test_cases')
+    @mock.patch('bodhi.server.tasks.updates.UpdatesHandler.work_on_bugs')
+    def test_not_rawhide_update_signed_stays_pending(self, work_on_bugs, fetch_test_cases, sleep):
+        """
+        Assert that a non rawhide pending update that was edited does not get moved to testing
+        if all the builds in the update are signed.
+        """
+        update = models.Build.query.filter_by(nvr='bodhi-2.0-1.fc17').one().update
+        update.status = models.UpdateStatus.pending
+        update.release.composed_by_bodhi = True
+        update.builds[0].signed = True
+
+        h = updates.UpdatesHandler()
+        h.db_factory = base.TransactionalSessionMaker(self.Session)
+        h.run(api_version=1,
+              data={
+                  'action': 'edit',
+                  'update': {'alias': update.alias, 'builds': [{'nvr': 'bodhi-2.0-1.fc17'}],
+                             'user': {'name': 'bodhi'}, 'status': str(update.status),
+                             'request': str(update.request)}, 'new_bugs': []})
+
+        self.assertEqual(update.status, models.UpdateStatus.pending)
+
 
 class TestUpdatesHandlerInit(unittest.TestCase):
     """This test class contains tests for the UpdatesHandler.__init__() method."""