From cbace7c959914e629c50a1e0ecd302c239c2198f Mon Sep 17 00:00:00 2001 From: Matthew D'Alonzo Date: Wed, 3 Apr 2024 15:53:14 -0400 Subject: [PATCH 1/4] 1.5.7 Changes * Add Notification UMessageType * Add isShortForm() * Various Fixes * 100% Code Coverage * Added Linter and Coverage checker to build Co-Authored-By: Patrick Yang <47698870+hostilechild007@users.noreply.github.com> Co-Authored-By: Neelam Kushwah <7788957+neelam-kushwah@users.noreply.github.com> --- .github/workflows/python-checker.yml | 68 +++ pyproject.toml | 8 +- scripts/pull_and_compile_protos.py | 4 +- .../test_datamodel/test_ucloudevent.py | 343 ++++++++--- .../test_ucloudeventattributes.py | 17 + .../test_factory/test_cloudeventfactory.py | 465 +++++++++++---- .../test_base64protobufserializer.py | 44 +- .../test_cloudeventtojsonserializer.py | 113 ++-- .../test_cloudeventtoprotobufserializer.py | 190 ++++-- .../test_cloudeventvalidator.py | 437 +++++++++----- tests/test_rpc/test_calloptions.py | 54 +- tests/test_rpc/test_rpc.py | 214 +++++-- .../test_builder/test_uattributesbuilder.py | 63 +- .../test_builder/test_upayloadbuilder.py | 260 +++++++++ .../test_uattributesvalidator.py | 542 +++++++++++------- tests/test_uri/test_factory/__init__.py | 0 .../test_factory/test_uentity_factory.py | 124 ++++ .../test_factory/test_uresource_builder.py | 63 ++ .../test_serializer/test_ipaddress.py | 135 +++++ .../test_serializer/test_longuriserializer.py | 380 ++++++++---- .../test_microuriserializer.py | 46 +- .../test_shorturiserializer.py | 240 ++++++++ .../test_serializer/test_uriserializer.py | 252 ++++++-- .../test_validator/test_urivalidator.py | 267 +++++++-- .../test_factory/test_uuidfactory.py | 146 ++--- .../test_uuid/test_factory/test_uuidutils.py | 119 ++++ .../test_validator/test_uuidvalidator.py | 6 +- .../datamodel/ucloudeventattributes.py | 72 ++- .../cloudevent/factory/cloudeventfactory.py | 132 +++-- uprotocol/cloudevent/factory/ucloudevent.py | 480 +++++++++++----- .../cloudeventtoprotobufserializer.py | 39 +- .../validate/cloudeventvalidator.py | 248 +++++--- uprotocol/rpc/calloptions.py | 102 ---- uprotocol/rpc/rpcclient.py | 36 +- uprotocol/rpc/rpcmapper.py | 74 ++- uprotocol/rpc/rpcresult.py | 38 +- uprotocol/rpc/rpcserver.py | 59 -- uprotocol/rpc/urpclistener.py | 46 -- .../transport/builder/uattributesbuilder.py | 81 ++- .../transport/builder/upayloadbuilder.py | 55 +- uprotocol/transport/utransport.py | 3 +- .../validate/uattributesvalidator.py | 344 +++++++---- uprotocol/uri/README.adoc | 12 - uprotocol/uri/factory/uentity_factory.py | 45 +- uprotocol/uri/factory/uresource_builder.py | 60 +- uprotocol/uri/serializer/ipaddress.py | 79 +++ uprotocol/uri/serializer/longuriserializer.py | 65 ++- .../uri/serializer/microuriserializer.py | 98 ++-- .../uri/serializer/shorturiserializer.py | 226 ++++++++ uprotocol/uri/serializer/uriserializer.py | 41 +- uprotocol/uri/validator/urivalidator.py | 231 +++++--- uprotocol/uuid/factory/uuidfactory.py | 26 +- uprotocol/uuid/factory/uuidutils.py | 223 +++++-- .../uuid/serializer/longuuidserializer.py | 22 +- .../uuid/serializer/microuuidserializer.py | 20 +- uprotocol/uuid/serializer/uuidserializer.py | 8 +- uprotocol/uuid/validate/uuidvalidator.py | 55 +- uprotocol/validation/validationresult.py | 4 +- 58 files changed, 5527 insertions(+), 2097 deletions(-) create mode 100644 .github/workflows/python-checker.yml create mode 100644 tests/test_transport/test_builder/test_upayloadbuilder.py create mode 100644 tests/test_uri/test_factory/__init__.py create mode 100644 tests/test_uri/test_factory/test_uentity_factory.py create mode 100644 tests/test_uri/test_factory/test_uresource_builder.py create mode 100644 tests/test_uri/test_serializer/test_ipaddress.py create mode 100644 tests/test_uri/test_serializer/test_shorturiserializer.py create mode 100644 tests/test_uuid/test_factory/test_uuidutils.py delete mode 100644 uprotocol/rpc/calloptions.py delete mode 100644 uprotocol/rpc/rpcserver.py delete mode 100644 uprotocol/rpc/urpclistener.py create mode 100644 uprotocol/uri/serializer/ipaddress.py create mode 100644 uprotocol/uri/serializer/shorturiserializer.py diff --git a/.github/workflows/python-checker.yml b/.github/workflows/python-checker.yml new file mode 100644 index 0000000..f643869 --- /dev/null +++ b/.github/workflows/python-checker.yml @@ -0,0 +1,68 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Run Linter/Coverage Test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Apache Maven Central + uses: actions/setup-java@v3 + with: # configure settings.xml + distribution: 'temurin' + java-version: '11' + server-id: ossrh + server-username: OSSRH_USER + server-password: OSSRH_PASSWORD + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 pytest coverage cloudevents multimethod protobuf==4.24.2 + python -m pip install poetry + + - name: Install poetry dependencies + run: | + poetry install + + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 --ignore W503,W504,F811 . --max-line-length=127 --exclude **/*pb2.py + + - name: Run prebuild script + run: | + cd scripts + # Run the script within the Poetry virtual environment + poetry run python pull_and_compile_protos.py + + - name: Run tests and coverage report + run: | + coverage run --source tests/ -m unittest discover \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 73c4faa..d95cef2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,13 +15,11 @@ packages = [{ include = "uprotocol" }, [tool.poetry.dependencies] python = "^3.8" cloudevents = "*" -multipledispatch = "*" +multimethod = "*" gitpython = ">=3.1.41" googleapis-common-protos = ">=1.56.4" +protobuf = "4.24.2" [build-system] requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - - - +build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/scripts/pull_and_compile_protos.py b/scripts/pull_and_compile_protos.py index 947028f..02f959b 100644 --- a/scripts/pull_and_compile_protos.py +++ b/scripts/pull_and_compile_protos.py @@ -34,7 +34,7 @@ REPO_URL = "https://github.com/eclipse-uprotocol/up-core-api.git" PROTO_REPO_DIR = os.path.abspath("../target") -TAG_NAME = "uprotocol-core-api-1.5.6" +TAG_NAME = "uprotocol-core-api-1.5.7" PROTO_OUTPUT_DIR = os.path.abspath("../uprotocol/proto") @@ -44,7 +44,7 @@ def clone_or_pull(repo_url, PROTO_REPO_DIR): print(f"Repository cloned successfully from {repo_url} to {PROTO_REPO_DIR}") # Checkout the specific tag repo.git.checkout(TAG_NAME) - except git.exc.GitCommandError as clone_error: + except git.exc.GitCommandError: try: git_pull_command = ["git", "pull", "origin", TAG_NAME] subprocess.run(git_pull_command, cwd=PROTO_REPO_DIR, check=True) diff --git a/tests/test_cloudevent/test_datamodel/test_ucloudevent.py b/tests/test_cloudevent/test_datamodel/test_ucloudevent.py index 3d34001..e6f9696 100644 --- a/tests/test_cloudevent/test_datamodel/test_ucloudevent.py +++ b/tests/test_cloudevent/test_datamodel/test_ucloudevent.py @@ -37,19 +37,28 @@ from google.protobuf import any_pb2 -from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from uprotocol.cloudevent.datamodel.ucloudeventattributes import ( + UCloudEventAttributesBuilder, +) from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent + def build_uri_for_test(): - uri = UUri(entity=UEntity(name="body.access"), - resource=UResource(name="door", instance="front_left", message="Door")) + uri = UUri( + entity=UEntity(name="body.access"), + resource=UResource(name="door", instance="front_left", message="Door"), + ) return LongUriSerializer().serialize(uri) def build_proto_payload_for_test(): - ce_proto = CloudEvent(spec_version="1.0", source="//VCU.MY_CAR_VIN/body.access//door.front_left#Door", id="hello", - type="example.demo", - proto_data=any_pb2.Any()) + ce_proto = CloudEvent( + spec_version="1.0", + source="//VCU.MY_CAR_VIN/body.access//door.front_left#Door", + id="hello", + type="example.demo", + proto_data=any_pb2.Any(), + ) any_obj = any_pb2.Any() any_obj.Pack(ce_proto) @@ -60,14 +69,24 @@ def build_cloud_event_for_test(): source = build_uri_for_test() proto_payload = build_proto_payload_for_test() # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS1) + .with_ttl(3) + .with_token("someOAuthToken") + .build() + ) # build the cloud event - cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload.SerializeToString(), - proto_payload.type_url, u_cloud_event_attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event = CloudEventFactory.build_base_cloud_event( + "testme", + source, + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) return cloud_event @@ -94,11 +113,15 @@ def test_extract_requestId_from_cloudevent_when_requestId_exists(self): cloud_event.__setitem__("reqid", "somereqid") self.assertEqual("somereqid", UCloudEvent.get_request_id(cloud_event)) - def test_extract_requestId_from_cloudevent_when_requestId_does_not_exist(self): + def test_extract_requestId_from_cloudevent_when_requestId_does_not_exist( + self, + ): cloud_event = build_cloud_event_for_test() self.assertEqual(None, UCloudEvent.get_request_id(cloud_event)) - def test_extract_requestId_from_cloudevent_when_requestId_value_is_null(self): + def test_extract_requestId_from_cloudevent_when_requestId_value_is_null( + self, + ): cloud_event = build_cloud_event_for_test() cloud_event.__setitem__("reqid", None) self.assertEqual(None, UCloudEvent.get_request_id(cloud_event)) @@ -114,16 +137,23 @@ def test_extract_hash_from_cloudevent_when_hash_does_not_exist(self): def test_extract_priority_from_cloudevent_when_priority_exists(self): cloud_event = build_cloud_event_for_test() - self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS1), UCloudEvent.get_priority(cloud_event)) - - def test_extract_priority_from_cloudevent_when_priority_does_not_exist(self): + self.assertEqual( + UPriority.Name(UPriority.UPRIORITY_CS1), + UCloudEvent.get_priority(cloud_event), + ) + + def test_extract_priority_from_cloudevent_when_priority_does_not_exist( + self, + ): cloud_event = build_cloud_event_for_test() cloud_event.__delitem__("priority") self.assertEqual(None, UCloudEvent.get_priority(cloud_event)) def test_extract_ttl_from_cloudevent_when_ttl_exists(self): cloud_event = build_cloud_event_for_test() - self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) + self.assertEqual( + UCode.INVALID_ARGUMENT, UCloudEvent.get_ttl(cloud_event) + ) def test_extract_ttl_from_cloudevent_when_ttl_does_not_exists(self): cloud_event = build_cloud_event_for_test() @@ -142,70 +172,100 @@ def test_extract_token_from_cloudevent_when_token_does_not_exists(self): def test_cloudevent_has_platform_error_when_platform_error_exists(self): cloud_event = build_cloud_event_for_test() cloud_event.__setitem__("commstatus", UCode.ABORTED) - self.assertEqual(10, UCloudEvent.get_communication_status(cloud_event)) + self.assertEqual( + UCode.ABORTED, UCloudEvent.get_communication_status(cloud_event) + ) - def test_cloudevent_has_platform_error_when_platform_error_does_not_exist(self): + def test_cloudevent_has_platform_error_when_platform_error_does_not_exist( + self, + ): cloud_event = build_cloud_event_for_test() - self.assertEqual(UCode.OK, UCloudEvent.get_communication_status(cloud_event)) + self.assertEqual( + UCode.OK, UCloudEvent.get_communication_status(cloud_event) + ) - def test_extract_platform_error_from_cloudevent_when_platform_error_exists_in_wrong_format(self): + def test_extract_platform_error_from_cloudevent_when_platform_error_exists_in_wrong_format( + self, + ): cloud_event = build_cloud_event_for_test() cloud_event.__setitem__("commstatus", "boom") - self.assertEqual(UCode.OK, UCloudEvent.get_communication_status(cloud_event)) + self.assertEqual( + UCode.OK, UCloudEvent.get_communication_status(cloud_event) + ) def test_cloudevent_is_not_expired_cd_when_no_ttl_configured(self): cloud_event = build_cloud_event_for_test() cloud_event.__delitem__("ttl") - self.assertFalse(UCloudEvent.is_expired_by_cloud_event_creation_date(cloud_event)) + self.assertFalse( + UCloudEvent.is_expired_by_cloud_event_creation_date(cloud_event) + ) def test_cloudevent_is_not_expired_cd_when_ttl_is_zero(self): cloud_event = build_cloud_event_for_test() cloud_event.__setitem__("ttl", 0) - self.assertFalse(UCloudEvent.is_expired_by_cloud_event_creation_date(cloud_event)) + self.assertFalse( + UCloudEvent.is_expired_by_cloud_event_creation_date(cloud_event) + ) def test_cloudevent_is_not_expired_cd_when_ttl_is_minus_one(self): cloud_event = build_cloud_event_for_test() cloud_event.__setitem__("ttl", -1) - self.assertFalse(UCloudEvent.is_expired_by_cloud_event_creation_date(cloud_event)) + self.assertFalse( + UCloudEvent.is_expired_by_cloud_event_creation_date(cloud_event) + ) def test_cloudevent_is_expired_when_ttl_1_mili(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) cloud_event = build_cloud_event_for_test() cloud_event.__setitem__("ttl", 1) - cloud_event.__setitem__('id', str_uuid) + cloud_event.__setitem__("id", str_uuid) time.sleep(8) self.assertTrue(UCloudEvent.is_expired(cloud_event)) def test_cloudevent_is_expired_for_invalid_uuid(self): - uuid = Factories.UPROTOCOL.create() cloud_event = build_cloud_event_for_test() cloud_event.__setitem__("ttl", 50000) - cloud_event.__setitem__('id', "") + cloud_event.__setitem__("id", "") self.assertFalse(UCloudEvent.is_expired(cloud_event)) def test_cloudevent_has_a_UUIDV8_id(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) cloud_event = build_cloud_event_for_test() - cloud_event.__setitem__('id', str_uuid) + cloud_event.__setitem__("id", str_uuid) self.assertTrue(UCloudEvent.is_cloud_event_id(cloud_event)) def test_to_message_with_valid_event(self): # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_priority( - UPriority.UPRIORITY_CS2).with_ttl(3).build() - cloud_event = CloudEventFactory.publish(build_uri_for_test(), build_proto_payload_for_test(), - u_cloud_event_attributes) + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_priority(UPriority.UPRIORITY_CS2) + .with_ttl(3) + .build() + ) + cloud_event = CloudEventFactory.publish( + build_uri_for_test(), + build_proto_payload_for_test(), + u_cloud_event_attributes, + ) u_message = UCloudEvent.toMessage(cloud_event) self.assertIsNotNone(u_message) def test_from_message_with_valid_message(self): # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_priority( - UPriority.UPRIORITY_CS2).with_ttl(3).build() - cloud_event = CloudEventFactory.publish(build_uri_for_test(), build_proto_payload_for_test(), - u_cloud_event_attributes) + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_priority(UPriority.UPRIORITY_CS2) + .with_ttl(3) + .with_traceparent("someParent") + .build() + ) + cloud_event = CloudEventFactory.publish( + build_uri_for_test(), + build_proto_payload_for_test(), + u_cloud_event_attributes, + ) u_message = UCloudEvent.toMessage(cloud_event) self.assertIsNotNone(u_message) cloud_event1 = UCloudEvent.fromMessage(u_message) @@ -214,86 +274,187 @@ def test_from_message_with_valid_message(self): cloud_event1.__delitem__("time") self.assertEqual(cloud_event, cloud_event1) self.assertEqual(cloud_event.get_data(), cloud_event1.get_data()) - self.assertEqual(UCloudEvent.get_source(cloud_event), UCloudEvent.get_source(cloud_event1)) - self.assertEqual(UCloudEvent.get_specversion(cloud_event), UCloudEvent.get_specversion(cloud_event1)) - self.assertEqual(UCloudEvent.get_priority(cloud_event), UCloudEvent.get_priority(cloud_event1)) - self.assertEqual(UCloudEvent.get_id(cloud_event), UCloudEvent.get_id(cloud_event1)) - self.assertEqual(UCloudEvent.get_type(cloud_event), UCloudEvent.get_type(cloud_event1)) + self.assertEqual( + UCloudEvent.get_source(cloud_event), + UCloudEvent.get_source(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_specversion(cloud_event), + UCloudEvent.get_specversion(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_priority(cloud_event), + UCloudEvent.get_priority(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_id(cloud_event), UCloudEvent.get_id(cloud_event1) + ) + self.assertEqual( + UCloudEvent.get_type(cloud_event), + UCloudEvent.get_type(cloud_event1), + ) def test_to_from_message_from_request_cloudevent(self): # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_priority( - UPriority.UPRIORITY_CS2).with_ttl(3).with_token("someOAuthToken").build() - - cloud_event = CloudEventFactory.request(build_uri_for_test(), "//bo.cloud/petapp/1/rpc.response", - CloudEventFactory.generate_cloud_event_id(), - build_proto_payload_for_test(), - u_cloud_event_attributes) + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_priority(UPriority.UPRIORITY_CS2) + .with_ttl(3) + .with_token("someOAuthToken") + .build() + ) + + cloud_event = CloudEventFactory.request( + build_uri_for_test(), + "//bo.cloud/petapp/1/rpc.response", + CloudEventFactory.generate_cloud_event_id(), + build_proto_payload_for_test(), + u_cloud_event_attributes, + ) result = UCloudEvent.toMessage(cloud_event) self.assertIsNotNone(result) - self.assertEqual(UCloudEvent.get_ttl(cloud_event), result.attributes.ttl) - self.assertEqual(UCloudEvent.get_token(cloud_event), result.attributes.token) - self.assertEqual(UCloudEvent.get_sink(cloud_event), - LongUriSerializer().serialize(result.attributes.sink)) - self.assertEqual(UCloudEvent.get_payload(cloud_event).SerializeToString(), result.payload.value) - self.assertEqual(UCloudEvent.get_source(cloud_event), - LongUriSerializer().serialize(result.attributes.source)) - self.assertEqual(UCloudEvent.get_priority(cloud_event), - UPriority.Name(result.attributes.priority)) + self.assertEqual( + UCloudEvent.get_ttl(cloud_event), result.attributes.ttl + ) + self.assertEqual( + UCloudEvent.get_token(cloud_event), result.attributes.token + ) + self.assertEqual( + UCloudEvent.get_sink(cloud_event), + LongUriSerializer().serialize(result.attributes.sink), + ) + self.assertEqual( + UCloudEvent.get_payload(cloud_event).SerializeToString(), + result.payload.value, + ) + self.assertEqual( + UCloudEvent.get_source(cloud_event), + LongUriSerializer().serialize(result.attributes.source), + ) + self.assertEqual( + UCloudEvent.get_priority(cloud_event), + UPriority.Name(result.attributes.priority), + ) cloud_event1 = UCloudEvent.fromMessage(result) cloud_event.__delitem__("time") cloud_event1.__delitem__("time") self.assertEqual(cloud_event, cloud_event1) self.assertEqual(cloud_event.get_data(), cloud_event1.get_data()) - self.assertEqual(UCloudEvent.get_source(cloud_event), UCloudEvent.get_source(cloud_event1)) - self.assertEqual(UCloudEvent.get_sink(cloud_event), UCloudEvent.get_sink(cloud_event1)) - self.assertEqual(UCloudEvent.get_specversion(cloud_event), UCloudEvent.get_specversion(cloud_event1)) - self.assertEqual(UCloudEvent.get_priority(cloud_event), UCloudEvent.get_priority(cloud_event1)) - self.assertEqual(UCloudEvent.get_id(cloud_event), UCloudEvent.get_id(cloud_event1)) - self.assertEqual(UCloudEvent.get_type(cloud_event), UCloudEvent.get_type(cloud_event1)) - self.assertEqual(UCloudEvent.get_request_id(cloud_event), UCloudEvent.get_request_id(cloud_event1)) + self.assertEqual( + UCloudEvent.get_source(cloud_event), + UCloudEvent.get_source(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_sink(cloud_event), + UCloudEvent.get_sink(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_specversion(cloud_event), + UCloudEvent.get_specversion(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_priority(cloud_event), + UCloudEvent.get_priority(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_id(cloud_event), UCloudEvent.get_id(cloud_event1) + ) + self.assertEqual( + UCloudEvent.get_type(cloud_event), + UCloudEvent.get_type(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_request_id(cloud_event), + UCloudEvent.get_request_id(cloud_event1), + ) def test_to_from_message_from_request_cloudevent_without_attributes(self): # additional attributes u_cloud_event_attributes = UCloudEventAttributesBuilder().build() - cloud_event = CloudEventFactory.request(build_uri_for_test(), "//bo.cloud/petapp/1/rpc.response", - CloudEventFactory.generate_cloud_event_id(), - build_proto_payload_for_test(), - u_cloud_event_attributes) + cloud_event = CloudEventFactory.request( + build_uri_for_test(), + "//bo.cloud/petapp/1/rpc.response", + CloudEventFactory.generate_cloud_event_id(), + build_proto_payload_for_test(), + u_cloud_event_attributes, + ) result = UCloudEvent.toMessage(cloud_event) self.assertIsNotNone(result) - self.assertFalse(result.attributes.HasField('ttl')) - self.assertEqual(UCloudEvent.get_sink(cloud_event), - LongUriSerializer().serialize(result.attributes.sink)) - self.assertEqual(UCloudEvent.get_payload(cloud_event).SerializeToString(), result.payload.value) - self.assertEqual(UCloudEvent.get_source(cloud_event), LongUriSerializer().serialize(result.attributes.source)) + self.assertFalse(result.attributes.HasField("ttl")) + self.assertEqual( + UCloudEvent.get_sink(cloud_event), + LongUriSerializer().serialize(result.attributes.sink), + ) + self.assertEqual( + UCloudEvent.get_payload(cloud_event).SerializeToString(), + result.payload.value, + ) + self.assertEqual( + UCloudEvent.get_source(cloud_event), + LongUriSerializer().serialize(result.attributes.source), + ) self.assertEqual(result.attributes.priority, 0) cloud_event1 = UCloudEvent.fromMessage(result) self.assertEqual(cloud_event.get_data(), cloud_event1.get_data()) - self.assertEqual(UCloudEvent.get_source(cloud_event),UCloudEvent.get_source(cloud_event1)) - self.assertEqual(UCloudEvent.get_sink(cloud_event),UCloudEvent.get_sink(cloud_event1)) - self.assertEqual(UCloudEvent.get_specversion(cloud_event),UCloudEvent.get_specversion(cloud_event1)) - self.assertEqual(UCloudEvent.get_priority(cloud_event),UCloudEvent.get_priority(cloud_event1)) - self.assertEqual(UCloudEvent.get_id(cloud_event),UCloudEvent.get_id(cloud_event1)) - self.assertEqual(UCloudEvent.get_type(cloud_event),UCloudEvent.get_type(cloud_event1)) - self.assertEqual(UCloudEvent.get_request_id(cloud_event),UCloudEvent.get_request_id(cloud_event1)) + self.assertEqual( + UCloudEvent.get_source(cloud_event), + UCloudEvent.get_source(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_sink(cloud_event), + UCloudEvent.get_sink(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_specversion(cloud_event), + UCloudEvent.get_specversion(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_priority(cloud_event), + UCloudEvent.get_priority(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_id(cloud_event), UCloudEvent.get_id(cloud_event1) + ) + self.assertEqual( + UCloudEvent.get_type(cloud_event), + UCloudEvent.get_type(cloud_event1), + ) + self.assertEqual( + UCloudEvent.get_request_id(cloud_event), + UCloudEvent.get_request_id(cloud_event1), + ) def test_to_from_message_from_UCP_cloudevent(self): # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_ttl(3).with_token("someOAuthToken").build() - - cloud_event = CloudEventFactory.request(build_uri_for_test(), "//bo.cloud/petapp/1/rpc.response", - CloudEventFactory.generate_cloud_event_id(), - build_proto_payload_for_test(), - u_cloud_event_attributes) + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_ttl(3) + .with_token("someOAuthToken") + .build() + ) + + cloud_event = CloudEventFactory.request( + build_uri_for_test(), + "//bo.cloud/petapp/1/rpc.response", + CloudEventFactory.generate_cloud_event_id(), + build_proto_payload_for_test(), + u_cloud_event_attributes, + ) cloud_event.__setitem__("priority", "CS4") result = UCloudEvent.toMessage(cloud_event) self.assertIsNotNone(result) - self.assertEqual(UPriority.UPRIORITY_CS4,result.attributes.priority) + self.assertEqual(UPriority.UPRIORITY_CS4, result.attributes.priority) cloud_event1 = UCloudEvent.fromMessage(result) - self.assertEqual(UCloudEvent.get_priority(cloud_event1),UPriority.Name(result.attributes.priority)) - + self.assertEqual( + UCloudEvent.get_priority(cloud_event1), + UPriority.Name(result.attributes.priority), + ) + + def test_from_message_with_null_message(self): + with self.assertRaises(ValueError) as context: + UCloudEvent.fromMessage(None) + self.assertTrue("message cannot be null." in context.exception) diff --git a/tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py b/tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py index 1c16de9..d3d9355 100644 --- a/tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py +++ b/tests/test_cloudevent/test_datamodel/test_ucloudeventattributes.py @@ -41,6 +41,23 @@ def test_to_string(self): expected = "UCloudEventAttributes{hash='somehash', priority=UPRIORITY_CS1, ttl=3, token='someOAuthToken'}" self.assertEqual(expected, str(u_cloud_event_attributes)) + def test_create_valid_with_blank_traceparent(self): + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( + UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").with_traceparent(" ").build() + self.assertTrue(u_cloud_event_attributes.get_hash() is not None) + self.assertEqual("somehash", u_cloud_event_attributes.get_hash()) + self.assertFalse(u_cloud_event_attributes.get_traceparent() is not None) + + def test_create_empty_with_only_traceparent(self): + u_cloud_event_attributes = UCloudEventAttributesBuilder().with_traceparent("someTraceParent").build() + self.assertFalse(u_cloud_event_attributes.get_hash() is not None) + self.assertFalse(u_cloud_event_attributes.get_priority() is not None) + self.assertFalse(u_cloud_event_attributes.get_token() is not None) + self.assertFalse(u_cloud_event_attributes.get_ttl() is not None) + self.assertTrue(u_cloud_event_attributes.get_traceparent() is not None) + self.assertFalse(u_cloud_event_attributes.is_empty()) + self.assertEqual("someTraceParent", u_cloud_event_attributes.get_traceparent()) + def test_create_valid(self): u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( UPriority.UPRIORITY_CS6).with_ttl(3).with_token("someOAuthToken").build() diff --git a/tests/test_cloudevent/test_factory/test_cloudeventfactory.py b/tests/test_cloudevent/test_factory/test_cloudeventfactory.py index 448d8e0..09a9a32 100644 --- a/tests/test_cloudevent/test_factory/test_cloudeventfactory.py +++ b/tests/test_cloudevent/test_factory/test_cloudeventfactory.py @@ -29,12 +29,18 @@ import json import os from google.protobuf import any_pb2 -from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder, \ - UCloudEventAttributes +from uprotocol.cloudevent.datamodel.ucloudeventattributes import ( + UCloudEventAttributesBuilder, + UCloudEventAttributes, +) from uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer -from uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer +from uprotocol.cloudevent.serialize.base64protobufserializer import ( + Base64ProtobufSerializer, +) +from uprotocol.cloudevent.serialize.cloudeventtojsonserializer import ( + CloudEventToJsonSerializer, +) from uprotocol.cloudevent.cloudevents_pb2 import CloudEvent from uprotocol.proto.uattributes_pb2 import UMessageType, UPriority from uprotocol.proto.uri_pb2 import UUri, UEntity, UResource @@ -45,23 +51,36 @@ def get_json_object(): current_directory = os.getcwd() - json_file_path = os.path.join(current_directory, "tests","test_cloudevent","test_factory","cloudevent.json") - - with open(json_file_path, 'r') as json_file: + json_file_path = os.path.join( + current_directory, + "tests", + "test_cloudevent", + "test_factory", + "cloudevent.json", + ) + + with open(json_file_path, "r") as json_file: json_data = json.load(json_file) return json_data def build_uri_for_test(): - uri = UUri(entity=UEntity(name="body.access"), - resource=UResource(name="door", instance="front_left", message="Door")) + uri = UUri( + entity=UEntity(name="body.access"), + resource=UResource(name="door", instance="front_left", message="Door"), + ) return LongUriSerializer().serialize(uri) def build_proto_payload_for_test(): - ce_proto = CloudEvent(spec_version="1.0", source="https://example.com", id="hello", type="example.demo", - proto_data=any_pb2.Any()) + ce_proto = CloudEvent( + spec_version="1.0", + source="https://example.com", + id="hello", + type="example.demo", + proto_data=any_pb2.Any(), + ) any_obj = any_pb2.Any() any_obj.Pack(ce_proto) @@ -74,30 +93,57 @@ class TestCloudEventFactory(unittest.TestCase): def test_all_cloud_events_from_json(self): cloudevents = get_json_object() for ce_json in cloudevents: - bytes_ce = Base64ProtobufSerializer().serialize(ce_json['serialized_ce']) + bytes_ce = Base64ProtobufSerializer().serialize( + ce_json["serialized_ce"] + ) cloudevent = CloudEventToJsonSerializer().deserialize(bytes_ce) - self.assertEqual(UCloudEvent.get_id(cloudevent), ce_json['id']) - self.assertEqual(UCloudEvent.get_specversion(cloudevent), ce_json['specversion']) - if 'source' in ce_json: - self.assertEqual(UCloudEvent.get_source(cloudevent), ce_json['source']) - if 'sink' in ce_json: - self.assertEqual(UCloudEvent.get_sink(cloudevent), ce_json['sink']) - if 'type' in ce_json: - self.assertEqual(UCloudEvent.get_type(cloudevent), ce_json['type']) - if 'priority' in ce_json: - self.assertEqual(UCloudEvent.get_priority(cloudevent), ce_json['priority']) - if 'ttl' in ce_json: - self.assertEqual(UCloudEvent.get_ttl(cloudevent), ce_json['ttl']) - if 'hash' in ce_json: - self.assertEqual(UCloudEvent.get_hash(cloudevent), ce_json['hash']) - if 'token' in ce_json: - self.assertEqual(UCloudEvent.get_token(cloudevent), ce_json['token']) - if 'dataschema' in ce_json: - self.assertEqual(UCloudEvent.get_data_schema(cloudevent), ce_json['dataschema']) - if 'datacontenttype' in ce_json: - self.assertEqual(UCloudEvent.get_data_content_type(cloudevent), ce_json['datacontenttype']) - if 'commstatus' in ce_json: - self.assertEqual(UCloudEvent.get_communication_status(cloudevent), ce_json['commstatus']) + self.assertEqual(UCloudEvent.get_id(cloudevent), ce_json["id"]) + self.assertEqual( + UCloudEvent.get_specversion(cloudevent), ce_json["specversion"] + ) + if "source" in ce_json: + self.assertEqual( + UCloudEvent.get_source(cloudevent), ce_json["source"] + ) + if "sink" in ce_json: + self.assertEqual( + UCloudEvent.get_sink(cloudevent), ce_json["sink"] + ) + if "type" in ce_json: + self.assertEqual( + UCloudEvent.get_type(cloudevent), ce_json["type"] + ) + if "priority" in ce_json: + self.assertEqual( + UCloudEvent.get_priority(cloudevent), ce_json["priority"] + ) + if "ttl" in ce_json: + self.assertEqual( + UCloudEvent.get_ttl(cloudevent), ce_json["ttl"] + ) + if "hash" in ce_json: + self.assertEqual( + UCloudEvent.get_hash(cloudevent), ce_json["hash"] + ) + if "token" in ce_json: + self.assertEqual( + UCloudEvent.get_token(cloudevent), ce_json["token"] + ) + if "dataschema" in ce_json: + self.assertEqual( + UCloudEvent.get_data_schema(cloudevent), + ce_json["dataschema"], + ) + if "datacontenttype" in ce_json: + self.assertEqual( + UCloudEvent.get_data_content_type(cloudevent), + ce_json["datacontenttype"], + ) + if "commstatus" in ce_json: + self.assertEqual( + UCloudEvent.get_communication_status(cloudevent), + ce_json["commstatus"], + ) def test_create_base_cloud_event(self): source = build_uri_for_test() @@ -106,25 +152,44 @@ def test_create_base_cloud_event(self): proto_payload = build_proto_payload_for_test() # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS1) + .with_ttl(3) + .with_token("someOAuthToken") + .with_traceparent("sometraceparent") + .build() + ) # build the cloud event - cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload.SerializeToString(), - proto_payload.type_url, u_cloud_event_attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event = CloudEventFactory.build_base_cloud_event( + "testme", + source, + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) self.assertEqual("testme", UCloudEvent.get_id(cloud_event)) self.assertEqual(source, UCloudEvent.get_source(cloud_event)) - self.assertEqual('pub.v1', UCloudEvent.get_type(cloud_event)) + self.assertEqual("pub.v1", UCloudEvent.get_type(cloud_event)) self.assertNotIn("sink", cloud_event.get_attributes()) self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) - self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS1), UCloudEvent.get_priority(cloud_event)) + self.assertEqual( + UPriority.Name(UPriority.UPRIORITY_CS1), + UCloudEvent.get_priority(cloud_event), + ) self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) self.assertEqual("someOAuthToken", UCloudEvent.get_token(cloud_event)) - self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + self.assertEqual( + "sometraceparent", UCloudEvent.get_traceparent(cloud_event) + ) + self.assertEqual( + proto_payload.SerializeToString(), cloud_event.get_data() + ) def test_create_base_cloud_event_with_datacontenttype_and_schema(self): source = build_uri_for_test() @@ -133,32 +198,56 @@ def test_create_base_cloud_event_with_datacontenttype_and_schema(self): proto_payload = build_proto_payload_for_test() # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS1) + .with_ttl(3) + .with_token("someOAuthToken") + .build() + ) # build the cloud event - cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload.SerializeToString(), - proto_payload.type_url, u_cloud_event_attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) - - cloud_event.__setitem__('datacontenttype', CloudEventFactory.PROTOBUF_CONTENT_TYPE) - cloud_event.__setitem__('dataschema', proto_payload.type_url) + cloud_event = CloudEventFactory.build_base_cloud_event( + "testme", + source, + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) + + cloud_event.__setitem__( + "datacontenttype", CloudEventFactory.PROTOBUF_CONTENT_TYPE + ) + cloud_event.__setitem__("dataschema", proto_payload.type_url) # test all attributes self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) self.assertEqual("testme", UCloudEvent.get_id(cloud_event)) self.assertEqual(source, UCloudEvent.get_source(cloud_event)) - self.assertEqual(UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), - UCloudEvent.get_type(cloud_event)) - self.assertEqual(self.DATA_CONTENT_TYPE, UCloudEvent.get_data_content_type(cloud_event)) - self.assertEqual(proto_payload.type_url, UCloudEvent.get_data_schema(cloud_event)) + self.assertEqual( + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + UCloudEvent.get_type(cloud_event), + ) + self.assertEqual( + self.DATA_CONTENT_TYPE, + UCloudEvent.get_data_content_type(cloud_event), + ) + self.assertEqual( + proto_payload.type_url, UCloudEvent.get_data_schema(cloud_event) + ) self.assertNotIn("sink", cloud_event.get_attributes()) self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) - self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS1), UCloudEvent.get_priority(cloud_event)) + self.assertEqual( + UPriority.Name(UPriority.UPRIORITY_CS1), + UCloudEvent.get_priority(cloud_event), + ) self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) self.assertEqual("someOAuthToken", UCloudEvent.get_token(cloud_event)) - self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + self.assertEqual( + proto_payload.SerializeToString(), cloud_event.get_data() + ) def test_create_base_cloud_event_without_attributes(self): source = build_uri_for_test() @@ -171,22 +260,30 @@ def test_create_base_cloud_event_without_attributes(self): # build the cloud event # build the cloud event - cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload.SerializeToString(), - proto_payload.type_url, u_cloud_event_attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event = CloudEventFactory.build_base_cloud_event( + "testme", + source, + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) # test all attributes self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) self.assertEqual("testme", UCloudEvent.get_id(cloud_event)) self.assertEqual(source, UCloudEvent.get_source(cloud_event)) - self.assertEqual(UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), - UCloudEvent.get_type(cloud_event)) + self.assertEqual( + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + UCloudEvent.get_type(cloud_event), + ) self.assertNotIn("sink", cloud_event.get_attributes()) self.assertNotIn("hash", cloud_event.get_attributes()) self.assertNotIn("priority", cloud_event.get_attributes()) self.assertNotIn("ttl", cloud_event.get_attributes()) - self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + self.assertEqual( + proto_payload.SerializeToString(), cloud_event.get_data() + ) def test_create_publish_cloud_event(self): # source @@ -196,23 +293,37 @@ def test_create_publish_cloud_event(self): proto_payload = build_proto_payload_for_test() # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS1).with_ttl(3).build() - - cloud_event = CloudEventFactory.publish(source, proto_payload, u_cloud_event_attributes) + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS1) + .with_ttl(3) + .build() + ) + + cloud_event = CloudEventFactory.publish( + source, proto_payload, u_cloud_event_attributes + ) # test all attributes self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) self.assertIsNotNone(UCloudEvent.get_id(cloud_event)) self.assertEqual(source, UCloudEvent.get_source(cloud_event)) - self.assertEqual(UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), - UCloudEvent.get_type(cloud_event)) + self.assertEqual( + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + UCloudEvent.get_type(cloud_event), + ) self.assertNotIn("sink", cloud_event.get_attributes()) self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) - self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS1), UCloudEvent.get_priority(cloud_event)) + self.assertEqual( + UPriority.Name(UPriority.UPRIORITY_CS1), + UCloudEvent.get_priority(cloud_event), + ) self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) - self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + self.assertEqual( + proto_payload.SerializeToString(), cloud_event.get_data() + ) def test_create_notification_cloud_event(self): # source @@ -226,11 +337,18 @@ def test_create_notification_cloud_event(self): # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS2).with_ttl(3).build() + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS2) + .with_ttl(3) + .build() + ) # build the cloud event of type publish with destination - a notification - cloud_event = CloudEventFactory.notification(source, sink, proto_payload, u_cloud_event_attributes) + cloud_event = CloudEventFactory.notification( + source, sink, proto_payload, u_cloud_event_attributes + ) # test all attributes self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) @@ -240,13 +358,20 @@ def test_create_notification_cloud_event(self): self.assertIn("sink", cloud_event.get_attributes()) self.assertEqual(sink, UCloudEvent.get_sink(cloud_event)) - self.assertEqual(UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), - UCloudEvent.get_type(cloud_event)) + self.assertEqual( + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + UCloudEvent.get_type(cloud_event), + ) self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) - self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS2), UCloudEvent.get_priority(cloud_event)) + self.assertEqual( + UPriority.Name(UPriority.UPRIORITY_CS2), + UCloudEvent.get_priority(cloud_event), + ) self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) - self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + self.assertEqual( + proto_payload.SerializeToString(), cloud_event.get_data() + ) def test_create_request_cloud_event_from_local_use(self): # UriPart for the application requesting the RPC @@ -260,28 +385,45 @@ def test_create_request_cloud_event_from_local_use(self): # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS2).with_ttl(3).with_token("someOAuthToken").build() - - cloud_event = CloudEventFactory.request(application_uri_for_rpc, service_method_uri, - CloudEventFactory.generate_cloud_event_id(), proto_payload, - u_cloud_event_attributes) + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS2) + .with_ttl(3) + .with_token("someOAuthToken") + .build() + ) + + cloud_event = CloudEventFactory.request( + application_uri_for_rpc, + service_method_uri, + CloudEventFactory.generate_cloud_event_id(), + proto_payload, + u_cloud_event_attributes, + ) # test all attributes self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) self.assertIsNotNone(UCloudEvent.get_id(cloud_event)) - self.assertEqual(application_uri_for_rpc, UCloudEvent.get_source(cloud_event)) + self.assertEqual( + application_uri_for_rpc, UCloudEvent.get_source(cloud_event) + ) self.assertIn("sink", cloud_event.get_attributes()) self.assertEqual(service_method_uri, UCloudEvent.get_sink(cloud_event)) self.assertEqual("req.v1", UCloudEvent.get_type(cloud_event)) self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) - self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS2), UCloudEvent.get_priority(cloud_event)) + self.assertEqual( + UPriority.Name(UPriority.UPRIORITY_CS2), + UCloudEvent.get_priority(cloud_event), + ) self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) self.assertEqual("someOAuthToken", UCloudEvent.get_token(cloud_event)) - self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + self.assertEqual( + proto_payload.SerializeToString(), cloud_event.get_data() + ) def test_create_response_cloud_event_originating_from_local_use(self): # UriPart for the application requesting the RPC @@ -295,32 +437,55 @@ def test_create_response_cloud_event_originating_from_local_use(self): # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS2).with_ttl(3).build() - - cloud_event = CloudEventFactory.response(application_uri_for_rpc, service_method_uri, - "requestIdFromRequestCloudEvent", proto_payload, - u_cloud_event_attributes) + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS2) + .with_ttl(3) + .build() + ) + + cloud_event = CloudEventFactory.response( + application_uri_for_rpc, + service_method_uri, + "requestIdFromRequestCloudEvent", + proto_payload, + u_cloud_event_attributes, + ) # test all attributes self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) self.assertIsNotNone(UCloudEvent.get_id(cloud_event)) - self.assertEqual(service_method_uri, UCloudEvent.get_source(cloud_event)) + self.assertEqual( + service_method_uri, UCloudEvent.get_source(cloud_event) + ) self.assertIn("sink", cloud_event.get_attributes()) - self.assertEqual(application_uri_for_rpc, UCloudEvent.get_sink(cloud_event)) + self.assertEqual( + application_uri_for_rpc, UCloudEvent.get_sink(cloud_event) + ) self.assertEqual("res.v1", UCloudEvent.get_type(cloud_event)) self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) - self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS2), UCloudEvent.get_priority(cloud_event)) + self.assertEqual( + UPriority.Name(UPriority.UPRIORITY_CS2), + UCloudEvent.get_priority(cloud_event), + ) self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) - self.assertEqual("requestIdFromRequestCloudEvent", UCloudEvent.get_request_id(cloud_event)) + self.assertEqual( + "requestIdFromRequestCloudEvent", + UCloudEvent.get_request_id(cloud_event), + ) # Use assertEqual to compare byte arrays - self.assertEqual(proto_payload.SerializeToString(), cloud_event.get_data()) + self.assertEqual( + proto_payload.SerializeToString(), cloud_event.get_data() + ) - def test_create_failed_response_cloud_event_originating_from_local_use(self): + def test_create_failed_response_cloud_event_originating_from_local_use( + self, + ): # UriPart for the application requesting the RPC application_uri_for_rpc = build_uri_for_test() @@ -329,30 +494,54 @@ def test_create_failed_response_cloud_event_originating_from_local_use(self): # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS2).with_ttl(3).build() - - cloud_event = CloudEventFactory.failed_response(application_uri_for_rpc, service_method_uri, - "requestIdFromRequestCloudEvent", UCode.INVALID_ARGUMENT, - u_cloud_event_attributes) + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS2) + .with_ttl(3) + .build() + ) + + cloud_event = CloudEventFactory.failed_response( + application_uri_for_rpc, + service_method_uri, + "requestIdFromRequestCloudEvent", + UCode.INVALID_ARGUMENT, + u_cloud_event_attributes, + ) # test all attributes self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) self.assertIsNotNone(UCloudEvent.get_id(cloud_event)) - self.assertEqual(service_method_uri, UCloudEvent.get_source(cloud_event)) + self.assertEqual( + service_method_uri, UCloudEvent.get_source(cloud_event) + ) self.assertIn("sink", cloud_event.get_attributes()) - self.assertEqual(application_uri_for_rpc, UCloudEvent.get_sink(cloud_event)) + self.assertEqual( + application_uri_for_rpc, UCloudEvent.get_sink(cloud_event) + ) self.assertEqual("res.v1", UCloudEvent.get_type(cloud_event)) self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) - self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS2), UCloudEvent.get_priority(cloud_event)) + self.assertEqual( + UPriority.Name(UPriority.UPRIORITY_CS2), + UCloudEvent.get_priority(cloud_event), + ) self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) - self.assertEqual(UCode.INVALID_ARGUMENT, UCloudEvent.get_communication_status(cloud_event)) - - self.assertEqual("requestIdFromRequestCloudEvent", UCloudEvent.get_request_id(cloud_event)) - - def test_create_failed_response_cloud_event_originating_from_remote_use(self): + self.assertEqual( + UCode.INVALID_ARGUMENT, + UCloudEvent.get_communication_status(cloud_event), + ) + + self.assertEqual( + "requestIdFromRequestCloudEvent", + UCloudEvent.get_request_id(cloud_event), + ) + + def test_create_failed_response_cloud_event_originating_from_remote_use( + self, + ): # UriPart for the application requesting the RPC application_uri_for_rpc = build_uri_for_test() @@ -361,29 +550,51 @@ def test_create_failed_response_cloud_event_originating_from_remote_use(self): # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS2).with_ttl(3).build() - - cloud_event = CloudEventFactory.failed_response(application_uri_for_rpc, service_method_uri, - "requestIdFromRequestCloudEvent", UCode.INVALID_ARGUMENT, - u_cloud_event_attributes) + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS2) + .with_ttl(3) + .build() + ) + + cloud_event = CloudEventFactory.failed_response( + application_uri_for_rpc, + service_method_uri, + "requestIdFromRequestCloudEvent", + UCode.INVALID_ARGUMENT, + u_cloud_event_attributes, + ) # test all attributes self.assertEqual("1.0", UCloudEvent.get_specversion(cloud_event)) self.assertIsNotNone(UCloudEvent.get_id(cloud_event)) - self.assertEqual(service_method_uri, UCloudEvent.get_source(cloud_event)) + self.assertEqual( + service_method_uri, UCloudEvent.get_source(cloud_event) + ) self.assertIn("sink", cloud_event.get_attributes()) - self.assertEqual(application_uri_for_rpc, UCloudEvent.get_sink(cloud_event)) + self.assertEqual( + application_uri_for_rpc, UCloudEvent.get_sink(cloud_event) + ) self.assertEqual("res.v1", UCloudEvent.get_type(cloud_event)) self.assertEqual("somehash", UCloudEvent.get_hash(cloud_event)) - self.assertEqual(UPriority.Name(UPriority.UPRIORITY_CS2), UCloudEvent.get_priority(cloud_event)) + self.assertEqual( + UPriority.Name(UPriority.UPRIORITY_CS2), + UCloudEvent.get_priority(cloud_event), + ) self.assertEqual(3, UCloudEvent.get_ttl(cloud_event)) - self.assertEqual(UCode.INVALID_ARGUMENT, UCloudEvent.get_communication_status(cloud_event)) + self.assertEqual( + UCode.INVALID_ARGUMENT, + UCloudEvent.get_communication_status(cloud_event), + ) - self.assertEqual("requestIdFromRequestCloudEvent", UCloudEvent.get_request_id(cloud_event)) + self.assertEqual( + "requestIdFromRequestCloudEvent", + UCloudEvent.get_request_id(cloud_event), + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_cloudevent/test_serialize/test_base64protobufserializer.py b/tests/test_cloudevent/test_serialize/test_base64protobufserializer.py index 3148ab9..db8afbb 100644 --- a/tests/test_cloudevent/test_serialize/test_base64protobufserializer.py +++ b/tests/test_cloudevent/test_serialize/test_base64protobufserializer.py @@ -27,23 +27,36 @@ import unittest -from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from uprotocol.cloudevent.datamodel.ucloudeventattributes import ( + UCloudEventAttributesBuilder, +) from uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory -from uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer -from uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers +from uprotocol.cloudevent.serialize.base64protobufserializer import ( + Base64ProtobufSerializer, +) +from uprotocol.cloudevent.serialize.cloudeventserializers import ( + CloudEventSerializers, +) class TestBase64ProtobufSerializer(unittest.TestCase): def test_deserialize_bytes_to_string(self): - ce = CloudEventFactory.build_base_cloud_event("hello", "http://localhost", bytearray(), "", - UCloudEventAttributesBuilder().build(), "example.vertx") + ce = CloudEventFactory.build_base_cloud_event( + "hello", + "http://localhost", + bytearray(), + "", + UCloudEventAttributesBuilder().build(), + "example.vertx", + ) ce.__delitem__("time") bytes_data = CloudEventSerializers.PROTOBUF.serializer().serialize(ce) payload = Base64ProtobufSerializer().deserialize(bytes_data) self.assertEqual( "CgVoZWxsbxIQaHR0cDovL2xvY2FsaG9zdBoDMS4wIg1leGFtcGxlLnZlcnR4", - payload) + payload, + ) def test_deserialize_bytes_to_string_when_bytes_is_null(self): payload = Base64ProtobufSerializer().deserialize(None) @@ -54,11 +67,20 @@ def test_deserialize_bytes_to_string_when_bytes_is_empty(self): self.assertEqual("", payload) def test_serialize_string_into_bytes(self): - json_str = "eyJzcGVjdmVyc2lvbiI6ICIxLjAiLCAiaWQiOiAiaGVsbG8iLCAic291cmNlIjogImh0dHA6Ly9sb2NhbGhvc3QiLCAidHlwZSI6ICJleGFtcGxlLnZlcnR4IiwgImRhdGFfYmFzZTY0IjogIiJ9" + json_str = ( + "eyJzcGVjdmVyc2lvbiI6ICIxLjAiLCAiaWQiOiAiaGVsbG8iLCAic2" + + "91cmNlIjogImh0dHA6Ly9sb2NhbGhvc3QiLCAidHlwZSI6ICJleGFtcGxlLnZlcnR4IiwgImRhdGFfYmFzZTY0IjogIiJ9" + ) bytes_json = Base64ProtobufSerializer().serialize(json_str) - ce = CloudEventFactory.build_base_cloud_event("hello", "http://localhost", bytearray(), "", - UCloudEventAttributesBuilder().build(), "example.vertx") + ce = CloudEventFactory.build_base_cloud_event( + "hello", + "http://localhost", + bytearray(), + "", + UCloudEventAttributesBuilder().build(), + "example.vertx", + ) ce.__delitem__("time") bytes_data = CloudEventSerializers.JSON.serializer().serialize(ce) @@ -69,9 +91,9 @@ def test_serialize_string_into_bytes_when_string_is_null(self): self.assertEqual(bytearray(), bytes_data) def test_serialize_string_into_bytes_when_string_is_empty(self): - bytes_data = Base64ProtobufSerializer().serialize('') + bytes_data = Base64ProtobufSerializer().serialize("") self.assertEqual(bytearray(), bytes_data) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py b/tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py index 57cefcf..09e8242 100644 --- a/tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py +++ b/tests/test_cloudevent/test_serialize/test_cloudeventtojsonserializer.py @@ -29,11 +29,14 @@ from google.protobuf import any_pb2 -from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from uprotocol.cloudevent.datamodel.ucloudeventattributes import ( + UCloudEventAttributesBuilder, +) from uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers -from uprotocol.cloudevent.serialize.cloudeventtojsonserializer import CloudEventToJsonSerializer +from uprotocol.cloudevent.serialize.cloudeventserializers import ( + CloudEventSerializers, +) from uprotocol.cloudevent.cloudevents_pb2 import CloudEvent from uprotocol.proto.uattributes_pb2 import UPriority, UMessageType @@ -42,9 +45,14 @@ def build_proto_payload_for_test(): - ce_proto = CloudEvent(spec_version="1.0", source="https://example.com", id="hello", type="example.demo", - proto_data=any_pb2.Any(), - attributes={"ttl": CloudEvent.CloudEventAttributeValue(ce_string="3")}) + ce_proto = CloudEvent( + spec_version="1.0", + source="https://example.com", + id="hello", + type="example.demo", + proto_data=any_pb2.Any(), + attributes={"ttl": CloudEvent.CloudEventAttributeValue(ce_string="3")}, + ) any_obj = any_pb2.Any() any_obj.Pack(ce_proto) @@ -56,40 +64,58 @@ class TestCloudEventToJsonSerializer(unittest.TestCase): def test_serialize_cloud_event_to_json(self): proto_payload = build_proto_payload_for_test() # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_priority( - UPriority.UPRIORITY_CS1).with_ttl(3).build() + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_priority(UPriority.UPRIORITY_CS1) + .with_ttl(3) + .build() + ) # build the cloud event - cloud_event = CloudEventFactory.build_base_cloud_event("hello", "/body.access/1/door.front_left", - proto_payload.SerializeToString(), - proto_payload.type_url, u_cloud_event_attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) - cloud_event.__setitem__('datacontenttype', protoContentType) - cloud_event.__setitem__('dataschema', proto_payload.type_url) + cloud_event = CloudEventFactory.build_base_cloud_event( + "hello", + "/body.access/1/door.front_left", + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) + cloud_event.__setitem__("datacontenttype", protoContentType) + cloud_event.__setitem__("dataschema", proto_payload.type_url) cloud_event.__delitem__("time") bytes_data = serializer.serialize(cloud_event) - json_str = bytes_data.decode('utf-8') - expected = ('{"specversion": "1.0", "id": "hello", "source": "/body.access/1/door.front_left", ' - '"type": "pub.v1", "datacontenttype": "application/x-protobuf", "dataschema": ' - '"type.googleapis.com/io.cloudevents.v1.CloudEvent", "data_base64": ' - '"CjB0eXBlLmdvb2dsZWFwaXMuY29tL2lvLmNsb3VkZXZlbnRzLnYxLkNsb3VkRXZlbnQSPQoFaGVsbG8SE2h0dHBzOi8vZXhhbXBsZS5jb20aAzEuMCIMZXhhbXBsZS5kZW1vKgoKA3R0bBIDGgEzQgA=", "ttl": 3, "priority": "UPRIORITY_CS1"}') + json_str = bytes_data.decode("utf-8") + expected = ( + '{"specversion": "1.0", "id": "hello", "source": "/body.access/1/door.front_left", ' + '"type": "pub.v1", "datacontenttype": "application/x-protobuf", "dataschema": ' + '"type.googleapis.com/io.cloudevents.v1.CloudEvent", "data_base64": ' + '"CjB0eXBlLmdvb2dsZWFwaXMuY29tL2lvLmNsb3VkZXZlbnRzLnYxLkNsb3VkRXZlbnQSPQoFaGVs' + 'bG8SE2h0dHBzOi8vZXhhbXBsZS5jb20aAzEuMCIMZXhhbXBsZS5kZW1vKgoKA3R0bBIDGgEzQgA=", ' + '"ttl": 3, "priority": "UPRIORITY_CS1"}' + ) self.assertEqual(expected, json_str) def test_serialize_and_deserialize_cloud_event_to_json(self): proto_payload = build_proto_payload_for_test() # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_priority( - UPriority.UPRIORITY_CS1).with_ttl(3).build() + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_priority(UPriority.UPRIORITY_CS1) + .with_ttl(3) + .build() + ) # build the cloud event - cloud_event = CloudEventFactory.build_base_cloud_event("hello", "/body.access/1/door.front_left", - proto_payload.SerializeToString(), - proto_payload.type_url, u_cloud_event_attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) - cloud_event.__setitem__('datacontenttype', protoContentType) - cloud_event.__setitem__('dataschema', proto_payload.type_url) + cloud_event = CloudEventFactory.build_base_cloud_event( + "hello", + "/body.access/1/door.front_left", + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) + cloud_event.__setitem__("datacontenttype", protoContentType) + cloud_event.__setitem__("dataschema", proto_payload.type_url) cloud_event.__delitem__("time") serialized_data = serializer.serialize(cloud_event) deserialized_data = serializer.deserialize(serialized_data) @@ -97,20 +123,29 @@ def test_serialize_and_deserialize_cloud_event_to_json(self): self.assertEqual(cloud_event, deserialized_data) - def test_double_serialization_protobuf_when_creating_cloud_event_with_factory_methods(self): + def test_double_serialization_protobuf_when_creating_cloud_event_with_factory_methods( + self, + ): proto_payload = build_proto_payload_for_test() # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_priority( - UPriority.UPRIORITY_CS1).with_ttl(3).build() + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_priority(UPriority.UPRIORITY_CS1) + .with_ttl(3) + .build() + ) # build the cloud event - cloud_event1 = CloudEventFactory.build_base_cloud_event("hello", "/body.access/1/door.front_left", - proto_payload.SerializeToString(), - proto_payload.type_url, u_cloud_event_attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) - cloud_event1.__setitem__('datacontenttype', protoContentType) - cloud_event1.__setitem__('dataschema', proto_payload.type_url) + cloud_event1 = CloudEventFactory.build_base_cloud_event( + "hello", + "/body.access/1/door.front_left", + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) + cloud_event1.__setitem__("datacontenttype", protoContentType) + cloud_event1.__setitem__("dataschema", proto_payload.type_url) cloud_event1.__delitem__("time") serialized_data1 = serializer.serialize(cloud_event1) cloud_event2 = serializer.deserialize(serialized_data1) diff --git a/tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py b/tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py index b44522c..154f30a 100644 --- a/tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py +++ b/tests/test_cloudevent/test_serialize/test_cloudeventtoprotobufserializer.py @@ -30,12 +30,20 @@ from cloudevents.http import CloudEvent as ApacheCloudEvent from google.protobuf import any_pb2 -from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from uprotocol.cloudevent.datamodel.ucloudeventattributes import ( + UCloudEventAttributesBuilder, +) from uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from uprotocol.cloudevent.serialize.base64protobufserializer import Base64ProtobufSerializer -from uprotocol.cloudevent.serialize.cloudeventserializers import CloudEventSerializers -from uprotocol.cloudevent.serialize.cloudeventtoprotobufserializer import CloudEventToProtobufSerializer +from uprotocol.cloudevent.serialize.base64protobufserializer import ( + Base64ProtobufSerializer, +) +from uprotocol.cloudevent.serialize.cloudeventserializers import ( + CloudEventSerializers, +) +from uprotocol.cloudevent.serialize.cloudeventtoprotobufserializer import ( + CloudEventToProtobufSerializer, +) from uprotocol.cloudevent.cloudevents_pb2 import CloudEvent from uprotocol.proto.uattributes_pb2 import UPriority, UMessageType from uprotocol.proto.uri_pb2 import UUri, UEntity, UResource @@ -45,15 +53,22 @@ def build_uuri_for_test(): - uri = UUri(entity=UEntity(name="body.access"), - resource=UResource(name="door", instance="front_left", message="Door")) + uri = UUri( + entity=UEntity(name="body.access"), + resource=UResource(name="door", instance="front_left", message="Door"), + ) return LongUriSerializer().serialize(uri) def build_proto_payload_for_test(): - ce_proto = CloudEvent(spec_version="1.0", source="https://example.com", id="hello", type="example.demo", - proto_data=any_pb2.Any(), - attributes={"ttl": CloudEvent.CloudEventAttributeValue(ce_string="3")}) + ce_proto = CloudEvent( + spec_version="1.0", + source="https://example.com", + id="hello", + type="example.demo", + proto_data=any_pb2.Any(), + attributes={"ttl": CloudEvent.CloudEventAttributeValue(ce_string="3")}, + ) any_obj = any_pb2.Any() any_obj.Pack(ce_proto) @@ -62,9 +77,14 @@ def build_proto_payload_for_test(): def get_json_object(): current_directory = os.getcwd() - json_file_path = os.path.join(current_directory, "tests","test_cloudevent", - "test_serialize","cloudevent_to_protobuf.json") - with open(json_file_path, 'r') as json_file: + json_file_path = os.path.join( + current_directory, + "tests", + "test_cloudevent", + "test_serialize", + "cloudevent_to_protobuf.json", + ) + with open(json_file_path, "r") as json_file: json_data = json.load(json_file) return json_data @@ -75,44 +95,78 @@ class TestCloudEventToProtobufSerializer(unittest.TestCase): def test_all_cloud_events_from_json(self): cloudevents = get_json_object() for ce_json in cloudevents: - bytes_ce = Base64ProtobufSerializer().serialize(ce_json['serialized_ce']) + bytes_ce = Base64ProtobufSerializer().serialize( + ce_json["serialized_ce"] + ) cloudevent = CloudEventToProtobufSerializer().deserialize(bytes_ce) - self.assertEqual(UCloudEvent.get_id(cloudevent), ce_json['id']) - self.assertEqual(UCloudEvent.get_specversion(cloudevent), ce_json['specversion']) - if 'source' in ce_json: - self.assertEqual(UCloudEvent.get_source(cloudevent), ce_json['source']) - if 'sink' in ce_json: - self.assertEqual(UCloudEvent.get_sink(cloudevent), ce_json['sink']) - if 'type' in ce_json: - self.assertEqual(UCloudEvent.get_type(cloudevent), ce_json['type']) - if 'priority' in ce_json: - self.assertEqual(UCloudEvent.get_priority(cloudevent), ce_json['priority']) - if 'ttl' in ce_json: - self.assertEqual(UCloudEvent.get_ttl(cloudevent), ce_json['ttl']) - if 'hash' in ce_json: - self.assertEqual(UCloudEvent.get_hash(cloudevent), ce_json['hash']) - if 'token' in ce_json: - self.assertEqual(UCloudEvent.get_token(cloudevent), ce_json['token']) - if 'dataschema' in ce_json: - self.assertEqual(UCloudEvent.get_data_schema(cloudevent), ce_json['dataschema']) - if 'datacontenttype' in ce_json: - self.assertEqual(UCloudEvent.get_data_content_type(cloudevent), ce_json['datacontenttype']) - if 'commstatus' in ce_json: - self.assertEqual(UCloudEvent.get_communication_status(cloudevent), ce_json['commstatus']) + self.assertEqual(UCloudEvent.get_id(cloudevent), ce_json["id"]) + self.assertEqual( + UCloudEvent.get_specversion(cloudevent), ce_json["specversion"] + ) + if "source" in ce_json: + self.assertEqual( + UCloudEvent.get_source(cloudevent), ce_json["source"] + ) + if "sink" in ce_json: + self.assertEqual( + UCloudEvent.get_sink(cloudevent), ce_json["sink"] + ) + if "type" in ce_json: + self.assertEqual( + UCloudEvent.get_type(cloudevent), ce_json["type"] + ) + if "priority" in ce_json: + self.assertEqual( + UCloudEvent.get_priority(cloudevent), ce_json["priority"] + ) + if "ttl" in ce_json: + self.assertEqual( + UCloudEvent.get_ttl(cloudevent), ce_json["ttl"] + ) + if "hash" in ce_json: + self.assertEqual( + UCloudEvent.get_hash(cloudevent), ce_json["hash"] + ) + if "token" in ce_json: + self.assertEqual( + UCloudEvent.get_token(cloudevent), ce_json["token"] + ) + if "dataschema" in ce_json: + self.assertEqual( + UCloudEvent.get_data_schema(cloudevent), + ce_json["dataschema"], + ) + if "datacontenttype" in ce_json: + self.assertEqual( + UCloudEvent.get_data_content_type(cloudevent), + ce_json["datacontenttype"], + ) + if "commstatus" in ce_json: + self.assertEqual( + UCloudEvent.get_communication_status(cloudevent), + ce_json["commstatus"], + ) def test_serialize_and_deserialize_cloud_event_to_protobuf(self): - source = build_uuri_for_test() proto_payload = build_proto_payload_for_test() # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS0).with_ttl(3).build() + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS0) + .with_ttl(3) + .build() + ) # build the cloud event - cloud_event = CloudEventFactory.build_base_cloud_event("hello", "/body.access/1/door.front_left", - proto_payload.SerializeToString(), - proto_payload.type_url, u_cloud_event_attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event = CloudEventFactory.build_base_cloud_event( + "hello", + "/body.access/1/door.front_left", + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) cloud_event.__delitem__("time") serialized_data = serializer.serialize(cloud_event) @@ -123,35 +177,57 @@ def test_serialize_and_deserialize_cloud_event_to_protobuf(self): def test_serialize_two_different_cloud_event_are_not_the_same(self): proto_payload = build_proto_payload_for_test() - json_attributes1 = {"id": "hello", "source": "/body.access/1/door.front_left", "type": "pub.v1"} - cloud_event1 = ApacheCloudEvent(json_attributes1, proto_payload.SerializeToString()) - cloud_event1.__setitem__('datacontenttype', 'application/protobuf') - cloud_event1.__setitem__('dataschema', proto_payload.type_url) + json_attributes1 = { + "id": "hello", + "source": "/body.access/1/door.front_left", + "type": "pub.v1", + } + cloud_event1 = ApacheCloudEvent( + json_attributes1, proto_payload.SerializeToString() + ) + cloud_event1.__setitem__("datacontenttype", "application/protobuf") + cloud_event1.__setitem__("dataschema", proto_payload.type_url) cloud_event1.__delitem__("time") - json_attributes2 = {"source": "/body.access/1/door.front_left", "type": "file.v1"} - cloud_event2 = ApacheCloudEvent(json_attributes2, proto_payload.SerializeToString()) + json_attributes2 = { + "source": "/body.access/1/door.front_left", + "type": "file.v1", + } + cloud_event2 = ApacheCloudEvent( + json_attributes2, proto_payload.SerializeToString() + ) cloud_event2.__delitem__("time") serialized1 = serializer.serialize(cloud_event1) serialized2 = serializer.serialize(cloud_event2) self.assertNotEqual(serialized1, serialized2) - def test_double_serialization_protobuf_when_creating_cloud_event_with_factory_methods(self): + def test_double_serialization_protobuf_when_creating_cloud_event_with_factory_methods( + self, + ): proto_payload = build_proto_payload_for_test() source = build_uuri_for_test() # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS1) + .with_ttl(3) + .with_token("someOAuthToken") + .build() + ) # build the cloud event - cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload.SerializeToString(), - proto_payload.type_url, u_cloud_event_attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event = CloudEventFactory.build_base_cloud_event( + "testme", + source, + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) cloud_event.__delitem__("time") serialized_data = serializer.serialize(cloud_event) deserialized_data = serializer.deserialize(serialized_data) deserialized_data.__delitem__("time") self.assertEqual(cloud_event, deserialized_data) - diff --git a/tests/test_cloudevent/test_validator/test_cloudeventvalidator.py b/tests/test_cloudevent/test_validator/test_cloudeventvalidator.py index b791685..3746d5c 100644 --- a/tests/test_cloudevent/test_validator/test_cloudeventvalidator.py +++ b/tests/test_cloudevent/test_validator/test_cloudeventvalidator.py @@ -25,15 +25,19 @@ # ------------------------------------------------------------------------- - import unittest from google.protobuf import any_pb2 -from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributesBuilder +from uprotocol.cloudevent.datamodel.ucloudeventattributes import ( + UCloudEventAttributesBuilder, +) from uprotocol.cloudevent.factory.cloudeventfactory import CloudEventFactory from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent -from uprotocol.cloudevent.validate.cloudeventvalidator import CloudEventValidator, Validators +from uprotocol.cloudevent.validate.cloudeventvalidator import ( + CloudEventValidator, + Validators, +) from uprotocol.cloudevent.cloudevents_pb2 import CloudEvent from uprotocol.proto.uattributes_pb2 import UPriority, UMessageType from uprotocol.proto.uri_pb2 import UUri, UEntity, UResource @@ -44,28 +48,72 @@ from uprotocol.validation.validationresult import ValidationResult -def build_base_cloud_event_for_test(): +def build_base_publish_cloud_event_for_test(): + # uri + source = build_long_uri_for_test() + + # fake payload + proto_payload = build_proto_payload_for_test() + # additional attributes + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS1) + .with_ttl(3) + .with_token("someOAuthToken") + .build() + ) + # build the cloud event + cloud_event = CloudEventFactory.build_base_cloud_event( + "testme", + source, + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) + return cloud_event + + pass + + +def build_base_notification_cloud_event_for_test(): # uri source = build_long_uri_for_test() # fake payload proto_payload = build_proto_payload_for_test() # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_hash("somehash").with_priority( - UPriority.UPRIORITY_CS1).with_ttl(3).with_token("someOAuthToken").build() + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_hash("somehash") + .with_priority(UPriority.UPRIORITY_CS1) + .with_ttl(3) + .with_token("someOAuthToken") + .build() + ) # build the cloud event - cloud_event = CloudEventFactory.build_base_cloud_event("testme", source, proto_payload.SerializeToString(), - proto_payload.type_url, u_cloud_event_attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event = CloudEventFactory.build_base_cloud_event( + "testme", + source, + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_NOTIFICATION), + ) return cloud_event pass def build_proto_payload_for_test(): - ce_proto = CloudEvent(spec_version="1.0", source="https://example.com", id="hello", type="example.demo", - proto_data=any_pb2.Any()) + ce_proto = CloudEvent( + spec_version="1.0", + source="https://example.com", + id="hello", + type="example.demo", + proto_data=any_pb2.Any(), + ) any_obj = any_pb2.Any() any_obj.Pack(ce_proto) @@ -73,8 +121,10 @@ def build_proto_payload_for_test(): def build_uuri_for_test(): - return UUri(entity=UEntity(name="body.access"), - resource=UResource(name="door", instance="front_left", message="Door")) + return UUri( + entity=UEntity(name="body.access"), + resource=UResource(name="door", instance="front_left", message="Door"), + ) def build_long_uri_for_test(): @@ -84,14 +134,14 @@ def build_long_uri_for_test(): class TestCloudEventValidator(unittest.TestCase): def test_get_a_publish_cloud_event_validator(self): - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() validator = CloudEventValidator.get_validator(cloud_event) status = validator.validate_type(cloud_event).to_status() self.assertEqual(status, ValidationResult.STATUS_SUCCESS) self.assertEqual("CloudEventValidator.Publish", str(validator)) def test_get_a_notification_cloud_event_validator(self): - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_notification_cloud_event_for_test() cloud_event.__setitem__("sink", "//bo.cloud/petapp") validator = Validators.NOTIFICATION.validator() status = validator.validate_type(cloud_event).to_status() @@ -99,25 +149,29 @@ def test_get_a_notification_cloud_event_validator(self): self.assertEqual("CloudEventValidator.Notification", str(validator)) def test_publish_cloud_event_type(self): - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("type", "res.v1") validator = Validators.PUBLISH.validator() status = validator.validate_type(cloud_event).to_status() self.assertEqual(UCode.INVALID_ARGUMENT, status.code) - self.assertEqual("Invalid CloudEvent type [res.v1]. CloudEvent of type Publish must have a type of 'pub.v1'", - status.message) + self.assertEqual( + "Invalid CloudEvent type [res.v1]. CloudEvent of type Publish must have a type of 'pub.v1'", + status.message, + ) def test_notification_cloud_event_type(self): - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("type", "res.v1") validator = Validators.NOTIFICATION.validator() status = validator.validate_type(cloud_event).to_status() self.assertEqual(UCode.INVALID_ARGUMENT, status.code) - self.assertEqual("Invalid CloudEvent type [res.v1]. CloudEvent of type Publish must have a type of 'pub.v1'", - status.message) + self.assertEqual( + "Invalid CloudEvent type [res.v1]. CloudEvent of type Notification must have a type of 'not.v1'", + status.message, + ) def test_get_a_request_cloud_event_validator(self): - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("type", "req.v1") validator = CloudEventValidator.get_validator(cloud_event) status = validator.validate_type(cloud_event).to_status() @@ -125,16 +179,18 @@ def test_get_a_request_cloud_event_validator(self): self.assertEqual("CloudEventValidator.Request", str(validator)) def test_request_cloud_event_type(self): - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("type", "pub.v1") validator = Validators.REQUEST.validator() status = validator.validate_type(cloud_event).to_status() self.assertEqual(UCode.INVALID_ARGUMENT, status.code) - self.assertEqual("Invalid CloudEvent type [pub.v1]. CloudEvent of type Request must have a type of 'req.v1'", - status.message) + self.assertEqual( + "Invalid CloudEvent type [pub.v1]. CloudEvent of type Request must have a type of 'req.v1'", + status.message, + ) def test_get_a_response_cloud_event_validator(self): - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("type", "res.v1") validator = CloudEventValidator.get_validator(cloud_event) status = validator.validate_type(cloud_event).to_status() @@ -142,25 +198,28 @@ def test_get_a_response_cloud_event_validator(self): self.assertEqual("CloudEventValidator.Response", str(validator)) def test_response_cloud_event_type(self): - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("type", "pub.v1") validator = Validators.RESPONSE.validator() status = validator.validate_type(cloud_event).to_status() self.assertEqual(UCode.INVALID_ARGUMENT, status.code) - self.assertEqual("Invalid CloudEvent type [pub.v1]. CloudEvent of type Response must have a type of 'res.v1'", - status.message) - - def test_get_a_publish_cloud_event_validator_when_cloud_event_type_is_unknown(self): - cloud_event = build_base_cloud_event_for_test() + self.assertEqual( + "Invalid CloudEvent type [pub.v1]. CloudEvent of type Response must have a type of 'res.v1'", + status.message, + ) + + def test_get_a_publish_cloud_event_validator_when_cloud_event_type_is_unknown( + self, + ): + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("type", "lala.v1") validator = CloudEventValidator.get_validator(cloud_event) - status = validator.validate_type(cloud_event).to_status() self.assertEqual("CloudEventValidator.Publish", str(validator)) def test_validate_cloud_event_version_when_valid(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("type", "pub.v1") cloud_event.__setitem__("id", str_uuid) status = CloudEventValidator.validate_version(cloud_event).to_status() @@ -169,17 +228,20 @@ def test_validate_cloud_event_version_when_valid(self): def test_validate_cloud_event_version_when_not_valid(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("specversion", "0.3") cloud_event.__setitem__("id", str_uuid) status = CloudEventValidator.validate_version(cloud_event).to_status() self.assertEqual(UCode.INVALID_ARGUMENT, status.code) - self.assertEqual("Invalid CloudEvent version [0.3]. CloudEvent version must be 1.0.", status.message) + self.assertEqual( + "Invalid CloudEvent version [0.3]. CloudEvent version must be 1.0.", + status.message, + ) def test_validate_cloud_event_id_when_valid(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "pub.v1") status = CloudEventValidator.validate_version(cloud_event).to_status() @@ -187,110 +249,166 @@ def test_validate_cloud_event_id_when_valid(self): def test_validate_cloud_event_id_when_not_uuidv8_type_id(self): str_uuid = "1dd9200c-d41b-4658-8102-3101f0b91378" - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "pub.v1") status = CloudEventValidator.validate_id(cloud_event).to_status() self.assertEqual(UCode.INVALID_ARGUMENT, status.code) - self.assertEqual("Invalid CloudEvent Id [" + str_uuid + "]. CloudEvent Id must be of type UUIDv8.", - status.message) + self.assertEqual( + "Invalid CloudEvent Id [" + + str_uuid + + "]. CloudEvent Id must be of type UUIDv8.", + status.message, + ) def test_validate_cloud_event_id_when_not_valid(self): - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", "testme") cloud_event.__setitem__("type", "pub.v1") status = CloudEventValidator.validate_id(cloud_event).to_status() self.assertEqual(UCode.INVALID_ARGUMENT, status.code) - self.assertEqual("Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.", status.message) + self.assertEqual( + "Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.", + status.message, + ) - def test_publish_type_cloudevent_is_valid_when_everything_is_valid_local(self): + def test_publish_type_cloudevent_is_valid_when_everything_is_valid_local( + self, + ): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "pub.v1") - cloud_event.__setitem__("source", "/body.access/1/door.front_left#Door") + cloud_event.__setitem__( + "source", "/body.access/1/door.front_left#Door" + ) validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) self.assertEqual(ValidationResult.success(), result) - def test_publish_type_cloudevent_is_valid_when_everything_is_valid_remote(self): + def test_publish_type_cloudevent_is_valid_when_everything_is_valid_remote( + self, + ): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "pub.v1") - cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/door.front_left#Door") + cloud_event.__setitem__( + "source", "//VCU.myvin/body.access/1/door.front_left#Door" + ) validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) self.assertEqual(ValidationResult.success(), result) - def test_publish_type_cloudevent_is_valid_when_everything_is_valid_remote_with_a_sink(self): + def test_publish_type_cloudevent_is_valid_when_everything_is_valid_remote_with_a_sink( + self, + ): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "pub.v1") - cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/door.front_left#Door") + cloud_event.__setitem__( + "source", "//VCU.myvin/body.access/1/door.front_left#Door" + ) cloud_event.__setitem__("sink", "//bo.cloud/petapp") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) self.assertEqual(ValidationResult.success(), result) - def test_publish_type_cloudevent_is_not_valid_when_remote_with_invalid_sink(self): + def test_publish_type_cloudevent_is_not_valid_when_remote_with_invalid_sink( + self, + ): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "pub.v1") - cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/door.front_left#Door") + cloud_event.__setitem__( + "source", "//VCU.myvin/body.access/1/door.front_left#Door" + ) cloud_event.__setitem__("sink", "//bo.cloud") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) - self.assertEqual("Invalid CloudEvent sink [//bo.cloud]. Uri is missing uSoftware Entity name.", - result.get_message()) + self.assertEqual( + "Invalid CloudEvent sink [//bo.cloud]. Uri is missing uSoftware Entity name.", + result.get_message(), + ) def test_publish_type_cloudevent_is_not_valid_when_source_is_empty(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("source", "/") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) - self.assertEqual("Invalid Publish type CloudEvent source [/]. Uri is empty.", result.get_message()) + self.assertEqual( + "Invalid Publish type CloudEvent source [/]. Uri is empty.", + result.get_message(), + ) - def test_publish_type_cloudevent_is_not_valid_when_source_is_missing_authority(self): - cloud_event = build_base_cloud_event_for_test() + def test_notification_type_cloudevent_is_not_valid_when_source_is_empty( + self, + ): + uuid = Factories.UPROTOCOL.create() + str_uuid = LongUuidSerializer.instance().serialize(uuid) + cloud_event = build_base_notification_cloud_event_for_test() + cloud_event.__setitem__("id", str_uuid) + cloud_event.__setitem__("source", "/") + validator = Validators.NOTIFICATION.validator() + result = validator.validate(cloud_event) + self.assertEqual( + "Invalid Notification type CloudEvent source [/], Uri is empty.," + + "Invalid CloudEvent sink. Notification CloudEvent sink must be an uri.", + result.get_message(), + ) + + def test_publish_type_cloudevent_is_not_valid_when_source_is_missing_authority( + self, + ): + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", "testme") cloud_event.__setitem__("type", "pub.v1") cloud_event.__setitem__("source", "/body.access") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) self.assertEqual( - "Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.," + "Invalid Publish type " + - "CloudEvent source [/body.access]. UriPart is missing uResource name.", - result.get_message()) - - def test_publish_type_cloudevent_is_not_valid_when_source_is_missing_message_info(self): - cloud_event = build_base_cloud_event_for_test() + "Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.," + + "Invalid Publish type " + + "CloudEvent source [/body.access]. UriPart is missing uResource name.", + result.get_message(), + ) + + def test_publish_type_cloudevent_is_not_valid_when_source_is_missing_message_info( + self, + ): + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", "testme") cloud_event.__setitem__("type", "pub.v1") cloud_event.__setitem__("source", "/body.access/1/door.front_left") validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) self.assertEqual( - "Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.," + "Invalid Publish type " + - "CloudEvent source [/body.access/1/door.front_left]. UriPart is missing Message information.", - result.get_message()) - - def test_notification_type_cloudevent_is_valid_when_everything_is_valid(self): + "Invalid CloudEvent Id [testme]. CloudEvent Id must be of type UUIDv8.," + + "Invalid Publish type " + + "CloudEvent source [/body.access/1/door.front_left]. UriPart is missing Message information.", + result.get_message(), + ) + + def test_notification_type_cloudevent_is_valid_when_everything_is_valid( + self, + ): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_notification_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) - cloud_event.__setitem__("type", "pub.v1") - cloud_event.__setitem__("source", "/body.access/1/door.front_left#Door") + cloud_event.__setitem__("type", "not.v1") + cloud_event.__setitem__( + "source", "/body.access/1/door.front_left#Door" + ) cloud_event.__setitem__("sink", "//bo.cloud/petapp") validator = Validators.NOTIFICATION.validator() result = validator.validate(cloud_event) @@ -299,36 +417,45 @@ def test_notification_type_cloudevent_is_valid_when_everything_is_valid(self): def test_notification_type_cloudevent_is_not_valid_missing_sink(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_notification_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) - cloud_event.__setitem__("type", "pub.v1") - cloud_event.__setitem__("source", "/body.access/1/door.front_left#Door") + cloud_event.__setitem__("type", "not.v1") + cloud_event.__setitem__( + "source", "/body.access/1/door.front_left#Door" + ) validator = Validators.NOTIFICATION.validator() result = validator.validate(cloud_event) - self.assertEqual("Invalid CloudEvent sink. Notification CloudEvent sink must be an uri.", - result.get_message()) + self.assertEqual( + "Invalid CloudEvent sink. Notification CloudEvent sink must be an uri.", + result.get_message(), + ) def test_notification_type_cloudevent_is_not_valid_invalid_sink(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_notification_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) - cloud_event.__setitem__("type", "pub.v1") + cloud_event.__setitem__("type", "not.v1") cloud_event.__setitem__("sink", "//bo.cloud") - cloud_event.__setitem__("source", "/body.access/1/door.front_left#Door") + cloud_event.__setitem__( + "source", "/body.access/1/door.front_left#Door" + ) validator = Validators.NOTIFICATION.validator() result = validator.validate(cloud_event) self.assertEqual( "Invalid Notification type CloudEvent sink [//bo.cloud]. Uri is missing uSoftware Entity name.", - result.get_message()) + result.get_message(), + ) def test_request_type_cloudevent_is_valid_when_everything_is_valid(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "req.v1") - cloud_event.__setitem__("sink", "//VCU.myvin/body.access/1/rpc.UpdateDoor") + cloud_event.__setitem__( + "sink", "//VCU.myvin/body.access/1/rpc.UpdateDoor" + ) cloud_event.__setitem__("source", "//bo.cloud/petapp//rpc.response") validator = Validators.REQUEST.validator() result = validator.validate(cloud_event) @@ -337,22 +464,26 @@ def test_request_type_cloudevent_is_valid_when_everything_is_valid(self): def test_request_type_cloudevent_is_not_valid_invalid_source(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "req.v1") - cloud_event.__setitem__("sink", "//VCU.myvin/body.access/1/rpc.UpdateDoor") + cloud_event.__setitem__( + "sink", "//VCU.myvin/body.access/1/rpc.UpdateDoor" + ) cloud_event.__setitem__("source", "//bo.cloud/petapp//dog") validator = Validators.REQUEST.validator() result = validator.validate(cloud_event) self.assertEqual( - "Invalid RPC Request CloudEvent source [//bo.cloud/petapp//dog]. " + "Invalid RPC uri application " + - "response topic. UriPart is missing rpc.response.", - result.get_message()) + "Invalid RPC Request CloudEvent source [//bo.cloud/petapp//dog]. " + + "Invalid RPC uri application " + + "response topic. UriPart is missing rpc.response.", + result.get_message(), + ) def test_request_type_cloudevent_is_not_valid_missing_sink(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "req.v1") cloud_event.__setitem__("source", "//bo.cloud/petapp//rpc.response") @@ -360,12 +491,15 @@ def test_request_type_cloudevent_is_not_valid_missing_sink(self): result = validator.validate(cloud_event) self.assertEqual( "Invalid RPC Request CloudEvent sink. Request CloudEvent sink must be uri for the method to be called.", - result.get_message()) + result.get_message(), + ) - def test_request_type_cloudevent_is_not_valid_invalid_sink_not_rpc_command(self): + def test_request_type_cloudevent_is_not_valid_invalid_sink_not_rpc_command( + self, + ): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "req.v1") cloud_event.__setitem__("source", "//bo.cloud/petapp//rpc.response") @@ -373,18 +507,22 @@ def test_request_type_cloudevent_is_not_valid_invalid_sink_not_rpc_command(self) validator = Validators.REQUEST.validator() result = validator.validate(cloud_event) self.assertEqual( - "Invalid RPC Request CloudEvent sink [//VCU.myvin/body.access/1/UpdateDoor]. " + "Invalid RPC method " + - "uri. UriPart should be the method to be called, or method from response.", - result.get_message()) + "Invalid RPC Request CloudEvent sink [//VCU.myvin/body.access/1/UpdateDoor]. " + + "Invalid RPC method " + + "uri. UriPart should be the method to be called, or method from response.", + result.get_message(), + ) def test_response_type_cloudevent_is_valid_when_everything_is_valid(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "res.v1") cloud_event.__setitem__("sink", "//bo.cloud/petapp//rpc.response") - cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/rpc.UpdateDoor") + cloud_event.__setitem__( + "source", "//VCU.myvin/body.access/1/rpc.UpdateDoor" + ) validator = Validators.RESPONSE.validator() result = validator.validate(cloud_event) self.assertEqual(ValidationResult.success(), result) @@ -392,36 +530,48 @@ def test_response_type_cloudevent_is_valid_when_everything_is_valid(self): def test_response_type_cloudevent_is_not_valid_invalid_source(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "res.v1") cloud_event.__setitem__("sink", "//bo.cloud/petapp//rpc.response") - cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/UpdateDoor") + cloud_event.__setitem__( + "source", "//VCU.myvin/body.access/1/UpdateDoor" + ) validator = Validators.RESPONSE.validator() result = validator.validate(cloud_event) self.assertEqual( - "Invalid RPC Response CloudEvent source [//VCU.myvin/body.access/1/UpdateDoor]. " + "Invalid RPC " + - "method uri. UriPart should be the method to be called, or method from response.", - result.get_message()) - - def test_response_type_cloudevent_is_not_valid_missing_sink_and_invalid_source(self): + "Invalid RPC Response CloudEvent source [//VCU.myvin/body.access/1/UpdateDoor]. " + + "Invalid RPC " + + "method uri. UriPart should be the method to be called, or method from response.", + result.get_message(), + ) + + def test_response_type_cloudevent_is_not_valid_missing_sink_and_invalid_source( + self, + ): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "res.v1") - cloud_event.__setitem__("source", "//VCU.myvin/body.access/1/UpdateDoor") + cloud_event.__setitem__( + "source", "//VCU.myvin/body.access/1/UpdateDoor" + ) validator = Validators.RESPONSE.validator() result = validator.validate(cloud_event) self.assertEqual( - "Invalid RPC Response CloudEvent source [//VCU.myvin/body.access/1/UpdateDoor]. " + "Invalid RPC " + - "method uri. UriPart should be the method to be called, or method from response.," + "Invalid" + " CloudEvent sink. Response CloudEvent sink must be uri the destination of the response.", - result.get_message()) + "Invalid RPC Response CloudEvent source [//VCU.myvin/body.access/1/UpdateDoor]. " + + "Invalid RPC " + + "method uri. UriPart should be the method to be called, or method from response.," + + "Invalid" + + " CloudEvent sink. Response CloudEvent sink must be uri the destination of the response.", + result.get_message(), + ) def test_response_type_cloudevent_is_not_valid_invalid_sink(self): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "res.v1") cloud_event.__setitem__("sink", "//bo.cloud") @@ -429,15 +579,18 @@ def test_response_type_cloudevent_is_not_valid_invalid_sink(self): validator = Validators.RESPONSE.validator() result = validator.validate(cloud_event) self.assertEqual( - "Invalid RPC Response CloudEvent source [//VCU.myvin]. Invalid RPC method uri. Uri is missing " + - "uSoftware Entity name.,Invalid RPC Response CloudEvent sink [//bo.cloud]. Invalid RPC uri " + - "application response topic. Uri is missing uSoftware Entity name.", - result.get_message()) - - def test_response_type_cloudevent_is_not_valid_invalid_source_not_rpc_command(self): + "Invalid RPC Response CloudEvent source [//VCU.myvin]. Invalid RPC method uri. Uri is missing " + + "uSoftware Entity name.,Invalid RPC Response CloudEvent sink [//bo.cloud]. Invalid RPC uri " + + "application response topic. Uri is missing uSoftware Entity name.", + result.get_message(), + ) + + def test_response_type_cloudevent_is_not_valid_invalid_source_not_rpc_command( + self, + ): uuid = Factories.UPROTOCOL.create() str_uuid = LongUuidSerializer.instance().serialize(uuid) - cloud_event = build_base_cloud_event_for_test() + cloud_event = build_base_publish_cloud_event_for_test() cloud_event.__setitem__("id", str_uuid) cloud_event.__setitem__("type", "res.v1") cloud_event.__setitem__("source", "//bo.cloud/petapp/1/dog") @@ -445,26 +598,46 @@ def test_response_type_cloudevent_is_not_valid_invalid_source_not_rpc_command(se validator = Validators.RESPONSE.validator() result = validator.validate(cloud_event) self.assertEqual( - "Invalid RPC Response CloudEvent source [//bo.cloud/petapp/1/dog]. Invalid RPC method uri. UriPart " + - "should be the method to be called, or method from response.," + "Invalid RPC Response " + "CloudEvent " - "sink [" - "//VCU.myvin/body.access/1/UpdateDoor]. " + "Invalid RPC uri application " + "response topic. UriPart is missing rpc.response.", - result.get_message()) + "Invalid RPC Response CloudEvent source [//bo.cloud/petapp/1/dog]. Invalid RPC method uri. UriPart " + + "should be the method to be called, or method from response.," + + "Invalid RPC Response " + + "CloudEvent " + "sink [" + "//VCU.myvin/body.access/1/UpdateDoor]. " + + "Invalid RPC uri application " + + "response topic. UriPart is missing rpc.response.", + result.get_message(), + ) def test_create_a_v6_cloudevent_and_validate_it_against_sdk(self): source = build_long_uri_for_test() uuid = Factories.UUIDV6.create() id = LongUuidSerializer.instance().serialize(uuid) - proto_payload=build_proto_payload_for_test() + proto_payload = build_proto_payload_for_test() # additional attributes - u_cloud_event_attributes = UCloudEventAttributesBuilder().with_priority( - UPriority.UPRIORITY_CS0).with_ttl(1000).build() + u_cloud_event_attributes = ( + UCloudEventAttributesBuilder() + .with_priority(UPriority.UPRIORITY_CS0) + .with_ttl(1000) + .build() + ) # build the cloud event - cloud_event = CloudEventFactory.build_base_cloud_event(id, source, proto_payload.SerializeToString(), - proto_payload.type_url, u_cloud_event_attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event = CloudEventFactory.build_base_cloud_event( + id, + source, + proto_payload.SerializeToString(), + proto_payload.type_url, + u_cloud_event_attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) validator = Validators.PUBLISH.validator() result = validator.validate(cloud_event) self.assertTrue(result.is_success()) self.assertFalse(UCloudEvent.is_expired(cloud_event)) + + def fetching_the_notification_validator(self): + cloud_event = build_base_notification_cloud_event_for_test() + validator = CloudEventValidator.get_validator(cloud_event) + status = validator.validate_type(cloud_event).to_status() + self.assertEqual(status, ValidationResult.STATUS_SUCCESS) + self.assertEqual("CloudEventValidator.Notification", str(validator)) diff --git a/tests/test_rpc/test_calloptions.py b/tests/test_rpc/test_calloptions.py index 18fa71c..7ec5171 100644 --- a/tests/test_rpc/test_calloptions.py +++ b/tests/test_rpc/test_calloptions.py @@ -27,54 +27,40 @@ import unittest -from uprotocol.rpc.calloptions import CallOptions, CallOptionsBuilder +from uprotocol.proto.uattributes_pb2 import CallOptions class TestCallOptions(unittest.TestCase): - def test_hash_code_equals(self): - call_options = CallOptionsBuilder().build() - self.assertEqual(hash(call_options), hash(call_options)) - def test_to_string(self): - call_options = CallOptionsBuilder().with_timeout(30).with_token("someToken").build() - expected = "CallOptions{mTimeout=30, mToken='someToken'}" - self.assertEqual(expected, str(call_options)) - - def test_creating_call_options_default(self): - call_options = CallOptionsBuilder.DEFAULT - self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) - self.assertTrue(call_options.get_token() is None or call_options.get_token() == "") + call_options = CallOptions(ttl=30, token="someToken") + self.assertEqual(30, call_options.ttl) + self.assertEqual("someToken", call_options.token) def test_creating_call_options_with_a_token(self): - call_options = CallOptionsBuilder().with_token("someToken").build() - self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) - self.assertTrue(call_options.get_token() == "someToken") + call_options = CallOptions(token="someToken") + self.assertTrue(call_options.HasField("token")) + self.assertTrue(call_options.token == "someToken") - def test_creating_call_options_with_a_null_token(self): - call_options = CallOptionsBuilder().with_token(None).build() - self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) - self.assertTrue(call_options.get_token() is None or call_options.get_token() == "") + def test_creating_call_options_without_token(self): + call_options = CallOptions() + self.assertFalse(call_options.HasField("token")) + self.assertEqual(0, call_options.ttl) def test_creating_call_options_with_an_empty_string_token(self): - call_options = CallOptionsBuilder().with_token("").build() - self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) - self.assertTrue(call_options.get_token() is None or call_options.get_token() == "") + call_options = CallOptions(token="") + self.assertTrue(call_options.HasField("token")) + self.assertTrue(call_options.token == "") def test_creating_call_options_with_a_token_with_only_spaces(self): - call_options = CallOptionsBuilder().with_token(" ").build() - self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) - self.assertTrue(call_options.get_token() is None or call_options.get_token().isspace()) + call_options = CallOptions(token=" ") + self.assertTrue(call_options.HasField("token")) + self.assertTrue(call_options.token.strip() == "") def test_creating_call_options_with_a_timeout(self): - call_options = CallOptionsBuilder().with_timeout(30).build() - self.assertEqual(30, call_options.get_timeout()) - self.assertTrue(call_options.get_token() is None or call_options.get_token() == "") - - def test_creating_call_options_with_a_negative_timeout(self): - call_options = CallOptionsBuilder().with_timeout(-3).build() - self.assertEqual(CallOptions.TIMEOUT_DEFAULT, call_options.get_timeout()) - self.assertTrue(call_options.get_token() is None or call_options.get_token() == "") + call_options = CallOptions(ttl=30) + self.assertEqual(30, call_options.ttl) + self.assertTrue(call_options.token is None or call_options.token == "") if __name__ == '__main__': diff --git a/tests/test_rpc/test_rpc.py b/tests/test_rpc/test_rpc.py index a4751d0..0eeb5fc 100644 --- a/tests/test_rpc/test_rpc.py +++ b/tests/test_rpc/test_rpc.py @@ -29,36 +29,42 @@ from concurrent.futures import Future from google.protobuf.any_pb2 import Any from google.protobuf.wrappers_pb2 import Int32Value -from uprotocol.rpc.calloptions import CallOptions +from uprotocol.proto.uattributes_pb2 import CallOptions from uprotocol.cloudevent.cloudevents_pb2 import CloudEvent -from uprotocol.proto.uattributes_pb2 import UPriority from uprotocol.proto.upayload_pb2 import UPayload, UPayloadFormat from uprotocol.proto.uri_pb2 import UUri, UEntity, UAuthority from uprotocol.proto.ustatus_pb2 import UStatus, UCode from uprotocol.rpc.rpcclient import RpcClient from uprotocol.rpc.rpcmapper import RpcMapper -from uprotocol.rpc.rpcresult import RpcResult -from uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder from uprotocol.uri.serializer.longuriserializer import LongUriSerializer from uprotocol.uri.factory.uresource_builder import UResourceBuilder from uprotocol.proto.umessage_pb2 import UMessage def build_source(): - return UUri(authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), - entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), - resource=UResourceBuilder.for_rpc_request(None)) + return UUri( + authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), + entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), + resource=UResourceBuilder.for_rpc_request(None), + ) def build_cloud_event(): - return CloudEvent(spec_version="1.0", source="https://example.com", id="HARTLEY IS THE BEST") + return CloudEvent( + spec_version="1.0", + source="https://example.com", + id="HARTLEY IS THE BEST", + ) def build_upayload(): any_obj = Any() any_obj.Pack(build_cloud_event()) - return UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, value=any_obj.SerializeToString()) + return UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, + value=any_obj.SerializeToString(), + ) def build_topic(): @@ -66,21 +72,28 @@ def build_topic(): def build_calloptions(): - return CallOptions() + return CallOptions(ttl=1000) class ReturnsNumber3(RpcClient): - def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): + def invoke_method( + self, topic: UUri, payload: UPayload, options: CallOptions + ): future = Future() any_obj = Any() any_obj.Pack(Int32Value(value=3)) - data = UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, value=any_obj.SerializeToString()) + data = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, + value=any_obj.SerializeToString(), + ) future.set_result(UMessage(payload=data)) return future class HappyPath(RpcClient): - def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): + def invoke_method( + self, topic: UUri, payload: UPayload, options: CallOptions + ): future = Future() data = build_upayload() future.set_result(UMessage(payload=data)) @@ -88,54 +101,86 @@ def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): class WithUStatusCodeInsteadOfHappyPath(RpcClient): - def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): + def invoke_method( + self, topic: UUri, payload: UPayload, options: CallOptions + ): future = Future() status = UStatus(code=UCode.INVALID_ARGUMENT, message="boom") any_value = Any() any_value.Pack(status) - data = UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, value=any_value.SerializeToString()) + data = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, + value=any_value.SerializeToString(), + ) future.set_result(UMessage(payload=data)) return future class WithUStatusCodeHappyPath(RpcClient): - def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): + def invoke_method( + self, topic: UUri, payload: UPayload, options: CallOptions + ): future = Future() status = UStatus(code=UCode.OK, message="all good") any_value = Any() any_value.Pack(status) - data = UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, value=any_value.SerializeToString()) + data = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, + value=any_value.SerializeToString(), + ) future.set_result(UMessage(payload=data)) return future class ThatBarfsCrapyPayload(RpcClient): - def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): + def invoke_method( + self, topic: UUri, payload: UPayload, options: CallOptions + ): future = Future() - response = UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_RAW, value=bytes([0])) + response = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_RAW, value=bytes([0]) + ) future.set_result(UMessage(payload=response)) return future class ThatCompletesWithAnException(RpcClient): - def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): + def invoke_method( + self, topic: UUri, payload: UPayload, options: CallOptions + ): future = Future() future.set_exception(RuntimeError("Boom")) return future class ThatReturnsTheWrongProto(RpcClient): - def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): + def invoke_method( + self, topic: UUri, payload: UPayload, options: CallOptions + ): future = Future() any_value = Any() any_value.Pack(Int32Value(value=42)) - data = UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, value=any_value.SerializeToString()) + data = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, + value=any_value.SerializeToString(), + ) future.set_result(UMessage(payload=data)) return future +class WithNullMessage(RpcClient): + def invoke_method( + self, topic: UUri, payload: UPayload, options: CallOptions + ): + future = Future() + future.set_result(UMessage()) + return future + + class WithNullInPayload(RpcClient): - def invoke_method(self, topic: UUri, payload: UPayload, options: CallOptions): + def invoke_method( + self, topic: UUri, payload: UPayload, options: CallOptions + ): future = Future() future.set_result(None) return future @@ -169,7 +214,9 @@ def rpc_response(invoke_method_response: Future): try: status = UStatus() any_value.Unpack(status) - raise RuntimeError(f"Error returned, status code: [{status.code}], message: [{status.message}]") + raise RuntimeError( + f"Error returned, status code: [{status.code}], message: [{status.message}]" + ) except Exception as e: raise RuntimeError(f"{str(e)} [com.google.grpc.UStatus]") from e @@ -180,15 +227,22 @@ class TestRpc(unittest.TestCase): def test_compose_happy_path(self): rpc_response = RpcMapper.map_response_to_result( - ReturnsNumber3().invoke_method(build_topic(), build_upayload(), build_calloptions()), Int32Value) + ReturnsNumber3().invoke_method( + build_topic(), build_upayload(), build_calloptions() + ), + Int32Value, + ) mapped = rpc_response.map(lambda x: x.value + 5) self.assertTrue(rpc_response.isSuccess()) self.assertEqual(8, mapped.successValue()) def test_compose_that_returns_status(self): rpc_response = RpcMapper.map_response_to_result( - WithUStatusCodeInsteadOfHappyPath().invoke_method(build_topic(), build_upayload(), build_calloptions()), - Int32Value) + WithUStatusCodeInsteadOfHappyPath().invoke_method( + build_topic(), build_upayload(), build_calloptions() + ), + Int32Value, + ) mapped = rpc_response.map(lambda x: x.value + 5) self.assertTrue(rpc_response.isFailure()) self.assertEqual(UCode.INVALID_ARGUMENT, mapped.failureValue().code) @@ -196,57 +250,119 @@ def test_compose_that_returns_status(self): def test_compose_with_failure(self): rpc_response = RpcMapper.map_response_to_result( - ThatCompletesWithAnException().invoke_method(build_topic(), build_upayload(), build_calloptions()), - Int32Value) + ThatCompletesWithAnException().invoke_method( + build_topic(), build_upayload(), build_calloptions() + ), + Int32Value, + ) mapped = rpc_response.map(lambda x: x.value + 5) self.assertTrue(rpc_response.isFailure()) status = UStatus(code=UCode.UNKNOWN, message="Boom") self.assertEqual(status, mapped.failureValue()) - def test_success_invoke_method_happy_flow_using_mapResponseToRpcResponse(self): + def test_map_response_with_payload_is_null(self): + rpc_response = RpcMapper.map_response_to_result( + WithNullInPayload().invoke_method( + build_topic(), None, build_calloptions() + ), + UStatus, + ) + mapped = rpc_response.map(lambda x: x.value + 5) + self.assertTrue(rpc_response.isFailure()) + self.assertEqual(UCode.UNKNOWN, mapped.failureValue().code) + self.assertEqual( + "Server returned a null payload. Expected UStatus", + mapped.failureValue().message, + ) + + def test_success_invoke_method_happy_flow_using_mapResponseToRpcResponse( + self, + ): rpc_response = RpcMapper.map_response_to_result( - HappyPath().invoke_method(build_topic(), build_upayload(), build_calloptions()), - CloudEvent) + HappyPath().invoke_method( + build_topic(), build_upayload(), build_calloptions() + ), + CloudEvent, + ) self.assertTrue(rpc_response.isSuccess()) self.assertEqual(build_cloud_event(), rpc_response.successValue()) - def test_fail_invoke_method_when_invoke_method_returns_a_status_using_mapResponseToRpcResponse(self): + def test_fail_invoke_method_when_invoke_method_returns_a_status_using_mapResponseToRpcResponse( + self, + ): rpc_response = RpcMapper.map_response_to_result( - WithUStatusCodeInsteadOfHappyPath().invoke_method(build_topic(), build_upayload(), build_calloptions()), - CloudEvent) + WithUStatusCodeInsteadOfHappyPath().invoke_method( + build_topic(), build_upayload(), build_calloptions() + ), + CloudEvent, + ) self.assertTrue(rpc_response.isFailure()) - self.assertEqual(UCode.INVALID_ARGUMENT, rpc_response.failureValue().code) + self.assertEqual( + UCode.INVALID_ARGUMENT, rpc_response.failureValue().code + ) self.assertEqual("boom", rpc_response.failureValue().message) - def test_fail_invoke_method_when_invoke_method_threw_an_exception_using_mapResponseToRpcResponse(self): + def test_fail_invoke_method_when_invoke_method_threw_an_exception_using_mapResponseToRpcResponse( + self, + ): rpc_response = RpcMapper.map_response_to_result( - ThatCompletesWithAnException().invoke_method(build_topic(), build_upayload(), build_calloptions()), - CloudEvent) + ThatCompletesWithAnException().invoke_method( + build_topic(), build_upayload(), build_calloptions() + ), + CloudEvent, + ) self.assertTrue(rpc_response.isFailure()) self.assertEqual(UCode.UNKNOWN, rpc_response.failureValue().code) self.assertEqual("Boom", rpc_response.failureValue().message) - def test_fail_invoke_method_when_invoke_method_returns_a_bad_proto_using_mapResponseToRpcResponse(self): + def test_fail_invoke_method_when_invoke_method_returns_a_bad_proto_using_mapResponseToRpcResponse( + self, + ): rpc_response = RpcMapper.map_response_to_result( - ThatReturnsTheWrongProto().invoke_method(build_topic(), build_upayload(), build_calloptions()), - CloudEvent) + ThatReturnsTheWrongProto().invoke_method( + build_topic(), build_upayload(), build_calloptions() + ), + CloudEvent, + ) self.assertTrue(rpc_response.isFailure()) self.assertEqual(UCode.UNKNOWN, rpc_response.failureValue().code) self.assertEqual( "Unknown payload type [type.googleapis.com/google.protobuf.Int32Value]. Expected [" "io.cloudevents.v1.CloudEvent]", - rpc_response.failureValue().message) + rpc_response.failureValue().message, + ) def test_success_invoke_method_happy_flow_using_mapResponse(self): rpc_response = RpcMapper.map_response( - HappyPath().invoke_method(build_topic(), build_upayload(), build_calloptions()), - CloudEvent) + HappyPath().invoke_method( + build_topic(), build_upayload(), build_calloptions() + ), + CloudEvent, + ) self.assertEqual(build_cloud_event(), rpc_response.result()) - def test_fail_invoke_method_when_invoke_method_returns_a_status_using_mapResponse(self): + def test_fail_invoke_method_when_invoke_method_returns_a_status_using_mapResponse( + self, + ): + rpc_response = RpcMapper.map_response( + WithUStatusCodeInsteadOfHappyPath().invoke_method( + build_topic(), build_upayload(), build_calloptions() + ), + CloudEvent, + ) + exception = RuntimeError( + "Unknown payload type [type.googleapis.com/uprotocol.v1.UStatus]. Expected [CloudEvent]" + ) + self.assertEqual(str(exception), str(rpc_response.exception())) + + def test_map_response_when_response_message_is_null(self): rpc_response = RpcMapper.map_response( - WithUStatusCodeInsteadOfHappyPath().invoke_method(build_topic(), build_upayload(), build_calloptions()), - CloudEvent) + WithNullMessage().invoke_method( + build_topic(), build_upayload(), build_calloptions() + ), + CloudEvent, + ) exception = RuntimeError( - "Unknown payload type [type.googleapis.com/uprotocol.v1.UStatus]. Expected [CloudEvent]") + "Server returned a null payload. Expected CloudEvent" + ) self.assertEqual(str(exception), str(rpc_response.exception())) diff --git a/tests/test_transport/test_builder/test_uattributesbuilder.py b/tests/test_transport/test_builder/test_uattributesbuilder.py index 5133c03..9757728 100644 --- a/tests/test_transport/test_builder/test_uattributesbuilder.py +++ b/tests/test_transport/test_builder/test_uattributesbuilder.py @@ -29,19 +29,25 @@ from uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder from uprotocol.proto.uattributes_pb2 import UPriority, UMessageType from uprotocol.proto.uri_pb2 import UUri, UAuthority, UEntity +from uprotocol.proto.ustatus_pb2 import UCode from uprotocol.uri.factory.uresource_builder import UResourceBuilder from uprotocol.uuid.factory.uuidfactory import Factories + def build_source(): - return UUri(authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), - entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), - resource=UResourceBuilder.for_rpc_request(None)) + return UUri( + authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), + entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), + resource=UResourceBuilder.for_rpc_request(None), + ) def build_sink(): - return UUri(authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), - entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), - resource=UResourceBuilder.for_rpc_response()) + return UUri( + authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), + entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), + resource=UResourceBuilder.for_rpc_response(), + ) def get_uuid(): @@ -62,11 +68,15 @@ def test_publish(self): def test_notification(self): source = build_source() sink = build_sink() - builder = UAttributesBuilder.notification(source, sink, UPriority.UPRIORITY_CS1) + builder = UAttributesBuilder.notification( + source, sink, UPriority.UPRIORITY_CS1 + ) self.assertIsNotNone(builder) attributes = builder.build() self.assertIsNotNone(attributes) - self.assertEqual(UMessageType.UMESSAGE_TYPE_PUBLISH, attributes.type) + self.assertEqual( + UMessageType.UMESSAGE_TYPE_NOTIFICATION, attributes.type + ) self.assertEqual(UPriority.UPRIORITY_CS1, attributes.priority) self.assertEqual(sink, attributes.sink) @@ -74,7 +84,9 @@ def test_request(self): source = build_source() sink = build_sink() ttl = 1000 - builder = UAttributesBuilder.request(source, sink, UPriority.UPRIORITY_CS4, ttl) + builder = UAttributesBuilder.request( + source, sink, UPriority.UPRIORITY_CS4, ttl + ) self.assertIsNotNone(builder) attributes = builder.build() self.assertIsNotNone(attributes) @@ -87,7 +99,9 @@ def test_response(self): source = build_source() sink = build_sink() req_id = get_uuid() - builder = UAttributesBuilder.response(source, sink, UPriority.UPRIORITY_CS6, req_id) + builder = UAttributesBuilder.response( + source, sink, UPriority.UPRIORITY_CS6, req_id + ) self.assertIsNotNone(builder) attributes = builder.build() self.assertIsNotNone(attributes) @@ -96,10 +110,31 @@ def test_response(self): self.assertEqual(sink, attributes.sink) self.assertEqual(req_id, attributes.reqid) + def test_response_with_existing_request(self): + request = UAttributesBuilder.request( + build_source(), build_sink(), UPriority.UPRIORITY_CS6, 1000 + ).build() + builder = UAttributesBuilder.response(request) + self.assertIsNotNone(builder) + response = builder.build() + self.assertIsNotNone(response) + self.assertEqual(UMessageType.UMESSAGE_TYPE_RESPONSE, response.type) + self.assertEqual(UPriority.UPRIORITY_CS6, response.priority) + self.assertEqual(request.sink, response.source) + self.assertEqual(request.source, response.sink) + self.assertEqual(request.id, response.reqid) + def test_build(self): req_id = get_uuid() - builder = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS1).withTtl(1000).withToken("test_token").withSink( - build_sink()).withPermissionLevel(2).withCommStatus(1).withReqId(req_id) + builder = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS1) + .withTtl(1000) + .withToken("test_token") + .withSink(build_sink()) + .withPermissionLevel(2) + .withCommStatus(UCode.CANCELLED) + .withReqId(req_id) + ) attributes = builder.build() self.assertIsNotNone(attributes) self.assertEqual(UMessageType.UMESSAGE_TYPE_PUBLISH, attributes.type) @@ -109,9 +144,9 @@ def test_build(self): self.assertEqual(build_source(), attributes.source) self.assertEqual(build_sink(), attributes.sink) self.assertEqual(2, attributes.permission_level) - self.assertEqual(1, attributes.commstatus) + self.assertEqual(UCode.CANCELLED, attributes.commstatus) self.assertEqual(req_id, attributes.reqid) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_transport/test_builder/test_upayloadbuilder.py b/tests/test_transport/test_builder/test_upayloadbuilder.py new file mode 100644 index 0000000..18e68f6 --- /dev/null +++ b/tests/test_transport/test_builder/test_upayloadbuilder.py @@ -0,0 +1,260 @@ +# ------------------------------------------------------------------------- + +# Copyright (c) 2023 General Motors GTO LLC +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# SPDX-FileType: SOURCE +# SPDX-FileCopyrightText: 2023 General Motors GTO LLC +# SPDX-License-Identifier: Apache-2.0 + +# ------------------------------------------------------------------------- +import unittest +from google.protobuf.any_pb2 import Any +from google.protobuf.wrappers_pb2 import BoolValue +from google.protobuf.api_pb2 import Method +from google.protobuf.message import Message +from uprotocol.proto.upayload_pb2 import UPayload, UPayloadFormat + +from uprotocol.transport.builder.upayloadbuilder import UPayloadBuilder + + +class TestUPayloadBuilder(unittest.TestCase): + + def _create_upayload_builder(self): + return UPayloadBuilder() + + def _assert_pack_to_any(self, expected: Message, actual: UPayload): + self.assertEqual( + UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY, + actual.format, + ) + self.assertEqual(0, actual.length) + self.assertEqual(0, actual.reference) + self.assertEqual(expected.SerializeToString(), actual.value) + + def test_pack_to_any_given_boolvalue_returns_upayload(self): + builder = self._create_upayload_builder() + + msg: Message = BoolValue(value=True) + any_message = Any() + any_message.Pack(msg) + + upayload: UPayload = builder.pack_to_any(msg) + self.assertIsNotNone(upayload) + + self._assert_pack_to_any(any_message, upayload) + + def test_pack_to_any_given_any_returns_upayload(self): + builder = self._create_upayload_builder() + + msg: Message = Any(type_url=None, value=b"bytes") + any_message = Any() + any_message.Pack(msg) + + upayload: UPayload = builder.pack_to_any(msg) + self.assertIsNotNone(upayload) + + self._assert_pack_to_any(any_message, upayload) + + def test_pack_to_any_given_method_returns_upayload(self): + builder = self._create_upayload_builder() + + msg: Message = Method( + name="name", + request_type_url="request_type_url", + response_type_url="response_type_url", + request_streaming=None, + ) + any_message = Any() + any_message.Pack(msg) + + upayload: UPayload = builder.pack_to_any(msg) + self.assertIsNotNone(upayload) + + self._assert_pack_to_any(any_message, upayload) + + def _assert_pack(self, expected: Message, actual: UPayload): + self.assertEqual( + UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, actual.format + ) + self.assertEqual(0, actual.length) + self.assertEqual(0, actual.reference) + self.assertEqual(expected.SerializeToString(), actual.value) + + def test_pack_given_boolvalue_returns_upayload(self): + builder = self._create_upayload_builder() + + msg: Message = BoolValue(value=True) + + upayload: UPayload = builder.pack(msg) + + self._assert_pack(msg, upayload) + + def test_pack_given_any_returns_upayload(self): + builder = self._create_upayload_builder() + + msg: Message = Any(type_url=None, value=b"bytes") + + upayload: UPayload = builder.pack(msg) + + self._assert_pack(msg, upayload) + + def test_pack_given_method_returns_upayload(self): + builder = self._create_upayload_builder() + + msg: Message = Method( + name="name", + request_type_url="request_type_url", + response_type_url="response_type_url", + request_streaming=None, + ) + + upayload: UPayload = builder.pack(msg) + + self._assert_pack(msg, upayload) + + def test_unpack_given_pack_returns_boolvalue(self): + builder = self._create_upayload_builder() + + original_msg: Message = BoolValue(value=False) + upayload: UPayload = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, + value=original_msg.SerializeToString(), + ) + + unpacked_msg: BoolValue = builder.unpack(upayload, BoolValue) + + self.assertEqual(original_msg, unpacked_msg) + + def test_unpack_given_upayload_proto_returns_method(self): + builder = self._create_upayload_builder() + + original_msg: Message = Method( + name="name", + request_type_url="request_type_url", + response_type_url="response_type_url", + request_streaming=None, + ) + upayload: UPayload = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, + value=original_msg.SerializeToString(), + ) + + unpacked_msg: Method = builder.unpack(upayload, Method) + + self.assertEqual(original_msg, unpacked_msg) + + def test_unpack_given_upayload_proto_returns_any(self): + builder = self._create_upayload_builder() + + original_msg: Message = Any(type_url=None, value=b"bytes") + upayload: UPayload = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, + value=original_msg.SerializeToString(), + ) + + unpacked_msg: Any = builder.unpack(upayload, Any) + + self.assertEqual(original_msg, unpacked_msg) + + def test_unpack_given_any_wrapped_upayload_returns_boolvalue(self): + builder = self._create_upayload_builder() + + original_msg: Message = BoolValue(value=False) + any_message = Any() + any_message.Pack(original_msg) + + upayload: UPayload = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY, + value=any_message.SerializeToString(), + ) + + unpacked_msg: BoolValue = builder.unpack(upayload, BoolValue) + + self.assertEqual(original_msg, unpacked_msg) + + def test_unpack_given_any_wrapped_upayload_returns_any(self): + builder = self._create_upayload_builder() + + original_msg: Message = Any(value=b"bytes") + any_message = Any() + any_message.Pack(original_msg) + + upayload: UPayload = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY, + value=any_message.SerializeToString(), + ) + + unpacked_msg: Any = builder.unpack(upayload, Any) + + self.assertEqual(original_msg, unpacked_msg) + + def test_unpack_given_any_wrapped_upayload_returns_method(self): + builder = self._create_upayload_builder() + + original_msg: Message = Method( + name="name", + request_type_url="request_type_url", + response_type_url="response_type_url", + request_streaming=None, + ) + any_message = Any() + any_message.Pack(original_msg) + + upayload: UPayload = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY, + value=any_message.SerializeToString(), + ) + + unpacked_msg: Method = builder.unpack(upayload, Method) + + self.assertEqual(original_msg, unpacked_msg) + + def test_unpack_given_wrong_format_returns_none(self): + builder = self._create_upayload_builder() + + original_msg: Message = Any() + any_message = Any() + any_message.Pack(original_msg) + + upayload: UPayload = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_JSON, + value=any_message.SerializeToString(), + ) + + unpacked_msg: Any = builder.unpack(upayload, Any) + + self.assertIsNone(unpacked_msg) + + def test_unpack_given_none_returns_none(self): + builder = self._create_upayload_builder() + + unpacked_msg: Any = builder.unpack(None, Any) + + self.assertIsNone(unpacked_msg) + + def test_unpack_given_no_upayload_value_returns_none(self): + builder = self._create_upayload_builder() + + upayload: UPayload = UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_JSON + ) + + unpacked_msg: Any = builder.unpack(upayload, Any) + + self.assertIsNone(unpacked_msg) diff --git a/tests/test_transport/test_validate/test_uattributesvalidator.py b/tests/test_transport/test_validate/test_uattributesvalidator.py index d3dcfde..a7768e6 100644 --- a/tests/test_transport/test_validate/test_uattributesvalidator.py +++ b/tests/test_transport/test_validate/test_uattributesvalidator.py @@ -29,10 +29,12 @@ from uprotocol.proto.uattributes_pb2 import UPriority from uprotocol.proto.uri_pb2 import UUri, UAuthority, UEntity -from uprotocol.proto.ustatus_pb2 import UCode from uprotocol.proto.uuid_pb2 import UUID from uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder -from uprotocol.transport.validate.uattributesvalidator import UAttributesValidator, Validators +from uprotocol.transport.validate.uattributesvalidator import ( + UAttributesValidator, + Validators, +) from uprotocol.uri.factory.uresource_builder import UResourceBuilder from uprotocol.uri.serializer.longuriserializer import LongUriSerializer from uprotocol.uuid.factory.uuidfactory import Factories @@ -40,81 +42,120 @@ def build_sink(): - return UUri(authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), - entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), - resource=UResourceBuilder.for_rpc_response()) + return UUri( + authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), + entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), + resource=UResourceBuilder.for_rpc_response(), + ) + def build_source(): - return UUri(authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), - entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), - resource=UResourceBuilder.for_rpc_request(None)) + return UUri( + authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), + entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), + resource=UResourceBuilder.for_rpc_request(None), + ) + class TestUAttributesValidator(unittest.TestCase): def test_fetching_validator_for_valid_types(self): - publish = UAttributesValidator.get_validator(UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).build()) + publish = UAttributesValidator.get_validator( + UAttributesBuilder.publish( + build_source(), UPriority.UPRIORITY_CS0 + ).build() + ) self.assertEqual("UAttributesValidator.Publish", str(publish)) request = UAttributesValidator.get_validator( - UAttributesBuilder.request(build_source(), UUri(), UPriority.UPRIORITY_CS4, 1000).build()) + UAttributesBuilder.request( + build_source(), UUri(), UPriority.UPRIORITY_CS4, 1000 + ).build() + ) self.assertEqual("UAttributesValidator.Request", str(request)) response = UAttributesValidator.get_validator( - UAttributesBuilder.response(build_source(), UUri(), UPriority.UPRIORITY_CS4, Factories.UPROTOCOL.create()).build()) + UAttributesBuilder.response( + build_source(), + UUri(), + UPriority.UPRIORITY_CS4, + Factories.UPROTOCOL.create(), + ).build() + ) self.assertEqual("UAttributesValidator.Response", str(response)) def test_validate_uAttributes_for_publish_message_payload(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).build() + attributes = UAttributesBuilder.publish( + build_source(), UPriority.UPRIORITY_CS0 + ).build() validator = Validators.PUBLISH.validator() status = validator.validate(attributes) self.assertTrue(status.is_success()) self.assertEqual("", status.get_message()) def test_validate_uAttributes_for_publish_message_payload_alls(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(1000).withSink( - build_sink()).withPermissionLevel(2).withCommStatus(3).withReqId(Factories.UPROTOCOL.create()).build() + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withTtl(1000) + .withSink(build_sink()) + .withPermissionLevel(2) + .withCommStatus(3) + .withReqId(Factories.UPROTOCOL.create()) + .build() + ) validator = Validators.PUBLISH.validator() status = validator.validate(attributes) self.assertTrue(status.is_success()) self.assertEqual("", status.get_message()) - def test_validate_uAttributes_for_publish_message_payload_invalid_type(self): - attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS0, - Factories.UPROTOCOL.create()).build() - validator = Validators.PUBLISH.validator() - status = validator.validate(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_RESPONSE]", status.get_message()) - - def test_validate_uAttributes_for_publish_message_payload_invalid_ttl(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(-1).build() - + def test_validate_uAttributes_for_publish_message_payload_invalid_type( + self, + ): + attributes = UAttributesBuilder.response( + build_source(), + build_sink(), + UPriority.UPRIORITY_CS0, + Factories.UPROTOCOL.create(), + ).build() validator = Validators.PUBLISH.validator() status = validator.validate(attributes) self.assertTrue(status.is_failure()) - self.assertEqual("Invalid TTL [-1]", status.get_message()) - - def test_validate_uAttributes_for_publish_message_payload_invalid_sink(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withSink(UUri()).build() + self.assertEqual( + "Wrong Attribute Type [UMESSAGE_TYPE_RESPONSE]", + status.get_message(), + ) + + def test_validate_uAttributes_for_publish_message_payload_invalid_ttl( + self, + ): + with self.assertRaises(ValueError) as context: + UAttributesBuilder.publish( + build_source(), UPriority.UPRIORITY_CS0 + ).withTtl(-1).build() + self.assertTrue("Value out of range: -1" in context.exception) + + def test_validate_uAttributes_for_publish_message_payload_invalid_sink( + self, + ): + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withSink(UUri()) + .build() + ) validator = Validators.PUBLISH.validator() status = validator.validate(attributes) self.assertTrue(status.is_failure()) self.assertEqual("Uri is empty.", status.get_message()) - def test_validate_uAttributes_for_publish_message_payload_invalid_permission_level(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withPermissionLevel(-42).build() - validator = Validators.PUBLISH.validator() - status = validator.validate(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Invalid Permission Level", status.get_message()) - - def test_validate_uAttributes_for_publish_message_payload_invalid_communication_status(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withCommStatus(-42).build() - validator = Validators.PUBLISH.validator() - status = validator.validate(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Invalid Communication Status Code", status.get_message()) + def test_validate_uAttributes_for_publish_message_payload_invalid_permission_level( + self, + ): + with self.assertRaises(ValueError) as context: + UAttributesBuilder.publish( + build_source(), UPriority.UPRIORITY_CS0 + ).withPermissionLevel(-42).build() + self.assertTrue("Value out of range: -42" in context.exception) # def test_validate_uAttributes_for_publish_message_payload_invalid_request_id(self): # uuid = java.util.UUID.randomUUID() @@ -128,62 +169,81 @@ def test_validate_uAttributes_for_publish_message_payload_invalid_communication_ # self.assertEqual("Invalid UUID", status.get_message()) def test_validate_uAttributes_for_rpc_request_message_payload(self): - attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS4, 1000).build() + attributes = UAttributesBuilder.request( + build_source(), build_sink(), UPriority.UPRIORITY_CS4, 1000 + ).build() validator = Validators.REQUEST.validator() status = validator.validate(attributes) self.assertTrue(status.is_success()) self.assertEqual("", status.get_message()) def test_validate_uAttributes_for_rpc_request_message_payload_alls(self): - attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS4, 1000).withPermissionLevel( - 2).withCommStatus(3).withReqId(Factories.UPROTOCOL.create()).build() + attributes = ( + UAttributesBuilder.request( + build_source(), build_sink(), UPriority.UPRIORITY_CS4, 1000 + ) + .withPermissionLevel(2) + .withCommStatus(3) + .withReqId(Factories.UPROTOCOL.create()) + .build() + ) validator = Validators.REQUEST.validator() status = validator.validate(attributes) self.assertTrue(status.is_success()) self.assertEqual("", status.get_message()) - def test_validate_uAttributes_for_rpc_request_message_payload_invalid_type(self): - attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, - Factories.UPROTOCOL.create()).withTtl(1000).build() + def test_validate_uAttributes_for_rpc_request_message_payload_invalid_type( + self, + ): + attributes = ( + UAttributesBuilder.response( + build_source(), + build_sink(), + UPriority.UPRIORITY_CS4, + Factories.UPROTOCOL.create(), + ) + .withTtl(1000) + .build() + ) validator = Validators.REQUEST.validator() status = validator.validate(attributes) self.assertTrue(status.is_failure()) - self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_RESPONSE]", status.get_message()) - - def test_validate_uAttributes_for_rpc_request_message_payload_invalid_ttl(self): - attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS4, -1).build() - - validator = Validators.REQUEST.validator() - status = validator.validate(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Invalid TTL [-1]", status.get_message()) - - def test_validate_uAttributes_for_rpc_request_message_payload_invalid_sink(self): - attributes = UAttributesBuilder.request(build_source(), UUri(), UPriority.UPRIORITY_CS4, 1000).build() + self.assertEqual( + "Wrong Attribute Type [UMESSAGE_TYPE_RESPONSE]", + status.get_message(), + ) + + def test_validate_uAttributes_for_rpc_request_message_payload_invalid_ttl( + self, + ): + with self.assertRaises(ValueError) as context: + UAttributesBuilder.request( + build_source(), build_sink(), UPriority.UPRIORITY_CS4, -1 + ).build() + self.assertTrue("Value out of range: -1" in context.exception) + + def test_validate_uAttributes_for_rpc_request_message_payload_invalid_sink( + self, + ): + attributes = UAttributesBuilder.request( + build_source(), UUri(), UPriority.UPRIORITY_CS4, 1000 + ).build() validator = Validators.REQUEST.validator() status = validator.validate(attributes) self.assertTrue(status.is_failure()) self.assertEqual("Uri is empty.", status.get_message()) - def test_validate_uAttributes_for_rpc_request_message_payload_invalid_permission_level(self): - attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS4, 1000).withPermissionLevel( - -42).build() - - validator = Validators.REQUEST.validator() - status = validator.validate(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Invalid Permission Level", status.get_message()) - - def test_validate_uAttributes_for_rpc_request_message_payload_invalid_communication_status(self): - attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS4, 1000).withCommStatus(-42).build() - - validator = Validators.REQUEST.validator() - status = validator.validate(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Invalid Communication Status Code", status.get_message()) + def test_validate_uAttributes_for_rpc_request_message_payload_invalid_permission_level( + self, + ): + with self.assertRaises(ValueError) as context: + UAttributesBuilder.request( + build_source(), build_sink(), UPriority.UPRIORITY_CS4, 1000 + ).withPermissionLevel(-42).build() + self.assertTrue("Value out of range: -42" in context.exception) # def test_validate_uAttributes_for_rpc_request_message_payload_invalid_request_id(self): # uuid_java = java.util.UUID.randomUUID() @@ -197,8 +257,12 @@ def test_validate_uAttributes_for_rpc_request_message_payload_invalid_communicat # self.assertEqual("Invalid UUID", status.get_message()) def test_validate_uAttributes_for_rpc_response_message_payload(self): - attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, - Factories.UPROTOCOL.create()).build() + attributes = UAttributesBuilder.response( + build_source(), + build_sink(), + UPriority.UPRIORITY_CS4, + Factories.UPROTOCOL.create(), + ).build() validator = Validators.RESPONSE.validator() status = validator.validate(attributes) @@ -206,60 +270,82 @@ def test_validate_uAttributes_for_rpc_response_message_payload(self): self.assertEqual("", status.get_message()) def test_validate_uAttributes_for_rpc_response_message_payload_alls(self): - attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, - Factories.UPROTOCOL.create()).withPermissionLevel(2).withCommStatus( - 3).build() + attributes = ( + UAttributesBuilder.response( + build_source(), + build_sink(), + UPriority.UPRIORITY_CS4, + Factories.UPROTOCOL.create(), + ) + .withPermissionLevel(2) + .withCommStatus(3) + .build() + ) validator = Validators.RESPONSE.validator() status = validator.validate(attributes) self.assertTrue(status.is_success()) self.assertEqual("", status.get_message()) - def test_validate_uAttributes_for_rpc_response_message_payload_invalid_type(self): - attributes = UAttributesBuilder.notification(build_source(), build_sink(), UPriority.UPRIORITY_CS4).build() - - validator = Validators.RESPONSE.validator() - status = validator.validate(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_PUBLISH],Missing correlationId", status.get_message()) - - def test_validate_uAttributes_for_rpc_response_message_payload_invalid_ttl(self): - attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, - Factories.UPROTOCOL.create()).withTtl(-1).build() - - validator = Validators.RESPONSE.validator() - status = validator.validate(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Invalid TTL [-1]", status.get_message()) - - def test_validate_uAttributes_for_rpc_response_message_payload_missing_sink_and_missing_requestId(self): - attributes = UAttributesBuilder.response(build_source(), UUri(), UPriority.UPRIORITY_CS4, UUID()).build() - - validator = Validators.RESPONSE.validator() - status = validator.validate(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Missing Sink,Missing correlationId", status.get_message()) - - def test_validate_uAttributes_for_rpc_response_message_payload_invalid_permission_level(self): - attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, - Factories.UPROTOCOL.create()).withPermissionLevel(-42).build() + def test_validate_uAttributes_for_rpc_response_message_payload_invalid_type( + self, + ): + attributes = UAttributesBuilder.notification( + build_source(), build_sink(), UPriority.UPRIORITY_CS4 + ).build() validator = Validators.RESPONSE.validator() status = validator.validate(attributes) self.assertTrue(status.is_failure()) - self.assertEqual("Invalid Permission Level", status.get_message()) - - def test_validate_uAttributes_for_rpc_response_message_payload_invalid_communication_status(self): - attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, - Factories.UPROTOCOL.create()).withCommStatus(-42).build() + self.assertEqual( + "Wrong Attribute Type [UMESSAGE_TYPE_NOTIFICATION],Missing correlationId", + status.get_message(), + ) + + def test_validate_uAttributes_for_rpc_response_message_payload_invalid_ttl( + self, + ): + with self.assertRaises(ValueError) as context: + UAttributesBuilder.response( + build_source(), + build_sink(), + UPriority.UPRIORITY_CS4, + Factories.UPROTOCOL.create(), + ).withTtl(-1).build() + self.assertTrue("Value out of range: -1" in context.exception) + + def test_validate_uAttributes_for_rpc_response_message_payload_missing_sink_and_missing_requestId( + self, + ): + attributes = UAttributesBuilder.response( + build_source(), UUri(), UPriority.UPRIORITY_CS4, UUID() + ).build() validator = Validators.RESPONSE.validator() status = validator.validate(attributes) self.assertTrue(status.is_failure()) - self.assertEqual("Invalid Communication Status Code", status.get_message()) - - def test_validate_uAttributes_for_rpc_response_message_payload_missing_request_id(self): - attributes = UAttributesBuilder.response(build_source(), build_sink(), UPriority.UPRIORITY_CS4, UUID()).build() + self.assertEqual( + "Missing Sink,Missing correlationId", status.get_message() + ) + + def test_validate_uAttributes_for_rpc_response_message_payload_invalid_permission_level( + self, + ): + with self.assertRaises(ValueError) as context: + UAttributesBuilder.response( + build_source(), + build_sink(), + UPriority.UPRIORITY_CS4, + Factories.UPROTOCOL.create(), + ).withPermissionLevel(-42).build() + self.assertTrue("Value out of range: -42" in context.exception) + + def test_validate_uAttributes_for_rpc_response_message_payload_missing_request_id( + self, + ): + attributes = UAttributesBuilder.response( + build_source(), build_sink(), UPriority.UPRIORITY_CS4, UUID() + ).build() validator = Validators.RESPONSE.validator() status = validator.validate(attributes) @@ -278,26 +364,48 @@ def test_validate_uAttributes_for_rpc_response_message_payload_missing_request_i # self.assertEqual(f"Invalid correlationId [{reqid}]", status.get_message()) # ---- - def test_validate_uAttributes_for_publish_message_payload_not_expired(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).build() + def test_validate_uAttributes_for_publish_message_payload_not_expired( + self, + ): + attributes = UAttributesBuilder.publish( + build_source(), UPriority.UPRIORITY_CS0 + ).build() validator = Validators.PUBLISH.validator() self.assertFalse(validator.is_expired(attributes)) - def test_validate_uAttributes_for_publish_message_payload_not_expired_withTtl_zero(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(0).build() + def test_validate_uAttributes_for_publish_message_payload_not_expired_withTtl_zero( + self, + ): + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withTtl(0) + .build() + ) validator = Validators.PUBLISH.validator() self.assertFalse(validator.is_expired(attributes)) - def test_validate_uAttributes_for_publish_message_payload_not_expired_withTtl(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(10000).build() + def test_validate_uAttributes_for_publish_message_payload_not_expired_withTtl( + self, + ): + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withTtl(10000) + .build() + ) validator = Validators.PUBLISH.validator() self.assertFalse(validator.is_expired(attributes)) - def test_validate_uAttributes_for_publish_message_payload_expired_withTtl(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(1).build() + def test_validate_uAttributes_for_publish_message_payload_expired_withTtl( + self, + ): + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withTtl(1) + .build() + ) time.sleep(0.8) @@ -307,15 +415,18 @@ def test_validate_uAttributes_for_publish_message_payload_expired_withTtl(self): # ---- def test_validating_publish_invalid_ttl_attribute(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(-1).build() - - validator = Validators.PUBLISH.validator() - status = validator.validate_ttl(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Invalid TTL [-1]", status.get_message()) + with self.assertRaises(ValueError) as context: + UAttributesBuilder.publish( + build_source(), UPriority.UPRIORITY_CS0 + ).withTtl(-1).build() + self.assertTrue("Value out of range: -1" in context.exception) def test_validating_valid_ttl_attribute(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withTtl(100).build() + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withTtl(100) + .build() + ) validator = Validators.PUBLISH.validator() status = validator.validate_ttl(attributes) @@ -323,17 +434,27 @@ def test_validating_valid_ttl_attribute(self): def test_validating_invalid_sink_attribute(self): uri = LongUriSerializer().deserialize("//") - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withSink(uri).build() + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withSink(uri) + .build() + ) validator = Validators.PUBLISH.validator() status = validator.validate_sink(attributes) self.assertTrue(status.is_failure()) self.assertEqual("Uri is empty.", status.get_message()) def test_validating_valid_sink_attribute(self): - uri = UUri(authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), - entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), - resource=UResourceBuilder.for_rpc_response()) - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withSink(uri).build() + uri = UUri( + authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), + entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), + resource=UResourceBuilder.for_rpc_response(), + ) + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withSink(uri) + .build() + ) validator = Validators.PUBLISH.validator() status = validator.validate_sink(attributes) @@ -352,53 +473,50 @@ def test_validating_valid_sink_attribute(self): # self.assertEqual("Invalid UUID", status.get_message()) def test_validating_valid_ReqId_attribute(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withReqId( - Factories.UPROTOCOL.create()).build() + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withReqId(Factories.UPROTOCOL.create()) + .build() + ) validator = Validators.PUBLISH.validator() status = validator.validate_req_id(attributes) self.assertEqual(ValidationResult.success(), status) def test_validating_invalid_PermissionLevel_attribute(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withPermissionLevel(-1).build() - - validator = Validators.PUBLISH.validator() - status = validator.validate_permission_level(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Invalid Permission Level", status.get_message()) + with self.assertRaises(ValueError) as context: + UAttributesBuilder.publish( + build_source(), UPriority.UPRIORITY_CS0 + ).withPermissionLevel(-1).build() + self.assertTrue("Value out of range: -1" in context.exception) def test_validating_valid_PermissionLevel_attribute(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withPermissionLevel(3).build() + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withPermissionLevel(3) + .build() + ) validator = Validators.PUBLISH.validator() status = validator.validate_permission_level(attributes) self.assertEqual(ValidationResult.success(), status) def test_validating_valid_PermissionLevel_attribute_invalid(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withPermissionLevel(0).build() + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withPermissionLevel(0) + .build() + ) validator = Validators.PUBLISH.validator() status = validator.validate_permission_level(attributes) self.assertTrue(status.is_failure()) self.assertEqual("Invalid Permission Level", status.get_message()) - def test_validating_invalid_commstatus_attribute(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withCommStatus(100).build() - - validator = Validators.PUBLISH.validator() - status = validator.validate_comm_status(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Invalid Communication Status Code", status.get_message()) - - def test_validating_valid_commstatus_attribute(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withCommStatus(UCode.ABORTED).build() - - validator = Validators.PUBLISH.validator() - status = validator.validate_comm_status(attributes) - self.assertEqual(ValidationResult.success(), status) - def test_validating_request_message_types(self): - attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS6, 100).build() + attributes = UAttributesBuilder.request( + build_source(), build_sink(), UPriority.UPRIORITY_CS6, 100 + ).build() validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Request", str(validator)) @@ -407,54 +525,72 @@ def test_validating_request_message_types(self): self.assertEqual("", status.get_message()) def test_validating_request_validator_with_wrong_messagetype(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS6).build() + attributes = UAttributesBuilder.publish( + build_source(), UPriority.UPRIORITY_CS6 + ).build() validator = Validators.REQUEST.validator() self.assertEqual("UAttributesValidator.Request", str(validator)) status = validator.validate(attributes) self.assertTrue(status.is_failure()) - self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_PUBLISH],Missing TTL,Missing Sink", status.get_message()) + self.assertEqual( + "Wrong Attribute Type [UMESSAGE_TYPE_PUBLISH],Missing TTL,Missing Sink", + status.get_message(), + ) def test_validating_request_validator_with_wrong_bad_ttl(self): - attributes = UAttributesBuilder.request(build_source(), LongUriSerializer().deserialize("/hartley/1/rpc.response"), - UPriority.UPRIORITY_CS6, -1).build() - - validator = Validators.REQUEST.validator() - self.assertEqual("UAttributesValidator.Request", str(validator)) - status = validator.validate(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Invalid TTL [-1]", status.get_message()) + with self.assertRaises(ValueError) as context: + UAttributesBuilder.request( + build_source(), + LongUriSerializer().deserialize("/hartley/1/rpc.response"), + UPriority.UPRIORITY_CS6, + -1, + ).build() + self.assertTrue("Value out of range: -1" in context.exception) def test_validating_response_validator_with_wrong_bad_ttl(self): - attributes = UAttributesBuilder.response(build_source(), LongUriSerializer().deserialize("/hartley/1/rpc.response"), - UPriority.UPRIORITY_CS6, Factories.UPROTOCOL.create()).withTtl(-1).build() - - validator = Validators.RESPONSE.validator() - self.assertEqual("UAttributesValidator.Response", str(validator)) - status = validator.validate(attributes) - self.assertTrue(status.is_failure()) - self.assertEqual("Invalid TTL [-1]", status.get_message()) + with self.assertRaises(ValueError) as context: + UAttributesBuilder.response( + build_source(), + LongUriSerializer().deserialize("/hartley/1/rpc.response"), + UPriority.UPRIORITY_CS6, + Factories.UPROTOCOL.create(), + ).withTtl(-1).build() + self.assertTrue("Value out of range: -1" in context.exception) def test_validating_publish_validator_with_wrong_messagetype(self): - attributes = UAttributesBuilder.request(build_source(), build_sink(), UPriority.UPRIORITY_CS6, 1000).build() + attributes = UAttributesBuilder.request( + build_source(), build_sink(), UPriority.UPRIORITY_CS6, 1000 + ).build() validator = Validators.PUBLISH.validator() self.assertEqual("UAttributesValidator.Publish", str(validator)) status = validator.validate(attributes) self.assertTrue(status.is_failure()) - self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_REQUEST]", status.get_message()) + self.assertEqual( + "Wrong Attribute Type [UMESSAGE_TYPE_REQUEST]", + status.get_message(), + ) def test_validating_response_validator_with_wrong_messagetype(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS6).build() + attributes = UAttributesBuilder.publish( + build_source(), UPriority.UPRIORITY_CS6 + ).build() validator = Validators.RESPONSE.validator() self.assertEqual("UAttributesValidator.Response", str(validator)) status = validator.validate(attributes) self.assertTrue(status.is_failure()) - self.assertEqual("Wrong Attribute Type [UMESSAGE_TYPE_PUBLISH],Missing Sink,Missing correlationId", - status.get_message()) + self.assertEqual( + "Wrong Attribute Type [UMESSAGE_TYPE_PUBLISH],Missing Sink,Missing correlationId", + status.get_message(), + ) def test_validating_request_containing_token(self): - attributes = UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0).withToken("null").build() + attributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withToken("null") + .build() + ) validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Publish", str(validator)) @@ -463,7 +599,9 @@ def test_validating_request_containing_token(self): def test_valid_request_methoduri_in_sink(self): sink = LongUriSerializer().deserialize("/test.service/1/rpc.method") - attributes = UAttributesBuilder.request(build_source(), sink, UPriority.UPRIORITY_CS0, 3000).build() + attributes = UAttributesBuilder.request( + build_source(), sink, UPriority.UPRIORITY_CS0, 3000 + ).build() validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Request", str(validator)) status = validator.validate(attributes) @@ -471,15 +609,25 @@ def test_valid_request_methoduri_in_sink(self): def test_invalid_request_methoduri_in_sink(self): sink = LongUriSerializer().deserialize("/test.client/1/test.response") - attributes = UAttributesBuilder.request(build_source(), sink, UPriority.UPRIORITY_CS0, 3000).build() + attributes = UAttributesBuilder.request( + build_source(), sink, UPriority.UPRIORITY_CS0, 3000 + ).build() validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Request", str(validator)) status = validator.validate(attributes) - self.assertEqual("Invalid RPC method uri. Uri should be the method to be called, or method from response.", status.get_message()) + self.assertEqual( + "Invalid RPC method uri. Uri should be the method to be called, or method from response.", + status.get_message(), + ) def test_valid_response_uri_in_sink(self): sink = LongUriSerializer().deserialize("/test.client/1/rpc.response") - attributes = UAttributesBuilder.response(build_source(), sink, UPriority.UPRIORITY_CS0, Factories.UPROTOCOL.create()).build() + attributes = UAttributesBuilder.response( + build_source(), + sink, + UPriority.UPRIORITY_CS0, + Factories.UPROTOCOL.create(), + ).build() validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Response", str(validator)) status = validator.validate(attributes) @@ -487,13 +635,17 @@ def test_valid_response_uri_in_sink(self): def test_invalid_response_uri_in_sink(self): sink = LongUriSerializer().deserialize("/test.client/1/rpc.method") - attributes = UAttributesBuilder.response(build_source(), sink, UPriority.UPRIORITY_CS0, Factories.UPROTOCOL.create()).build() + attributes = UAttributesBuilder.response( + build_source(), + sink, + UPriority.UPRIORITY_CS0, + Factories.UPROTOCOL.create(), + ).build() validator = UAttributesValidator.get_validator(attributes) self.assertEqual("UAttributesValidator.Response", str(validator)) status = validator.validate(attributes) self.assertEqual("Invalid RPC response type.", status.get_message()) - -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_uri/test_factory/__init__.py b/tests/test_uri/test_factory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_uri/test_factory/test_uentity_factory.py b/tests/test_uri/test_factory/test_uentity_factory.py new file mode 100644 index 0000000..d0e2fa7 --- /dev/null +++ b/tests/test_uri/test_factory/test_uentity_factory.py @@ -0,0 +1,124 @@ +# ------------------------------------------------------------------------- + +# Copyright (c) 2023 General Motors GTO LLC +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# SPDX-FileType: SOURCE +# SPDX-FileCopyrightText: 2023 General Motors GTO LLC +# SPDX-License-Identifier: Apache-2.0 + +# ------------------------------------------------------------------------- +import unittest + +from uprotocol.proto.core.usubscription.v3.usubscription_pb2 import ( + DESCRIPTOR as USubscriptionFileDescriptor, +) +from uprotocol.proto.core.udiscovery.v3.udiscovery_pb2 import ( + DESCRIPTOR as UDiscoveryFileDescriptor, +) +from uprotocol.proto.core.utwin.v2.utwin_pb2 import ( + DESCRIPTOR as UTwinFileDescriptor, +) +from uprotocol.proto.uri_pb2 import UEntity +from uprotocol.uri.factory.uentity_factory import UEntityFactory + +from google.protobuf.descriptor import ServiceDescriptor, FileDescriptor + + +class TestUEntityFactory(unittest.TestCase): + + def test_from_proto_given_usubscription_descriptor_return_uentity(self): + """ + Get default service values: + Example: in uSubscription.proto... + + // Subscription Service Interface definition + service uSubscription { + option (name) = "core.usubscription"; // Service name + option (version_major) = 3; + option (version_minor) = 0; + option (id) = 0; + ... + } + """ + file_descriptor: FileDescriptor = USubscriptionFileDescriptor + service_descriptor: ServiceDescriptor = ( + file_descriptor.services_by_name["uSubscription"] + ) + default_service_name: str = "core.usubscription" + default_version_major: str = 3 + default_version_minor: str = 0 + default_id = 0 + + actual_uentity: UEntity = UEntityFactory.from_proto(service_descriptor) + self.assertIsNotNone(actual_uentity) + + self.assertEqual(default_service_name, actual_uentity.name) + self.assertEqual(default_version_major, actual_uentity.version_major) + self.assertEqual(default_version_minor, actual_uentity.version_minor) + self.assertEqual(default_id, actual_uentity.id) + + def test_from_proto_given_udiscovery_descriptor_return_uentity(self): + file_descriptor: FileDescriptor = UDiscoveryFileDescriptor + service_descriptor: ServiceDescriptor = ( + file_descriptor.services_by_name["uDiscovery"] + ) + default_service_name: str = "core.udiscovery" + default_version_major: str = 3 + default_version_minor: str = 0 + default_id = 1 + + actual_uentity: UEntity = UEntityFactory.from_proto(service_descriptor) + self.assertIsNotNone(actual_uentity) + + self.assertEqual(default_service_name, actual_uentity.name) + self.assertEqual(default_version_major, actual_uentity.version_major) + self.assertEqual(default_version_minor, actual_uentity.version_minor) + self.assertEqual(default_id, actual_uentity.id) + + def test_from_proto_given_utwin_descriptor_return_uentity(self): + file_descriptor: FileDescriptor = UTwinFileDescriptor + service_descriptor: ServiceDescriptor = ( + file_descriptor.services_by_name["uTwin"] + ) + default_service_name: str = "core.utwin" + default_version_major: str = 2 + default_version_minor: str = 0 + default_id = 26 + + actual_uentity: UEntity = UEntityFactory.from_proto(service_descriptor) + self.assertIsNotNone(actual_uentity) + + self.assertEqual(default_service_name, actual_uentity.name) + self.assertEqual(default_version_major, actual_uentity.version_major) + self.assertEqual(default_version_minor, actual_uentity.version_minor) + self.assertEqual(default_id, actual_uentity.id) + + def test_from_proto_given_none_return_empty_uentity(self): + empty_service_name: str = "" + empty_version_major: str = 0 + empty_version_minor: str = 0 + empty_id = 0 + + actual_uentity: UEntity = UEntityFactory.from_proto(None) + self.assertIsNotNone(actual_uentity) + + self.assertEqual(empty_service_name, actual_uentity.name) + self.assertEqual(empty_version_major, actual_uentity.version_major) + self.assertEqual(empty_version_minor, actual_uentity.version_minor) + self.assertEqual(empty_id, actual_uentity.id) diff --git a/tests/test_uri/test_factory/test_uresource_builder.py b/tests/test_uri/test_factory/test_uresource_builder.py new file mode 100644 index 0000000..81ee3e8 --- /dev/null +++ b/tests/test_uri/test_factory/test_uresource_builder.py @@ -0,0 +1,63 @@ +import unittest +from uprotocol.uri.factory.uresource_builder import UResourceBuilder +from uprotocol.proto.uprotocol_options_pb2 import UServiceTopic + + +class TestUResourceBuilder(unittest.TestCase): + + def test_from_id_valid_id(self): + id = 0 + resource = UResourceBuilder.from_id(id) + self.assertEqual(resource.name, "rpc") + self.assertEqual(resource.instance, "response") + self.assertEqual(resource.id, 0) + + def test_from_id_invalid_id(self): + id = -1 + with self.assertRaises(ValueError) as context: + UResourceBuilder.from_id(id) + self.assertEqual(str(context.exception), "Value out of range: -1") + + def test_from_id_valid_id_below_min_topic_id(self): + id = 0x7FFF + resource = UResourceBuilder.from_id(id) + self.assertEqual(resource.name, "rpc") + self.assertEqual(resource.instance, "") + self.assertEqual(resource.id, 0x7FFF) + + def test_from_id_valid_id_above_min_topic_id(self): + id = 0x8000 + resource = UResourceBuilder.from_id(id) + self.assertEqual(resource.name, "") + self.assertEqual(resource.instance, "") + self.assertEqual(resource.id, 0x8000) + + def test_from_uservice_topic_valid_service_topic(self): + topic = UServiceTopic( + name="SubscriptionChange", id=0, message="Update" + ) + resource = UResourceBuilder.from_uservice_topic(topic) + self.assertEqual(resource.name, "SubscriptionChange") + self.assertEqual(resource.instance, "") + self.assertEqual(resource.id, 0) + self.assertEqual(resource.message, "Update") + + def test_from_uservice_topic_valid_service_topic_with_instance(self): + topic = UServiceTopic( + name="door.front_left", id=0x8000, message="Door" + ) + resource = UResourceBuilder.from_uservice_topic(topic) + self.assertEqual(resource.name, "door") + self.assertEqual(resource.instance, "front_left") + self.assertEqual(resource.id, 0x8000) + self.assertEqual(resource.message, "Door") + + def test_from_uservice_topic_invalid_service_topic(self): + topic = None + with self.assertRaises(ValueError) as context: + UResourceBuilder.from_uservice_topic(topic) + self.assertEqual(str(context.exception), "topic cannot be None.") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_uri/test_serializer/test_ipaddress.py b/tests/test_uri/test_serializer/test_ipaddress.py new file mode 100644 index 0000000..b799b72 --- /dev/null +++ b/tests/test_uri/test_serializer/test_ipaddress.py @@ -0,0 +1,135 @@ +import unittest +from uprotocol.uri.serializer.ipaddress import IpAddress +import socket + + +class TestIpAddress(unittest.TestCase): + + def test_to_bytes_with_null_ip_address(self): + bytes_ = IpAddress.to_bytes(None) + self.assertEqual(len(bytes_), 0) + + def test_to_bytes_with_empty_ip_address(self): + bytes_ = IpAddress.to_bytes("") + self.assertEqual(len(bytes_), 0) + + def test_to_bytes_with_invalid_ip_address(self): + bytes_ = IpAddress.to_bytes("invalid") + self.assertEqual(len(bytes_), 0) + + def test_to_bytes_with_valid_ipv4_address(self): + bytes_ = IpAddress.to_bytes("192.168.1.100") + self.assertEqual(len(bytes_), 4) + self.assertEqual(socket.inet_ntoa(bytes_), "192.168.1.100") + + def test_to_bytes_with_valid_ipv6_address(self): + bytes_ = IpAddress.to_bytes("2001:db8:85a3:0:0:8a2e:370:7334") + self.assertEqual(len(bytes_), 16) + self.assertEqual( + socket.inet_ntop(socket.AF_INET6, bytes_), + "2001:db8:85a3::8a2e:370:7334", + ) + + def test_is_valid_with_null_ip_address(self): + self.assertFalse(IpAddress.is_valid(None)) + + def test_is_valid_with_empty_ip_address(self): + self.assertFalse(IpAddress.is_valid("")) + + def test_is_valid_with_invalid_ip_address(self): + self.assertFalse(IpAddress.is_valid("invalid")) + + def test_is_valid_with_valid_ipv4_address(self): + self.assertTrue(IpAddress.is_valid("192.168.1.100")) + + def test_is_valid_with_valid_ipv6_address(self): + self.assertTrue(IpAddress.is_valid("2001:db8:85a3:0:0:8a2e:370:7334")) + + def test_is_valid_with_invalid_ipv4_address(self): + self.assertFalse(IpAddress.is_valid("192.168.1.2586")) + + def test_is_valid_with_invalid_ipv4_passing_large_number(self): + self.assertFalse( + IpAddress.is_valid( + "2875687346587326457836485623874658723645782364875623847562378465.1.1.abc" + ) + ) + + def test_is_valid_with_invalid_ipv4_passing_negative(self): + self.assertFalse(IpAddress.is_valid("-1.1.1.abc")) + + def test_is_valid_with_invalid_ipv4_passing_characters(self): + self.assertFalse(IpAddress.is_valid("1.1.1.abc")) + + def test_is_valid_with_invalid_ipv6_address(self): + self.assertFalse(IpAddress.is_valid("ZX1:db8::")) + + def test_is_valid_with_invalid_ipv6_address_passing_weird_values(self): + self.assertFalse(IpAddress.is_valid("-1:ZX1:db8::")) + + def test_is_valid_with_invalid_ipv6_address_that_has_way_too_many_groups( + self, + ): + self.assertFalse( + IpAddress.is_valid("2001:db8:85a3:0:0:8a2e:370:7334:1234") + ) + + def test_is_valid_with_valid_ipv6_address_that_has_8_groups(self): + self.assertTrue(IpAddress.is_valid("2001:db8:85a3:0:0:8a2e:370:7334")) + + def test_is_valid_with_invalid_ipv6_address_with_too_many_empty_groups( + self, + ): + self.assertFalse(IpAddress.is_valid("2001::85a3::8a2e::7334")) + + def test_is_valid_with_valid_ipv6_address_with_one_empty_group(self): + self.assertTrue(IpAddress.is_valid("2001:db8:85a3::8a2e:370:7334")) + + def test_is_valid_with_invalid_ipv6_address_that_ends_with_a_colon(self): + self.assertFalse(IpAddress.is_valid("2001:db8:85a3::8a2e:370:7334:")) + + def test_is_valid_with_invalid_ipv6_address_that_doesnt_have_double_colon_and_not_enough_groups( + self, + ): + self.assertFalse(IpAddress.is_valid("2001:db8:85a3:0:0:8a2e:370")) + + def test_is_valid_with_valid_ipv6_address_that_ends_with_double_colons( + self, + ): + self.assertTrue(IpAddress.is_valid("2001:db8:85a3:8a2e::")) + + def test_is_valid_with_all_number_values(self): + self.assertTrue(IpAddress.is_valid("123:456:7890::")) + + def test_is_valid_with_valid_lowercase_hexidecimal_letters(self): + self.assertTrue(IpAddress.is_valid("abcd:ef12:3456::")) + + def test_is_valid_with_valid_uppercase_hexidecimal_letters(self): + self.assertTrue(IpAddress.is_valid("ABCD:EF12:3456::")) + + def test_is_valid_with_invalid_uppercase_hexidecimal_letters(self): + self.assertFalse(IpAddress.is_valid("ABCD:EFG2:3456::")) + + def test_is_valid_with_invalid_lowercase_hexidecimal_letters(self): + self.assertFalse(IpAddress.is_valid("-C=[]:E{12g:3456")) + + def test_is_valid_with_invalid_digit1(self): + self.assertFalse(IpAddress.is_valid("aC=[]:E{12g:3456")) + + def test_is_valid_with_invalid_digit2(self): + self.assertFalse(IpAddress.is_valid("aCd[:E{12g:3456")) + + def test_is_valid_with_invalid_digit3(self): + self.assertFalse(IpAddress.is_valid("aCd:E{2g:3456")) + + def test_is_valid_with_invalid_ipv6_address_that_has_double_colon_and_8_groups( + self, + ): + self.assertTrue(IpAddress.is_valid("dead:beef:85a3::0:0:8a2e:370")) + + def test_is_valid_with_invalid_ipv6_address_that_has_only_7_groups(self): + self.assertFalse(IpAddress.is_valid("dead:beef:85a3:0:0:8a2e:370")) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_uri/test_serializer/test_longuriserializer.py b/tests/test_uri/test_serializer/test_longuriserializer.py index fddac3e..b64ea95 100644 --- a/tests/test_uri/test_serializer/test_longuriserializer.py +++ b/tests/test_uri/test_serializer/test_longuriserializer.py @@ -36,7 +36,10 @@ class TestLongUriSerializer(unittest.TestCase): def test_using_the_serializers(self): - uri = UUri(entity=UEntity(name="hartley"), resource=UResourceBuilder.for_rpc_request("raise")) + uri = UUri( + entity=UEntity(name="hartley"), + resource=UResourceBuilder.for_rpc_request("raise"), + ) str_uri = LongUriSerializer().serialize(uri) self.assertEqual("/hartley//rpc.raise", str_uri) @@ -50,7 +53,7 @@ def test_parse_protocol_uri_when_is_null(self): self.assertFalse(UriValidator.is_long_form(uri)) def test_parse_protocol_uri_when_is_empty_string(self): - uri = '' + uri = "" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_empty(uuri)) uri2 = LongUriSerializer().serialize(None) @@ -59,27 +62,27 @@ def test_parse_protocol_uri_when_is_empty_string(self): def test_parse_protocol_uri_with_schema_and_slash(self): uri = "/" uuri = LongUriSerializer().deserialize(uri) - self.assertFalse(uuri.HasField('authority')) + self.assertFalse(uuri.HasField("authority")) self.assertTrue(UriValidator.is_empty(uuri)) - self.assertFalse(uuri.HasField('resource')) - self.assertFalse(uuri.HasField('entity')) + self.assertFalse(uuri.HasField("resource")) + self.assertFalse(uuri.HasField("entity")) uri2 = LongUriSerializer().serialize(UUri()) self.assertTrue(len(uri2) == 0) def test_parse_protocol_uri_with_schema_and_double_slash(self): uri = "//" uuri = LongUriSerializer().deserialize(uri) - self.assertFalse(uuri.HasField('authority')) - self.assertFalse(uuri.HasField('resource')) - self.assertFalse(uuri.HasField('entity')) + self.assertFalse(uuri.HasField("authority")) + self.assertFalse(uuri.HasField("resource")) + self.assertFalse(uuri.HasField("entity")) self.assertTrue(UriValidator.is_empty(uuri)) def test_parse_protocol_uri_with_schema_and_3_slash_and_something(self): uri = "///body.access" uuri = LongUriSerializer().deserialize(uri) - self.assertFalse(uuri.HasField('authority')) - self.assertFalse(uuri.HasField('resource')) - self.assertFalse(uuri.HasField('entity')) + self.assertFalse(uuri.HasField("authority")) + self.assertFalse(uuri.HasField("resource")) + self.assertFalse(uuri.HasField("entity")) self.assertTrue(UriValidator.is_empty(uuri)) self.assertNotEqual("body.access", uuri.entity.name) self.assertEqual(0, uuri.entity.version_major) @@ -89,8 +92,8 @@ def test_parse_protocol_uri_with_schema_and_4_slash_and_something(self): uuri = LongUriSerializer().deserialize(uri) self.assertFalse(UriValidator.is_remote(uuri.authority)) - self.assertFalse(uuri.HasField('resource')) - self.assertFalse(uuri.HasField('entity')) + self.assertFalse(uuri.HasField("resource")) + self.assertFalse(uuri.HasField("entity")) self.assertTrue(len(uuri.entity.name) == 0) self.assertEqual(0, uuri.entity.version_major) @@ -98,8 +101,8 @@ def test_parse_protocol_uri_with_schema_and_5_slash_and_something(self): uri = "/////body.access" uuri = LongUriSerializer().deserialize(uri) self.assertFalse(UriValidator.is_remote(uuri.authority)) - self.assertFalse(uuri.HasField('resource')) - self.assertFalse(uuri.HasField('entity')) + self.assertFalse(uuri.HasField("resource")) + self.assertFalse(uuri.HasField("entity")) self.assertTrue(UriValidator.is_empty(uuri)) def test_parse_protocol_uri_with_schema_and_6_slash_and_something(self): @@ -117,7 +120,7 @@ def test_parse_protocol_uri_with_local_service_no_version(self): self.assertEqual("body.access", uuri.entity.name) self.assertEqual(0, uuri.entity.version_major) self.assertEqual(0, uuri.entity.version_minor) - self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField("resource")) def test_parse_protocol_uri_with_local_service_with_version(self): uri = "/body.access/1" @@ -126,9 +129,11 @@ def test_parse_protocol_uri_with_local_service_with_version(self): self.assertFalse(UriValidator.is_remote(uuri.authority)) self.assertEqual("body.access", uuri.entity.name) self.assertEqual(1, uuri.entity.version_major) - self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField("resource")) - def test_parse_protocol_uri_with_local_service_no_version_with_resource_name_only(self): + def test_parse_protocol_uri_with_local_service_no_version_with_resource_name_only( + self, + ): uri = "/body.access//door" uuri = LongUriSerializer().deserialize(uri) @@ -140,7 +145,9 @@ def test_parse_protocol_uri_with_local_service_no_version_with_resource_name_onl self.assertTrue(len(uuri.resource.instance) == 0) self.assertTrue(len(uuri.resource.message) == 0) - def test_parse_protocol_uri_with_local_service_with_version_with_resource_name_only(self): + def test_parse_protocol_uri_with_local_service_with_version_with_resource_name_only( + self, + ): uri = "/body.access/1/door" uuri = LongUriSerializer().deserialize(uri) self.assertFalse(UriValidator.is_remote(uuri.authority)) @@ -150,7 +157,9 @@ def test_parse_protocol_uri_with_local_service_with_version_with_resource_name_o self.assertTrue(len(uuri.resource.instance) == 0) self.assertTrue(len(uuri.resource.message) == 0) - def test_parse_protocol_uri_with_local_service_no_version_with_resource_with_instance(self): + def test_parse_protocol_uri_with_local_service_no_version_with_resource_with_instance( + self, + ): uri = "/body.access//door.front_left" uuri = LongUriSerializer().deserialize(uri) self.assertFalse(UriValidator.is_remote(uuri.authority)) @@ -161,7 +170,9 @@ def test_parse_protocol_uri_with_local_service_no_version_with_resource_with_ins self.assertEqual("front_left", uuri.resource.instance) self.assertTrue(len(uuri.resource.message) == 0) - def test_parse_protocol_uri_with_local_service_with_version_with_resource_with_getMessage(self): + def test_parse_protocol_uri_with_local_service_with_version_with_resource_with_getMessage( + self, + ): uri = "/body.access/1/door.front_left" uuri = LongUriSerializer().deserialize(uri) self.assertFalse(UriValidator.is_remote(uuri.authority)) @@ -173,7 +184,9 @@ def test_parse_protocol_uri_with_local_service_with_version_with_resource_with_g self.assertEqual("front_left", uuri.resource.instance) self.assertTrue(len(uuri.resource.message) == 0) - def test_parse_protocol_uri_with_local_service_no_version_with_resource_with_instance_and_getMessage(self): + def test_parse_protocol_uri_with_local_service_no_version_with_resource_with_instance_and_getMessage( + self, + ): uri = "/body.access//door.front_left#Door" uuri = LongUriSerializer().deserialize(uri) self.assertFalse(UriValidator.is_remote(uuri.authority)) @@ -185,7 +198,9 @@ def test_parse_protocol_uri_with_local_service_no_version_with_resource_with_ins self.assertFalse(len(uuri.resource.message) == 0) self.assertEqual("Door", uuri.resource.message) - def test_parse_protocol_uri_with_local_service_with_version_with_resource_with_instance_and_getMessage(self): + def test_parse_protocol_uri_with_local_service_with_version_with_resource_with_instance_and_getMessage( + self, + ): uri = "/body.access/1/door.front_left#Door" uuri = LongUriSerializer().deserialize(uri) self.assertFalse(UriValidator.is_remote(uuri.authority)) @@ -221,21 +236,25 @@ def test_parse_protocol_rpc_uri_with_local_service_with_version(self): self.assertEqual("response", uuri.resource.instance) self.assertTrue(len(uuri.resource.message) == 0) - def test_parse_protocol_uri_with_remote_service_only_device_and_domain(self): + def test_parse_protocol_uri_with_remote_service_only_device_and_domain( + self, + ): uri = "//VCU.MY_CAR_VIN" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) self.assertFalse(len(uuri.authority.name) == 0) self.assertEqual("VCU.MY_CAR_VIN", uuri.authority.name) - def test_parse_protocol_uri_with_remote_service_only_device_and_cloud_domain(self): + def test_parse_protocol_uri_with_remote_service_only_device_and_cloud_domain( + self, + ): uri = "//cloud.uprotocol.example.com" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) self.assertFalse(len(uuri.authority.name) == 0) self.assertEqual("cloud.uprotocol.example.com", uuri.authority.name) - self.assertFalse(uuri.HasField('entity')) - self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField("entity")) + self.assertFalse(uuri.HasField("resource")) def test_parse_protocol_uri_with_remote_service_no_version(self): uri = "//VCU.MY_CAR_VIN/body.access" @@ -246,7 +265,7 @@ def test_parse_protocol_uri_with_remote_service_no_version(self): self.assertFalse(len(uuri.entity.name) == 0) self.assertEqual("body.access", uuri.entity.name) self.assertEqual(0, uuri.entity.version_major) - self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField("resource")) def test_parse_protocol_uri_with_remote_cloud_service_no_version(self): uri = "//cloud.uprotocol.example.com/body.access" @@ -257,7 +276,7 @@ def test_parse_protocol_uri_with_remote_cloud_service_no_version(self): self.assertFalse(len(uuri.entity.name) == 0) self.assertEqual("body.access", uuri.entity.name) self.assertEqual(0, uuri.entity.version_major) - self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField("resource")) def test_parse_protocol_uri_with_remote_service_with_version(self): uri = "//VCU.MY_CAR_VIN/body.access/1" @@ -269,7 +288,7 @@ def test_parse_protocol_uri_with_remote_service_with_version(self): self.assertEqual("body.access", uuri.entity.name) self.assertNotEqual(0, uuri.entity.version_major) self.assertEqual(1, uuri.entity.version_major) - self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField("resource")) def test_parse_protocol_uri_with_remote_cloud_service_with_version(self): uri = "//cloud.uprotocol.example.com/body.access/1" @@ -281,9 +300,11 @@ def test_parse_protocol_uri_with_remote_cloud_service_with_version(self): self.assertEqual("body.access", uuri.entity.name) self.assertNotEqual(0, uuri.entity.version_major) self.assertEqual(1, uuri.entity.version_major) - self.assertFalse(uuri.HasField('resource')) + self.assertFalse(uuri.HasField("resource")) - def test_parse_protocol_uri_with_remote_service_no_version_with_resource_name_only(self): + def test_parse_protocol_uri_with_remote_service_no_version_with_resource_name_only( + self, + ): uri = "//VCU.MY_CAR_VIN/body.access//door" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) @@ -296,7 +317,9 @@ def test_parse_protocol_uri_with_remote_service_no_version_with_resource_name_on self.assertTrue(len(uuri.resource.instance) == 0) self.assertTrue(len(uuri.resource.message) == 0) - def test_parse_protocol_uri_with_remote_cloud_service_no_version_with_resource_name_only(self): + def test_parse_protocol_uri_with_remote_cloud_service_no_version_with_resource_name_only( + self, + ): uri = "//cloud.uprotocol.example.com/body.access//door" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) @@ -309,7 +332,9 @@ def test_parse_protocol_uri_with_remote_cloud_service_no_version_with_resource_n self.assertTrue(len(uuri.resource.instance) == 0) self.assertTrue(len(uuri.resource.message) == 0) - def test_parse_protocol_uri_with_remote_service_with_version_with_resource_name_only(self): + def test_parse_protocol_uri_with_remote_service_with_version_with_resource_name_only( + self, + ): uri = "//VCU.MY_CAR_VIN/body.access/1/door" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) @@ -323,7 +348,9 @@ def test_parse_protocol_uri_with_remote_service_with_version_with_resource_name_ self.assertTrue(len(uuri.resource.instance) == 0) self.assertTrue(len(uuri.resource.message) == 0) - def test_parse_protocol_uri_with_remote_service_cloud_with_version_with_resource_name_only(self): + def test_parse_protocol_uri_with_remote_service_cloud_with_version_with_resource_name_only( + self, + ): uri = "//cloud.uprotocol.example.com/body.access/1/door" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) @@ -337,7 +364,9 @@ def test_parse_protocol_uri_with_remote_service_cloud_with_version_with_resource self.assertTrue(len(uuri.resource.instance) == 0) self.assertTrue(len(uuri.resource.message) == 0) - def test_parse_protocol_uri_with_remote_service_no_version_with_resource_and_instance_no_getMessage(self): + def test_parse_protocol_uri_with_remote_service_no_version_with_resource_and_instance_no_getMessage( + self, + ): uri = "//VCU.MY_CAR_VIN/body.access//door.front_left" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) @@ -351,7 +380,9 @@ def test_parse_protocol_uri_with_remote_service_no_version_with_resource_and_ins self.assertEqual("front_left", uuri.resource.instance) self.assertTrue(len(uuri.resource.message) == 0) - def test_parse_protocol_uri_with_remote_service_with_version_with_resource_and_instance_no_getMessage(self): + def test_parse_protocol_uri_with_remote_service_with_version_with_resource_and_instance_no_getMessage( + self, + ): uri = "//VCU.MY_CAR_VIN/body.access/1/door.front_left" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) @@ -366,7 +397,9 @@ def test_parse_protocol_uri_with_remote_service_with_version_with_resource_and_i self.assertEqual("front_left", uuri.resource.instance) self.assertTrue(len(uuri.resource.message) == 0) - def test_parse_protocol_uri_with_remote_service_no_version_with_resource_and_instance_and_getMessage(self): + def test_parse_protocol_uri_with_remote_service_no_version_with_resource_and_instance_and_getMessage( + self, + ): uri = "//VCU.MY_CAR_VIN/body.access//door.front_left#Door" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) @@ -381,7 +414,9 @@ def test_parse_protocol_uri_with_remote_service_no_version_with_resource_and_ins self.assertFalse(len(uuri.resource.message) == 0) self.assertEqual("Door", uuri.resource.message) - def test_parse_protocol_uri_with_remote_cloud_service_no_version_with_resource_and_instance_and_getMessage(self): + def test_parse_protocol_uri_with_remote_cloud_service_no_version_with_resource_and_instance_and_getMessage( + self, + ): uri = "//cloud.uprotocol.example.com/body.access//door.front_left#Door" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) @@ -396,7 +431,9 @@ def test_parse_protocol_uri_with_remote_cloud_service_no_version_with_resource_a self.assertFalse(len(uuri.resource.message) == 0) self.assertEqual("Door", uuri.resource.message) - def test_parse_protocol_uri_with_remote_service_with_version_with_resource_and_instance_and_getMessage(self): + def test_parse_protocol_uri_with_remote_service_with_version_with_resource_and_instance_and_getMessage( + self, + ): uri = "//VCU.MY_CAR_VIN/body.access/1/door.front_left#Door" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) @@ -412,8 +449,12 @@ def test_parse_protocol_uri_with_remote_service_with_version_with_resource_and_i self.assertFalse(len(uuri.resource.message) == 0) self.assertEqual("Door", uuri.resource.message) - def test_parse_protocol_uri_with_remote_cloud_service_with_version_with_resource_and_instance_and_getMessage(self): - uri = "//cloud.uprotocol.example.com/body.access/1/door.front_left#Door" + def test_parse_protocol_uri_with_remote_cloud_service_with_version_with_resource_and_instance_and_getMessage( + self, + ): + uri = ( + "//cloud.uprotocol.example.com/body.access/1/door.front_left#Door" + ) uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) self.assertFalse(len(uuri.authority.name) == 0) @@ -428,7 +469,9 @@ def test_parse_protocol_uri_with_remote_cloud_service_with_version_with_resource self.assertFalse(len(uuri.resource.message) == 0) self.assertEqual("Door", uuri.resource.message) - def test_parse_protocol_uri_with_remote_service_with_version_with_resource_with_message_device_no_domain(self): + def test_parse_protocol_uri_with_remote_service_with_version_with_resource_with_message_device_no_domain( + self, + ): uri = "//VCU/body.access/1/door.front_left" uuri = LongUriSerializer().deserialize(uri) self.assertTrue(UriValidator.is_remote(uuri.authority)) @@ -474,166 +517,269 @@ def test_parse_protocol_rpc_uri_with_remote_service_with_version(self): def test_build_protocol_uri_from__uri_when__uri_isnull(self): uprotocol_uri = LongUriSerializer().serialize(None) - self.assertEqual('', uprotocol_uri) + self.assertEqual("", uprotocol_uri) def test_build_protocol_uri_from__uri_when__uri_isEmpty(self): uuri = UUri() uprotocol_uri = LongUriSerializer().serialize(uuri) - self.assertEqual('', uprotocol_uri) + self.assertEqual("", uprotocol_uri) def test_build_protocol_uri_from__uri_when__uri_has_empty_use(self): use = UEntity() - uuri = UUri(authority=UAuthority(), entity=use, resource=UResource(name="door")) + uuri = UUri( + authority=UAuthority(), entity=use, resource=UResource(name="door") + ) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("/////door", uprotocol_uri) - def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_no_version(self): + def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_no_version( + self, + ): uuri = UUri(entity=UEntity(name="body.access")) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("/body.access", uprotocol_uri) - def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_and_version(self): + def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_and_version( + self, + ): use = UEntity(name="body.access", version_major=1) uuri = UUri(entity=use, resource=UResource()) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("/body.access/1", uprotocol_uri) - def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_no_version_with_resource(self): + def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_no_version_with_resource( + self, + ): use = UEntity(name="body.access") uuri = UUri(entity=use, resource=UResource(name="door")) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("/body.access//door", uprotocol_uri) - def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_and_version_with_resource(self): + def test_build_protocol_uri_from__uri_when__uri_has_local_authority_service_and_version_with_resource( + self, + ): use = UEntity(name="body.access", version_major=1) uuri = UUri(entity=use, resource=UResource(name="door")) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("/body.access/1/door", uprotocol_uri) - def test_build_protocol_uri_from_uri_when_uri_has_local_authority_service_no_version_with_resource_with_instance_no_getMessage( - self): + def test_build_protocol_uri_from_uri_when_uri_has_l_authority_service_n_vsn_with_rsrc_with_instance_n_getMessage( + self, + ): use = UEntity(name="body.access") - uuri = UUri(entity=use, resource=UResource(name="door", instance="front_left")) + uuri = UUri( + entity=use, resource=UResource(name="door", instance="front_left") + ) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("/body.access//door.front_left", uprotocol_uri) - def test_build_protocol_uri_from_uri_when_uri_has_local_authority_service_and_version_with_resource_with_instance_no_getMessage( - self): + def test_build_protocol_uri_from_uri_when_uri_has_l_authority_service_and_vsn_with_rsrc_with_instance_n_getMessage( + self, + ): use = UEntity(name="body.access", version_major=1) - uuri = UUri(entity=use, resource=UResource(name="door", instance="front_left")) + uuri = UUri( + entity=use, resource=UResource(name="door", instance="front_left") + ) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("/body.access/1/door.front_left", uprotocol_uri) - def test_build_protocol_uri_from_uri_when_uri_has_local_authority_service_no_version_with_resource_with_instance_with_getMessage( - self): + def test_build_protocol_uri_from_uri_when_uri_has_l_authority_svc_no_vsn_with_rsrc_with_instance_with_getMessage( + self, + ): use = UEntity(name="body.access") - uuri = UUri(entity=use, resource=UResource(name="door", instance="front_left", message="Door")) + uuri = UUri( + entity=use, + resource=UResource( + name="door", instance="front_left", message="Door" + ), + ) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("/body.access//door.front_left#Door", uprotocol_uri) - def test_build_protocol_uri_from_uri_when_uri_has_local_authority_service_and_version_with_resource_with_instance_with_getMessage( - self): + def test_build_protocol_uri_from_uri_when_uri_has_l_authority_svc_and_vsn_with_rsrc_with_instance_with_getMessage( + self, + ): use = UEntity(name="body.access", version_major=1) - uuri = UUri(entity=use, resource=UResource(name="door", instance="front_left", message="Door")) + uuri = UUri( + entity=use, + resource=UResource( + name="door", instance="front_left", message="Door" + ), + ) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("/body.access/1/door.front_left#Door", uprotocol_uri) - def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version(self): + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version( + self, + ): use = UEntity(name="body.access") uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("//vcu.my_car_vin/body.access", uprotocol_uri) - def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_version(self): + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_version( + self, + ): use = UEntity(name="body.access", version_major=1) uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("//vcu.my_car_vin/body.access/1", uprotocol_uri) - def test_build_protocol_uri_from_uri_when_uri_has_remote_cloud_authority_service_and_version(self): + def test_build_protocol_uri_from_uri_when_uri_has_remote_cloud_authority_service_and_version( + self, + ): use = UEntity(name="body.access", version_major=1) - uuri = UUri(authority=UAuthority(name="cloud.uprotocol.example.com"), entity=use) + uuri = UUri( + authority=UAuthority(name="cloud.uprotocol.example.com"), + entity=use, + ) uprotocol_uri = LongUriSerializer().serialize(uuri) - self.assertEqual("//cloud.uprotocol.example.com/body.access/1", uprotocol_uri) + self.assertEqual( + "//cloud.uprotocol.example.com/body.access/1", uprotocol_uri + ) - def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_version_with_resource(self): + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_version_with_resource( + self, + ): use = UEntity(name="body.access", version_major=1) - uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, resource=UResource(name="door")) + uuri = UUri( + authority=UAuthority(name="vcu.my_car_vin"), + entity=use, + resource=UResource(name="door"), + ) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("//vcu.my_car_vin/body.access/1/door", uprotocol_uri) - def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version_with_resource(self): + def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version_with_resource( + self, + ): use = UEntity(name="body.access") - uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, resource=UResource(name="door")) + uuri = UUri( + authority=UAuthority(name="vcu.my_car_vin"), + entity=use, + resource=UResource(name="door"), + ) uprotocol_uri = LongUriSerializer().serialize(uuri) self.assertEqual("//vcu.my_car_vin/body.access//door", uprotocol_uri) - def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_version_with_resource_with_instance_no_getMessage( - self): + def test_build_protocol_uri_from_uri_when_uri_has_r_authority_svc_and_vsn_with_rsrc_with_instance_no_getMessage( + self, + ): use = UEntity(name="body.access", version_major=1) - uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, - resource=UResource(name="door", instance="front_left")) + uuri = UUri( + authority=UAuthority(name="vcu.my_car_vin"), + entity=use, + resource=UResource(name="door", instance="front_left"), + ) uprotocol_uri = LongUriSerializer().serialize(uuri) - self.assertEqual("//vcu.my_car_vin/body.access/1/door.front_left", uprotocol_uri) + self.assertEqual( + "//vcu.my_car_vin/body.access/1/door.front_left", uprotocol_uri + ) - def test_build_protocol_uri_from_uri_when_uri_has_remote_cloud_authority_service_and_version_with_resource_with_instance_no_getMessage( - self): + def test_build_protocol_uri_from_uri_when_uri_has_r_cld_authority_svc_and_vsn_with_rsrc_with_instance_n_getMessage( + self, + ): use = UEntity(name="body.access", version_major=1) - uuri = UUri(authority=UAuthority(name="cloud.uprotocol.example.com"), entity=use, - resource=UResource(name="door", instance="front_left")) + uuri = UUri( + authority=UAuthority(name="cloud.uprotocol.example.com"), + entity=use, + resource=UResource(name="door", instance="front_left"), + ) uprotocol_uri = LongUriSerializer().serialize(uuri) - self.assertEqual("//cloud.uprotocol.example.com/body.access/1/door.front_left", uprotocol_uri) - - def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version_with_resource_with_instance_no_getMessage( - self): + self.assertEqual( + "//cloud.uprotocol.example.com/body.access/1/door.front_left", + uprotocol_uri, + ) + + def test_build_protocol_uri_from_uri_when_uri_has_r_authority_svc_no_vsn_with_resource_with_instance_n_getMessage( + self, + ): use = UEntity(name="body.access") - uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, - resource=UResource(name="door", instance="front_left")) + uuri = UUri( + authority=UAuthority(name="vcu.my_car_vin"), + entity=use, + resource=UResource(name="door", instance="front_left"), + ) uprotocol_uri = LongUriSerializer().serialize(uuri) - self.assertEqual("//vcu.my_car_vin/body.access//door.front_left", uprotocol_uri) + self.assertEqual( + "//vcu.my_car_vin/body.access//door.front_left", uprotocol_uri + ) - def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_and_version_with_resource_with_instance_and_getMessage( - self): + def test_build_protocol_uri_from_uri_when_uri_has_r_authority_svc_and_version_with_rsrc_with_i_and_getMessage( + self, + ): use = UEntity(name="body.access", version_major=1) - uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, - resource=UResource(name="door", instance="front_left", message="Door")) + uuri = UUri( + authority=UAuthority(name="vcu.my_car_vin"), + entity=use, + resource=UResource( + name="door", instance="front_left", message="Door" + ), + ) uprotocol_uri = LongUriSerializer().serialize(uuri) - self.assertEqual("//vcu.my_car_vin/body.access/1/door.front_left#Door", uprotocol_uri) - - def test_build_protocol_uri_from_uri_when_uri_has_remote_authority_service_no_version_with_resource_with_instance_and_getMessage( - self): + self.assertEqual( + "//vcu.my_car_vin/body.access/1/door.front_left#Door", + uprotocol_uri, + ) + + def test_build_protocol_uri_from_uri_when_uri_has_r_authority_svc_no_version_with_rsrc_with_i_and_getMessage( + self, + ): use = UEntity(name="body.access") - uuri = UUri(authority=UAuthority(name="vcu.my_car_vin"), entity=use, - resource=UResource(name="door", instance="front_left", message="Door")) + uuri = UUri( + authority=UAuthority(name="vcu.my_car_vin"), + entity=use, + resource=UResource( + name="door", instance="front_left", message="Door" + ), + ) uprotocol_uri = LongUriSerializer().serialize(uuri) - self.assertEqual("//vcu.my_car_vin/body.access//door.front_left#Door", uprotocol_uri) + self.assertEqual( + "//vcu.my_car_vin/body.access//door.front_left#Door", uprotocol_uri + ) - def test_build_protocol_uri_for_source_part_of_rpc_request_where_source_is_local(self): + def test_build_protocol_uri_for_source_part_of_rpc_request_where_source_is_local( + self, + ): use = UEntity(name="petapp", version_major=1) resource = UResource(name="rpc", instance="response") - uprotocol_uri = LongUriSerializer().serialize(UUri(entity=use, resource=resource)) + uprotocol_uri = LongUriSerializer().serialize( + UUri(entity=use, resource=resource) + ) self.assertEqual("/petapp/1/rpc.response", uprotocol_uri) - def test_build_protocol_uri_for_source_part_of_rpc_request_where_source_is_remote(self): + def test_build_protocol_uri_for_source_part_of_rpc_request_where_source_is_remote( + self, + ): uAuthority = UAuthority(name="cloud.uprotocol.example.com") use = UEntity(name="petapp") resource = UResource(name="rpc", instance="response") - uprotocol_uri = LongUriSerializer().serialize(UUri(authority=uAuthority, entity=use, resource=resource)) - self.assertEqual("//cloud.uprotocol.example.com/petapp//rpc.response", uprotocol_uri) + uprotocol_uri = LongUriSerializer().serialize( + UUri(authority=uAuthority, entity=use, resource=resource) + ) + self.assertEqual( + "//cloud.uprotocol.example.com/petapp//rpc.response", uprotocol_uri + ) - def test_build_protocol_uri_from_uri_parts_when_uri_has_remote_authority_service_and_version_with_resource(self): + def test_build_protocol_uri_from_uri_parts_when_uri_has_remote_authority_service_and_version_with_resource( + self, + ): u_authority = UAuthority(name="vcu.my_car_vin") use = UEntity(name="body.access", version_major=1) resource = UResource(name="door") - uprotocol_uri = LongUriSerializer().serialize(UUri(authority=u_authority, entity=use, resource=resource)) + uprotocol_uri = LongUriSerializer().serialize( + UUri(authority=u_authority, entity=use, resource=resource) + ) self.assertEqual("//vcu.my_car_vin/body.access/1/door", uprotocol_uri) def test_custom_scheme_no_scheme(self): u_authority = UAuthority(name="vcu.my_car_vin") use = UEntity(name="body.access", version_major=1) resource = UResource(name="door") - ucustom_uri = LongUriSerializer().serialize(UUri(authority=u_authority, entity=use, resource=resource)) + ucustom_uri = LongUriSerializer().serialize( + UUri(authority=u_authority, entity=use, resource=resource) + ) self.assertEqual("//vcu.my_car_vin/body.access/1/door", ucustom_uri) def test_parse_local_protocol_uri_with_custom_scheme(self): @@ -661,26 +807,6 @@ def test_parse_remote_protocol_uri_with_custom_scheme(self): self.assertEqual("Door", uuri.resource.message) self.assertEqual(uri2, LongUriSerializer().serialize(uuri)) - def test_deserialize_long_and_micro_passing_null(self): - uri = LongUriSerializer().build_resolved(None, None) - self.assertTrue(uri is not None) - self.assertEqual("", LongUriSerializer().serialize(uri)) - - def test_deserialize_long_and_micro_passing_null_long_uri_empty_byte_array(self): - uri = LongUriSerializer().build_resolved(None, bytearray()) - self.assertTrue(uri is not None) - self.assertEqual("", LongUriSerializer().serialize(uri)) - - def test_deserialize_long_and_micro_passing_nullempty_long_uri_null_byte_array(self): - uri = LongUriSerializer().build_resolved("", None) - self.assertTrue(uri is not None) - self.assertEqual("", LongUriSerializer().serialize(uri)) - - def test_deserialize_long_and_micro_passing_empty_long_uri_empty_byte_array(self): - uri = LongUriSerializer().build_resolved("", bytearray()) - self.assertTrue(uri is not None) - self.assertEqual("", LongUriSerializer().serialize(uri)) - -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_uri/test_serializer/test_microuriserializer.py b/tests/test_uri/test_serializer/test_microuriserializer.py index 9df8bb0..0f93994 100644 --- a/tests/test_uri/test_serializer/test_microuriserializer.py +++ b/tests/test_uri/test_serializer/test_microuriserializer.py @@ -1,29 +1,3 @@ -# ------------------------------------------------------------------------- -# -# Copyright (c) 2023 General Motors GTO LLC -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# SPDX-FileType: SOURCE -# SPDX-FileCopyrightText: 2023 General Motors GTO LLC -# SPDX-License-Identifier: Apache-2.0 -# -# ------------------------------------------------------------------------- - import socket import unittest @@ -48,9 +22,11 @@ def test_null(self): self.assertTrue(UriValidator.is_empty(uri2)) def test_serialize_uri(self): - uri = UUri(entity=UEntity(id=29999, version_major=254), resource=UResource(id=19999)) + uri = UUri(entity=UEntity(id=29999, version_major=254), resource=UResourceBuilder.from_id(19999)) bytes_uuri = MicroUriSerializer().serialize(uri) uri2 = MicroUriSerializer().deserialize(bytes_uuri) + self.assertTrue(UriValidator.is_micro_form(uri)) + self.assertTrue(len(bytes_uuri) > 0) self.assertEqual(uri, uri2) def test_serialize_remote_uri_without_address(self): @@ -102,8 +78,8 @@ def test_deserialize_bad_microuri_valid_address_type_invalid_length(self): self.assertTrue(UriValidator.is_empty(uuri)) def test_serialize_good_ipv4_based_authority(self): - uri = UUri(authority=UAuthority(ip=bytes(socket.inet_pton(socket.AF_INET, "10.0.3.3"))), - entity=UEntity(id=29999, version_major=254), resource=UResourceBuilder.for_rpc_request_with_id(99)) + uri = UUri(authority=UAuthority(ip=socket.inet_pton(socket.AF_INET, "10.0.3.3")), + entity=UEntity(id=29999, version_major=254), resource=UResourceBuilder.for_rpc_request(99)) bytes_uuri = MicroUriSerializer().serialize(uri) uri2 = MicroUriSerializer().deserialize(bytes_uuri) self.assertTrue(len(bytes_uuri) > 0) @@ -114,8 +90,8 @@ def test_serialize_good_ipv4_based_authority(self): def test_serialize_good_ipv6_based_authority(self): uri = UUri(authority=UAuthority( - ip=bytes(socket.inet_pton(socket.AF_INET6, "2001:0db8:85a3:0000:0000:8a2e:0370:7334"))), - entity=UEntity(id=29999, version_major=254), resource=UResource(id=19999)) + ip=socket.inet_pton(socket.AF_INET6, "2001:0db8:85a3:0000:0000:8a2e:0370:7334")), + entity=UEntity(id=29999, version_major=254), resource=UResource(id=19999, name="rpc")) bytes_uuri = MicroUriSerializer().serialize(uri) uri2 = MicroUriSerializer().deserialize(bytes_uuri) self.assertTrue(UriValidator.is_micro_form(uri)) @@ -126,7 +102,7 @@ def test_serialize_id_based_authority(self): size = 13 byteArray = bytearray(i for i in range(size)) uri = UUri(authority=UAuthority(id=bytes(byteArray)), entity=UEntity(id=29999, version_major=254), - resource=UResource(id=19999)) + resource=UResource(id=19999, name="rpc")) bytes_uuri = MicroUriSerializer().serialize(uri) uri2 = MicroUriSerializer().deserialize(bytes_uuri) self.assertTrue(UriValidator.is_micro_form(uri)) @@ -135,7 +111,7 @@ def test_serialize_id_based_authority(self): def test_serialize_bad_length_ip_based_authority(self): byteArray = bytes([127, 1, 23, 123, 12, 6]) - uri = UUri(authority=UAuthority(ip=bytes(byteArray)), entity=UEntity(id=29999, version_major=254), + uri = UUri(authority=UAuthority(ip=byteArray), entity=UEntity(id=29999, version_major=254), resource=UResource(id=19999)) bytes_uuri = MicroUriSerializer().serialize(uri) self.assertTrue(len(bytes_uuri) == 0) @@ -143,8 +119,8 @@ def test_serialize_bad_length_ip_based_authority(self): def test_serialize_id_size_255_based_authority(self): size = 129 byteArray = bytes(i for i in range(size)) - uri = UUri(authority=UAuthority(id=bytes(byteArray)), entity=UEntity(id=29999, version_major=254), - resource=UResourceBuilder.for_rpc_request_with_id(99)) + uri = UUri(authority=UAuthority(id=byteArray), entity=UEntity(id=29999, version_major=254), + resource=UResourceBuilder.for_rpc_request(99)) bytes_uuri = MicroUriSerializer().serialize(uri) self.assertEqual(len(bytes_uuri), 9 + size) uri2 = MicroUriSerializer().deserialize(bytes_uuri) diff --git a/tests/test_uri/test_serializer/test_shorturiserializer.py b/tests/test_uri/test_serializer/test_shorturiserializer.py new file mode 100644 index 0000000..1604008 --- /dev/null +++ b/tests/test_uri/test_serializer/test_shorturiserializer.py @@ -0,0 +1,240 @@ +import unittest +from uprotocol.uri.serializer.shorturiserializer import ShortUriSerializer +from uprotocol.uri.factory.uresource_builder import UResourceBuilder +from uprotocol.proto.uri_pb2 import UAuthority, UEntity, UResource, UUri +from socket import inet_aton + + +class ShortUriSerializerTest(unittest.TestCase): + + def test_serialize_with_null_uri(self): + str_uri = ShortUriSerializer().serialize(None) + self.assertEqual("", str_uri) + + def test_serialize_with_empty_uri(self): + str_uri = ShortUriSerializer().serialize(UUri()) + self.assertEqual("", str_uri) + + def test_creating_short_uri_serializer(self): + uri = UUri( + entity=UEntity(id=1, version_major=1), + resource=UResourceBuilder.for_rpc_response(), + ) + str_uri = ShortUriSerializer().serialize(uri) + self.assertEqual("/1/1/0", str_uri) + uri2 = ShortUriSerializer().deserialize(str_uri) + self.assertEqual(uri, uri2) + + def test_creating_short_uri_serializer_with_method(self): + uri = UUri( + entity=UEntity(id=1, version_major=1), + resource=UResourceBuilder.for_rpc_request(10), + ) + str_uri = ShortUriSerializer().serialize(uri) + self.assertEqual("/1/1/10", str_uri) + uri2 = ShortUriSerializer().deserialize(str_uri) + self.assertEqual(uri, uri2) + + def test_creating_short_uri_serializer_with_topic(self): + uri = UUri( + entity=UEntity(id=1, version_major=1), + resource=UResourceBuilder.from_id(20000), + ) + str_uri = ShortUriSerializer().serialize(uri) + self.assertEqual("/1/1/20000", str_uri) + uri2 = ShortUriSerializer().deserialize(str_uri) + self.assertEqual(uri, uri2) + + def test_creating_short_uri_serializer_with_authority(self): + uri = UUri( + entity=UEntity(id=1, version_major=1), + authority=UAuthority(id=b"19UYA31581L000000"), + resource=UResourceBuilder.from_id(20000), + ) + str_uri = ShortUriSerializer().serialize(uri) + self.assertEqual("//19UYA31581L000000/1/1/20000", str_uri) + uri2 = ShortUriSerializer().deserialize(str_uri) + self.assertEqual(uri, uri2) + + def test_creating_short_uri_serializer_with_ip_authority(self): + ip_bytes = inet_aton("192.168.1.100") + uri = UUri( + entity=UEntity(id=1, version_major=1), + authority=UAuthority(ip=ip_bytes), + resource=UResourceBuilder.from_id(20000), + ) + str_uri = ShortUriSerializer().serialize(uri) + self.assertEqual("//192.168.1.100/1/1/20000", str_uri) + uri2 = ShortUriSerializer().deserialize(str_uri) + self.assertEqual(uri, uri2) + + def test_short_serializing_uri_without_resource(self): + uri = UUri(entity=UEntity(id=1, version_major=1)) + str_uri = ShortUriSerializer().serialize(uri) + self.assertEqual(str_uri, "/1/1") + + def test_short_serializing_uri_with_negative_version_major(self): + with self.assertRaises(ValueError) as context: + UUri( + entity=UEntity(id=1, version_major=-1), + resource=UResourceBuilder.from_id(20000), + ) + self.assertTrue("Value out of range: -1" in str(context.exception)) + + def test_short_deserialize_null_uri(self): + uri = ShortUriSerializer().deserialize(None) + self.assertEqual(uri, UUri()) + + def test_short_deserialize_empty_uri(self): + uri = ShortUriSerializer().deserialize("") + self.assertEqual(uri, UUri()) + + def test_short_deserialize_uri_with_scheme_and_authority(self): + uri = ShortUriSerializer().deserialize("up://mypc/1/1/1") + self.assertTrue(uri.authority is not None) + self.assertEqual(uri.authority.id, b"mypc") + self.assertFalse(uri.authority.HasField("name")) + self.assertFalse(uri.authority.HasField("ip")) + self.assertTrue(uri.HasField("entity")) + self.assertEqual(uri.entity.id, 1) + self.assertEqual(uri.entity.version_major, 1) + self.assertTrue(uri.HasField("resource")) + self.assertEqual(uri.resource.id, 1) + + def test_short_deserialize_uri_without_scheme(self): + uri = ShortUriSerializer().deserialize("//mypc/1/1/1") + self.assertTrue(uri.HasField("authority")) + self.assertEqual(uri.authority.id, bytes("mypc", "utf-8")) + self.assertFalse(uri.authority.HasField("name")) + self.assertFalse(uri.authority.HasField("ip")) + self.assertTrue(uri.HasField("entity")) + self.assertEqual(uri.entity.id, 1) + self.assertEqual(uri.entity.version_major, 1) + self.assertTrue(uri.HasField("resource")) + self.assertEqual(uri.resource.id, 1) + + def test_short_deserialize_uri_with_only_authority(self): + uri = ShortUriSerializer().deserialize("//") + self.assertEqual(uri, UUri()) + + def test_short_deserialize_uri_with_scheme_and_only_authority(self): + uri = ShortUriSerializer().deserialize("up://") + self.assertEqual(uri, UUri()) + + def test_short_serialize_with_invalid_ip_address(self): + uri = UUri( + entity=UEntity(id=1, version_major=1), + authority=UAuthority(ip=b"34823748273"), + ) + uri_string = ShortUriSerializer().serialize(uri) + self.assertEqual(uri_string, "") + + def test_short_serialize_with_authority_only_name(self): + uri = UUri( + entity=UEntity(id=1, version_major=1), + authority=UAuthority(name="mypc"), + ) + uri_string = ShortUriSerializer().serialize(uri) + self.assertEqual(uri_string, "") + + def test_short_deserialize_local_uri_with_too_many_parts(self): + uri = ShortUriSerializer().deserialize("/1/1/1/1") + self.assertEqual(uri, UUri()) + + def test_short_deserialize_local_uri_with_only_two_parts(self): + uri = ShortUriSerializer().deserialize("/1/1") + self.assertTrue(uri.HasField("entity")) + self.assertEqual(uri.entity.id, 1) + self.assertEqual(uri.entity.version_major, 1) + + def test_short_deserialize_local_uri_with_three_parts(self): + uri = ShortUriSerializer().deserialize("/1") + self.assertTrue(uri.HasField("entity")) + self.assertEqual(uri.entity.id, 1) + self.assertFalse(uri.HasField("resource")) + + def test_short_deserialize_with_blank_authority(self): + uri = ShortUriSerializer().deserialize("///1/1/1") + self.assertEqual(uri, UUri()) + + def test_short_deserialize_with_remote_authority_ip_and_too_many_parts( + self, + ): + uri = ShortUriSerializer().deserialize("//192.168.1.100/1/1/1/1") + self.assertEqual(uri, UUri()) + + def test_short_deserialize_with_remote_authority_ip_and_right_number_of_parts( + self, + ): + uri = ShortUriSerializer().deserialize("//192.168.1.100/1/1/1") + self.assertTrue(uri.authority is not None) + self.assertTrue(uri.authority.HasField("ip")) + self.assertEqual(uri.authority.ip, inet_aton("192.168.1.100")) + self.assertTrue(uri.entity is not None) + self.assertEqual(uri.entity.id, 1) + self.assertEqual(uri.entity.version_major, 1) + self.assertTrue(uri.resource is not None) + self.assertEqual(uri.resource.id, 1) + + def test_short_deserialize_with_remote_authority_ip_address_missing_resource( + self, + ): + uri = ShortUriSerializer().deserialize("//192.168.1.100/1/1") + self.assertTrue(uri.HasField("authority")) + self.assertTrue(uri.authority.HasField("ip")) + self.assertEqual(uri.authority.ip, inet_aton("192.168.1.100")) + self.assertTrue(uri.HasField("entity")) + self.assertEqual(uri.entity.id, 1) + self.assertEqual(uri.entity.version_major, 1) + self.assertFalse(uri.HasField("resource")) + + def test_short_deserialize_with_remote_authority_ip_address_missing_resource_and_version_major( + self, + ): + uri = ShortUriSerializer().deserialize("//192.168.1.100/1") + self.assertTrue(uri.HasField("authority")) + self.assertTrue(uri.authority.HasField("ip")) + self.assertEqual(uri.authority.ip, inet_aton("192.168.1.100")) + self.assertTrue(uri.HasField("entity")) + self.assertEqual(uri.entity.id, 1) + self.assertFalse(uri.entity.HasField("version_major")) + + def test_short_deserialize_with_remote_authority_ip_address_missing_resource_and_version_major_and_ueid( + self, + ): + uri = ShortUriSerializer().deserialize("//192.168.1.100//") + self.assertTrue(uri.HasField("authority")) + self.assertTrue(uri.authority.HasField("ip")) + self.assertEqual(uri.authority.ip, inet_aton("192.168.1.100")) + self.assertFalse(uri.HasField("entity")) + + def test_short_deserialize_with_remote_authority_and_blank_ueversion_and_ueid( + self, + ): + uri = ShortUriSerializer().deserialize("//mypc//1/") + self.assertTrue(uri.HasField("authority")) + self.assertTrue(uri.authority.HasField("id")) + self.assertEqual(uri.authority.id, b"mypc") + self.assertTrue(uri.HasField("entity")) + + def test_short_deserialize_with_remote_authority_and_missing_parts(self): + uri = ShortUriSerializer().deserialize("//mypc") + self.assertTrue(uri.HasField("authority")) + self.assertTrue(uri.authority.HasField("id")) + self.assertEqual(uri.authority.id, b"mypc") + + def test_short_deserialize_with_remote_authority_and_invalid_characters_for_entity_id_and_version( + self, + ): + uri = ShortUriSerializer().deserialize("//mypc/abc/def") + self.assertEqual(uri, UUri()) + + def test_short_deserialize_with_remote_authority_and_invalid_characters_for_resource_id( + self, + ): + uri = ShortUriSerializer().deserialize("//mypc/1/1/abc") + self.assertEqual(uri.resource, UResource()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_uri/test_serializer/test_uriserializer.py b/tests/test_uri/test_serializer/test_uriserializer.py index 9ad89f3..8c6c2ef 100644 --- a/tests/test_uri/test_serializer/test_uriserializer.py +++ b/tests/test_uri/test_serializer/test_uriserializer.py @@ -26,48 +26,230 @@ import unittest +import socket from uprotocol.proto.uri_pb2 import UAuthority, UEntity, UResource, UUri +from uprotocol.uri.factory.uentity_factory import UEntityFactory from uprotocol.uri.serializer.longuriserializer import LongUriSerializer from uprotocol.uri.serializer.microuriserializer import MicroUriSerializer +from uprotocol.uri.serializer.shorturiserializer import ShortUriSerializer + from uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.proto.core.usubscription.v3.usubscription_pb2 import ( + DESCRIPTOR as USubscriptionFileDescriptor, +) +from uprotocol.proto.uprotocol_options_pb2 import ( + notification_topic as Notification_Topic, +) +from uprotocol.proto.uprotocol_options_pb2 import UServiceTopic + +from google.protobuf.descriptor import ServiceDescriptor, FileDescriptor +from google.protobuf.descriptor_pb2 import ServiceOptions +from google.protobuf.internal.containers import RepeatedCompositeFieldContainer class TestUriSerializer(unittest.TestCase): - def test_build_resolved_valid_long_micro_uri(self): - long_uuri = UUri(authority=UAuthority(name="testauth"), entity=UEntity(name="neelam"), - resource=UResource(name="rpc", instance="response")) - micro_uuri = UUri(authority=UAuthority(id="abcdefg".encode('UTF-8')), - entity=UEntity(id=29999, version_major=254), resource=UResource(id=39999)) - microuri = MicroUriSerializer().serialize(micro_uuri) - longuri = LongUriSerializer().serialize(long_uuri) - resolved_uuri = LongUriSerializer().build_resolved(longuri, microuri) - self.assertTrue(resolved_uuri) - self.assertFalse(UriValidator.is_empty(resolved_uuri)) - self.assertEqual("testauth", resolved_uuri.authority.name) - self.assertEqual("neelam", resolved_uuri.entity.name) - self.assertEqual(29999, resolved_uuri.entity.id) - self.assertEqual(254, resolved_uuri.entity.version_major) - self.assertEqual("rpc", resolved_uuri.resource.name) - self.assertEqual("response", resolved_uuri.resource.instance) - self.assertEqual(39999, resolved_uuri.resource.id) - - def test_build_resolved_null_long_null_micro_uri(self): - result = MicroUriSerializer().build_resolved(None, None) - self.assertTrue(result) - self.assertTrue(UriValidator.is_empty(result)) - - def test_build_resolved_null_long_micro_uri(self): - result = MicroUriSerializer().build_resolved(None, bytes()) - self.assertTrue(result) - self.assertTrue(UriValidator.is_empty(result)) - - def test_build_resolved_valid_long_null_micro_uri(self): - result = MicroUriSerializer().build_resolved("", bytes()) - self.assertTrue(result) - self.assertTrue(UriValidator.is_empty(result)) - - -if __name__ == '__main__': + def get_uresource( + self, container: RepeatedCompositeFieldContainer + ) -> UResource: + if len(container) == 0: + resrc = UResource() + else: + first_topic: UServiceTopic = container[0] + id: int = first_topic.id + name: str = first_topic.name + message: str = first_topic.message + + resrc = UResource() + if id is not None: + resrc.id = id + if name is not None: + resrc.name = name + if message is not None: + resrc.message = message + return resrc + + def check_given_test_data_is_formed_correctly(self, uuri: UUri): + self.assertFalse(UriValidator.is_empty(uuri)) + self.assertTrue(UriValidator.is_micro_form(uuri)) + self.assertTrue(UriValidator.is_long_form(uuri)) + self.assertTrue(UriValidator.is_short_form(uuri)) + + def test_build_resolved_full_information_compare(self): + + file_descriptor: FileDescriptor = USubscriptionFileDescriptor + service_descriptor: ServiceDescriptor = ( + file_descriptor.services_by_name["uSubscription"] + ) + options: ServiceOptions = service_descriptor.GetOptions() + container: RepeatedCompositeFieldContainer = options.Extensions[ + Notification_Topic + ] + + uresrc: UResource = self.get_uresource(container) + uentity: UEntity = UEntityFactory.from_proto(service_descriptor) + uuri: UUri = UUri(entity=uentity, resource=uresrc) + + actual_long_uri: str = LongUriSerializer().serialize(uuri) + actual_short_uri: str = ShortUriSerializer().serialize(uuri) + actual_micro_uri: bytes = MicroUriSerializer().serialize(uuri) + + expected_longuri_given_no_uauthority: str = ( + "/core.usubscription/3/SubscriptionChange#Update" + ) + expected_shorturi_given_no_uauthority: str = "/0/3/32768" + expected_micro_uri_given_no_uauthority = bytearray( + b"\x01\x00\x80\x00\x00\x00\x03\x00" + ) + + self.check_given_test_data_is_formed_correctly(uuri) + + self.assertEqual(expected_longuri_given_no_uauthority, actual_long_uri) + self.assertEqual( + expected_shorturi_given_no_uauthority, actual_short_uri + ) + self.assertEqual( + expected_micro_uri_given_no_uauthority, actual_micro_uri + ) + + def test_build_resolved_full_information_compare_with_ipv4(self): + file_descriptor: FileDescriptor = USubscriptionFileDescriptor + service_descriptor: ServiceDescriptor = ( + file_descriptor.services_by_name["uSubscription"] + ) + options: ServiceOptions = service_descriptor.GetOptions() + container: RepeatedCompositeFieldContainer = options.Extensions[ + Notification_Topic + ] + + uresrc: UResource = self.get_uresource(container) + uentity: UEntity = UEntityFactory.from_proto(service_descriptor) + + ipv4_address: str = "192.168.1.100" + ipv4_packed_ip: bytes = socket.inet_pton(socket.AF_INET, ipv4_address) + + authority: UAuthority = UAuthority( + name="vcu.veh.gm.com", ip=ipv4_packed_ip + ) + uuri: UUri = UUri(entity=uentity, resource=uresrc, authority=authority) + + self.check_given_test_data_is_formed_correctly(uuri) + + actual_long_uri: str = LongUriSerializer().serialize(uuri) + actual_short_uri: str = ShortUriSerializer().serialize(uuri) + actual_micro_uri: bytes = MicroUriSerializer().serialize(uuri) + + expected_longuri_given_ipv4_uathority: str = ( + "//vcu.veh.gm.com/core.usubscription/3/SubscriptionChange#Update" + ) + expected_shorturi_given_ipv4_uathority: str = ( + "//192.168.1.100/0/3/32768" + ) + expected_microuri_given_ipv4_uathority = bytearray( + b"\x01\x01\x80\x00\x00\x00\x03\x00\xc0\xa8\x01d" + ) + + self.assertEqual( + expected_longuri_given_ipv4_uathority, actual_long_uri + ) + self.assertEqual( + expected_shorturi_given_ipv4_uathority, actual_short_uri + ) + self.assertEqual( + expected_microuri_given_ipv4_uathority, actual_micro_uri + ) + + def test_build_resolved_full_information_compare_with_id(self): + file_descriptor: FileDescriptor = USubscriptionFileDescriptor + service_descriptor: ServiceDescriptor = ( + file_descriptor.services_by_name["uSubscription"] + ) + options: ServiceOptions = service_descriptor.GetOptions() + container: RepeatedCompositeFieldContainer = options.Extensions[ + Notification_Topic + ] + + uresrc: UResource = self.get_uresource(container) + uentity: UEntity = UEntityFactory.from_proto(service_descriptor) + + authority: UAuthority = UAuthority( + name="1G1YZ23J9P5800001.veh.gm.com", id=b"1G1YZ23J9P5800001" + ) + uuri: UUri = UUri(entity=uentity, resource=uresrc, authority=authority) + + self.check_given_test_data_is_formed_correctly(uuri) + + actual_long_uri: str = LongUriSerializer().serialize(uuri) + actual_short_uri: str = ShortUriSerializer().serialize(uuri) + actual_micro_uri: bytes = MicroUriSerializer().serialize(uuri) + + expected_longuri_given_id_uathority: str = ( + "//1G1YZ23J9P5800001.veh.gm.com/core.usubscription/3/SubscriptionChange#Update" + ) + expected_shorturi_given_id_uathority: str = ( + "//1G1YZ23J9P5800001/0/3/32768" + ) + expected_microuri_given_id_uathority = bytearray( + b"\x01\x03\x80\x00\x00\x00\x03\x00\x111G1YZ23J9P5800001" + ) + + self.assertEqual(expected_longuri_given_id_uathority, actual_long_uri) + self.assertEqual( + expected_shorturi_given_id_uathority, actual_short_uri + ) + self.assertEqual( + expected_microuri_given_id_uathority, actual_micro_uri + ) + + def test_build_resolved_full_information_compare_with_ipv6(self): + file_descriptor: FileDescriptor = USubscriptionFileDescriptor + service_descriptor: ServiceDescriptor = ( + file_descriptor.services_by_name["uSubscription"] + ) + options: ServiceOptions = service_descriptor.GetOptions() + container: RepeatedCompositeFieldContainer = options.Extensions[ + Notification_Topic + ] + + uresrc: UResource = self.get_uresource(container) + uentity: UEntity = UEntityFactory.from_proto(service_descriptor) + + # inet_pton(): Convert IP address from its family-specific string format -> a packed, binary format + ipv6_address: str = "2001:db8:85a3:0:0:8a2e:370:7334" + ipv6_packed_ip: bytes = socket.inet_pton(socket.AF_INET6, ipv6_address) + + authority: UAuthority = UAuthority( + name="vcu.veh.gm.com", ip=ipv6_packed_ip + ) + uuri: UUri = UUri(entity=uentity, resource=uresrc, authority=authority) + + self.check_given_test_data_is_formed_correctly(uuri) + + actual_long_uri: str = LongUriSerializer().serialize(uuri) + actual_short_uri: str = ShortUriSerializer().serialize(uuri) + actual_micro_uri: bytes = MicroUriSerializer().serialize(uuri) + + expected_longuri_given_ipv6_uathority: str = ( + "//vcu.veh.gm.com/core.usubscription/3/SubscriptionChange#Update" + ) + expected_shorturi_given_ipv6_uathority: str = ( + "//2001:db8:85a3::8a2e:370:7334/0/3/32768" + ) + expected_microuri_given_ipv6_uathority = bytearray( + b"\x01\x02\x80\x00\x00\x00\x03\x00 \x01\r\xb8\x85\xa3\x00\x00\x00\x00\x8a.\x03ps4" + ) + + self.assertEqual( + expected_longuri_given_ipv6_uathority, actual_long_uri + ) + self.assertEqual( + expected_shorturi_given_ipv6_uathority, actual_short_uri + ) + self.assertEqual( + expected_microuri_given_ipv6_uathority, actual_micro_uri + ) + + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_uri/test_validator/test_urivalidator.py b/tests/test_uri/test_validator/test_urivalidator.py index 691a278..f5131a5 100644 --- a/tests/test_uri/test_validator/test_urivalidator.py +++ b/tests/test_uri/test_validator/test_urivalidator.py @@ -25,13 +25,13 @@ # ------------------------------------------------------------------------- - import json import os import unittest from uprotocol.uri.serializer.longuriserializer import LongUriSerializer from uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.uri.factory.uresource_builder import UResourceBuilder from uprotocol.validation.validationresult import ValidationResult from uprotocol.proto.uri_pb2 import UUri, UEntity, UResource, UAuthority @@ -55,7 +55,6 @@ def test_validate_uri_with_getEntity(self): status = UriValidator.validate(uri) self.assertTrue(ValidationResult.success().__eq__(status)) - def test_validate_with_malformed_uri(self): uri = LongUriSerializer().deserialize("neelam") status = UriValidator.validate(uri) @@ -89,7 +88,6 @@ def test_validateRpcResponse_with_valid_uri(self): status = UriValidator.validate_rpc_response(uri) self.assertTrue(ValidationResult.success().__eq__(status)) - def test_validateRpcResponse_with_malformed_uri(self): uri = LongUriSerializer().deserialize("neelam") status = UriValidator.validate_rpc_response(uri) @@ -165,27 +163,37 @@ def test_topic_uri_invalid_when_uri_is_missing_use_name_local(self): def test_rpc_topic_uri_with_version_when_it_is_valid_remote(self): uri = "//bo.cloud/petapp/1/rpc.response" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_success) def test_rpc_topic_uri_no_version_when_it_is_valid_remote(self): uri = "//bo.cloud/petapp//rpc.response" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_success) def test_rpc_topic_uri_with_version_when_it_is_valid_local(self): uri = "/petapp/1/rpc.response" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_success) def test_rpc_topic_uri_no_version_when_it_is_valid_local(self): uri = "/petapp//rpc.response" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_success) def test_rpc_topic_uri_invalid_when_uri_has_schema_only(self): uri = ":" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) def test_rpc_topic_uri_with_version_when_it_is_not_valid_missing_rpc_response_local(self): @@ -193,103 +201,154 @@ def test_rpc_topic_uri_with_version_when_it_is_not_valid_missing_rpc_response_lo status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) self.assertTrue(status.is_failure()) - def test_rpc_topic_uri_with_version_when_it_is_not_valid_missing_rpc_response_remote(self): + def test_rpc_topic_uri_with_version_when_it_is_not_valid_missing_rpc_response_remote( + self, + ): uri = "//petapp/1/dog" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) def test_rpc_topic_uri_invalid_when_uri_is_remote_no_authority(self): uri = "//" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) - def test_rpc_topic_uri_invalid_when_uri_is_remote_no_authority_with_use(self): + def test_rpc_topic_uri_invalid_when_uri_is_remote_no_authority_with_use( + self, + ): uri = "///body.access/1" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) def test_rpc_topic_uri_invalid_when_uri_is_missing_use(self): uri = "//VCU.myvin" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) def test_rpc_topic_uri_invalid_when_uri_is_missing_use_name_remote(self): uri = "/1" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) def test_rpc_topic_uri_invalid_when_uri_is_missing_use_name_local(self): uri = "//VCU.myvin//1" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) def test_rpc_method_uri_with_version_when_it_is_valid_remote(self): uri = "//VCU.myvin/body.access/1/rpc.UpdateDoor" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_success) def test_rpc_method_uri_no_version_when_it_is_valid_remote(self): uri = "//VCU.myvin/body.access//rpc.UpdateDoor" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_success) def test_rpc_method_uri_with_version_when_it_is_valid_local(self): uri = "/body.access/1/rpc.UpdateDoor" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_success) def test_rpc_method_uri_no_version_when_it_is_valid_local(self): uri = "/body.access//rpc.UpdateDoor" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_success) def test_rpc_method_uri_invalid_when_uri_has_schema_only(self): uri = ":" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) - def test_rpc_method_uri_with_version_when_it_is_not_valid_not_rpc_method_local(self): + def test_rpc_method_uri_with_version_when_it_is_not_valid_not_rpc_method_local( + self, + ): uri = "/body.access//UpdateDoor" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) - def test_rpc_method_uri_with_version_when_it_is_not_valid_not_rpc_method_remote(self): + def test_rpc_method_uri_with_version_when_it_is_not_valid_not_rpc_method_remote( + self, + ): uri = "//body.access/1/UpdateDoor" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) def test_rpc_method_uri_invalid_when_uri_is_remote_no_authority(self): uri = "//" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) - def test_rpc_method_uri_invalid_when_uri_is_remote_no_authority_with_use(self): + def test_rpc_method_uri_invalid_when_uri_is_remote_no_authority_with_use( + self, + ): uri = "///body.access/1/rpc.UpdateDoor" uuri = LongUriSerializer().deserialize(uri) status = UriValidator.validate_rpc_method(uuri) self.assertEqual("", str(uuri)) self.assertTrue(status.is_failure()) - def test_rpc_method_uri_invalid_when_uri_is_remote_missing_authority_remotecase(self): - uuri = UUri(entity=UEntity(name="body.access"),resource=UResource(name="rpc",instance="UpdateDoor"),authority= - UAuthority()) + def test_rpc_method_uri_invalid_when_uri_is_remote_missing_authority_remotecase( + self, + ): + uuri = UUri( + entity=UEntity(name="body.access"), + resource=UResource(name="rpc", instance="UpdateDoor"), + authority=UAuthority(), + ) status = UriValidator.validate_rpc_method(uuri) self.assertTrue(status.is_failure()) - self.assertEqual("Uri is remote missing uAuthority.", status.get_message()) + self.assertEqual( + "Uri is remote missing uAuthority.", status.get_message() + ) def test_rpc_method_uri_invalid_when_uri_is_missing_use(self): uri = "//VCU.myvin" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) def test_rpc_method_uri_invalid_when_uri_is_missing_use_name_local(self): uri = "/1/rpc.UpdateDoor" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) def test_rpc_method_uri_invalid_when_uri_is_missing_use_name_remote(self): uri = "//VCU.myvin//1/rpc.UpdateDoor" - status = UriValidator.validate_rpc_method(LongUriSerializer().deserialize(uri)) + status = UriValidator.validate_rpc_method( + LongUriSerializer().deserialize(uri) + ) self.assertTrue(status.is_failure()) def test_all_valid_uris(self): @@ -307,7 +366,9 @@ def test_all_invalid_uris(self): uuri = LongUriSerializer().deserialize(invalid_uri["uri"]) status = UriValidator.validate(uuri) self.assertTrue(status.is_failure()) - self.assertEqual(status.get_message(), invalid_uri["status_message"]) + self.assertEqual( + status.get_message(), invalid_uri["status_message"] + ) def test_all_valid_rpc_uris(self): valid_rpc_uris = self.get_json_object()["validRpcUris"] @@ -322,10 +383,14 @@ def test_all_invalid_rpc_uris(self): uuri = LongUriSerializer().deserialize(invalid_rpc_uri["uri"]) status = UriValidator.validate_rpc_method(uuri) self.assertTrue(status.is_failure()) - self.assertEqual(status.get_message(), invalid_rpc_uri["status_message"]) + self.assertEqual( + status.get_message(), invalid_rpc_uri["status_message"] + ) def test_all_valid_rpc_response_uris(self): - valid_rpc_response_uris = self.get_json_object()["validRpcResponseUris"] + valid_rpc_response_uris = self.get_json_object()[ + "validRpcResponseUris" + ] for valid_rpc_response_uri in valid_rpc_response_uris: uuri = LongUriSerializer().deserialize(valid_rpc_response_uri) status = UriValidator.validate_rpc_response(uuri) @@ -333,28 +398,142 @@ def test_all_valid_rpc_response_uris(self): self.assertTrue(status.is_success) def test_valid_rpc_response_uri(self): - uuri = UUri(entity=UEntity(name="neelam"),resource=UResource(name="rpc",id=0,instance="response")) + uuri = UUri( + entity=UEntity(name="neelam"), + resource=UResource(name="rpc", id=0, instance="response"), + ) status = UriValidator.validate_rpc_response(uuri) self.assertTrue(UriValidator.is_rpc_response(uuri)) self.assertTrue(status.is_success) def test_all_invalid_rpc_response_uris(self): - invalid_rpc_response_uris = self.get_json_object()["invalidRpcResponseUris"] + invalid_rpc_response_uris = self.get_json_object()[ + "invalidRpcResponseUris" + ] for invalid_rpc_response_uri in invalid_rpc_response_uris: uuri = LongUriSerializer().deserialize(invalid_rpc_response_uri) status = UriValidator.validate_rpc_response(uuri) self.assertTrue(status.is_failure()) + def test_invalid_rpc_method_uri(self): + uuri: UUri = UUri( + entity=UEntity(name="hello.world"), + resource=UResource(name="testrpc", instance="SayHello"), + ) + status = UriValidator.validate_rpc_method(uuri) + self.assertFalse(UriValidator.is_rpc_method(uuri)) + self.assertFalse(status.is_success()) + + def test_invalid_rpc_response_uri(self): + uuri: UUri = UUri( + entity=UEntity(name="hartley"), + resource=UResource(name="rpc", id=19999), + ) + status = UriValidator.validate_rpc_response(uuri) + self.assertFalse(UriValidator.is_rpc_response(uuri)) + self.assertFalse(status.is_success()) + + uuri: UUri = UUri( + entity=UEntity(name="hartley"), + resource=UResource(name="testrpc", instance="response"), + ) + status = UriValidator.validate_rpc_response(uuri) + self.assertFalse(UriValidator.is_rpc_response(uuri)) + self.assertFalse(status.is_success()) + + uuri: UUri = UUri( + entity=UEntity(name="hartley"), + resource=UResource(name="testrpc", instance="response"), + ) + status = UriValidator.validate_rpc_response(uuri) + self.assertFalse(UriValidator.is_rpc_response(uuri)) + self.assertFalse(status.is_success()) + @staticmethod def get_json_object(): current_directory = os.getcwd() - json_file_path = os.path.join(current_directory,"tests","test_uri","test_validator", "uris.json") - - with open(json_file_path, 'r') as json_file: + json_file_path = os.path.join( + current_directory, + "tests", + "test_uri", + "test_validator", + "uris.json", + ) + + with open(json_file_path, "r") as json_file: json_data = json.load(json_file) return json_data - -if __name__ == '__main__': + def test_is_rpc_method_with_uresource_and_no_uauthority(self): + self.assertFalse(UriValidator.is_rpc_method(UUri())) + + uri = UUri( + entity=UEntity(name="hartley"), + resource=UResourceBuilder.from_id(0x8000), + ) + self.assertFalse(UriValidator.is_rpc_method(uri)) + + def test_is_rpc_method_passing_null_for_uri(self): + self.assertFalse(UriValidator.is_rpc_method(None)) + + def test_is_rpc_method_passing_null_for_resource(self): + self.assertFalse(UriValidator.is_rpc_method(None)) + + def test_is_rpc_method_for_uresource_without_an_instance(self): + resource = UResource(name="rpc") + self.assertFalse(UriValidator.is_rpc_method(resource)) + + def test_is_rpc_method_for_uresource_with_an_empty_instance(self): + resource = UResource(name="rpc", instance="") + self.assertFalse(UriValidator.is_rpc_method(resource)) + + def test_is_rpc_method_for_uresource_with_id_that_is_less_than_min_topic( + self, + ): + resource = UResource(name="rpc", id=0) + self.assertTrue(UriValidator.is_rpc_method(resource)) + + def test_is_rpc_method_for_uresource_with_id_that_is_greater_than_min_topic( + self, + ): + resource = UResource(name="rpc", id=0x8000) + self.assertFalse(UriValidator.is_rpc_method(resource)) + + def test_is_empty_with_null_uri(self): + self.assertTrue(UriValidator.is_empty(None)) + + def test_is_resolved_when_uri_is_long_form_only(self): + uri = UUri( + entity=UEntity(name="hartley", version_major=23), + resource=UResource(name="rpc", instance="echo"), + ) + self.assertFalse(UriValidator.is_resolved(uri)) + + def test_is_local_when_authority_is_null(self): + self.assertFalse(UriValidator.is_local(None)) + + def test_is_remote_when_authority_is_null(self): + self.assertFalse(UriValidator.is_remote(None)) + + # def test_is_remote_when_authority_does_not_have_a_name_but_does_have_a_number_set(self): + # authority = UAuthority(id=b"hello Jello") + # self.assertTrue(UriValidator.is_remote(authority)) + # self.assertFalse(authority.name is not None) + # self.assertEqual(authority.number_case(), UAuthority.NumberCase.ID) + + # def test_is_remote_when_authority_has_name_and_number_set(self): + # authority = UAuthority(name="vcu.veh.gm.com", id=b"hello Jello") + # self.assertTrue(UriValidator.is_remote(authority)) + # self.assertTrue(authority.name is not None) + # self.assertEqual(authority.number_case(), UAuthority.NumberCase.ID) + + # def test_is_remote_when_authority_has_name_and_number_is_not_set(self): + # authority = UAuthority(name="vcu.veh.gm.com") + # self.assertTrue(UriValidator.is_remote(authority)) + # self.assertTrue(authority.name is not None) + # self.assertEqual(authority.number_case(), UAuthority.NumberCase.NUMBER_NOT_SET) + + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_uuid/test_factory/test_uuidfactory.py b/tests/test_uuid/test_factory/test_uuidfactory.py index badf2f4..8ae0db3 100644 --- a/tests/test_uuid/test_factory/test_uuidfactory.py +++ b/tests/test_uuid/test_factory/test_uuidfactory.py @@ -1,6 +1,6 @@ # ------------------------------------------------------------------------- # -# Copyright (c) 2023 General Motors GTO LLC +# Copyright (c) 2024 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -19,36 +19,35 @@ # specific language governing permissions and limitations # under the License. # SPDX-FileType: SOURCE -# SPDX-FileCopyrightText: 2023 General Motors GTO LLC +# SPDX-FileCopyrightText: 2024 General Motors GTO LLC # SPDX-License-Identifier: Apache-2.0 # # ------------------------------------------------------------------------- -from datetime import datetime, timedelta +from datetime import datetime, timedelta, UTC import unittest from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer from uprotocol.uuid.serializer.microuuidserializer import MicroUuidSerializer -from uprotocol.uuid.factory.uuidfactory import UUIDFactory, Factories +from uprotocol.uuid.factory.uuidfactory import Factories from uprotocol.uuid.factory.uuidutils import UUIDUtils, Version from uprotocol.proto.uuid_pb2 import UUID - class TestUUIDFactory(unittest.TestCase): def test_uuidv8_creation(self): now = datetime.now() uuid = Factories.UPROTOCOL.create(now) - version = UUIDUtils.getVersion(uuid) - time = UUIDUtils.getTime(uuid) + version = UUIDUtils.get_version(uuid) + time = UUIDUtils.get_time(uuid) bytes_data = MicroUuidSerializer.instance().serialize(uuid) uuid_string = LongUuidSerializer.instance().serialize(uuid) self.assertIsNotNone(uuid) - self.assertTrue(UUIDUtils.isUProtocol(uuid)) - self.assertTrue(UUIDUtils.isuuid(uuid)) - self.assertFalse(UUIDUtils.isUuidv6(uuid)) + self.assertTrue(UUIDUtils.is_uprotocol(uuid)) + self.assertTrue(UUIDUtils.is_uuid(uuid)) + self.assertFalse(UUIDUtils.is_uuidv6(uuid)) self.assertTrue(version) self.assertTrue(time) self.assertEqual(time, int(now.timestamp() * 1000)) @@ -66,15 +65,15 @@ def test_uuidv8_creation(self): def test_uuidv8_creation_with_null_instant(self): uuid = Factories.UPROTOCOL.create(None) - version = UUIDUtils.getVersion(uuid) - time = UUIDUtils.getTime(uuid) + version = UUIDUtils.get_version(uuid) + time = UUIDUtils.get_time(uuid) bytes_data = MicroUuidSerializer.instance().serialize(uuid) uuid_string = LongUuidSerializer.instance().serialize(uuid) self.assertIsNotNone(uuid) - self.assertTrue(UUIDUtils.isUProtocol(uuid)) - self.assertTrue(UUIDUtils.isuuid(uuid)) - self.assertFalse(UUIDUtils.isUuidv6(uuid)) + self.assertTrue(UUIDUtils.is_uprotocol(uuid)) + self.assertTrue(UUIDUtils.is_uuid(uuid)) + self.assertFalse(UUIDUtils.is_uuidv6(uuid)) self.assertTrue(version) self.assertTrue(time) self.assertGreater(len(bytes_data), 0) @@ -96,7 +95,10 @@ def test_uuidv8_overflow(self): for i in range(max_count * 2): uuid_list.append(Factories.UPROTOCOL.create(now)) - self.assertEqual(UUIDUtils.getTime(uuid_list[0]), UUIDUtils.getTime(uuid_list[i])) + self.assertEqual( + UUIDUtils.get_time(uuid_list[0]), + UUIDUtils.get_time(uuid_list[i]), + ) self.assertEqual(uuid_list[0].lsb, uuid_list[i].lsb) if i > max_count: self.assertEqual(uuid_list[max_count].msb, uuid_list[i].msb) @@ -104,15 +106,15 @@ def test_uuidv8_overflow(self): def test_uuidv6_creation_with_instant(self): now = datetime.now() uuid = Factories.UUIDV6.create(now) - version = UUIDUtils.getVersion(uuid) + version = UUIDUtils.get_version(uuid) # time = UUIDUtils.getTime(uuid) bytes_data = MicroUuidSerializer.instance().serialize(uuid) uuid_string = LongUuidSerializer.instance().serialize(uuid) self.assertIsNotNone(uuid) - self.assertTrue(UUIDUtils.isUuidv6(uuid)) - self.assertTrue(UUIDUtils.isuuid(uuid)) - self.assertFalse(UUIDUtils.isUProtocol(uuid)) + self.assertTrue(UUIDUtils.is_uuidv6(uuid)) + self.assertTrue(UUIDUtils.is_uuid(uuid)) + self.assertFalse(UUIDUtils.is_uprotocol(uuid)) self.assertTrue(version) # self.assertTrue(time) # self.assertEqual(time, int(17007094616498160 * 1000)) @@ -129,15 +131,15 @@ def test_uuidv6_creation_with_instant(self): def test_uuidv6_creation_with_null_instant(self): uuid = Factories.UUIDV6.create(None) - version = UUIDUtils.getVersion(uuid) - time = UUIDUtils.getTime(uuid) + version = UUIDUtils.get_version(uuid) + time = UUIDUtils.get_time(uuid) bytes_data = MicroUuidSerializer.instance().serialize(uuid) uuid_string = LongUuidSerializer.instance().serialize(uuid) self.assertIsNotNone(uuid) - self.assertTrue(UUIDUtils.isUuidv6(uuid)) - self.assertFalse(UUIDUtils.isUProtocol(uuid)) - self.assertTrue(UUIDUtils.isuuid(uuid)) + self.assertTrue(UUIDUtils.is_uuidv6(uuid)) + self.assertFalse(UUIDUtils.is_uprotocol(uuid)) + self.assertTrue(UUIDUtils.is_uuid(uuid)) self.assertTrue(version) self.assertTrue(time) self.assertGreater(len(bytes_data), 0) @@ -152,16 +154,18 @@ def test_uuidv6_creation_with_null_instant(self): self.assertEqual(uuid, uuid2) def test_UUIDUtils_for_random_uuid(self): - uuid = LongUuidSerializer.instance().deserialize("195f9bd1-526d-4c28-91b1-ff34c8e3632d") - version = UUIDUtils.getVersion(uuid) - time = UUIDUtils.getTime(uuid) + uuid = LongUuidSerializer.instance().deserialize( + "195f9bd1-526d-4c28-91b1-ff34c8e3632d" + ) + version = UUIDUtils.get_version(uuid) + time = UUIDUtils.get_time(uuid) bytes_data = MicroUuidSerializer.instance().serialize(uuid) uuid_string = LongUuidSerializer.instance().serialize(uuid) self.assertIsNotNone(uuid) - self.assertFalse(UUIDUtils.isUuidv6(uuid)) - self.assertFalse(UUIDUtils.isUProtocol(uuid)) - self.assertFalse(UUIDUtils.isuuid(uuid)) + self.assertFalse(UUIDUtils.is_uuidv6(uuid)) + self.assertFalse(UUIDUtils.is_uprotocol(uuid)) + self.assertFalse(UUIDUtils.is_uuid(uuid)) self.assertTrue(version) self.assertFalse(time) self.assertGreater(len(bytes_data), 0) @@ -177,22 +181,22 @@ def test_UUIDUtils_for_random_uuid(self): def test_UUIDUtils_for_empty_uuid(self): uuid = UUID() - version = UUIDUtils.getVersion(uuid) - time = UUIDUtils.getTime(uuid) + version = UUIDUtils.get_version(uuid) + time = UUIDUtils.get_time(uuid) bytes_data = MicroUuidSerializer.instance().serialize(uuid) uuid_string = LongUuidSerializer.instance().serialize(uuid) self.assertIsNotNone(uuid) - self.assertFalse(UUIDUtils.isUuidv6(uuid)) - self.assertFalse(UUIDUtils.isUProtocol(uuid)) + self.assertFalse(UUIDUtils.is_uuidv6(uuid)) + self.assertFalse(UUIDUtils.is_uprotocol(uuid)) self.assertTrue(version) self.assertEqual(version, Version.VERSION_UNKNOWN) self.assertFalse(time) self.assertGreater(len(bytes_data), 0) self.assertFalse(uuid_string.isspace()) - self.assertFalse(UUIDUtils.isUuidv6(None)) - self.assertFalse(UUIDUtils.isUProtocol(None)) - self.assertFalse(UUIDUtils.isuuid(None)) + self.assertFalse(UUIDUtils.is_uuidv6(None)) + self.assertFalse(UUIDUtils.is_uprotocol(None)) + self.assertFalse(UUIDUtils.is_uuid(None)) uuid1 = MicroUuidSerializer.instance().deserialize(bytes_data) @@ -204,24 +208,30 @@ def test_UUIDUtils_for_empty_uuid(self): self.assertEqual(uuid, uuid2) def test_UUIDUtils_for_null_uuid(self): - self.assertFalse(UUIDUtils.getVersion(None)) - self.assertEqual(len(MicroUuidSerializer.instance().serialize(None)), 0) - self.assertEqual(len(LongUuidSerializer.instance().serialize(None)),0) - self.assertFalse(UUIDUtils.isUuidv6(None)) - self.assertFalse(UUIDUtils.isUProtocol(None)) - self.assertFalse(UUIDUtils.isuuid(None)) - self.assertFalse(UUIDUtils.getTime(None)) + self.assertFalse(UUIDUtils.get_version(None)) + self.assertEqual( + len(MicroUuidSerializer.instance().serialize(None)), 0 + ) + self.assertEqual(len(LongUuidSerializer.instance().serialize(None)), 0) + self.assertFalse(UUIDUtils.is_uuidv6(None)) + self.assertFalse(UUIDUtils.is_uprotocol(None)) + self.assertFalse(UUIDUtils.is_uuid(None)) + self.assertFalse(UUIDUtils.get_time(None)) def test_uuidutils_from_invalid_uuid(self): uuid = UUID(msb=9 << 12, lsb=0) # Invalid UUID type - self.assertFalse(UUIDUtils.getVersion(uuid)) - self.assertFalse(UUIDUtils.getTime(uuid)) - self.assertTrue(len(MicroUuidSerializer.instance().serialize(uuid)) > 0) - self.assertFalse(LongUuidSerializer.instance().serialize(uuid).isspace()) - self.assertFalse(UUIDUtils.isUuidv6(uuid)) - self.assertFalse(UUIDUtils.isUProtocol(uuid)) - self.assertFalse(UUIDUtils.isuuid(uuid)) - self.assertFalse(UUIDUtils.getTime(uuid)) + self.assertFalse(UUIDUtils.get_version(uuid)) + self.assertFalse(UUIDUtils.get_time(uuid)) + self.assertTrue( + len(MicroUuidSerializer.instance().serialize(uuid)) > 0 + ) + self.assertFalse( + LongUuidSerializer.instance().serialize(uuid).isspace() + ) + self.assertFalse(UUIDUtils.is_uuidv6(uuid)) + self.assertFalse(UUIDUtils.is_uprotocol(uuid)) + self.assertFalse(UUIDUtils.is_uuid(uuid)) + self.assertFalse(UUIDUtils.get_time(uuid)) def test_uuidutils_fromstring_with_invalid_string(self): uuid = LongUuidSerializer.instance().deserialize(None) @@ -236,26 +246,27 @@ def test_uuidutils_frombytes_with_invalid_bytes(self): self.assertTrue(uuid1 == UUID()) def test_create_uprotocol_uuid_in_the_past(self): - past = datetime.utcnow() - timedelta(seconds=10) + past = datetime.now(UTC) - timedelta(seconds=10) uuid = Factories.UPROTOCOL.create(past) - time = UUIDUtils.getTime(uuid) - self.assertTrue(UUIDUtils.isUProtocol(uuid)) - self.assertTrue(UUIDUtils.isuuid(uuid)) + time = UUIDUtils.get_time(uuid) + self.assertTrue(UUIDUtils.is_uprotocol(uuid)) + self.assertTrue(UUIDUtils.is_uuid(uuid)) self.assertTrue(time is not None) self.assertEqual(time, int(past.timestamp() * 1000)) def test_create_uprotocol_uuid_with_different_time_values(self): uuid = Factories.UPROTOCOL.create() import time - time.sleep(0.01) # Sleep for 10 milliseconds + + time.sleep(0.01) # Sleep for 10 milliseconds uuid1 = Factories.UPROTOCOL.create() - time = UUIDUtils.getTime(uuid) - time1 = UUIDUtils.getTime(uuid1) + time = UUIDUtils.get_time(uuid) + time1 = UUIDUtils.get_time(uuid1) - self.assertTrue(UUIDUtils.isUProtocol(uuid)) - self.assertTrue(UUIDUtils.isuuid(uuid)) - self.assertTrue(UUIDUtils.isUProtocol(uuid1)) - self.assertTrue(UUIDUtils.isuuid(uuid1)) + self.assertTrue(UUIDUtils.is_uprotocol(uuid)) + self.assertTrue(UUIDUtils.is_uuid(uuid)) + self.assertTrue(UUIDUtils.is_uprotocol(uuid1)) + self.assertTrue(UUIDUtils.is_uuid(uuid1)) self.assertTrue(time is not None) self.assertTrue(time1 is not None) @@ -266,19 +277,14 @@ def test_create_both_uuidv6_and_v8_to_compare_performance(self): uuidv8_list = [] max_count = 10000 - start = datetime.utcnow() for _ in range(max_count): uuidv8_list.append(Factories.UPROTOCOL.create()) - v8_diff = datetime.utcnow() - start - start = datetime.utcnow() for _ in range(max_count): uuidv6_list.append(Factories.UUIDV6.create()) - v6_diff = datetime.utcnow() - start # print( # f"UUIDv8: [{v8_diff.total_seconds() / max_count}s] UUIDv6: [{v6_diff.total_seconds() / max_count}s]") -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() - diff --git a/tests/test_uuid/test_factory/test_uuidutils.py b/tests/test_uuid/test_factory/test_uuidutils.py new file mode 100644 index 0000000..db5416f --- /dev/null +++ b/tests/test_uuid/test_factory/test_uuidutils.py @@ -0,0 +1,119 @@ +# ------------------------------------------------------------------------- +# +# Copyright (c) 2024 General Motors GTO LLC +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# SPDX-FileType: SOURCE +# SPDX-FileCopyrightText: 2024 General Motors GTO LLC +# SPDX-License-Identifier: Apache-2.0 +# +# ------------------------------------------------------------------------- + +import unittest +import time + +from uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder +from uprotocol.uuid.factory.uuidfactory import Factories +from uprotocol.uuid.factory.uuidutils import UUIDUtils +from uprotocol.uri.factory.uresource_builder import UResourceBuilder + +from uprotocol.proto.uattributes_pb2 import UAttributes, UPriority +from uprotocol.proto.uri_pb2 import UUri, UEntity, UAuthority +from uprotocol.proto.uuid_pb2 import UUID + + +def create_id(): + return Factories.UPROTOCOL.create() + + +def build_source(): + return UUri( + authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), + entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), + resource=UResourceBuilder.for_rpc_request(None), + ) + + +DELTA = 30 +DELAY_MS = 100 +DELAY_LONG_MS = 100000 +TTL = 10000 + + +class TestUUIDUtils(unittest.TestCase): + + def test_get_elapsed_time_creation_time_unknown(self): + self.assertFalse(UUIDUtils.get_elapsed_time(UUID()) is not None) + + def test_get_remaining_time_no_ttl(self): + id: UUID = create_id() + self.assertFalse(UUIDUtils.get_remaining_time(id, 0) is not None) + self.assertFalse(UUIDUtils.get_remaining_time(id, -1) is not None) + + def test_remaining_time_none_uuid(self): + id: UUID = None + self.assertFalse(UUIDUtils.get_remaining_time(id, 0) is not None) + + def test_get_remaining_time_expired(self): + id: UUID = create_id() + time.sleep(DELAY_MS / 1000) + self.assertFalse( + UUIDUtils.get_remaining_time(id, DELAY_MS - DELTA) is not None + ) + + def test_get_remaining_time_attributes_no_ttl(self): + attributes: UAttributes = UAttributesBuilder.publish( + build_source(), UPriority.UPRIORITY_CS0 + ).build() + self.assertFalse(UUIDUtils.get_remaining_time(attributes) is not None) + + def test_get_remaining_time_attributes_expired(self): + attributes: UAttributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withTtl(DELAY_MS - DELTA) + .build() + ) + time.sleep(DELAY_MS / 1000) + self.assertFalse(UUIDUtils.get_remaining_time(attributes) is not None) + + def test_is_expired(self): + id: UUID = create_id() + self.assertFalse(UUIDUtils.is_expired(id, DELAY_MS - DELTA)) + time.sleep(DELAY_MS / 1000) + self.assertTrue(UUIDUtils.is_expired(id, DELAY_MS - DELTA)) + + def test_is_expired_no_ttl(self): + id: UUID = create_id() + self.assertFalse(UUIDUtils.is_expired(id, 0)) + self.assertFalse(UUIDUtils.is_expired(id, -1)) + + def test_is_expired_attributes(self): + attributes: UAttributes = ( + UAttributesBuilder.publish(build_source(), UPriority.UPRIORITY_CS0) + .withTtl(DELAY_MS - DELTA) + .build() + ) + self.assertFalse(UUIDUtils.is_expired(attributes)) + time.sleep(DELAY_MS / 1000) + self.assertTrue(UUIDUtils.is_expired(attributes)) + + def test_is_expired_attributes_no_ttl(self): + attributes: UAttributes = UAttributesBuilder.publish( + build_source(), UPriority.UPRIORITY_CS0 + ).build() + self.assertFalse(UUIDUtils.is_expired(attributes)) diff --git a/tests/test_uuid/test_validator/test_uuidvalidator.py b/tests/test_uuid/test_validator/test_uuidvalidator.py index 45e0455..eeb8f77 100644 --- a/tests/test_uuid/test_validator/test_uuidvalidator.py +++ b/tests/test_uuid/test_validator/test_uuidvalidator.py @@ -26,7 +26,7 @@ import unittest -from datetime import datetime, timezone +from datetime import datetime, UTC from uprotocol.uuid.factory.uuidutils import UUIDUtils from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer @@ -61,7 +61,7 @@ def test_invalid_uuid(self): ) def test_invalid_time_uuid(self): - epoch_time = datetime.utcfromtimestamp(0).replace(tzinfo=timezone.utc) + epoch_time = datetime.fromtimestamp(0, UTC) uuid = Factories.UPROTOCOL.create( epoch_time @@ -102,7 +102,7 @@ def test_good_uuidv6(self): validator = UuidValidator.get_validator(uuid) self.assertIsNotNone(validator) - self.assertTrue(UUIDUtils.isUuidv6(uuid)) + self.assertTrue(UUIDUtils.is_uuidv6(uuid)) self.assertEqual(UCode.OK, validator.validate(uuid).code) def test_uuidv6_with_invalid_uuid(self): diff --git a/uprotocol/cloudevent/datamodel/ucloudeventattributes.py b/uprotocol/cloudevent/datamodel/ucloudeventattributes.py index c28244e..493542c 100644 --- a/uprotocol/cloudevent/datamodel/ucloudeventattributes.py +++ b/uprotocol/cloudevent/datamodel/ucloudeventattributes.py @@ -24,10 +24,8 @@ # ------------------------------------------------------------------------- -from typing import Optional from uprotocol.proto.uattributes_pb2 import UPriority -from uprotocol.proto.uattributes_pb2 import UPriority class UCloudEventAttributes: @@ -35,7 +33,14 @@ class UCloudEventAttributes: Specifies the properties that can configure the UCloudEvent. """ - def __init__(self, priority: UPriority, hash_value: str = None, ttl: int = None, token: str = None): + def __init__( + self, + priority: UPriority, + hash_value: str = None, + ttl: int = None, + token: str = None, + traceparent: str = None, + ): """ Construct the properties object.

@param hash_value: an HMAC generated on the data portion of the CloudEvent message using the device key. @@ -48,7 +53,7 @@ def __init__(self, priority: UPriority, hash_value: str = None, ttl: int = None, self.priority = priority self.ttl = ttl self.token = token - self.traceparent = None + self.traceparent = traceparent @staticmethod def empty(): @@ -65,9 +70,13 @@ def is_empty(self): @return: Returns true if this attributes container is an empty container and has no valuable information in building a CloudEvent. """ - return (self.hash is None or self.hash.isspace()) and (self.ttl is None) and ( - self.token is None or self.token.isspace()) and (self.priority is None or self.priority.isspace()) and ( - self.traceparent is None or self.traceparent.isspace()) + return ( + (self.hash is None or self.hash.isspace()) + and (self.ttl is None) + and (self.token is None or self.token.isspace()) + and (self.priority is None or self.priority.isspace()) + and (self.traceparent is None or self.traceparent.isspace()) + ) def get_hash(self) -> str: """ @@ -97,22 +106,43 @@ def get_token(self) -> str: """ return self.token if self.token and self.token.strip() else None + def get_traceparent(self) -> str: + """ + Traceparent of the event. + @return: Returns an optional traceparent attribute. + """ + return ( + self.traceparent + if self.traceparent and self.traceparent.strip() + else None + ) + def __eq__(self, other): if self is other: return True if not isinstance(other, UCloudEventAttributes): return False return ( - self.hash == other.hash and self.priority == other.priority and self.ttl == other.ttl and self.token - == other.token and self.traceparent == other.traceparent) + self.hash == other.hash + and self.priority == other.priority + and self.ttl == other.ttl + and self.token == other.token + and self.traceparent == other.traceparent + ) def __hash__(self): - return hash((self.hash, self.priority, self.ttl, self.token, self.traceparent)) + return hash( + (self.hash, self.priority, self.ttl, self.token, self.traceparent) + ) def __str__(self): - traceparent_string = f", traceparent='{self.traceparent}'" if self.traceparent else "" - return f"UCloudEventAttributes{{hash='{self.hash}', priority={self.priority}," \ - f" ttl={self.ttl}, token='{self.token}'{traceparent_string}}}" + traceparent_string = ( + f", traceparent='{self.traceparent}'" if self.traceparent else "" + ) + return ( + f"UCloudEventAttributes{{hash='{self.hash}', priority={self.priority}," + f" ttl={self.ttl}, token='{self.token}'{traceparent_string}}}" + ) class UCloudEventAttributesBuilder: @@ -165,7 +195,6 @@ def with_token(self, token: str): self.token = token return self - def with_traceparent(self, traceparent: str): """ An identifier used to correlate observability across related events. @@ -180,11 +209,20 @@ def build(self): Construct the UCloudEventAttributes from the builder.

@return: Returns a constructed UProperty. """ - return UCloudEventAttributes(self.priority, self.hash, self.ttl, self.token) + return UCloudEventAttributes( + self.priority, self.hash, self.ttl, self.token, self.traceparent + ) if __name__ == "__main__": # Example usage: - attributes = UCloudEventAttributesBuilder().with_hash("abc123").with_priority(UPriority.UPRIORITY_CS0).with_ttl( - 1000).with_token("xyz456").with_traceparent("123456").build() + attributes = ( + UCloudEventAttributesBuilder() + .with_hash("abc123") + .with_priority(UPriority.UPRIORITY_CS0) + .with_ttl(1000) + .with_token("xyz456") + .with_traceparent("123456") + .build() + ) print(attributes) diff --git a/uprotocol/cloudevent/factory/cloudeventfactory.py b/uprotocol/cloudevent/factory/cloudeventfactory.py index 3788702..fee6de8 100644 --- a/uprotocol/cloudevent/factory/cloudeventfactory.py +++ b/uprotocol/cloudevent/factory/cloudeventfactory.py @@ -29,7 +29,9 @@ from google.protobuf import empty_pb2 from google.protobuf.any_pb2 import Any -from uprotocol.cloudevent.datamodel.ucloudeventattributes import UCloudEventAttributes +from uprotocol.cloudevent.datamodel.ucloudeventattributes import ( + UCloudEventAttributes, +) from uprotocol.cloudevent.factory.ucloudevent import UCloudEvent from uprotocol.proto.uattributes_pb2 import UMessageType from uprotocol.uuid.factory.uuidfactory import Factories @@ -44,8 +46,13 @@ class CloudEventFactory: PROTOBUF_CONTENT_TYPE = "application/x-protobuf" @staticmethod - def request(application_uri_for_rpc: str, service_method_uri: str, request_id: str, proto_payload: Any, - attributes: UCloudEventAttributes) -> CloudEvent: + def request( + application_uri_for_rpc: str, + service_method_uri: str, + request_id: str, + proto_payload: Any, + attributes: UCloudEventAttributes, + ) -> CloudEvent: """ Create a CloudEvent for an event for the use case of: RPC Request message. @param application_uri_for_rpc: The uri for the application requesting the RPC. @@ -56,21 +63,27 @@ def request(application_uri_for_rpc: str, service_method_uri: str, request_id: s @return: Returns an request CloudEvent. """ event_id = CloudEventFactory.generate_cloud_event_id() - cloud_event = CloudEventFactory.build_base_cloud_event(event_id, application_uri_for_rpc, - - proto_payload.SerializeToString(), - proto_payload.DESCRIPTOR.full_name, attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_REQUEST) - ) + cloud_event = CloudEventFactory.build_base_cloud_event( + event_id, + application_uri_for_rpc, + proto_payload.SerializeToString(), + proto_payload.DESCRIPTOR.full_name, + attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_REQUEST), + ) cloud_event.__setitem__("sink", service_method_uri) cloud_event.__setitem__("reqid", request_id) return cloud_event @staticmethod - def response(application_uri_for_rpc: str, service_method_uri: str, request_id: str, proto_payload: Any, - attributes: UCloudEventAttributes) -> CloudEvent: + def response( + application_uri_for_rpc: str, + service_method_uri: str, + request_id: str, + proto_payload: Any, + attributes: UCloudEventAttributes, + ) -> CloudEvent: """ Create a CloudEvent for an event for the use case of: RPC Response message. @param application_uri_for_rpc: The destination of the response. The uri for the original application that @@ -84,20 +97,27 @@ def response(application_uri_for_rpc: str, service_method_uri: str, request_id: @return: Returns an response CloudEvent. """ event_id = CloudEventFactory.generate_cloud_event_id() - cloud_event = CloudEventFactory.build_base_cloud_event(event_id, service_method_uri, - - proto_payload.SerializeToString(), - proto_payload.DESCRIPTOR.full_name, attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_RESPONSE)) + cloud_event = CloudEventFactory.build_base_cloud_event( + event_id, + service_method_uri, + proto_payload.SerializeToString(), + proto_payload.DESCRIPTOR.full_name, + attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_RESPONSE), + ) cloud_event.__setitem__("sink", application_uri_for_rpc) cloud_event.__setitem__("reqid", request_id) return cloud_event @staticmethod - def failed_response(application_uri_for_rpc: str, service_method_uri: str, request_id: str, - communication_status: int, attributes: UCloudEventAttributes) -> CloudEvent: + def failed_response( + application_uri_for_rpc: str, + service_method_uri: str, + request_id: str, + communication_status: int, + attributes: UCloudEventAttributes, + ) -> CloudEvent: """ Create a CloudEvent for an event for the use case of: RPC Response message that failed. @param application_uri_for_rpc: The destination of the response. The uri for the original application that @@ -114,13 +134,14 @@ def failed_response(application_uri_for_rpc: str, service_method_uri: str, reque # Create an Any message packing an Empty message empty_proto_payload = Any() empty_proto_payload.Pack(empty_pb2.Empty()) - cloud_event = CloudEventFactory.build_base_cloud_event(event_id, service_method_uri, - - empty_proto_payload.SerializeToString(), # Empty payload - "google.protobuf.Empty", attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_RESPONSE) - ) + cloud_event = CloudEventFactory.build_base_cloud_event( + event_id, + service_method_uri, + empty_proto_payload.SerializeToString(), # Empty payload + "google.protobuf.Empty", + attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_RESPONSE), + ) cloud_event.__setitem__("sink", application_uri_for_rpc) cloud_event.__setitem__("reqid", request_id) cloud_event.__setitem__("commstatus", communication_status) @@ -128,7 +149,9 @@ def failed_response(application_uri_for_rpc: str, service_method_uri: str, reque return cloud_event @staticmethod - def publish(source: str, proto_payload: Any, attributes: UCloudEventAttributes) -> CloudEvent: + def publish( + source: str, proto_payload: Any, attributes: UCloudEventAttributes + ) -> CloudEvent: """ Create a CloudEvent for an event for the use case of: Publish generic message. @param source:The uri of the topic being published. @@ -137,15 +160,24 @@ def publish(source: str, proto_payload: Any, attributes: UCloudEventAttributes) @return:Returns a publish CloudEvent. """ event_id = CloudEventFactory.generate_cloud_event_id() - cloud_event = CloudEventFactory.build_base_cloud_event(event_id, source, proto_payload.SerializeToString(), - proto_payload.DESCRIPTOR.full_name, attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event = CloudEventFactory.build_base_cloud_event( + event_id, + source, + proto_payload.SerializeToString(), + proto_payload.DESCRIPTOR.full_name, + attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) return cloud_event @staticmethod - def notification(source: str, sink: str, proto_payload: Any, attributes: UCloudEventAttributes) -> CloudEvent: + def notification( + source: str, + sink: str, + proto_payload: Any, + attributes: UCloudEventAttributes, + ) -> CloudEvent: """ Create a CloudEvent for an event for the use case of: Publish a notification message. A published event containing the sink (destination) is often referred to as a notification, it is an event sent to a specific @@ -157,10 +189,14 @@ def notification(source: str, sink: str, proto_payload: Any, attributes: UCloudE @return: Returns a publish CloudEvent. """ event_id = CloudEventFactory.generate_cloud_event_id() - cloud_event = CloudEventFactory.build_base_cloud_event(event_id, source, proto_payload.SerializeToString(), - proto_payload.DESCRIPTOR.full_name, attributes, - UCloudEvent.get_event_type( - UMessageType.UMESSAGE_TYPE_PUBLISH)) + cloud_event = CloudEventFactory.build_base_cloud_event( + event_id, + source, + proto_payload.SerializeToString(), + proto_payload.DESCRIPTOR.full_name, + attributes, + UCloudEvent.get_event_type(UMessageType.UMESSAGE_TYPE_PUBLISH), + ) cloud_event.__setitem__("sink", sink) return cloud_event @@ -175,11 +211,17 @@ def generate_cloud_event_id() -> str: return LongUuidSerializer.instance().serialize(uuid_inst) @staticmethod - def build_base_cloud_event(id: str, source: str, proto_payload_bytes: bytes, proto_payload_schema: str, - attributes: UCloudEventAttributes, type) -> CloudEvent: + def build_base_cloud_event( + id: str, + source: str, + proto_payload_bytes: bytes, + proto_payload_schema: str, + attributes: UCloudEventAttributes, + type, + ) -> CloudEvent: """ Base CloudEvent builder that is the same for all CloudEvent types. - + @param id:Event unique identifier. @param source: Identifies who is sending this event in the format of a uProtocol URI that can be built from a UUri object. @@ -194,13 +236,15 @@ def build_base_cloud_event(id: str, source: str, proto_payload_bytes: bytes, pro """ json_attributes = {"id": id, "source": source, "type": type} if attributes.get_hash() is not None: - json_attributes['hash'] = attributes.get_hash() + json_attributes["hash"] = attributes.get_hash() if attributes.get_ttl() is not None: - json_attributes['ttl'] = attributes.get_ttl() + json_attributes["ttl"] = attributes.get_ttl() if attributes.get_priority() is not None: - json_attributes['priority'] = attributes.get_priority() + json_attributes["priority"] = attributes.get_priority() if attributes.get_token() is not None: - json_attributes['token'] = attributes.get_token() + json_attributes["token"] = attributes.get_token() + if attributes.get_traceparent() is not None: + json_attributes["traceparent"] = attributes.get_traceparent() cloud_event = CloudEvent(json_attributes, proto_payload_bytes) diff --git a/uprotocol/cloudevent/factory/ucloudevent.py b/uprotocol/cloudevent/factory/ucloudevent.py index 7eb3a14..154864b 100644 --- a/uprotocol/cloudevent/factory/ucloudevent.py +++ b/uprotocol/cloudevent/factory/ucloudevent.py @@ -1,5 +1,4 @@ # ------------------------------------------------------------------------- -import time # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -25,6 +24,7 @@ # ------------------------------------------------------------------------- +import time from datetime import datetime, timedelta from cloudevents.http import CloudEvent @@ -32,13 +32,16 @@ from google.protobuf.message import DecodeError from uprotocol.proto.ustatus_pb2 import UCode -from uprotocol.proto.uattributes_pb2 import UMessageType, UPriority, UAttributes +from uprotocol.proto.uattributes_pb2 import ( + UMessageType, + UPriority, + UAttributes, +) from uprotocol.proto.upayload_pb2 import UPayload -from uprotocol.proto.uri_pb2 import UUri from uprotocol.uri.serializer.longuriserializer import LongUriSerializer from uprotocol.uuid.factory.uuidutils import UUIDUtils from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer -from uprotocol.proto.upayload_pb2 import UPayloadFormat, UPayload +from uprotocol.proto.upayload_pb2 import UPayloadFormat from uprotocol.proto.umessage_pb2 import UMessage from uprotocol.proto.uuid_pb2 import UUID @@ -51,48 +54,65 @@ class UCloudEvent: @staticmethod def get_source(ce: CloudEvent) -> str: """ - Extract the source from a cloud event. The source is a mandatory attribute. The CloudEvent constructor does - not allow creating a cloud event without a source.

- @param ce:CloudEvent with source to be extracted. - @return:Returns the String value of a CloudEvent source attribute. + Extract the source from a cloud event. The source is a mandatory + attribute. The CloudEvent constructor does not allow creating a cloud + event without a source.

+ @param ce:CloudEvent with source to be + extracted. + @return:Returns the String value of a CloudEvent source + attribute. """ return UCloudEvent.extract_string_value_from_attributes("source", ce) @staticmethod def get_data_content_type(ce: CloudEvent) -> str: """ - Extract the source from a cloud event. The source is a mandatory attribute. The CloudEvent constructor does - not allow creating a cloud event without a source.

- @param ce:CloudEvent with source to be extracted. - @return:Returns the String value of a CloudEvent source attribute. + Extract the source from a cloud event. The source is a mandatory + attribute. The CloudEvent constructor does not allow creating a cloud + event without a source.

+ @param ce:CloudEvent with source to be + extracted. + @return:Returns the String value of a CloudEvent source + attribute. """ - return UCloudEvent.extract_string_value_from_attributes("datacontenttype", ce) + return UCloudEvent.extract_string_value_from_attributes( + "datacontenttype", ce + ) @staticmethod def get_data_schema(ce: CloudEvent) -> str: """ - Extract the source from a cloud event. The source is a mandatory attribute. The CloudEvent constructor does - not allow creating a cloud event without a source.

- @param ce:CloudEvent with source to be extracted. - @return:Returns the String value of a CloudEvent source attribute. + Extract the source from a cloud event. The source is a mandatory + attribute. The CloudEvent constructor does not allow creating a cloud + event without a source.

+ @param ce:CloudEvent with source to be + extracted. + @return:Returns the String value of a CloudEvent source + attribute. """ - return UCloudEvent.extract_string_value_from_attributes("dataschema", ce) + return UCloudEvent.extract_string_value_from_attributes( + "dataschema", ce + ) @staticmethod def get_type(ce: CloudEvent) -> str: """ - Extract the type from a cloud event. The source is a mandatory attribute. The CloudEvent constructor does - not allow creating a cloud event without a type.

- @param ce:CloudEvent with source to be extracted. - @return:Returns the String value of a CloudEvent type attribute. + Extract the type from a cloud event. The source is a mandatory + attribute. The CloudEvent constructor does not allow creating a cloud + event without a type.

+ @param ce:CloudEvent with source to be + extracted. + @return:Returns the String value of a CloudEvent type + attribute. """ return UCloudEvent.extract_string_value_from_attributes("type", ce) @staticmethod def get_id(ce: CloudEvent) -> str: """ - Extract the id from a cloud event. The id is a mandatory attribute. The CloudEvent constructor does - not allow creating a cloud event without an id.

+ Extract the id from a cloud event. The id is a mandatory attribute. The + CloudEvent constructor does not allow creating a cloud event without an + id.

@param ce:CloudEvent with source to be extracted. @return:Returns the String value of a CloudEvent id attribute. """ @@ -102,48 +122,59 @@ def get_id(ce: CloudEvent) -> str: def get_specversion(ce: CloudEvent) -> str: """ Extract the specversion from a cloud event.

- @param ce:CloudEvent with source to be extracted. - @return:Returns the String value of a CloudEvent spec version attribute. + @param + ce:CloudEvent with source to be extracted. + @return:Returns the String + value of a CloudEvent spec version attribute. """ - return UCloudEvent.extract_string_value_from_attributes("specversion", ce) + return UCloudEvent.extract_string_value_from_attributes( + "specversion", ce + ) @staticmethod def get_sink(ce: CloudEvent) -> str: """ - Extract the sink from a cloud event. The sink attribute is optional.

+ Extract the sink from a cloud event. The sink attribute is + optional.

@param ce:CloudEvent with sink to be extracted. - @return:Returns an Optional String value of a CloudEvent sink attribute if it exists, otherwise an - Optional.empty() is returned. + @return:Returns an Optional String value of a CloudEvent sink attribute + if it exists, otherwise an Optional.empty() is returned. """ return UCloudEvent.extract_string_value_from_attributes("sink", ce) @staticmethod def get_request_id(ce: CloudEvent) -> str: """ - Extract the request id from a cloud event that is a response RPC CloudEvent. The attribute is optional.

- @param ce: the response RPC CloudEvent with request id to be extracted. - @return: Returns an Optional String value of a response RPC CloudEvent request id attribute if it exists, - otherwise an Optional.empty() is returned. + Extract the request id from a cloud event that is a response RPC + CloudEvent. The attribute is optional.

+ @param ce: the response + RPC CloudEvent with request id to be extracted. + @return: Returns an + Optional String value of a response RPC CloudEvent request id attribute + if it exists, otherwise an Optional.empty() is returned. """ return UCloudEvent.extract_string_value_from_attributes("reqid", ce) @staticmethod def get_hash(ce: CloudEvent) -> str: """ - Extract the hash attribute from a cloud event. The hash attribute is optional.

+ Extract the hash attribute from a cloud event. The hash attribute is + optional.

@param ce: CloudEvent with hash to be extracted. - @return:Returns an Optional String value of a CloudEvent hash attribute if it exists, otherwise an - Optional.empty() is returned. + @return:Returns an Optional String value of a CloudEvent hash attribute + if it exists, otherwise an Optional.empty() is returned. """ return UCloudEvent.extract_string_value_from_attributes("hash", ce) @staticmethod def get_priority(ce: CloudEvent) -> str: """ - Extract the string value of the priority attribute from a cloud event. The priority attribute is - optional.

- @param ce:CloudEvent with priority to be extracted. - @return:Returns an Optional String value of a CloudEvent priority attribute if it exists, otherwise an + Extract the string value of the priority attribute from a cloud event. + The priority attribute is optional.

+ @param ce:CloudEvent with + priority to be extracted. + @return:Returns an Optional String value of a + CloudEvent priority attribute if it exists, otherwise an Optional.empty() is returned. """ return UCloudEvent.extract_string_value_from_attributes("priority", ce) @@ -151,10 +182,12 @@ def get_priority(ce: CloudEvent) -> str: @staticmethod def get_ttl(ce: CloudEvent) -> int: """ - Extract the integer value of the ttl attribute from a cloud event. The ttl attribute is optional.

- @param ce:CloudEvent with ttl to be extracted. - @return: Returns an Optional String value of a CloudEvent ttl attribute if it exists,otherwise an - Optional.empty() is returned. + Extract the integer value of the ttl attribute from a cloud event. The + ttl attribute is optional.

+ @param ce:CloudEvent with ttl to be + extracted. + @return: Returns an Optional String value of a CloudEvent + ttl attribute if it exists,otherwise an Optional.empty() is returned. """ ttl_str = UCloudEvent.extract_string_value_from_attributes("ttl", ce) return int(ttl_str) if ttl_str is not None else None @@ -162,46 +195,79 @@ def get_ttl(ce: CloudEvent) -> int: @staticmethod def get_token(ce: CloudEvent) -> str: """ - Extract the string value of the token attribute from a cloud event. The token attribute is optional.

- @param ce: CloudEvent with token to be extracted. - @return:Returns an Optional String value of a CloudEvent priority token if it exists, otherwise an - Optional.empty() is returned. + Extract the string value of the token attribute from a cloud event. The + token attribute is optional.

+ @param ce: CloudEvent with token + to be extracted. + @return:Returns an Optional String value of a + CloudEvent priority token if it exists, otherwise an Optional.empty() + is returned. """ return UCloudEvent.extract_string_value_from_attributes("token", ce) @staticmethod - def get_communication_status(ce: CloudEvent) -> int: - """ - Extract the integer value of the communication status attribute from a cloud event. The communication status - attribute is optional. If there was a platform communication error that occurred while delivering this - cloudEvent, it will be indicated in this attribute. If the attribute does not exist, it is assumed that - everything was UCode.OK_VALUE.

- @param ce: CloudEvent with the platformError to be extracted. - @return: Returns a UCode value that indicates of a platform communication error while delivering this - CloudEvent or UCode.OK_VALUE. + def get_communication_status(ce: CloudEvent) -> UCode: + """ + Extract the integer value of the communication status attribute from a + cloud event. The communication status attribute is optional. If there + was a platform communication error that occurred while delivering this + cloudEvent, it will be indicated in this attribute. If the attribute + does not exist, it is assumed that everything was + UCode.OK_VALUE.

+ @param ce: CloudEvent with the platformError to + be extracted. + @return: Returns a UCode value that indicates of a + platform communication error while delivering this CloudEvent or + UCode.OK_VALUE. """ try: - comm_status = UCloudEvent.extract_string_value_from_attributes("commstatus", ce) + comm_status = UCloudEvent.extract_string_value_from_attributes( + "commstatus", ce + ) return int(comm_status) if comm_status is not None else UCode.OK - except: + except Exception: return UCode.OK + @staticmethod + def get_traceparent(ce: CloudEvent) -> str: + """ + Extract the string value of the traceparent attribute from a cloud + event. The traceparent attribute is optional. + @param ce: CloudEvent + with traceparent to be extracted. + @return: Returns the string value of + the traceparent if it exists, else returns None. + """ + return UCloudEvent.extract_string_value_from_attributes( + "traceparent", ce + ) + @staticmethod def has_communication_status_problem(ce: CloudEvent) -> bool: """ - Indication of a platform communication error that occurred while trying to deliver the CloudEvent.

- @param ce:CloudEvent to be queried for a platform delivery error. - @return:returns true if the provided CloudEvent is marked with having a platform delivery problem. + Indication of a platform communication error that occurred while trying + to deliver the CloudEvent.

+ @param ce:CloudEvent to be queried + for a platform delivery error. + @return:returns true if the provided + CloudEvent is marked with having a platform delivery problem. """ return UCloudEvent.get_communication_status(ce) != UCode.OK @staticmethod - def add_communication_status(ce: CloudEvent, communication_status) -> CloudEvent: - """ - Returns a new CloudEvent from the supplied CloudEvent, with the platform communication added.

- @param ce:CloudEvent that the platform delivery error will be added. - @param communication_status:the platform delivery error UCode to add to the CloudEvent. - @return:Returns a new CloudEvent from the supplied CloudEvent, with the platform communication added. + def add_communication_status( + ce: CloudEvent, communication_status + ) -> CloudEvent: + """ + Returns a new CloudEvent from the supplied CloudEvent, with the + platform communication added.

+ @param ce:CloudEvent that the + platform delivery error will be added. + @param communication_status:the + platform delivery error UCode to add to the CloudEvent. + @return:Returns + a new CloudEvent from the supplied CloudEvent, with the platform + communication added. """ if communication_status is None: return ce @@ -212,10 +278,15 @@ def add_communication_status(ce: CloudEvent, communication_status) -> CloudEvent def get_creation_timestamp(ce: CloudEvent) -> int: """ Extract the timestamp from the UUIDV8 CloudEvent Id.

- @param ce:The CloudEvent with the timestamp to extract. - @return:Return the timestamp from the UUIDV8 CloudEvent Id or an empty Optional if timestamp can't be extracted. - """ - cloud_event_id = UCloudEvent.extract_string_value_from_attributes("id", ce) + @param + ce:The CloudEvent with the timestamp to extract. + @return:Return the + timestamp from the UUIDV8 CloudEvent Id or an empty Optional if + timestamp can't be extracted. + """ + cloud_event_id = UCloudEvent.extract_string_value_from_attributes( + "id", ce + ) uuid = LongUuidSerializer.instance().deserialize(cloud_event_id) return UUIDUtils.getTime(uuid) if uuid is not None else None @@ -223,43 +294,55 @@ def get_creation_timestamp(ce: CloudEvent) -> int: @staticmethod def is_expired_by_cloud_event_creation_date(ce: CloudEvent) -> bool: """ - Calculate if a CloudEvent configured with a creation time and a ttl attribute is expired. The ttl attribute - is a configuration of how long this event should live for after it was generated (in milliseconds)

- @param ce:The CloudEvent to inspect for being expired. - @return:Returns true if the CloudEvent was configured with a ttl > 0 and a creation time to compare for - expiration. + Calculate if a CloudEvent configured with a creation time and a ttl + attribute is expired. The ttl attribute is a configuration of how long + this event should live for after it was generated (in + milliseconds)

+ @param ce:The CloudEvent to inspect for being + expired. + @return:Returns true if the CloudEvent was configured with a + ttl > 0 and a creation time to compare for expiration. """ maybe_ttl = UCloudEvent.get_ttl(ce) if not maybe_ttl or maybe_ttl <= 0: return False - cloud_event_creation_time = UCloudEvent.extract_string_value_from_attributes("time", ce) + cloud_event_creation_time = ( + UCloudEvent.extract_string_value_from_attributes("time", ce) + ) if cloud_event_creation_time is None: return False now = datetime.now() - creation_time_plus_ttl = cloud_event_creation_time + timedelta(milliseconds=maybe_ttl) + creation_time_plus_ttl = cloud_event_creation_time + timedelta( + milliseconds=maybe_ttl + ) return now > creation_time_plus_ttl @staticmethod def is_expired(ce: CloudEvent) -> bool: """ - Calculate if a CloudEvent configured with UUIDv8 id and a ttl attribute is expired. The ttl attribute is a - configuration of how long this event should live for after it was generated (in milliseconds).

+ Calculate if a CloudEvent configured with UUIDv8 id and a ttl attribute + is expired. The ttl attribute is a configuration of how long this event + should live for after it was generated (in milliseconds).

@param ce:The CloudEvent to inspect for being expired. - @return:Returns true if the CloudEvent was configured with a ttl > 0 and UUIDv8 id to compare for expiration. + @return:Returns + true if the CloudEvent was configured with a ttl > 0 and UUIDv8 id + to compare for expiration. """ maybe_ttl = UCloudEvent.get_ttl(ce) if not maybe_ttl or maybe_ttl <= 0: return False - cloud_event_id = UCloudEvent.extract_string_value_from_attributes("id", ce) + cloud_event_id = UCloudEvent.extract_string_value_from_attributes( + "id", ce + ) try: uuid = LongUuidSerializer.instance().deserialize(cloud_event_id) if uuid is None or uuid == UUID(): return False - delta = int(round(time.time() * 1000)) - UUIDUtils.getTime(uuid) + delta = int(round(time.time() * 1000)) - UUIDUtils.get_time(uuid) except ValueError: # Invalid UUID, handle accordingly delta = 0 @@ -272,18 +355,24 @@ def is_cloud_event_id(ce: CloudEvent) -> bool: @param ce:The CloudEvent with the id to inspect. @return: Returns true if the CloudEvent is valid. """ - cloud_event_id = UCloudEvent.extract_string_value_from_attributes("id", ce) + cloud_event_id = UCloudEvent.extract_string_value_from_attributes( + "id", ce + ) uuid = LongUuidSerializer.instance().deserialize(cloud_event_id) - return uuid is not None and UUIDUtils.isuuid(uuid) + return uuid is not None and UUIDUtils.is_uuid(uuid) @staticmethod def get_payload(ce: CloudEvent) -> any_pb2.Any: """ - Extract the payload from the CloudEvent as a protobuf Any object.
An all or nothing error handling - strategy is implemented. If anything goes wrong, an Any.getDefaultInstance() will be returned.

- @param ce:CloudEvent containing the payload to extract. - @return:Extracts the payload from a CloudEvent as a Protobuf Any object. + Extract the payload from the CloudEvent as a protobuf Any object. +
An all or nothing error handling strategy is implemented. If + anything goes wrong, an Any.getDefaultInstance() will be + returned.

+ @param ce:CloudEvent containing the payload to + extract. + @return:Extracts the payload from a CloudEvent as a Protobuf + Any object. """ data = ce.get_data() if data is None: @@ -296,13 +385,19 @@ def get_payload(ce: CloudEvent) -> any_pb2.Any: @staticmethod def unpack(ce: CloudEvent, clazz): """ - Extract the payload from the CloudEvent as a protobuf Message of the provided class. The protobuf of this - message class must be loaded on the client for this to work.
An all or nothing error handling strategy - is implemented. If anything goes wrong, an empty optional will be returned.

Example:
-
Optional<SomeMessage> unpacked = UCloudEvent.unpack(cloudEvent, SomeMessage.class);


- @param ce:CloudEvent containing the payload to extract. - @param clazz:The class that extends Message that the payload is extracted into. - @return: Returns a Message payload of the class type that is provided. + Extract the payload from the CloudEvent as a protobuf Message of the + provided class. The protobuf of this message class must be loaded on + the client for this to work.
An all or nothing error handling + strategy is implemented. If anything goes wrong, an empty optional will + be returned.

Example:
Optional<SomeMessage>
+        unpacked = UCloudEvent.unpack(cloudEvent,
+        SomeMessage.class);


+ @param ce:CloudEvent containing the + payload to extract. + @param clazz:The class that extends Message that + the payload is extracted into. + @return: Returns a Message payload of + the class type that is provided. """ try: any_obj = UCloudEvent.get_payload(ce) @@ -315,17 +410,21 @@ def unpack(ce: CloudEvent, clazz): @staticmethod def to_string(ce: CloudEvent) -> str: """ - Function used to pretty print a CloudEvent containing only the id, source, type and maybe a sink. Used mainly - for logging.

- @param ce:The CloudEvent we want to pretty print. - @return:returns the String representation of the CloudEvent containing only the id, source, type and maybe a - sink. + Function used to pretty print a CloudEvent containing only the id, + source, type and maybe a sink. Used mainly for logging.

+ @param + ce:The CloudEvent we want to pretty print. + @return:returns the String + representation of the CloudEvent containing only the id, source, type + and maybe a sink. """ if ce is not None: sink_str = UCloudEvent.get_sink(ce) sink_str = f", sink='{sink_str}'" if sink_str is not None else "" id = UCloudEvent.extract_string_value_from_attributes("id", ce) - source = UCloudEvent.extract_string_value_from_attributes("source", ce) + source = UCloudEvent.extract_string_value_from_attributes( + "source", ce + ) type = UCloudEvent.extract_string_value_from_attributes("type", ce) return f"CloudEvent{{id='{id}', source='{source}'{sink_str}, type='{type}'}}" else: @@ -335,44 +434,80 @@ def to_string(ce: CloudEvent) -> str: def extract_string_value_from_attributes(attr_name, ce: CloudEvent) -> str: """ Utility for extracting the String value of an attribute.

- @param attr_name:The name of the CloudEvent attribute. - @param ce:The CloudEvent containing the data. - @return:the Optional String value of an attribute matching the attribute name, or an Optional.empty() is the + @param + attr_name:The name of the CloudEvent attribute. + @param ce:The + CloudEvent containing the data. + @return:the Optional String value of an + attribute matching the attribute name, or an Optional.empty() is the value does not exist. """ return ce.get_attributes().get(attr_name) @staticmethod - def extract_integer_value_from_attributes(attr_name, ce: CloudEvent) -> int: + def extract_integer_value_from_attributes( + attr_name, ce: CloudEvent + ) -> int: """ Utility for extracting the Integer value of an attribute.

@param attr_name:The name of the CloudEvent attribute. - @param ce:The CloudEvent containing the data. - @return:returns the Optional Integer value of an attribute matching the attribute name,or an Optional.empty() - is the value does not exist. + @param ce:The + CloudEvent containing the data. + @return:returns the Optional Integer + value of an attribute matching the attribute name,or an + Optional.empty() is the value does not exist. """ value = UCloudEvent.extract_string_value_from_attributes(attr_name, ce) return int(value) if value is not None else None @staticmethod def get_event_type(type): - return {UMessageType.UMESSAGE_TYPE_PUBLISH: "pub.v1", UMessageType.UMESSAGE_TYPE_REQUEST: "req.v1", - UMessageType.UMESSAGE_TYPE_RESPONSE: "res.v1"}.get(type, "") + """ + Get the string representation of the UMessageType. Note: The + UMessageType is determined by the type of the CloudEvent. If the + UMessageType is UMESSAGE_TYPE_NOTIFICATION, we assume the CloudEvent + type is "pub.v1" and the sink is present. + @param type The UMessageType + @return returns the string representation of the UMessageType + """ + return { + UMessageType.UMESSAGE_TYPE_PUBLISH: "pub.v1", + UMessageType.UMESSAGE_TYPE_REQUEST: "req.v1", + UMessageType.UMESSAGE_TYPE_RESPONSE: "res.v1", + UMessageType.UMESSAGE_TYPE_NOTIFICATION: "not.v1", + }.get(type, "") @staticmethod def get_message_type(ce_type): - return {"pub.v1": UMessageType.UMESSAGE_TYPE_PUBLISH, "req.v1": UMessageType.UMESSAGE_TYPE_REQUEST, - "res.v1": UMessageType.UMESSAGE_TYPE_RESPONSE}.get(ce_type, UMessageType.UMESSAGE_TYPE_UNSPECIFIED) + """ + Get the UMessageType from the string representation. Note: The + UMessageType is determined by the type of the CloudEvent. If the + CloudEvent type is "pub.v1" and the sink is present, the UMessageType + is assumed to be UMESSAGE_TYPE_NOTIFICATION, this is because uProtocol + CloudEvent definition did not have an explicit notification type. + @param cloudEvent The CloudEvent containing the data. + @return returns + the UMessageType + """ + return { + "pub.v1": UMessageType.UMESSAGE_TYPE_PUBLISH, + "req.v1": UMessageType.UMESSAGE_TYPE_REQUEST, + "res.v1": UMessageType.UMESSAGE_TYPE_RESPONSE, + }.get(ce_type, UMessageType.UMESSAGE_TYPE_UNSPECIFIED) @staticmethod def get_content_type_from_upayload_format(payload_format: UPayloadFormat): """ - Retrieves the string representation of the data content type based on the provided UPayloadFormat.
- This method uses the uProtocol mimeType custom options declared in upayload.proto. - @param payload_format The UPayloadFormat enumeration representing the payload format. - @return The corresponding content type string based on the payload format. + Retrieves the string representation of the data content type based on + the provided UPayloadFormat.
This method uses the uProtocol + mimeType custom options declared in upayload.proto. + @param + payload_format The UPayloadFormat enumeration representing the payload + format. + @return The corresponding content type string based on the + payload format. """ return { UPayloadFormat.UPAYLOAD_FORMAT_JSON: "application/json", @@ -385,10 +520,14 @@ def get_content_type_from_upayload_format(payload_format: UPayloadFormat): @staticmethod def get_upayload_format_from_content_type(contenttype: str): """ - Retrieves the payload format enumeration based on the provided string representation of the data content type
- This method uses the uProtocol mimeType custom options declared in upayload.proto. - @param contentType The content type string representing the format of the payload. - @return The corresponding UPayloadFormat enumeration based on the content type. + Retrieves the payload format enumeration based on the provided string + representation of the data content type
This method uses the + uProtocol mimeType custom options declared in upayload.proto. + @param + contentType The content type string representing the format of the + payload. + @return The corresponding UPayloadFormat enumeration based on + the content type. """ if contenttype is None: return UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY @@ -400,18 +539,24 @@ def get_upayload_format_from_content_type(contenttype: str): "application/x-someip": UPayloadFormat.UPAYLOAD_FORMAT_SOMEIP, "application/x-someip_tlv": UPayloadFormat.UPAYLOAD_FORMAT_SOMEIP_TLV, } - return content_type_mapping.get(contenttype, UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF) + return content_type_mapping.get( + contenttype, UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF + ) @staticmethod def fromMessage(message: UMessage) -> CloudEvent: """ Get the Cloudevent from the UMessage
- Note: For now, only the value format of UPayload is supported in the SDK.If the UPayload has a reference, it + Note: For now, only the value format of + UPayload is supported in the SDK.If the UPayload has a reference, it needs to be copied to CloudEvent. @param message The UMessage protobuf containing the data @return returns the cloud event """ + if message is None: + raise ValueError("message cannot be null.") + attributes = message.attributes payload = message.payload @@ -421,31 +566,41 @@ def fromMessage(message: UMessage) -> CloudEvent: payload = UPayload() data = bytearray() - json_attributes = {"id": LongUuidSerializer.instance().serialize(attributes.id), - "source": LongUriSerializer().serialize(message.attributes.source), - "type": UCloudEvent.get_event_type(attributes.type)} - contenttype = UCloudEvent.get_content_type_from_upayload_format(payload.format) + json_attributes = { + "id": LongUuidSerializer.instance().serialize(attributes.id), + "source": LongUriSerializer().serialize(message.attributes.source), + "type": UCloudEvent.get_event_type(attributes.type), + } + contenttype = UCloudEvent.get_content_type_from_upayload_format( + payload.format + ) if contenttype: - json_attributes['datacontenttype'] = "application/x-protobuf" + json_attributes["datacontenttype"] = "application/x-protobuf" # IMPORTANT: Currently, ONLY the VALUE format is supported in the SDK! - if payload.HasField('value'): + if payload.HasField("value"): data = payload.value - if attributes.HasField('ttl'): - json_attributes['ttl'] = attributes.ttl + if attributes.HasField("ttl"): + json_attributes["ttl"] = attributes.ttl if attributes.priority > 0: - json_attributes['priority'] = UPriority.Name(attributes.priority) - if attributes.HasField('token'): - json_attributes['token'] = attributes.token - if attributes.HasField('sink'): - json_attributes['sink'] = LongUriSerializer().serialize(attributes.sink) - if attributes.HasField('commstatus'): - json_attributes['commstatus'] = attributes.commstatus - if attributes.HasField('reqid'): - json_attributes['reqid'] = LongUuidSerializer.instance().serialize(attributes.reqid) - if attributes.HasField('permission_level'): - json_attributes['plevel'] = attributes.permission_level + json_attributes["priority"] = UPriority.Name(attributes.priority) + if attributes.HasField("token"): + json_attributes["token"] = attributes.token + if attributes.HasField("sink"): + json_attributes["sink"] = LongUriSerializer().serialize( + attributes.sink + ) + if attributes.HasField("commstatus"): + json_attributes["commstatus"] = attributes.commstatus + if attributes.HasField("reqid"): + json_attributes["reqid"] = LongUuidSerializer.instance().serialize( + attributes.reqid + ) + if attributes.HasField("permission_level"): + json_attributes["plevel"] = attributes.permission_level + if attributes.HasField("traceparent"): + json_attributes["traceparent"] = attributes.traceparent cloud_event = CloudEvent(json_attributes, data) return cloud_event @@ -454,25 +609,34 @@ def fromMessage(message: UMessage) -> CloudEvent: def toMessage(event: CloudEvent) -> UMessage: """ - Get the UMessage from the cloud event - @param event The CloudEvent containing the data. - @return returns the UMessage + Get the UMessage from the cloud event + @param event The CloudEvent containing the data. + @return returns the UMessage """ if event is None: raise ValueError("Cloud Event can't be None") payload = UPayload( - format=UCloudEvent.get_upayload_format_from_content_type(UCloudEvent.get_data_content_type(event)), - value=UCloudEvent.get_payload(event).SerializeToString()) - attributes = UAttributes(id=LongUuidSerializer.instance().deserialize(UCloudEvent.get_id(event)), - type=UCloudEvent.get_message_type(UCloudEvent.get_type(event)), - source=LongUriSerializer().deserialize(UCloudEvent.get_source(event))) + format=UCloudEvent.get_upayload_format_from_content_type( + UCloudEvent.get_data_content_type(event) + ), + value=UCloudEvent.get_payload(event).SerializeToString(), + ) + attributes = UAttributes( + id=LongUuidSerializer.instance().deserialize( + UCloudEvent.get_id(event) + ), + type=UCloudEvent.get_message_type(UCloudEvent.get_type(event)), + source=LongUriSerializer().deserialize( + UCloudEvent.get_source(event) + ), + ) if UCloudEvent.has_communication_status_problem(event): attributes.commstatus = UCloudEvent.get_communication_status(event) priority = UCloudEvent.get_priority(event) - if priority and 'UPRIORITY_' not in priority: - priority = 'UPRIORITY_' + priority + if priority and "UPRIORITY_" not in priority: + priority = "UPRIORITY_" + priority if priority is not None: attributes.priority = priority @@ -493,7 +657,13 @@ def toMessage(event: CloudEvent) -> UMessage: if token is not None: attributes.token = token - plevel = UCloudEvent.extract_integer_value_from_attributes("plevel", event) + traceparent = UCloudEvent.get_traceparent(event) + if traceparent is not None: + attributes.traceparent = traceparent + + plevel = UCloudEvent.extract_integer_value_from_attributes( + "plevel", event + ) if plevel is not None: attributes.permission_level = plevel diff --git a/uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py b/uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py index df50921..71ed869 100644 --- a/uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py +++ b/uprotocol/cloudevent/serialize/cloudeventtoprotobufserializer.py @@ -26,7 +26,9 @@ from cloudevents.http import CloudEvent -from uprotocol.cloudevent.serialize.cloudeventserializer import CloudEventSerializer +from uprotocol.cloudevent.serialize.cloudeventserializer import ( + CloudEventSerializer, +) from uprotocol.cloudevent import cloudevents_pb2 @@ -42,15 +44,14 @@ def __init__(self): def serialize(self, http_event: CloudEvent) -> bytes: proto_event = cloudevents_pb2.CloudEvent() # Set required attributes - proto_event.id = http_event['id'] - proto_event.source = http_event['source'] - proto_event.spec_version = http_event['specversion'] - proto_event.type = http_event['type'] - + proto_event.id = http_event["id"] + proto_event.source = http_event["source"] + proto_event.spec_version = http_event["specversion"] + proto_event.type = http_event["type"] # Set optional & extension attributes for key, value in http_event.get_attributes().items(): - if key not in ['specversion', 'id', 'source', 'type', 'data']: + if key not in ["specversion", "id", "source", "type", "data"]: attribute_value = proto_event.attributes[key] if isinstance(value, bool): attribute_value.ce_boolean = value @@ -74,29 +75,31 @@ def deserialize(self, bytes_data: bytes) -> CloudEvent: proto_event = cloudevents_pb2.CloudEvent() proto_event.ParseFromString(bytes_data) - json_attributes = {"id": proto_event.id, "source": proto_event.source, "type": proto_event.type, - "specversion": proto_event.spec_version} - - + json_attributes = { + "id": proto_event.id, + "source": proto_event.source, + "type": proto_event.type, + "specversion": proto_event.spec_version, + } # Set optional & extension attributes for key in proto_event.attributes: - if key not in ['specversion', 'id', 'source', 'type', 'data']: + if key not in ["specversion", "id", "source", "type", "data"]: attribute_value = proto_event.attributes[key] - if attribute_value.HasField('ce_boolean'): + if attribute_value.HasField("ce_boolean"): json_attributes[key] = attribute_value.ce_boolean - elif attribute_value.HasField('ce_integer'): + elif attribute_value.HasField("ce_integer"): json_attributes[key] = attribute_value.ce_integer - elif attribute_value.HasField('ce_string'): + elif attribute_value.HasField("ce_string"): json_attributes[key] = attribute_value.ce_string - elif attribute_value.HasField('ce_bytes'): + elif attribute_value.HasField("ce_bytes"): json_attributes[key] = attribute_value.ce_bytes # Set data data = bytearray() - if proto_event.HasField('binary_data'): + if proto_event.HasField("binary_data"): data = proto_event.binary_data - elif proto_event.HasField('text_data'): + elif proto_event.HasField("text_data"): data = proto_event.text_data cloud_event = CloudEvent(json_attributes, data) diff --git a/uprotocol/cloudevent/validate/cloudeventvalidator.py b/uprotocol/cloudevent/validate/cloudeventvalidator.py index 06f74b9..269efdb 100644 --- a/uprotocol/cloudevent/validate/cloudeventvalidator.py +++ b/uprotocol/cloudevent/validate/cloudeventvalidator.py @@ -43,13 +43,15 @@ class CloudEventValidator(ABC): @staticmethod def get_validator(ce: CloudEvent): """ - Obtain a CloudEventValidator according to the type attribute in the CloudEvent.

+ Obtain a CloudEventValidator according to the type + attribute in the CloudEvent.

@param ce:The CloudEvent with the type attribute. - @return:Returns a CloudEventValidator according to the type attribute in the CloudEvent. + @return:Returns a CloudEventValidator according to the + type attribute in the CloudEvent. """ cloud_event_type = ce.get_attributes().get("type") - if cloud_event_type is None or not cloud_event_type: + if cloud_event_type is None: return Validators.PUBLISH.validator() message_type = UCloudEvent.get_message_type(cloud_event_type) @@ -58,21 +60,33 @@ def get_validator(ce: CloudEvent): return Validators.RESPONSE.validator() elif message_type == UMessageType.UMESSAGE_TYPE_REQUEST: return Validators.REQUEST.validator() + elif message_type == UMessageType.UMESSAGE_TYPE_NOTIFICATION: + return Validators.NOTIFICATION.validator() else: return Validators.PUBLISH.validator() def validate(self, ce: CloudEvent) -> ValidationResult: """ - Validate the CloudEvent. A CloudEventValidator instance is obtained according to the type attribute on the - CloudEvent.

- @param ce:The CloudEvent to validate. - @return:Returns a ValidationResult with success or a ValidationResult with failure containing all the - errors that were found. + Validate the CloudEvent. A CloudEventValidator instance is obtained + according to the type attribute on the CloudEvent.

@param + ce:The CloudEvent to validate. @return:Returns a ValidationResult with + success or a ValidationResult with failure containing all the errors + that were found. """ - validation_results = [self.validate_version(ce), self.validate_id(ce), self.validate_source(ce), - self.validate_type(ce), self.validate_sink(ce)] - - error_messages = [result.get_message() for result in validation_results if not result.is_success()] + self.validate_source(ce) + validation_results = [ + self.validate_version(ce), + self.validate_id(ce), + self.validate_source(ce), + self.validate_type(ce), + self.validate_sink(ce), + ] + + error_messages = [ + result.get_message() + for result in validation_results + if not result.is_success() + ] error_message = ",".join(error_messages) if not error_message: @@ -82,27 +96,39 @@ def validate(self, ce: CloudEvent) -> ValidationResult: @staticmethod def validate_version(ce: CloudEvent) -> ValidationResult: - return CloudEventValidator.validate_version_spec(ce.get_attributes().get("specversion")) + return CloudEventValidator.validate_version_spec( + ce.get_attributes().get("specversion") + ) @staticmethod def validate_version_spec(version) -> ValidationResult: if version == "1.0": return ValidationResult.success() else: - return ValidationResult.failure(f"Invalid CloudEvent version [{version}]. CloudEvent version must be 1.0.") + return ValidationResult.failure( + f"Invalid CloudEvent version [{version}]. CloudEvent version must be 1.0." + ) @staticmethod def validate_id(ce: CloudEvent) -> ValidationResult: id = UCloudEvent.extract_string_value_from_attributes("id", ce) - return (ValidationResult.success() if UCloudEvent.is_cloud_event_id(ce) else ValidationResult.failure( - f"Invalid CloudEvent Id [{id}]. CloudEvent Id must be of type UUIDv8.")) + return ( + ValidationResult.success() + if UCloudEvent.is_cloud_event_id(ce) + else ValidationResult.failure( + f"Invalid CloudEvent Id [{id}]. CloudEvent Id must be of type UUIDv8." + ) + ) @abstractmethod def validate_source(self, ce: CloudEvent): """ Validate the source value of a cloud event.

- @param ce:The cloud event containing the source to validate. - @return:Returns the ValidationResult containing a success or a failure with the error message. + @param ce:The cloud + event containing the source to validate. + @return:Returns the + ValidationResult containing a success or a failure with the error + message. """ raise NotImplementedError("Subclasses must implement this method") @@ -110,33 +136,44 @@ def validate_source(self, ce: CloudEvent): def validate_type(self, ce: CloudEvent): """ Validate the type value of a cloud event.

- @param ce:The cloud event containing the type to validate. - @return:Returns the ValidationResult containing a success or a failure with the error message. + @param ce:The cloud + event containing the type to validate. + @return:Returns the + ValidationResult containing a success or a failure with the error + message. """ raise NotImplementedError("Subclasses must implement this method") def validate_sink(self, ce: CloudEvent) -> ValidationResult: """ - Validate the sink value of a cloud event in the default scenario where the sink attribute is optional.

- @param ce:The cloud event containing the sink to validate. - @return:Returns the ValidationResult containing a success or a failure with the error message. + Validate the sink value of a cloud event in the default scenario where + the sink attribute is optional.

+ @param ce:The cloud event + containing the sink to validate. + @return:Returns the ValidationResult + containing a success or a failure with the error message. """ maybe_sink = UCloudEvent.get_sink(ce) if maybe_sink: sink = maybe_sink check_sink = self.validate_u_entity_uri(sink) if check_sink.is_failure(): - return ValidationResult.failure(f"Invalid CloudEvent sink [{sink}]. {check_sink.get_message()}") + return ValidationResult.failure( + f"Invalid CloudEvent sink [{sink}]. {check_sink.get_message()}" + ) return ValidationResult.success() @staticmethod def validate_u_entity_uri(uri: str) -> ValidationResult: """ - Validate an UriPart for an Software Entity must have an authority in the case of a microRemote uri, - and must contain the name of the USE.

+ Validate an UriPart for an Software Entity must have an authority in + the case of a microRemote uri, and must contain the name of the + USE.

@param uri:uri string to validate. - @return:Returns the ValidationResult containing a success or a failure with the error message. + @return:Returns the + ValidationResult containing a success or a failure with the error + message. """ uri = LongUriSerializer().deserialize(uri) return CloudEventValidator.validate_u_entity_uri_from_UURI(uri) @@ -145,14 +182,15 @@ def validate_u_entity_uri(uri: str) -> ValidationResult: def validate_u_entity_uri_from_UURI(uri: UUri) -> ValidationResult: return UriValidator.validate(uri) - @staticmethod def validate_topic_uri(uri: str) -> ValidationResult: """ - Validate a UriPart that is to be used as a topic in publish scenarios for events such as publish, - file and notification.

+ Validate a UriPart that is to be used as a topic in publish scenarios + for events such as publish, file and notification.

@param uri:String UriPart to validate - @return:Returns the ValidationResult containing a success or a failure with the error message. + @return:Returns the + ValidationResult containing a success or a failure with the error + message. """ Uri = LongUriSerializer().deserialize(uri) return CloudEventValidator.validate_topic_uri_from_UURI(Uri) @@ -160,31 +198,41 @@ def validate_topic_uri(uri: str) -> ValidationResult: @staticmethod def validate_topic_uri_from_UURI(uri: UUri) -> ValidationResult: """ - Validate a UriPart that is to be used as a topic in publish scenarios for events such as publish, - file and notification.

- @param uri: UriPart to validate. - @return:Returns the ValidationResult containing a success or a failure with the error message. + Validate a UriPart that is to be used as a topic in publish scenarios + for events such as publish, file and notification.

+ @param uri: + UriPart to validate. + @return:Returns the ValidationResult containing a + success or a failure with the error message. """ - validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(uri) + validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI( + uri + ) if validationResult.is_failure(): return validationResult u_resource = uri.resource if not u_resource.name: - return ValidationResult.failure("UriPart is missing uResource name.") + return ValidationResult.failure( + "UriPart is missing uResource name." + ) if not u_resource.message: - return ValidationResult.failure("UriPart is missing Message information.") + return ValidationResult.failure( + "UriPart is missing Message information." + ) return ValidationResult.success() @staticmethod def validate_rpc_topic_uri(uri: str) -> ValidationResult: """ - Validate a UriPart that is meant to be used as the application response topic for rpc calls.
Used in - Request source values and Response sink values.

+ Validate a UriPart that is meant to be used as the application response + topic for rpc calls.
Used in Request source values and Response + sink values.

@param uri:String UriPart to validate. - @return:Returns the ValidationResult containing a success or a failure with the error message. + @return:Returns the ValidationResult containing a success or a failure + with the error message. """ Uri = LongUriSerializer().deserialize(uri) return CloudEventValidator.validate_rpc_topic_uri_from_uuri(Uri) @@ -192,20 +240,32 @@ def validate_rpc_topic_uri(uri: str) -> ValidationResult: @staticmethod def validate_rpc_topic_uri_from_uuri(uri: UUri) -> ValidationResult: """ - Validate a UriPart that is meant to be used as the application response topic for rpc calls.
Used in - Request source values and Response sink values.

+ Validate a UriPart that is meant to be used as the application response + topic for rpc calls.
Used in Request source values and Response + sink values.

@param uri:UriPart to validate. - @return:Returns the ValidationResult containing a success or a failure with the error message. + @return:Returns + the ValidationResult containing a success or a failure with the error + message. """ - validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(uri) + validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI( + uri + ) if validationResult.is_failure(): return ValidationResult.failure( - f"Invalid RPC uri application response topic. {validationResult.get_message()}") + f"Invalid RPC uri application response topic. {validationResult.get_message()}", + ) u_resource = uri.resource - topic = f"{u_resource.name}.{u_resource.instance}" if u_resource.instance else f"{u_resource.name}" + topic = ( + f"{u_resource.name}.{u_resource.instance}" + if u_resource.instance + else f"{u_resource.name}" + ) if topic != "rpc.response": - return ValidationResult.failure("Invalid RPC uri application response topic. UriPart is missing rpc.response.") + return ValidationResult.failure( + "Invalid RPC uri application response topic. UriPart is missing rpc.response.", + ) return ValidationResult.success() @@ -218,13 +278,18 @@ def validate_rpc_method(uri: str) -> ValidationResult: @return:Returns the ValidationResult containing a success or a failure with the error message. """ uuri = LongUriSerializer().deserialize(uri) - validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI(uuri) + validationResult = CloudEventValidator.validate_u_entity_uri_from_UURI( + uuri + ) if validationResult.is_failure(): - return ValidationResult.failure(f"Invalid RPC method uri. {validationResult.get_message()}") + return ValidationResult.failure( + f"Invalid RPC method uri. {validationResult.get_message()}" + ) if not UriValidator.is_rpc_method(uuri): return ValidationResult.failure( - "Invalid RPC method uri. UriPart should be the method to be called, or method from response.") + "Invalid RPC method uri. UriPart should be the method to be called, or method from response.", + ) return ValidationResult.success() @@ -239,14 +304,20 @@ def validate_source(self, cl_event: CloudEvent) -> ValidationResult: check_source = self.validate_topic_uri(source) if check_source.is_failure(): return ValidationResult.failure( - f"Invalid Publish type CloudEvent source [{source}]. {check_source.get_message()}") + f"Invalid Publish type CloudEvent source [{source}]. {check_source.get_message()}", + ) return ValidationResult.success() def validate_type(self, cl_event: CloudEvent) -> ValidationResult: type = cl_event.get_attributes().get("type") - return (ValidationResult.success() if type == "pub.v1" else ValidationResult.failure( - f"Invalid CloudEvent type [{type}]. CloudEvent of type Publish must have a type of 'pub.v1'")) + return ( + ValidationResult.success() + if type == "pub.v1" + else ValidationResult.failure( + f"Invalid CloudEvent type [{type}]. CloudEvent of type Publish must have a type of 'pub.v1'", + ) + ) def __str__(self) -> str: return "CloudEventValidator.Publish" @@ -254,22 +325,45 @@ def __str__(self) -> str: class Notification(Publish): """ - Implements Validations for a CloudEvent of type Publish that behaves as a Notification, meaning it must have a sink. + Implements Validations for a CloudEvent of type Publish that behaves as a + Notification, meaning it must have a sink. """ def validate_sink(self, cl_event: CloudEvent) -> ValidationResult: maybe_sink = UCloudEvent.get_sink(cl_event) if not maybe_sink: - return ValidationResult.failure("Invalid CloudEvent sink. Notification CloudEvent sink must be an uri.") + return ValidationResult.failure( + "Invalid CloudEvent sink. Notification CloudEvent sink must be an uri.", + ) else: sink = maybe_sink check_sink = self.validate_u_entity_uri(sink) if check_sink.is_failure(): return ValidationResult.failure( - f"Invalid Notification type CloudEvent sink [{sink}]. {check_sink.get_message()}") + f"Invalid Notification type CloudEvent sink [{sink}]. {check_sink.get_message()}", + ) return ValidationResult.success() + def validate_source(self, cl_event: CloudEvent) -> ValidationResult: + source = UCloudEvent.get_source(cl_event) + check_source = self.validate_topic_uri(source) + if check_source.is_failure(): + return ValidationResult.failure( + f"Invalid Notification type CloudEvent source [{source}], {check_source.get_message()}", + ) + return ValidationResult.success() + + def validate_type(self, cl_event: CloudEvent) -> ValidationResult: + return ( + ValidationResult.success() + if UCloudEvent.get_type(cl_event) == "not.v1" + else ValidationResult.failure( + f"Invalid CloudEvent type [{UCloudEvent.get_type(cl_event)}]. " + + "CloudEvent of type Notification must have a type of 'not.v1'", + ) + ) + def __str__(self): return "CloudEventValidator.Notification" @@ -284,28 +378,36 @@ def validate_source(self, cl_event: CloudEvent) -> ValidationResult: check_source = self.validate_rpc_topic_uri(source) if check_source.is_failure(): return ValidationResult.failure( - f"Invalid RPC Request CloudEvent source [{source}]. {check_source.get_message()}") + f"Invalid RPC Request CloudEvent source [{source}]. {check_source.get_message()}", + ) return ValidationResult.success() def validate_sink(self, cl_event: CloudEvent) -> ValidationResult: maybe_sink = UCloudEvent.get_sink(cl_event) if not maybe_sink: return ValidationResult.failure( - "Invalid RPC Request CloudEvent sink. Request CloudEvent sink must be uri for the method to be called.") + "Invalid RPC Request CloudEvent sink. Request CloudEvent sink must be uri for the method to be called." + ) else: sink = maybe_sink check_sink = self.validate_rpc_method(sink) if check_sink.is_failure(): return ValidationResult.failure( - f"Invalid RPC Request CloudEvent sink [{sink}]. {check_sink.get_message()}") + f"Invalid RPC Request CloudEvent sink [{sink}]. {check_sink.get_message()}", + ) return ValidationResult.success() def validate_type(self, cl_event: CloudEvent) -> ValidationResult: type = cl_event.get_attributes().get("type") - return (ValidationResult.success() if type == "req.v1" else ValidationResult.failure( - f"Invalid CloudEvent type [{type}]. CloudEvent of type Request must have a type of 'req.v1'")) + return ( + ValidationResult.success() + if type == "req.v1" + else ValidationResult.failure( + f"Invalid CloudEvent type [{type}]. CloudEvent of type Request must have a type of 'req.v1'", + ) + ) def __str__(self): return "CloudEventValidator.Request" @@ -321,7 +423,8 @@ def validate_source(self, cl_event: CloudEvent) -> ValidationResult: check_source = self.validate_rpc_method(source) if check_source.is_failure(): return ValidationResult.failure( - f"Invalid RPC Response CloudEvent source [{source}]. {check_source.get_message()}") + f"Invalid RPC Response CloudEvent source [{source}]. {check_source.get_message()}", + ) return ValidationResult.success() @@ -329,21 +432,28 @@ def validate_sink(self, cl_event) -> ValidationResult: maybe_sink = UCloudEvent.get_sink(cl_event) if not maybe_sink: return ValidationResult.failure( - "Invalid CloudEvent sink. Response CloudEvent sink must be uri the destination of the response.") + "Invalid CloudEvent sink. Response CloudEvent sink must be uri the destination of the response.", + ) else: sink = maybe_sink check_sink = self.validate_rpc_topic_uri(sink) if check_sink.is_failure(): return ValidationResult.failure( - f"Invalid RPC Response CloudEvent sink [{sink}]. {check_sink.get_message()}") + f"Invalid RPC Response CloudEvent sink [{sink}]. {check_sink.get_message()}", + ) return ValidationResult.success() def validate_type(self, cl_event: CloudEvent) -> ValidationResult: type = cl_event.get_attributes().get("type") - return (ValidationResult.success() if type == "res.v1" else ValidationResult.failure( - f"Invalid CloudEvent type [{type}]. CloudEvent of type Response must have a type of 'res.v1'")) + return ( + ValidationResult.success() + if type == "res.v1" + else ValidationResult.failure( + f"Invalid CloudEvent type [{type}]. CloudEvent of type Response must have a type of 'res.v1'", + ) + ) def __str__(self): return "CloudEventValidator.Response" @@ -351,8 +461,10 @@ def __str__(self): class Validators(Enum): """ - Enum that hold the implementations of CloudEventValidator according to type. + Enum that hold the implementations of CloudEventValidator according to + type. """ + PUBLISH = Publish() NOTIFICATION = Notification() REQUEST = Request() diff --git a/uprotocol/rpc/calloptions.py b/uprotocol/rpc/calloptions.py deleted file mode 100644 index 954335c..0000000 --- a/uprotocol/rpc/calloptions.py +++ /dev/null @@ -1,102 +0,0 @@ -# ------------------------------------------------------------------------- -# -# Copyright (c) 2023 General Motors GTO LLC -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# SPDX-FileType: SOURCE -# SPDX-FileCopyrightText: 2023 General Motors GTO LLC -# SPDX-License-Identifier: Apache-2.0 -# -# ------------------------------------------------------------------------- - - -class CallOptions: - """ - This class is used when making uRPC calls to pass additional options. - """ - TIMEOUT_DEFAULT = 10000 - """ - Default timeout of a call in milliseconds. - """ - - - def __init__(self, timeout=TIMEOUT_DEFAULT, token=""): - self.mTimeout = timeout - self.mToken = token if token else "" - - - def get_timeout(self): - """ - Get a timeout.

- @return: A timeout in milliseconds. - """ - return self.mTimeout - - def get_token(self): - """ - Get an OAuth2 access token.

- @return: An Optional OAuth2 access token. - """ - return self.mToken if self.mToken else None - - def __eq__(self, other): - if not isinstance(other, CallOptions): - return False - return self.mTimeout == other.mTimeout and self.mToken == other.mToken - - def __hash__(self): - return hash((self.mTimeout, self.mToken)) - - def __str__(self): - return f"CallOptions{{mTimeout={self.mTimeout}, mToken='{self.mToken}'}}" - - -class CallOptionsBuilder: - """ - Builder for constructing CallOptions. - """ - TIMEOUT_DEFAULT = 10000 - DEFAULT= CallOptions(TIMEOUT_DEFAULT,'') - def __init__(self): - self.mTimeout = self.TIMEOUT_DEFAULT - self.mToken = "" - - def with_timeout(self, timeout): - """ - Add a timeout.

- @param timeout:A timeout in milliseconds. - @return:This builder. - """ - self.mTimeout = timeout if timeout > 0 else self.TIMEOUT_DEFAULT - return self - - def with_token(self, token): - """ - Add an OAuth2 access token.

- @param token:An OAuth2 access token. - @return:This builder. - """ - self.mToken = token - return self - - def build(self): - """ - Construct a CallOptions from this builder.

- @return:A constructed CallOptions. - """ - return CallOptions(self.mTimeout, self.mToken) diff --git a/uprotocol/rpc/rpcclient.py b/uprotocol/rpc/rpcclient.py index 65937d3..5e75d08 100644 --- a/uprotocol/rpc/rpcclient.py +++ b/uprotocol/rpc/rpcclient.py @@ -30,30 +30,34 @@ from uprotocol.proto.uri_pb2 import UUri from uprotocol.proto.upayload_pb2 import UPayload -from uprotocol.rpc.calloptions import CallOptions +from uprotocol.proto.uattributes_pb2 import CallOptions class RpcClient(ABC): """ - RpcClient is an interface used by code generators for uProtocol services defined in proto files such as the core - uProtocol services found in here.
The - interface provides a - clean contract for all transports to implement to be able to support RPC on their platform.
Each platform MUST - implement this interface.
For more details please refer to
- [RpcClient - Specifications] + RpcClient is an interface used by code generators for uProtocol services + defined in proto files such as the core + uProtocol services found in + here + .
The interface provides a clean contract for all transports + to implement to be able to support RPC on their platform.
+ Each platform MUST implement this interface.
""" @abstractmethod - def invoke_method(self, methodUri: UUri, request_payload: UPayload, options: CallOptions) -> Future: + def invoke_method( + self, methodUri: UUri, request_payload: UPayload, options: CallOptions + ) -> Future: """ - API for clients to invoke a method (send an RPC request) and receive the response (the returned - Future UMessage.
- Client will set method to be the URI of the method they want to invoke, - payload to the request message, and attributes with the various metadata for the - method invocation. - @param methodUri The method URI to be invoked, ex (long form): /example.hello_world/1/rpc.SayHello. - @param requestPayload The request message to be sent to the server. + API for clients to invoke a method (send an RPC request) and + receive the response (the returned Future UMessage.
+ Client will set method to be the URI of the method they want to invoke, + payload to the request message, and attributes + with the various metadata for the method invocation. + @param methodUri The method URI to be invoked, + ex (long form): /example.hello_world/1/rpc.SayHello. + @param requestPayload The request + message to be sent to the server. @param options RPC method invocation call options, see CallOptions @return: Returns the CompletableFuture with the result or exception. """ diff --git a/uprotocol/rpc/rpcmapper.py b/uprotocol/rpc/rpcmapper.py index 601c8e0..09e032b 100644 --- a/uprotocol/rpc/rpcmapper.py +++ b/uprotocol/rpc/rpcmapper.py @@ -32,24 +32,30 @@ from uprotocol.proto.ustatus_pb2 import UStatus from uprotocol.rpc.rpcresult import RpcResult -from uprotocol.proto.upayload_pb2 import UPayload class RpcMapper: """ - RPC Wrapper is an interface that provides static methods to be able to wrap an RPC request with an RPC Response ( - uP-L2). APIs that return Message assumes that the message is protobuf serialized com.google.protobuf.Any ( + RPC Wrapper is an interface that provides static methods to be able to + wrap an RPC request with an RPC Response ( + uP-L2). APIs that return Message assumes that the message is + protobuf serialized com.google.protobuf.Any ( UMessageFormat.PROTOBUF) and will barf if anything else is passed """ @staticmethod def map_response(message_future: Future, expected_cls): """ - Map a response of CompletableFuture<UMessage> from Link into a CompletableFuture containing the - declared expected return type of the RPC method or an exception.

- @param response_future:CompletableFuture<UMessage> response from uTransport. - @param expected_cls:The class name of the declared expected return type of the RPC method. - @return:Returns a CompletableFuture containing the declared expected return type of the RPC method or an + Map a response of CompletableFuture<UMessage> from Link + into a CompletableFuture containing the + declared expected return type of the RPC method or an + exception.

+ @param response_future:CompletableFuture<UMessage> + response from uTransport. + @param expected_cls:The class name of the declared expected + return type of the RPC method. + @return:Returns a CompletableFuture containing the declared + expected return type of the RPC method or an exception. """ response_future: Future = Future() @@ -57,22 +63,31 @@ def map_response(message_future: Future, expected_cls): def handle_response(message): nonlocal response_future message = message.result() - if not message or not message.HasField('payload'): + if not message or not message.HasField("payload"): response_future.set_exception( - RuntimeError(f"Server returned a null payload. Expected {expected_cls.__name__}")) - + RuntimeError( + f"Server returned a null payload. Expected {expected_cls.__name__}" + ) + ) + return response_future try: any_message = any_pb2.Any() any_message.ParseFromString(message.payload.value) if any_message.Is(expected_cls.DESCRIPTOR): - response_future.set_result(RpcMapper.unpack_payload(any_message, expected_cls)) + response_future.set_result( + RpcMapper.unpack_payload(any_message, expected_cls) + ) else: response_future.set_exception( RuntimeError( - f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.__name__}]")) + f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.__name__}]" + ) + ) except Exception as e: - response_future.set_exception(RuntimeError(f"{str(e)} [{UStatus.__name__}]")) + response_future.set_exception( + RuntimeError(f"{str(e)} [{UStatus.__name__}]") + ) message_future.add_done_callback(handle_response) @@ -92,12 +107,18 @@ def map_response_to_result(response_future: Future, expected_cls): def handle_response(message): if message.exception(): exception = message.exception() - return RpcResult.failure(value=exception, message=str(exception)) + return RpcResult.failure( + value=exception, message=str(exception) + ) message = message.result() - if not message or not message.HasField('payload'): - exception = RuntimeError(f"Server returned a null payload. Expected {expected_cls.__name__}") - return RpcResult.failure(value=exception, message=str(exception)) + if not message or not message.HasField("payload"): + exception = RuntimeError( + f"Server returned a null payload. Expected {expected_cls.__name__}" + ) + return RpcResult.failure( + value=exception, message=str(exception) + ) try: any_message = any_pb2.Any() @@ -107,16 +128,21 @@ def handle_response(message): if expected_cls == UStatus: return RpcMapper.calculate_status_result(any_message) else: - return RpcResult.success(RpcMapper.unpack_payload(any_message, expected_cls)) + return RpcResult.success( + RpcMapper.unpack_payload(any_message, expected_cls) + ) if any_message.Is(UStatus.DESCRIPTOR): return RpcMapper.calculate_status_result(any_message) except Exception as e: exception = RuntimeError(f"{str(e)} [{UStatus.__name__}]") - return RpcResult.failure(value=exception, message=str(exception)) + return RpcResult.failure( + value=exception, message=str(exception) + ) exception = RuntimeError( - f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.DESCRIPTOR.full_name}]") + f"Unknown payload type [{any_message.type_url}]. Expected [{expected_cls.DESCRIPTOR.full_name}]" + ) return RpcResult.failure(value=exception, message=str(exception)) result = None # Initialize result @@ -131,7 +157,11 @@ def callback_wrapper(payload): @staticmethod def calculate_status_result(payload): status = RpcMapper.unpack_payload(payload, UStatus) - return RpcResult.success(status) if status.code == UCode.OK else RpcResult.failure(status) + return ( + RpcResult.success(status) + if status.code == UCode.OK + else RpcResult.failure(status) + ) @staticmethod def unpack_payload(payload, expected_cls): diff --git a/uprotocol/rpc/rpcresult.py b/uprotocol/rpc/rpcresult.py index 67a6582..598f73f 100644 --- a/uprotocol/rpc/rpcresult.py +++ b/uprotocol/rpc/rpcresult.py @@ -31,7 +31,7 @@ from uprotocol.proto.ustatus_pb2 import UCode from uprotocol.proto.ustatus_pb2 import UStatus -T = TypeVar('T') +T = TypeVar("T") class RpcResult(ABC): @@ -53,15 +53,15 @@ def getOrElse(self, default_value: Callable[[], T]) -> T: pass @abstractmethod - def map(self, f: Callable[[T], T]) -> 'RpcResult': + def map(self, f: Callable[[T], T]) -> "RpcResult": pass @abstractmethod - def flatMap(self, f: Callable[[T], 'RpcResult']) -> 'RpcResult': + def flatMap(self, f: Callable[[T], "RpcResult"]) -> "RpcResult": pass @abstractmethod - def filter(self, f: Callable[[T], bool]) -> 'RpcResult': + def filter(self, f: Callable[[T], bool]) -> "RpcResult": pass @abstractmethod @@ -73,16 +73,23 @@ def successValue(self) -> T: pass @staticmethod - def success(value: T) -> 'RpcResult': + def success(value: T) -> "RpcResult": return Success(value) @staticmethod - def failure(value: Union[UStatus,'Failure', Exception,] = None, code: UCode = UCode.UNKNOWN, - message: str = '') -> 'RpcResult': + def failure( + value: Union[ + UStatus, + "Failure", + Exception, + ] = None, + code: UCode = UCode.UNKNOWN, + message: str = "", + ) -> "RpcResult": return Failure(value, code, message) @staticmethod - def flatten(result: 'RpcResult') -> 'RpcResult': + def flatten(result: "RpcResult") -> "RpcResult": return result.flatMap(lambda x: x) @@ -115,7 +122,13 @@ def flatMap(self, f: Callable[[T], RpcResult]) -> RpcResult: def filter(self, f: Callable[[T], bool]) -> RpcResult: try: - return self if f(self.successValue()) else self.failure(code=UCode.FAILED_PRECONDITION, message="filtered out") + return ( + self + if f(self.successValue()) + else self.failure( + code=UCode.FAILED_PRECONDITION, message="filtered out" + ) + ) except Exception as e: return self.failure(e) @@ -131,7 +144,12 @@ def __str__(self) -> str: class Failure(RpcResult): - def __init__(self, value: Union[UStatus,'Failure', Exception, None] = None, code: UCode = UCode.UNKNOWN, message: str = ''): + def __init__( + self, + value: Union[UStatus, "Failure", Exception, None] = None, + code: UCode = UCode.UNKNOWN, + message: str = "", + ): if isinstance(value, UStatus): self.value = value elif isinstance(value, Exception): diff --git a/uprotocol/rpc/rpcserver.py b/uprotocol/rpc/rpcserver.py deleted file mode 100644 index 58ba748..0000000 --- a/uprotocol/rpc/rpcserver.py +++ /dev/null @@ -1,59 +0,0 @@ -# ------------------------------------------------------------------------- - -# Copyright (c) 2023 General Motors GTO LLC -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# SPDX-FileType: SOURCE -# SPDX-FileCopyrightText: 2023 General Motors GTO LLC -# SPDX-License-Identifier: Apache-2.0 - -# ------------------------------------------------------------------------- - -from abc import ABC, abstractmethod - -from uprotocol.proto.uri_pb2 import UUri -from uprotocol.proto.ustatus_pb2 import UStatus -from uprotocol.rpc.urpclistener import URpcListener - -class RpcServer(ABC): - """ - RpcServer is an interface called by uServices to register method listeners for incoming RPC requests - from clients. - """ - - @abstractmethod - def register_rpc_listener(method: UUri, listener: URpcListener) -> UStatus: - ''' - Register a listener for a particular method URI to be notified when requests are sent against said method. -

Note: Only one listener is allowed to be registered per method URI. - @param method Uri for the method to register the listener for. - @param listener The listener for handling the request method. - @return Returns the status of registering the RpcListener. - ''' - pass - - @abstractmethod - def unregister_rpc_listener(method: UUri, listener: URpcListener) -> UStatus: - ''' - Unregister an RPC listener for a given method Uri. Messages arriving on this topic will no longer be processed - by this listener. - @param method Resolved UUri for where the listener was registered to receive messages from. - @param listener The method to execute to process the date for the topic. - @return Returns status of registering the RpcListener - ''' - pass \ No newline at end of file diff --git a/uprotocol/rpc/urpclistener.py b/uprotocol/rpc/urpclistener.py deleted file mode 100644 index a09ed48..0000000 --- a/uprotocol/rpc/urpclistener.py +++ /dev/null @@ -1,46 +0,0 @@ -# ------------------------------------------------------------------------- - -# Copyright (c) 2023 General Motors GTO LLC -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# SPDX-FileType: SOURCE -# SPDX-FileCopyrightText: 2023 General Motors GTO LLC -# SPDX-License-Identifier: Apache-2.0 - -# ------------------------------------------------------------------------- - -from abc import ABC, abstractmethod -from concurrent.futures import Future - -from uprotocol.proto.umessage_pb2 import UMessage - -class URpcListener(ABC): - ''' - uService (servers) implement this to receive requests messages from clients.
- The service must implement the onReceive(UMessage, CompletableFuture) method to handle - the request and then complete the future passed to the method that triggers the uLink library to - send (over the transport) the response. - ''' - - @abstractmethod - def on_receive(message: UMessage, response_future: Future) -> None: - ''' - Method called to handle/process events. - @param message Message received. - ''' - pass \ No newline at end of file diff --git a/uprotocol/transport/builder/uattributesbuilder.py b/uprotocol/transport/builder/uattributesbuilder.py index 40cc194..b33833d 100644 --- a/uprotocol/transport/builder/uattributesbuilder.py +++ b/uprotocol/transport/builder/uattributesbuilder.py @@ -24,11 +24,17 @@ # ------------------------------------------------------------------------- +from multimethod import multimethod -from uprotocol.proto.uattributes_pb2 import UAttributes, UPriority, UMessageType +from uprotocol.proto.uattributes_pb2 import ( + UAttributes, + UPriority, + UMessageType, +) from uprotocol.proto.uri_pb2 import UUri +from uprotocol.proto.ustatus_pb2 import UCode from uprotocol.proto.uuid_pb2 import UUID -from uprotocol.uuid.factory.uuidfactory import * +from uprotocol.uuid.factory.uuidfactory import Factories class UAttributesBuilder: @@ -40,7 +46,9 @@ class UAttributesBuilder: @param priority uProtocol Prioritization classifications. """ - def __init__(self, source: UUri, id: UUID, type: UMessageType, priority: UPriority): + def __init__( + self, source: UUri, id: UUID, type: UMessageType, priority: UPriority + ): self.source = source self.id = id self.type = UMessageType.Name(type) @@ -65,7 +73,12 @@ def publish(source: UUri, priority: UPriority): raise ValueError("Source cannot be None.") if priority is None: raise ValueError("UPriority cannot be None.") - return UAttributesBuilder(source, Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_PUBLISH, priority) + return UAttributesBuilder( + source, + Factories.UPROTOCOL.create(), + UMessageType.UMESSAGE_TYPE_PUBLISH, + priority, + ) @staticmethod def notification(source: UUri, sink: UUri, priority: UPriority): @@ -82,8 +95,12 @@ def notification(source: UUri, sink: UUri, priority: UPriority): raise ValueError("UPriority cannot be null.") if sink is None: raise ValueError("sink cannot be null.") - return UAttributesBuilder(source, Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_PUBLISH, priority - ).withSink(sink) + return UAttributesBuilder( + source, + Factories.UPROTOCOL.create(), + UMessageType.UMESSAGE_TYPE_NOTIFICATION, + priority, + ).withSink(sink) @staticmethod def request(source: UUri, sink: UUri, priority: UPriority, ttl: int): @@ -104,11 +121,19 @@ def request(source: UUri, sink: UUri, priority: UPriority, ttl: int): if ttl is None: raise ValueError("ttl cannot be null.") - return UAttributesBuilder(source, Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_REQUEST, priority - ).withTtl(ttl).withSink(sink) + return ( + UAttributesBuilder( + source, + Factories.UPROTOCOL.create(), + UMessageType.UMESSAGE_TYPE_REQUEST, + priority, + ) + .withTtl(ttl) + .withSink(sink) + ) - @staticmethod - def response(source: UUri, sink: UUri, priority: UPriority, reqid: UUID): + @multimethod + def response(source: UUri, sink: UUri, priority: int, reqid: UUID): """ Construct a UAttributesBuilder for a response message. @param source Source address of the message. @@ -124,8 +149,24 @@ def response(source: UUri, sink: UUri, priority: UPriority, reqid: UUID): if reqid is None: raise ValueError("reqid cannot be null.") - return UAttributesBuilder(source, Factories.UPROTOCOL.create(), UMessageType.UMESSAGE_TYPE_RESPONSE, priority - ).withSink(sink).withReqId(reqid) + return ( + UAttributesBuilder( + source, + Factories.UPROTOCOL.create(), + UMessageType.UMESSAGE_TYPE_RESPONSE, + priority, + ) + .withSink(sink) + .withReqId(reqid) + ) + + @multimethod + def response(request: UAttributes): + if request is None: + raise ValueError("request cannot be null.") + return UAttributesBuilder.response( + request.sink, request.source, request.priority, request.id + ) def withTtl(self, ttl: int): """ @@ -146,7 +187,7 @@ def withToken(self, token: str): """ self.token = token return self - + def withSink(self, sink: UUri): """ Add the explicit destination URI. @@ -167,7 +208,7 @@ def withPermissionLevel(self, plevel: int): self.plevel = plevel return self - def withCommStatus(self, commstatus: int): + def withCommStatus(self, commstatus: UCode): """ Add the communication status of the message. @@ -186,7 +227,7 @@ def withReqId(self, reqid: UUID): """ self.reqid = reqid return self - + def withTraceparent(self, traceparent: str): """ Add the traceparent. @@ -197,14 +238,18 @@ def withTraceparent(self, traceparent: str): self.traceparent = traceparent return self - def build(self): """ Construct the UAttributes from the builder. @return Returns a constructed """ - attributes = UAttributes(source=self.source, id=self.id, type=self.type, priority=self.priority) + attributes = UAttributes( + source=self.source, + id=self.id, + type=self.type, + priority=self.priority, + ) if self.sink is not None: attributes.sink.CopyFrom(self.sink) if self.ttl is not None: @@ -217,6 +262,6 @@ def build(self): attributes.reqid.CopyFrom(self.reqid) if self.traceparent is not None: attributes.traceparent = self.traceparent - if self.token != None: + if self.token is not None: attributes.token = self.token return attributes diff --git a/uprotocol/transport/builder/upayloadbuilder.py b/uprotocol/transport/builder/upayloadbuilder.py index fd547b1..54c973e 100644 --- a/uprotocol/transport/builder/upayloadbuilder.py +++ b/uprotocol/transport/builder/upayloadbuilder.py @@ -24,41 +24,49 @@ # ------------------------------------------------------------------------- -import typing +from typing import Optional, Type from uprotocol.proto.upayload_pb2 import UPayload, UPayloadFormat from google.protobuf.any_pb2 import Any -from google.protobuf import message +from google.protobuf.message import Message + class UPayloadBuilder: - - def pack_to_any(message: message) -> UPayload: - ''' - Build a uPayload from google.protobuf.Message by stuffing the message into an Any. + + @staticmethod + def pack_to_any(message: Message) -> UPayload: + """ + Build a uPayload from google.protobuf.Message by stuffing the message into an Any. @param message the message to pack - @return the UPayload - ''' + @return the UPayload + """ any_message = Any() any_message.Pack(message) - return UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY, - value=any_message.SerializeToString()) - - def pack(message: message) -> UPayload: - ''' + return UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY, + value=any_message.SerializeToString(), + ) + + @staticmethod + def pack(message: Message) -> UPayload: + """ Build a uPayload from google.protobuf.Message using protobuf PayloadFormat. @param message the message to pack @return the UPayload - ''' - return UPayload(format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, - value=message.SerializeToString()) + """ + return UPayload( + format=UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF, + value=message.SerializeToString(), + ) - def unpack(payload: UPayload, clazz: typing.Type) -> typing.Type: - ''' + @staticmethod + def unpack(payload: UPayload, clazz: Type[Message]) -> Optional[Message]: + """ Unpack a uPayload into a google.protobuf.Message. @param payload the payload to unpack @param clazz the class of the message to unpack @return the unpacked message - ''' + """ if payload is None or payload.value is None: return None try: @@ -66,7 +74,10 @@ def unpack(payload: UPayload, clazz: typing.Type) -> typing.Type: message = clazz() message.ParseFromString(payload.value) return message - elif payload.format == UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY: + elif ( + payload.format + == UPayloadFormat.UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY + ): any_message = Any() any_message.ParseFromString(payload.value) message = clazz() @@ -74,5 +85,5 @@ def unpack(payload: UPayload, clazz: typing.Type) -> typing.Type: return message else: return None - except: - return None \ No newline at end of file + except Exception: + return None diff --git a/uprotocol/transport/utransport.py b/uprotocol/transport/utransport.py index c41ffee..22be94f 100644 --- a/uprotocol/transport/utransport.py +++ b/uprotocol/transport/utransport.py @@ -32,6 +32,7 @@ from uprotocol.proto.ustatus_pb2 import UStatus from uprotocol.proto.umessage_pb2 import UMessage + class UTransport(ABC): """ UTransport is the uP-L1 interface that provides a common API for uE developers to send and receive @@ -54,7 +55,7 @@ def register_listener(self, topic: UUri, listener: UListener) -> UStatus: """ Register UListener for UUri topic to be called when a message is received. @param topic UUri to listen for messages from. - @param listener The UListener that will be execute when the message is + @param listener The UListener that will be execute when the message is received on the given UUri. @return Returns UStatus with UCode.OK if the listener is registered correctly, otherwise it returns with the appropriate failure. diff --git a/uprotocol/transport/validate/uattributesvalidator.py b/uprotocol/transport/validate/uattributesvalidator.py index 60a30f5..82f17fc 100644 --- a/uprotocol/transport/validate/uattributesvalidator.py +++ b/uprotocol/transport/validate/uattributesvalidator.py @@ -1,5 +1,6 @@ # ------------------------------------------------------------------------- import time + # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -28,8 +29,11 @@ from abc import abstractmethod from enum import Enum from uprotocol.proto.uri_pb2 import UUri -from uprotocol.proto.uattributes_pb2 import UAttributes, UMessageType -from uprotocol.proto.ustatus_pb2 import UCode +from uprotocol.proto.uattributes_pb2 import ( + UAttributes, + UMessageType, + UPriority, +) from uprotocol.uri.validator.urivalidator import UriValidator from uprotocol.uuid.factory.uuidutils import UUIDUtils from uprotocol.validation.validationresult import ValidationResult @@ -37,11 +41,15 @@ class UAttributesValidator: """ - UAttributes is the class that defines the Payload. It is the place for configuring time to live, priority, + UAttributes is the class that defines the Payload. It is the place + for configuring time to live, priority, security tokens and more.

- Each UAttributes class defines a different type of message payload. The payload can represent a simple published - payload with some state change,Payload representing an RPC request or Payload representing an RPC response.

- UAttributesValidator is a base class for all UAttribute validators, that can help validate that the + Each UAttributes class defines a different type of message payload. + The payload can represent a simple published + payload with some state change,Payload representing an RPC + request or Payload representing an RPC response.

+ UAttributesValidator is a base class for all UAttribute + validators, that can help validate that the UAttributes object is correctly defined to define the Payload correctly. """ @@ -49,10 +57,12 @@ class UAttributesValidator: @staticmethod def get_validator(attribute: UAttributes): """ - Static factory method for getting a validator according to the UMessageType defined in the + Static factory method for getting a validator according to the + UMessageType defined in the UAttributes.
@param attribute: UAttributes containing the UMessageType. - @return: returns a UAttributesValidator according to the UMessageType defined in the + @return: returns a UAttributesValidator according to the + UMessageType defined in the UAttributes. """ if attribute.type is None: @@ -61,6 +71,8 @@ def get_validator(attribute: UAttributes): return Validators.RESPONSE.validator() elif attribute.type == UMessageType.UMESSAGE_TYPE_REQUEST: return Validators.REQUEST.validator() + elif attribute.type == UMessageType.UMESSAGE_TYPE_NOTIFICATION: + return Validators.NOTIFICATION.validator() else: return Validators.PUBLISH.validator() @@ -68,16 +80,24 @@ def validate(self, attributes: UAttributes) -> ValidationResult: """ Take a UAttributes object and run validations.

@param attributes:The UAttriubes to validate. - @return:Returns a ValidationResult that is success or failed with a message containing all validation errors + @return:Returns a ValidationResult that is success or failed + with a message containing all validation errors for invalid configurations. """ - error_messages = [self.validate_type(attributes), - self.validate_ttl(attributes), - self.validate_sink(attributes), self.validate_comm_status(attributes), - self.validate_permission_level(attributes), self.validate_req_id(attributes)] - - error_messages = [status.get_message() for status in error_messages if - status.is_failure()] + error_messages = [ + self.validate_type(attributes), + self.validate_ttl(attributes), + self.validate_sink(attributes), + self.validate_priority(attributes), + self.validate_permission_level(attributes), + self.validate_req_id(attributes), + ] + + error_messages = [ + status.get_message() + for status in error_messages + if status.is_failure() + ] if error_messages: return ValidationResult.failure(",".join(error_messages)) @@ -86,31 +106,36 @@ def validate(self, attributes: UAttributes) -> ValidationResult: @staticmethod def is_expired(u_attributes: UAttributes) -> bool: - ''' + """ Check the time-to-live attribute to see if it has expired.
- The message has expired when the current time is greater than the original UUID time + The message has expired when the current time is greater + than the original UUID time plus the ttl attribute. @param uAttributes UAttributes with time to live value. - @return Returns a true if the original time plus the ttl is less than the current time - ''' + @return Returns a true if the original time plus the ttl + is less than the current time + """ ttl = u_attributes.ttl - maybe_time = UUIDUtils.getTime(u_attributes.id) + maybe_time = UUIDUtils.get_time(u_attributes.id) - if not u_attributes.HasField('ttl') or maybe_time is None or ttl <=0: + if maybe_time is None or ttl <= 0: return False - + return (maybe_time + ttl) < int(time.time() * 1000) @staticmethod def validate_ttl(attr: UAttributes) -> ValidationResult: """ - Validate the time to live configuration. If the UAttributes does not contain a time to live then the + Validate the time to live configuration. If the UAttributes + does not contain a time to live then the ValidationResult is ok.

- @param attr:UAttributes object containing the message time to live configuration to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. + @param attr:UAttributes object containing the message time + to live configuration to validate. + @return:Returns a ValidationResult that is success or + failed with a failure message. """ - if attr.HasField('ttl') and attr.ttl <= 0: + if attr.HasField("ttl") and attr.ttl <= 0: return ValidationResult.failure(f"Invalid TTL [{attr.ttl}]") else: return ValidationResult.success() @@ -118,53 +143,66 @@ def validate_ttl(attr: UAttributes) -> ValidationResult: @staticmethod def validate_sink(attr: UAttributes) -> ValidationResult: """ - Validate the sink UriPart for the default case. If the UAttributes does not contain a sink then the + Validate the sink UriPart for the default case. If the + UAttributes does not contain a sink then the ValidationResult is ok.

@param attr:UAttributes object containing the sink to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. + @return:Returns a ValidationResult that is success or + failed with a failure message. + """ + return ( + UriValidator.validate(attr.sink) + if attr.HasField("sink") + else ValidationResult.success() + ) + + @staticmethod + def validate_priority(attr: UAttributes): + """ + Validate the priority value to ensure it is one of the known CS values + + @param attributes Attributes object containing the + Priority to validate. + @return Returns a ValidationResult that is success or + failed with a failure message. """ - return UriValidator.validate(attr.sink) if attr.HasField('sink') else ValidationResult.success() + return ( + ValidationResult.success() + if attr.priority >= UPriority.UPRIORITY_CS0 + else ValidationResult.failure( + f"Invalid UPriority [{UPriority.Name(attr.priority)}]" + ) + ) @staticmethod def validate_permission_level(attr: UAttributes) -> ValidationResult: """ - Validate the permissionLevel for the default case. If the UAttributes does not contain a permission level + Validate the permissionLevel for the default case. If the + UAttributes does not contain a permission level then the ValidationResult is ok.

- @param attr:UAttributes object containing the permission level to validate. - @return:Returns a ValidationResult indicating if the permissionLevel is valid or not. + @param attr:UAttributes object containing the permission + level to validate. + @return:Returns a ValidationResult indicating if the + permissionLevel is valid or not. """ - if attr.HasField('permission_level') and attr.permission_level <= 0: + if attr.HasField("permission_level") and attr.permission_level <= 0: return ValidationResult.failure("Invalid Permission Level") else: return ValidationResult.success() - @staticmethod - def validate_comm_status(attr: UAttributes) -> ValidationResult: - """ - Validate the commStatus for the default case. If the UAttributes does not contain a comm status then the - ValidationResult is ok.

- @param attr:UAttributes object containing the comm status to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. - """ - if attr.HasField('commstatus'): - try: - UCode.Name(attr.commstatus) - except ValueError: - return ValidationResult.failure("Invalid Communication Status Code") - - return ValidationResult.success() - @staticmethod def validate_req_id(attr: UAttributes) -> ValidationResult: """ - Validate the correlationId for the default case. If the UAttributes does not contain a request id then the - ValidationResult is ok.

+ Validate the correlationId for the default case. If the + UAttributes does not contain a request id then the + ValidationResult is ok.

@param attr:Attributes object containing the request id to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. + @return:Returns a ValidationResult that is + success or failed with a failure message. """ - if attr.HasField('reqid') and not UUIDUtils.isuuid(attr.reqid): + if attr.HasField("reqid") and not UUIDUtils.is_uuid(attr.reqid): return ValidationResult.failure("Invalid UUID") else: return ValidationResult.success() @@ -173,26 +211,38 @@ def validate_req_id(attr: UAttributes) -> ValidationResult: def validate_type(self, attr: UAttributes): """ Validate the UMessageType attribute, it is required.

- @param attr:UAttributes object containing the message type to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. + @param attr:UAttributes object containing the + message type to validate. + @return:Returns a ValidationResult that is success + or failed with a failure message. """ raise NotImplementedError("Subclasses must implement this method.") class Publish(UAttributesValidator): """ - Implements validations for UAttributes that define a message that is meant for publishing state changes. + Implements validations for UAttributes that define a message + that is meant for publishing state changes. """ def validate_type(self, attributes_value: UAttributes) -> ValidationResult: """ - Validates that attributes for a message meant to publish state changes has the correct type.

- @param attributes_value:UAttributes object containing the message type to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. + Validates that attributes for a message meant to publish + state changes has the correct type.

+ @param attributes_value:UAttributes object containing + the message type to validate. + @return:Returns a ValidationResult that is success or + failed with a failure message. """ - return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_PUBLISH else ( - ValidationResult.failure( - f"Wrong Attribute Type [{UMessageType.Name(attributes_value.type)}]")) + return ( + ValidationResult.success() + if attributes_value.type == UMessageType.UMESSAGE_TYPE_PUBLISH + else ( + ValidationResult.failure( + f"Wrong Attribute Type [{UMessageType.Name(attributes_value.type)}]" + ) + ) + ) def __str__(self): return "UAttributesValidator.Publish" @@ -200,39 +250,60 @@ def __str__(self): class Request(UAttributesValidator): """ - Implements validations for UAttributes that define a message that is meant for an RPC request. + Implements validations for UAttributes that define a message + that is meant for an RPC request. """ def validate_type(self, attributes_value: UAttributes) -> ValidationResult: """ - Validates that attributes for a message meant for an RPC request has the correct type.

- @param attributes_value:UAttributes object containing the message type to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. + Validates that attributes for a message meant for an + RPC request has the correct type.

+ @param attributes_value:UAttributes object containing + the message type to validate. + @return:Returns a ValidationResult that is success + or failed with a failure message. """ - return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_REQUEST else ( - ValidationResult.failure( - f"Wrong Attribute Type [{UMessageType.Name(attributes_value.type)}]")) + return ( + ValidationResult.success() + if attributes_value.type == UMessageType.UMESSAGE_TYPE_REQUEST + else ( + ValidationResult.failure( + f"Wrong Attribute Type [{UMessageType.Name(attributes_value.type)}]" + ) + ) + ) def validate_sink(self, attributes_value: UAttributes) -> ValidationResult: """ - Validates that attributes for a message meant for an RPC request has a destination sink.

In the case + Validates that attributes for a message meant for an RPC + request has a destination sink.

In the case of an RPC request, the sink is required. - @param attributes_value:UAttributes object containing the sink to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. + @param attributes_value:UAttributes object containing + the sink to validate. + @return:Returns a ValidationResult that is success or + failed with a failure message. """ - return UriValidator.validate_rpc_method( - attributes_value.sink) if attributes_value.HasField('sink') else ValidationResult.failure("Missing Sink") + return ( + UriValidator.validate_rpc_method(attributes_value.sink) + if attributes_value.HasField("sink") + else ValidationResult.failure("Missing Sink") + ) def validate_ttl(self, attributes_value: UAttributes) -> ValidationResult: """ - Validate the time to live configuration.
In the case of an RPC request, the time to live is required.

- @param attributes_value:UAttributes object containing the time to live to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. + Validate the time to live configuration.
In the case of an RPC + request, the time to live is required.

+ @param attributes_value:UAttributes object containing the + time to live to validate. + @return:Returns a ValidationResult that is success or + failed with a failure message. """ - if not attributes_value.HasField('ttl'): + if not attributes_value.HasField("ttl"): return ValidationResult.failure("Missing TTL") if attributes_value.ttl <= 0: - return ValidationResult.failure(f"Invalid TTL [{attributes_value.ttl}]") + return ValidationResult.failure( + f"Invalid TTL [{attributes_value.ttl}]" + ) return ValidationResult.success() @@ -242,53 +313,128 @@ def __str__(self): class Response(UAttributesValidator): """ - Implements validations for UAttributes that define a message that is meant for an RPC response. + Implements validations for UAttributes that define a message that is + meant for an RPC response. """ def validate_type(self, attributes_value: UAttributes) -> ValidationResult: """ - Validates that attributes for a message meant for an RPC response has the correct type.

- @param attributes_value:UAttributes object containing the message type to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. + Validates that attributes for a message meant for an RPC + response has the correct type.

+ @param attributes_value:UAttributes object containing the + message type to validate. + @return:Returns a ValidationResult that is success or + failed with a failure message. """ - return ValidationResult.success() if attributes_value.type == UMessageType.UMESSAGE_TYPE_RESPONSE else ( - ValidationResult.failure( - f"Wrong Attribute Type [{UMessageType.Name(attributes_value.type)}]")) + return ( + ValidationResult.success() + if attributes_value.type == UMessageType.UMESSAGE_TYPE_RESPONSE + else ( + ValidationResult.failure( + f"Wrong Attribute Type [{UMessageType.Name(attributes_value.type)}]" + ) + ) + ) def validate_sink(self, attributes_value: UAttributes) -> ValidationResult: """ - Validates that attributes for a message meant for an RPC response has a destination sink.
In the case of + Validates that attributes for a message meant for an RPC response + has a destination sink.
In the case of an RPC response, the sink is required.

- @param attributes_value:UAttributes object containing the sink to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. + @param attributes_value:UAttributes object containing the sink + to validate. + @return:Returns a ValidationResult that is success or failed + with a failure message. """ - if not attributes_value.HasField('sink') or attributes_value.sink == UUri(): + if ( + not attributes_value.HasField("sink") + or attributes_value.sink == UUri() + ): return ValidationResult.failure("Missing Sink") result = UriValidator.validate_rpc_response(attributes_value.sink) return result - - def validate_req_id(self, attributes_value: UAttributes) -> ValidationResult: + def validate_req_id( + self, attributes_value: UAttributes + ) -> ValidationResult: """ - Validate the correlationId. n the case of an RPC response, the correlation id is required.

- @param attributes_value:UAttributes object containing the correlation id to validate. - @return:Returns a ValidationResult that is success or failed with a failure message. + Validate the correlationId. n the case of an RPC response, the + correlation id is required.

+ @param attributes_value:UAttributes object containing the + correlation id to validate. + @return:Returns a ValidationResult that is success or + failed with a failure message. """ - return ValidationResult.success() if attributes_value.reqid and UUIDUtils.isuuid( - attributes_value.reqid) else ValidationResult.failure("Missing correlationId") + return ( + ValidationResult.success() + if attributes_value.reqid + and UUIDUtils.is_uuid(attributes_value.reqid) + else ValidationResult.failure("Missing correlationId") + ) def __str__(self): return "UAttributesValidator.Response" +class Notification(UAttributesValidator): + """ + Implements validations for UAttributes that define a message that is + meant for notifications. + """ + + def validate_type(self, attributes_value: UAttributes) -> ValidationResult: + """ + Validates that attributes for a message meant for Notification + state changes has the correct type.

+ @param attributes_value:UAttributes object containing the message + type to validate. + @return:Returns a ValidationResult that is success or failed with + a failure message. + """ + return ( + ValidationResult.success() + if attributes_value.type == UMessageType.UMESSAGE_TYPE_NOTIFICATION + else ( + ValidationResult.failure( + f"Wrong Attribute Type [{UMessageType.Name(attributes_value.type)}]" + ) + ) + ) + + def validate_sink(self, attributes_value: UAttributes) -> ValidationResult: + """ + Validates that attributes for a message meant for an RPC response + has a destination sink.
In the case of + an RPC response, the sink is required.

+ @param attributes_value:UAttributes object containing the + sink to validate. + @return:Returns a ValidationResult that is success or + failed with a failure message. + """ + if attributes_value is None: + return ValidationResult.failure("UAttributes cannot be null.") + if ( + not attributes_value.HasField("sink") + or attributes_value.sink == UUri() + ): + return ValidationResult.failure("Missing Sink") + return ValidationResult.success() + + def __str__(self): + return "UAttributesValidator.Notification" + + class Validators(Enum): """ - Validators Factory.
Example: UAttributesValidator validateForPublishMessageType = + Validators Factory.
Example: UAttributesValidator + validateForPublishMessageType = UAttributesValidator.Validators.PUBLISH.validator() """ + PUBLISH = Publish() REQUEST = Request() RESPONSE = Response() + NOTIFICATION = Notification() def validator(self): return self.value diff --git a/uprotocol/uri/README.adoc b/uprotocol/uri/README.adoc index 579a6f9..191c9cf 100644 --- a/uprotocol/uri/README.adoc +++ b/uprotocol/uri/README.adoc @@ -31,15 +31,3 @@ uri: UUri = UUri( status : ValidationResult = UriValidator.validate_rpc_method(uuri) assertTrue(status.is_success()); ---- - -=== Serializing & Deserializing -[,python] ----- - uri = .../* UUri example above */ - micro = MicroUriSerializer().serialize(uri) - long = LongUriSerializer().serialize(uri) - deserialized_micro_uuri = MicroUriSerializer().deserialize(uri) - deserialized_long_uuri = LongUriSerializer().deserialize(uri) - uri2 = UriSerializer.build_resolved(long, micro) - ----- \ No newline at end of file diff --git a/uprotocol/uri/factory/uentity_factory.py b/uprotocol/uri/factory/uentity_factory.py index 804aceb..d69f900 100644 --- a/uprotocol/uri/factory/uentity_factory.py +++ b/uprotocol/uri/factory/uentity_factory.py @@ -23,10 +23,19 @@ # SPDX-License-Identifier: Apache-2.0 # ------------------------------------------------------------------------- +from google.protobuf.descriptor import ServiceDescriptor +from google.protobuf.descriptor_pb2 import ServiceOptions from uprotocol.proto.uri_pb2 import UEntity -from uprotocol.proto.uprotocol_options_pb2 import UProtocolOptions -from google.protobuf.descriptor_pb2 import ServiceDescriptorProto +from uprotocol.proto.uprotocol_options_pb2 import name as Name +from uprotocol.proto.uprotocol_options_pb2 import ( + version_major as Version_Major, +) +from uprotocol.proto.uprotocol_options_pb2 import ( + version_minor as Version_Minor, +) +from uprotocol.proto.uprotocol_options_pb2 import id as Id + class UEntityFactory: """ @@ -34,29 +43,25 @@ class UEntityFactory: """ @staticmethod - def from_proto(descriptor: ServiceDescriptorProto) -> UEntity: - ''' - Builds a UEntity for an protobuf generated code Service Descriptor. - @param descriptor The protobuf generated code Service Descriptor. - @return Returns a UEntity for an protobuf generated code Service Descriptor. - ''' - - if descriptor is None: + def from_proto(service_descriptor: ServiceDescriptor): + if service_descriptor is None: return UEntity() - - options = descriptor.options - uentity = UEntity() + options: ServiceOptions = service_descriptor.GetOptions() - name = options.getExtension(UProtocolOptions.name) - id = options.getExtension(UProtocolOptions.id) - version = options.getExtension(UProtocolOptions.version_major) - + name: str = options.Extensions[Name] + version_major: int = options.Extensions[Version_Major] + version_minor: int = options.Extensions[Version_Minor] + id: int = options.Extensions[Id] + + uentity = UEntity() if name is not None: uentity.name = name + if version_major is not None: + uentity.version_major = version_major + if version_minor is not None: + uentity.version_minor = version_minor if id is not None: uentity.id = id - if version is not None: - uentity.version_major = version - return uentity \ No newline at end of file + return uentity diff --git a/uprotocol/uri/factory/uresource_builder.py b/uprotocol/uri/factory/uresource_builder.py index cdf22fb..d873d5a 100644 --- a/uprotocol/uri/factory/uresource_builder.py +++ b/uprotocol/uri/factory/uresource_builder.py @@ -25,18 +25,28 @@ # ------------------------------------------------------------------------- +import re from uprotocol.proto.uri_pb2 import UResource +from multimethod import multimethod +from typing import Union class UResourceBuilder: MAX_RPC_ID = 1000 + # The minimum topic ID, below this value are methods. + MIN_TOPIC_ID = 0x8000 + @staticmethod def for_rpc_response(): return UResource(name="rpc", instance="response", id=0) - @staticmethod - def for_rpc_request(method, id=None): + @multimethod + def for_rpc_request(method: Union[str, None]): + return UResourceBuilder.for_rpc_request(method, None) + + @multimethod + def for_rpc_request(method: Union[str, None], id: Union[int, None] = None): uresource = UResource(name="rpc") if method is not None: uresource.instance = method @@ -45,8 +55,10 @@ def for_rpc_request(method, id=None): return uresource - @staticmethod - def for_rpc_request_with_id(id): + @multimethod + def for_rpc_request(id: int): + if id is None: + raise ValueError("id cannot be None") return UResourceBuilder.for_rpc_request(None, id) @staticmethod @@ -54,14 +66,38 @@ def from_id(id): if id is None: raise ValueError("id cannot be None") - return UResourceBuilder.for_rpc_request_with_id(id) if id < UResourceBuilder.MAX_RPC_ID else UResource(id=id) + return ( + UResourceBuilder.for_rpc_response() + if id == 0 + else ( + UResourceBuilder.for_rpc_request(id) + if id < UResourceBuilder.MIN_TOPIC_ID + else UResource(id=id) + ) + ) @staticmethod - def from_proto(instance): - ''' - Build a UResource from a protobuf message. This method will determine if - the message is a RPC or topic message based on the message type - @param message The protobuf message. + def from_uservice_topic(topic): + """ + Build a UResource from a UServiceTopic that is defined in protos and + available from generated stubs. + @param topic The UServiceTopic to build the UResource from. @return Returns a UResource for an RPC request. - ''' - pass \ No newline at end of file + """ + if topic is None: + raise ValueError("topic cannot be None.") + name_and_instance_parts = re.split(r"[\\.]", topic.name) + resource_name = name_and_instance_parts[0] + resource_instance = ( + None + if len(name_and_instance_parts) <= 1 + else name_and_instance_parts[1] + ) + + resource = UResource( + name=resource_name, id=topic.id, message=topic.message + ) + if resource_instance is not None: + resource.instance = resource_instance + + return resource diff --git a/uprotocol/uri/serializer/ipaddress.py b/uprotocol/uri/serializer/ipaddress.py new file mode 100644 index 0000000..38891f6 --- /dev/null +++ b/uprotocol/uri/serializer/ipaddress.py @@ -0,0 +1,79 @@ +# ------------------------------------------------------------------------- + +# Copyright (c) 2024 General Motors GTO LLC +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# SPDX-FileType: SOURCE +# SPDX-FileCopyrightText: 2024 General Motors GTO LLC +# SPDX-License-Identifier: Apache-2.0 + +# ------------------------------------------------------------------------- + +import socket + + +class IpAddress: + + @staticmethod + def to_bytes(ip_address): + if ip_address is None or ip_address.strip() == "": + return b"" + + if IpAddress.is_valid_ipv4_address(ip_address): + return IpAddress.convert_ipv4_to_byte_array(ip_address) + elif IpAddress.is_valid_ipv6_address(ip_address): + return IpAddress.convert_ipv6_to_byte_array(ip_address) + else: + return b"" + + @staticmethod + def is_valid(ip_address): + return ( + (ip_address is not None) + and not ip_address.strip() == "" + and ( + IpAddress.is_valid_ipv4_address(ip_address) + or IpAddress.is_valid_ipv6_address(ip_address) + ) + ) + + @staticmethod + def is_valid_ipv4_address(ip_address): + try: + return len(ip_address.split(".")) == 4 and socket.inet_pton( + socket.AF_INET, ip_address + ) + except OSError: + return False + + @staticmethod + def convert_ipv4_to_byte_array(ip_address): + return bytes(socket.inet_pton(socket.AF_INET, ip_address)) + + @staticmethod + def is_valid_ipv6_address(ip_address): + try: + return len(ip_address.split(":")) <= 8 and socket.inet_pton( + socket.AF_INET6, ip_address + ) + except OSError: + return False + + @staticmethod + def convert_ipv6_to_byte_array(ip_address): + return bytes(socket.inet_pton(socket.AF_INET6, ip_address)) diff --git a/uprotocol/uri/serializer/longuriserializer.py b/uprotocol/uri/serializer/longuriserializer.py index 784f05c..3bb981f 100644 --- a/uprotocol/uri/serializer/longuriserializer.py +++ b/uprotocol/uri/serializer/longuriserializer.py @@ -46,7 +46,8 @@ def serialize(self, uri: UUri) -> str: """ Support for serializing UUri objects into their String format.

@param uri: UUri object to be serialized to the String format. - @return:Returns the String format of the supplied UUri that can be used as a sink or a source in a + @return:Returns the String format of the supplied UUri that can be + used as a sink or a source in a uProtocol publish communication. """ if uri is None or UriValidator.is_empty(uri): @@ -54,20 +55,21 @@ def serialize(self, uri: UUri) -> str: sb = [] - if uri.HasField('authority'): - sb.append(self.build_authority_part_of_uri(uri.authority)) + if uri.HasField("authority"): + sb.append("//") + sb.append(uri.authority.name) sb.append("/") sb.append(self.build_software_entity_part_of_uri(uri.entity)) sb.append(self.build_resource_part_of_uri(uri)) - return re.sub('/+$', '', "".join(sb)) + return re.sub("/+$", "", "".join(sb)) @staticmethod def build_resource_part_of_uri(uuri: UUri) -> str: - if not uuri.HasField('resource'): + if not uuri.HasField("resource"): return "" u_resource = uuri.resource @@ -83,9 +85,12 @@ def build_resource_part_of_uri(uuri: UUri) -> str: @staticmethod def build_software_entity_part_of_uri(entity: UEntity) -> str: """ - Create the service part of the uProtocol URI from an UEntity object.

- @param entity:Software Entity representing a service or an application. - @return: Returns the String representation of the UEntity in the uProtocol URI. + Create the service part of the uProtocol URI from an + UEntity object.

+ @param entity:Software Entity representing a service + or an application. + @return: Returns the String representation of the UEntity + in the uProtocol URI. """ sb = str(entity.name.strip()) sb += "/" @@ -95,22 +100,6 @@ def build_software_entity_part_of_uri(entity: UEntity) -> str: return sb - @staticmethod - def build_authority_part_of_uri(authority: UAuthority) -> str: - """ - Create the authority part of the uProtocol URI from an UAuthority object.

- @param authority:represents the deployment location of a specific Software Entity in the Ultiverse. - @return:Returns the String representation of the Authority in the uProtocol URI. - """ - - partial_uri = "//" - maybe_name = authority.name - - if maybe_name is not None or maybe_name != "": - partial_uri += maybe_name - - return partial_uri - def deserialize(self, u_protocol_uri: str) -> UUri: """ Deserialize a String into a UUri object.

@@ -119,8 +108,11 @@ def deserialize(self, u_protocol_uri: str) -> UUri: """ if u_protocol_uri is None or u_protocol_uri.strip() == "": return UUri() - uri = u_protocol_uri[u_protocol_uri.index(":") + 1:] \ - if ":" in u_protocol_uri else u_protocol_uri.replace('\\', '/') + uri = ( + u_protocol_uri[u_protocol_uri.index(":") + 1:] + if ":" in u_protocol_uri + else u_protocol_uri.replace("\\", "/") + ) is_local = not uri.startswith("//") uri_parts = LongUriSerializer.remove_empty(uri.split("/")) @@ -177,7 +169,8 @@ def deserialize(self, u_protocol_uri: str) -> UUri: @staticmethod def parse_from_string(resource_string: str) -> UResource: """ - Static builder method for creating a UResource using a string that contains name + instance + message.

+ Static builder method for creating a UResource using a string + that contains name + instance + message.

@param resource_string:String that contains the UResource information. @return:Returns a UResource object. """ @@ -187,9 +180,15 @@ def parse_from_string(resource_string: str) -> UResource: parts = LongUriSerializer.remove_empty(resource_string.split("#")) name_and_instance = parts[0] - name_and_instance_parts = LongUriSerializer.remove_empty(name_and_instance.split(".")) + name_and_instance_parts = LongUriSerializer.remove_empty( + name_and_instance.split(".") + ) resource_name = name_and_instance_parts[0] - resource_instance = name_and_instance_parts[1] if len(name_and_instance_parts) > 1 else None + resource_instance = ( + name_and_instance_parts[1] + if len(name_and_instance_parts) > 1 + else None + ) resource_message = parts[1] if len(parts) > 1 else None u_resource = UResource(name=resource_name) @@ -197,7 +196,11 @@ def parse_from_string(resource_string: str) -> UResource: u_resource.instance = resource_instance if resource_message is not None: u_resource.message = resource_message - if "rpc" in resource_name and resource_instance is not None and "response" in resource_instance: + if ( + "rpc" in resource_name + and resource_instance is not None + and "response" in resource_instance + ): u_resource.id = 0 return u_resource @@ -207,7 +210,7 @@ def remove_empty(parts): result = parts[:] # Iterate through the list in reverse and remove empty strings - while result and result[-1] == '': + while result and result[-1] == "": result.pop() return result diff --git a/uprotocol/uri/serializer/microuriserializer.py b/uprotocol/uri/serializer/microuriserializer.py index 7b06c4f..e021df5 100644 --- a/uprotocol/uri/serializer/microuriserializer.py +++ b/uprotocol/uri/serializer/microuriserializer.py @@ -25,8 +25,6 @@ # ------------------------------------------------------------------------- -import io -import struct from enum import Enum from uprotocol.proto.uri_pb2 import UAuthority @@ -41,13 +39,14 @@ class AddressType(Enum): """ The type of address used for Micro URI. """ + LOCAL = 0 IPv4 = 1 IPv6 = 2 ID = 3 - def getValue(self): - return bytes(self.value) + # def getValue(self): + # return bytes(self.value) @classmethod def from_value(cls, value): @@ -57,12 +56,18 @@ def from_value(cls, value): return None # Return None if no matching enum value is found +def keep_8_least_significant_bits(n: int): + # by default, python is little endian + return n & 0xFF + + class MicroUriSerializer(UriSerializer): """ UUri Serializer that serializes a UUri to a byte[] (micro format) per https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/basics/uri.adoc """ + LOCAL_MICRO_URI_LENGTH = 8 IPV4_MICRO_URI_LENGTH = 12 IPV6_MICRO_URI_LENGTH = 24 @@ -70,21 +75,28 @@ class MicroUriSerializer(UriSerializer): def serialize(self, uri: UUri) -> bytes: """ - Serialize a UUri into a byte[] following the Micro-URI specifications.

+ Serialize a UUri into a byte[] following the Micro-URI specifications + for python, bits are unsigned integers [0, 255) @param uri:The UUri data object. @return:Returns a byte[] representing the serialized UUri. """ - if uri is None or UriValidator.is_empty(uri) or not UriValidator.is_micro_form(uri): + if ( + uri is None + or UriValidator.is_empty(uri) + or not UriValidator.is_micro_form(uri) + ): return bytearray() - maybe_ue_id = uri.entity.id - maybe_uresource_id = uri.resource.id + maybe_ue_id: int = uri.entity.id + maybe_uresource_id: int = uri.resource.id + + byte_arr: bytearray = bytearray() - os = io.BytesIO() - os.write(bytes([self.UP_VERSION])) + byte_arr.append(self.UP_VERSION) - if uri.authority.HasField('ip'): + address_type: AddressType = AddressType.LOCAL + if uri.authority.HasField("ip"): length: int = len(uri.authority.ip) if length == 4: address_type = AddressType.IPv4 @@ -92,52 +104,48 @@ def serialize(self, uri: UUri) -> bytes: address_type = AddressType.IPv6 else: return bytearray() - elif uri.authority.HasField('id'): + elif uri.authority.HasField("id"): address_type = AddressType.ID - else: - address_type = AddressType.LOCAL - os.write(address_type.value.to_bytes(1, 'big')) + byte_arr.append(address_type.value) # URESOURCE_ID - os.write((maybe_uresource_id >> 8).to_bytes(1, 'big')) - os.write((maybe_uresource_id & 0xFF).to_bytes(1, 'big')) + byte_arr.append(keep_8_least_significant_bits(maybe_uresource_id >> 8)) + byte_arr.append(keep_8_least_significant_bits(maybe_uresource_id)) # UENTITY_ID - os.write((maybe_ue_id >> 8).to_bytes(1, 'big')) - os.write((maybe_ue_id & 0xFF).to_bytes(1, 'big')) + byte_arr.append(keep_8_least_significant_bits(maybe_ue_id >> 8)) + byte_arr.append(keep_8_least_significant_bits(maybe_ue_id)) # UE_VERSION - unsigned_value = uri.entity.version_major - if unsigned_value > 127: - signed_byte = unsigned_value - 256 - else: - signed_byte = unsigned_value - os.write(struct.pack('b', signed_byte)) + unsigned_value: int = uri.entity.version_major + byte_arr.append(keep_8_least_significant_bits(unsigned_value)) + # UNUSED - os.write(bytes([0])) + byte_arr.append(0x0) # Populating the UAuthority if address_type != AddressType.LOCAL: # Write the ID length if the type is ID if address_type == AddressType.ID: - os.write(len(uri.authority.id).to_bytes(1, 'big')) - + byte_arr.append( + keep_8_least_significant_bits(len(uri.authority.id)) + ) try: if uri.authority.HasField("ip"): - os.write(uri.authority.ip) + byte_arr.extend(bytearray(uri.authority.ip)) elif uri.authority.HasField("id"): - os.write(uri.authority.id) - except Exception as e: - print(e) # Handle the exception as needed - - return os.getvalue() + byte_arr.extend(bytearray(uri.authority.id)) + except Exception: + return bytearray() + return byte_arr def deserialize(self, micro_uri: bytes) -> UUri: """ Deserialize a byte[] into a UUri object.

@param micro_uri:A byte[] uProtocol micro URI. - @return:Returns an UUri data object from the serialized format of a microUri. + @return:Returns an UUri data object from the serialized + format of a microUri. """ if micro_uri is None or len(micro_uri) < self.LOCAL_MICRO_URI_LENGTH: return UUri() @@ -154,11 +162,20 @@ def deserialize(self, micro_uri: bytes) -> UUri: # Validate that the micro_uri is the correct length for the type address_type = addresstype - if address_type == AddressType.LOCAL and len(micro_uri) != self.LOCAL_MICRO_URI_LENGTH: + if ( + address_type == AddressType.LOCAL + and len(micro_uri) != self.LOCAL_MICRO_URI_LENGTH + ): return UUri() - elif address_type == AddressType.IPv4 and len(micro_uri) != self.IPV4_MICRO_URI_LENGTH: + elif ( + address_type == AddressType.IPv4 + and len(micro_uri) != self.IPV4_MICRO_URI_LENGTH + ): return UUri() - elif address_type == AddressType.IPv6 and len(micro_uri) != self.IPV6_MICRO_URI_LENGTH: + elif ( + address_type == AddressType.IPv6 + and len(micro_uri) != self.IPV6_MICRO_URI_LENGTH + ): return UUri() # UENTITY_ID @@ -176,7 +193,10 @@ def deserialize(self, micro_uri: bytes) -> UUri: length = micro_uri[8] u_authority = UAuthority(id=bytes(micro_uri[9:9 + length])) - uri = UUri(entity=UEntity(id=ue_id, version_major=ui_version), resource=UResourceBuilder.from_id(u_resource_id)) + uri = UUri( + entity=UEntity(id=ue_id, version_major=ui_version), + resource=UResourceBuilder.from_id(u_resource_id), + ) if u_authority is not None: uri.authority.CopyFrom(u_authority) diff --git a/uprotocol/uri/serializer/shorturiserializer.py b/uprotocol/uri/serializer/shorturiserializer.py new file mode 100644 index 0000000..03a5a40 --- /dev/null +++ b/uprotocol/uri/serializer/shorturiserializer.py @@ -0,0 +1,226 @@ +# ------------------------------------------------------------------------- + +# Copyright (c) 2023 General Motors GTO LLC +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# SPDX-FileType: SOURCE +# SPDX-FileCopyrightText: 2023 General Motors GTO LLC +# SPDX-License-Identifier: Apache-2.0 + +# ------------------------------------------------------------------------- + +import re +import socket +from typing import List + +from uprotocol.proto.uri_pb2 import UUri, UAuthority, UEntity +from uprotocol.proto.uri_pb2 import UResource + +from uprotocol.uri.serializer.ipaddress import IpAddress +from uprotocol.uri.serializer.uriserializer import UriSerializer +from uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.uri.factory.uresource_builder import UResourceBuilder + + +def convert_packed_ipaddr_to_string(packed_ipaddr: bytes): + for address_type in [socket.AF_INET, socket.AF_INET6]: + try: + """ + socket.inet_ntop(): + Convert a packed IP address (a bytes-like object + of some number of bytes) + to its standard, family-specific string representation + (for example, '7.10.0.5' or '5aef:2b::8') + """ + return socket.inet_ntop(address_type, packed_ipaddr) + except ValueError: + pass + + raise Exception( + "Could not find correct address family to unpack ip address", + "from bytes to str", + ) + + +class ShortUriSerializer(UriSerializer): + """ + UUri Serializer that serializes a UUri to a Short format string. + """ + + def serialize(self, uri: UUri) -> str: + if uri is None or UriValidator.is_empty(uri): + return "" + string_builder: List[str] = [] + + if uri.HasField("authority"): + authority: UAuthority = uri.authority + if uri.authority.HasField("ip"): + try: + string_builder.append("//") + string_builder.append( + convert_packed_ipaddr_to_string(authority.ip) + ) + except Exception: + print("in exception") + return "" + elif uri.authority.HasField("id"): + string_builder.append("//") + string_builder.append(authority.id.decode("utf-8")) + else: + return "" + + string_builder.append("/") + string_builder.append( + self.build_software_entity_part_of_uri(uri.entity) + ) + string_builder.append(self.build_resource_part_of_uri(uri)) + + return re.sub("/+$", "", "".join(string_builder)) + + @staticmethod + def build_resource_part_of_uri(uri: UUri): + if not uri.HasField("resource"): + return "" + resource: UResource = uri.resource + + string_builder: List[str] = ["/"] + string_builder.append(str(resource.id)) + + return "".join(string_builder) + + @staticmethod + def build_software_entity_part_of_uri(entity): + """ + Create the service part of the uProtocol URI from a + software entity object. + @param use Software Entity representing a service or an application. + """ + string_builder: List[str] = [] + string_builder.append(str(entity.id)) + string_builder.append("/") + if entity.version_major > 0: + string_builder.append(str(entity.version_major)) + + return "".join(string_builder) + + def deserialize(self, uprotocol_uri: str) -> UUri: + """ + Deserialize a String into a UUri object. + @param uProtocolUri A short format uProtocol URI. + @return Returns an UUri data object. + """ + if uprotocol_uri is None or uprotocol_uri.strip() == "": + return UUri() + + uri = ( + uprotocol_uri[uprotocol_uri.index(":") + 1:] + if ":" in uprotocol_uri + else uprotocol_uri.replace("\\", "/") + ) + + is_local = not uri.startswith("//") + + uri_parts = uri.split("/") + number_of_parts_in_uri = len(uri_parts) + + if number_of_parts_in_uri < 2: + return UUri() + + ue_id = "" + ue_version = "" + + resource = None + authority = None + + if is_local: + ue_id = uri_parts[1] + if number_of_parts_in_uri > 2: + ue_version = uri_parts[2] + + if number_of_parts_in_uri > 3: + resource = ShortUriSerializer.parse_from_string( + uri_parts[3] + ) + + if number_of_parts_in_uri > 4: + return UUri() + else: + if uri_parts[2].strip() == "": + return UUri() + if IpAddress.is_valid(uri_parts[2]): + authority = UAuthority(ip=IpAddress.to_bytes(uri_parts[2])) + else: + authority = UAuthority(id=bytes(uri_parts[2], "utf-8")) + + if len(uri_parts) > 3: + ue_id = uri_parts[3] + if number_of_parts_in_uri > 4: + ue_version = uri_parts[4] + if number_of_parts_in_uri > 5: + resource = ShortUriSerializer.parse_from_string( + uri_parts[5] + ) + if number_of_parts_in_uri > 6: + return UUri() + else: + return UUri(authority=authority) + + ue_version_int = None + ue_id_int = None + + try: + if ue_version.strip() != "": + ue_version_int = int(ue_version) + if ue_id.strip() != "": + ue_id_int = int(ue_id) + except Exception: + return UUri() + + entity = UEntity() + new_uri = UUri() + if ue_id_int is not None: + entity.id = ue_id_int + new_uri.entity.CopyFrom(entity) + if ue_version_int is not None: + entity.version_major = ue_version_int + new_uri.entity.CopyFrom(entity) + + if authority is not None: + new_uri.authority.CopyFrom(authority) + + if resource is not None: + new_uri.resource.CopyFrom(resource) + + return new_uri + + @staticmethod + def parse_from_string(resource_string): + """ + Static factory method for creating a UResource using a string value + @param resource_string String that contains the UResource id. + @return Returns a UResource object. + """ + if resource_string is None: + raise ValueError(" Resource must have a command name") + id = None + try: + id = int(resource_string) + except Exception: + return UResource() + + return UResourceBuilder.from_id(id) diff --git a/uprotocol/uri/serializer/uriserializer.py b/uprotocol/uri/serializer/uriserializer.py index b68f9f2..fef58ec 100644 --- a/uprotocol/uri/serializer/uriserializer.py +++ b/uprotocol/uri/serializer/uriserializer.py @@ -27,17 +27,14 @@ from abc import ABC, abstractmethod from re import T -from typing import Optional -from uprotocol.proto.uri_pb2 import UUri,UAuthority,UEntity,UResource -from uprotocol.uri.validator.urivalidator import UriValidator +from uprotocol.proto.uri_pb2 import UUri class UriSerializer(ABC): """ - UUris are used in transport layers and hence need to be serialized.
Each transport supports different - serialization formats.
For more information, please refer to - https://github.com/eclipse-uprotocol/uprotocol-spec/blob/main/basics/uri.adoc + UUris are used in transport layers and hence need to be serialized.
+ Each transport supports different + serialization formats. """ @abstractmethod @@ -57,33 +54,3 @@ def serialize(self, uri: UUri) -> T: @return:Returns the UUri in the transport serialized format. """ pass - - def build_resolved(self, long_uri: str, micro_uri: bytes) -> UUri: - """ - Build a fully resolved UUri from the serialized long format and the serializes micro format.

- @param long_uri:UUri serialized as a Sting. - @param micro_uri:UUri serialized as a byte[]. - @return:Returns a UUri object serialized from one of the forms. - """ - if (not long_uri or long_uri.isspace()) and (not micro_uri or len(micro_uri) == 0): - return UUri() - from uprotocol.uri.serializer.longuriserializer import LongUriSerializer - from uprotocol.uri.serializer.microuriserializer import MicroUriSerializer - long_u_uri = LongUriSerializer().deserialize(long_uri) - micro_u_uri = MicroUriSerializer().deserialize(micro_uri) - u_authority = UAuthority() - u_authority.CopyFrom(micro_u_uri.authority) - - u_authority.name = long_u_uri.authority.name - - u_entity = UEntity() - u_entity.CopyFrom(micro_u_uri.entity) - - u_entity.name = long_u_uri.entity.name - - u_resource = UResource() - u_resource.CopyFrom(long_u_uri.resource) - u_resource.id = micro_u_uri.resource.id - - u_uri = UUri(authority=u_authority, entity=u_entity, resource=u_resource) - return u_uri if UriValidator.is_resolved(u_uri) else None diff --git a/uprotocol/uri/validator/urivalidator.py b/uprotocol/uri/validator/urivalidator.py index 8be64f0..1975730 100644 --- a/uprotocol/uri/validator/urivalidator.py +++ b/uprotocol/uri/validator/urivalidator.py @@ -24,13 +24,14 @@ # ------------------------------------------------------------------------- +from multimethod import multimethod from uprotocol.proto.uri_pb2 import UAuthority -from uprotocol.proto.uri_pb2 import UEntity from uprotocol.proto.uri_pb2 import UResource from uprotocol.proto.uri_pb2 import UUri +from uprotocol.uri.factory.uresource_builder import UResourceBuilder from uprotocol.validation.validationresult import ValidationResult -from multipledispatch import dispatch + class UriValidator: """ @@ -40,28 +41,38 @@ class UriValidator: @staticmethod def validate(uri: UUri) -> ValidationResult: """ - Validate a UUri to ensure that it has at least a name for the uEntity.

+ Validate a UUri to ensure that it has at least a name + for the uEntity.

@param uri:UUri to validate. - @return:Returns UStatus containing a success or a failure with the error message. + @return:Returns UStatus containing a success or a failure + with the error message. """ if UriValidator.is_empty(uri): return ValidationResult.failure("Uri is empty.") - if uri.HasField('authority') and not UriValidator.is_remote(uri.authority): - return ValidationResult.failure("Uri is remote missing uAuthority.") + if uri.HasField("authority") and not UriValidator.is_remote( + uri.authority + ): + return ValidationResult.failure( + "Uri is remote missing uAuthority." + ) if uri.entity.name.strip() == "": - return ValidationResult.failure("Uri is missing uSoftware Entity name.") + return ValidationResult.failure( + "Uri is missing uSoftware Entity name." + ) return ValidationResult.success() @staticmethod def validate_rpc_method(uri: UUri) -> ValidationResult: """ - Validate a UUri that is meant to be used as an RPC method URI. Used in Request sink values and Response + Validate a UUri that is meant to be used as an RPC method URI. + Used in Request sink values and Response source values.

@param uri:UUri to validate. - @return:Returns UStatus containing a success or a failure with the error message. + @return:Returns UStatus containing a success or a failure + with the error message. """ status = UriValidator.validate(uri) if status.is_failure(): @@ -69,17 +80,20 @@ def validate_rpc_method(uri: UUri) -> ValidationResult: if not UriValidator.is_rpc_method(uri): return ValidationResult.failure( - "Invalid RPC method uri. Uri should be the method to be called, or method from response.") + "Invalid RPC method uri. Uri should be the method to be called, or method from response." + ) return ValidationResult.success() @staticmethod def validate_rpc_response(uri: UUri) -> ValidationResult: """ - Validate a UUri that is meant to be used as an RPC response URI. Used in Request source values and + Validate a UUri that is meant to be used as an RPC response URI. + Used in Request source values and Response sink values.

@param uri:UUri to validate. - @return:Returns UStatus containing a success or a failure with the error message. + @return:Returns UStatus containing a success or a failure with + the error message. """ status = UriValidator.validate(uri) if status.is_failure(): @@ -92,102 +106,173 @@ def validate_rpc_response(uri: UUri) -> ValidationResult: @staticmethod def is_empty(uri: UUri) -> bool: - ''' - Indicates that this URI is an empty as it does not contain authority, entity, and resource. + """ + Indicates that this URI is an empty as it does not contain + authority, entity, and resource. @param uri UUri to check if it is empty - @return Returns true if this URI is an empty container and has no valuable information in building uProtocol sinks or sources. - ''' - return uri is not None and not uri.HasField('authority') and not uri.HasField('entity') and not uri.HasField('resource') - + @return Returns true if this URI is an empty container and has + no valuable information in building uProtocol sinks or sources. + """ + return uri is None or uri == UUri() - @staticmethod + @multimethod def is_rpc_method(uri: UUri) -> bool: """ - Returns true if this resource specifies an RPC method call or RPC response.

- @param uri: - @return:Returns true if this resource specifies an RPC method call or RPC response. + Returns true if URI is of type RPC. A UUri is of type RPC if it + contains the word rpc in the resource name + and has an instance name and/or the id is less than MIN_TOPIC_ID. + @param uri: UUri to check if it is of type RPC + @return: Returns true if this resource specifies an RPC method + call or RPC response. + """ + return uri is not None and UriValidator.is_rpc_method(uri.resource) + + @multimethod + def is_rpc_method(uri: None) -> bool: + """ + Returns false if input is None. + @param uri: None + @return Returns false. """ - return not UriValidator.is_empty(uri) and uri.resource.name == "rpc" and ( - uri.resource.HasField('instance') and uri.resource.instance.strip() != "" or ( - uri.resource.HasField('id') and uri.resource.id != 0)) + return False + + @multimethod + def is_rpc_method(resource: UResource) -> bool: + """ + Returns true if Uresource is of type RPC. + @param resource: UResource to check if it is of type RPC method + @return Returns true if URI is of type RPC. + """ + return ( + resource is not None + and resource.name == "rpc" + and ( + resource.HasField("instance") + and resource.instance.strip() != "" + or ( + resource.HasField("id") + and resource.id < UResourceBuilder.MIN_TOPIC_ID + ) + ) + ) + + @multimethod + def is_rpc_method(resource: None) -> bool: + """ + Returns false if input is None. + @param resource: None + @return Returns false. + """ + return False @staticmethod def is_resolved(uri: UUri) -> bool: - return uri is not None and not UriValidator.is_empty(uri) and \ - UriValidator.is_long_form(uri) and UriValidator.is_micro_form(uri) + return UriValidator.is_long_form(uri) and UriValidator.is_micro_form( + uri + ) @staticmethod def is_rpc_response(uri: UUri) -> bool: - if uri is None: - return False - - resource = uri.resource - - return "rpc" in resource.name and uri.HasField("resource") and "response" in resource.instance and resource.HasField("id") and resource.id == 0 + return ( + uri is not None + and uri.resource == UResourceBuilder.for_rpc_response() + ) - @staticmethod - @dispatch(UUri) + @multimethod def is_micro_form(uri: UUri) -> bool: """ - Determines if this UUri can be serialized into a micro form UUri.

+ Determines if this UUri can be serialized into + a micro form UUri.

@param uuri: An UUri proto message object - @return:Returns true if this UUri can be serialized into a micro form UUri. + @return:Returns true if this UUri can be serialized into + a micro form UUri. """ - return uri is not None and not UriValidator.is_empty(uri) and uri.entity.HasField('id') \ - and uri.resource.HasField('id') and UriValidator.is_micro_form(uri.authority) - - @staticmethod - @dispatch(UAuthority) + return ( + uri is not None + and not UriValidator.is_empty(uri) + and uri.entity.HasField("id") + and uri.resource.HasField("id") + and UriValidator.is_micro_form(uri.authority) + ) + + @multimethod def is_micro_form(authority: UAuthority) -> bool: - ''' - check if UAuthority can be represented in micro format. Micro UAuthorities are local or ones + """ + check if UAuthority can be represented in micro format. + Micro UAuthorities are local or ones that contain IP address or IDs. @param authority UAuthority to check @return Returns true if UAuthority can be represented in micro format - ''' - + """ - return UriValidator.is_local(authority) or (authority.HasField('ip') or (authority.HasField('id'))) + return UriValidator.is_local(authority) or ( + authority.HasField("ip") or (authority.HasField("id")) + ) - @staticmethod - @dispatch(UUri) + @multimethod def is_long_form(uri: UUri) -> bool: """ - Determines if this UUri can be serialized into a long form UUri.

+ Determines if this UUri can be serialized into + a long form UUri.

@param uuri: An UUri proto message object - @return:Returns true if this UUri can be serialized into a long form UUri. + @return:Returns true if this UUri can be serialized into + a long form UUri. """ - return uri is not None and not UriValidator.is_empty(uri) and UriValidator.is_long_form(uri.authority) and \ - uri.entity.name.strip() != "" and uri.resource.name.strip() != "" - - @staticmethod - @dispatch(UAuthority) + return ( + uri is not None + and not UriValidator.is_empty(uri) + and UriValidator.is_long_form(uri.authority) + and uri.entity.name.strip() != "" + and uri.resource.name.strip() != "" + ) + + @multimethod def is_long_form(authority: UAuthority) -> bool: - ''' - Returns true if UAuthority contains names so that it can be serialized into long format. + """ + Returns true if UAuthority is local contains names so + that it can be serialized into long format. @param authority UAuthority to check - @return Returns true if URI contains names so that it can be serialized into long format. - ''' - return authority is not None and authority.HasField('name') and authority.name.strip() != "" - + @return Returns true if URI contains names so that + it can be serialized into long format. + """ + return authority is not None and ( + UriValidator.is_local(authority) + or (authority.HasField("name") and authority.name.strip() != "") + ) + @staticmethod def is_local(authority: UAuthority) -> bool: - ''' - Returns true if UAuthority is local meaning there is no name/ip/id set. + """ + Returns true if UAuthority is local meaning there + is no name/ip/id set. @param authority UAuthority to check if it is local or not - @return Returns true if UAuthority is local meaning the Authority is not populated with name, ip and id - ''' - return (authority is None) or (authority == UAuthority()) + @return Returns true if UAuthority is local meaning the + Authority is not populated with name, ip and id + """ + return (authority is not None) and (authority == UAuthority()) @staticmethod def is_remote(authority: UAuthority) -> bool: - ''' - Returns true if UAuthority is remote meaning the name and/or ip/id is populated. + """ + Returns true if UAuthority is remote meaning + the name and/or ip/id is populated. @param authority UAuthority to check if it is remote or not - @return Returns true if UAuthority is remote meaning the name and/or ip/id is populated. - ''' - return (authority is not None) and (not authority == UAuthority()) and \ - (UriValidator.is_long_form(authority) or UriValidator.is_micro_form(authority)) \ No newline at end of file + @return Returns true if UAuthority is remote meaning + the name and/or ip/id is populated. + """ + return (authority is not None) and (not authority == UAuthority()) + + @staticmethod + def is_short_form(uri: UUri) -> bool: + """ + Return True of the UUri is Short form. A UUri that + is micro form (contains numbers) can + also be a Short form Uri. + @param uri {@link UUri} to check + @return Returns true if contains ids can can + be serialized to short format. + """ + return UriValidator.is_micro_form(uri) diff --git a/uprotocol/uuid/factory/uuidfactory.py b/uprotocol/uuid/factory/uuidfactory.py index 2b62b97..25acc3b 100644 --- a/uprotocol/uuid/factory/uuidfactory.py +++ b/uprotocol/uuid/factory/uuidfactory.py @@ -1,9 +1,5 @@ # ------------------------------------------------------------------------- -import hashlib -import random -import struct -from datetime import datetime -# Copyright (c) 2023 General Motors GTO LLC +# Copyright (c) 2024 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file @@ -22,16 +18,17 @@ # specific language governing permissions and limitations # under the License. # SPDX-FileType: SOURCE -# SPDX-FileCopyrightText: 2023 General Motors GTO LLC +# SPDX-FileCopyrightText: 2024 General Motors GTO LLC # SPDX-License-Identifier: Apache-2.0 # ------------------------------------------------------------------------- -from enum import Enum +import random +from datetime import datetime from uprotocol.proto.uuid_pb2 import UUID -from uprotocol.uuid.factory import * +from uprotocol.uuid.factory import uuid6 from uprotocol.uuid.factory.uuidutils import UUIDUtils @@ -55,12 +52,17 @@ def _create(self, instant) -> UUID: class UUIDv8Factory(UUIDFactory): - MAX_COUNT = 0xfff - _lsb = (random.getrandbits(63) & 0x3fffffffffffffff) | 0x8000000000000000 + MAX_COUNT = 0xFFF + _lsb = (random.getrandbits(63) & 0x3FFFFFFFFFFFFFFF) | 0x8000000000000000 UUIDV8_VERSION = 8 _msb = UUIDV8_VERSION << 12 + def _create(self, instant) -> UUID: - time = int(instant.timestamp() * 1000) if instant else int(datetime.now().timestamp() * 1000) + time = ( + int(instant.timestamp() * 1000) + if instant + else int(datetime.now().timestamp() * 1000) + ) if time == (self._msb >> 16): if (self._msb & 0xFFF) < self.MAX_COUNT: @@ -72,6 +74,6 @@ def _create(self, instant) -> UUID: # return UUID(msb=msb, lsb=lsb) -class Factories(): +class Factories: UUIDV6 = UUIDv6Factory() UPROTOCOL = UUIDv8Factory() diff --git a/uprotocol/uuid/factory/uuidutils.py b/uprotocol/uuid/factory/uuidutils.py index 8ef0c0e..1be68ad 100644 --- a/uprotocol/uuid/factory/uuidutils.py +++ b/uprotocol/uuid/factory/uuidutils.py @@ -1,37 +1,38 @@ -# ------------------------------------------------------------------------- - -# Copyright (c) 2023 General Motors GTO LLC -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# SPDX-FileType: SOURCE -# SPDX-FileCopyrightText: 2023 General Motors GTO LLC -# SPDX-License-Identifier: Apache-2.0 - -# ------------------------------------------------------------------------- +# # ------------------------------------------------------------------------- +# # Copyright (c) 2023 General Motors GTO LLC +# # +# # Licensed to the Apache Software Foundation (ASF) under one +# # or more contributor license agreements. See the NOTICE file +# # distributed with this work for additional information +# # regarding copyright ownership. The ASF licenses this file +# # to you under the Apache License, Version 2.0 (the +# # "License"); you may not use this file except in compliance +# # with the License. You may obtain a copy of the License at +# # +# # http://www.apache.org/licenses/LICENSE-2.0 +# # +# # Unless required by applicable law or agreed to in writing, +# # software distributed under the License is distributed on an +# # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# # KIND, either express or implied. See the License for the +# # specific language governing permissions and limitations +# # under the License. +# # SPDX-FileType: SOURCE +# # SPDX-FileCopyrightText: 2023 General Motors GTO LLC +# # SPDX-License-Identifier: Apache-2.0 + +# # ------------------------------------------------------------------------- import uuid -from datetime import datetime +import time from enum import Enum -from typing import Optional +from typing import Optional, Union +from multimethod import multimethod from uprotocol.proto.uuid_pb2 import UUID +from uprotocol.proto.uattributes_pb2 import UAttributes from uprotocol.uuid.factory import PythonUUID @@ -39,17 +40,20 @@ class Version(Enum): """ UUID Version """ - VERSION_UNKNOWN = 0 # An unknown version. - VERSION_RANDOM_BASED = 4 # The randomly or pseudo-randomly generated version specified in RFC-4122. - VERSION_TIME_ORDERED = 6 # The time-ordered version with gregorian epoch proposed by Peabody and Davis. - VERSION_UPROTOCOL = 8 # The custom or free-form version proposed by Peabody and Davis. + + VERSION_UNKNOWN = 0 + VERSION_RANDOM_BASED = 4 + VERSION_TIME_ORDERED = 6 + VERSION_UPROTOCOL = 8 @staticmethod - def getVersion(value: int): + def get_version(value: int): """ - Get the Version from the passed integer representation of the version.

+ Get the Version from the passed integer representation + of the version.

@param value:The integer representation of the version. - @return:The Version object or Optional.empty() if the value is not a valid version. + @return:The Version object or Optional.empty() if the value + is not a valid version. """ for version in Version: if version.value == value: @@ -63,20 +67,20 @@ class UUIDUtils: """ @staticmethod - def getVersion(uuid_obj: UUID) -> Optional[Version]: + def get_version(uuid_obj: UUID) -> Optional[Version]: """ Fetch the UUID version.

@param uuid_obj:The UUID to fetch the version from. - @return: Return the UUID version from the UUID object or Optional.empty() if the uuid is null. + @return: Return the UUID version from the UUID object + or Optional.empty() if the uuid is null. """ if uuid_obj is None: return None - return Version.getVersion((uuid_obj.msb >> 12) & 0x0f) - + return Version.get_version((uuid_obj.msb >> 12) & 0x0F) @staticmethod - def getVariant(uuid_obj: UUID) -> Optional[str]: + def get_variant(uuid_obj: UUID) -> Optional[str]: """ Fetch the Variant from the passed UUID.

@param uuid_obj:The UUID to fetch the variant from. @@ -84,52 +88,73 @@ def getVariant(uuid_obj: UUID) -> Optional[str]: """ if uuid_obj is None: return None - python_uuid = UUIDUtils.create_pythonuuid_from_eclipseuuid(uuid_obj) + python_uuid = UUIDUtils.create_pythonuuid_from_eclipseuuid( + uuid_obj + ) return python_uuid.variant @staticmethod - def isUProtocol(uuid_obj: UUID) -> bool: + def is_uprotocol(uuid_obj: UUID) -> bool: """ Verify if version is a formal UUIDv8 uProtocol ID.

@param uuid_obj:UUID object - @return:true if is a uProtocol UUID or false if uuid passed is null or the UUID is not uProtocol format. + @return:true if is a uProtocol UUID or false if uuid + passed is null or the UUID is not uProtocol format. """ - return UUIDUtils.getVersion(uuid_obj) == Version.VERSION_UPROTOCOL if uuid_obj is not None else False + return ( + UUIDUtils.get_version(uuid_obj) + == Version.VERSION_UPROTOCOL + if uuid_obj is not None + else False + ) @staticmethod - def isUuidv6(uuid_obj: UUID) -> bool: + def is_uuidv6(uuid_obj: UUID) -> bool: """ Verify if version is UUIDv6

@param uuid_obj:UUID object - @return:true if is UUID version 6 or false if uuid is null or not version 6 + @return:true if is UUID version 6 or false if uuid + is null or not version 6 """ if uuid_obj is None: return False - return UUIDUtils.getVersion(uuid_obj) == Version.VERSION_TIME_ORDERED and UUIDUtils.getVariant( - uuid_obj) == uuid.RFC_4122 if uuid_obj is not None else False + return ( + UUIDUtils.get_version(uuid_obj) + == Version.VERSION_TIME_ORDERED + and UUIDUtils.get_variant(uuid_obj) == uuid.RFC_4122 + if uuid_obj is not None + else False + ) @staticmethod - def isuuid(uuid_obj: UUID) -> bool: + def is_uuid(uuid_obj: UUID) -> bool: """ Verify uuid is either v6 or v8

@param uuid_obj: UUID object @return:true if is UUID version 6 or 8 """ - return UUIDUtils.isUProtocol(uuid_obj) or UUIDUtils.isUuidv6(uuid_obj) if uuid_obj is not None else False + return ( + UUIDUtils.is_uprotocol(uuid_obj) + or UUIDUtils.is_uuidv6(uuid_obj) + if uuid_obj is not None + else False + ) @staticmethod - def getTime(uuid: UUID): + def get_time(uuid: UUID): """ - Return the number of milliseconds since unix epoch from a passed UUID.

+ Return the number of milliseconds since unix epoch from + a passed UUID.

@param uuid:passed uuid to fetch the time. - @return:number of milliseconds since unix epoch or empty if uuid is null. + @return:number of milliseconds since unix epoch or + empty if uuid is null. """ time = None - version = UUIDUtils.getVersion(uuid) + version = UUIDUtils.get_version(uuid) if uuid is None or version is None: return None @@ -137,7 +162,9 @@ def getTime(uuid: UUID): time = uuid.msb >> 16 elif version == Version.VERSION_TIME_ORDERED: try: - python_uuid = UUIDUtils.create_pythonuuid_from_eclipseuuid(uuid) + python_uuid = ( + UUIDUtils.create_pythonuuid_from_eclipseuuid(uuid) + ) # Convert 100-nanoseconds ticks to milliseconds time = python_uuid.time // 10000 except ValueError: @@ -145,6 +172,90 @@ def getTime(uuid: UUID): return time + @staticmethod + def get_elapsed_time(id: UUID): + """ + Calculates the elapsed time since the creation of the specified UUID. + + @param id The UUID of the object whose creation time + needs to be determined. + @return The elapsed time in milliseconds, + or None if the creation time cannot be determined. + """ + creation_time = UUIDUtils.get_time(id) or -1 + if creation_time < 0: + return None + now = int(time.time() * 1000) + return now - creation_time if now >= creation_time else None + + @multimethod + def get_remaining_time(id: Union[UUID, None], ttl: int): + """ + Calculates the remaining time until the expiration of the event + identified by the given UUID. + + @param id The UUID of the object whose remaining time needs to + be determined. + @param ttl The time-to-live (TTL) in milliseconds. + @return The remaining time in milliseconds until the event expires, + or None if the UUID is null, TTL is non-positive, or the + creation time cannot be determined. + """ + if id is None or ttl <= 0: + return None + elapsed_time = UUIDUtils.get_elapsed_time(id) + return ttl - elapsed_time if ttl > elapsed_time else None + + @multimethod + def get_remaining_time(attributes: Union[UAttributes, None]): + """ + Calculates the remaining time until the expiration of the event + identified by the given UAttributes. + @param attributes The attributes containing information about + the event, including its ID and TTL. + @return The remaining time in milliseconds until the event expires, + or None if the attributes do not contain TTL information or the + creation time cannot be determined. + """ + return ( + UUIDUtils.get_remaining_time( + attributes.id, attributes.ttl + ) + if attributes.HasField("ttl") + else None + ) + + @multimethod + def is_expired(id: Union[UUID, None], ttl: int): + """ + Checks if the event identified by the given UUID has expired based + on the specified time-to-live (TTL). + + @param id The UUID identifying the event. + @param ttl The time-to-live (TTL) in milliseconds for the event. + @return true if the event has expired, false otherwise. Returns false + if TTL is non-positive or creation time + cannot be determined. + """ + return ( + ttl > 0 and UUIDUtils.get_remaining_time(id, ttl) is None + ) + + @multimethod + def is_expired(attributes: Union[UAttributes, None]): + """ + Checks if the event identified by the given UAttributes has expired. + + @param attributes The attributes containing information about the + event, including its ID and TTL. + @return true if the event has expired, false otherwise.Returns false + if the attributes do not contain TTL + information or creation time cannot be determined. + """ + return attributes.HasField("ttl") and UUIDUtils.is_expired( + attributes.id, attributes.ttl + ) + @staticmethod def get_msb_lsb(uuid: PythonUUID): # Convert UUID to a 128-bit integer @@ -159,8 +270,6 @@ def get_msb_lsb(uuid: PythonUUID): return msb, lsb @staticmethod - def create_pythonuuid_from_eclipseuuid(uuid:UUID) -> PythonUUID: + def create_pythonuuid_from_eclipseuuid(uuid: UUID) -> PythonUUID: combined_int = (uuid.msb << 64) + uuid.lsb return PythonUUID(int=combined_int) - # from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer - # return PythonUUID(hex=LongUuidSerializer.instance().serialize(uuid)) diff --git a/uprotocol/uuid/serializer/longuuidserializer.py b/uprotocol/uuid/serializer/longuuidserializer.py index 239bb65..fe565ad 100644 --- a/uprotocol/uuid/serializer/longuuidserializer.py +++ b/uprotocol/uuid/serializer/longuuidserializer.py @@ -33,7 +33,8 @@ class LongUuidSerializer(UuidSerializer): """ - UUID Serializer implementation used to serialize/deserialize UUIDs to/from a string + UUID Serializer implementation used to serialize/deserialize UUIDs to/from + a string """ @staticmethod @@ -44,15 +45,20 @@ def deserialize(self, string_uuid: str) -> UUID: """ Deserialize from the string format to a UUID. :param string_uuid: Serialized UUID in string format. - :return: Returns a UUID object from the serialized format from the wire. + :return: Returns a UUID object from the serialized format from the + wire. """ if not string_uuid or string_uuid.isspace(): - return UUID() # Return default UUID if string is empty or whitespace + return ( + UUID() + ) # Return default UUID if string is empty or whitespace try: msb, lsb = UUIDUtils.get_msb_lsb(PythonUUID(string_uuid)) return UUID(msb=msb, lsb=lsb) except ValueError: - return UUID() # Return default UUID in case of parsing failure + return ( + UUID() + ) # Return default UUID in case of parsing failure def serialize(self, uuid: UUID) -> str: """ @@ -61,7 +67,9 @@ def serialize(self, uuid: UUID) -> str: :return: Returns the UUID in the string serialized format. """ if uuid is None: - return '' + return "" - pythonuuid = UUIDUtils.create_pythonuuid_from_eclipseuuid(uuid) - return str(pythonuuid) if uuid else '' + pythonuuid = UUIDUtils.create_pythonuuid_from_eclipseuuid( + uuid + ) + return str(pythonuuid) if uuid else "" diff --git a/uprotocol/uuid/serializer/microuuidserializer.py b/uprotocol/uuid/serializer/microuuidserializer.py index 0b55bfe..6d7a6b3 100644 --- a/uprotocol/uuid/serializer/microuuidserializer.py +++ b/uprotocol/uuid/serializer/microuuidserializer.py @@ -34,7 +34,8 @@ class MicroUuidSerializer(UuidSerializer): """ - UUID Serializer implementation used to serialize/deserialize UUIDs to/from bytes + UUID Serializer implementation used to serialize/deserialize UUIDs to/from + bytes """ @staticmethod @@ -45,12 +46,15 @@ def deserialize(self, uuid_bytes: bytes) -> UUID: """ Deserialize from the bytes format to a UUID. :param uuid_bytes: Serialized UUID in bytes format. - :return: Returns a UUID object from the serialized format from the wire. + :return: Returns a UUID object from the serialized format from the + wire. """ if not uuid_bytes or len(uuid_bytes) != 16: - return UUID() # Return default UUID if bytes are empty or not 16 bytes + return ( + UUID() + ) # Return default UUID if bytes are empty or not 16 bytes - msb, lsb = struct.unpack('>QQ', uuid_bytes) + msb, lsb = struct.unpack(">QQ", uuid_bytes) return UUID(msb=msb, lsb=lsb) def serialize(self, uuid: UUID) -> bytes: @@ -61,6 +65,8 @@ def serialize(self, uuid: UUID) -> bytes: """ if uuid is None: return bytearray() - pythonuuid = UUIDUtils.create_pythonuuid_from_eclipseuuid(uuid) - msb, lsb = divmod(pythonuuid.int, 2 ** 64) - return struct.pack('>QQ', msb, lsb) + pythonuuid = UUIDUtils.create_pythonuuid_from_eclipseuuid( + uuid + ) + msb, lsb = divmod(pythonuuid.int, 2**64) + return struct.pack(">QQ", msb, lsb) diff --git a/uprotocol/uuid/serializer/uuidserializer.py b/uprotocol/uuid/serializer/uuidserializer.py index 44356d4..22ca927 100644 --- a/uprotocol/uuid/serializer/uuidserializer.py +++ b/uprotocol/uuid/serializer/uuidserializer.py @@ -30,12 +30,13 @@ from uprotocol.proto.uuid_pb2 import UUID -T = TypeVar('T') +T = TypeVar("T") class UuidSerializer(ABC, Generic[T]): """ - UUID Serializer interface used to serialize/deserialize UUIDs to/from either Long (string) or micro (bytes) form + UUID Serializer interface used to serialize/deserialize UUIDs + to/from either Long (string) or micro (bytes) form """ @abstractmethod @@ -43,7 +44,8 @@ def deserialize(self, uuid: T) -> UUID: """ Deserialize from the format to a UUID. :param uuid: Serialized UUID. - :return: Returns a UUID object from the serialized format from the wire. + :return: Returns a UUID object from the + serialized format from the wire. """ pass # Implement your deserialization logic here diff --git a/uprotocol/uuid/validate/uuidvalidator.py b/uprotocol/uuid/validate/uuidvalidator.py index dce36cd..efef6a0 100644 --- a/uprotocol/uuid/validate/uuidvalidator.py +++ b/uprotocol/uuid/validate/uuidvalidator.py @@ -1,5 +1,6 @@ # ------------------------------------------------------------------------- from abc import ABC, abstractmethod + # Copyright (c) 2023 General Motors GTO LLC # # Licensed to the Apache Software Foundation (ASF) under one @@ -25,7 +26,6 @@ # ------------------------------------------------------------------------- -from collections import namedtuple from enum import Enum from uprotocol.proto.uuid_pb2 import UUID @@ -45,16 +45,24 @@ class UuidValidator(ABC): @staticmethod def get_validator(uuid: UUID): - if UUIDUtils.isUuidv6(uuid): + if UUIDUtils.is_uuidv6(uuid): return Validators.UUIDV6.validator() - elif UUIDUtils.isUProtocol(uuid): + elif UUIDUtils.is_uprotocol(uuid): return Validators.UPROTOCOL.validator() else: return Validators.UNKNOWN.validator() def validate(self, uuid: UUID) -> UStatus: - error_messages = [self.validate_version(uuid), self.validate_variant(uuid), self.validate_time(uuid)] - error_messages = [result.get_message() for result in error_messages if result.is_failure()] + error_messages = [ + self.validate_version(uuid), + self.validate_variant(uuid), + self.validate_time(uuid), + ] + error_messages = [ + result.get_message() + for result in error_messages + if result.is_failure() + ] error_message = ",".join(error_messages) if not error_message: return ValidationResult.success().to_status() @@ -65,8 +73,12 @@ def validate_version(self, uuid: UUID) -> ValidationResult: raise NotImplementedError def validate_time(self, uuid: UUID) -> ValidationResult: - time = UUIDUtils.getTime(uuid) - return ValidationResult.success() if (time is not None and time > 0 )else ValidationResult.failure("Invalid UUID Time") + time = UUIDUtils.get_time(uuid) + return ( + ValidationResult.success() + if (time is not None and time > 0) + else ValidationResult.failure("Invalid UUID Time") + ) @abstractmethod def validate_variant(self, uuid: UUID) -> ValidationResult: @@ -83,23 +95,30 @@ def validate_variant(self, uuid: UUID) -> ValidationResult: class UUIDv6Validator(UuidValidator): def validate_version(self, uuid: UUID) -> ValidationResult: - version = UUIDUtils.getVersion(uuid) - return ValidationResult.success() if version and version == Version.VERSION_TIME_ORDERED else ( - ValidationResult.failure( - "Not a UUIDv6 Version")) + version = UUIDUtils.get_version(uuid) + return ( + ValidationResult.success() + if version and version == Version.VERSION_TIME_ORDERED + else (ValidationResult.failure("Not a UUIDv6 Version")) + ) def validate_variant(self, uuid: UUID) -> ValidationResult: - variant = UUIDUtils.getVariant(uuid) - return ValidationResult.success() if variant and "RFC 4122" in variant else ValidationResult.failure( - "Invalid UUIDv6 variant") + variant = UUIDUtils.get_variant(uuid) + return ( + ValidationResult.success() + if variant and "RFC 4122" in variant + else ValidationResult.failure("Invalid UUIDv6 variant") + ) class UUIDv8Validator(UuidValidator): def validate_version(self, uuid: UUID) -> ValidationResult: - version = UUIDUtils.getVersion(uuid) - return ValidationResult.success() if version and version == Version.VERSION_UPROTOCOL else ( - ValidationResult.failure( - "Invalid UUIDv8 Version")) + version = UUIDUtils.get_version(uuid) + return ( + ValidationResult.success() + if version and version == Version.VERSION_UPROTOCOL + else (ValidationResult.failure("Invalid UUIDv8 Version")) + ) def validate_variant(self, uuid: UUID) -> ValidationResult: return ValidationResult.success() diff --git a/uprotocol/validation/validationresult.py b/uprotocol/validation/validationresult.py index 0d8e37c..7faca0c 100644 --- a/uprotocol/validation/validationresult.py +++ b/uprotocol/validation/validationresult.py @@ -30,10 +30,10 @@ from uprotocol.proto.ustatus_pb2 import UCode, UStatus - class ValidationResult(ABC): """ - Class wrapping a ValidationResult of success or failure wrapping the value of a UStatus. + Class wrapping a ValidationResult of success or failure wrapping the value + of a UStatus. """ STATUS_SUCCESS = UStatus(code=UCode.OK, message="OK") From 44ab4d6ded18d8b4ec6b8ac5862a955857d6c58c Mon Sep 17 00:00:00 2001 From: Matthew D'Alonzo Date: Thu, 4 Apr 2024 15:27:30 -0400 Subject: [PATCH 2/4] Bump up-python version to 0.1.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d95cef2..275cdc9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "up-python" -version = "0.1.1-dev" +version = "0.1.2-dev" description = "Language specific uProtocol library for building and using UUri, UUID, UAttributes, UTransport, and more." authors = ["Neelam Kushwah "] license = "The Apache License, Version 2.0" From 7d33be909e780e670a86b0939a4f6aecef0b9e03 Mon Sep 17 00:00:00 2001 From: Matthew D'Alonzo Date: Fri, 5 Apr 2024 20:53:33 -0400 Subject: [PATCH 3/4] Remove Linter Workflow The Linter I had (flake8) is not as robust as the linter we want to use (pylint), so I will be removing the workflow with flake8 for now. We will be creating an issue which will integrate pylint and better coverage testing. --- .github/workflows/python-checker.yml | 68 ---------------------------- 1 file changed, 68 deletions(-) delete mode 100644 .github/workflows/python-checker.yml diff --git a/.github/workflows/python-checker.yml b/.github/workflows/python-checker.yml deleted file mode 100644 index f643869..0000000 --- a/.github/workflows/python-checker.yml +++ /dev/null @@ -1,68 +0,0 @@ -# This workflow will upload a Python Package using Twine when a release is created -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Run Linter/Coverage Test - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -permissions: - contents: read - -jobs: - deploy: - - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Apache Maven Central - uses: actions/setup-java@v3 - with: # configure settings.xml - distribution: 'temurin' - java-version: '11' - server-id: ossrh - server-username: OSSRH_USER - server-password: OSSRH_PASSWORD - - - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.x' - - - name: Install Dependencies - run: | - python -m pip install --upgrade pip - python -m pip install flake8 pytest coverage cloudevents multimethod protobuf==4.24.2 - python -m pip install poetry - - - name: Install poetry dependencies - run: | - poetry install - - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 --ignore W503,W504,F811 . --max-line-length=127 --exclude **/*pb2.py - - - name: Run prebuild script - run: | - cd scripts - # Run the script within the Poetry virtual environment - poetry run python pull_and_compile_protos.py - - - name: Run tests and coverage report - run: | - coverage run --source tests/ -m unittest discover \ No newline at end of file From d99c0300cfb61418fad5fc7c540172cebb631305 Mon Sep 17 00:00:00 2001 From: Matthew D'Alonzo Date: Fri, 5 Apr 2024 21:57:22 -0400 Subject: [PATCH 4/4] Fix UTC ref, streamline multimethod Originally, I had UTC references from datetime library in a few of my tests. I remember them working, but when I ran again, they weren't working anymore. So, I switched to timezone in datetime which seems to work well. Also, my multimethod implementations were a bit redundant in places, so I cleaned those up. --- tests/test_uuid/test_factory/test_uuidfactory.py | 6 ++++-- tests/test_uuid/test_factory/test_uuidutils.py | 4 ++-- tests/test_uuid/test_validator/test_uuidvalidator.py | 4 ++-- uprotocol/uri/factory/uresource_builder.py | 8 +------- uprotocol/uri/serializer/shorturiserializer.py | 1 - uprotocol/uri/validator/urivalidator.py | 9 --------- 6 files changed, 9 insertions(+), 23 deletions(-) diff --git a/tests/test_uuid/test_factory/test_uuidfactory.py b/tests/test_uuid/test_factory/test_uuidfactory.py index 8ae0db3..8c4f474 100644 --- a/tests/test_uuid/test_factory/test_uuidfactory.py +++ b/tests/test_uuid/test_factory/test_uuidfactory.py @@ -25,7 +25,7 @@ # ------------------------------------------------------------------------- -from datetime import datetime, timedelta, UTC +from datetime import datetime, timedelta, timezone import unittest from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer from uprotocol.uuid.serializer.microuuidserializer import MicroUuidSerializer @@ -246,7 +246,9 @@ def test_uuidutils_frombytes_with_invalid_bytes(self): self.assertTrue(uuid1 == UUID()) def test_create_uprotocol_uuid_in_the_past(self): - past = datetime.now(UTC) - timedelta(seconds=10) + now = datetime.now() + past = now - timedelta(seconds=10) + past = past.replace(tzinfo=timezone.utc) uuid = Factories.UPROTOCOL.create(past) time = UUIDUtils.get_time(uuid) self.assertTrue(UUIDUtils.is_uprotocol(uuid)) diff --git a/tests/test_uuid/test_factory/test_uuidutils.py b/tests/test_uuid/test_factory/test_uuidutils.py index db5416f..403aab2 100644 --- a/tests/test_uuid/test_factory/test_uuidutils.py +++ b/tests/test_uuid/test_factory/test_uuidutils.py @@ -33,7 +33,7 @@ from uprotocol.uri.factory.uresource_builder import UResourceBuilder from uprotocol.proto.uattributes_pb2 import UAttributes, UPriority -from uprotocol.proto.uri_pb2 import UUri, UEntity, UAuthority +from uprotocol.proto.uri_pb2 import UUri, UEntity, UAuthority, UResource from uprotocol.proto.uuid_pb2 import UUID @@ -45,7 +45,7 @@ def build_source(): return UUri( authority=UAuthority(name="vcu.someVin.veh.ultifi.gm.com"), entity=UEntity(name="petapp.ultifi.gm.com", version_major=1), - resource=UResourceBuilder.for_rpc_request(None), + resource=UResource(name="door", instance="front_left", message="Door"), ) diff --git a/tests/test_uuid/test_validator/test_uuidvalidator.py b/tests/test_uuid/test_validator/test_uuidvalidator.py index eeb8f77..9d50ca3 100644 --- a/tests/test_uuid/test_validator/test_uuidvalidator.py +++ b/tests/test_uuid/test_validator/test_uuidvalidator.py @@ -26,7 +26,7 @@ import unittest -from datetime import datetime, UTC +from datetime import datetime, timezone from uprotocol.uuid.factory.uuidutils import UUIDUtils from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer @@ -61,7 +61,7 @@ def test_invalid_uuid(self): ) def test_invalid_time_uuid(self): - epoch_time = datetime.fromtimestamp(0, UTC) + epoch_time = datetime.fromtimestamp(0, tz=timezone.utc) uuid = Factories.UPROTOCOL.create( epoch_time diff --git a/uprotocol/uri/factory/uresource_builder.py b/uprotocol/uri/factory/uresource_builder.py index d873d5a..dbd6363 100644 --- a/uprotocol/uri/factory/uresource_builder.py +++ b/uprotocol/uri/factory/uresource_builder.py @@ -42,11 +42,7 @@ def for_rpc_response(): return UResource(name="rpc", instance="response", id=0) @multimethod - def for_rpc_request(method: Union[str, None]): - return UResourceBuilder.for_rpc_request(method, None) - - @multimethod - def for_rpc_request(method: Union[str, None], id: Union[int, None] = None): + def for_rpc_request(method: Union[str, None], id: int = None): uresource = UResource(name="rpc") if method is not None: uresource.instance = method @@ -57,8 +53,6 @@ def for_rpc_request(method: Union[str, None], id: Union[int, None] = None): @multimethod def for_rpc_request(id: int): - if id is None: - raise ValueError("id cannot be None") return UResourceBuilder.for_rpc_request(None, id) @staticmethod diff --git a/uprotocol/uri/serializer/shorturiserializer.py b/uprotocol/uri/serializer/shorturiserializer.py index 03a5a40..df35ea7 100644 --- a/uprotocol/uri/serializer/shorturiserializer.py +++ b/uprotocol/uri/serializer/shorturiserializer.py @@ -76,7 +76,6 @@ def serialize(self, uri: UUri) -> str: convert_packed_ipaddr_to_string(authority.ip) ) except Exception: - print("in exception") return "" elif uri.authority.HasField("id"): string_builder.append("//") diff --git a/uprotocol/uri/validator/urivalidator.py b/uprotocol/uri/validator/urivalidator.py index 1975730..0721285 100644 --- a/uprotocol/uri/validator/urivalidator.py +++ b/uprotocol/uri/validator/urivalidator.py @@ -156,15 +156,6 @@ def is_rpc_method(resource: UResource) -> bool: ) ) - @multimethod - def is_rpc_method(resource: None) -> bool: - """ - Returns false if input is None. - @param resource: None - @return Returns false. - """ - return False - @staticmethod def is_resolved(uri: UUri) -> bool: