From 76796f1501957e58c218ba6ba2cd3bf2285903b6 Mon Sep 17 00:00:00 2001
From: Nolan <nboukachab@teklia.com>
Date: Fri, 2 Sep 2022 11:27:31 +0000
Subject: [PATCH] Helper for CreateMetaDataBulk

---
 arkindex_worker/worker/metadata.py          |  79 +++++++-
 tests/test_elements_worker/test_metadata.py | 209 ++++++++++++++++++++
 2 files changed, 286 insertions(+), 2 deletions(-)

diff --git a/arkindex_worker/worker/metadata.py b/arkindex_worker/worker/metadata.py
index f5865304..79b039be 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 3aa74f96..0a7d5b80 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(
-- 
GitLab