diff --git a/arkindex_worker/worker/metadata.py b/arkindex_worker/worker/metadata.py index f5865304883f512b8d1915f4a8b4b7d369e432cc..79b039be347829911402d0be20a72f8d322a9f9e 100644 --- a/arkindex_worker/worker/metadata.py +++ b/arkindex_worker/worker/metadata.py @@ -105,10 +105,85 @@ class MetaDataMixin(object): return metadata["id"] + def create_metadatas( + self, + element: Element, + metadatas: list, + ): + """ + Create multiple metadatas on an existing element. + This method does not support cache. + + :param element Element: The element to create multiple metadata on. + :param metadata_list List(Dict): The list of dict whose keys are the following: + - type : MetaType + - name : str + - value : Union[str, Union[int, float]] + - entity_id : Union[str, None] + """ + assert element and isinstance( + element, Element + ), "element shouldn't be null and should be of type Element" + + assert metadatas and isinstance( + metadatas, list + ), "type shouldn't be null and should be of type list of Dict" + + # Make a copy to avoid modifiying the metadata_list argument + metas = [] + for index, metadata in enumerate(metadatas): + assert isinstance( + metadata, dict + ), f"Element at index {index} in metadata_list: Should be of type dict" + + assert metadata.get("type") and isinstance( + metadata.get("type"), MetaType + ), "type shouldn't be null and should be of type MetaType" + + assert metadata.get("name") and isinstance( + metadata.get("name"), str + ), "name shouldn't be null and should be of type str" + + assert metadata.get("value") and isinstance( + metadata.get("value"), (str, float, int) + ), "value shouldn't be null and should be of type (str or float or int)" + + assert metadata.get("entity_id") is None or isinstance( + metadata.get("entity_id"), str + ), "entity_id should be None or a str" + + metas.append( + { + "type": metadata.get("type").value, + "name": metadata.get("name"), + "value": metadata.get("value"), + "entity_id": metadata.get("entity_id"), + } + ) + + if self.is_read_only: + logger.warning("Cannot create metadata as this worker is in read-only mode") + return + + created_metadatas = self.request( + "CreateMetaDataBulk", + id=element.id, + body={ + "worker_version": self.worker_version_id, + "worker_run_id": self.worker_run_id, + "metadata_list": metas, + }, + )["metadata_list"] + + for meta in created_metadatas: + self.report.add_metadata(element.id, meta["id"], meta["type"], meta["name"]) + + return created_metadatas + def list_metadata(self, element: Element): """ - List all metadata linked to an element - This method does not support cache + List all metadata linked to an element. + This method does not support cache. :param element Element: The element to list metadata on. """ diff --git a/tests/test_elements_worker/test_metadata.py b/tests/test_elements_worker/test_metadata.py index 3aa74f96d2848750f583c95f71c6a759b9df3c18..0a7d5b800ae7f962667d7541f69c03bcde2ba315 100644 --- a/tests/test_elements_worker/test_metadata.py +++ b/tests/test_elements_worker/test_metadata.py @@ -198,6 +198,215 @@ def test_create_metadata(responses, mock_elements_worker): assert metadata_id == "12345678-1234-1234-1234-123456789123" +@pytest.mark.parametrize( + "metadatas", + [ + ([{"type": MetaType.Text, "name": "fake_name", "value": "fake_value"}]), + ( + [ + { + "type": MetaType.Text, + "name": "fake_name", + "value": "fake_value", + "entity_id": "fake_entity_id", + } + ] + ), + ], +) +def test_create_metadatas(responses, mock_elements_worker, metadatas): + element = Element({"id": "12341234-1234-1234-1234-123412341234"}) + responses.add( + responses.POST, + "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/metadata/bulk/", + status=201, + json={ + "worker_version": mock_elements_worker.worker_version_id, + "worker_run_id": mock_elements_worker.worker_run_id, + "metadata_list": [ + { + "id": "fake_metadata_id", + "type": metadatas[0]["type"].value, + "name": metadatas[0]["name"], + "value": metadatas[0]["value"], + "dates": [], + "entity_id": metadatas[0].get("entity_id"), + } + ], + }, + ) + + created_metadatas = mock_elements_worker.create_metadatas(element, metadatas) + + assert len(responses.calls) == len(BASE_API_CALLS) + 1 + assert [ + (call.request.method, call.request.url) for call in responses.calls + ] == BASE_API_CALLS + [ + ( + "POST", + "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/metadata/bulk/", + ), + ] + assert json.loads(responses.calls[-1].request.body)["metadata_list"] == [ + { + "type": metadatas[0]["type"].value, + "name": metadatas[0]["name"], + "value": metadatas[0]["value"], + "entity_id": metadatas[0].get("entity_id"), + } + ] + assert created_metadatas == [ + { + "id": "fake_metadata_id", + "type": metadatas[0]["type"].value, + "name": metadatas[0]["name"], + "value": metadatas[0]["value"], + "dates": [], + "entity_id": metadatas[0].get("entity_id"), + } + ] + + +@pytest.mark.parametrize( + "wrong_element", + [ + None, + "not_element_type", + 1234, + 12.5, + ], +) +def test_create_metadatas_wrong_element(mock_elements_worker, wrong_element): + wrong_metadatas = [ + {"type": MetaType.Text, "name": "fake_name", "value": "fake_value"} + ] + with pytest.raises(AssertionError) as e: + mock_elements_worker.create_metadatas( + element=wrong_element, metadatas=wrong_metadatas + ) + assert str(e.value) == "element shouldn't be null and should be of type Element" + + +@pytest.mark.parametrize( + "wrong_type", + [ + None, + "not_metadata_type", + 1234, + 12.5, + ], +) +def test_create_metadatas_wrong_type(mock_elements_worker, wrong_type): + element = Element({"id": "12341234-1234-1234-1234-123412341234"}) + wrong_metadatas = [{"type": wrong_type, "name": "fake_name", "value": "fake_value"}] + with pytest.raises(AssertionError) as e: + mock_elements_worker.create_metadatas( + element=element, metadatas=wrong_metadatas + ) + assert str(e.value) == "type shouldn't be null and should be of type MetaType" + + +@pytest.mark.parametrize("wrong_name", [(None), (1234), (12.5), ([1, 2, 3, 4])]) +def test_create_metadatas_wrong_name(mock_elements_worker, wrong_name): + element = Element({"id": "fake_element_id"}) + wrong_metadatas = [ + {"type": MetaType.Text, "name": wrong_name, "value": "fake_value"} + ] + with pytest.raises(AssertionError) as e: + mock_elements_worker.create_metadatas( + element=element, metadatas=wrong_metadatas + ) + assert str(e.value) == "name shouldn't be null and should be of type str" + + +@pytest.mark.parametrize("wrong_value", [(None), ([1, 2, 3, 4])]) +def test_create_metadatas_wrong_value(mock_elements_worker, wrong_value): + element = Element({"id": "fake_element_id"}) + wrong_metadatas = [ + {"type": MetaType.Text, "name": "fake_name", "value": wrong_value} + ] + with pytest.raises(AssertionError) as e: + mock_elements_worker.create_metadatas( + element=element, metadatas=wrong_metadatas + ) + assert ( + str(e.value) + == "value shouldn't be null and should be of type (str or float or int)" + ) + + +@pytest.mark.parametrize( + "wrong_entity", + [ + [1, 2, 3, 4], + 1234, + 12.5, + ], +) +def test_create_metadatas_wrong_entity(mock_elements_worker, wrong_entity): + element = Element({"id": "fake_element_id"}) + wrong_metadatas = [ + { + "type": MetaType.Text, + "name": "fake_name", + "value": "fake_value", + "entity_id": wrong_entity, + } + ] + with pytest.raises(AssertionError) as e: + mock_elements_worker.create_metadatas( + element=element, metadatas=wrong_metadatas + ) + assert str(e.value) == "entity_id should be None or a str" + + +def test_create_metadatas_api_error(responses, mock_elements_worker): + element = Element({"id": "12341234-1234-1234-1234-123412341234"}) + metadatas = [ + { + "type": MetaType.Text, + "name": "fake_name", + "value": "fake_value", + "entity_id": "fake_entity_id", + } + ] + responses.add( + responses.POST, + "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/metadata/bulk/", + status=500, + ) + + with pytest.raises(ErrorResponse): + mock_elements_worker.create_metadatas(element, metadatas) + + assert len(responses.calls) == len(BASE_API_CALLS) + 5 + assert [ + (call.request.method, call.request.url) for call in responses.calls + ] == BASE_API_CALLS + [ + # We retry 5 times the API call + ( + "POST", + "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/metadata/bulk/", + ), + ( + "POST", + "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/metadata/bulk/", + ), + ( + "POST", + "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/metadata/bulk/", + ), + ( + "POST", + "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/metadata/bulk/", + ), + ( + "POST", + "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/metadata/bulk/", + ), + ] + + def test_list_metadata(fake_dummy_worker): element = Element({"id": "element_id"}) fake_dummy_worker.api_client.add_response(