diff --git a/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py b/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py index ed80a2e4..5f7cd999 100644 --- a/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py +++ b/site/cds_rdm/inspire_harvester/transform/mappers/identifiers.py @@ -13,7 +13,7 @@ from flask import current_app from idutils.normalizers import normalize_isbn, normalize_urn -from idutils.validators import is_doi, is_urn +from idutils.validators import is_doi, is_url, is_urn from cds_rdm import schemes from cds_rdm.inspire_harvester.transform.mappers.mapper import MapperBase @@ -342,6 +342,23 @@ def map_value(self, src_record, ctx, logger): } ) + # External links from INSPIRE (e.g. university repository pages). + for url_entry in src_metadata.get("urls", []): + value = url_entry.get("value") if isinstance(url_entry, dict) else None + if not value: + continue + if not is_url(value): + ctx.errors.append(f"Invalid URL. | details: value={value}") + continue + identifiers.append( + { + "scheme": "url", + "identifier": value, + "relation_type": {"id": "references"}, + "resource_type": {"id": ctx.resource_type.value}, + } + ) + has_cds_doi = any( d.get("value", "").startswith(current_app.config["DATACITE_PREFIX"]) for d in src_metadata.get("dois", []) diff --git a/site/tests/conftest.py b/site/tests/conftest.py index f8ac8506..a0f6def8 100644 --- a/site/tests/conftest.py +++ b/site/tests/conftest.py @@ -1573,6 +1573,16 @@ def relation_type_v(app, relation_type): }, ) + vocab = vocabulary_service.create( + system_identity, + { + "id": "references", + "props": {"datacite": "References"}, + "title": {"en": "References"}, + "type": "relationtypes", + }, + ) + vocab = vocabulary_service.create( system_identity, { diff --git a/site/tests/inspire_harvester/test_harvester_job.py b/site/tests/inspire_harvester/test_harvester_job.py index ee6ecb72..ab1c21cc 100644 --- a/site/tests/inspire_harvester/test_harvester_job.py +++ b/site/tests/inspire_harvester/test_harvester_job.py @@ -79,6 +79,23 @@ "publication_date": "2024", "languages": [{"id": "eng", "title": {"en": "English", "da": "Engelsk"}}], "related_identifiers": [ + { + "identifier": "https://wigner.hu/~vertesi/publ/24-PhD-Thesis-VargaZ.pdf", + "relation_type": { + "id": "references", + "title": { + "en": "References", + }, + }, + "resource_type": { + "id": "publication-dissertation", + "title": { + "de": "Abschlussarbeit", + "en": "Thesis", + }, + }, + "scheme": "url", + }, { "identifier": "2840463", "relation_type": { @@ -213,6 +230,40 @@ }, "scheme": "cdsrn", }, + { + "identifier": "https://tel.archives-ouvertes.fr/tel-01155127/", + "relation_type": { + "id": "references", + "title": { + "en": "References", + }, + }, + "resource_type": { + "id": "publication-dissertation", + "title": { + "de": "Abschlussarbeit", + "en": "Thesis", + }, + }, + "scheme": "url", + }, + { + "identifier": "https://tel.archives-ouvertes.fr/tel-01155127", + "relation_type": { + "id": "references", + "title": { + "en": "References", + }, + }, + "resource_type": { + "id": "publication-dissertation", + "title": { + "de": "Abschlussarbeit", + "en": "Thesis", + }, + }, + "scheme": "url", + }, { "identifier": "1452604", "relation_type": { @@ -289,6 +340,43 @@ "publication_date": "2024-05", "languages": [{"id": "spa", "title": {"en": "Spanish"}}], "related_identifiers": [ + { + "identifier": ( + "https://www.ifisica.uaslp.mx/~jurgen/" + "AkbarEmmanuelDiazRodarte-Lic.pdf" + ), + "relation_type": { + "id": "references", + "title": { + "en": "References", + }, + }, + "resource_type": { + "id": "publication-dissertation", + "title": { + "de": "Abschlussarbeit", + "en": "Thesis", + }, + }, + "scheme": "url", + }, + { + "identifier": "https://www.ifisica.uaslp.mx/~jurgen/Theses.html", + "relation_type": { + "id": "references", + "title": { + "en": "References", + }, + }, + "resource_type": { + "id": "publication-dissertation", + "title": { + "de": "Abschlussarbeit", + "en": "Thesis", + }, + }, + "scheme": "url", + }, { "identifier": "2802969", "relation_type": { diff --git a/site/tests/inspire_harvester/test_transformer.py b/site/tests/inspire_harvester/test_transformer.py index 5211b877..0165f89a 100644 --- a/site/tests/inspire_harvester/test_transformer.py +++ b/site/tests/inspire_harvester/test_transformer.py @@ -138,6 +138,75 @@ def test_transform_related_identifiers_skips_invalid_urn(running_app): assert f"Invalid URN. | details: value={raw}" in ctx.errors +def test_transform_related_identifiers_maps_inspire_urls(running_app): + """INSPIRE metadata.urls become related identifiers with scheme url.""" + src_record = { + "metadata": { + "urls": [ + { + "value": ( + "https://tesidottorato.depositolegale.it/" + "handle/20.500.14242/121964" + ) + }, + {"value": "https://iris.unife.it/handle/11392/2549512"}, + {"value": "not-a-url"}, + {"value": ""}, + {}, + ], + }, + "created": "2023-01-01", + } + ctx = MetadataSerializationContext( + resource_type=ResourceType.THESIS, inspire_id="2808071" + ) + logger = Logger(inspire_id="2808071") + + result = RelatedIdentifiersMapper().map_value(src_record, ctx, logger) + + assert { + "scheme": "url", + "identifier": ( + "https://tesidottorato.depositolegale.it/handle/20.500.14242/121964" + ), + "relation_type": {"id": "references"}, + "resource_type": {"id": "publication-dissertation"}, + } in result + assert { + "scheme": "url", + "identifier": "https://iris.unife.it/handle/11392/2549512", + "relation_type": {"id": "references"}, + "resource_type": {"id": "publication-dissertation"}, + } in result + assert not any(item.get("identifier") == "not-a-url" for item in result) + assert "Invalid URL. | details: value=not-a-url" in ctx.errors + + +def test_transform_related_identifiers_dedupes_urls(running_app): + """Duplicate INSPIRE urls are mapped once.""" + url = "https://example.org/thesis" + src_record = { + "metadata": {"urls": [{"value": url}, {"value": url}]}, + "created": "2023-01-01", + } + ctx = MetadataSerializationContext( + resource_type=ResourceType.OTHER, inspire_id="1" + ) + logger = Logger(inspire_id="1") + + result = RelatedIdentifiersMapper().map_value(src_record, ctx, logger) + + url_items = [item for item in result if item.get("scheme") == "url"] + assert url_items == [ + { + "scheme": "url", + "identifier": url, + "relation_type": {"id": "references"}, + "resource_type": {"id": "publication-other"}, + } + ] + + def test_transform_identifiers(running_app): """Test IdentifiersMapper.""" src_metadata = {