diff --git a/qa/qa/checks/models.py b/qa/qa/checks/models.py index 14194466..aa6a1862 100644 --- a/qa/qa/checks/models.py +++ b/qa/qa/checks/models.py @@ -56,6 +56,12 @@ class Result(BaseModel): offsets: list[Offset] | None = None results: list["Result"] | None = None + def failure_messages(self, *dispositions: Disposition) -> str: + """Return a concatenated string containing all messages for failed results with the given dispositions.""" + if not self.results: + return self.message if not self.passed else "" + return "\n".join(r.message for r in self.results if not r.passed and r.disposition in dispositions) + class Flag(BaseModel): id: str = Field(pattern=kebab_case) diff --git a/qa/tests/checks/test_models.py b/qa/tests/checks/test_models.py index 416cd02c..3a8a5450 100644 --- a/qa/tests/checks/test_models.py +++ b/qa/tests/checks/test_models.py @@ -3,7 +3,7 @@ from pydantic import ValidationError from unittest import TestCase -from qa.checks.models import BaseReport, Flag +from qa.checks.models import BaseReport, Disposition, Flag, Result def base_report( @@ -39,6 +39,81 @@ def test_submission_id_must_be_positive(self): base_report(submission_id=0) +class TestResultFailureMessages(TestCase): + def test_no_results_and_passed_returns_empty_string(self): + result = Result(check_config={}, passed=True, disposition=Disposition.OK, message="") + self.assertEqual(result.failure_messages(Disposition.WARN, Disposition.REJECT), "") + + def test_no_results_and_failed_returns_own_message(self): + result = Result(check_config={}, passed=False, disposition=Disposition.REJECT, message="own message") + self.assertEqual(result.failure_messages(Disposition.WARN, Disposition.REJECT), "own message") + + def test_no_dispositions_returns_empty_string(self): + result = Result( + check_config={}, + passed=False, + disposition=Disposition.REJECT, + message="", + results=[ + Result(check_config={}, passed=False, disposition=Disposition.REJECT, message="reject message"), + ], + ) + self.assertEqual(result.failure_messages(), "") + + def test_concatenates_messages_matching_given_dispositions(self): + result = Result( + check_config={}, + passed=False, + disposition=Disposition.REJECT, + message="", + results=[ + Result(check_config={}, passed=False, disposition=Disposition.WARN, message="warn message"), + Result(check_config={}, passed=False, disposition=Disposition.REJECT, message="reject message"), + ], + ) + self.assertEqual(result.failure_messages(Disposition.WARN, Disposition.REJECT), "warn message\nreject message") + + def test_filters_to_a_single_disposition(self): + result = Result( + check_config={}, + passed=False, + disposition=Disposition.REJECT, + message="", + results=[ + Result(check_config={}, passed=False, disposition=Disposition.WARN, message="warn message"), + Result(check_config={}, passed=False, disposition=Disposition.REJECT, message="reject message"), + ], + ) + self.assertEqual(result.failure_messages(Disposition.REJECT), "reject message") + + def test_excludes_passed_and_ignored_sub_checks(self): + result = Result( + check_config={}, + passed=True, + disposition=Disposition.OK, + message="", + results=[ + Result(check_config={}, passed=True, disposition=Disposition.OK, message="passed message"), + Result(check_config={}, passed=False, disposition=Disposition.OK, message="ignored message"), + Result(check_config={}, passed=False, disposition=Disposition.WARN, message="warn message"), + ], + ) + self.assertEqual(result.failure_messages(Disposition.WARN, Disposition.REJECT), "warn message") + + def test_excludes_passed_sub_checks_even_with_matching_disposition(self): + result = Result( + check_config={}, + passed=True, + disposition=Disposition.OK, + message="", + results=[ + Result(check_config={}, passed=True, disposition=Disposition.OK, message="passed message"), + Result(check_config={}, passed=False, disposition=Disposition.OK, message="ignored message"), + ], + ) + self.assertEqual(result.failure_messages(Disposition.OK), "ignored message") + + class TestFlag(TestCase): def test_id_rejects_underscores(self): with self.assertRaises(ValidationError):