From ee9bfd3858ae4292e4aa14989d9dc9f821193827 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Thu, 7 May 2026 08:43:26 -0700 Subject: [PATCH 1/2] fix bug cuasing RecordSet to be updated to v2 --- .../curator/record_based_metadata_task.py | 3 - .../test_record_based_metadata_task.py | 130 ++++++++++++++++++ 2 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 tests/integration/synapseclient/extensions/curator/test_record_based_metadata_task.py diff --git a/synapseclient/extensions/curator/record_based_metadata_task.py b/synapseclient/extensions/curator/record_based_metadata_task.py index 7698d9532..d7dc2b171 100644 --- a/synapseclient/extensions/curator/record_based_metadata_task.py +++ b/synapseclient/extensions/curator/record_based_metadata_task.py @@ -283,9 +283,6 @@ def create_record_based_metadata_task( record_set_id=record_set_id, ) curation_grid.create(synapse_client=synapse_client) - curation_grid = curation_grid.export_to_record_set( - synapse_client=synapse_client - ) synapse_client.logger.info( f"Created Grid view for RecordSet ID: {record_set_id} for curation task {curation_task_name}" ) diff --git a/tests/integration/synapseclient/extensions/curator/test_record_based_metadata_task.py b/tests/integration/synapseclient/extensions/curator/test_record_based_metadata_task.py new file mode 100644 index 000000000..b34d6db0f --- /dev/null +++ b/tests/integration/synapseclient/extensions/curator/test_record_based_metadata_task.py @@ -0,0 +1,130 @@ +"""Integration tests for create_record_based_metadata_task.""" + +import uuid + +import pytest + +from synapseclient import Synapse +from synapseclient.extensions.curator.record_based_metadata_task import ( + create_record_based_metadata_task, +) +from synapseclient.models import Folder, JSONSchema, Project, SchemaOrganization + + +def _test_name() -> str: + random_string = "".join(c for c in str(uuid.uuid4()) if c.isalpha()) + return f"SYNPY.TEST.{random_string}" + + +@pytest.fixture(scope="module") +def patient_schema_uri(syn: Synapse, request: pytest.FixtureRequest) -> str: + """ + Create a SchemaOrganization and a Patient JSON schema for the module. + Returns the schema URI. + """ + org_name = _test_name() + schema_name = "test.schematic.Patient" + + org = SchemaOrganization(name=org_name) + org.store(synapse_client=syn) + + schema = JSONSchema(name=schema_name, organization_name=org_name) + schema_body = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": f"https://example.com/schema/{org_name}-{schema_name}.json", + "title": "Patient", + "type": "object", + "properties": { + "PatientID": {"type": "string"}, + "Sex": {"type": "string", "enum": ["Male", "Female", "Other"]}, + "Age": {"type": "integer", "minimum": 0}, + }, + "required": ["PatientID"], + } + schema.store(schema_body=schema_body, synapse_client=syn) + + def cleanup(): + for js in org.get_json_schemas(synapse_client=syn): + js.delete(synapse_client=syn) + org.delete(synapse_client=syn) + + request.addfinalizer(cleanup) + + return schema.uri + + +@pytest.fixture(scope="function") +def folder(syn: Synapse, project: Project, request: pytest.FixtureRequest) -> Folder: + """Create a Folder for the test and tear it down on completion. + + The finalizer unbinds any JSON schema from the folder before deletion so + that the module-scoped schema cleanup in patient_schema_uri can succeed + (the server refuses to delete a schema that is still bound to an entity). + """ + folder = Folder(name=_test_name(), parent_id=project.id).store(synapse_client=syn) + + def cleanup(): + folder.unbind_schema(synapse_client=syn) + folder.delete(synapse_client=syn) + + request.addfinalizer(cleanup) + + return folder + + +class TestCreateRecordBasedMetadataTask: + """Integration tests for create_record_based_metadata_task.""" + + def test_creates_single_record_set_version( + self, + syn: Synapse, + project: Project, + request: pytest.FixtureRequest, + patient_schema_uri: str, + folder: Folder, + ): + """ + The Grid created during bootstrap is initialized from the RecordSet's + CSV with no edits, so exporting that Grid back to the RecordSet (the + reported bug) writes the same content as a duplicate v2. + """ + test_name = _test_name() + upsert_keys = ["PatientID"] + instructions = "Curate per the schema." + + record_set, curation_task, grid = create_record_based_metadata_task( + folder_id=folder.id, + record_set_name=test_name, + record_set_description=test_name, + curation_task_name=test_name, + upsert_keys=upsert_keys, + instructions=instructions, + schema_uri=patient_schema_uri, + synapse_client=syn, + ) + + def cleanup(): + curation_task.delete(synapse_client=syn) + grid.delete(synapse_client=syn) + record_set.unbind_schema(synapse_client=syn) + record_set.delete(synapse_client=syn) + + request.addfinalizer(cleanup) + + from synapseclient.models import RecordSet + + record_set = RecordSet(id=record_set.id).get(synapse_client=syn) + + assert grid.record_set_id == record_set.id + assert grid.grid_json_schema_id == patient_schema_uri + + assert record_set.upsert_keys == upsert_keys + assert record_set.version_number == 1 + assert record_set.parent_id == folder.id + assert record_set.name == test_name + assert record_set.description == test_name + + assert curation_task.data_type == test_name + assert curation_task.project_id == project.id + assert curation_task.instructions == instructions + assert curation_task.task_properties.record_set_id == record_set.id From 5d5619c97972b4ef3d7ad25cd5c5efe58bfd15bc Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Thu, 7 May 2026 10:10:11 -0700 Subject: [PATCH 2/2] updated unit tests --- .../unit/synapseclient/extensions/unit_test_curator.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/unit/synapseclient/extensions/unit_test_curator.py b/tests/unit/synapseclient/extensions/unit_test_curator.py index caf32adcc..11de9f79d 100644 --- a/tests/unit/synapseclient/extensions/unit_test_curator.py +++ b/tests/unit/synapseclient/extensions/unit_test_curator.py @@ -565,9 +565,7 @@ def test_create_record_based_metadata_task_success( mock_curation_task.store.return_value = mock_task mock_curation_task_cls.return_value = mock_curation_task - mock_grid = Mock() mock_grid_instance = Mock() - mock_grid_instance.export_to_record_set.return_value = mock_grid mock_grid_cls.return_value = mock_grid_instance # WHEN I create the record-based metadata task @@ -590,7 +588,7 @@ def test_create_record_based_metadata_task_success( record_set, task, grid = result assert record_set == mock_record_set assert task == mock_task - assert grid == mock_grid + assert grid == mock_grid_instance mock_extract_schema.assert_called_once_with( syn=self.mock_syn, schema_uri=self.schema_uri @@ -652,9 +650,7 @@ def test_create_record_based_metadata_task_no_schema_binding( mock_curation_task.store.return_value = mock_task mock_curation_task_cls.return_value = mock_curation_task - mock_grid = Mock() mock_grid_instance = Mock() - mock_grid_instance.export_to_record_set.return_value = mock_grid mock_grid_cls.return_value = mock_grid_instance # Call function @@ -1010,9 +1006,7 @@ def test_create_record_based_metadata_task_with_assignee( mock_curation_task.store.return_value = mock_task mock_curation_task_cls.return_value = mock_curation_task - mock_grid = Mock() mock_grid_instance = Mock() - mock_grid_instance.export_to_record_set.return_value = mock_grid mock_grid_cls.return_value = mock_grid_instance # WHEN I create the record-based metadata task with assignee_principal_id @@ -1035,7 +1029,7 @@ def test_create_record_based_metadata_task_with_assignee( record_set, task, grid = result assert record_set == mock_record_set assert task == mock_task - assert grid == mock_grid + assert grid == mock_grid_instance # AND the CurationTask should be called with assignee_principal_id as string mock_curation_task_cls.assert_called_once_with(