diff --git a/dojo/filters.py b/dojo/filters.py index db37fa87119..cbe0d1f70f7 100644 --- a/dojo/filters.py +++ b/dojo/filters.py @@ -3129,6 +3129,10 @@ class Meta: class ApiRiskAcceptanceFilter(DojoFilter): created = DateRangeFilter() updated = DateRangeFilter() + created_before = DateTimeFilter(field_name="created", lookup_expr="lt") + created_after = DateTimeFilter(field_name="created", lookup_expr="gt") + updated_before = DateTimeFilter(field_name="updated", lookup_expr="lt") + updated_after = DateTimeFilter(field_name="updated", lookup_expr="gt") o = OrderingFilter( # tuple-mapping retains order diff --git a/unittests/test_risk_acceptance_api.py b/unittests/test_risk_acceptance_api.py index 45756bb6e54..e3a6a96d4d3 100644 --- a/unittests/test_risk_acceptance_api.py +++ b/unittests/test_risk_acceptance_api.py @@ -176,6 +176,19 @@ def setUp(self): self.client.credentials(HTTP_AUTHORIZATION="Token " + self.token.key) self.url = reverse("risk_acceptance-list") + # Helper method to create a risk acceptance for testing filters + def create_risk_acceptance(self): + risk_acceptance = Risk_Acceptance.objects.create( + name="Filter Test RA", + recommendation="A", + decision="A", + accepted_by="Test User", + owner=self.user, + ) + risk_acceptance.accepted_findings.add(self.finding_a1) + self.engagement_a.risk_acceptance.add(risk_acceptance) + return risk_acceptance + def test_create_risk_acceptance_links_to_engagement(self): """Test that risk acceptance created via API appears in engagement.risk_acceptance""" payload = { @@ -358,3 +371,49 @@ def test_update_risk_acceptance_add_cross_engagement_fails(self): response = self.client.put(f"{self.url}{ra.id}/", payload, format="json") self.assertEqual(403, response.status_code, response.content) self.assertIn("multiple engagements", str(response.data)) + + def test_risk_acceptance_created_filter(self): + # 1. Create a baseline Risk Acceptance using the existing test setup + risk_acceptance = self.create_risk_acceptance() + + # 2. Manually backdate the created date to test ranges + past_date = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=10) + risk_acceptance.created = past_date + risk_acceptance.save() + + # 3. Test `created_before` (Less than / Before) + # Should return the risk acceptance because it was created 10 days ago + future_date = datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%S.%fZ") + response = self.client.get(reverse("risk_acceptance-list") + f"?created_before={future_date}") + self.assertEqual(response.status_code, 200) + result_ids = {item["id"] for item in response.json()["results"]} + self.assertIn(risk_acceptance.id, result_ids) + + # 4. Test `created_after` (Greater than / After) + # Should NOT return the risk acceptance because it is not newer than today + response = self.client.get(reverse("risk_acceptance-list") + f"?created_after={future_date}") + self.assertEqual(response.status_code, 200) + result_ids = {item["id"] for item in response.json()["results"]} + self.assertNotIn(risk_acceptance.id, result_ids) + + def test_risk_acceptance_updated_filter(self): + risk_acceptance = self.create_risk_acceptance() + + # Manually backdate the updated date + past_date = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=10) + # We use .update() to bypass the auto_now=True behavior on the updated field + type(risk_acceptance).objects.filter(pk=risk_acceptance.id).update(updated=past_date) + + future_date = datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%S.%fZ") + + # Test updated_before + response = self.client.get(reverse("risk_acceptance-list") + f"?updated_before={future_date}") + self.assertEqual(response.status_code, 200) + result_ids = {item["id"] for item in response.json()["results"]} + self.assertIn(risk_acceptance.id, result_ids) + + # Test updated_after + response = self.client.get(reverse("risk_acceptance-list") + f"?updated_after={future_date}") + self.assertEqual(response.status_code, 200) + result_ids = {item["id"] for item in response.json()["results"]} + self.assertNotIn(risk_acceptance.id, result_ids)