Add bulk-delete endpoints for remaining grid entities - #2569
Add bulk-delete endpoints for remaining grid entities#2569EmanueleDeRossi1 wants to merge 7 commits into
Conversation
bulk_delete_by_ids only understood organization scope and the
visibility column, so it couldn't back an owner-only (":own") delete
rule the way single-item delete does via authorize_object. Ids that
exist but fail the new owner_attr check land in "forbidden_ids"
instead of being silently skipped or deleted.
Single-item delete already enforces that only the creator may delete their own test run (DELETE_OWN). Wires bulk_delete_by_ids's new owner_attr check into a DELETE /test_runs/bulk endpoint so bulk delete enforces the same rule instead of silently dropping it, and revokes the Celery task for any active run among the ones actually deleted.
TestSet's visibility column is the only delete gate (no owner-only rule on top, unlike TestRun), so this ports Tests' existing bulk_delete_by_ids pattern as-is behind a new DELETE /test_sets/bulk endpoint, replacing the per-row delete loop the frontend used before.
No owner-only rule on endpoint delete, so this ports the existing bulk_delete_by_ids pattern as-is behind a new DELETE /endpoints/bulk endpoint.
No owner-only rule on source delete, so this ports the existing bulk_delete_by_ids pattern as-is behind a new DELETE /sources/bulk endpoint.
No owner-only rule on token delete, so this ports the existing bulk_delete_by_ids pattern as-is behind a new DELETE /tokens/bulk endpoint, matching the existing revoke_token naming internally.
Single-item delete already enforces that only the creator may delete their own task (DELETE_OWN). Wires bulk_delete_by_ids's owner_attr check into a DELETE /tasks/bulk endpoint, same treatment as test runs, so bulk delete enforces the same rule instead of silently dropping it.
There was a problem hiding this comment.
[Improvement] bulk_delete_by_ids(owner_attr=...) should probably guard against missing user_id (and optionally validate owner_attr exists) to avoid accidental weakening of owner-only semantics.
[Improvement] /test_runs/bulk fetches task ids using visibility filtering; consider aligning that query with the same ownership filter used for deletion to avoid extra work / subtle coupling.
[Question] /tasks/bulk doesn’t emit telemetry like the single-item delete—intentional?
Found 3 issues (0 critical, 2 improvements, 1 question).
| not_found_ids = [i for i in item_ids if i not in existing_ids] | ||
| not_found_ids = [i for i in item_ids if i not in visible_ids] | ||
|
|
||
| forbidden_ids = None |
There was a problem hiding this comment.
[Improvement] owner_attr is meant to enforce an owner-only delete rule, but if a caller ever passes owner_attr without a user_id (it’s still optional in the signature), the query will silently treat everything as not-owned.
Fix: consider an explicit guard like
if owner_attr and not user_id: raise ValueError(...)(and optionally validatehasattr(model, owner_attr)) so misuse can’t weaken the authorization semantics.
| """ | ||
| from rhesis.backend.celery.core import app as celery_app | ||
|
|
||
| organization_id, user_id = tenant_context |
There was a problem hiding this comment.
[Improvement] Bulk delete gets task ids via get_test_run_task_ids(..., user_id=user_id) which applies .with_visibility_filter(user_id). For TestRun you’re also enforcing creator-only delete (owner_attr="user_id").
If with_visibility_filter for TestRun is broader than “creator-only” (e.g., org-visible), you’ll fetch task IDs for runs you won’t delete (forbidden). That’s not a security issue, but it’s extra work and makes the revoke logic depend on visibility semantics.
Fix: consider aligning the task-id query with the same ownership filter (either pass an
owner_attr-like filter intoget_test_run_task_ids, or compute task ids only fromresult["deleted_ids"]after the delete by re-querying those ids).
| raise HTTPException(status_code=500, detail="Internal server error") | ||
|
|
||
|
|
||
| @router.delete("/bulk", response_model=schemas.TaskBulkDeleteResponse) |
There was a problem hiding this comment.
[Question] The bulk delete path enforces creator-only via owner_attr, but (unlike the single-item delete) it doesn’t record telemetry (track_feature_usage) or return a success message.
Is that intentional for bulk endpoints? If you want parity, you could track a single action="bulk_deleted" with count + ids (or just count), and keep the response shape consistent with other bulk deletes.
Purpose
Add bulk-select delete support to every backend entity that has a grid in the frontend. This covers the backend half of #2261 (Test Runs and Test Sets) and extends the same pattern to Endpoints, Sources, Tokens, and Tasks. Two of these entities (Test Run, Task) have an owner-only delete rule that the existing generic bulk-delete helper didn't understand, so this also extends that helper to support it safely instead of letting any org member bulk-delete rows they don't own.
What Changed
bulk_delete_by_ids()gained an optionalowner_attrparameter: when set, only rows owned by the caller are deleted, and ids that exist but belong to someone else are reported in a newforbidden_idsfield instead of being silently skipped or deleted.DELETE /test_runs/bulk, respecting the existing creator-only delete rule (also revokes the Celery task for any active run among the ones actually deleted).DELETE /test_sets/bulk, replacing the one-row-at-a-time delete loop the frontend used before.DELETE /endpoints/bulk,DELETE /sources/bulk, andDELETE /tokens/bulk— none of these have an owner-only rule, so each is a direct wrapper around the existing bulk-delete helper.DELETE /tasks/bulk, respecting the existing creator-only delete rule, same treatment as test runs.Additional Context
Closes the backend half of #2261. Team Members and account-level User deletion were intentionally left out of this batch — Team Members isn't backed by a real data grid today, and deleting another user's account carries different risk than deleting a resource like a test or endpoint. The frontend wiring (selection UI, bulk-action bars, API client methods) is a separate PR.
Testing
Added a route-level test file per entity under
tests/backend/routes/, covering the deleted/not-found split for every entity and the deleted/forbidden/not-found split for Test Run and Task specifically. Ran the full set of new tests together with the existing Tests bulk-delete suite and the object-level authorization suite to confirm no regressions; all 36 tests pass.