From 96bb0a2b83b35bb17906ed4f4cb7756fa332872e Mon Sep 17 00:00:00 2001 From: SystemsPurge Date: Fri, 14 Feb 2025 15:46:51 +0100 Subject: [PATCH 1/7] Added ContextEntityLDKeyValues as possible parameter for entity creation and update Signed-off-by: SystemsPurge --- filip/clients/ngsi_ld/cb.py | 13 +- tests/clients/test_ngsi_ld_cb_kv.py | 792 ++++++++++++++++++++++++++++ 2 files changed, 800 insertions(+), 5 deletions(-) create mode 100644 tests/clients/test_ngsi_ld_cb_kv.py diff --git a/filip/clients/ngsi_ld/cb.py b/filip/clients/ngsi_ld/cb.py index 8959e388..a84a6489 100644 --- a/filip/clients/ngsi_ld/cb.py +++ b/filip/clients/ngsi_ld/cb.py @@ -196,7 +196,10 @@ def get_statistics(self) -> Dict: raise def post_entity( - self, entity: ContextLDEntity, append: bool = False, update: bool = False + self, + entity: Union[ContextLDEntity,ContextLDEntityKeyValues], + append: bool = False, + update: bool = False ): """ Function registers an Object with the NGSI-LD Context Broker, @@ -236,7 +239,7 @@ def post_entity( self.log_error(err=err, msg=msg) raise - def override_entities(self, entities: List[ContextLDEntity]): + def override_entities(self, entities: List[Union[ContextLDEntity,ContextLDEntityKeyValues]]): """ Function to create or override existing entites with the NGSI-LD Context Broker. The batch operation with Upsert will be used. @@ -423,7 +426,7 @@ def get_entity_list( raise def replace_existing_attributes_of_entity( - self, entity: ContextLDEntity, append: bool = False + self, entity: Union[ContextLDEntity,ContextLDEntityKeyValues], append: bool = False ): """ The attributes previously existing in the entity are removed and @@ -524,7 +527,7 @@ def update_entity_attribute( raise def append_entity_attributes( - self, entity: ContextLDEntity, options: Optional[str] = None + self, entity: Union[ContextLDEntity,ContextLDEntityKeyValues], options: Optional[str] = None ): """ Append new Entity attributes to an existing Entity within an NGSI-LD system @@ -851,7 +854,7 @@ def handle_multi_status_response(self, res: requests.Response): def entity_batch_operation( self, *, - entities: List[ContextLDEntity], + entities: List[Union[ContextLDEntity,ContextLDEntityKeyValues]], action_type: Union[ActionTypeLD, str], options: Literal["noOverwrite", "replace", "update"] = None, ) -> None: diff --git a/tests/clients/test_ngsi_ld_cb_kv.py b/tests/clients/test_ngsi_ld_cb_kv.py new file mode 100644 index 00000000..54f8d532 --- /dev/null +++ b/tests/clients/test_ngsi_ld_cb_kv.py @@ -0,0 +1,792 @@ +""" +Tests for filip.cb.client +""" + +import unittest +import logging +from urllib.parse import urljoin + + +from pydantic import AnyHttpUrl,BaseModel,fields +from requests import RequestException, Session +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry + +from filip.clients.base_http_client import NgsiURLVersion, BaseHttpClient +from filip.clients.ngsi_ld.cb import ContextBrokerLDClient +from filip.models.base import FiwareLDHeader, core_context +from filip.models.ngsi_ld.context import ( + ActionTypeLD, + ContextLDEntityKeyValues, + ContextProperty, + NamedContextProperty, +) +from tests.config import settings +import requests +from filip.utils.cleanup import clear_context_broker_ld + + +# Setting up logging +logging.basicConfig( + level="ERROR", format="%(asctime)s %(name)s %(levelname)s: %(message)s" +) + + +class TestContextBroker(unittest.TestCase): + """ + Test class for ContextBrokerClient + """ + + def setUp(self) -> None: + """ + Setup test data + Returns: + None + """ + self.resources = { + "entities_url": "/ngsi-ld/v1/entities", + "types_url": "/ngsi-ld/v1/types", + } + self.attr = {"testtemperature": {"type": "Property", "value": 20.0}} + self.entity = ContextLDEntityKeyValues( + id="urn:ngsi-ld:my:id4", type="MyType", **self.attr + ) + self.entity_2 = ContextLDEntityKeyValues(id="urn:ngsi-ld:room2", type="room") + self.fiware_header = FiwareLDHeader(ngsild_tenant=settings.FIWARE_SERVICE) + # Set up retry strategy + session = Session() + retry_strategy = Retry( + total=5, # Maximum number of retries + backoff_factor=1, # Exponential backoff (1, 2, 4, 8, etc.) + status_forcelist=[ + 429, + 500, + 502, + 503, + 504, + ], # Retry on these HTTP status codes + ) + # Set the HTTP adapter with retry strategy + adapter = HTTPAdapter(max_retries=retry_strategy) + session.mount("https://", adapter) + session.mount("http://", adapter) + self.client = ContextBrokerLDClient( + fiware_header=self.fiware_header, session=session, url=settings.LD_CB_URL + ) + clear_context_broker_ld(cb_ld_client=self.client) + + def tearDown(self) -> None: + """ + Cleanup test server + """ + clear_context_broker_ld(cb_ld_client=self.client) + self.client.close() + + @unittest.skip("Only for local testing environment") + def test_not_existing_tenant(self): + """ + Test the expected behavior of the client when the tenant does not exist + This test will not be included in the CI/CD pipeline. For local testing please + comment out the decorator. + """ + # create uuid for the tenant + import uuid + + tenant = str(uuid.uuid4()).split("-")[0] + fiware_header = FiwareLDHeader(ngsild_tenant=tenant) + client = ContextBrokerLDClient( + fiware_header=fiware_header, url=settings.LD_CB_URL + ) + entities = client.get_entity_list() + self.assertEqual(len(entities), 0) + + def test_url_composition_ld(self): + """ + Test URL composition for ngsi-ld context broker client + """ + user_input_urls = { + "http://example.org/orion/": "http://example.org/orion/ngsi-ld/v1/entities", + "http://example.org/orion": "http://example.org/orion/ngsi-ld/v1/entities", + "http://123.0.0.0:1026": "http://123.0.0.0:1026/ngsi-ld/v1/entities", + "http://123.0.0.0:1026/": "http://123.0.0.0:1026/ngsi-ld/v1/entities", + "http://123.0.0.0/orion": "http://123.0.0.0/orion/ngsi-ld/v1/entities", + "http://123.0.0.0/orion/": "http://123.0.0.0/orion/ngsi-ld/v1/entities", + } + for url in user_input_urls: + url_correct = AnyHttpUrl(user_input_urls[url]) + bhc = BaseHttpClient(url=url) + url_filip = AnyHttpUrl( + urljoin(bhc.base_url, f"{NgsiURLVersion.ld_url.value}/entities") + ) + self.assertEqual(url_correct, url_filip) + + def test_get_entities_pagination(self): + """ + Test pagination of get entities + """ + init_numb = 2000 + entities_a = [ + ContextLDEntityKeyValues(id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:TypeA") + for i in range(0, init_numb) + ] + + self.client.entity_batch_operation( + action_type=ActionTypeLD.CREATE, entities=entities_a + ) + + entity_list = self.client.get_entity_list(limit=1) + self.assertEqual(len(entity_list), 1) + + entity_list = self.client.get_entity_list(limit=400) + self.assertEqual(len(entity_list), 400) + + entity_list = self.client.get_entity_list(limit=800) + self.assertEqual(len(entity_list), 800) + + entity_list = self.client.get_entity_list(limit=1000) + self.assertEqual(len(entity_list), 1000) + + # currently, there is a limit of 1000 entities per delete request + self.client.entity_batch_operation( + action_type=ActionTypeLD.DELETE, entities=entities_a[0:800] + ) + self.client.entity_batch_operation( + action_type=ActionTypeLD.DELETE, entities=entities_a[800:1600] + ) + entity_list = self.client.get_entity_list(limit=1000) + self.assertEqual(len(entity_list), init_numb - 1600) + + def test_get_entites(self): + """ + Retrieve a set of entities which matches a specific query from an NGSI-LD system + Args: + - id(string): Comma separated list of URIs to be retrieved + - idPattern(string): Regular expression that must be matched by Entity ids + - type(string): Comma separated list of Entity type names to be retrieved + - attrs(string): Comma separated list of attribute names (properties or relationships) to be retrieved + - q(string): Query + - georel: Geo-relationship + - geometry(string): Geometry; Available values : Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon + - coordinates: Coordinates serialized as a string + - geoproperty(string): The name of the property that contains the geo-spatial data that will be used to resolve the geoquery + - csf(string): Context Source Filter + - limit(integer): Pagination limit + - options(string): Options dictionary; Available values : keyValues, sysAttrs + """ + entity_list = self.client.get_entity_list() + self.assertEqual(len(entity_list), 0) + + self.client.post_entity(entity=self.entity) + entity_list_idpattern = self.client.get_entity_list( + id_pattern="urn:ngsi-ld:my*" + ) + self.assertEqual(len(entity_list_idpattern), 1) + self.assertEqual(entity_list_idpattern[0].id, self.entity.id) + + entity_list_attrs = self.client.get_entity_list(attrs=["testtemperature"]) + self.assertEqual(len(entity_list_attrs), 1) + self.assertEqual(entity_list_attrs[0].id, self.entity.id) + + def test_post_entity(self): + """ + Post an entity. + Args: + - Entity{ + @context: LdContext{} + location: GeoProperty{} + observationSpace: GeoProperty{} + operationSpace: GeoProperty{} + id: string($uri) required + type: Name(string) required + (NGSI-LD Name) + createdAt: string($date-time) + modifiedAt: string($date_time) + <*>: Property{} + Relationship{} + GeoProperty{} + } + Returns: + - (201) Created. Contains the resource URI of the created Entity + - (400) Bad request. + - (409) Already exists. + - (422) Unprocessable Entity. + Tests: + - Post an entity -> Does it return 201? + - Post an entity again -> Does it return 409? + - Post an entity without requires args -> Does it return 422? + - Post entity from class with default value + """ + # create entity + self.client.post_entity(entity=self.entity) + entity_list = self.client.get_entity_list(entity_type=self.entity.type) + self.assertEqual(len(entity_list), 1) + self.assertEqual(entity_list[0].id, self.entity.id) + self.assertEqual(entity_list[0].type, self.entity.type) + self.assertEqual( + entity_list[0].testtemperature.value, self.entity.testtemperature['value'] + ) + + # existed entity + self.entity_identical = self.entity.model_copy() + with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: + self.client.post_entity(entity=self.entity_identical) + response = contextmanager.exception.response + self.assertEqual(response.status_code, 409) + + entity_list = self.client.get_entity_list( + entity_type=self.entity_identical.type + ) + self.assertEqual(len(entity_list), 1) + + # append new attribute to existed entity + self.entity_append = self.entity.model_copy() + del self.entity_append.testtemperature + self.entity_append.humidity = ContextProperty(**{"type": "Property", "value": 50}) + self.client.post_entity(entity=self.entity_append, append=True) + entity_append_res = self.client.get_entity(entity_id=self.entity_append.id) + self.assertEqual( + entity_append_res.humidity.value, self.entity_append.humidity.value + ) + self.assertEqual( + entity_append_res.testtemperature.value, self.entity.testtemperature['value'] + ) + + # override existed entity + new_attr = {"newattr": {"type": "Property", "value": 999}} + self.entity_override = ContextLDEntityKeyValues( + id=self.entity.id, type=self.entity.type, **new_attr + ) + self.client.post_entity(entity=self.entity_override, update=True) + entity_override_res = self.client.get_entity(entity_id=self.entity.id) + self.assertEqual( + entity_override_res.newattr.value, self.entity_override.newattr['value'] + ) + self.assertNotIn("testtemperature", entity_override_res.model_dump()) + + # post without entity type is not allowed + with self.assertRaises(Exception): + self.client.post_entity(ContextLDEntityKeyValues(id="room2")) + entity_list = self.client.get_entity_list() + self.assertNotIn("room2", entity_list) + + """delete""" + self.client.entity_batch_operation( + entities=entity_list, action_type=ActionTypeLD.DELETE + ) + + def test_get_entity(self): + """ + Get an entity with an specific ID. + Args: + - entityID(string): Entity ID, required + - attrs(string): Comma separated list of attribute names (properties or relationships) to be retrieved + - type(string): Entity Type + - options(string): Options dictionary; Available values : keyValues, sysAttrs + Returns: + - (200) Entity + - (400) Bad request + - (404) Not found + Tests for get entity: + - Post entity and see if get entity with the same ID returns the entity + with the correct values + - Get entity with an ID that does not exit. See if Not found error is + raised + """ + + """ + Test 1: + post entity_1 with entity_1_ID + get enntity_1 with enity_1_ID + compare if the posted entity_1 is the same as the get_enity_1 + If attributes posted entity.id != ID get entity: + Raise Error + If type posted entity != type get entity: + Raise Error + Test 2: + get enitity with enitity_ID that does not exit + If return != 404: + Raise Error + """ + """Test1""" + self.client.post_entity(entity=self.entity) + ret_entity = self.client.get_entity(entity_id=self.entity.id) + ret_entity_with_type = self.client.get_entity( + entity_id=self.entity.id, entity_type=self.entity.type + ) + ret_entity_keyValues = self.client.get_entity( + entity_id=self.entity.id, options="keyValues" + ) + ret_entity_sysAttrs = self.client.get_entity( + entity_id=self.entity.id, options="sysAttrs" + ) + + self.assertEqual(ret_entity.id, self.entity.id) + self.assertEqual(ret_entity.type, self.entity.type) + self.assertEqual(ret_entity_with_type.id, self.entity.id) + self.assertEqual(ret_entity_with_type.type, self.entity.type) + self.assertEqual(ret_entity_keyValues.id, self.entity.id) + self.assertEqual(ret_entity_keyValues.type, self.entity.type) + self.assertEqual(ret_entity_sysAttrs.id, self.entity.id) + self.assertEqual(ret_entity_sysAttrs.type, self.entity.type) + self.assertNotEqual(ret_entity_sysAttrs.createdAt, None) + + """Test2""" + with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: + self.client.get_entity("urn:roomDoesnotExist") + response = contextmanager.exception.response + self.assertEqual(response.status_code, 404) + + with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: + self.client.get_entity("roomDoesnotExist") + response = contextmanager.exception.response + self.assertEqual(response.status_code, 400) + + def test_different_context(self): + """ + Get entities with different contexts. + Returns: + """ + temperature_sensor_dict = { + "id": "urn:ngsi-ld:temperatureSensor", + "type": "TemperatureSensor", + "temperature": {"type": "Property", "value": 23, "unitCode": "CEL"}, + } + + # client with custom context + custom_context = ( + "https://n5geh.github.io/n5geh.test-context.io/context_saref.jsonld" + ) + custom_header = FiwareLDHeader( + ngsild_tenant=settings.FIWARE_SERVICE, + ) + custom_header.set_context(custom_context) + client_custom_context = ContextBrokerLDClient( + fiware_header=custom_header, url=settings.LD_CB_URL + ) + + # default context + temperature_sensor = ContextLDEntityKeyValues(**temperature_sensor_dict) + self.client.post_entity(entity=temperature_sensor) + entity_default = self.client.get_entity(entity_id=temperature_sensor.id) + self.assertEqual(entity_default.context, core_context) + self.assertEqual( + entity_default.model_dump(exclude_unset=True, exclude={"context"}), + temperature_sensor_dict, + ) + entity_custom_context = client_custom_context.get_entity( + entity_id=temperature_sensor.id + ) + self.assertEqual(entity_custom_context.context, custom_context) + self.assertEqual( + entity_custom_context.model_dump(exclude_unset=True, exclude={"context"}), + temperature_sensor_dict, + ) + self.client.delete_entity_by_id(entity_id=temperature_sensor.id) + + # custom context in client + temperature_sensor = ContextLDEntityKeyValues(**temperature_sensor_dict) + client_custom_context.post_entity(entity=temperature_sensor) + entity_custom = client_custom_context.get_entity( + entity_id=temperature_sensor.id + ) + self.assertEqual(entity_custom.context, custom_context) + self.assertEqual( + entity_custom.model_dump(exclude_unset=True, exclude={"context"}), + temperature_sensor_dict, + ) + entity_default_context = self.client.get_entity(entity_id=temperature_sensor.id) + self.assertEqual(entity_default_context.context, core_context) + self.assertNotEqual( + entity_default_context.model_dump(exclude_unset=True, exclude={"context"}), + temperature_sensor_dict, + ) + client_custom_context.delete_entity_by_id(entity_id=temperature_sensor.id) + + # custom context in entity + temperature_sensor = ContextLDEntityKeyValues( + context=[ + "https://n5geh.github.io/n5geh.test-context.io/context_saref.jsonld" + ], + **temperature_sensor_dict + ) + + self.client.post_entity(entity=temperature_sensor) + entity_custom = client_custom_context.get_entity( + entity_id=temperature_sensor.id + ) + self.assertEqual(entity_custom.context, custom_context) + self.assertEqual( + entity_custom.model_dump(exclude_unset=True, exclude={"context"}), + temperature_sensor_dict, + ) + entity_default_context = self.client.get_entity(entity_id=temperature_sensor.id) + self.assertEqual(entity_default_context.context, core_context) + self.assertNotEqual( + entity_default_context.model_dump(exclude_unset=True, exclude={"context"}), + temperature_sensor_dict, + ) + self.client.delete_entity_by_id(entity_id=temperature_sensor.id) + + def test_delete_entity(self): + """ + Removes an specific Entity from an NGSI-LD system. + Args: + - entityID(string): Entity ID; required + - type(string): Entity Type + Returns: + - (204) No Content. The entity was removed successfully. + - (400) Bad request. + - (404) Not found. + Tests: + - Try to delete an non existent entity -> Does it return a Not found? + - Post an entity and try to delete the entity -> Does it return 204? + - Try to get to delete an deleted entity -> Does it return 404? + """ + + """ + Test 1: + delete entity with non existent entity_ID + If return != 404: + Raise Error + + Test 2: + post an entity with entity_ID and entity_type + delete entity with entity_ID + get entity list + If entity with entity_ID in entity list: + Raise Error + + Test 3: + delete entity with entity_ID + return != 404 ? + yes: + Raise Error + """ + + """Test1""" + # try to delete nonexistent entity + with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: + self.client.get_entity(entity_id=self.entity.id) + response = contextmanager.exception.response + self.assertEqual(response.status_code, 404) + self.assertEqual(response.json()["title"], "Entity Not Found") + + """Test2""" + self.client.post_entity(entity=self.entity) + self.client.post_entity(entity=self.entity_2) + entity_list = self.client.get_entity_list() + entity_ids = [entity.id for entity in entity_list] + self.assertIn(self.entity.id, entity_ids) + + self.client.delete_entity_by_id(entity_id=self.entity.id) + entity_list = self.client.get_entity_list() + entity_ids = [entity.id for entity in entity_list] + self.assertNotIn(self.entity.id, entity_ids) + self.assertIn(self.entity_2.id, entity_ids) + + """Test3""" + # entity was already deleted + with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: + self.client.get_entity(entity_id=self.entity.id) + response = contextmanager.exception.response + self.assertEqual(response.status_code, 404) + self.assertEqual(response.json()["title"], "Entity Not Found") + + def test_add_attributes_entity(self): + """ + Append new Entity attributes to an existing Entity within an NGSI-LD system. + Args: + - entityID(string): Entity ID; required + - options(string): Indicates that no attribute overwrite shall be performed. + Available values: noOverwrite + Returns: + - (204) No Content + - (207) Partial Success. Only the attributes included in the response payload were successfully appended. + - (400) Bad Request + - (404) Not Found + Tests: + - Post an entity and add an attribute. Test if the attribute is added when Get is done. + - Try to add an attribute to an non existent entity -> Return 404 + - Try to overwrite an attribute even though noOverwrite option is used + """ + """ + Test 1: + post an entity with entity_ID and entity_type + add attribute to the entity with entity_ID + get entity with entity_ID and new attribute + Is new attribute not added to enitity ? + yes: + Raise Error + Test 2: + add attribute to an non existent entity + Raise Error + Test 3: + post an entity with entity_ID, entity_type, entity_attribute + add attribute that already exists with noOverwrite + Raise Error + get entity and compare previous with entity attributes + If attributes are different? + Raise Error + """ + """Test 1""" + self.client.post_entity(self.entity) + attr = ContextProperty(**{"value": 20, "unitCode": "Number"}) + + self.entity.test_value = attr + self.client.append_entity_attributes(self.entity) + + entity = self.client.get_entity(entity_id=self.entity.id) + self.assertEqual(first=entity.test_value.value, second=attr.value) + self.client.delete_entity_by_id(entity_id=entity.id) + + """Test 2""" + attr = ContextProperty(**{"value": 20, "type": "Property"}) + with self.assertRaises(Exception): + self.entity.test_value = attr + self.client.append_entity_attributes(self.entity) + + """Test 3""" + self.client.post_entity(self.entity) + # What makes an property/ attribute unique ??? + attr = ContextProperty(**{"value": 20, "type": "Property"}) + attr_same = ContextProperty(**{"value": 40, "type": "Property"}) + + self.entity.test_value = attr + self.client.append_entity_attributes(self.entity) + self.entity.test_value = attr_same + + self.client.append_entity_attributes(self.entity, options="noOverwrite") + entity = self.client.get_entity(entity_id=self.entity.id) + self.assertEqual(first=entity.test_value.value, second=attr.value) + self.assertNotEqual(first=entity.test_value.value, second=attr_same.value) + + def test_patch_entity_attrs(self): + """ + Update existing Entity attributes within an NGSI-LD system + Args: + - entityId(string): Entity ID; required + - Request body; required + Returns: + - (201) Created. Contains the resource URI of the created Entity + - (400) Bad request + - (409) Already exists + - (422) Unprocessable Entity + Tests: + - Post an enitity with specific attributes. Change the attributes with patch. + """ + """ + Test 1: + post an enitity with entity_ID and entity_type and attributes + patch one of the attributes with entity_id by sending request body + get entity list + If new attribute is not added to the entity? + Raise Error + """ + """Test1""" + newer_prop = NamedContextProperty(value=40, name="new_prop") + + self.entity.new_prop = ContextProperty(value=25) + self.client.post_entity(entity=self.entity) + self.client.update_entity_attribute( + entity_id=self.entity.id, attr=newer_prop, attr_name="new_prop" + ) + entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") + prop_dict = entity.model_dump() + self.assertIn("new_prop", prop_dict) + self.assertEqual(prop_dict["new_prop"], 40) + + def test_patch_entity_attrs_contextprop(self): + """ + Update existing Entity attributes within an NGSI-LD system + Args: + - entityId(string): Entity ID; required + - Request body; required + Returns: + - (201) Created. Contains the resource URI of the created Entity + - (400) Bad request + - (409) Already exists + - (422) Unprocessable Entity + Tests: + - Post an enitity with specific attributes. Change the attributes with patch. + """ + """ + Test 1: + post an enitity with entity_ID and entity_type and attributes + patch one of the attributes with entity_id by sending request body + get entity list + If new attribute is not added to the entity? + Raise Error + """ + """Test1""" + newer_prop = ContextProperty(value=55) + + self.entity.new_prop = ContextProperty(value=25) + self.client.post_entity(entity=self.entity) + self.client.update_entity_attribute( + entity_id=self.entity.id, attr=newer_prop, attr_name="new_prop" + ) + entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") + prop_dict = entity.model_dump() + self.assertIn("new_prop", prop_dict) + self.assertEqual(prop_dict["new_prop"], 55) + + def test_patch_entity_attrs_attrId(self): + """ + Update existing Entity attribute ID within an NGSI-LD system + Args: + - entityId(string): Entity Id; required + - attrId(string): Attribute Id; required + Returns: + - (204) No Content + - (400) Bad Request + - (404) Not Found + Tests: + - Post an enitity with specific attributes. Change the attributes with patch. + """ + """ + Test 1: + post an entity with entity_ID, entity_type and attributes + patch with entity_ID and attribute_ID + return != 204: + yes: + Raise Error + """ + """Test 1""" + attr = NamedContextProperty(name="test_value", value=20) + self.entity.test_value = attr + self.client.post_entity(entity=self.entity) + + attr.value = 40 + self.client.update_entity_attribute( + entity_id=self.entity.id, attr=attr, attr_name="test_value" + ) + entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") + prop_dict = entity.model_dump() + self.assertIn("test_value", prop_dict) + self.assertEqual(prop_dict["test_value"], 40) + + def test_delete_entity_attribute(self): + """ + Delete existing Entity atrribute within an NGSI-LD system. + Args: + - entityId: Entity Id; required + - attrId: Attribute Id; required + Returns: + - (204) No Content + - (400) Bad Request + - (404) Not Found + Tests: + - Post an entity with attributes. Try to delete non existent attribute with non existent attribute + id. Then check response code. + - Post an entity with attributes. Try to delete one the attributes. Test if the attribute is really + removed by either posting the entity or by trying to delete it again. + """ + """ + Test 1: + post an enitity with entity_ID, entity_type and attribute with attribute_ID + delete an attribute with an non existent attribute_ID of the entity with the entity_ID + Raise Error + Test 2: + post an entity with entity_ID, entitiy_name and attribute with attribute_ID + delete the attribute with the attribute_ID of the entity with the entity_ID + get entity with entity_ID + If attribute with attribute_ID is still there? + Raise Error + delete the attribute with the attribute_ID of the entity with the entity_ID + Raise Error + """ + """Test 1""" + + attr = NamedContextProperty(name="test_value", value=20) + self.entity.test_value = attr + self.client.post_entity(entity=self.entity) + with self.assertRaises(Exception): + self.client.delete_attribute( + entity_id=self.entity.id, attribute_id="does_not_exist" + ) + + entity_list = self.client.get_entity_list() + + for entity in entity_list: + self.client.delete_entity_by_id(entity_id=entity.id) + + """Test 2""" + attr = NamedContextProperty(name="test_value", value=20) + self.entity.test_value = attr + self.client.post_entity(entity=self.entity) + self.client.delete_attribute( + entity_id=self.entity.id, attribute_id="test_value" + ) + + with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: + self.client.delete_attribute( + entity_id=self.entity.id, attribute_id="test_value" + ) + response = contextmanager.exception.response + self.assertEqual(response.status_code, 404) + + def test_replacing_attributes(self): + """ + Patch existing Entity attributes within an NGSI-LD system. + Args: + - entityId: Entity Id; required + Returns: + - (204) No Content + - (400) Bad Request + - (404) Not Found + Tests: + - Post an entity with attribute. Change the attributes with patch. + """ + """ + Test 1: + replace attribute with same name and different value + Test 2: + replace two attributes + """ + + """Test 1""" + attr1 = NamedContextProperty(name="test_value", value=20) + self.entity.test_value = attr1 + self.client.post_entity(entity=self.entity) + entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") + prop_dict = entity.model_dump() + self.assertIn("test_value", prop_dict) + self.assertEqual(prop_dict["test_value"], 20) + + attr2 = NamedContextProperty(name="test_value", value=44) + del self.entity.test_value + self.entity.test_value = attr2 + self.client.replace_existing_attributes_of_entity(entity=self.entity) + entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") + prop_dict = entity.model_dump() + self.assertIn("test_value", prop_dict) + self.assertEqual(prop_dict["test_value"], 44) + + self.client.delete_entity_by_id(entity_id=self.entity.id) + + """Test 2""" + attr1 = NamedContextProperty(name="test_value", value=20) + attr2 = NamedContextProperty(name="my_value", value=44) + self.entity.test_value = attr1 + self.entity.my_value = attr2 + self.client.post_entity(entity=self.entity) + entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") + prop_dict = entity.model_dump() + self.assertIn("test_value", prop_dict) + self.assertEqual(prop_dict["test_value"], 20) + self.assertIn("my_value", prop_dict) + self.assertEqual(prop_dict["my_value"], 44) + + del self.entity.test_value + del self.entity.my_value + attr3 = NamedContextProperty(name="test_value", value=25) + attr4 = NamedContextProperty(name="my_value", value=45) + self.entity.test_value = attr3 + self.entity.my_value = attr4 + self.client.replace_existing_attributes_of_entity(entity=self.entity) + entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") + prop_dict = entity.model_dump() + self.assertIn("test_value", prop_dict) + self.assertEqual(prop_dict["test_value"], 25) + self.assertIn("my_value", prop_dict) + self.assertEqual(prop_dict["my_value"], 45) From 11368250662e6f81b16e23ef32b7d4e87b04ba54 Mon Sep 17 00:00:00 2001 From: SystemsPurge Date: Mon, 17 Feb 2025 14:06:53 +0100 Subject: [PATCH 2/7] Bug correction in different context test Signed-off-by: SystemsPurge --- tests/clients/test_ngsi_ld_cb_kv.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/clients/test_ngsi_ld_cb_kv.py b/tests/clients/test_ngsi_ld_cb_kv.py index 54f8d532..c3ec0e6e 100644 --- a/tests/clients/test_ngsi_ld_cb_kv.py +++ b/tests/clients/test_ngsi_ld_cb_kv.py @@ -404,9 +404,9 @@ def test_different_context(self): # custom context in entity temperature_sensor = ContextLDEntityKeyValues( - context=[ + **dict({"@context":[ "https://n5geh.github.io/n5geh.test-context.io/context_saref.jsonld" - ], + ]}), **temperature_sensor_dict ) @@ -555,7 +555,8 @@ def test_add_attributes_entity(self): self.client.append_entity_attributes(self.entity) self.entity.test_value = attr_same - self.client.append_entity_attributes(self.entity, options="noOverwrite") + with self.assertRaises(RequestException): + self.client.append_entity_attributes(self.entity, options="noOverwrite") entity = self.client.get_entity(entity_id=self.entity.id) self.assertEqual(first=entity.test_value.value, second=attr.value) self.assertNotEqual(first=entity.test_value.value, second=attr_same.value) From 095f0da6840f740db159a1717ae3aebc3c176779 Mon Sep 17 00:00:00 2001 From: SystemsPurge Date: Mon, 17 Feb 2025 15:35:49 +0100 Subject: [PATCH 3/7] Removed redundant tests, duplicated batch operation tests Signed-off-by: SystemsPurge --- tests/clients/test_ngsi_ld_cb_kv.py | 694 ++++++++++++++++++---------- 1 file changed, 457 insertions(+), 237 deletions(-) diff --git a/tests/clients/test_ngsi_ld_cb_kv.py b/tests/clients/test_ngsi_ld_cb_kv.py index c3ec0e6e..682f25ee 100644 --- a/tests/clients/test_ngsi_ld_cb_kv.py +++ b/tests/clients/test_ngsi_ld_cb_kv.py @@ -82,111 +82,6 @@ def tearDown(self) -> None: clear_context_broker_ld(cb_ld_client=self.client) self.client.close() - @unittest.skip("Only for local testing environment") - def test_not_existing_tenant(self): - """ - Test the expected behavior of the client when the tenant does not exist - This test will not be included in the CI/CD pipeline. For local testing please - comment out the decorator. - """ - # create uuid for the tenant - import uuid - - tenant = str(uuid.uuid4()).split("-")[0] - fiware_header = FiwareLDHeader(ngsild_tenant=tenant) - client = ContextBrokerLDClient( - fiware_header=fiware_header, url=settings.LD_CB_URL - ) - entities = client.get_entity_list() - self.assertEqual(len(entities), 0) - - def test_url_composition_ld(self): - """ - Test URL composition for ngsi-ld context broker client - """ - user_input_urls = { - "http://example.org/orion/": "http://example.org/orion/ngsi-ld/v1/entities", - "http://example.org/orion": "http://example.org/orion/ngsi-ld/v1/entities", - "http://123.0.0.0:1026": "http://123.0.0.0:1026/ngsi-ld/v1/entities", - "http://123.0.0.0:1026/": "http://123.0.0.0:1026/ngsi-ld/v1/entities", - "http://123.0.0.0/orion": "http://123.0.0.0/orion/ngsi-ld/v1/entities", - "http://123.0.0.0/orion/": "http://123.0.0.0/orion/ngsi-ld/v1/entities", - } - for url in user_input_urls: - url_correct = AnyHttpUrl(user_input_urls[url]) - bhc = BaseHttpClient(url=url) - url_filip = AnyHttpUrl( - urljoin(bhc.base_url, f"{NgsiURLVersion.ld_url.value}/entities") - ) - self.assertEqual(url_correct, url_filip) - - def test_get_entities_pagination(self): - """ - Test pagination of get entities - """ - init_numb = 2000 - entities_a = [ - ContextLDEntityKeyValues(id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:TypeA") - for i in range(0, init_numb) - ] - - self.client.entity_batch_operation( - action_type=ActionTypeLD.CREATE, entities=entities_a - ) - - entity_list = self.client.get_entity_list(limit=1) - self.assertEqual(len(entity_list), 1) - - entity_list = self.client.get_entity_list(limit=400) - self.assertEqual(len(entity_list), 400) - - entity_list = self.client.get_entity_list(limit=800) - self.assertEqual(len(entity_list), 800) - - entity_list = self.client.get_entity_list(limit=1000) - self.assertEqual(len(entity_list), 1000) - - # currently, there is a limit of 1000 entities per delete request - self.client.entity_batch_operation( - action_type=ActionTypeLD.DELETE, entities=entities_a[0:800] - ) - self.client.entity_batch_operation( - action_type=ActionTypeLD.DELETE, entities=entities_a[800:1600] - ) - entity_list = self.client.get_entity_list(limit=1000) - self.assertEqual(len(entity_list), init_numb - 1600) - - def test_get_entites(self): - """ - Retrieve a set of entities which matches a specific query from an NGSI-LD system - Args: - - id(string): Comma separated list of URIs to be retrieved - - idPattern(string): Regular expression that must be matched by Entity ids - - type(string): Comma separated list of Entity type names to be retrieved - - attrs(string): Comma separated list of attribute names (properties or relationships) to be retrieved - - q(string): Query - - georel: Geo-relationship - - geometry(string): Geometry; Available values : Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon - - coordinates: Coordinates serialized as a string - - geoproperty(string): The name of the property that contains the geo-spatial data that will be used to resolve the geoquery - - csf(string): Context Source Filter - - limit(integer): Pagination limit - - options(string): Options dictionary; Available values : keyValues, sysAttrs - """ - entity_list = self.client.get_entity_list() - self.assertEqual(len(entity_list), 0) - - self.client.post_entity(entity=self.entity) - entity_list_idpattern = self.client.get_entity_list( - id_pattern="urn:ngsi-ld:my*" - ) - self.assertEqual(len(entity_list_idpattern), 1) - self.assertEqual(entity_list_idpattern[0].id, self.entity.id) - - entity_list_attrs = self.client.get_entity_list(attrs=["testtemperature"]) - self.assertEqual(len(entity_list_attrs), 1) - self.assertEqual(entity_list_attrs[0].id, self.entity.id) - def test_post_entity(self): """ Post an entity. @@ -274,73 +169,6 @@ def test_post_entity(self): entities=entity_list, action_type=ActionTypeLD.DELETE ) - def test_get_entity(self): - """ - Get an entity with an specific ID. - Args: - - entityID(string): Entity ID, required - - attrs(string): Comma separated list of attribute names (properties or relationships) to be retrieved - - type(string): Entity Type - - options(string): Options dictionary; Available values : keyValues, sysAttrs - Returns: - - (200) Entity - - (400) Bad request - - (404) Not found - Tests for get entity: - - Post entity and see if get entity with the same ID returns the entity - with the correct values - - Get entity with an ID that does not exit. See if Not found error is - raised - """ - - """ - Test 1: - post entity_1 with entity_1_ID - get enntity_1 with enity_1_ID - compare if the posted entity_1 is the same as the get_enity_1 - If attributes posted entity.id != ID get entity: - Raise Error - If type posted entity != type get entity: - Raise Error - Test 2: - get enitity with enitity_ID that does not exit - If return != 404: - Raise Error - """ - """Test1""" - self.client.post_entity(entity=self.entity) - ret_entity = self.client.get_entity(entity_id=self.entity.id) - ret_entity_with_type = self.client.get_entity( - entity_id=self.entity.id, entity_type=self.entity.type - ) - ret_entity_keyValues = self.client.get_entity( - entity_id=self.entity.id, options="keyValues" - ) - ret_entity_sysAttrs = self.client.get_entity( - entity_id=self.entity.id, options="sysAttrs" - ) - - self.assertEqual(ret_entity.id, self.entity.id) - self.assertEqual(ret_entity.type, self.entity.type) - self.assertEqual(ret_entity_with_type.id, self.entity.id) - self.assertEqual(ret_entity_with_type.type, self.entity.type) - self.assertEqual(ret_entity_keyValues.id, self.entity.id) - self.assertEqual(ret_entity_keyValues.type, self.entity.type) - self.assertEqual(ret_entity_sysAttrs.id, self.entity.id) - self.assertEqual(ret_entity_sysAttrs.type, self.entity.type) - self.assertNotEqual(ret_entity_sysAttrs.createdAt, None) - - """Test2""" - with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: - self.client.get_entity("urn:roomDoesnotExist") - response = contextmanager.exception.response - self.assertEqual(response.status_code, 404) - - with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: - self.client.get_entity("roomDoesnotExist") - response = contextmanager.exception.response - self.assertEqual(response.status_code, 400) - def test_different_context(self): """ Get entities with different contexts. @@ -427,71 +255,6 @@ def test_different_context(self): ) self.client.delete_entity_by_id(entity_id=temperature_sensor.id) - def test_delete_entity(self): - """ - Removes an specific Entity from an NGSI-LD system. - Args: - - entityID(string): Entity ID; required - - type(string): Entity Type - Returns: - - (204) No Content. The entity was removed successfully. - - (400) Bad request. - - (404) Not found. - Tests: - - Try to delete an non existent entity -> Does it return a Not found? - - Post an entity and try to delete the entity -> Does it return 204? - - Try to get to delete an deleted entity -> Does it return 404? - """ - - """ - Test 1: - delete entity with non existent entity_ID - If return != 404: - Raise Error - - Test 2: - post an entity with entity_ID and entity_type - delete entity with entity_ID - get entity list - If entity with entity_ID in entity list: - Raise Error - - Test 3: - delete entity with entity_ID - return != 404 ? - yes: - Raise Error - """ - - """Test1""" - # try to delete nonexistent entity - with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: - self.client.get_entity(entity_id=self.entity.id) - response = contextmanager.exception.response - self.assertEqual(response.status_code, 404) - self.assertEqual(response.json()["title"], "Entity Not Found") - - """Test2""" - self.client.post_entity(entity=self.entity) - self.client.post_entity(entity=self.entity_2) - entity_list = self.client.get_entity_list() - entity_ids = [entity.id for entity in entity_list] - self.assertIn(self.entity.id, entity_ids) - - self.client.delete_entity_by_id(entity_id=self.entity.id) - entity_list = self.client.get_entity_list() - entity_ids = [entity.id for entity in entity_list] - self.assertNotIn(self.entity.id, entity_ids) - self.assertIn(self.entity_2.id, entity_ids) - - """Test3""" - # entity was already deleted - with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: - self.client.get_entity(entity_id=self.entity.id) - response = contextmanager.exception.response - self.assertEqual(response.status_code, 404) - self.assertEqual(response.json()["title"], "Entity Not Found") - def test_add_attributes_entity(self): """ Append new Entity attributes to an existing Entity within an NGSI-LD system. @@ -791,3 +554,460 @@ def test_replacing_attributes(self): self.assertEqual(prop_dict["test_value"], 25) self.assertIn("my_value", prop_dict) self.assertEqual(prop_dict["my_value"], 45) + +from random import Random +import unittest +from requests.exceptions import HTTPError +from requests import RequestException +from pydantic import ValidationError + +from filip.models.base import FiwareLDHeader + +# FiwareLDHeader issue with pydantic +from filip.clients.ngsi_ld.cb import ContextBrokerLDClient +from filip.models.ngsi_ld.context import ContextLDEntityKeyValues, ActionTypeLD +from tests.config import settings +from filip.utils.cleanup import clear_context_broker_ld + + +class EntitiesBatchOperations(unittest.TestCase): + """ + Test class for entity endpoints. + Args: + unittest (_type_): _description_ + """ + + def setUp(self) -> None: + """ + Setup test data + Returns: + None + """ + self.r = Random() + self.fiware_header = FiwareLDHeader(ngsild_tenant=settings.FIWARE_SERVICE) + self.cb_client = ContextBrokerLDClient( + fiware_header=self.fiware_header, url=settings.LD_CB_URL + ) + clear_context_broker_ld(cb_ld_client=self.cb_client) + + def tearDown(self) -> None: + """ + Cleanup test server + """ + clear_context_broker_ld(cb_ld_client=self.cb_client) + self.cb_client.close() + + def test_entity_batch_operations_create(self) -> None: + """ + Batch Entity creation. + Args: + - Request body(Entity List); required + Returns: + - (200) Success + - (400) Bad Request + Tests: + - Post the creation of batch entities. Check if each of the created entities exists and if all attributes exist. + """ + """ + Test 1: + post create batch entity + get entity list + for all elements in entity list: + if entity list element != batch entity element: + Raise Error + Test 2: + post create batch entity with two entities that have the same id + post in try block + no exception raised + check if the entities list only contains one element (No duplicates) + if not raise assert + """ + """Test 1""" + entities_a = [ + ContextLDEntityKeyValues(id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test") + for i in range(0, 10) + ] + self.cb_client.entity_batch_operation( + entities=entities_a, action_type=ActionTypeLD.CREATE + ) + entity_list = self.cb_client.get_entity_list(entity_type=f"filip:object:test") + id_list = [entity.id for entity in entity_list] + self.assertEqual(len(entities_a), len(entity_list)) + for entity in entities_a: + self.assertIsInstance(entity, ContextLDEntityKeyValues) + self.assertIn(entity.id, id_list) + for entity in entity_list: + self.cb_client.delete_entity_by_id(entity_id=entity.id) + + """Test 2""" + entities_b = [ + ContextLDEntityKeyValues(id=f"urn:ngsi-ld:test:eins", type=f"filip:object:test"), + ContextLDEntityKeyValues(id=f"urn:ngsi-ld:test:eins", type=f"filip:object:test"), + ] + entity_list_b = [] + try: + self.cb_client.entity_batch_operation( + entities=entities_b, action_type=ActionTypeLD.CREATE + ) + entity_list_b = self.cb_client.get_entity_list( + entity_type=f"filip:object:test" + ) + self.assertEqual(len(entity_list), 1) + except: + pass + finally: + for entity in entity_list_b: + self.cb_client.delete_entity_by_id(entity_id=entity.id) + + def test_entity_batch_operations_update(self) -> None: + """ + Batch Entity update. + Args: + - options(string): Available values: noOverwrite + - Request body(EntityList); required + Returns: + - (200) Success + - (400) Bad Request + Tests: + - Post the update of batch entities. Check if each of the updated entities exists and if the updates appear. + - Try the same with the noOverwrite statement and check if the nooverwrite is acknowledged. + """ + """ + Test 1: + post create entity batches + post update of batch entity + get entities + for all entities in entity list: + if entity list element != updated batch entity element: + Raise Error + Test 2: + post create entity batches + post update of batch entity with no overwrite + get entities + for all entities in entity list: + if entity list element != updated batch entity element but not the existings are overwritten: + Raise Error + + """ + """Test 1""" + entities_a = [ + ContextLDEntityKeyValues( + id=f"urn:ngsi-ld:test:{str(i)}", + type=f"filip:object:test", + **{ + "temperature": {"value": self.r.randint(20, 50), "type": "Property"} + }, + ) + for i in range(0, 5) + ] + + self.cb_client.entity_batch_operation( + entities=entities_a, action_type=ActionTypeLD.CREATE + ) + + entities_update = [ + ContextLDEntityKeyValues( + id=f"urn:ngsi-ld:test:{str(i)}", + type=f"filip:object:test", + **{"temperature": {"value": self.r.randint(0, 20), "type": "Property"}}, + ) + for i in range(3, 6) + ] + self.cb_client.entity_batch_operation( + entities=entities_update, action_type=ActionTypeLD.UPDATE + ) + entity_list = self.cb_client.get_entity_list(entity_type=f"filip:object:test") + self.assertEqual(len(entity_list), 5) + updated = [ + x.model_dump(exclude_unset=True, exclude={"context"}) + for x in entity_list + if int(x.id.split(":")[3]) in range(3, 5) + ] + nupdated = [ + x.model_dump(exclude_unset=True, exclude={"context"}) + for x in entity_list + if int(x.id.split(":")[3]) in range(0, 3) + ] + + self.assertCountEqual( + [entity.model_dump(exclude_unset=True) for entity in entities_a[0:3]], + nupdated, + ) + + self.assertCountEqual( + [entity.model_dump(exclude_unset=True) for entity in entities_update[0:2]], + updated, + ) + + """Test 2""" + # presssure will be appended while the existing temperature will + # not be overwritten + entities_update = [ + ContextLDEntityKeyValues( + id=f"urn:ngsi-ld:test:{str(i)}", + type=f"filip:object:test", + **{ + "temperature": { + "value": self.r.randint(50, 100), + "type": "Property", + }, + "pressure": {"value": self.r.randint(1, 100), "type": "Property"}, + }, + ) + for i in range(0, 5) + ] + + self.cb_client.entity_batch_operation( + entities=entities_update, + action_type=ActionTypeLD.UPDATE, + options="noOverwrite", + ) + + previous = entity_list + previous.sort(key=lambda x: int(x.id.split(":")[3])) + + entity_list = self.cb_client.get_entity_list(entity_type=f"filip:object:test",options='keyValues') + entity_list.sort(key=lambda x: int(x.id.split(":")[3])) + + self.assertEqual(len(entity_list), len(entities_update)) + + for updated, entity, prev in zip(entities_update, entity_list, previous): + self.assertEqual( + updated.model_dump().get("pressure").value, + entity.model_dump().get("pressure"), + ) + self.assertNotEqual( + updated.model_dump().get("temperature").value, + entity.model_dump().get("temperature"), + ) + self.assertEqual( + prev.model_dump().get("temperature").value, + entity.model_dump().get("temperature"), + ) + + with self.assertRaises(HTTPError): + self.cb_client.entity_batch_operation( + entities=[], action_type=ActionTypeLD.UPDATE + ) + + # according to spec, this should raise bad request data, + # but pydantic is intercepting + with self.assertRaises(ValidationError): + self.cb_client.entity_batch_operation( + entities=[None], action_type=ActionTypeLD.UPDATE + ) + + for entity in entity_list: + self.cb_client.delete_entity_by_id(entity_id=entity.id) + + def test_entity_batch_operations_upsert(self) -> None: + """ + Batch Entity upsert. + Args: + - options(string): Available values: replace, update + - Request body(EntityList); required + Returns: + - (200) Success + - (400) Bad request + Tests: + - Post entity list and then post the upsert with update and replace. + - Get the entitiy list and see if the results are correct. + + """ + """ + Test 1: + post a create entity batch b0 with attr a0 + post entity upsert batch b1 with attr a1 with update, b0 ∩ b1 != ∅ + post entity upsert batch b2 with attr a1 with replace, b0 ∩ b2 != ∅ && b1 ∩ b2 == ∅ + get entity list + for e in entity list: + if e in b0 ∩ b1: + e should contain a1 and a0 + if e in b0 ∩ b2: + e should contain only a1 + else: + e should contain only a0 + """ + """Test 1""" + # create entities 1 -3 + entities_a = [ + ContextLDEntityKeyValues( + id=f"urn:ngsi-ld:test:{str(i)}", + type=f"filip:object:test", + **{"temperature": {"value": self.r.randint(0, 20), "type": "Property"}}, + ) + for i in range(1, 4) + ] + self.cb_client.entity_batch_operation( + entities=entities_a, action_type=ActionTypeLD.CREATE + ) + + # replace entities 0 - 1 + entities_replace = [ + ContextLDEntityKeyValues( + id=f"urn:ngsi-ld:test:{str(i)}", + type=f"filip:object:test", + **{"pressure": {"value": self.r.randint(50, 100), "type": "Property"}}, + ) + for i in range(0, 2) + ] + self.cb_client.entity_batch_operation( + entities=entities_replace, + action_type=ActionTypeLD.UPSERT, + options="replace", + ) + + # update entities 3 - 4, + # pressure will be appended for 3 + # temperature will be appended for 4 + entities_update = [ + ContextLDEntityKeyValues( + id=f"urn:ngsi-ld:test:{str(i)}", + type=f"filip:object:test", + **{"pressure": {"value": self.r.randint(50, 100), "type": "Property"}}, + ) + for i in range(3, 5) + ] + self.cb_client.entity_batch_operation( + entities=entities_update, action_type=ActionTypeLD.UPSERT, options="update" + ) + + # 0,1 and 4 should have pressure only + # 2 should have temperature only + # 3 should have both + # can be made modular for variable size batches + entity_list = self.cb_client.get_entity_list() + self.assertEqual(len(entity_list), 5) + for _e in entity_list: + _id = int(_e.id.split(":")[3]) + e = _e.model_dump(exclude_unset=True, exclude={"context"}) + if _id in [0, 1]: + self.assertIsNone(e.get("temperature", None)) + self.assertIsNotNone(e.get("pressure", None)) + self.assertCountEqual( + [e], + [ + x.model_dump(exclude_unset=True) + for x in entities_replace + if x.id == _e.id + ], + ) + elif _id == 4: + self.assertIsNone(e.get("temperature", None)) + self.assertIsNotNone(e.get("pressure", None)) + self.assertCountEqual( + [e], + [ + x.model_dump(exclude_unset=True) + for x in entities_update + if x.id == _e.id + ], + ) + elif _id == 2: + self.assertIsNone(e.get("pressure", None)) + self.assertIsNotNone(e.get("temperature", None)) + self.assertCountEqual( + [e], + [ + x.model_dump(exclude_unset=True) + for x in entities_a + if x.id == _e.id + ], + ) + elif _id == 3: + self.assertIsNotNone(e.get("temperature", None)) + self.assertIsNotNone(e.get("pressure", None)) + self.assertCountEqual( + [e.get("temperature")], + [ + x.model_dump(exclude_unset=True).get("temperature") + for x in entities_a + if x.id == _e.id + ], + ) + self.assertCountEqual( + [e.get("pressure")], + [ + x.model_dump(exclude_unset=True).get("pressure") + for x in entities_update + if x.id == _e.id + ], + ) + + for entity in entity_list: + self.cb_client.delete_entity_by_id(entity_id=entity.id) + + def test_entity_batch_operations_delete(self) -> None: + """ + Batch entity delete. + Args: + - Request body(string list); required + Returns + - (200) Success + - (400) Bad request + Tests: + - Try to delete non existent entity. + - Try to delete existent entity and check if it is deleted. + """ + """ + Test 1: + delete batch entity that is non existent + if return != 400: + Raise Error + Test 2: + post batch entity + delete batch entity + if return != 200: + Raise Error + get entity list + if batch entities are still on entity list: + Raise Error: + """ + """Test 1""" + entities_delete = [ + ContextLDEntityKeyValues(id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test") + for i in range(0, 1) + ] + with self.assertRaises(Exception): + self.cb_client.entity_batch_operation( + entities=entities_delete, action_type=ActionTypeLD.DELETE + ) + + """Test 2""" + entity_del_type = "filip:object:test" + entities_ids_a = [f"urn:ngsi-ld:test:{str(i)}" for i in range(0, 4)] + entities_a = [ + ContextLDEntityKeyValues(id=id_a, type=entity_del_type) for id_a in entities_ids_a + ] + + self.cb_client.entity_batch_operation( + entities=entities_a, action_type=ActionTypeLD.CREATE + ) + + entities_delete = [ + ContextLDEntityKeyValues(id=id_a, type=entity_del_type) + for id_a in entities_ids_a[:3] + ] + entities_delete_ids = [entity.id for entity in entities_delete] + + # send update to delete entities + self.cb_client.entity_batch_operation( + entities=entities_delete, action_type=ActionTypeLD.DELETE + ) + + # get list of entities which is still stored + entity_list = self.cb_client.get_entity_list(entity_type=entity_del_type) + entity_ids = [entity.id for entity in entity_list] + + self.assertEqual(len(entity_list), 1) # all but one entity were deleted + + for entityId in entity_ids: + self.assertIn(entityId, entities_ids_a) + for entityId in entities_delete_ids: + self.assertNotIn(entityId, entity_ids) + for entity in entity_list: + self.cb_client.delete_entity_by_id(entity_id=entity.id) + + entity_list = self.cb_client.get_entity_list(entity_type=entity_del_type) + self.assertEqual(len(entity_list), 0) # all entities were deleted + From ecee1dd4d27658d62b5e83fbfb525f57abdba87c Mon Sep 17 00:00:00 2001 From: SystemsPurge Date: Tue, 18 Feb 2025 13:26:10 +0100 Subject: [PATCH 4/7] Corrected double imports and wrong expected field type in batch op tests Signed-off-by: SystemsPurge --- tests/clients/test_ngsi_ld_cb_kv.py | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/tests/clients/test_ngsi_ld_cb_kv.py b/tests/clients/test_ngsi_ld_cb_kv.py index 682f25ee..33efcf1b 100644 --- a/tests/clients/test_ngsi_ld_cb_kv.py +++ b/tests/clients/test_ngsi_ld_cb_kv.py @@ -5,13 +5,12 @@ import unittest import logging from urllib.parse import urljoin - - -from pydantic import AnyHttpUrl,BaseModel,fields +from random import Random +from pydantic import ValidationError from requests import RequestException, Session from requests.adapters import HTTPAdapter +from requests.exceptions import HTTPError from urllib3.util.retry import Retry - from filip.clients.base_http_client import NgsiURLVersion, BaseHttpClient from filip.clients.ngsi_ld.cb import ContextBrokerLDClient from filip.models.base import FiwareLDHeader, core_context @@ -555,20 +554,6 @@ def test_replacing_attributes(self): self.assertIn("my_value", prop_dict) self.assertEqual(prop_dict["my_value"], 45) -from random import Random -import unittest -from requests.exceptions import HTTPError -from requests import RequestException -from pydantic import ValidationError - -from filip.models.base import FiwareLDHeader - -# FiwareLDHeader issue with pydantic -from filip.clients.ngsi_ld.cb import ContextBrokerLDClient -from filip.models.ngsi_ld.context import ContextLDEntityKeyValues, ActionTypeLD -from tests.config import settings -from filip.utils.cleanup import clear_context_broker_ld - class EntitiesBatchOperations(unittest.TestCase): """ @@ -773,15 +758,15 @@ def test_entity_batch_operations_update(self) -> None: for updated, entity, prev in zip(entities_update, entity_list, previous): self.assertEqual( - updated.model_dump().get("pressure").value, + updated.model_dump().get("pressure").get("value"), entity.model_dump().get("pressure"), ) self.assertNotEqual( - updated.model_dump().get("temperature").value, + updated.model_dump().get("temperature").get("value"), entity.model_dump().get("temperature"), ) self.assertEqual( - prev.model_dump().get("temperature").value, + prev.model_dump().get("temperature").get("value"), entity.model_dump().get("temperature"), ) From f2df076093c04469f953fdc0211aa707cfc298ab Mon Sep 17 00:00:00 2001 From: SystemsPurge Date: Mon, 24 Feb 2025 14:48:55 +0100 Subject: [PATCH 5/7] Correction of declared ContextEntityLDKeyValues in test setups Signed-off-by: SystemsPurge --- tests/clients/test_ngsi_ld_cb_kv.py | 281 ++++++++++------------------ 1 file changed, 96 insertions(+), 185 deletions(-) diff --git a/tests/clients/test_ngsi_ld_cb_kv.py b/tests/clients/test_ngsi_ld_cb_kv.py index 33efcf1b..563722e3 100644 --- a/tests/clients/test_ngsi_ld_cb_kv.py +++ b/tests/clients/test_ngsi_ld_cb_kv.py @@ -4,21 +4,17 @@ import unittest import logging -from urllib.parse import urljoin from random import Random from pydantic import ValidationError from requests import RequestException, Session from requests.adapters import HTTPAdapter from requests.exceptions import HTTPError from urllib3.util.retry import Retry -from filip.clients.base_http_client import NgsiURLVersion, BaseHttpClient from filip.clients.ngsi_ld.cb import ContextBrokerLDClient from filip.models.base import FiwareLDHeader, core_context from filip.models.ngsi_ld.context import ( ActionTypeLD, - ContextLDEntityKeyValues, - ContextProperty, - NamedContextProperty, + ContextLDEntityKeyValues ) from tests.config import settings import requests @@ -46,7 +42,7 @@ def setUp(self) -> None: "entities_url": "/ngsi-ld/v1/entities", "types_url": "/ngsi-ld/v1/types", } - self.attr = {"testtemperature": {"type": "Property", "value": 20.0}} + self.attr = {"testtemperature": 20.0} self.entity = ContextLDEntityKeyValues( id="urn:ngsi-ld:my:id4", type="MyType", **self.attr ) @@ -112,12 +108,12 @@ def test_post_entity(self): """ # create entity self.client.post_entity(entity=self.entity) - entity_list = self.client.get_entity_list(entity_type=self.entity.type) + entity_list = self.client.get_entity_list(entity_type=self.entity.type,options='keyValues') self.assertEqual(len(entity_list), 1) self.assertEqual(entity_list[0].id, self.entity.id) self.assertEqual(entity_list[0].type, self.entity.type) self.assertEqual( - entity_list[0].testtemperature.value, self.entity.testtemperature['value'] + entity_list[0].testtemperature, self.entity.testtemperature ) # existed entity @@ -128,39 +124,40 @@ def test_post_entity(self): self.assertEqual(response.status_code, 409) entity_list = self.client.get_entity_list( - entity_type=self.entity_identical.type + entity_type=self.entity_identical.type, + options='keyValues' ) self.assertEqual(len(entity_list), 1) # append new attribute to existed entity self.entity_append = self.entity.model_copy() del self.entity_append.testtemperature - self.entity_append.humidity = ContextProperty(**{"type": "Property", "value": 50}) + self.entity_append.humidity = 50 self.client.post_entity(entity=self.entity_append, append=True) - entity_append_res = self.client.get_entity(entity_id=self.entity_append.id) + entity_append_res = self.client.get_entity(entity_id=self.entity_append.id,options='keyValues') self.assertEqual( - entity_append_res.humidity.value, self.entity_append.humidity.value + entity_append_res.humidity, self.entity_append.humidity ) self.assertEqual( - entity_append_res.testtemperature.value, self.entity.testtemperature['value'] + entity_append_res.testtemperature, self.entity.testtemperature ) # override existed entity - new_attr = {"newattr": {"type": "Property", "value": 999}} + new_attr = {"newattr": 999} self.entity_override = ContextLDEntityKeyValues( id=self.entity.id, type=self.entity.type, **new_attr ) self.client.post_entity(entity=self.entity_override, update=True) - entity_override_res = self.client.get_entity(entity_id=self.entity.id) + entity_override_res = self.client.get_entity(entity_id=self.entity.id,options='keyValues') self.assertEqual( - entity_override_res.newattr.value, self.entity_override.newattr['value'] + entity_override_res.newattr, self.entity_override.newattr ) self.assertNotIn("testtemperature", entity_override_res.model_dump()) # post without entity type is not allowed with self.assertRaises(Exception): self.client.post_entity(ContextLDEntityKeyValues(id="room2")) - entity_list = self.client.get_entity_list() + entity_list = self.client.get_entity_list(options='keyValues') self.assertNotIn("room2", entity_list) """delete""" @@ -176,7 +173,7 @@ def test_different_context(self): temperature_sensor_dict = { "id": "urn:ngsi-ld:temperatureSensor", "type": "TemperatureSensor", - "temperature": {"type": "Property", "value": 23, "unitCode": "CEL"}, + "temperature": 23, } # client with custom context @@ -197,16 +194,32 @@ def test_different_context(self): entity_default = self.client.get_entity(entity_id=temperature_sensor.id) self.assertEqual(entity_default.context, core_context) self.assertEqual( - entity_default.model_dump(exclude_unset=True, exclude={"context"}), - temperature_sensor_dict, + entity_default.id, + temperature_sensor_dict['id'], + ) + self.assertEqual( + entity_default.type, + temperature_sensor_dict['type'], + ) + self.assertEqual( + entity_default.temperature.value, + temperature_sensor_dict['temperature'], ) entity_custom_context = client_custom_context.get_entity( entity_id=temperature_sensor.id ) self.assertEqual(entity_custom_context.context, custom_context) self.assertEqual( - entity_custom_context.model_dump(exclude_unset=True, exclude={"context"}), - temperature_sensor_dict, + entity_custom_context.id, + temperature_sensor_dict['id'], + ) + self.assertEqual( + entity_custom_context.type, + temperature_sensor_dict['type'], + ) + self.assertEqual( + entity_custom_context.temperature.value, + temperature_sensor_dict['temperature'], ) self.client.delete_entity_by_id(entity_id=temperature_sensor.id) @@ -218,8 +231,16 @@ def test_different_context(self): ) self.assertEqual(entity_custom.context, custom_context) self.assertEqual( - entity_custom.model_dump(exclude_unset=True, exclude={"context"}), - temperature_sensor_dict, + entity_custom_context.id, + temperature_sensor_dict['id'], + ) + self.assertEqual( + entity_custom_context.type, + temperature_sensor_dict['type'], + ) + self.assertEqual( + entity_custom_context.temperature.value, + temperature_sensor_dict['temperature'], ) entity_default_context = self.client.get_entity(entity_id=temperature_sensor.id) self.assertEqual(entity_default_context.context, core_context) @@ -243,8 +264,16 @@ def test_different_context(self): ) self.assertEqual(entity_custom.context, custom_context) self.assertEqual( - entity_custom.model_dump(exclude_unset=True, exclude={"context"}), - temperature_sensor_dict, + entity_custom_context.id, + temperature_sensor_dict['id'], + ) + self.assertEqual( + entity_custom_context.type, + temperature_sensor_dict['type'], + ) + self.assertEqual( + entity_custom_context.temperature.value, + temperature_sensor_dict['temperature'], ) entity_default_context = self.client.get_entity(entity_id=temperature_sensor.id) self.assertEqual(entity_default_context.context, core_context) @@ -292,141 +321,32 @@ def test_add_attributes_entity(self): """ """Test 1""" self.client.post_entity(self.entity) - attr = ContextProperty(**{"value": 20, "unitCode": "Number"}) - self.entity.test_value = attr + self.entity.test_value = 20 self.client.append_entity_attributes(self.entity) - entity = self.client.get_entity(entity_id=self.entity.id) - self.assertEqual(first=entity.test_value.value, second=attr.value) + entity = self.client.get_entity(entity_id=self.entity.id,options='keyValues') + self.assertEqual(first=entity.test_value, second=self.entity.test_value) self.client.delete_entity_by_id(entity_id=entity.id) """Test 2""" - attr = ContextProperty(**{"value": 20, "type": "Property"}) with self.assertRaises(Exception): - self.entity.test_value = attr + self.entity.test_value = 20 self.client.append_entity_attributes(self.entity) """Test 3""" self.client.post_entity(self.entity) # What makes an property/ attribute unique ??? - attr = ContextProperty(**{"value": 20, "type": "Property"}) - attr_same = ContextProperty(**{"value": 40, "type": "Property"}) - self.entity.test_value = attr + self.entity.test_value = 20 self.client.append_entity_attributes(self.entity) - self.entity.test_value = attr_same + self.entity.test_value = 40 with self.assertRaises(RequestException): self.client.append_entity_attributes(self.entity, options="noOverwrite") - entity = self.client.get_entity(entity_id=self.entity.id) - self.assertEqual(first=entity.test_value.value, second=attr.value) - self.assertNotEqual(first=entity.test_value.value, second=attr_same.value) - - def test_patch_entity_attrs(self): - """ - Update existing Entity attributes within an NGSI-LD system - Args: - - entityId(string): Entity ID; required - - Request body; required - Returns: - - (201) Created. Contains the resource URI of the created Entity - - (400) Bad request - - (409) Already exists - - (422) Unprocessable Entity - Tests: - - Post an enitity with specific attributes. Change the attributes with patch. - """ - """ - Test 1: - post an enitity with entity_ID and entity_type and attributes - patch one of the attributes with entity_id by sending request body - get entity list - If new attribute is not added to the entity? - Raise Error - """ - """Test1""" - newer_prop = NamedContextProperty(value=40, name="new_prop") - - self.entity.new_prop = ContextProperty(value=25) - self.client.post_entity(entity=self.entity) - self.client.update_entity_attribute( - entity_id=self.entity.id, attr=newer_prop, attr_name="new_prop" - ) - entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") - prop_dict = entity.model_dump() - self.assertIn("new_prop", prop_dict) - self.assertEqual(prop_dict["new_prop"], 40) - - def test_patch_entity_attrs_contextprop(self): - """ - Update existing Entity attributes within an NGSI-LD system - Args: - - entityId(string): Entity ID; required - - Request body; required - Returns: - - (201) Created. Contains the resource URI of the created Entity - - (400) Bad request - - (409) Already exists - - (422) Unprocessable Entity - Tests: - - Post an enitity with specific attributes. Change the attributes with patch. - """ - """ - Test 1: - post an enitity with entity_ID and entity_type and attributes - patch one of the attributes with entity_id by sending request body - get entity list - If new attribute is not added to the entity? - Raise Error - """ - """Test1""" - newer_prop = ContextProperty(value=55) - - self.entity.new_prop = ContextProperty(value=25) - self.client.post_entity(entity=self.entity) - self.client.update_entity_attribute( - entity_id=self.entity.id, attr=newer_prop, attr_name="new_prop" - ) - entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") - prop_dict = entity.model_dump() - self.assertIn("new_prop", prop_dict) - self.assertEqual(prop_dict["new_prop"], 55) - - def test_patch_entity_attrs_attrId(self): - """ - Update existing Entity attribute ID within an NGSI-LD system - Args: - - entityId(string): Entity Id; required - - attrId(string): Attribute Id; required - Returns: - - (204) No Content - - (400) Bad Request - - (404) Not Found - Tests: - - Post an enitity with specific attributes. Change the attributes with patch. - """ - """ - Test 1: - post an entity with entity_ID, entity_type and attributes - patch with entity_ID and attribute_ID - return != 204: - yes: - Raise Error - """ - """Test 1""" - attr = NamedContextProperty(name="test_value", value=20) - self.entity.test_value = attr - self.client.post_entity(entity=self.entity) - - attr.value = 40 - self.client.update_entity_attribute( - entity_id=self.entity.id, attr=attr, attr_name="test_value" - ) - entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") - prop_dict = entity.model_dump() - self.assertIn("test_value", prop_dict) - self.assertEqual(prop_dict["test_value"], 40) + entity = self.client.get_entity(entity_id=self.entity.id,options='keyValues') + self.assertEqual(first=entity.test_value, second=20) + self.assertNotEqual(first=entity.test_value, second=40) def test_delete_entity_attribute(self): """ @@ -460,22 +380,20 @@ def test_delete_entity_attribute(self): """ """Test 1""" - attr = NamedContextProperty(name="test_value", value=20) - self.entity.test_value = attr + self.entity.test_value = 20 self.client.post_entity(entity=self.entity) with self.assertRaises(Exception): self.client.delete_attribute( entity_id=self.entity.id, attribute_id="does_not_exist" ) - entity_list = self.client.get_entity_list() + entity_list = self.client.get_entity_list(options='keyValues') for entity in entity_list: self.client.delete_entity_by_id(entity_id=entity.id) """Test 2""" - attr = NamedContextProperty(name="test_value", value=20) - self.entity.test_value = attr + self.entity.test_value = 20 self.client.post_entity(entity=self.entity) self.client.delete_attribute( entity_id=self.entity.id, attribute_id="test_value" @@ -508,17 +426,15 @@ def test_replacing_attributes(self): """ """Test 1""" - attr1 = NamedContextProperty(name="test_value", value=20) - self.entity.test_value = attr1 + self.entity.test_value = 20 self.client.post_entity(entity=self.entity) entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") prop_dict = entity.model_dump() self.assertIn("test_value", prop_dict) self.assertEqual(prop_dict["test_value"], 20) - attr2 = NamedContextProperty(name="test_value", value=44) del self.entity.test_value - self.entity.test_value = attr2 + self.entity.test_value = 44 self.client.replace_existing_attributes_of_entity(entity=self.entity) entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") prop_dict = entity.model_dump() @@ -528,10 +444,8 @@ def test_replacing_attributes(self): self.client.delete_entity_by_id(entity_id=self.entity.id) """Test 2""" - attr1 = NamedContextProperty(name="test_value", value=20) - attr2 = NamedContextProperty(name="my_value", value=44) - self.entity.test_value = attr1 - self.entity.my_value = attr2 + self.entity.test_value = 20 + self.entity.my_value = 44 self.client.post_entity(entity=self.entity) entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") prop_dict = entity.model_dump() @@ -542,10 +456,8 @@ def test_replacing_attributes(self): del self.entity.test_value del self.entity.my_value - attr3 = NamedContextProperty(name="test_value", value=25) - attr4 = NamedContextProperty(name="my_value", value=45) - self.entity.test_value = attr3 - self.entity.my_value = attr4 + self.entity.test_value = 25 + self.entity.my_value = 45 self.client.replace_existing_attributes_of_entity(entity=self.entity) entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") prop_dict = entity.model_dump() @@ -615,7 +527,7 @@ def test_entity_batch_operations_create(self) -> None: self.cb_client.entity_batch_operation( entities=entities_a, action_type=ActionTypeLD.CREATE ) - entity_list = self.cb_client.get_entity_list(entity_type=f"filip:object:test") + entity_list = self.cb_client.get_entity_list(entity_type=f"filip:object:test",options='keyValues') id_list = [entity.id for entity in entity_list] self.assertEqual(len(entities_a), len(entity_list)) for entity in entities_a: @@ -635,7 +547,8 @@ def test_entity_batch_operations_create(self) -> None: entities=entities_b, action_type=ActionTypeLD.CREATE ) entity_list_b = self.cb_client.get_entity_list( - entity_type=f"filip:object:test" + entity_type=f"filip:object:test", + options='keyValues' ) self.assertEqual(len(entity_list), 1) except: @@ -680,7 +593,7 @@ def test_entity_batch_operations_update(self) -> None: id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test", **{ - "temperature": {"value": self.r.randint(20, 50), "type": "Property"} + "temperature": self.r.randint(20, 50) }, ) for i in range(0, 5) @@ -694,22 +607,24 @@ def test_entity_batch_operations_update(self) -> None: ContextLDEntityKeyValues( id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test", - **{"temperature": {"value": self.r.randint(0, 20), "type": "Property"}}, + **{ + "temperature": self.r.randint(0, 20) + }, ) for i in range(3, 6) ] self.cb_client.entity_batch_operation( entities=entities_update, action_type=ActionTypeLD.UPDATE ) - entity_list = self.cb_client.get_entity_list(entity_type=f"filip:object:test") + entity_list = self.cb_client.get_entity_list(entity_type=f"filip:object:test",options='keyValues') self.assertEqual(len(entity_list), 5) updated = [ - x.model_dump(exclude_unset=True, exclude={"context"}) + x.model_dump(exclude_unset=True, exclude={"@context"}) for x in entity_list if int(x.id.split(":")[3]) in range(3, 5) ] nupdated = [ - x.model_dump(exclude_unset=True, exclude={"context"}) + x.model_dump(exclude_unset=True, exclude={"@context"}) for x in entity_list if int(x.id.split(":")[3]) in range(0, 3) ] @@ -732,11 +647,8 @@ def test_entity_batch_operations_update(self) -> None: id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test", **{ - "temperature": { - "value": self.r.randint(50, 100), - "type": "Property", - }, - "pressure": {"value": self.r.randint(1, 100), "type": "Property"}, + "temperature": self.r.randint(50, 100), + "pressure":self.r.randint(1, 100), }, ) for i in range(0, 5) @@ -758,15 +670,15 @@ def test_entity_batch_operations_update(self) -> None: for updated, entity, prev in zip(entities_update, entity_list, previous): self.assertEqual( - updated.model_dump().get("pressure").get("value"), + updated.model_dump().get("pressure"), entity.model_dump().get("pressure"), ) self.assertNotEqual( - updated.model_dump().get("temperature").get("value"), + updated.model_dump().get("temperature"), entity.model_dump().get("temperature"), ) self.assertEqual( - prev.model_dump().get("temperature").get("value"), + prev.model_dump().get("temperature"), entity.model_dump().get("temperature"), ) @@ -819,7 +731,7 @@ def test_entity_batch_operations_upsert(self) -> None: ContextLDEntityKeyValues( id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test", - **{"temperature": {"value": self.r.randint(0, 20), "type": "Property"}}, + **{"temperature": self.r.randint(0, 20)}, ) for i in range(1, 4) ] @@ -832,7 +744,7 @@ def test_entity_batch_operations_upsert(self) -> None: ContextLDEntityKeyValues( id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test", - **{"pressure": {"value": self.r.randint(50, 100), "type": "Property"}}, + **{"pressure": self.r.randint(50, 100)}, ) for i in range(0, 2) ] @@ -849,7 +761,7 @@ def test_entity_batch_operations_upsert(self) -> None: ContextLDEntityKeyValues( id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test", - **{"pressure": {"value": self.r.randint(50, 100), "type": "Property"}}, + **{"pressure": self.r.randint(50, 100)}, ) for i in range(3, 5) ] @@ -861,11 +773,11 @@ def test_entity_batch_operations_upsert(self) -> None: # 2 should have temperature only # 3 should have both # can be made modular for variable size batches - entity_list = self.cb_client.get_entity_list() + entity_list = self.cb_client.get_entity_list(options='keyValues') self.assertEqual(len(entity_list), 5) for _e in entity_list: _id = int(_e.id.split(":")[3]) - e = _e.model_dump(exclude_unset=True, exclude={"context"}) + e = _e.model_dump(exclude_unset=True, exclude={"@context"}) if _id in [0, 1]: self.assertIsNone(e.get("temperature", None)) self.assertIsNotNone(e.get("pressure", None)) @@ -981,7 +893,7 @@ def test_entity_batch_operations_delete(self) -> None: ) # get list of entities which is still stored - entity_list = self.cb_client.get_entity_list(entity_type=entity_del_type) + entity_list = self.cb_client.get_entity_list(entity_type=entity_del_type,options='keyValues') entity_ids = [entity.id for entity in entity_list] self.assertEqual(len(entity_list), 1) # all but one entity were deleted @@ -993,6 +905,5 @@ def test_entity_batch_operations_delete(self) -> None: for entity in entity_list: self.cb_client.delete_entity_by_id(entity_id=entity.id) - entity_list = self.cb_client.get_entity_list(entity_type=entity_del_type) - self.assertEqual(len(entity_list), 0) # all entities were deleted - + entity_list = self.cb_client.get_entity_list(entity_type=entity_del_type,options='keyValues') + self.assertEqual(len(entity_list), 0) # all entities were deleted \ No newline at end of file From 74d0438aa358f89619b616d8931ed4905cb996d0 Mon Sep 17 00:00:00 2001 From: JunsongDu Date: Tue, 25 Feb 2025 09:10:53 +0100 Subject: [PATCH 6/7] chore: pre-commit formatting --- tests/clients/test_ngsi_ld_cb_kv.py | 136 +++++++++++++++------------- 1 file changed, 74 insertions(+), 62 deletions(-) diff --git a/tests/clients/test_ngsi_ld_cb_kv.py b/tests/clients/test_ngsi_ld_cb_kv.py index 563722e3..081e738a 100644 --- a/tests/clients/test_ngsi_ld_cb_kv.py +++ b/tests/clients/test_ngsi_ld_cb_kv.py @@ -12,10 +12,7 @@ from urllib3.util.retry import Retry from filip.clients.ngsi_ld.cb import ContextBrokerLDClient from filip.models.base import FiwareLDHeader, core_context -from filip.models.ngsi_ld.context import ( - ActionTypeLD, - ContextLDEntityKeyValues -) +from filip.models.ngsi_ld.context import ActionTypeLD, ContextLDEntityKeyValues from tests.config import settings import requests from filip.utils.cleanup import clear_context_broker_ld @@ -108,13 +105,13 @@ def test_post_entity(self): """ # create entity self.client.post_entity(entity=self.entity) - entity_list = self.client.get_entity_list(entity_type=self.entity.type,options='keyValues') + entity_list = self.client.get_entity_list( + entity_type=self.entity.type, options="keyValues" + ) self.assertEqual(len(entity_list), 1) self.assertEqual(entity_list[0].id, self.entity.id) self.assertEqual(entity_list[0].type, self.entity.type) - self.assertEqual( - entity_list[0].testtemperature, self.entity.testtemperature - ) + self.assertEqual(entity_list[0].testtemperature, self.entity.testtemperature) # existed entity self.entity_identical = self.entity.model_copy() @@ -124,8 +121,7 @@ def test_post_entity(self): self.assertEqual(response.status_code, 409) entity_list = self.client.get_entity_list( - entity_type=self.entity_identical.type, - options='keyValues' + entity_type=self.entity_identical.type, options="keyValues" ) self.assertEqual(len(entity_list), 1) @@ -134,13 +130,11 @@ def test_post_entity(self): del self.entity_append.testtemperature self.entity_append.humidity = 50 self.client.post_entity(entity=self.entity_append, append=True) - entity_append_res = self.client.get_entity(entity_id=self.entity_append.id,options='keyValues') - self.assertEqual( - entity_append_res.humidity, self.entity_append.humidity - ) - self.assertEqual( - entity_append_res.testtemperature, self.entity.testtemperature + entity_append_res = self.client.get_entity( + entity_id=self.entity_append.id, options="keyValues" ) + self.assertEqual(entity_append_res.humidity, self.entity_append.humidity) + self.assertEqual(entity_append_res.testtemperature, self.entity.testtemperature) # override existed entity new_attr = {"newattr": 999} @@ -148,16 +142,16 @@ def test_post_entity(self): id=self.entity.id, type=self.entity.type, **new_attr ) self.client.post_entity(entity=self.entity_override, update=True) - entity_override_res = self.client.get_entity(entity_id=self.entity.id,options='keyValues') - self.assertEqual( - entity_override_res.newattr, self.entity_override.newattr + entity_override_res = self.client.get_entity( + entity_id=self.entity.id, options="keyValues" ) + self.assertEqual(entity_override_res.newattr, self.entity_override.newattr) self.assertNotIn("testtemperature", entity_override_res.model_dump()) # post without entity type is not allowed with self.assertRaises(Exception): self.client.post_entity(ContextLDEntityKeyValues(id="room2")) - entity_list = self.client.get_entity_list(options='keyValues') + entity_list = self.client.get_entity_list(options="keyValues") self.assertNotIn("room2", entity_list) """delete""" @@ -195,15 +189,15 @@ def test_different_context(self): self.assertEqual(entity_default.context, core_context) self.assertEqual( entity_default.id, - temperature_sensor_dict['id'], + temperature_sensor_dict["id"], ) self.assertEqual( entity_default.type, - temperature_sensor_dict['type'], + temperature_sensor_dict["type"], ) self.assertEqual( entity_default.temperature.value, - temperature_sensor_dict['temperature'], + temperature_sensor_dict["temperature"], ) entity_custom_context = client_custom_context.get_entity( entity_id=temperature_sensor.id @@ -211,15 +205,15 @@ def test_different_context(self): self.assertEqual(entity_custom_context.context, custom_context) self.assertEqual( entity_custom_context.id, - temperature_sensor_dict['id'], + temperature_sensor_dict["id"], ) self.assertEqual( entity_custom_context.type, - temperature_sensor_dict['type'], + temperature_sensor_dict["type"], ) self.assertEqual( entity_custom_context.temperature.value, - temperature_sensor_dict['temperature'], + temperature_sensor_dict["temperature"], ) self.client.delete_entity_by_id(entity_id=temperature_sensor.id) @@ -232,15 +226,15 @@ def test_different_context(self): self.assertEqual(entity_custom.context, custom_context) self.assertEqual( entity_custom_context.id, - temperature_sensor_dict['id'], + temperature_sensor_dict["id"], ) self.assertEqual( entity_custom_context.type, - temperature_sensor_dict['type'], + temperature_sensor_dict["type"], ) self.assertEqual( entity_custom_context.temperature.value, - temperature_sensor_dict['temperature'], + temperature_sensor_dict["temperature"], ) entity_default_context = self.client.get_entity(entity_id=temperature_sensor.id) self.assertEqual(entity_default_context.context, core_context) @@ -252,12 +246,16 @@ def test_different_context(self): # custom context in entity temperature_sensor = ContextLDEntityKeyValues( - **dict({"@context":[ - "https://n5geh.github.io/n5geh.test-context.io/context_saref.jsonld" - ]}), - **temperature_sensor_dict + **dict( + { + "@context": [ + "https://n5geh.github.io/n5geh.test-context.io/context_saref.jsonld" + ] + } + ), + **temperature_sensor_dict, ) - + self.client.post_entity(entity=temperature_sensor) entity_custom = client_custom_context.get_entity( entity_id=temperature_sensor.id @@ -265,15 +263,15 @@ def test_different_context(self): self.assertEqual(entity_custom.context, custom_context) self.assertEqual( entity_custom_context.id, - temperature_sensor_dict['id'], + temperature_sensor_dict["id"], ) self.assertEqual( entity_custom_context.type, - temperature_sensor_dict['type'], + temperature_sensor_dict["type"], ) self.assertEqual( entity_custom_context.temperature.value, - temperature_sensor_dict['temperature'], + temperature_sensor_dict["temperature"], ) entity_default_context = self.client.get_entity(entity_id=temperature_sensor.id) self.assertEqual(entity_default_context.context, core_context) @@ -325,7 +323,7 @@ def test_add_attributes_entity(self): self.entity.test_value = 20 self.client.append_entity_attributes(self.entity) - entity = self.client.get_entity(entity_id=self.entity.id,options='keyValues') + entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") self.assertEqual(first=entity.test_value, second=self.entity.test_value) self.client.delete_entity_by_id(entity_id=entity.id) @@ -341,10 +339,10 @@ def test_add_attributes_entity(self): self.entity.test_value = 20 self.client.append_entity_attributes(self.entity) self.entity.test_value = 40 - + with self.assertRaises(RequestException): self.client.append_entity_attributes(self.entity, options="noOverwrite") - entity = self.client.get_entity(entity_id=self.entity.id,options='keyValues') + entity = self.client.get_entity(entity_id=self.entity.id, options="keyValues") self.assertEqual(first=entity.test_value, second=20) self.assertNotEqual(first=entity.test_value, second=40) @@ -387,7 +385,7 @@ def test_delete_entity_attribute(self): entity_id=self.entity.id, attribute_id="does_not_exist" ) - entity_list = self.client.get_entity_list(options='keyValues') + entity_list = self.client.get_entity_list(options="keyValues") for entity in entity_list: self.client.delete_entity_by_id(entity_id=entity.id) @@ -521,13 +519,17 @@ def test_entity_batch_operations_create(self) -> None: """ """Test 1""" entities_a = [ - ContextLDEntityKeyValues(id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test") + ContextLDEntityKeyValues( + id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test" + ) for i in range(0, 10) ] self.cb_client.entity_batch_operation( entities=entities_a, action_type=ActionTypeLD.CREATE ) - entity_list = self.cb_client.get_entity_list(entity_type=f"filip:object:test",options='keyValues') + entity_list = self.cb_client.get_entity_list( + entity_type=f"filip:object:test", options="keyValues" + ) id_list = [entity.id for entity in entity_list] self.assertEqual(len(entities_a), len(entity_list)) for entity in entities_a: @@ -538,8 +540,12 @@ def test_entity_batch_operations_create(self) -> None: """Test 2""" entities_b = [ - ContextLDEntityKeyValues(id=f"urn:ngsi-ld:test:eins", type=f"filip:object:test"), - ContextLDEntityKeyValues(id=f"urn:ngsi-ld:test:eins", type=f"filip:object:test"), + ContextLDEntityKeyValues( + id=f"urn:ngsi-ld:test:eins", type=f"filip:object:test" + ), + ContextLDEntityKeyValues( + id=f"urn:ngsi-ld:test:eins", type=f"filip:object:test" + ), ] entity_list_b = [] try: @@ -547,8 +553,7 @@ def test_entity_batch_operations_create(self) -> None: entities=entities_b, action_type=ActionTypeLD.CREATE ) entity_list_b = self.cb_client.get_entity_list( - entity_type=f"filip:object:test", - options='keyValues' + entity_type=f"filip:object:test", options="keyValues" ) self.assertEqual(len(entity_list), 1) except: @@ -592,9 +597,7 @@ def test_entity_batch_operations_update(self) -> None: ContextLDEntityKeyValues( id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test", - **{ - "temperature": self.r.randint(20, 50) - }, + **{"temperature": self.r.randint(20, 50)}, ) for i in range(0, 5) ] @@ -607,16 +610,16 @@ def test_entity_batch_operations_update(self) -> None: ContextLDEntityKeyValues( id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test", - **{ - "temperature": self.r.randint(0, 20) - }, + **{"temperature": self.r.randint(0, 20)}, ) for i in range(3, 6) ] self.cb_client.entity_batch_operation( entities=entities_update, action_type=ActionTypeLD.UPDATE ) - entity_list = self.cb_client.get_entity_list(entity_type=f"filip:object:test",options='keyValues') + entity_list = self.cb_client.get_entity_list( + entity_type=f"filip:object:test", options="keyValues" + ) self.assertEqual(len(entity_list), 5) updated = [ x.model_dump(exclude_unset=True, exclude={"@context"}) @@ -648,7 +651,7 @@ def test_entity_batch_operations_update(self) -> None: type=f"filip:object:test", **{ "temperature": self.r.randint(50, 100), - "pressure":self.r.randint(1, 100), + "pressure": self.r.randint(1, 100), }, ) for i in range(0, 5) @@ -663,7 +666,9 @@ def test_entity_batch_operations_update(self) -> None: previous = entity_list previous.sort(key=lambda x: int(x.id.split(":")[3])) - entity_list = self.cb_client.get_entity_list(entity_type=f"filip:object:test",options='keyValues') + entity_list = self.cb_client.get_entity_list( + entity_type=f"filip:object:test", options="keyValues" + ) entity_list.sort(key=lambda x: int(x.id.split(":")[3])) self.assertEqual(len(entity_list), len(entities_update)) @@ -773,7 +778,7 @@ def test_entity_batch_operations_upsert(self) -> None: # 2 should have temperature only # 3 should have both # can be made modular for variable size batches - entity_list = self.cb_client.get_entity_list(options='keyValues') + entity_list = self.cb_client.get_entity_list(options="keyValues") self.assertEqual(len(entity_list), 5) for _e in entity_list: _id = int(_e.id.split(":")[3]) @@ -862,7 +867,9 @@ def test_entity_batch_operations_delete(self) -> None: """ """Test 1""" entities_delete = [ - ContextLDEntityKeyValues(id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test") + ContextLDEntityKeyValues( + id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test" + ) for i in range(0, 1) ] with self.assertRaises(Exception): @@ -874,7 +881,8 @@ def test_entity_batch_operations_delete(self) -> None: entity_del_type = "filip:object:test" entities_ids_a = [f"urn:ngsi-ld:test:{str(i)}" for i in range(0, 4)] entities_a = [ - ContextLDEntityKeyValues(id=id_a, type=entity_del_type) for id_a in entities_ids_a + ContextLDEntityKeyValues(id=id_a, type=entity_del_type) + for id_a in entities_ids_a ] self.cb_client.entity_batch_operation( @@ -893,7 +901,9 @@ def test_entity_batch_operations_delete(self) -> None: ) # get list of entities which is still stored - entity_list = self.cb_client.get_entity_list(entity_type=entity_del_type,options='keyValues') + entity_list = self.cb_client.get_entity_list( + entity_type=entity_del_type, options="keyValues" + ) entity_ids = [entity.id for entity in entity_list] self.assertEqual(len(entity_list), 1) # all but one entity were deleted @@ -905,5 +915,7 @@ def test_entity_batch_operations_delete(self) -> None: for entity in entity_list: self.cb_client.delete_entity_by_id(entity_id=entity.id) - entity_list = self.cb_client.get_entity_list(entity_type=entity_del_type,options='keyValues') - self.assertEqual(len(entity_list), 0) # all entities were deleted \ No newline at end of file + entity_list = self.cb_client.get_entity_list( + entity_type=entity_del_type, options="keyValues" + ) + self.assertEqual(len(entity_list), 0) # all entities were deleted From 957482860e0f7bc28476bc3d2a134d8ccf5979a2 Mon Sep 17 00:00:00 2001 From: JunsongDu Date: Tue, 25 Feb 2025 09:52:50 +0100 Subject: [PATCH 7/7] chore: validate attribut no exist after deleting --- tests/clients/test_ngsi_ld_cb_kv.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/clients/test_ngsi_ld_cb_kv.py b/tests/clients/test_ngsi_ld_cb_kv.py index 081e738a..7a514929 100644 --- a/tests/clients/test_ngsi_ld_cb_kv.py +++ b/tests/clients/test_ngsi_ld_cb_kv.py @@ -119,7 +119,6 @@ def test_post_entity(self): self.client.post_entity(entity=self.entity_identical) response = contextmanager.exception.response self.assertEqual(response.status_code, 409) - entity_list = self.client.get_entity_list( entity_type=self.entity_identical.type, options="keyValues" ) @@ -154,7 +153,7 @@ def test_post_entity(self): entity_list = self.client.get_entity_list(options="keyValues") self.assertNotIn("room2", entity_list) - """delete""" + # delete entity self.client.entity_batch_operation( entities=entity_list, action_type=ActionTypeLD.DELETE ) @@ -384,11 +383,7 @@ def test_delete_entity_attribute(self): self.client.delete_attribute( entity_id=self.entity.id, attribute_id="does_not_exist" ) - - entity_list = self.client.get_entity_list(options="keyValues") - - for entity in entity_list: - self.client.delete_entity_by_id(entity_id=entity.id) + self.client.delete_entity_by_id(entity_id=self.entity.id) """Test 2""" self.entity.test_value = 20 @@ -396,7 +391,8 @@ def test_delete_entity_attribute(self): self.client.delete_attribute( entity_id=self.entity.id, attribute_id="test_value" ) - + entity_response = self.client.get_entity(entity_id=self.entity.id) + self.assertNotIn("test_value", entity_response.model_dump()) with self.assertRaises(requests.exceptions.HTTPError) as contextmanager: self.client.delete_attribute( entity_id=self.entity.id, attribute_id="test_value" @@ -520,7 +516,9 @@ def test_entity_batch_operations_create(self) -> None: """Test 1""" entities_a = [ ContextLDEntityKeyValues( - id=f"urn:ngsi-ld:test:{str(i)}", type=f"filip:object:test" + id=f"urn:ngsi-ld:test:{str(i)}", + type=f"filip:object:test", + temperature=i, ) for i in range(0, 10) ]