Data Bugs

Soft-Deleted Record Still Returned in API

When a user record is soft-deleted — its deleted_at column is set to a non-null timestamp — it continues to appear in GET /api/users list responses. The database query does not include a WHERE deleted_at IS NULL filter, so soft-deleted records are indistinguishable from active ones in API responses.

MediumBeginnerAPI testingManual testingDatabase testing

// UNDERSTAND

// Symptoms

  • A user with ID 201 that was deleted via DELETE /api/users/201 still appears in the response body of GET /api/users
  • The deleted record's data is returned in search results and auto-complete suggestions
  • Attempting to interact with the returned record (e.g. adding it to a team) succeeds even though the account no longer exists
  • The total count returned by the API includes deleted records, causing the displayed count to be higher than the actual active record count

// Root Cause

  • The ORM query or raw SQL for list and search endpoints omits the WHERE deleted_at IS NULL filter (or the equivalent WHERE is_deleted = false). Soft-deletion sets the flag in the database but the read path never inspects it.
  • A global default scope that would automatically exclude deleted records is either absent from the ORM model configuration or was explicitly disabled for this query and never restored.

// Where It Appears

  • User management and team member list endpoints that use soft-deletion
  • Product or content catalogues where items are archived rather than hard-deleted
  • Search and autocomplete endpoints that query the same table without the deletion filter
  • Reporting endpoints that count or aggregate records including soft-deleted rows

// REPRODUCE & TEST

// How to Reproduce

  1. 01Create a user record with POST /api/users with body { "name": "Test User", "email": "testuser@example.com" }; note the returned user ID (e.g. 201)
  2. 02Soft-delete the record with DELETE /api/users/201; confirm the response is 200 OK or 204 No Content
  3. 03Send GET /api/users and scan the response for a record with id: 201 or email: 'testuser@example.com'
  4. 04If user 201 appears in the list, the soft-deleted record is still being returned

// Test Data Needed

  • Permission to create and delete user records via the API
  • The user ID returned from the creation step (201) to search for in the list response

// Manual Testing Ideas

  • Create a record, delete it via the UI, then check the list view and API response to confirm it is no longer present
  • Search by the deleted record's name or email and confirm no results are returned
  • Check whether the API's total count field reflects the deletion — a count that includes deleted records suggests the filter is missing from count queries too
  • Query a search or autocomplete endpoint with the deleted record's name to confirm it is excluded from those results as well
  • Check related join queries: if soft-deleted users are still returned in team-member lists or assignment dropdowns, the filter is missing from multiple places

// API Testing Ideas

  • Send POST /api/users with { "name": "Test User", "email": "testuser@example.com" }; record the returned id (201)
  • Send DELETE /api/users/201; assert the response is 200 OK or 204 No Content
  • Send GET /api/users and assert no record with id 201 or email 'testuser@example.com' appears in the response
  • Send GET /api/users?search=testuser@example.com and assert an empty result set is returned
  • Send GET /api/users/201 directly and assert the response is 404 Not Found — not 200 with the deleted record

// Automation Idea

Send POST /api/users to create a test user; capture the returned id (201). Send DELETE /api/users/201. Send GET /api/users and assert that no item in the response array has id equal to 201. Also send GET /api/users/201 and assert the status is 404. Both assertions confirm the deleted record is excluded from all read paths.

// Expected Result

After DELETE /api/users/201, the record does not appear in GET /api/users list responses, search results, or direct GET /api/users/201 lookups.

// Actual Result (Example)

After DELETE /api/users/201 returns 204 No Content, GET /api/users includes a record with id: 201, name: 'Test User', and email: 'testuser@example.com'. The soft-deleted user is returned as an active record.

// REPORT IT

Example Bug Report

Title
Soft-deleted user ID 201 still appears in GET /api/users after DELETE /api/users/201
Severity
Medium
Environment
Staging environment Postman Admin bearer token Endpoint: GET /api/users
Steps to Reproduce
  1. 01Send POST /api/users with { "name": "Test User", "email": "testuser@example.com" }; note the returned id (201)
  2. 02Send DELETE /api/users/201; confirm the response is 204 No Content
  3. 03Send GET /api/users and search the response for id 201
Expected Result
No record with id 201 appears in the GET /api/users response.
Actual Result
GET /api/users returns a record with id: 201, name: 'Test User', email: 'testuser@example.com'. The deleted record is present in the active user list.
Impact
Deleted users appear in team-member lists, assignment dropdowns, and audit reports. Applications that trust the list API to reflect current state may act on stale data — for example, sending notifications to deleted users or including their data in exports.

// RELATED