From 53f4ac6ad41ab44ce43b1ce2efd8571c0e04d72d Mon Sep 17 00:00:00 2001 From: Saksham Date: Fri, 26 Jun 2026 12:16:33 +0200 Subject: [PATCH 1/3] feat(rdm.migration): Configurable auto-adding secondary community --- cds_migrator_kit/errors.py | 6 + .../rdm/records/transform/config.py | 16 ++ .../rdm/records/transform/transform.py | 28 +++- tests/cds-rdm/conftest.py | 14 +- tests/cds-rdm/test_bulletin_issue.py | 5 - tests/cds-rdm/test_ep_approval_entry.py | 8 +- tests/cds-rdm/test_full_migration.py | 9 ++ tests/cds-rdm/test_hr_migration.py | 1 + tests/cds-rdm/test_scientific_community.py | 147 ++++++++++++++++++ 9 files changed, 223 insertions(+), 11 deletions(-) create mode 100644 tests/cds-rdm/test_scientific_community.py diff --git a/cds_migrator_kit/errors.py b/cds_migrator_kit/errors.py index 5cbe912d..a998ac4a 100644 --- a/cds_migrator_kit/errors.py +++ b/cds_migrator_kit/errors.py @@ -85,3 +85,9 @@ class RecordFlaggedCuration(CDSMigrationException): """Record statistics error.""" description = "[Record needs to be curated]" + + +class MissingConfiguration(CDSMigrationException): + """Missing configuration exception.""" + + description = "[Missing configuration]" diff --git a/cds_migrator_kit/rdm/records/transform/config.py b/cds_migrator_kit/rdm/records/transform/config.py index aeca8b90..87b36807 100644 --- a/cds_migrator_kit/rdm/records/transform/config.py +++ b/cds_migrator_kit/rdm/records/transform/config.py @@ -157,3 +157,19 @@ FILE_SUBFORMATS_TO_DROP = ["pdfa", "unstamped"] + +# Public research publication resource types that are auto-included in the CERN Research community. +CDS_CERN_SCIENTIFIC_RESOURCE_TYPES = { + "publication-dissertation", # Already included by the migrator for thesis records. + "publication-book", + "publication-section", + "publication-conferencepaper", + "publication-conferenceproceeding", + "publication-journal", + "publication-article", + "publication-preprint", + "publication-report", + "publication-technicalnote", + "publication-note", + "publication", +} diff --git a/cds_migrator_kit/rdm/records/transform/transform.py b/cds_migrator_kit/rdm/records/transform/transform.py index 9b456df4..8f721735 100644 --- a/cds_migrator_kit/rdm/records/transform/transform.py +++ b/cds_migrator_kit/rdm/records/transform/transform.py @@ -6,7 +6,7 @@ # the terms of the MIT License; see LICENSE file for more details. """CDS-RDM transform step module.""" -import datetime + import logging from collections import OrderedDict from copy import deepcopy @@ -32,6 +32,7 @@ from cds_migrator_kit.errors import ( ManualImportRequired, + MissingConfiguration, MissingRequiredField, MultipleModelsMatched, RecordFlaggedCuration, @@ -43,6 +44,7 @@ VOCABULARIES_NAMES_SCHEMES, ) from cds_migrator_kit.rdm.records.transform.config import ( + CDS_CERN_SCIENTIFIC_RESOURCE_TYPES, FILE_SUBFORMATS_TO_DROP, IDENTIFIERS_SCHEMES_TO_DROP, IDENTIFIERS_VALUES_TO_DROP, @@ -853,9 +855,32 @@ def __init__( self.db_state = {"affiliations": CDSMigrationAffiliationMapping} super().__init__(workers, throw) + def _should_add_scientific_community(self, record): + if self.restricted or record.get("access") != "public": + return False + resource_type_id = ( + record.get("json", {}) + .get("metadata", {}) + .get("resource_type", {}) + .get("id") + ) + return resource_type_id in CDS_CERN_SCIENTIFIC_RESOURCE_TYPES + def _communities_ids(self, entry, record): communities = record.get("communities", []) communities = self.communities_ids + [slug for slug in communities] + + scientific_community = current_app.config.get( + "CDS_CERN_SCIENTIFIC_COMMUNITY_ID" + ) + if not scientific_community: + raise MissingConfiguration( + "CDS_CERN_SCIENTIFIC_COMMUNITY_ID is not configured" + ) + if self._should_add_scientific_community(record): + if scientific_community not in communities: + communities.append(scientific_community) + if communities: return {"ids": communities, "default": self.communities_ids[0]} return {} @@ -909,6 +934,7 @@ def _transform(self, entry): ManualImportRequired, MissingRequiredField, MultipleModelsMatched, + MissingConfiguration, ) as e: migration_logger.add_log(e, record=entry) diff --git a/tests/cds-rdm/conftest.py b/tests/cds-rdm/conftest.py index 501806fa..00161589 100644 --- a/tests/cds-rdm/conftest.py +++ b/tests/cds-rdm/conftest.py @@ -277,7 +277,7 @@ def running_app( @pytest.fixture -def test_app(running_app): +def test_app(running_app, cern_scientific_community): """Get current app.""" running_app.app.config["RDM_PERSISTENT_IDENTIFIERS"]["doi"]["required"] = False running_app.app.config["RDM_PARENT_PERSISTENT_IDENTIFIERS"]["doi"][ @@ -1702,6 +1702,18 @@ def community(running_app, db): return comm +@pytest.fixture() +def cern_scientific_community(running_app, db, app): + """A CERN Research community fixture.""" + comm = Community.create({}) + comm.slug = "cern-research" + comm.metadata = {"title": "CERN Research"} + comm.commit() + db.session.commit() + app.config["CDS_CERN_SCIENTIFIC_COMMUNITY_ID"] = str(comm.id) + return comm + + # @pytest.fixture() # def users(app, db): # """Create example user.""" diff --git a/tests/cds-rdm/test_bulletin_issue.py b/tests/cds-rdm/test_bulletin_issue.py index abd99390..5135b70a 100644 --- a/tests/cds-rdm/test_bulletin_issue.py +++ b/tests/cds-rdm/test_bulletin_issue.py @@ -79,8 +79,3 @@ def test_bulletin_issue( ) if record["legacy_recid"] == "2234683": parent_related_identifier(loaded_rec) - - -# FAILED tests/cds-rdm/test_access_permissions.py::test_access_permissions - assert 2 == 3 -# FAILED tests/cds-rdm/test_bulletin_issue.py::test_bulletin_issue - AssertionError: assert [{'identifier...heme': 'cds'}] == [{'identifier...heme': 'cds'}] -# FAILED tests/cds-rdm/test_full_migration.py::test_full_migration_stream - assert 0 == 2 diff --git a/tests/cds-rdm/test_ep_approval_entry.py b/tests/cds-rdm/test_ep_approval_entry.py index 33bd9d3b..ac6374ca 100644 --- a/tests/cds-rdm/test_ep_approval_entry.py +++ b/tests/cds-rdm/test_ep_approval_entry.py @@ -14,11 +14,11 @@ import pytest from cds_migrator_kit.errors import UnexpectedValue -from cds_migrator_kit.rdm.migration_config import CDS_CERN_SCIENTIFIC_COMMUNITY_ID from cds_migrator_kit.rdm.records.load.ep_approval_entry import ( EPPHAPP_FILE_TYPE, PublicEntry, RestrictedEntry, + _cern_scientific_community_id, ) RECID = "12345" @@ -526,7 +526,7 @@ def test_public_adds_cern_scientific_community(self, app): entry, _make_approval_request(), _make_migration_logger() ).build() - assert CDS_CERN_SCIENTIFIC_COMMUNITY_ID in ( + assert _cern_scientific_community_id() in ( result["parent"]["json"]["communities"]["ids"] ) @@ -534,14 +534,14 @@ def test_public_does_not_duplicate_community(self, app): entry = _make_entry(_versions_with_epphapp()) entry["parent"]["json"]["communities"]["ids"] = [ "example-community", - CDS_CERN_SCIENTIFIC_COMMUNITY_ID, + _cern_scientific_community_id(), ] result = PublicEntry( entry, _make_approval_request(), _make_migration_logger() ).build() community_ids = result["parent"]["json"]["communities"]["ids"] - assert community_ids.count(CDS_CERN_SCIENTIFIC_COMMUNITY_ID) == 1 + assert community_ids.count(_cern_scientific_community_id()) == 1 class TestEntryImmutability: diff --git a/tests/cds-rdm/test_full_migration.py b/tests/cds-rdm/test_full_migration.py index 3df27f70..f9bf9d9f 100644 --- a/tests/cds-rdm/test_full_migration.py +++ b/tests/cds-rdm/test_full_migration.py @@ -471,6 +471,7 @@ def test_full_migration_stream( superuser_identity, orcid_name_data, community, + cern_scientific_community, # Fixture for the CERN Scientific community, so that it gets auto included for PUBLIC-ations. mocker, groups, ): @@ -526,6 +527,8 @@ def test_full_migration_stream( if record["legacy_recid"] == "2294138": author_with_inspire(loaded_rec) + # Check if the record is also included in the CERN Scientific community since it is a publication report + scientific_community_inclusion(loaded_rec, cern_scientific_community.id) # Check if remote account has the correct metadata # Check if user profile has the correct metadata user_metadata() @@ -595,3 +598,9 @@ def user_metadata(): "person_id": "11115", "department": "IT", } + + +def scientific_community_inclusion(record, cern_scientific_community_uuid): + """Checks if the record is included in the CERN Scientific community.""" + assert str(cern_scientific_community_uuid) in record._record.parent.communities.ids + assert cern_scientific_community_uuid != record._record.parent.communities.default diff --git a/tests/cds-rdm/test_hr_migration.py b/tests/cds-rdm/test_hr_migration.py index 8977f413..c6e87d97 100644 --- a/tests/cds-rdm/test_hr_migration.py +++ b/tests/cds-rdm/test_hr_migration.py @@ -6,6 +6,7 @@ # the terms of the MIT License; see LICENSE file for more details. """Tests suites.""" + import json from pathlib import Path diff --git a/tests/cds-rdm/test_scientific_community.py b/tests/cds-rdm/test_scientific_community.py new file mode 100644 index 00000000..e7b79ab4 --- /dev/null +++ b/tests/cds-rdm/test_scientific_community.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2026 CERN. +# +# CDS-RDM is free software; you can redistribute it and/or modify it under +# the terms of the MIT License; see LICENSE file for more details. + +"""Tests for auto-inclusion in the CERN Research community.""" + +from unittest.mock import MagicMock + +import pytest + +from cds_migrator_kit.errors import MissingConfiguration +from cds_migrator_kit.rdm.records.transform.config import ( + CDS_CERN_SCIENTIFIC_RESOURCE_TYPES, +) +from cds_migrator_kit.rdm.records.transform.transform import CDSToRDMRecordTransform + + +def _test_record( + access="public", + resource_type="publication-preprint", + communities=[], + recid="123456", +): + """Build a minimal CDSToRDMRecordEntry.transform() output for community tests.""" + metadata = { + "title": "Test record", + "publication_date": "2020-01-01", + } + if resource_type is not None: + metadata["resource_type"] = {"id": resource_type} + + return { + "recid": recid, + "access": access, + "communities": communities, + "json": { + "files": {"enabled": False}, + "metadata": metadata, + }, + } + + +@pytest.fixture +def transform(tmp_path, community): + """Transform instance with a collection community configured.""" + return CDSToRDMRecordTransform( + files_dump_dir=tmp_path, + missing_users=tmp_path, + communities_ids=[str(community.id)], + migration_logger=MagicMock(), + ) + + +class TestCommunitiesIds: + """Test CDSToRDMRecordTransform._communities_ids().""" + + def test_adds_scientific_community_for_public_research_test_record( + self, transform, community, cern_scientific_community + ): + """Public research records are included in the CERN Scientific community.""" + result = transform._communities_ids({}, _test_record()) + + assert result == { + "ids": [str(community.id), str(cern_scientific_community.id)], + "default": str(community.id), + } + + def test_keep_collection_community_as_default( + self, transform, community, cern_scientific_community + ): + """Collection community remains the default when CERN Scientific community is added.""" + result = transform._communities_ids( + {}, + _test_record(communities=["test-community"]), + ) + + assert result["default"] == str(community.id) + assert result["ids"] == [ + str(community.id), + "test-community", + str(cern_scientific_community.id), + ] + + @pytest.mark.parametrize("resource_type", CDS_CERN_SCIENTIFIC_RESOURCE_TYPES) + def test_research_resource_types( + self, transform, cern_scientific_community, resource_type + ): + """All configured public research resource types trigger inclusion.""" + result = transform._communities_ids( + {}, _test_record(resource_type=resource_type) + ) + + assert str(cern_scientific_community.id) in result["ids"] + assert cern_scientific_community.id != result["default"] + + def test_skip_restricted_test_record( + self, transform, community, cern_scientific_community + ): + """Restricted records are not included in the CERN Research community.""" + result = transform._communities_ids({}, _test_record(access="restricted")) + + assert result == { + "ids": [str(community.id)], + "default": str(community.id), + } + + def test_skip_non_research_resource_type( + self, transform, community, cern_scientific_community + ): + """Non-research resource types are not included in the CERN Scientific community.""" + result = transform._communities_ids({}, _test_record(resource_type="other")) + + assert result == { + "ids": [str(community.id)], + "default": str(community.id), + } + + def test_skip_when_stream_is_restricted( + self, tmp_path, community, cern_scientific_community + ): + """Records on restricted migration streams are not included in the CERN Scientific community.""" + transform = CDSToRDMRecordTransform( + files_dump_dir=tmp_path, + missing_users=tmp_path, + communities_ids=[str(community.id)], + restricted=True, + migration_logger=MagicMock(), + ) + + result = transform._communities_ids({}, _test_record()) + + assert result == { + "ids": [str(community.id)], + "default": str(community.id), + } + + def test_raise_when_cern_scientific_community_not_configured( + self, test_app, transform, community, monkeypatch + ): + """No CERN Scientific community is added when config is unset.""" + monkeypatch.setitem(test_app.config, "CDS_CERN_SCIENTIFIC_COMMUNITY_ID", None) + + with pytest.raises(MissingConfiguration): + transform._communities_ids({}, _test_record()) From 33494bf77287496ba5d43d5a8fcac79cbc2eccd1 Mon Sep 17 00:00:00 2001 From: Saksham Date: Fri, 31 Jul 2026 17:46:08 +0200 Subject: [PATCH 2/3] rdm.transform: don't auto add community if files are restricted --- .../rdm/records/transform/config.py | 2 +- .../rdm/records/transform/transform.py | 18 ++++++-- tests/cds-rdm/test_full_migration.py | 2 + tests/cds-rdm/test_scientific_community.py | 46 +++++++++++++++---- 4 files changed, 54 insertions(+), 14 deletions(-) diff --git a/cds_migrator_kit/rdm/records/transform/config.py b/cds_migrator_kit/rdm/records/transform/config.py index 87b36807..1f218c13 100644 --- a/cds_migrator_kit/rdm/records/transform/config.py +++ b/cds_migrator_kit/rdm/records/transform/config.py @@ -159,7 +159,7 @@ FILE_SUBFORMATS_TO_DROP = ["pdfa", "unstamped"] # Public research publication resource types that are auto-included in the CERN Research community. -CDS_CERN_SCIENTIFIC_RESOURCE_TYPES = { +CERN_SCIENTIFIC_RESOURCE_TYPES = { "publication-dissertation", # Already included by the migrator for thesis records. "publication-book", "publication-section", diff --git a/cds_migrator_kit/rdm/records/transform/transform.py b/cds_migrator_kit/rdm/records/transform/transform.py index 8f721735..ca2a97b1 100644 --- a/cds_migrator_kit/rdm/records/transform/transform.py +++ b/cds_migrator_kit/rdm/records/transform/transform.py @@ -44,7 +44,7 @@ VOCABULARIES_NAMES_SCHEMES, ) from cds_migrator_kit.rdm.records.transform.config import ( - CDS_CERN_SCIENTIFIC_RESOURCE_TYPES, + CERN_SCIENTIFIC_RESOURCE_TYPES, FILE_SUBFORMATS_TO_DROP, IDENTIFIERS_SCHEMES_TO_DROP, IDENTIFIERS_VALUES_TO_DROP, @@ -855,16 +855,26 @@ def __init__( self.db_state = {"affiliations": CDSMigrationAffiliationMapping} super().__init__(workers, throw) - def _should_add_scientific_community(self, record): + def _should_add_scientific_community(self, entry, record): + """ + Determine if the scientific community should be added to the record. + + The scientific community is added if the following conditions are met: + - The record is public + - The files are public + - The record has a resource type in the CERN Scientific resource types + """ if self.restricted or record.get("access") != "public": return False + if any(file.get("status") for file in entry.get("files", [])): + return False resource_type_id = ( record.get("json", {}) .get("metadata", {}) .get("resource_type", {}) .get("id") ) - return resource_type_id in CDS_CERN_SCIENTIFIC_RESOURCE_TYPES + return resource_type_id in CERN_SCIENTIFIC_RESOURCE_TYPES def _communities_ids(self, entry, record): communities = record.get("communities", []) @@ -877,7 +887,7 @@ def _communities_ids(self, entry, record): raise MissingConfiguration( "CDS_CERN_SCIENTIFIC_COMMUNITY_ID is not configured" ) - if self._should_add_scientific_community(record): + if self._should_add_scientific_community(entry, record): if scientific_community not in communities: communities.append(scientific_community) diff --git a/tests/cds-rdm/test_full_migration.py b/tests/cds-rdm/test_full_migration.py index f9bf9d9f..ec14c332 100644 --- a/tests/cds-rdm/test_full_migration.py +++ b/tests/cds-rdm/test_full_migration.py @@ -516,6 +516,8 @@ def test_full_migration_stream( if record["legacy_recid"] == "2783104": file_restricted(loaded_rec) parent_access_fields(loaded_rec) + # Skip scientific community inclusion check since it has restricted files + continue if record["legacy_recid"] == "2046076": irregular_exp_field(loaded_rec) if record["legacy_recid"] == "2041388": diff --git a/tests/cds-rdm/test_scientific_community.py b/tests/cds-rdm/test_scientific_community.py index e7b79ab4..f3f31e33 100644 --- a/tests/cds-rdm/test_scientific_community.py +++ b/tests/cds-rdm/test_scientific_community.py @@ -13,7 +13,7 @@ from cds_migrator_kit.errors import MissingConfiguration from cds_migrator_kit.rdm.records.transform.config import ( - CDS_CERN_SCIENTIFIC_RESOURCE_TYPES, + CERN_SCIENTIFIC_RESOURCE_TYPES, ) from cds_migrator_kit.rdm.records.transform.transform import CDSToRDMRecordTransform @@ -43,6 +43,12 @@ def _test_record( } +def _test_entry(files_restricted=False): + """Build a minimal raw dump entry for community tests.""" + status = "restricted" if files_restricted else "" + return {"files": [{"status": status}]} + + @pytest.fixture def transform(tmp_path, community): """Transform instance with a collection community configured.""" @@ -61,7 +67,12 @@ def test_adds_scientific_community_for_public_research_test_record( self, transform, community, cern_scientific_community ): """Public research records are included in the CERN Scientific community.""" - result = transform._communities_ids({}, _test_record()) + record = _test_record() + record["json"]["files"] = {"enabled": True} + result = transform._communities_ids( + _test_entry(), + record, + ) assert result == { "ids": [str(community.id), str(cern_scientific_community.id)], @@ -73,7 +84,7 @@ def test_keep_collection_community_as_default( ): """Collection community remains the default when CERN Scientific community is added.""" result = transform._communities_ids( - {}, + _test_entry(), _test_record(communities=["test-community"]), ) @@ -84,13 +95,13 @@ def test_keep_collection_community_as_default( str(cern_scientific_community.id), ] - @pytest.mark.parametrize("resource_type", CDS_CERN_SCIENTIFIC_RESOURCE_TYPES) + @pytest.mark.parametrize("resource_type", CERN_SCIENTIFIC_RESOURCE_TYPES) def test_research_resource_types( self, transform, cern_scientific_community, resource_type ): """All configured public research resource types trigger inclusion.""" result = transform._communities_ids( - {}, _test_record(resource_type=resource_type) + _test_entry(), _test_record(resource_type=resource_type) ) assert str(cern_scientific_community.id) in result["ids"] @@ -100,7 +111,22 @@ def test_skip_restricted_test_record( self, transform, community, cern_scientific_community ): """Restricted records are not included in the CERN Research community.""" - result = transform._communities_ids({}, _test_record(access="restricted")) + result = transform._communities_ids( + _test_entry(), _test_record(access="restricted") + ) + + assert result == { + "ids": [str(community.id)], + "default": str(community.id), + } + + def test_skip_restricted_files( + self, transform, community, cern_scientific_community + ): + """Records with restricted files are not included in the CERN Scientific community.""" + result = transform._communities_ids( + _test_entry(files_restricted=True), _test_record() + ) assert result == { "ids": [str(community.id)], @@ -111,7 +137,9 @@ def test_skip_non_research_resource_type( self, transform, community, cern_scientific_community ): """Non-research resource types are not included in the CERN Scientific community.""" - result = transform._communities_ids({}, _test_record(resource_type="other")) + result = transform._communities_ids( + _test_entry(), _test_record(resource_type="other") + ) assert result == { "ids": [str(community.id)], @@ -130,7 +158,7 @@ def test_skip_when_stream_is_restricted( migration_logger=MagicMock(), ) - result = transform._communities_ids({}, _test_record()) + result = transform._communities_ids(_test_entry(), _test_record()) assert result == { "ids": [str(community.id)], @@ -144,4 +172,4 @@ def test_raise_when_cern_scientific_community_not_configured( monkeypatch.setitem(test_app.config, "CDS_CERN_SCIENTIFIC_COMMUNITY_ID", None) with pytest.raises(MissingConfiguration): - transform._communities_ids({}, _test_record()) + transform._communities_ids(_test_entry(), _test_record()) From 5d13d980c664332a57dbfaf1edf8b714406da519 Mon Sep 17 00:00:00 2001 From: Saksham Date: Tue, 4 Aug 2026 15:55:53 +0200 Subject: [PATCH 3/3] add(config): Auto submit to cern research for conferencenote type --- cds_migrator_kit/rdm/records/transform/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cds_migrator_kit/rdm/records/transform/config.py b/cds_migrator_kit/rdm/records/transform/config.py index 1f218c13..a1229bff 100644 --- a/cds_migrator_kit/rdm/records/transform/config.py +++ b/cds_migrator_kit/rdm/records/transform/config.py @@ -165,6 +165,7 @@ "publication-section", "publication-conferencepaper", "publication-conferenceproceeding", + "publication-conferencenote", "publication-journal", "publication-article", "publication-preprint",