Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions site/cds_rdm/inspire_harvester/load/matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,12 @@ def match_migrated(self, entry, logger):
record = current_rdm_records_service.read_latest(
self.identity, id_=parent_pid.pid_value
)
if record._record.deletion_status.is_deleted:
raise WriterError(
"Matched CDS-RDM record is tombstoned - update the "
"CDSRDM id on INSPIRE to the live record. | details: "
f"cds_id={record.id}, legacy_recid={cds_id}"
)
logger.debug(f"Found record by legacy recid {cds_id}: {record.id}")
if record.id not in matched_ids:
matched_ids.append(record.id)
Expand Down
9 changes: 8 additions & 1 deletion site/cds_rdm/inspire_harvester/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from invenio_access.permissions import system_identity
from invenio_access.utils import get_identity
from invenio_rdm_records.proxies import current_rdm_records_service
from invenio_rdm_records.services.errors import RecordDeletedException
from invenio_users_resources.proxies import current_users_service
from invenio_vocabularies.datastreams.errors import WriterError
from invenio_vocabularies.datastreams.writers import BaseWriter
Expand Down Expand Up @@ -141,7 +142,13 @@ def _update_record(
"""Return True if published, None if skipped or validation failed."""
entry = {k: v for k, v in stream_entry.entry.items() if k != "_inspire_ctx"}
ctx = stream_entry.entry["_inspire_ctx"]
record = current_rdm_records_service.read(self.identity, record_pid)
try:
record = current_rdm_records_service.read(self.identity, record_pid)
except RecordDeletedException:
raise WriterError(
"Matched CDS-RDM record is tombstoned - update the CDSRDM id "
f"on INSPIRE to the live record. | details: cds_id={record_pid}"
)
record_dict = record.to_dict()
errors = self.record_validator.validate(
mode="update",
Expand Down
37 changes: 34 additions & 3 deletions site/tests/inspire_harvester/test_matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,40 @@ def test_matcher_finds_record_by_legacy_recid(
logger = Mock()
stream_entry = legacy_entry("2765541")
mock_get_pid.return_value = Mock(pid_value="parent-1")
mock_read_latest.return_value = Mock(id="rec-1")
mock_read_latest.return_value = Mock(
id="rec-1", _record=Mock(deletion_status=Mock(is_deleted=False))
)

result = matcher.match(stream_entry, inspire_id="111", logger=logger)

assert result.found is True
assert result.record_pid == "rec-1"


@patch("cds_rdm.inspire_harvester.load.matcher.current_rdm_records_service.read_latest")
@patch("cds_rdm.inspire_harvester.load.matcher.get_pid_by_legacy_recid")
def test_matcher_errors_when_legacy_recid_hits_tombstoned_record(
mock_get_pid, mock_read_latest, running_app
):
"""Tombstoned lrecid match should tell curators to fix the CDSRDM id on INSPIRE."""
matcher = RecordMatcher()
logger = Mock()
stream_entry = legacy_entry("2689813")
mock_get_pid.return_value = Mock(pid_value="parent-1")
mock_read_latest.return_value = Mock(
id="xcmbd-mvr19",
_record=Mock(deletion_status=Mock(is_deleted=True)),
)

with pytest.raises(WriterError, match="tombstoned") as exc_info:
matcher.match(stream_entry, inspire_id="1743143", logger=logger)

message = str(exc_info.value)
assert "update the CDSRDM id on INSPIRE" in message
assert "cds_id=xcmbd-mvr19" in message
assert "legacy_recid=2689813" in message


@patch("cds_rdm.inspire_harvester.load.matcher.current_rdm_records_service.read_latest")
@patch("cds_rdm.inspire_harvester.load.matcher.get_pid_by_legacy_recid")
def test_matcher_tries_each_legacy_cds_id_until_pidstore_hits(
Expand All @@ -179,7 +205,9 @@ def test_matcher_tries_each_legacy_cds_id_until_pidstore_hits(
logger = Mock()
stream_entry = legacy_entry("2798711", "2765541")
mock_get_pid.side_effect = [NoResultFound(), Mock(pid_value="parent-1")]
mock_read_latest.return_value = Mock(id="rec-1")
mock_read_latest.return_value = Mock(
id="rec-1", _record=Mock(deletion_status=Mock(is_deleted=False))
)

result = matcher.match(stream_entry, inspire_id="111", logger=logger)

Expand All @@ -199,7 +227,10 @@ def test_matcher_is_ambiguous_when_legacy_ids_hit_different_records(
logger = Mock()
stream_entry = legacy_entry("2765541", "2798711")
mock_get_pid.side_effect = [Mock(pid_value="parent-1"), Mock(pid_value="parent-2")]
mock_read_latest.side_effect = [Mock(id="rec-1"), Mock(id="rec-2")]
mock_read_latest.side_effect = [
Mock(id="rec-1", _record=Mock(deletion_status=Mock(is_deleted=False))),
Mock(id="rec-2", _record=Mock(deletion_status=Mock(is_deleted=False))),
]

result = matcher.match(stream_entry, inspire_id="111", logger=logger)

Expand Down
22 changes: 22 additions & 0 deletions site/tests/inspire_harvester/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from invenio_access.permissions import system_identity
from invenio_rdm_records.proxies import current_rdm_records, current_rdm_records_service
from invenio_rdm_records.records.api import RDMRecord
from invenio_rdm_records.services.errors import RecordDeletedException
from invenio_vocabularies.datastreams import StreamEntry
from invenio_vocabularies.datastreams.errors import WriterError

Expand Down Expand Up @@ -258,6 +259,27 @@ def test_writer_errors_when_legacy_recid_is_unknown(running_app, scientific_comm
assert "was not found in the old CDS" in result.errors[0]


@patch("cds_rdm.inspire_harvester.writer.current_rdm_records_service.read")
def test_writer_errors_when_matched_record_is_tombstoned(
mock_read, running_app, scientific_community
):
"""Reading a tombstoned match should yield a curator-facing harvest error."""
writer = InspireWriter()
writer.matcher.match = Mock(
return_value=MatchResult(found=True, record_pid="xcmbd-mvr19")
)
mock_read.side_effect = RecordDeletedException(Mock(id="xcmbd-mvr19"))
writer._create_record = Mock()

result = writer.write(legacy_entry("2689813"))

writer._create_record.assert_not_called()
assert result.errors
assert "tombstoned" in result.errors[0]
assert "update the CDSRDM id on INSPIRE" in result.errors[0]
assert "cds_id=xcmbd-mvr19" in result.errors[0]


def test_writer_errors_when_pidstore_missed_a_migrated_record(
running_app, scientific_community
):
Expand Down
Loading