From 88182d29e44e4bae3503eb48cb7d21e14d684fd6 Mon Sep 17 00:00:00 2001
From: Lukas Holecek <lholecek@redhat.com>
Date: Jun 07 2018 13:51:21 +0000
Subject: Print satisfied requirements when verbose flag is true


Fixes #124

---

diff --git a/functional-tests/test_api_v1.py b/functional-tests/test_api_v1.py
index fcf62cf..45a023c 100644
--- a/functional-tests/test_api_v1.py
+++ b/functional-tests/test_api_v1.py
@@ -244,6 +244,15 @@ def test_make_a_decision_with_verbose_flag(requests_session, greenwave_server, t
     expected_waivers = []
     assert res_data['waivers'] == expected_waivers
 
+    expected_satisfied_requirements = [
+        {
+            'result_id': result['id'],
+            'testcase': result['testcase']['name'],
+            'type': 'test-result-passed',
+        } for result in results
+    ]
+    assert res_data['satisfied_requirements'] == expected_satisfied_requirements
+
 
 def test_make_a_decision_on_failed_result_with_waiver(
         requests_session, greenwave_server, testdatabuilder):
diff --git a/greenwave/api_v1.py b/greenwave/api_v1.py
index af49fa8..b0f95da 100644
--- a/greenwave/api_v1.py
+++ b/greenwave/api_v1.py
@@ -207,6 +207,9 @@ def make_decision():
                    'type': 'test-result-missing'
                }
            ],
