From 544464ec1ced0b000fa36eed68c75752b9f9dfa9 Mon Sep 17 00:00:00 2001 From: ewuerger Date: Thu, 21 Nov 2024 13:49:15 +0100 Subject: [PATCH 1/2] fix!: Divide requirements text and generic serializer --- .../converters/converter_config.py | 36 ++++------ capella2polarion/converters/data_session.py | 2 + .../converters/element_converter.py | 66 +++++++++++++------ capella2polarion/converters/link_converter.py | 25 ++++++- capella2polarion/data_model/work_items.py | 7 ++ 5 files changed, 94 insertions(+), 42 deletions(-) diff --git a/capella2polarion/converters/converter_config.py b/capella2polarion/converters/converter_config.py index 5d5aff1..13bc22b 100644 --- a/capella2polarion/converters/converter_config.py +++ b/capella2polarion/converters/converter_config.py @@ -1,6 +1,7 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 """Module providing capella2polarion config class.""" + from __future__ import annotations import dataclasses @@ -16,6 +17,7 @@ _C2P_DEFAULT = "_C2P_DEFAULT" DESCRIPTION_REFERENCE_SERIALIZER = "description_reference" +REQUIREMENT_REFERENCE_SERIALIZER = "requirement_reference" DIAGRAM_ELEMENTS_SERIALIZER = "diagram_elements" @@ -81,9 +83,7 @@ def read_config_file( global_config_dict = config_dict.pop("*", {}) all_type_config = global_config_dict.pop("*", {}) global_links = all_type_config.get("links", []) - self.__global_config.links = self._force_link_config( - global_links, role_prefix - ) + self.__global_config.links = self._force_link_config(global_links, role_prefix) if "Diagram" in global_config_dict: diagram_config = global_config_dict.pop("Diagram") or {} @@ -91,9 +91,7 @@ def read_config_file( for c_type, type_config in global_config_dict.items(): type_config = type_config or {} - self.set_global_config( - c_type, type_config, type_prefix, role_prefix - ) + self.set_global_config(c_type, type_config, type_prefix, role_prefix) for layer, type_configs in config_dict.items(): type_configs = type_configs or {} @@ -143,9 +141,7 @@ def set_layer_config( type_prefix, ) self.polarion_types.add(p_type) - links = self._force_link_config( - type_config.get("links", []), role_prefix - ) + links = self._force_link_config(type_config.get("links", []), role_prefix) self._layer_configs[layer][c_type].append( CapellaTypeConfig( p_type, @@ -165,19 +161,15 @@ def set_global_config( ): """Set a global config for a specific type.""" p_type = add_prefix( - type_config.get("polarion_type") - or _default_type_conversion(c_type), + type_config.get("polarion_type") or _default_type_conversion(c_type), type_prefix, ) self.polarion_types.add(p_type) - link_config = self._force_link_config( - type_config.get("links", []), role_prefix - ) + link_config = self._force_link_config(type_config.get("links", []), role_prefix) self._global_configs[c_type] = CapellaTypeConfig( p_type, type_config.get("serializer"), - _filter_links(c_type, link_config) - + self._get_global_links(c_type), + _filter_links(c_type, link_config) + self._get_global_links(c_type), type_config.get("is_actor", _C2P_DEFAULT), type_config.get("nature", _C2P_DEFAULT), ) @@ -283,7 +275,7 @@ def config_matches(config: CapellaTypeConfig | None, **kwargs: t.Any) -> bool: def _read_capella_type_configs( - conf: dict[str, t.Any] | list[dict[str, t.Any]] | None + conf: dict[str, t.Any] | list[dict[str, t.Any]] | None, ) -> list[dict]: if conf is None: return [{}] @@ -300,7 +292,7 @@ def _read_capella_type_configs( def _force_dict( - config: str | list[str] | dict[str, dict[str, t.Any]] | None + config: str | list[str] | dict[str, dict[str, t.Any]] | None, ) -> dict[str, dict[str, t.Any]]: match config: case None: @@ -323,11 +315,12 @@ def add_prefix(polarion_type: str, prefix: str) -> str: def _filter_converter_config( - config: dict[str, dict[str, t.Any]] + config: dict[str, dict[str, t.Any]], ) -> dict[str, dict[str, t.Any]]: custom_converters = ( "include_pre_and_post_condition", "linked_text_as_description", + "add_requirements_text_grouped_by_type", "add_context_diagram", "add_tree_diagram", "add_jinja_fields", @@ -348,9 +341,7 @@ def _filter_converter_config( return filtered_config -def _filter_context_diagram_config( - config: dict[str, t.Any] -) -> dict[str, t.Any]: +def _filter_context_diagram_config(config: dict[str, t.Any]) -> dict[str, t.Any]: converted_filters = [] for filter_name in config.get("filters", []): try: @@ -381,6 +372,7 @@ def _filter_links( is_diagram_elements = capella_attr == DIAGRAM_ELEMENTS_SERIALIZER if ( capella_attr == DESCRIPTION_REFERENCE_SERIALIZER + or capella_attr == REQUIREMENT_REFERENCE_SERIALIZER or (is_diagram_elements and c_class == m.Diagram) or hasattr(c_class, capella_attr) ): diff --git a/capella2polarion/converters/data_session.py b/capella2polarion/converters/data_session.py index 012eb93..bb99272 100644 --- a/capella2polarion/converters/data_session.py +++ b/capella2polarion/converters/data_session.py @@ -1,6 +1,7 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 """A module to store data during the conversion process.""" + from __future__ import annotations import dataclasses @@ -20,6 +21,7 @@ class ConverterData: capella_element: m.ModelElement | m.Diagram work_item: dm.CapellaWorkItem | None = None description_references: list[str] = dataclasses.field(default_factory=list) + requirement_references: list[str] = dataclasses.field(default_factory=list) errors: set[str] = dataclasses.field(default_factory=set) diff --git a/capella2polarion/converters/element_converter.py b/capella2polarion/converters/element_converter.py index 237e8f7..202a3ba 100644 --- a/capella2polarion/converters/element_converter.py +++ b/capella2polarion/converters/element_converter.py @@ -1,6 +1,7 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 """Objects for serialization of capella objects to workitems.""" + from __future__ import annotations import collections @@ -40,8 +41,16 @@ def resolve_element_type(type_: str) -> str: return type_[0].lower() + type_[1:] +class SanitizedText(t.NamedTuple): + """Sanitized text from Capella.""" + + referenced_uuids: list[str] + text: markupsafe.Markup + attachments: list[data_model.Capella2PolarionAttachment] + + def _format_texts( - type_texts: dict[str, list[str]] + type_texts: dict[str, list[SanitizedText]], ) -> dict[str, dict[str, str]]: def _format(texts: list[str]) -> dict[str, str]: if len(texts) > 1: @@ -52,8 +61,8 @@ def _format(texts: list[str]) -> dict[str, str]: return {"type": "text/html", "value": text} requirement_types = {} - for typ, texts in type_texts.items(): - requirement_types[typ.lower()] = _format(texts) + for typ, stexts in type_texts.items(): + requirement_types[typ.lower()] = _format([s.text for s in stexts]) return requirement_types @@ -274,11 +283,7 @@ def _sanitize_linked_text(self, obj: m.ModelElement | m.Diagram) -> tuple[ def _sanitize_text( self, obj: m.ModelElement | m.Diagram, text: markupsafe.Markup | str - ) -> tuple[ - list[str], - markupsafe.Markup, - list[data_model.Capella2PolarionAttachment], - ]: + ) -> SanitizedText: referenced_uuids: list[str] = [] replaced_markup = RE_DESCR_LINK_PATTERN.sub( lambda match: self._replace_markup( @@ -334,7 +339,7 @@ def repair_images(node: etree._Element) -> None: repaired_markup = chelpers.process_html_fragments( replaced_markup, repair_images ) - return referenced_uuids, repaired_markup, attachments + return SanitizedText(referenced_uuids, repaired_markup, attachments) def _replace_markup( self, @@ -369,7 +374,13 @@ def _replace_markup( def _get_requirement_types_text( self, obj: m.ModelElement | m.Diagram - ) -> dict[str, dict[str, str]]: + ) -> tuple[ + list[str], + dict[str, dict[str, str]], + list[data_model.Capella2PolarionAttachment], + ]: + referenced_uuids: list[str] = [] + attachments: list[data_model.Capella2PolarionAttachment] = [] type_texts = collections.defaultdict(list) for req in getattr(obj, "requirements", []): if req is None: @@ -387,8 +398,13 @@ def _get_requirement_types_text( ) continue - type_texts[req.type.long_name].append(req.text) - return _format_texts(type_texts) + sanitized_text = self._sanitize_text( + obj, req.text or markupsafe.Markup("") + ) + referenced_uuids.extend(sanitized_text.referenced_uuids) + type_texts[req.type.long_name].append(sanitized_text) + attachments.extend(sanitized_text.attachments) + return referenced_uuids, _format_texts(type_texts), attachments # Serializer implementation starts below @@ -399,27 +415,39 @@ def __generic_work_item( ) -> data_model.CapellaWorkItem: obj = converter_data.capella_element raw_description = getattr(obj, "description", None) - uuids, value, attachments = self._sanitize_text( + sanitized_text = self._sanitize_text( obj, raw_description or markupsafe.Markup("") ) - converter_data.description_references = uuids - requirement_types = self._get_requirement_types_text(obj) - + converter_data.description_references = sanitized_text.referenced_uuids converter_data.work_item = data_model.CapellaWorkItem( id=work_item_id, type=converter_data.type_config.p_type, title=obj.name, uuid_capella=obj.uuid, - description=polarion_api.HtmlContent(value), + description=polarion_api.HtmlContent(sanitized_text.text), status="open", - **requirement_types, # type:ignore[arg-type] ) assert converter_data.work_item is not None - for attachment in attachments: + for attachment in sanitized_text.attachments: self._add_attachment(converter_data.work_item, attachment) return converter_data.work_item + def _add_requirements_text_grouped_by_type( + self, converter_data: data_session.ConverterData + ): + """Add requirements custom fields to work item.""" + obj = converter_data.capella_element + assert converter_data.work_item is not None + uuids, requirement_types, attachments = ( + self._get_requirement_types_text(obj) + ) + converter_data.work_item.additional_attributes |= requirement_types + converter_data.requirement_references = uuids + for attachment in attachments: + self._add_attachment(converter_data.work_item, attachment) + return converter_data.work_item + def _diagram( self, converter_data: data_session.ConverterData, diff --git a/capella2polarion/converters/link_converter.py b/capella2polarion/converters/link_converter.py index 2ae5a61..6c09e8d 100644 --- a/capella2polarion/converters/link_converter.py +++ b/capella2polarion/converters/link_converter.py @@ -1,6 +1,7 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 """Objects for synchronization of Capella model objects to Polarion.""" + from __future__ import annotations import collections.abc as cabc @@ -46,6 +47,7 @@ def __init__( self.serializers: dict[str, _Serializer] = { converter_config.DESCRIPTION_REFERENCE_SERIALIZER: self._handle_description_reference_links, # pylint: disable=line-too-long + converter_config.REQUIREMENT_REFERENCE_SERIALIZER: self._handle_requirement_reference_links, # pylint: disable=line-too-long converter_config.DIAGRAM_ELEMENTS_SERIALIZER: self._handle_diagram_reference_links, # pylint: disable=line-too-long } @@ -148,7 +150,28 @@ def _handle_description_reference_links( role_id: str, links: dict[str, polarion_api.WorkItemLink], ) -> list[polarion_api.WorkItemLink]: - refs = self.converter_session[obj.uuid].description_references + return self._handle_reference_links(obj, work_item_id, role_id, links) + + def _handle_requirement_reference_links( + self, + obj: m.ModelElement | m.Diagram, + work_item_id: str, + role_id: str, + links: dict[str, polarion_api.WorkItemLink], + ) -> list[polarion_api.WorkItemLink]: + return self._handle_reference_links( + obj, work_item_id, role_id, links, "requirement_references" + ) + + def _handle_reference_links( + self, + obj: m.ModelElement | m.Diagram, + work_item_id: str, + role_id: str, + links: dict[str, polarion_api.WorkItemLink], + reference_type: str = "description_references", + ) -> list[polarion_api.WorkItemLink]: + refs = getattr(self.converter_session[obj.uuid], reference_type) ref_set = set(self._get_work_item_ids(work_item_id, refs, role_id)) return self._create(work_item_id, role_id, ref_set, links) diff --git a/capella2polarion/data_model/work_items.py b/capella2polarion/data_model/work_items.py index ad99b6a..e088332 100644 --- a/capella2polarion/data_model/work_items.py +++ b/capella2polarion/data_model/work_items.py @@ -1,6 +1,7 @@ # Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 """Module providing the CapellaWorkItem class.""" + from __future__ import annotations import dataclasses @@ -183,3 +184,9 @@ def calculate_checksum(self) -> str: | dict(sorted(self._attachment_checksums.items())) ) return self.checksum + + def __eq__(self, other: object) -> bool: + """Compare two CapellaWorkItems.""" + if not isinstance(other, CapellaWorkItem): + return False + return self.calculate_checksum() == other.calculate_checksum() From effaef4067fd2904b8922e064851892b5029967d Mon Sep 17 00:00:00 2001 From: ewuerger Date: Thu, 21 Nov 2024 13:49:42 +0100 Subject: [PATCH 2/2] test: Test serialization --- tests/data/model/Melody Model Test.capella | 2 +- tests/test_elements.py | 581 ++++++++------------- 2 files changed, 215 insertions(+), 368 deletions(-) diff --git a/tests/data/model/Melody Model Test.capella b/tests/data/model/Melody Model Test.capella index 7ea8f5e..b7764ce 100644 --- a/tests/data/model/Melody Model Test.capella +++ b/tests/data/model/Melody Model Test.capella @@ -56,7 +56,7 @@ diff --git a/tests/test_elements.py b/tests/test_elements.py index ea9e294..5c91810 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -79,16 +79,22 @@ "This is a list\n\t
  • an unordered one
  • \n\n\n
      \n\t" "
    1. Ordered list
    2. \n\t
    3. Ok
    4. \n
    \n" ) +TEST_REQ_TEXT_1 = ( + "

    Test requirement 1 really l o n g text that is\xa0way too long to " + 'display here as that\xa0

    \n\n

    < > " \'

    \n\n
      \n\t' + "
    • This\xa0is a list
    • \n\t
    • an unordered one
    • \n
    \n\n
      " + "\n\t
    1. Ordered list
    2. \n\t
    3. Ok
    4. \n
    \n\n
    " + '
    \n' +) POLARION_ID_MAP = {f"uuid{i}": f"Obj-{i}" for i in range(3)} TEST_LOGICAL_COMPONENT = { "type": "logicalComponent", "title": "Hogwarts", "description": polarion_api.HtmlContent(markupsafe.Markup(TEST_DESCR)), } -TEST_CONDITION = { - "type": "text/html", - "value": '
    ', -} TEST_OPERATIONAL_CAPABILITY = { "type": "operationalCapability", "title": "Stay alive", @@ -173,9 +179,7 @@ def grouped_links_base_object( base_object: BaseObjectContainer, dummy_work_items: dict[str, data_model.CapellaWorkItem], ) -> GroupedLinksBaseObject: - config = converter_config.CapellaTypeConfig( - "fakeModelObject", links=[LINK_CONFIG] - ) + config = converter_config.CapellaTypeConfig("fakeModelObject", links=[LINK_CONFIG]) mock_model = mock.MagicMock() fake_2 = FakeModelObject("uuid2", "Fake 2") fake_1 = FakeModelObject("uuid1", "Fake 1") @@ -218,8 +222,8 @@ def diagr_base_object( ) } - base_object.pw.polarion_data_repo = ( - polarion_repo.PolarionDataRepository([work_item]) + base_object.pw.polarion_data_repo = polarion_repo.PolarionDataRepository( + [work_item] ) return base_object @@ -254,10 +258,7 @@ def test_delete_diagrams(diagr_base_object: BaseObjectContainer): pw.delete_orphaned_work_items(diagr_base_object.mc.converter_session) assert pw.project_client is not None assert pw.project_client.work_items.delete.call_count == 1 - assert ( - pw.project_client.work_items.delete.call_args[0][0][0].id - == "Diag-1" - ) + assert pw.project_client.work_items.delete.call_args[0][0][0].id == "Diag-1" assert pw.project_client.work_items.create.call_count == 0 @@ -313,9 +314,7 @@ def test_create_work_items_with_special_polarion_type( base_object.mc.converter_session = { uuid: data_session.ConverterData( "oa", - converter_config.CapellaTypeConfig( - _type[0].lower() + _type[1:] - ), + converter_config.CapellaTypeConfig(_type[0].lower() + _type[1:]), model.by_uuid(uuid), ) } @@ -353,9 +352,7 @@ def test_create_links_custom_resolver(base_object: BaseObjectContainer): polarion_role="description_reference", ) ] - base_object.mc.converter_session["uuid2"].description_references = [ - "uuid1" - ] + base_object.mc.converter_session["uuid2"].description_references = ["uuid1"] expected = polarion_api.WorkItemLink( "Obj-2", "Obj-1", @@ -404,15 +401,13 @@ def test_create_links_custom_exchanges_resolver( link_config = converter_config.LinkConfig( capella_attr="inputs.exchanges", polarion_role="input_exchanges" ) - base_object.mc.converter_session[function_uuid] = ( - data_session.ConverterData( - "fa", - converter_config.CapellaTypeConfig( - type(funtion_obj).__name__, links=[link_config] - ), - funtion_obj, - work_item_obj_1, - ) + base_object.mc.converter_session[function_uuid] = data_session.ConverterData( + "fa", + converter_config.CapellaTypeConfig( + type(funtion_obj).__name__, links=[link_config] + ), + funtion_obj, + work_item_obj_1, ) base_object.mc.converter_session[uuid] = data_session.ConverterData( "fa", @@ -449,9 +444,7 @@ def test_create_links_logs_error_when_no_uuid_is_found_on_value( ) no_uuid = FakeModelObject("") del no_uuid.uuid - base_object.mc.converter_session["uuid1"].capella_element.attribute = ( - no_uuid - ) + base_object.mc.converter_session["uuid1"].capella_element.attribute = no_uuid with caplog.at_level(logging.ERROR): link_serializer = link_converter.LinkSerializer( @@ -577,9 +570,7 @@ def test_create_links_with_new_links_and_errors( polarion_role="invalid_role", ), ] - base_object.mc.converter_session["uuid2"].description_references = [ - "uuid1" - ] + base_object.mc.converter_session["uuid2"].description_references = ["uuid1"] base_object.mc.converter_session["uuid2"].errors = set() expected_link = polarion_api.WorkItemLink( @@ -748,9 +739,7 @@ def test_update_work_items( uuid_capella="uuid1", status="open", title="Something", - description=polarion_api.HtmlContent( - markupsafe.Markup("Test") - ), + description=polarion_api.HtmlContent(markupsafe.Markup("Test")), ) ] polarion_api_get_all_work_items = mock.MagicMock() @@ -763,14 +752,14 @@ def test_update_work_items( base_object.pw.load_polarion_work_item_map() - base_object.mc.converter_session["uuid1"].work_item = ( - data_model.CapellaWorkItem( - id="Obj-1", - uuid_capella="uuid1", - title="Fake 1", - type="type", - description=polarion_api.HtmlContent(markupsafe.Markup("")), - ) + base_object.mc.converter_session[ + "uuid1" + ].work_item = data_model.CapellaWorkItem( + id="Obj-1", + uuid_capella="uuid1", + title="Fake 1", + type="type", + description=polarion_api.HtmlContent(markupsafe.Markup("")), ) del base_object.mc.converter_session["uuid2"] @@ -783,30 +772,17 @@ def test_update_work_items( get_work_item_mock, ) - base_object.pw.compare_and_update_work_items( - base_object.mc.converter_session - ) - assert ( - base_object.pw.project_client.work_items.links.get_all.call_count - == 0 - ) - assert ( - base_object.pw.project_client.work_items.links.delete.call_count - == 0 - ) - assert ( - base_object.pw.project_client.work_items.links.create.call_count - == 0 - ) + base_object.pw.compare_and_update_work_items(base_object.mc.converter_session) + assert base_object.pw.project_client.work_items.links.get_all.call_count == 0 + assert base_object.pw.project_client.work_items.links.delete.call_count == 0 + assert base_object.pw.project_client.work_items.links.create.call_count == 0 assert base_object.pw.project_client.work_items.update.call_count == 1 assert base_object.pw.project_client.work_items.get.call_count == 1 assert ( base_object.pw.project_client.work_items.attachments.get_all.call_count # pylint: disable=line-too-long == 0 ) - work_item = base_object.pw.project_client.work_items.update.call_args[ - 0 - ][0] + work_item = base_object.pw.project_client.work_items.update.call_args[0][0] assert isinstance(work_item, data_model.CapellaWorkItem) assert work_item.id == "Obj-1" assert work_item.title == "Fake 1" @@ -839,17 +815,15 @@ def test_update_deleted_work_item( base_object.pw.load_polarion_work_item_map() - base_object.mc.converter_session["uuid1"].work_item = ( - data_model.CapellaWorkItem( - id="Obj-1", - type="type", - uuid_capella="uuid1", - status="open", - title="Something", - description=polarion_api.HtmlContent( - markupsafe.Markup("Test") - ), - ) + base_object.mc.converter_session[ + "uuid1" + ].work_item = data_model.CapellaWorkItem( + id="Obj-1", + type="type", + uuid_capella="uuid1", + status="open", + title="Something", + description=polarion_api.HtmlContent(markupsafe.Markup("Test")), ) del base_object.mc.converter_session["uuid2"] @@ -861,22 +835,14 @@ def test_update_deleted_work_item( "get", get_work_item_mock, ) - base_object.pw.delete_orphaned_work_items( - base_object.mc.converter_session - ) + base_object.pw.delete_orphaned_work_items(base_object.mc.converter_session) assert base_object.pw.project_client.work_items.update.called is False - base_object.pw.create_missing_work_items( - base_object.mc.converter_session - ) + base_object.pw.create_missing_work_items(base_object.mc.converter_session) assert base_object.pw.project_client.work_items.create.called is False - base_object.pw.compare_and_update_work_items( - base_object.mc.converter_session - ) - work_item = base_object.pw.project_client.work_items.update.call_args[ - 0 - ][0] + base_object.pw.compare_and_update_work_items(base_object.mc.converter_session) + work_item = base_object.pw.project_client.work_items.update.call_args[0][0] assert isinstance(work_item, data_model.CapellaWorkItem) assert work_item.status == "open" @@ -896,13 +862,9 @@ def test_create_new_work_item(base_object: BaseObjectContainer): ) base_object.pw.load_polarion_work_item_map() - base_object.pw.create_missing_work_items( - base_object.mc.converter_session - ) + base_object.pw.create_missing_work_items(base_object.mc.converter_session) - polarion_api_create_work_items = ( - base_object.pw.project_client.work_items.create - ) + polarion_api_create_work_items = base_object.pw.project_client.work_items.create assert polarion_api_create_work_items.call_count == 1 assert len(polarion_api_create_work_items.call_args[0][0]) == 1 @@ -911,9 +873,7 @@ def test_create_new_work_item(base_object: BaseObjectContainer): assert work_item.id == "AUTO-0" assert len(base_object.pw.polarion_data_repo) == 2 assert ( - base_object.pw.polarion_data_repo.get_work_item_by_capella_uuid( - "uuid2" - ).id + base_object.pw.polarion_data_repo.get_work_item_by_capella_uuid("uuid2").id == "AUTO-0" ) @@ -935,9 +895,7 @@ def test_update_work_items_filters_work_items_with_same_checksum( del base_object.mc.converter_session["uuid2"] - base_object.pw.compare_and_update_work_items( - base_object.mc.converter_session - ) + base_object.pw.compare_and_update_work_items(base_object.mc.converter_session) assert base_object.pw.project_client.work_items.update.call_count == 0 @@ -960,36 +918,25 @@ def test_update_work_items_same_checksum_force( del base_object.mc.converter_session["uuid2"] - base_object.pw.compare_and_update_work_items( - base_object.mc.converter_session - ) + base_object.pw.compare_and_update_work_items(base_object.mc.converter_session) assert base_object.pw.project_client.work_items.update.call_count == 1 @staticmethod def test_update_links_with_no_elements(base_object: BaseObjectContainer): - base_object.pw.polarion_data_repo = ( - polarion_repo.PolarionDataRepository() - ) + base_object.pw.polarion_data_repo = polarion_repo.PolarionDataRepository() base_object.mc.converter_session = {} - base_object.pw.compare_and_update_work_items( - base_object.mc.converter_session - ) + base_object.pw.compare_and_update_work_items(base_object.mc.converter_session) - assert ( - base_object.pw.project_client.work_items.links.get_all.call_count - == 0 - ) + assert base_object.pw.project_client.work_items.links.get_all.call_count == 0 @staticmethod def test_update_links(base_object: BaseObjectContainer): link = polarion_api.WorkItemLink( "Obj-1", "Obj-2", "attribute", True, "project_id" ) - work_item = ( - base_object.pw.polarion_data_repo.get_work_item_by_capella_uuid( - "uuid1" - ) + work_item = base_object.pw.polarion_data_repo.get_work_item_by_capella_uuid( + "uuid1" ) work_item.linked_work_items = [link] base_object.pw.polarion_data_repo.update_work_items( @@ -1002,13 +949,13 @@ def test_update_links(base_object: BaseObjectContainer): ) ] ) - base_object.mc.converter_session["uuid2"].work_item = ( - data_model.CapellaWorkItem( - id="Obj-2", - uuid_capella="uuid2", - status="open", - type="fakeModelObject", - ) + base_object.mc.converter_session[ + "uuid2" + ].work_item = data_model.CapellaWorkItem( + id="Obj-2", + uuid_capella="uuid2", + status="open", + type="fakeModelObject", ) for data in base_object.mc.converter_session.values(): data.type_config.links[0].polarion_role = "attribute" @@ -1025,15 +972,11 @@ def test_update_links(base_object: BaseObjectContainer): generate_grouped_links_custom_fields=True, ) - work_item_1 = ( - base_object.pw.polarion_data_repo.get_work_item_by_capella_uuid( - "uuid1" - ) + work_item_1 = base_object.pw.polarion_data_repo.get_work_item_by_capella_uuid( + "uuid1" ) - work_item_2 = ( - base_object.pw.polarion_data_repo.get_work_item_by_capella_uuid( - "uuid2" - ) + work_item_2 = base_object.pw.polarion_data_repo.get_work_item_by_capella_uuid( + "uuid2" ) work_item_1.linked_work_items_truncated = True work_item_2.linked_work_items_truncated = True @@ -1043,34 +986,21 @@ def test_update_links(base_object: BaseObjectContainer): work_item_2, ) - base_object.pw.compare_and_update_work_items( - base_object.mc.converter_session - ) + base_object.pw.compare_and_update_work_items(base_object.mc.converter_session) links = ( base_object.pw.project_client.work_items.links.get_all.call_args_list # pylint: disable=line-too-long ) - assert ( - base_object.pw.project_client.work_items.links.get_all.call_count - == 2 - ) + assert base_object.pw.project_client.work_items.links.get_all.call_count == 2 assert [links[0][0][0], links[1][0][0]] == ["Obj-1", "Obj-2"] - new_links = ( - base_object.pw.project_client.work_items.links.create.call_args[0][ - 0 - ] - ) - assert ( - base_object.pw.project_client.work_items.links.create.call_count - == 1 - ) + new_links = base_object.pw.project_client.work_items.links.create.call_args[0][ + 0 + ] + assert base_object.pw.project_client.work_items.links.create.call_count == 1 assert new_links == [expected_new_link] - assert ( - base_object.pw.project_client.work_items.links.delete.call_count - == 1 - ) - assert base_object.pw.project_client.work_items.links.delete.call_args[ + assert base_object.pw.project_client.work_items.links.delete.call_count == 1 + assert base_object.pw.project_client.work_items.links.delete.call_args[0][ 0 - ][0] == [link] + ] == [link] @staticmethod def test_patch_work_item_grouped_links( @@ -1087,20 +1017,18 @@ def test_patch_work_item_grouped_links( ) for work_item in dummy_work_items.values() } - base_object.pw.polarion_data_repo = ( - polarion_repo.PolarionDataRepository( - [ - data_model.CapellaWorkItem( - id="Obj-0", uuid_capella="uuid0", status="open" - ), - data_model.CapellaWorkItem( - id="Obj-1", uuid_capella="uuid1", status="open" - ), - data_model.CapellaWorkItem( - id="Obj-2", uuid_capella="uuid2", status="open" - ), - ] - ) + base_object.pw.polarion_data_repo = polarion_repo.PolarionDataRepository( + [ + data_model.CapellaWorkItem( + id="Obj-0", uuid_capella="uuid0", status="open" + ), + data_model.CapellaWorkItem( + id="Obj-1", uuid_capella="uuid1", status="open" + ), + data_model.CapellaWorkItem( + id="Obj-2", uuid_capella="uuid2", status="open" + ), + ] ) mock_create_links = mock.MagicMock() monkeypatch.setattr( @@ -1142,9 +1070,7 @@ def mock_back_link(converter_data, back_links): base_object.pw.polarion_data_repo, generate_grouped_links_custom_fields=True, ) - base_object.pw.compare_and_update_work_items( - base_object.mc.converter_session - ) + base_object.pw.compare_and_update_work_items(base_object.mc.converter_session) update_work_item_calls = ( base_object.pw.project_client.work_items.update.call_args_list ) @@ -1152,18 +1078,9 @@ def mock_back_link(converter_data, back_links): mock_grouped_links_calls = mock_grouped_links.call_args_list assert len(mock_grouped_links_calls) == 3 assert mock_grouped_links_reverse.call_count == 3 - assert ( - mock_grouped_links_calls[0][0][0].work_item - == dummy_work_items["uuid0"] - ) - assert ( - mock_grouped_links_calls[1][0][0].work_item - == dummy_work_items["uuid1"] - ) - assert ( - mock_grouped_links_calls[2][0][0].work_item - == dummy_work_items["uuid2"] - ) + assert mock_grouped_links_calls[0][0][0].work_item == dummy_work_items["uuid0"] + assert mock_grouped_links_calls[1][0][0].work_item == dummy_work_items["uuid1"] + assert mock_grouped_links_calls[2][0][0].work_item == dummy_work_items["uuid2"] work_item_0 = update_work_item_calls[0][0][0] del work_item_0.additional_attributes["checksum"] work_item_1 = update_work_item_calls[1][0][0] @@ -1207,9 +1124,7 @@ def test_maintain_grouped_links_attributes( mock_model, ) for work_item in dummy_work_items.values(): - converter_data = data_session.ConverterData( - "test", config, [], work_item - ) + converter_data = data_session.ConverterData("test", config, [], work_item) link_serializer._link_field_groups["attribute"] = ( work_item.linked_work_items ) @@ -1219,15 +1134,11 @@ def test_maintain_grouped_links_attributes( del dummy_work_items["uuid2"].additional_attributes["uuid_capella"] assert ( - dummy_work_items["uuid0"].additional_attributes.pop("attribute")[ - "value" - ] + dummy_work_items["uuid0"].additional_attributes.pop("attribute")["value"] == HTML_LINK_0["attribute"] ) assert ( - dummy_work_items["uuid1"].additional_attributes.pop("attribute")[ - "value" - ] + dummy_work_items["uuid1"].additional_attributes.pop("attribute")["value"] == HTML_LINK_1["attribute"] ) assert dummy_work_items["uuid0"].additional_attributes == {} @@ -1270,9 +1181,7 @@ def test_maintain_grouped_links_attributes_with_role_prefix( ) for work_item in dummy_work_items.values(): - converter_data = data_session.ConverterData( - "test", config, [], work_item - ) + converter_data = data_session.ConverterData("test", config, [], work_item) if work_item.uuid_capella == "uuid0": link_serializer._link_field_groups["attribute"] = ( work_item.linked_work_items @@ -1306,12 +1215,8 @@ def test_grouped_links_attributes_different_link_field_in_config( reverse_field="attribute1_reverse", ) ) - converter_data_1.capella_element.attribute = ( - converter_data_2.capella_element - ) - converter_data_1.capella_element.attribute1 = ( - converter_data_2.capella_element - ) + converter_data_1.capella_element.attribute = converter_data_2.capella_element + converter_data_1.capella_element.attribute1 = converter_data_2.capella_element expected_html = ( "
    • " 'hehe' - "
      " + '
      hehe' "
      " ), }, - "postCondition": { - "type": "text/html", - "value": '
      ', - }, + "postCondition": {"type": "text/html", "value": ""}, }, }, id="scenario", @@ -1752,18 +1597,10 @@ def test_diagram(model: capellambse.MelodyModel): "type": "capabilityRealization", "title": "Capability Realization", "uuid_capella": TEST_CAP_REAL, - "description": polarion_api.HtmlContent( - markupsafe.Markup("") - ), + "description": polarion_api.HtmlContent(markupsafe.Markup("")), "additional_attributes": { - "preCondition": { - "type": "text/html", - "value": '
      ', - }, - "postCondition": { - "type": "text/html", - "value": '
      ', - }, + "preCondition": {"type": "text/html", "value": ""}, + "postCondition": {"type": "text/html", "value": ""}, }, }, id="capabilityRealization", @@ -1805,19 +1642,9 @@ def test_generic_work_item( serializer = element_converter.CapellaWorkItemSerializer( model, polarion_repo.PolarionDataRepository( - [ - data_model.CapellaWorkItem( - id="TEST", uuid_capella=TEST_E_UUID - ) - ] + [data_model.CapellaWorkItem(id="TEST", uuid_capella=TEST_E_UUID)] ), - { - uuid: data_session.ConverterData( - layer, - type_config, - obj, - ) - }, + {uuid: data_session.ConverterData(layer, type_config, obj)}, False, ) @@ -1829,6 +1656,49 @@ def test_generic_work_item( assert work_item == data_model.CapellaWorkItem(**expected) assert status == "open" + @staticmethod + def test_add_requirements_text_grouped_by_type( + model: capellambse.MelodyModel, + ): + expected = { + **TEST_LOGICAL_COMPONENT, + "uuid_capella": TEST_ELEMENT_UUID, + "reqtype": { + "type": "text/html", + "value": markupsafe.Markup(TEST_REQ_TEXT_1), + }, + } + obj = model.by_uuid(TEST_ELEMENT_UUID) + type_config = converter_config.CapellaTypeConfig( + "logicalComponent", + "add_requirements_text_grouped_by_type", + ) + session = { + TEST_ELEMENT_UUID: data_session.ConverterData("sa", type_config, obj) + } + work_items = [ + data_model.CapellaWorkItem( + id="TEST", uuid_capella="ceffa011-7b66-4b3c-9885-8e075e312ffa" + ), + data_model.CapellaWorkItem( + id="TEST1", uuid_capella="00e7b925-cf4c-4cb0-929e-5409a1cd872b" + ), + ] + serializer = element_converter.CapellaWorkItemSerializer( + model, + polarion_repo.PolarionDataRepository(work_items), + session, + False, + ) + + work_item = serializer.serialize(TEST_ELEMENT_UUID) + assert work_item is not None + status = work_item.status + work_item.status = None + + assert work_item == data_model.CapellaWorkItem(**expected) + assert status == "open" + @staticmethod def test_add_context_diagram(model: capellambse.MelodyModel): uuid = "11906f7b-3ae9-4343-b998-95b170be2e2b" @@ -1889,9 +1759,7 @@ def test_add_context_diagram_with_params( uuid = "00e7b925-cf4c-4cb0-929e-5409a1cd872b" fnc = model.by_uuid(uuid) config = {"add_context_diagram": {"filters": [context_diagram_filter]}} - type_config = converter_config.CapellaTypeConfig( - "systemFunction", config, [] - ) + type_config = converter_config.CapellaTypeConfig("systemFunction", config, []) expected_filter = getattr(filters, context_diagram_filter) monkeypatch.setattr( element_converter.CapellaWorkItemSerializer, @@ -1917,23 +1785,15 @@ def test_add_tree_view_with_params( ): cls = model.by_uuid("c710f1c2-ede6-444e-9e2b-0ff30d7fd040") config = {"add_tree_diagram": {"render_params": {"depth": 1}}} - type_config = converter_config.CapellaTypeConfig( - "systemFunction", config, [] - ) + type_config = converter_config.CapellaTypeConfig("systemFunction", config, []) serializer = element_converter.CapellaWorkItemSerializer( model, polarion_repo.PolarionDataRepository(), - { - TEST_OCAP_UUID: data_session.ConverterData( - "pa", type_config, cls - ) - }, + {TEST_OCAP_UUID: data_session.ConverterData("pa", type_config, cls)}, True, ) - with mock.patch.object( - context.ContextDiagram, "render" - ) as wrapped_render: + with mock.patch.object(context.ContextDiagram, "render") as wrapped_render: wis = serializer.serialize_all() _ = wis[0].attachments[0].content_bytes @@ -1981,11 +1841,7 @@ def test_multiple_serializers(model: capellambse.MelodyModel, prefix: str): serializer = element_converter.CapellaWorkItemSerializer( model, polarion_repo.PolarionDataRepository(), - { - TEST_OCAP_UUID: data_session.ConverterData( - "pa", type_config, cap - ) - }, + {TEST_OCAP_UUID: data_session.ConverterData("pa", type_config, cap)}, True, ) @@ -2010,20 +1866,6 @@ def test_multiple_serializers(model: capellambse.MelodyModel, prefix: str): @pytest.mark.parametrize( "layer,uuid,expected", [ - pytest.param( - "la", - TEST_ELEMENT_UUID, - { - **TEST_LOGICAL_COMPONENT, - "type": "_C2P_logicalComponent", - "uuid_capella": TEST_ELEMENT_UUID, - "reqtype": { - "type": "text/html", - "value": markupsafe.Markup(TEST_REQ_TEXT), - }, - }, - id="logicalComponent", - ), pytest.param( "oa", TEST_OCAP_UUID, @@ -2032,12 +1874,25 @@ def test_multiple_serializers(model: capellambse.MelodyModel, prefix: str): "type": "_C2P_operationalCapability", "uuid_capella": TEST_OCAP_UUID, "additional_attributes": { - "preCondition": TEST_CONDITION, - "postCondition": TEST_CONDITION, + "preCondition": {"type": "text/html", "value": ""}, + "postCondition": {"type": "text/html", "value": ""}, }, }, id="operationalCapability", ), + pytest.param( + "oa", + TEST_WE_UUID, + { + "type": "_C2P_entity", + "title": "Environment", + "uuid_capella": TEST_WE_UUID, + "description": polarion_api.HtmlContent( + markupsafe.Markup(TEST_WE_DESCR.replace("TEST", "_C2P_TEST")) + ), + }, + id="entity", + ), ], ) def test_generic_work_item_with_type_prefix( @@ -2060,7 +1915,9 @@ def test_generic_work_item_with_type_prefix( type_config = config.get_type_config(layer, c_type, **attributes) assert type_config is not None type_config.p_type = f"{prefix}_{type_config.p_type}" - ework_item = data_model.CapellaWorkItem(id=f"{prefix}_TEST") + ework_item = data_model.CapellaWorkItem( + id=f"{prefix}_TEST", uuid_capella=TEST_E_UUID + ) serializer = element_converter.CapellaWorkItemSerializer( model, polarion_repo.PolarionDataRepository([ework_item]), @@ -2077,8 +1934,7 @@ def test_generic_work_item_with_type_prefix( @staticmethod def test_read_config_context_diagram_with_params(): expected_filter = ( - "capellambse_context_diagrams-show.exchanges.or.exchange.items." - "filter" + "capellambse_context_diagrams-show.exchanges.or.exchange.items." "filter" ) config = converter_config.ConverterConfig() with open(TEST_MODEL_ELEMENTS_CONFIG, "r", encoding="utf8") as f: @@ -2113,18 +1969,11 @@ def test_read_config_tree_view_with_params( serializer = element_converter.CapellaWorkItemSerializer( model, polarion_repo.PolarionDataRepository(), - { - TEST_OCAP_UUID: data_session.ConverterData( - "pa", type_config, cap - ) - }, + {TEST_OCAP_UUID: data_session.ConverterData("pa", type_config, cap)}, True, ) - with mock.patch.object( - context.ContextDiagram, "render" - ) as wrapped_render: - + with mock.patch.object(context.ContextDiagram, "render") as wrapped_render: wis = serializer.serialize_all() _ = wis[0].attachments[0].content_bytes @@ -2154,9 +2003,7 @@ def test_read_config_links(caplog: pytest.LogCaptureFixture): if link.capella_attr == "parent" ) assert caplog.record_tuples[0] + caplog.record_tuples[1] == expected - assert ( - system_fnc_config := config.get_type_config("sa", "SystemFunction") - ) + assert (system_fnc_config := config.get_type_config("sa", "SystemFunction")) assert system_fnc_config.links[0] == converter_config.LinkConfig( capella_attr="inputs.exchanges", polarion_role="input_exchanges",