From 57a50bc67e81fceb68f48508f0c87f639c77e178 Mon Sep 17 00:00:00 2001 From: Mfon Eti-mfon Date: Thu, 16 Sep 2021 16:00:17 +0100 Subject: [PATCH] (api) Make testcase filtering aware of the duration fields The API method `TestCase.filter` is extended to work for queries that contain lookups on the fields `setup_duration`, `testing_duration` and `expected_duration`. It also returns the values of all three fields in its result. Refs #1923 --- tcms/rpc/api/testcase.py | 48 +++++++++++++++++++++++++++++++-- tcms/rpc/api/utils.py | 8 ++++++ tcms/rpc/tests/test_testcase.py | 24 +++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/tcms/rpc/api/testcase.py b/tcms/rpc/api/testcase.py index 30552133a9..c31955b052 100644 --- a/tcms/rpc/api/testcase.py +++ b/tcms/rpc/api/testcase.py @@ -1,7 +1,11 @@ # -*- coding: utf-8 -*- +from datetime import timedelta + +from django.db.models import Case, F, Q, When from django.forms import EmailField, ValidationError from django.forms.models import model_to_dict +from django.utils.dateparse import parse_duration from modernrpc.core import REQUEST_KEY, rpc_method from tcms.core import helpers @@ -9,6 +13,7 @@ from tcms.management.models import Component, Tag from tcms.rpc import utils from tcms.rpc.api.forms.testcase import NewForm, UpdateForm +from tcms.rpc.api.utils import stringify_values from tcms.rpc.decorators import permissions_required from tcms.testcases.models import TestCase, TestCasePlan @@ -279,8 +284,37 @@ def filter(query=None): # pylint: disable=redefined-builtin if query is None: query = {} - return list( - TestCase.objects.filter(**query) + for key, val in query.items(): + if not key.startswith( + ("setup_duration", "testing_duration", "expected_duration") + ): + continue + + try: + duration = parse_duration(val) + except TypeError: + # val isn't a string or byte-like object + # item is probably something like 'setup_duration__isnull=True' + continue + + if duration is None: + continue + + query[key] = duration + + qs = ( + TestCase.objects.annotate( + expected_duration=Case( + When( + Q(setup_duration__isnull=True) & Q(testing_duration__isnull=True), + then=timedelta(0), + ), + When(Q(setup_duration__isnull=True), then="testing_duration"), + When(Q(testing_duration__isnull=True), then="setup_duration"), + default=F("setup_duration") + F("testing_duration"), + ) + ) + .filter(**query) .values( "id", "create_date", @@ -304,9 +338,19 @@ def filter(query=None): # pylint: disable=redefined-builtin "default_tester__username", "reviewer", "reviewer__username", + "setup_duration", + "testing_duration", + "expected_duration", ) .distinct() ) + return [ + stringify_values( + testcase_dict, + keys=["setup_duration", "testing_duration", "expected_duration"], + ) + for testcase_dict in qs.iterator() + ] @permissions_required("testcases.view_testcase") diff --git a/tcms/rpc/api/utils.py b/tcms/rpc/api/utils.py index 7e72ff3f27..926f4fc953 100644 --- a/tcms/rpc/api/utils.py +++ b/tcms/rpc/api/utils.py @@ -18,3 +18,11 @@ def tracker_from_url(url, request): return import_string(bug_system.tracker_type)(bug_system, request) return None + + +def stringify_values(d, keys=None): + keys = keys or [] + return { + key: (str(val) if (key in keys and val is not None) else val) + for (key, val) in d.items() + } diff --git a/tcms/rpc/tests/test_testcase.py b/tcms/rpc/tests/test_testcase.py index c43de28b1a..d3ac04890f 100644 --- a/tcms/rpc/tests/test_testcase.py +++ b/tcms/rpc/tests/test_testcase.py @@ -173,12 +173,36 @@ def test_filter_query_none(self): self.assertIn("author", result[0]) self.assertIn("default_tester", result[0]) self.assertIn("reviewer", result[0]) + self.assertIn("setup_duration", result[0]) + self.assertIn("testing_duration", result[0]) + self.assertIn("expected_duration", result[0]) def test_filter_by_product_id(self): cases = self.rpc_client.TestCase.filter({"category__product": self.product.pk}) self.assertIsNotNone(cases) self.assertEqual(len(cases), self.cases_count) + def test_filter_by_setup_duration(self): + TestCaseFactory(setup_duration=timedelta(seconds=30)) + cases = self.rpc_client.TestCase.filter({"setup_duration": "00:00:30"}) + self.assertIsNotNone(cases) + self.assertEqual(len(cases), 1) + + def test_filter_by_testing_duration(self): + TestCaseFactory(testing_duration=timedelta(minutes=5, seconds=1)) + cases = self.rpc_client.TestCase.filter({"testing_duration__gt": "00:05:00"}) + self.assertIsNotNone(cases) + self.assertEqual(len(cases), 1) + + def test_filter_by_expected_duration(self): + TestCaseFactory( + setup_duration=timedelta(seconds=30), + testing_duration=timedelta(minutes=5, seconds=1), + ) + cases = self.rpc_client.TestCase.filter({"expected_duration__lt": "00:5:31"}) + self.assertIsNotNone(cases) + self.assertEqual(len(cases), self.cases_count) + class TestUpdate(APITestCase): non_existing_username = "FakeUsername"