mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
fix(api): align latest_resources scan selection with completed_at (#10802)
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
|
||||
All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
## [1.25.2] (Prowler v5.24.2)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- `/finding-groups/latest/<check_id>/resources` now selects the latest completed scan per provider by `-completed_at` (then `-inserted_at`) instead of `-inserted_at`, matching the `/finding-groups/latest` summary path and the daily-summary upsert so overlapping scans no longer produce diverging `delta`/`new_count` between the two endpoints [(#10802)](https://github.com/prowler-cloud/prowler/pull/10802)
|
||||
|
||||
---
|
||||
|
||||
## [1.25.1] (Prowler v5.24.1)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
@@ -17271,3 +17271,111 @@ class TestFindingGroupViewSet:
|
||||
attrs = item["attributes"]
|
||||
assert "finding_id" in attrs
|
||||
assert attrs["finding_id"] in rds_finding_ids
|
||||
|
||||
def test_latest_resources_picks_scan_by_completed_at_when_overlap(
|
||||
self,
|
||||
authenticated_client,
|
||||
tenants_fixture,
|
||||
providers_fixture,
|
||||
resources_fixture,
|
||||
):
|
||||
"""Overlapping scans on the same provider must resolve to the scan
|
||||
with the latest completed_at, matching the /latest summary path and
|
||||
the daily-summary upsert (keyed on midnight(completed_at)). Picking
|
||||
by inserted_at here produced /resources and /latest reading from
|
||||
different scans and reporting diverging delta/new counts.
|
||||
"""
|
||||
tenant = tenants_fixture[0]
|
||||
provider = providers_fixture[0]
|
||||
resource = resources_fixture[0]
|
||||
check_id = "overlap_regression_check"
|
||||
|
||||
t0 = datetime.now(timezone.utc) - timedelta(hours=5)
|
||||
t1 = t0 + timedelta(hours=1)
|
||||
t1_end = t1 + timedelta(minutes=30)
|
||||
t2 = t0 + timedelta(hours=4)
|
||||
|
||||
scan_long = Scan.objects.create(
|
||||
name="long overlap scan",
|
||||
provider=provider,
|
||||
trigger=Scan.TriggerChoices.MANUAL,
|
||||
state=StateChoices.COMPLETED,
|
||||
tenant_id=tenant.id,
|
||||
started_at=t0,
|
||||
completed_at=t2,
|
||||
)
|
||||
scan_short = Scan.objects.create(
|
||||
name="short overlap scan",
|
||||
provider=provider,
|
||||
trigger=Scan.TriggerChoices.MANUAL,
|
||||
state=StateChoices.COMPLETED,
|
||||
tenant_id=tenant.id,
|
||||
started_at=t1,
|
||||
completed_at=t1_end,
|
||||
)
|
||||
# inserted_at is auto_now_add so override with .update() to recreate
|
||||
# the overlap shape: short scan inserted later but completed earlier.
|
||||
Scan.all_objects.filter(pk=scan_long.pk).update(inserted_at=t0)
|
||||
Scan.all_objects.filter(pk=scan_short.pk).update(inserted_at=t1)
|
||||
scan_long.refresh_from_db()
|
||||
scan_short.refresh_from_db()
|
||||
|
||||
assert scan_short.inserted_at > scan_long.inserted_at
|
||||
assert scan_long.completed_at > scan_short.completed_at
|
||||
|
||||
long_finding = Finding.objects.create(
|
||||
tenant_id=tenant.id,
|
||||
uid=f"{check_id}_long",
|
||||
scan=scan_long,
|
||||
delta=None,
|
||||
status=Status.FAIL,
|
||||
status_extended="long scan finding",
|
||||
impact=Severity.high,
|
||||
impact_extended="high",
|
||||
severity=Severity.high,
|
||||
raw_result={"status": Status.FAIL, "severity": Severity.high},
|
||||
check_id=check_id,
|
||||
check_metadata={
|
||||
"CheckId": check_id,
|
||||
"checktitle": "Overlap regression",
|
||||
"Description": "Overlapping scan regression.",
|
||||
},
|
||||
first_seen_at=t0,
|
||||
muted=False,
|
||||
)
|
||||
long_finding.add_resources([resource])
|
||||
|
||||
short_finding = Finding.objects.create(
|
||||
tenant_id=tenant.id,
|
||||
uid=f"{check_id}_short",
|
||||
scan=scan_short,
|
||||
delta="new",
|
||||
status=Status.FAIL,
|
||||
status_extended="short scan finding",
|
||||
impact=Severity.high,
|
||||
impact_extended="high",
|
||||
severity=Severity.high,
|
||||
raw_result={"status": Status.FAIL, "severity": Severity.high},
|
||||
check_id=check_id,
|
||||
check_metadata={
|
||||
"CheckId": check_id,
|
||||
"checktitle": "Overlap regression",
|
||||
"Description": "Overlapping scan regression.",
|
||||
},
|
||||
first_seen_at=t1,
|
||||
muted=False,
|
||||
)
|
||||
short_finding.add_resources([resource])
|
||||
|
||||
response = authenticated_client.get(
|
||||
reverse(
|
||||
"finding-group-latest_resources",
|
||||
kwargs={"check_id": check_id},
|
||||
),
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
data = response.json()["data"]
|
||||
assert len(data) == 1
|
||||
attrs = data[0]["attributes"]
|
||||
assert attrs["finding_id"] == str(long_finding.id)
|
||||
assert attrs["delta"] is None
|
||||
|
||||
@@ -8145,10 +8145,13 @@ class FindingGroupViewSet(BaseRLSViewSet):
|
||||
tenant_id = request.tenant_id
|
||||
queryset = self._get_finding_queryset()
|
||||
|
||||
# Get latest completed scan for each provider
|
||||
# Order by -completed_at (matching the /latest summary path and the
|
||||
# daily summary upsert keyed on midnight(completed_at)) so that
|
||||
# overlapping scans do not make /resources and /latest read from
|
||||
# different scans and report diverging counts.
|
||||
latest_scan_ids = (
|
||||
Scan.objects.filter(tenant_id=tenant_id, state=StateChoices.COMPLETED)
|
||||
.order_by("provider_id", "-inserted_at")
|
||||
.order_by("provider_id", "-completed_at", "-inserted_at")
|
||||
.distinct("provider_id")
|
||||
.values_list("id", flat=True)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user