Repository: fedora-infra/bodhi

Issue:
Bodhi does not use the WaiverDB API correctly
We gave Bodhi an auth token for WaiverDB today and it turned out that Bodhi's code for using WaiverDB's API is incorrect in two ways:

0. It uses "proxy_user" instead of "username" to specify the acting user.
1. It sends all waivers in one API call, but WaiverDB needs one waiver per API call.


Discussion:

Resolving patch:
diff --git a/bodhi/server/models.py b/bodhi/server/models.py
--- a/bodhi/server/models.py
+++ b/bodhi/server/models.py
@@ -2624,17 +2624,17 @@ def waive_test_results(self, username, comment=None):
             'subject': self.greenwave_subject
         }
         decision = greenwave_api_post('{}/decision'.format(config.get('greenwave_api_url')), data)
-        results = [dict(subject=req['item'], testcase=req['testcase']) for req in
-                   decision['unsatisfied_requirements']]
-        log.debug('Waiving test results: %s' % results)
-        data = {
-            'results': results,
-            'product_version': self.product_version,
-            'waived': True,
-            'proxy_user': username,
-            'comment': comment
-        }
-        waiverdb_api_post('{}/waivers/'.format(config.get('waiverdb_api_url')), data)
+        for requirement in decision['unsatisfied_requirements']:
+            data = {
+                'subject': requirement['item'],
+                'testcase': requirement['testcase'],
+                'product_version': self.product_version,
+                'waived': True,
+                'username': username,
+                'comment': comment
+            }
+            log.debug('Waiving test results: %s' % data)
+            waiverdb_api_post('{}/waivers/'.format(config.get('waiverdb_api_url')), data)
 
     def add_tag(self, tag):
         """


Test patch:
diff --git a/bodhi/tests/server/test_models.py b/bodhi/tests/server/test_models.py
--- a/bodhi/tests/server/test_models.py
+++ b/bodhi/tests/server/test_models.py
@@ -2958,6 +2958,59 @@ def test_cannot_waive_test_results_of_an_update_which_is_locked(self):
             str(exc.exception),
             ("Can't waive test results on a locked update"))
 
+    @mock.patch('bodhi.server.models.greenwave_api_post')
+    @mock.patch('bodhi.server.util.http_session.post')
+    def test_can_waive_multiple_test_results_of_an_update(self, post, greenwave_api_post):
+        """Multiple failed tests getting waived should cause multiple calls to waiverdb."""
+        self.obj.status = UpdateStatus.testing
+        self.obj.test_gating_status = TestGatingStatus.failed
+        greenwave_api_post.return_value = {
+            "policies_satisfied": False,
+            "summary": "3 of 15 required tests failed",
+            "applicable_policies": ["1"],
+            "unsatisfied_requirements": [
+                {'item': {"item": "bodhi-3.6.0-1.fc28", "type": "koji_build"}, 'result_id': "123",
+                 'testcase': 'dist.depcheck', 'type': 'test-result-failed'},
+                {'item': {"item": "bodhi-3.6.0-1.fc28", "type": "koji_build"}, 'result_id': "124",
+                 'testcase': 'dist.rpmdeplint', 'type': 'test-result-failed'},
+                {'item': {"item": "bodhi-3.6.0-1.fc28", "type": "koji_build"}, 'result_id': "125",
+                 'testcase': 'dist.someothertest', 'type': 'test-result-failed'}]}
+        post.return_value.status_code = 200
+
+        with mock.patch.dict('bodhi.server.config.config',
+                             {'test_gating.required': True,
+                              'waiverdb.access_token': 'abc'}):
+            self.obj.waive_test_results('foo', 'this is not true!')
+
+        expected_calls = []
+        for test in ('dist.depcheck', 'dist.rpmdeplint', 'dist.someothertest'):
+            data = {
+                "username": "foo", "comment": "this is not true!", "waived": True,
+                "product_version": "{}".format(self.obj.product_version),
+                "testcase": "{}".format(test),
+                "subject": {"item": "bodhi-3.6.0-1.fc28", "type": "koji_build"}}
+            expected_calls.append(mock.call(
+                '{}/waivers/'.format(config.get('waiverdb_api_url')),
+                data=json.dumps(data),
+                headers={'Content-Type': 'application/json', 'Authorization': 'Bearer abc'},
+                timeout=60))
+            expected_calls.append(mock.call().json())
+        for i, v in enumerate(expected_calls):
+            # The even numbered calls have a JSON serialized data string in them, and the order of
+            # the keys is not guaranteed to be the same by Python. For these, we will just make sure
+            # that the interpreted JSON is equal rather than verifying that the strings are equal.
+            if not i % 2:
+                self.assertEqual(post.mock_calls[i][1], expected_calls[i][1])
+                self.assertEqual(post.mock_calls[i][2].keys(), v[2].keys())
+                for k in v[2].keys():
+                    if k == 'data':
+                        self.assertEqual(
+                            json.loads(post.mock_calls[i][2]['data']), json.loads(v[2]['data']))
+                    else:
+                        self.assertEqual(post.mock_calls[i][2][k], expected_calls[i][2][k])
+            else:
+                self.assertEqual(post.mock_calls[i], v)
+
     @mock.patch('bodhi.server.models.greenwave_api_post')
     @mock.patch('bodhi.server.models.waiverdb_api_post')
     def test_can_waive_test_results_of_an_update(self, mock_waiverdb, mock_greenwave):
@@ -2983,11 +3036,11 @@ def test_can_waive_test_results_of_an_update(self, mock_waiverdb, mock_greenwave
                               'waiverdb.access_token': 'abc'}):
             update.waive_test_results('foo', 'this is not true!')
             wdata = {
-                'results': [{'subject': {"item": "%s" % update.alias, "type": "koji_build"},
-                             'testcase': 'dist.depcheck'}],
+                'subject': {"item": "%s" % update.alias, "type": "koji_build"},
+                'testcase': 'dist.depcheck',
                 'product_version': update.product_version,
                 'waived': True,
-                'proxy_user': 'foo',
+                'username': 'foo',
                 'comment': 'this is not true!'
             }
             mock_waiverdb.assert_called_once_with(