Skip to content

Commit 5753371

Browse files
committed
fix(records-ui): deleted PIDs return 404
- Fixes cases where a PID (record or draft) with deleted status and its record row deleted, should return a 404 page.
1 parent e18f3fc commit 5753371

File tree

2 files changed

+64
-5
lines changed

2 files changed

+64
-5
lines changed

invenio_app_rdm/records_ui/views/records.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -475,20 +475,23 @@ def draft_not_found_error(error):
475475

476476
def record_tombstone_error(error):
477477
"""Tombstone page."""
478-
# the RecordDeletedError will have the following properties,
479-
# while the PIDDeletedError won't
478+
# Handles both RecordDeletedException and PIDDeletedError:
479+
# - RecordDeletedException: always has record and result_item attributes
480+
# - PIDDeletedError: has a record attribute that can be None if the underlying
481+
# record object doesn't exist (e.g. deleted draft, or purged record)
480482
record = getattr(error, "record", None)
481483
if (record_ui := getattr(error, "result_item", None)) is not None:
482484
if record is None:
483485
record = record_ui._record
484486

485487
record_ui = UIJSONSerializer().dump_obj(record_ui.to_dict())
486488

487-
# render a 404 page if the tombstone isn't visible
488-
if not record.tombstone.is_visible:
489+
# Return 404 if there's no record (e.g. deleted draft, purged record) or if the
490+
# tombstone is not visible (e.g. admin chose to hide it)
491+
if not (record and record.tombstone.is_visible):
489492
return not_found_error(error)
490493

491-
# we only render a tombstone page if there is a record with a visible tombstone
494+
# Only render a tombstone page if there is a record with a visible tombstone
492495
return (
493496
render_template(
494497
"invenio_app_rdm/records/tombstone.html",

tests/ui/test_tombstone.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (C) 2025 CERN.
4+
#
5+
# Invenio-App-RDM is free software; you can redistribute it and/or modify
6+
# it under the terms of the MIT License; see LICENSE file for more details.
7+
8+
"""Tests for tombstone/deleted record handling."""
9+
10+
from flask import url_for
11+
from invenio_access.permissions import system_identity
12+
from invenio_rdm_records.proxies import current_rdm_records_service as service
13+
14+
15+
def test_deleted_record_shows_tombstone(client, record_with_file):
16+
"""Test that a soft-deleted record shows the tombstone page (410)."""
17+
record_id = record_with_file.id
18+
19+
# Record should be accessible before deletion
20+
record_url = url_for("invenio_app_rdm_records.record_detail", pid_value=record_id)
21+
response = client.get(record_url)
22+
assert response.status_code == 200
23+
24+
# Delete the record (soft delete with tombstone)
25+
service.delete_record(system_identity, record_id, {})
26+
27+
# Accessing the record should now show the tombstone page
28+
response = client.get(record_url)
29+
assert response.status_code == 410
30+
31+
32+
def test_deleted_record_with_hidden_tombstone(client, record_with_file):
33+
"""Test that a deleted record with hidden tombstone returns 404."""
34+
record_id = record_with_file.id
35+
36+
# Delete the record with tombstone visibility set to False
37+
service.delete_record(system_identity, record_id, {"is_visible": False})
38+
39+
# Accessing a record with hidden tombstone should return 404
40+
record_url = url_for("invenio_app_rdm_records.record_detail", pid_value=record_id)
41+
response = client.get(record_url)
42+
assert response.status_code == 404
43+
44+
45+
def test_deleted_draft_returns_404(client, draft_with_file):
46+
"""Test that accessing a deleted draft returns 404.
47+
48+
When a draft is deleted, the PID is marked as DELETED but the underlying
49+
record object is removed. Accessing such a PID should return 404.
50+
"""
51+
draft_id = draft_with_file.id
52+
service.delete_draft(system_identity, draft_id)
53+
54+
record_url = url_for("invenio_app_rdm_records.record_detail", pid_value=draft_id)
55+
response = client.get(record_url)
56+
assert response.status_code == 404

0 commit comments

Comments
 (0)