Skip to content
Open
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 cds_migrator_kit/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]"
18 changes: 18 additions & 0 deletions cds_migrator_kit/rdm/records/transform/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,24 @@

FILE_SUBFORMATS_TO_DROP = ["pdfa", "unstamped"]


# Public research publication resource types that are auto-included in the CERN Research community.
CERN_SCIENTIFIC_RESOURCE_TYPES = {
"publication-dissertation", # Already included by the migrator for thesis records.
"publication-book",
"publication-section",
"publication-conferencepaper",
"publication-conferenceproceeding",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to add: 'publication-conferencenote', added recently

"publication-conferencenote",
"publication-journal",
"publication-article",
"publication-preprint",
"publication-report",
"publication-technicalnote",
"publication-note",
"publication",
}

# Legacy experiment names remapped to vocabulary ids before lookup
EXPERIMENT_ALIASES = {
"t2k": "re13",
Expand Down
38 changes: 37 additions & 1 deletion cds_migrator_kit/rdm/records/transform/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,6 +32,7 @@

from cds_migrator_kit.errors import (
ManualImportRequired,
MissingConfiguration,
MissingRequiredField,
MultipleModelsMatched,
RecordFlaggedCuration,
Expand All @@ -43,6 +44,7 @@
VOCABULARIES_NAMES_SCHEMES,
)
from cds_migrator_kit.rdm.records.transform.config import (
CERN_SCIENTIFIC_RESOURCE_TYPES,
EXPERIMENT_ALIASES,
FILE_SUBFORMATS_TO_DROP,
IDENTIFIERS_SCHEMES_TO_DROP,
Expand Down Expand Up @@ -857,9 +859,42 @@ def __init__(
self.db_state = {"affiliations": CDSMigrationAffiliationMapping}
super().__init__(workers, throw)

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":
Comment thread
sakshamarora1 marked this conversation as resolved.
return False
if any(file.get("status") for file in entry.get("files", [])):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you sure that this covers everything? I would also check if the output json in access.files has it set to restricted or not

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested all the possible scenarios in the test cases. Could you also check that and let me know if something is missing?

There is no access.files populated in this flow. Do you mean here? It only sets enabled key

record_json_output = {
"files": self._files(record_dump),
"pids": self._pids(json_data),
"metadata": self._metadata(json_data, entry),
}

def _files(self, record_dump):
"""Transform the files of a record."""
record_dump.prepare_files()
files = record_dump.files
return {"enabled": bool(files)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for checking this. One more case comes to mind: we can set the whole collection to restricted, in this case we shouldn't add the scientific community either

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return False
resource_type_id = (
record.get("json", {})
.get("metadata", {})
.get("resource_type", {})
.get("id")
)
return resource_type_id in 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(
Comment thread
sakshamarora1 marked this conversation as resolved.
"CDS_CERN_SCIENTIFIC_COMMUNITY_ID"
)
if not scientific_community:
Comment thread
sakshamarora1 marked this conversation as resolved.
raise MissingConfiguration(
"CDS_CERN_SCIENTIFIC_COMMUNITY_ID is not configured"
)
if self._should_add_scientific_community(entry, record):
if scientific_community not in communities:
communities.append(scientific_community)

if communities:
return {"ids": communities, "default": self.communities_ids[0]}
return {}
Expand Down Expand Up @@ -913,6 +948,7 @@ def _transform(self, entry):
ManualImportRequired,
MissingRequiredField,
MultipleModelsMatched,
MissingConfiguration,
) as e:
migration_logger.add_log(e, record=entry)

Expand Down
14 changes: 13 additions & 1 deletion tests/cds-rdm/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,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"][
Expand Down Expand Up @@ -1703,6 +1703,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."""
Expand Down
5 changes: 0 additions & 5 deletions tests/cds-rdm/test_bulletin_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,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
8 changes: 4 additions & 4 deletions tests/cds-rdm/test_ep_approval_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -526,22 +526,22 @@ 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"]
)

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:
Expand Down
11 changes: 11 additions & 0 deletions tests/cds-rdm/test_full_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
):
Expand Down Expand Up @@ -515,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":
Expand All @@ -526,6 +529,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()
Expand Down Expand Up @@ -595,3 +600,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
175 changes: 175 additions & 0 deletions tests/cds-rdm/test_scientific_community.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# -*- 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 (
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,
},
}


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."""
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."""
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)],
"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_entry(),
_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", 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_entry(), _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_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)],
"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_entry(), _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_entry(), _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_entry(), _test_record())
Loading