+           "satisfied_requirements": [
+               ...
+           ],
            "results": [
                {
                  'data': {
@@ -350,6 +353,8 @@ def make_decision():
         res.update({
             'results': results,
             'waivers': waivers,
+            'satisfied_requirements':
+                [answer.to_json() for answer in answers if answer.is_satisfied],
         })
     resp = jsonify(res)
     resp = insert_headers(resp)
diff --git a/greenwave/policies.py b/greenwave/policies.py
index 35d8186..b067ca2 100644
--- a/greenwave/policies.py
+++ b/greenwave/policies.py
@@ -50,7 +50,11 @@ class Answer(object):
     a subclass, depending on what the answer was.
     """
 
-    pass
+    def to_json(self):
+        """
+        Returns a machine-readable description of the problem for API responses.
+        """
+        raise NotImplementedError()
 
 
 class RuleSatisfied(Answer):
@@ -71,12 +75,6 @@ class RuleNotSatisfied(Answer):
 
     is_satisfied = False
 
-    def to_json(self):
-        """
-        Returns a machine-readable description of the problem for API responses.
-        """
-        raise NotImplementedError()
-
 
 class TestResultMissing(RuleNotSatisfied):
     """
@@ -102,6 +100,26 @@ class TestResultMissing(RuleNotSatisfied):
         }
 
 
+class TestResultMissingWaived(RuleSatisfied):
+    """
+    Same as TestResultMissing but the result was waived.
+    """
+    def __init__(self, subject_type, subject_identifier, test_case_name, scenario):
+        self.subject_type = subject_type
+        self.subject_identifier = subject_identifier
+        self.test_case_name = test_case_name
+        self.scenario = scenario
+
+    def to_json(self):
+        return {
+            'type': 'test-result-missing-waived',
+            'testcase': self.test_case_name,
+            'subject_type': self.subject_type,
+            'subject_identifier': self.subject_identifier,
+            'scenario': self.scenario,
+        }
+
+
 class TestResultFailed(RuleNotSatisfied):
     """
     A required test case did not pass (that is, its outcome in ResultsDB was
@@ -128,6 +146,55 @@ class TestResultFailed(RuleNotSatisfied):
         }
 
 
+class TestResultPassed(RuleSatisfied):
+    """
+    A required test case passed (that is, its outcome in ResultsDB was
+    ``PASSED`` or ``INFO``) or a corresponding waiver was found.
+    """
+    def __init__(self, test_case_name, result_id):
+        self.test_case_name = test_case_name
+        self.result_id = result_id
+
+    def to_json(self):
+        return {
+            'type': 'test-result-passed',
+            'testcase': self.test_case_name,
+            'result_id': self.result_id,
+        }
+
+
+class TestCaseNotApplicable(RuleSatisfied):
+    """
+    A required test case is not applicable to given subject.
+    """
+    def __init__(self, subject_type, subject_identifier, test_case_name):
+        self.subject_type = subject_type
+        self.subject_identifier = subject_identifier
+        self.test_case_name = test_case_name
+
+    def to_json(self):
+        return {
+            'type': 'test-case-not-applicable',
+            'testcase': self.test_case_name,
+            'subject_type': self.subject_type,
+            'subject_identifier': self.subject_identifier,
+        }
+
+
+class BlacklistedInPolicy(RuleSatisfied):
+    """
+    Package was blacklisted in policy.
+    """
+    def __init__(self, subject_identifier):
+        self.subject_identifier = subject_identifier
+
+    def to_json(self):
+        return {
+            'type': 'blacklisted',
+            'subject_identifier': self.subject_identifier,
+        }
+
+
 def summarize_answers(answers):
     """
     Produces a one-sentence human-readable summary of the result of evaluating a policy.
@@ -194,31 +261,31 @@ class RemoteOriginalSpecNvrRule(Rule):
 
     def check(self, subject_type, subject_identifier, results, waivers):
         if subject_type != 'koji_build':
-            return RuleSatisfied()
+            return []
 
         pkg_name = subject_identifier.rsplit('-', 2)[0]
         rev = greenwave.resources.retrieve_rev_from_koji(subject_identifier)
         response = greenwave.resources.retrieve_yaml_remote_original_spec_nvr_rule(rev, pkg_name)
 
-        if isinstance(response, RuleSatisfied):
+        if response is None:
             # greenwave extension file not found
-            return RuleSatisfied()
-        else:
-            policies = yaml.safe_load_all(response)
-            # policies is a generator, so listifying it
-            policies = list(policies)
-            # policies in dist-git are always about a package
-            for policy in policies:
-                policy.subject_type = 'koji_build'
-            validate_policies(policies, [RemoteOriginalSpecNvrRule])
-            answers = []
-            for policy in policies:
-                response = policy.check(subject_identifier, results, waivers)
-                if isinstance(response, list):
-                    answers.extend(response)
-                else:
-                    answers.append(response)
-            return answers
+            return []
+
+        policies = yaml.safe_load_all(response)
+        # policies is a generator, so listifying it
+        policies = list(policies)
+        # policies in dist-git are always about a package
+        for policy in policies:
+            policy.subject_type = 'koji_build'
+        validate_policies(policies, [RemoteOriginalSpecNvrRule])
+        answers = []
+        for policy in policies:
+            response = policy.check(subject_identifier, results, waivers)
+            if isinstance(response, list):
+                answers.extend(response)
+            else:
+                answers.append(response)
+        return answers
 
     def to_json(self):
         return {
@@ -250,23 +317,22 @@ class PassingTestCaseRule(Rule):
             if not matching_waivers:
                 return TestResultMissing(subject_type, subject_identifier, self.test_case_name,
                                          self._scenario)
-            else:
-                # The result is absent, but the absence is waived.
-                return RuleSatisfied()
+            return TestResultMissingWaived(
+                subject_type, subject_identifier, self.test_case_name, self._scenario)
 
         # If we find multiple matching results, we always use the first one which
         # will be the latest chronologically, because ResultsDB always returns
         # results ordered by `submit_time` descending.
         matching_result = matching_results[0]
         if matching_result['outcome'] in ['PASSED', 'INFO']:
-            return RuleSatisfied()
+            return TestResultPassed(self.test_case_name, matching_result['id'])
 
         # XXX limit who is allowed to waive
         if any(w['subject'] == dict([(key, value[0])
                for key, value in matching_result['data'].items()]) and
                w['testcase'] == matching_result['testcase']['name'] and
                w['waived'] for w in waivers):
-            return RuleSatisfied()
+            return TestResultPassed(self.test_case_name, matching_result['id'])
         return TestResultFailed(subject_type, subject_identifier, self.test_case_name,
                                 self._scenario, matching_result['id'])
 
@@ -313,11 +379,11 @@ class PackageSpecificRule(Rule):
         """
 
         if subject_type != 'koji_build':
-            return RuleSatisfied()
+            return TestCaseNotApplicable(subject_type, subject_identifier, self.test_case_name)
 
         pkg_name = subject_identifier.rsplit('-', 2)[0]
         if not any(fnmatch(pkg_name, repo) for repo in self.repos):
-            return RuleSatisfied()
+            return TestCaseNotApplicable(subject_type, subject_identifier, self.test_case_name)
 
         rule = PassingTestCaseRule()
         # pylint: disable=attribute-defined-outside-init
@@ -360,7 +426,7 @@ class Policy(yaml.YAMLObject):
         if self.subject_type == 'koji_build':
             name = subject_identifier.rsplit('-', 2)[0]
             if name in self.blacklist:
-                return [RuleSatisfied() for rule in self.rules]
+                return [BlacklistedInPolicy(subject_identifier) for rule in self.rules]
         answers = []
         for rule in self.rules:
             response = rule.check(self.subject_type, subject_identifier, results, waivers)
diff --git a/greenwave/resources.py b/greenwave/resources.py
index dcdac53..d8b8a8d 100644
--- a/greenwave/resources.py
+++ b/greenwave/resources.py
@@ -18,7 +18,6 @@ from werkzeug.exceptions import BadGateway
 
 from greenwave.cache import cached
 import greenwave.utils
-import greenwave.policies
 
 log = logging.getLogger(__name__)
 
@@ -58,8 +57,9 @@ def retrieve_yaml_remote_original_spec_nvr_rule(rev, pkg_name):
                                         headers={'Content-Type': 'application/json'},
                                         timeout=60)
     if response.status_code == 404:
-        return greenwave.policies.RuleSatisfied()
-    elif response.status_code != 200:
+        return None
+
+    if response.status_code != 200:
         raise BadGateway('Error occurred looking for gating.yaml file in the dist-git repo.')
 
     # gating.yaml found...

