Skip to content

Add bulk-delete endpoints for remaining grid entities - #2569

Merged
EmanueleDeRossi1 merged 8 commits into
mainfrom
feature/bulk-delete-test-runs-test-sets
Aug 25, 2026
Merged

Add bulk-delete endpoints for remaining grid entities#2569
EmanueleDeRossi1 merged 8 commits into
mainfrom
feature/bulk-delete-test-runs-test-sets

Conversation

@EmanueleDeRossi1

Copy link
Copy Markdown
Collaborator

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 optional owner_attr parameter: when set, only rows owned by the caller are deleted, and ids that exist but belong to someone else are reported in a new forbidden_ids field instead of being silently skipped or deleted.
  • Added 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).
  • Added DELETE /test_sets/bulk, replacing the one-row-at-a-time delete loop the frontend used before.
  • Added DELETE /endpoints/bulk, DELETE /sources/bulk, and DELETE /tokens/bulk — none of these have an owner-only rule, so each is a direct wrapper around the existing bulk-delete helper.
  • Added 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.

@peqy peqy Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 validate hasattr(model, owner_attr)) so misuse can’t weaken the authorization semantics.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked with_visibility_filter(None): passing owner_attr without user_id won’t delete anything (owned_ids ends up empty), so it’s not an auth bypass. Still think it’s worth an explicit guard (if owner_attr and not user_id: raise ...) + hasattr(model, owner_attr) to prevent silent misuse/confusing forbidden_ids results.

"""
from rhesis.backend.celery.core import app as celery_app

organization_id, user_id = tenant_context

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 into get_test_run_task_ids, or compute task ids only from result["deleted_ids"] after the delete by re-querying those ids).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed fixed in e1d8d4e: get_test_run_task_ids() now filters to TestRun.user_id == user_id, so we only fetch task ids for runs that can actually land in deleted_ids.

raise HTTPException(status_code=500, detail="Internal server error")


@router.delete("/bulk", response_model=schemas.TaskBulkDeleteResponse)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed fixed in e1d8d4e: bulk task delete now tracks telemetry via track_feature_usage(..., action="bulk_deleted", count=...).

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.
get_test_run_task_ids only filtered by visibility (a no-op for
TestRun), so it fetched task ids for runs that would land in
forbidden_ids too -- wasted work, since only deleted_ids is ever
consulted for revocation. Scopes it to the caller's own runs to match.

Also adds the track_feature_usage call to the task bulk-delete route,
matching every other action in this router.
@EmanueleDeRossi1
EmanueleDeRossi1 force-pushed the feature/bulk-delete-test-runs-test-sets branch from e1d8d4e to af5e669 Compare August 25, 2026 07:36
@peqy

peqy Bot commented Aug 25, 2026

Copy link
Copy Markdown

Overall looks solid: consistent bulk endpoints + good route-ordering notes/tests, and the owner_attr extension closes the creator-only hole for TestRun/Task.

[Improvement] bulk_delete_by_ids(owner_attr=...) still allows being called without user_id, which will silently produce zero deletions and confusing forbidden_ids/not_found_ids splits.

Fix: add a guard like if owner_attr and not user_id: raise ValueError(...) (and optionally validate hasattr(model, owner_attr)) so misuse fails loudly.

Found 1 issue (0 critical, 1 improvement).

@peqy

peqy Bot commented Aug 25, 2026

Copy link
Copy Markdown

Re-reviewed latest commits: the TestRun task-id query now aligns with the owner-only delete rule, and bulk Task delete now tracks telemetry — both look good.

[Improvement] bulk_delete_by_ids(owner_attr=...) can still be called with owner_attr but no user_id, which currently fails “silently” (no deletions) and can yield confusing forbidden_ids/not_found_ids splits.

Fix: add a guard like if owner_attr and not user_id: raise ValueError(...) (and ideally validate hasattr(model, owner_attr)) so misuse fails loudly.

@EmanueleDeRossi1
EmanueleDeRossi1 merged commit 25f149f into main Aug 25, 2026
16 of 17 checks passed
@EmanueleDeRossi1
EmanueleDeRossi1 deleted the feature/bulk-delete-test-runs-test-sets branch August 25, 2026 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant