diff --git a/tests/api/test_graphql_authenticated.py b/tests/api/test_graphql_authenticated.py index 716c787..7f77fb6 100644 --- a/tests/api/test_graphql_authenticated.py +++ b/tests/api/test_graphql_authenticated.py @@ -260,6 +260,55 @@ def test_audits_without_sort_options_returns_well_formed_response( assert "totalItemsCount" in wrapper +AUDITS_BY_UPDATED_AT = """ + query($limit: Int, $filters: [FilterSpec!], $sortOptions: [SortSpec!]) { + audits(limit: $limit, filters: $filters, sortOptions: $sortOptions) { + data { id updatedAt } + totalItemsCount + } + } +""" + + +class TestAuditsUpdatedAt: + """`updated_at` is in the audits sort and filter allowlists (ParakhAI-Backend#130, #133).""" + + def _audits(self, client, **variables): + result = client(AUDITS_BY_UPDATED_AT, variables={"limit": 20, **variables}) + assert not result.get("errors"), f"audits query errored: {result.get('errors')}" + return result["data"]["audits"] + + def test_audits_sorted_by_updated_at_asc_is_non_decreasing(self, authenticated_graphql_client): + # Ascending, because the default order (created_at desc) would fail it. + rows = self._audits( + authenticated_graphql_client, + sortOptions=[{"field": "updated_at", "direction": "asc"}], + )["data"] + values = [r["updatedAt"] for r in rows] + if len(values) < 2: + pytest.skip("Fewer than 2 audits — not enough data to prove ordering") + assert values == sorted(values), f"updatedAt is not ascending: {values}" + + def test_audits_filtered_by_updated_at_narrows_the_result(self, authenticated_graphql_client): + everything = self._audits( + authenticated_graphql_client, + sortOptions=[{"field": "updated_at", "direction": "desc"}], + ) + if everything["totalItemsCount"] < 11: + pytest.skip("Too few audits to pick a cutoff that narrows the result") + cutoff = everything["data"][9]["updatedAt"] + + filtered = self._audits( + authenticated_graphql_client, + filters=[{"field": "updated_at", "condition": "gte", "value": cutoff}], + ) + assert 0 < filtered["totalItemsCount"] < everything["totalItemsCount"], ( + f"Filtering on updated_at >= {cutoff} returned {filtered['totalItemsCount']} " + f"of {everything['totalItemsCount']} audits; the filter was not applied" + ) + assert all(r["updatedAt"] >= cutoff for r in filtered["data"]) + + # ── Audit tests pagination (new — Jun 2026) ─────────────────────────────────── diff --git a/tests/api/test_security.py b/tests/api/test_security.py index 710bc9c..ba8e020 100644 --- a/tests/api/test_security.py +++ b/tests/api/test_security.py @@ -649,7 +649,7 @@ def test_audits_sort_by_non_allowlisted_field_is_rejected( """A sort field outside the server allowlist must fail closed. Allowed sort fields for `audits` are name/status/model_snapshot__display_name/ - passed_tests/evaluation_mode/audit_type/completed_at/created_at. Anything else + passed_tests/evaluation_mode/audit_type/completed_at/created_at/updated_at. Anything else must be rejected — not silently ignored (falling back to default ordering while looking successful) and not passed through to the ORM. """ @@ -681,7 +681,7 @@ def test_audits_filter_by_non_allowlisted_field_is_rejected( """A filter field outside the server allowlist must fail closed. Allowed filter fields for `audits` are name/model_snapshot__display_name/ - audit_type/evaluation_mode/model_id/status/completed_at — notably `requested_by` + audit_type/evaluation_mode/model_id/status/completed_at/updated_at — notably `requested_by` (the owning user FK) is NOT allowed, so a client can't filter by arbitrary internal fields even though the underlying ORM call would otherwise accept them. """