diff --git a/capella2polarion/__main__.py b/capella2polarion/__main__.py
index 77a136d7..a9e93ea6 100644
--- a/capella2polarion/__main__.py
+++ b/capella2polarion/__main__.py
@@ -167,13 +167,10 @@ def render_documents(
overwrite_layouts,
)
- new_documents, updated_documents, work_items = renderer.render_documents(
- configs, documents
- )
-
- polarion_worker.post_documents(new_documents)
- polarion_worker.update_documents(updated_documents)
- polarion_worker.update_work_items(work_items)
+ projects_document_data = renderer.render_documents(configs, documents)
+ for project, project_data in projects_document_data.items():
+ polarion_worker.create_documents(project_data.new_docs, project)
+ polarion_worker.update_documents(project_data.updated_docs, project)
if __name__ == "__main__":
diff --git a/capella2polarion/connectors/polarion_repo.py b/capella2polarion/connectors/polarion_repo.py
index 7acd47d0..f40f70ca 100644
--- a/capella2polarion/connectors/polarion_repo.py
+++ b/capella2polarion/connectors/polarion_repo.py
@@ -6,6 +6,7 @@
import collections.abc as cabc
import bidict
+import polarion_rest_api_client as polarion_api
from capella2polarion import data_models
@@ -114,3 +115,15 @@ def remove_work_items_by_capella_uuid(self, uuids: cabc.Iterable[str]):
for uuid in uuids:
del self._work_items[uuid]
del self._id_mapping[uuid]
+
+
+DocumentRepository = dict[
+ tuple[str | None, str, str],
+ tuple[polarion_api.Document | None, list[polarion_api.WorkItem]],
+]
+"""A dict providing a mapping for documents and their text workitems.
+
+It has (project, space, name) of the document as key and (document,
+workitems) as value. The project can be None and the None value means
+that the document is in the same project as the model sync work items.
+"""
diff --git a/capella2polarion/connectors/polarion_worker.py b/capella2polarion/connectors/polarion_worker.py
index b60ae11d..e6b22371 100644
--- a/capella2polarion/connectors/polarion_worker.py
+++ b/capella2polarion/connectors/polarion_worker.py
@@ -25,6 +25,15 @@
int: 0,
bool: False,
}
+WORK_ITEMS_IN_PROJECT_QUERY = (
+ "SQL:(SELECT item.* FROM POLARION.WORKITEM item, POLARION.MODULE doc, "
+ "POLARION.PROJECT proj WHERE proj.C_ID = '{project}' AND "
+ "doc.FK_PROJECT = proj.C_PK AND doc.C_ID = '{doc_name}' AND "
+ "doc.C_MODULEFOLDER = '{doc_folder}' AND item.C_TYPE = '{wi_type}' AND "
+ "EXISTS (SELECT rel1.* FROM POLARION.REL_MODULE_WORKITEM rel1 WHERE "
+ "rel1.FK_URI_MODULE = doc.C_URI AND rel1.FK_URI_WORKITEM = item.C_URI))"
+)
+"""An SQL query to get work items which are inserted in a given document."""
class PolarionWorkerParams:
@@ -68,19 +77,43 @@ def __init__(
"Polarion PAT (Personal Access Token) parameter "
"is not a set properly."
)
- self.client = polarion_api.OpenAPIPolarionProjectClient(
- self.polarion_params.project_id,
- self.polarion_params.delete_work_items,
+
+ self.polarion_client = polarion_api.PolarionClient(
polarion_api_endpoint=f"{self.polarion_params.url}/rest/v1",
polarion_access_token=self.polarion_params.private_access_token,
- custom_work_item=data_models.CapellaWorkItem,
+ )
+ self.project_client = self.polarion_client.generate_project_client(
+ project_id=self.polarion_params.project_id,
+ delete_status=(
+ "deleted" if self.polarion_params.delete_work_items else None
+ ),
add_work_item_checksum=True,
)
+ self._additional_clients: dict[str, polarion_api.ProjectClient] = {}
self.check_client()
+ def _get_client(
+ self, project_id: str | None
+ ) -> polarion_api.ProjectClient:
+ if project_id is None:
+ return self.project_client
+ if project_id in self._additional_clients:
+ return self._additional_clients[project_id]
+ client = self.polarion_client.generate_project_client(
+ project_id=project_id,
+ delete_status=(
+ "deleted" if self.polarion_params.delete_work_items else None
+ ),
+ add_work_item_checksum=True,
+ )
+ if not client.exists():
+ raise KeyError(f"Miss Polarion project with id {project_id}")
+ self._additional_clients[project_id] = client
+ return client
+
def check_client(self) -> None:
"""Instantiate the polarion client as member."""
- if not self.client.project_exists():
+ if not self.project_client.exists():
raise KeyError(
"Miss Polarion project with id "
f"{self.polarion_params.project_id}"
@@ -88,9 +121,10 @@ def check_client(self) -> None:
def load_polarion_work_item_map(self):
"""Return a map from Capella UUIDs to Polarion work items."""
- work_items = self.client.get_all_work_items(
+ work_items = self.project_client.work_items.get_all(
"HAS_VALUE:uuid_capella",
- {"workitems": "id,uuid_capella,checksum,status,type"},
+ fields={"workitems": "id,uuid_capella,checksum,status,type"},
+ work_item_cls=data_models.CapellaWorkItem,
)
self.polarion_data_repo.update_work_items(work_items)
@@ -103,28 +137,27 @@ def delete_orphaned_work_items(
are marked as ``to be deleted`` via the status attribute.
"""
- def serialize_for_delete(uuid: str) -> str:
- work_item_id, _ = self.polarion_data_repo[uuid]
- logger.info("Delete work item %r...", work_item_id)
- return work_item_id
-
existing_work_items = {
uuid
for uuid, _, work_item in self.polarion_data_repo.items()
if work_item.status != "deleted"
}
uuids: set[str] = existing_work_items - set(converter_session)
- work_item_ids = [serialize_for_delete(uuid) for uuid in uuids]
- if work_item_ids:
+ work_items: list[data_models.CapellaWorkItem] = []
+ for uuid in uuids:
+ if wi := self.polarion_data_repo.get_work_item_by_capella_uuid(
+ uuid
+ ):
+ logger.info("Delete work item %r...", wi.id)
+ work_items.append(wi)
try:
- self.client.delete_work_items(work_item_ids)
- self.polarion_data_repo.remove_work_items_by_capella_uuid(
- uuids
- )
+ self.project_client.work_items.delete(work_items)
except polarion_api.PolarionApiException as error:
logger.error("Deleting work items failed. %s", error.args[0])
raise error
+ self.polarion_data_repo.remove_work_items_by_capella_uuid(uuids)
+
def create_missing_work_items(
self, converter_session: data_session.ConverterSession
) -> None:
@@ -146,7 +179,7 @@ def create_missing_work_items(
logger.info("Create work item for %r...", work_item.title)
if missing_work_items:
try:
- self.client.create_work_items(missing_work_items)
+ self.project_client.work_items.create(missing_work_items)
self.polarion_data_repo.update_work_items(missing_work_items)
except polarion_api.PolarionApiException as error:
logger.error("Creating work items failed. %s", error.args[0])
@@ -184,18 +217,22 @@ def compare_and_update_work_item(
work_item_changed = new_work_item_check_sum != old_work_item_check_sum
try:
if work_item_changed or self.force_update:
- old = self.client.get_work_item(old.id)
+ old = self.project_client.work_items.get(
+ old.id, work_item_cls=data_models.CapellaWorkItem
+ )
if old.attachments:
old_attachments = (
- self.client.get_all_work_item_attachments(
+ self.project_client.work_items.attachments.get_all(
work_item_id=old.id
)
)
else:
old_attachments = []
else:
- old_attachments = self.client.get_all_work_item_attachments(
- work_item_id=old.id
+ old_attachments = (
+ self.project_client.work_items.attachments.get_all(
+ work_item_id=old.id
+ )
)
if old_attachments or new.attachments:
work_item_changed |= self.update_attachments(
@@ -221,8 +258,8 @@ def compare_and_update_work_item(
del old.additional_attributes["uuid_capella"]
if old.linked_work_items_truncated:
- old.linked_work_items = self.client.get_all_work_item_links(
- old.id
+ old.linked_work_items = (
+ self.project_client.work_items.links.get_all(old.id)
)
# Type will only be updated, if set and should be used carefully
@@ -256,7 +293,7 @@ def compare_and_update_work_item(
new.title = None
try:
- self.client.update_work_item(new)
+ self.project_client.work_items.update(new)
if delete_links:
id_list_str = ", ".join(delete_links.keys())
logger.info(
@@ -265,7 +302,9 @@ def compare_and_update_work_item(
new.type,
new.title,
)
- self.client.delete_work_item_links(list(delete_links.values()))
+ self.project_client.work_items.links.delete(
+ list(delete_links.values())
+ )
if create_links:
id_list_str = ", ".join(create_links.keys())
@@ -275,7 +314,9 @@ def compare_and_update_work_item(
new.type,
new.title,
)
- self.client.create_work_item_links(list(create_links.values()))
+ self.project_client.work_items.links.create(
+ list(create_links.values())
+ )
except polarion_api.PolarionApiException as error:
logger.error(
@@ -346,12 +387,12 @@ def update_attachments(
attachment.file_name,
attachment.id,
)
- self.client.delete_work_item_attachment(attachment)
+ self.project_client.work_items.attachments.delete(attachment)
old_attachment_file_names = set(old_attachment_dict)
new_attachment_file_names = set(new_attachment_dict)
for file_name in old_attachment_file_names - new_attachment_file_names:
- self.client.delete_work_item_attachment(
+ self.project_client.work_items.attachments.delete(
old_attachment_dict[file_name]
)
@@ -361,7 +402,7 @@ def update_attachments(
new_attachment_file_names - old_attachment_file_names,
)
):
- self.client.create_work_item_attachments(new_attachments)
+ self.project_client.work_items.attachments.create(new_attachments)
created = True
attachments_for_update = {}
@@ -386,7 +427,7 @@ def update_attachments(
):
continue
- self.client.update_work_item_attachment(attachment)
+ self.project_client.work_items.attachments.update(attachment)
return created
@staticmethod
@@ -425,20 +466,76 @@ def compare_and_update_work_items(
if uuid in self.polarion_data_repo and data.work_item is not None:
self.compare_and_update_work_item(data)
- def post_documents(self, documents: list[polarion_api.Document]):
- """Create new documents."""
- self.client.project_client.documents.create(documents)
+ def create_documents(
+ self,
+ document_datas: list[data_models.DocumentData],
+ document_project: str | None = None,
+ ):
+ """Create new documents.
+
+ Notes
+ -----
+ If the ``document_project`` is ``None`` the default client is
+ taken.
+ """
+ client = self._get_client(document_project)
+ documents, _ = self._process_document_datas(client, document_datas)
+
+ client.documents.create(documents)
- def update_documents(self, documents: list[polarion_api.Document]):
- """Update existing documents."""
- self.client.project_client.documents.update(documents)
+ def update_documents(
+ self,
+ document_datas: list[data_models.DocumentData],
+ document_project: str | None = None,
+ ):
+ """Update existing documents.
+
+ Notes
+ -----
+ If the ``document_project`` is ``None`` the default client is
+ taken.
+ """
+ client = self._get_client(document_project)
+ documents, headings = self._process_document_datas(
+ client, document_datas
+ )
+
+ client.work_items.update(headings)
+ client.documents.update(documents)
+
+ def _process_document_datas(
+ self,
+ client: polarion_api.ProjectClient,
+ document_datas: list[data_models.DocumentData],
+ ):
+ documents: list[polarion_api.Document] = []
+ headings: list[polarion_api.WorkItem] = []
+ for document_data in document_datas:
+ headings.extend(document_data.headings)
+ documents.append(document_data.document)
+ if document_data.text_work_item_provider.new_text_work_items:
+ self._create_and_update_text_work_items(
+ document_data.text_work_item_provider.new_text_work_items,
+ client,
+ )
+ document_data.text_work_item_provider.insert_text_work_items(
+ document_data.document,
+ )
+ return documents, headings
def get_document(
- self, space: str, name: str
+ self, space: str, name: str, document_project: str | None = None
) -> polarion_api.Document | None:
- """Get a document from polarion and return None if not found."""
+ """Get a document from polarion and return None if not found.
+
+ Notes
+ -----
+ If the ``document_project`` is ``None`` the default client is
+ taken.
+ """
+ client = self._get_client(document_project)
try:
- return self.client.project_client.documents.get(
+ return client.documents.get(
space, name, fields={"documents": "@all"}
)
except polarion_api.PolarionApiBaseException as e:
@@ -446,15 +543,42 @@ def get_document(
return None
raise e
- def update_work_items(self, work_items: list[polarion_api.WorkItem]):
- """Update the given workitems without any additional checks."""
- self.client.project_client.work_items.update(work_items)
-
def load_polarion_documents(
- self, document_paths: t.Iterable[tuple[str, str]]
- ) -> dict[tuple[str, str], polarion_api.Document | None]:
- """Load the given document references from Polarion."""
+ self,
+ document_infos: t.Iterable[data_models.DocumentInfo],
+ ) -> polarion_repo.DocumentRepository:
+ """Load the documents referenced and text work items from Polarion."""
return {
- (space, name): self.get_document(space, name)
- for space, name in document_paths
+ (di.project_id, di.module_folder, di.module_name): (
+ self.get_document(
+ di.module_folder, di.module_name, di.project_id
+ ),
+ self._get_client(di.project_id).work_items.get_all(
+ WORK_ITEMS_IN_PROJECT_QUERY.format(
+ project=di.project_id
+ or self.polarion_params.project_id,
+ doc_folder=di.module_folder,
+ doc_name=di.module_name,
+ wi_type=di.text_work_item_type,
+ ),
+ fields={"workitems": f"id,{di.text_work_item_id_field}"},
+ ),
+ )
+ for di in document_infos
}
+
+ def _create_and_update_text_work_items(
+ self,
+ work_items: dict[str, polarion_api.WorkItem],
+ client: polarion_api.ProjectClient,
+ ):
+ client.work_items.update(
+ [work_item for work_item in work_items.values() if work_item.id]
+ )
+ client.work_items.create(
+ [
+ work_item
+ for work_item in work_items.values()
+ if not work_item.id
+ ]
+ )
diff --git a/capella2polarion/converters/document_config.py b/capella2polarion/converters/document_config.py
index 30ab8df6..6a936666 100644
--- a/capella2polarion/converters/document_config.py
+++ b/capella2polarion/converters/document_config.py
@@ -11,6 +11,7 @@
import pydantic
import yaml
+from capella2polarion import data_models
from capella2polarion.converters import polarion_html_helper
logger = logging.getLogger(__name__)
@@ -47,6 +48,10 @@ class BaseDocumentRenderingConfig(pydantic.BaseModel):
"""A template config, which can result in multiple Polarion documents."""
template_directory: str | pathlib.Path
+ project_id: str | None = None
+ text_work_item_type: str = polarion_html_helper.TEXT_WORK_ITEM_TYPE
+ text_work_item_id_field: str = polarion_html_helper.TEXT_WORK_ITEM_ID_FIELD
+ status_allow_list: list[str] | None = None
heading_numbering: bool = False
work_item_layouts: dict[str, WorkItemLayout] = pydantic.Field(
default_factory=dict
@@ -77,11 +82,17 @@ class DocumentConfigs(pydantic.BaseModel):
pydantic.Field(default_factory=list)
)
- def iterate_documents(self) -> t.Iterator[tuple[str, str]]:
+ def iterate_documents(self) -> t.Iterator[data_models.DocumentInfo]:
"""Yield all document paths of the config as tuples."""
for conf in self.full_authority + self.mixed_authority:
for inst in conf.instances:
- yield inst.polarion_space, inst.polarion_name
+ yield data_models.DocumentInfo(
+ project_id=conf.project_id,
+ module_folder=inst.polarion_space,
+ module_name=inst.polarion_name,
+ text_work_item_type=conf.text_work_item_type,
+ text_work_item_id_field=conf.text_work_item_id_field,
+ )
def read_config_file(
diff --git a/capella2polarion/converters/document_renderer.py b/capella2polarion/converters/document_renderer.py
index 7b012754..58e7af59 100644
--- a/capella2polarion/converters/document_renderer.py
+++ b/capella2polarion/converters/document_renderer.py
@@ -16,7 +16,9 @@
from capella2polarion.connectors import polarion_repo
+from .. import data_models
from . import document_config, polarion_html_helper
+from . import text_work_item_provider as twi
logger = logging.getLogger(__name__)
@@ -35,6 +37,21 @@ class RenderingSession:
inserted_work_items: list[polarion_api.WorkItem] = dataclasses.field(
default_factory=list
)
+ text_work_items: dict[str, polarion_api.WorkItem] = dataclasses.field(
+ default_factory=dict
+ )
+
+
+@dataclasses.dataclass
+class ProjectData:
+ """A class holding data of a project which documents are rendered for."""
+
+ new_docs: list[data_models.DocumentData] = dataclasses.field(
+ default_factory=list
+ )
+ updated_docs: list[data_models.DocumentData] = dataclasses.field(
+ default_factory=list
+ )
class DocumentRenderer(polarion_html_helper.JinjaRendererMixin):
@@ -52,6 +69,8 @@ def __init__(
self.jinja_envs: dict[str, jinja2.Environment] = {}
self.overwrite_heading_numbering = overwrite_heading_numbering
self.overwrite_layouts = overwrite_layouts
+ self.projects: dict[str | None, ProjectData] = {}
+ self.existing_documents: polarion_repo.DocumentRepository = {}
def setup_env(self, env: jinja2.Environment):
"""Add globals and filters to the environment."""
@@ -79,22 +98,10 @@ def __insert_work_item(
)
return f"
{self.__link_work_item(obj)}
"
- layout_index = 0
- for layout in session.rendering_layouts:
- if layout.type == wi.type:
- break
- layout_index += 1
-
- if layout_index >= len(session.rendering_layouts):
- session.rendering_layouts.append(
- polarion_api.RenderingLayout(
- type=wi.type,
- layouter="section",
- label=polarion_html_helper.camel_case_to_words(
- wi.type
- ),
- )
- )
+ assert wi.type
+ layout_index = polarion_html_helper.get_layout_index(
+ "section", session.rendering_layouts, wi.type
+ )
custom_info = ""
if level is not None:
@@ -131,7 +138,7 @@ def __heading(self, level: int, text: str, session: RenderingSession):
session.headings.append(polarion_api.WorkItem(id=hid, title=text))
return (
f"'
+ f'id="{polarion_html_helper.wi_id_prefix}{hid}">'
f""
)
return f"{text}"
@@ -159,8 +166,10 @@ def render_document(
document_title: str | None = None,
heading_numbering: bool = False,
rendering_layouts: list[polarion_api.RenderingLayout] | None = None,
+ *,
+ text_work_item_provider: twi.TextWorkItemProvider | None = None,
**kwargs: t.Any,
- ):
+ ) -> data_models.DocumentData:
"""Render a new Polarion document."""
@t.overload
@@ -170,8 +179,9 @@ def render_document(
template_name: str,
*,
document: polarion_api.Document,
+ text_work_item_provider: twi.TextWorkItemProvider | None = None,
**kwargs: t.Any,
- ):
+ ) -> data_models.DocumentData:
"""Update an existing Polarion document."""
def render_document(
@@ -184,9 +194,13 @@ def render_document(
heading_numbering: bool = False,
rendering_layouts: list[polarion_api.RenderingLayout] | None = None,
document: polarion_api.Document | None = None,
+ text_work_item_provider: twi.TextWorkItemProvider | None = None,
**kwargs: t.Any,
- ):
+ ) -> data_models.DocumentData:
"""Render a Polarion document."""
+ text_work_item_provider = (
+ text_work_item_provider or twi.TextWorkItemProvider()
+ )
if document is not None:
polarion_folder = document.module_folder
polarion_name = document.module_name
@@ -216,13 +230,22 @@ def render_document(
if rendering_layouts is not None:
session.rendering_layouts = rendering_layouts
+ rendering_result = template.render(
+ model=self.model, session=session, **kwargs
+ )
+ text_work_item_provider.generate_text_work_items(
+ lxmlhtml.fragments_fromstring(rendering_result),
+ )
+
document.home_page_content = polarion_api.TextContent(
"text/html",
- template.render(model=self.model, session=session, **kwargs),
+ rendering_result,
)
document.rendering_layouts = session.rendering_layouts
- return document, session.headings
+ return data_models.DocumentData(
+ document, session.headings, text_work_item_provider
+ )
def update_mixed_authority_document(
self,
@@ -231,8 +254,12 @@ def update_mixed_authority_document(
sections: dict[str, str],
global_parameters: dict[str, t.Any],
section_parameters: dict[str, dict[str, t.Any]],
- ):
+ text_work_item_provider: twi.TextWorkItemProvider | None = None,
+ ) -> data_models.DocumentData:
"""Update a mixed authority document."""
+ text_work_item_provider = (
+ text_work_item_provider or twi.TextWorkItemProvider()
+ )
assert (
document.home_page_content and document.home_page_content.value
), "In mixed authority the document must have content"
@@ -272,7 +299,14 @@ def update_mixed_authority_document(
| section_parameters.get(section_name, {})
),
)
- new_content += lxmlhtml.fragments_fromstring(content)
+ work_item_ids = polarion_html_helper.extract_work_items(
+ current_content
+ )
+ html_fragments = lxmlhtml.fragments_fromstring(content)
+ text_work_item_provider.generate_text_work_items(
+ html_fragments, work_item_ids
+ )
+ new_content += html_fragments
new_content += html_elements[last_section_end:]
new_content = polarion_html_helper.remove_table_ids(new_content)
@@ -288,20 +322,23 @@ def update_mixed_authority_document(
)
document.rendering_layouts = session.rendering_layouts
- return document, session.headings
+ return data_models.DocumentData(
+ document, session.headings, text_work_item_provider
+ )
def _get_and_customize_doc(
self,
+ project_id: str | None,
space: str,
name: str,
title: str | None,
rendering_layouts: list[polarion_api.RenderingLayout],
heading_numbering: bool,
- existing_documents: dict[
- tuple[str, str], polarion_api.Document | None
- ],
- ) -> polarion_api.Document | None:
- if old_doc := existing_documents.get((space, name)):
+ ) -> tuple[polarion_api.Document | None, list[polarion_api.WorkItem]]:
+ old_doc, text_work_items = self.existing_documents.get(
+ (project_id, space, name), (None, [])
+ )
+ if old_doc:
if title:
old_doc.title = title
if self.overwrite_layouts:
@@ -309,68 +346,72 @@ def _get_and_customize_doc(
if self.overwrite_heading_numbering:
old_doc.outline_numbering = heading_numbering
- return old_doc
+ return old_doc, text_work_items
def render_documents(
self,
configs: document_config.DocumentConfigs,
- existing_documents: dict[
- tuple[str, str], polarion_api.Document | None
- ],
- ) -> tuple[
- list[polarion_api.Document],
- list[polarion_api.Document],
- list[polarion_api.WorkItem],
- ]:
+ existing_documents: polarion_repo.DocumentRepository,
+ ) -> dict[str | None, ProjectData]:
"""Render all documents defined in the given config.
Returns a list new documents followed by updated documents and
work items, which need to be updated
"""
+ self.existing_documents = existing_documents
+ self.projects = {}
- new_docs: list[polarion_api.Document] = []
- updated_docs: list[polarion_api.Document] = []
- work_items: list[polarion_api.WorkItem] = []
- self._render_full_authority_documents(
- configs.full_authority,
- existing_documents,
- new_docs,
- updated_docs,
- work_items,
- )
+ self._render_full_authority_documents(configs.full_authority)
+ self._render_mixed_authority_documents(configs.mixed_authority)
- self._render_mixed_authority_documents(
- configs.mixed_authority,
- existing_documents,
- updated_docs,
- work_items,
- )
+ return self.projects
- return new_docs, updated_docs, work_items
+ def _check_document_status(
+ self,
+ document: polarion_api.Document,
+ config: document_config.BaseDocumentRenderingConfig,
+ ):
+ if (
+ config.status_allow_list is not None
+ and document.status not in config.status_allow_list
+ ):
+ logger.warning(
+ "Won't update document %s/%s due to status "
+ "restrictions. Status is %s and should be in %r.",
+ document.module_folder,
+ document.module_name,
+ document.status,
+ config.status_allow_list,
+ )
+ return False
+ return True
def _render_mixed_authority_documents(
self,
mixed_authority_configs: list[
- document_config.FullAuthorityDocumentRenderingConfig
+ document_config.MixedAuthorityDocumentRenderingConfig
],
- existing_documents: dict[
- tuple[str, str], polarion_api.Document | None
- ],
- updated_docs: list[polarion_api.Document],
- work_items: list[polarion_api.WorkItem],
):
for config in mixed_authority_configs:
rendering_layouts = document_config.generate_work_item_layouts(
config.work_item_layouts
)
+ project_data = self.projects.setdefault(
+ config.project_id, ProjectData()
+ )
for instance in config.instances:
- old_doc = self._get_and_customize_doc(
+ old_doc, text_work_items = self._get_and_customize_doc(
+ config.project_id,
instance.polarion_space,
instance.polarion_name,
instance.polarion_title,
rendering_layouts,
config.heading_numbering,
- existing_documents,
+ )
+ text_work_item_provider = twi.TextWorkItemProvider(
+ config.text_work_item_id_field,
+ config.text_work_item_type,
+ text_work_items,
)
if old_doc is None:
logger.error(
@@ -380,13 +421,18 @@ def _render_mixed_authority_documents(
instance.polarion_name,
)
continue
+
+ if not self._check_document_status(old_doc, config):
+ continue
+
try:
- new_doc, wis = self.update_mixed_authority_document(
+ document_data = self.update_mixed_authority_document(
old_doc,
config.template_directory,
config.sections,
instance.params,
instance.section_params,
+ text_work_item_provider,
)
except Exception as e:
logger.error(
@@ -398,37 +444,43 @@ def _render_mixed_authority_documents(
)
continue
- updated_docs.append(new_doc)
- work_items.extend(wis)
+ project_data.updated_docs.append(document_data)
def _render_full_authority_documents(
self,
full_authority_configs,
- existing_documents: dict[
- tuple[str, str], polarion_api.Document | None
- ],
- new_docs: list[polarion_api.Document],
- updated_docs: list[polarion_api.Document],
- work_items: list[polarion_api.WorkItem],
):
for config in full_authority_configs:
rendering_layouts = document_config.generate_work_item_layouts(
config.work_item_layouts
)
+ project_data = self.projects.setdefault(
+ config.project_id, ProjectData()
+ )
for instance in config.instances:
- if old_doc := self._get_and_customize_doc(
+ old_doc, text_work_items = self._get_and_customize_doc(
+ config.project_id,
instance.polarion_space,
instance.polarion_name,
instance.polarion_title,
rendering_layouts,
config.heading_numbering,
- existing_documents,
- ):
+ )
+ text_work_item_provider = twi.TextWorkItemProvider(
+ config.text_work_item_id_field,
+ config.text_work_item_type,
+ text_work_items,
+ )
+ if old_doc:
+ if not self._check_document_status(old_doc, config):
+ continue
+
try:
- new_doc, wis = self.render_document(
+ document_data = self.render_document(
config.template_directory,
config.template,
document=old_doc,
+ text_work_item_provider=text_work_item_provider,
**instance.params,
)
except Exception as e:
@@ -441,11 +493,10 @@ def _render_full_authority_documents(
)
continue
- updated_docs.append(new_doc)
- work_items.extend(wis)
+ project_data.updated_docs.append(document_data)
else:
try:
- new_doc, _ = self.render_document(
+ document_data = self.render_document(
config.template_directory,
config.template,
instance.polarion_space,
@@ -453,6 +504,7 @@ def _render_full_authority_documents(
instance.polarion_title,
config.heading_numbering,
rendering_layouts,
+ text_work_item_provider=text_work_item_provider,
**instance.params,
)
except Exception as e:
@@ -465,7 +517,7 @@ def _render_full_authority_documents(
)
continue
- new_docs.append(new_doc)
+ project_data.new_docs.append(document_data)
def _extract_section_areas(self, html_elements: list[etree._Element]):
section_areas = {}
diff --git a/capella2polarion/converters/link_converter.py b/capella2polarion/converters/link_converter.py
index bf8bb511..a618b087 100644
--- a/capella2polarion/converters/link_converter.py
+++ b/capella2polarion/converters/link_converter.py
@@ -13,10 +13,13 @@
from capellambse.model import common
from capellambse.model import diagram as diag
-import capella2polarion.converters.polarion_html_helper
from capella2polarion import data_models
from capella2polarion.connectors import polarion_repo
-from capella2polarion.converters import converter_config, data_session
+from capella2polarion.converters import (
+ converter_config,
+ data_session,
+ polarion_html_helper,
+)
logger = logging.getLogger(__name__)
@@ -346,9 +349,7 @@ def _group_by(
def _make_url_list(link_map: dict[str, dict[str, list[str]]]) -> str:
urls: list[str] = []
for link_id in sorted(link_map):
- url = capella2polarion.converters.polarion_html_helper.POLARION_WORK_ITEM_URL.format( # pylint: disable=line-too-long
- pid=link_id
- )
+ url = polarion_html_helper.POLARION_WORK_ITEM_URL.format(pid=link_id)
urls.append(f"{url}")
for key, include_wids in link_map[link_id].items():
_, display_name, _ = key.split(":")
@@ -365,9 +366,7 @@ def _sorted_unordered_html_list(
) -> str:
urls: list[str] = []
for pid in work_item_ids:
- url = capella2polarion.converters.polarion_html_helper.POLARION_WORK_ITEM_URL.format( # pylint: disable=line-too-long
- pid=pid
- )
+ url = polarion_html_helper.POLARION_WORK_ITEM_URL.format(pid=pid)
urls.append(f"{url}")
urls.sort()
diff --git a/capella2polarion/converters/polarion_html_helper.py b/capella2polarion/converters/polarion_html_helper.py
index 9d7fa573..2cc20ac7 100644
--- a/capella2polarion/converters/polarion_html_helper.py
+++ b/capella2polarion/converters/polarion_html_helper.py
@@ -8,14 +8,16 @@
import capellambse
import jinja2
+import polarion_rest_api_client as polarion_api
from capellambse import helpers as chelpers
-from lxml import etree, html
+from lxml import html
-heading_id_prefix = "polarion_wiki macro name=module-workitem;params=id="
+wi_id_prefix = "polarion_wiki macro name=module-workitem;params=id="
h_regex = re.compile("h[0-9]")
-wi_regex = re.compile(f"{heading_id_prefix}(.*)")
-
+wi_id_regex = re.compile(f"{wi_id_prefix}([A-Z|a-z|0-9]*-[0-9]+)")
+TEXT_WORK_ITEM_ID_FIELD = "__C2P__id"
+TEXT_WORK_ITEM_TYPE = "text"
POLARION_WORK_ITEM_URL = (
''
@@ -29,6 +31,7 @@
f"<deleted element ({chelpers.RE_VALID_UUID.pattern})>"
)
RED_TEXT = '{text}
'
+WORK_ITEM_TAG = "workitem"
def strike_through(string: str) -> str:
@@ -111,8 +114,8 @@ def setup_env(self, env: jinja2.Environment):
def remove_table_ids(
- html_content: str | list[etree._Element],
-) -> list[etree._Element]:
+ html_content: str | list[html.HtmlElement | str],
+) -> list[html.HtmlElement | str]:
"""Remove the ID field from all tables.
This is necessary due to a bug in Polarion where Polarion does not
@@ -120,34 +123,73 @@ def remove_table_ids(
time the REST-API does not allow posting or patching a document with
multiple tables having the same ID.
"""
- html_fragments = _ensure_fragments(html_content)
+ html_fragments = ensure_fragments(html_content)
for element in html_fragments:
+ if not isinstance(element, html.HtmlElement):
+ continue
+
if element.tag == "table":
- element.remove("id")
+ element.attrib.pop("id", None)
return html_fragments
-def _ensure_fragments(
- html_content: str | list[etree._Element],
-) -> list[etree._Element]:
+def ensure_fragments(
+ html_content: str | list[html.HtmlElement | str],
+) -> list[html.HtmlElement | str]:
+ """Convert string to html elements."""
if isinstance(html_content, str):
return html.fragments_fromstring(html_content)
return html_content
-def extract_headings(html_content: str | list[etree._Element]) -> list[str]:
+def extract_headings(
+ html_content: str | list[html.HtmlElement | str],
+) -> list[str]:
"""Return a list of work item IDs for all headings in the given content."""
- heading_ids = []
- html_fragments = _ensure_fragments(html_content)
+ return extract_work_items(html_content, h_regex)
+
+def extract_work_items(
+ html_content: str | list[html.HtmlElement | str],
+ tag_regex: re.Pattern | None = None,
+) -> list[str]:
+ """Return a list of work item IDs for work items in the given content."""
+ work_item_ids: list[str] = []
+ html_fragments = ensure_fragments(html_content)
for element in html_fragments:
- if isinstance(element, html.HtmlComment):
+ if not isinstance(element, html.HtmlElement):
continue
- if h_regex.fullmatch(element.tag):
- if matches := wi_regex.match(element.get("id")):
- heading_ids.append(matches.group(1))
+ if (tag_regex is not None and tag_regex.fullmatch(element.tag)) or (
+ tag_regex is None and element.tag == "div"
+ ):
+ if matches := wi_id_regex.match(element.get("id")):
+ work_item_ids.append(matches.group(1))
+ return work_item_ids
- return heading_ids
+
+def get_layout_index(
+ default_layouter: str,
+ rendering_layouts: list[polarion_api.RenderingLayout],
+ work_item_type: str,
+) -> int:
+ """Return the index of the layout of the requested workitem.
+
+ If there is no rendering config yet, it will be created.
+ """
+ layout_index = 0
+ for layout in rendering_layouts:
+ if layout.type == work_item_type:
+ return layout_index
+ layout_index += 1
+ if layout_index >= len(rendering_layouts):
+ rendering_layouts.append(
+ polarion_api.RenderingLayout(
+ type=work_item_type,
+ layouter=default_layouter,
+ label=camel_case_to_words(work_item_type),
+ )
+ )
+ return layout_index
diff --git a/capella2polarion/converters/text_work_item_provider.py b/capella2polarion/converters/text_work_item_provider.py
new file mode 100644
index 00000000..a75e022a
--- /dev/null
+++ b/capella2polarion/converters/text_work_item_provider.py
@@ -0,0 +1,125 @@
+# Copyright DB InfraGO AG and contributors
+# SPDX-License-Identifier: Apache-2.0
+"""Provides a class to generate and inset text work items in documents."""
+import polarion_rest_api_client as polarion_api
+from lxml import html
+
+from capella2polarion.converters import polarion_html_helper as html_helper
+
+
+class TextWorkItemProvider:
+ """Class providing text work items, their generation and insertion."""
+
+ def __init__(
+ self,
+ text_work_item_id_field: str = html_helper.TEXT_WORK_ITEM_ID_FIELD,
+ text_work_item_type: str = html_helper.TEXT_WORK_ITEM_TYPE,
+ existing_text_work_items: list[polarion_api.WorkItem] | None = None,
+ ):
+ self.old_text_work_items: dict[str, polarion_api.WorkItem] = {}
+ for work_item in existing_text_work_items or []:
+ # We only use those work items which have an ID defined by us
+ if text_id := work_item.additional_attributes.get(
+ text_work_item_id_field
+ ):
+ if text_id in self.old_text_work_items:
+ raise ValueError(
+ f"There are multiple text work items with "
+ f"{text_work_item_id_field} == {text_id}"
+ )
+
+ self.old_text_work_items[text_id] = work_item
+
+ self.text_work_item_id_field = text_work_item_id_field
+ self.text_work_item_type = text_work_item_type
+ self.new_text_work_items: dict[str, polarion_api.WorkItem] = {}
+
+ def generate_text_work_items(
+ self,
+ content: list[html.HtmlElement] | str,
+ work_item_id_filter: list[str] | None = None,
+ ):
+ """Generate text work items from the provided html."""
+ content = html_helper.ensure_fragments(content)
+ for element in content:
+ if element.tag != html_helper.WORK_ITEM_TAG:
+ continue
+
+ if not (text_id := element.get("id")):
+ raise ValueError("All work items must have an ID in template")
+
+ if not (
+ (work_item := self.old_text_work_items.get(text_id))
+ and (
+ work_item_id_filter is None
+ or work_item.id in work_item_id_filter
+ )
+ ):
+ work_item = polarion_api.WorkItem(
+ type=self.text_work_item_type,
+ title="",
+ status="open",
+ additional_attributes={
+ self.text_work_item_id_field: text_id
+ },
+ )
+
+ work_item.description_type = "text/html"
+ inner_content = "".join(
+ [
+ (
+ html.tostring(child, encoding="unicode")
+ if isinstance(child, html.HtmlElement)
+ else child
+ )
+ for child in element.iterchildren()
+ ]
+ )
+ if element.text:
+ inner_content = element.text + inner_content
+
+ work_item.description = inner_content
+ self.new_text_work_items[text_id] = work_item
+
+ def insert_text_work_items(
+ self,
+ document: polarion_api.Document,
+ ):
+ """Insert text work items into the given document."""
+ if not self.new_text_work_items:
+ return
+
+ assert document.home_page_content is not None
+ assert document.rendering_layouts is not None
+ layout_index = html_helper.get_layout_index(
+ "paragraph", document.rendering_layouts, self.text_work_item_type
+ )
+ html_fragments = html_helper.ensure_fragments(
+ document.home_page_content.value
+ )
+ new_content = []
+ last_match = -1
+ for index, element in enumerate(html_fragments):
+ if not isinstance(element, html.HtmlElement):
+ continue
+
+ if element.tag == "workitem":
+ new_content += html_fragments[last_match + 1 : index]
+ last_match = index
+ if work_item := self.new_text_work_items.get(
+ element.get("id")
+ ):
+ new_content.append(
+ html.fromstring(
+ html_helper.POLARION_WORK_ITEM_DOCUMENT.format(
+ pid=work_item.id,
+ lid=layout_index,
+ custom_info="",
+ )
+ )
+ )
+
+ new_content += html_fragments[last_match + 1 :]
+ document.home_page_content.value = "\n".join(
+ [html.tostring(element).decode("utf-8") for element in new_content]
+ )
diff --git a/capella2polarion/data_models.py b/capella2polarion/data_models.py
index 3b445795..b6dcd3c1 100644
--- a/capella2polarion/data_models.py
+++ b/capella2polarion/data_models.py
@@ -4,12 +4,15 @@
from __future__ import annotations
import base64
+import dataclasses
import hashlib
import json
import typing as t
import polarion_rest_api_client as polarion_api
+from capella2polarion.converters import text_work_item_provider
+
class CapellaWorkItem(polarion_api.WorkItem):
"""A WorkItem class with additional Capella related attributes."""
@@ -60,3 +63,23 @@ def calculate_checksum(self) -> str:
| dict(sorted(attachment_checksums.items()))
)
return self._checksum
+
+
+@dataclasses.dataclass
+class DocumentData:
+ """A class to store data related to a rendered document."""
+
+ document: polarion_api.Document
+ headings: list[polarion_api.WorkItem]
+ text_work_item_provider: text_work_item_provider.TextWorkItemProvider
+
+
+@dataclasses.dataclass
+class DocumentInfo:
+ """Class for information regarding a document which should be created."""
+
+ project_id: str | None
+ module_folder: str
+ module_name: str
+ text_work_item_type: str
+ text_work_item_id_field: str
diff --git a/tests/conftest.py b/tests/conftest.py
index a1310ecf..296cd368 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -128,8 +128,10 @@ def base_object(
)
c2p_cli.setup_logger()
- mock_api = mock.MagicMock(spec=polarion_api.OpenAPIPolarionProjectClient)
- monkeypatch.setattr(polarion_api, "OpenAPIPolarionProjectClient", mock_api)
+ mock_api_client = mock.MagicMock(spec=polarion_api.PolarionClient)
+ monkeypatch.setattr(polarion_api, "PolarionClient", mock_api_client)
+ mock_project_client = mock.MagicMock(spec=polarion_api.ProjectClient)
+ monkeypatch.setattr(polarion_api, "ProjectClient", mock_project_client)
c2p_cli.config = mock.Mock(converter_config.ConverterConfig)
fake = FakeModelObject("uuid1", name="Fake 1")
@@ -173,8 +175,10 @@ def base_object(
@pytest.fixture
def empty_polarion_worker(monkeypatch: pytest.MonkeyPatch):
- mock_api = mock.MagicMock(spec=polarion_api.OpenAPIPolarionProjectClient)
- monkeypatch.setattr(polarion_api, "OpenAPIPolarionProjectClient", mock_api)
+ mock_api_client = mock.MagicMock(spec=polarion_api.PolarionClient)
+ monkeypatch.setattr(polarion_api, "PolarionClient", mock_api_client)
+ mock_project_client = mock.MagicMock(spec=polarion_api.ProjectClient)
+ monkeypatch.setattr(polarion_api, "ProjectClient", mock_project_client)
polarion_params = polarion_worker.PolarionWorkerParams(
project_id="project_id",
url=TEST_HOST,
@@ -182,3 +186,7 @@ def empty_polarion_worker(monkeypatch: pytest.MonkeyPatch):
delete_work_items=True,
)
yield polarion_worker.CapellaPolarionWorker(polarion_params)
+
+
+DOCUMENT_TEMPLATES = TEST_DOCUMENT_ROOT / "templates"
+DOCUMENT_TEXT_WORK_ITEMS = "document_work_items.html.j2"
diff --git a/tests/data/documents/combined_config.yaml b/tests/data/documents/combined_config.yaml
index 6a1034b3..3660a6d2 100644
--- a/tests/data/documents/combined_config.yaml
+++ b/tests/data/documents/combined_config.yaml
@@ -37,6 +37,21 @@ mixed_authority:
section_params:
section1:
param_1: Test
+ - template_directory: jupyter-notebooks/document_templates
+ sections:
+ section1: test-icd.html.j2
+ section2: test-icd.html.j2
+ heading_numbering: True
+ project_id: TestProject
+ status_allow_list:
+ - draft
+ - open
+ instances:
+ - polarion_space: _default
+ polarion_name: id1239
+ section_params:
+ section1:
+ param_1: Test
full_authority:
- template_directory: jupyter-notebooks/document_templates
template: test-icd.html.j2
@@ -66,3 +81,14 @@ full_authority:
instances:
- polarion_space: _default
polarion_name: id1238
+ - template_directory: jupyter-notebooks/document_templates
+ template: test-icd.html.j2
+ project_id: TestProject
+ status_allow_list:
+ - draft
+ - open
+ instances:
+ - polarion_space: _default
+ polarion_name: id1240
+ params:
+ interface: 2681f26a-e492-4e5d-8b33-92fb00a48622
diff --git a/tests/data/documents/full_authority_config.yaml b/tests/data/documents/full_authority_config.yaml
index e56aca98..fdbb9899 100644
--- a/tests/data/documents/full_authority_config.yaml
+++ b/tests/data/documents/full_authority_config.yaml
@@ -3,6 +3,10 @@
- template_directory: jupyter-notebooks/document_templates
template: test-icd.html.j2
+ project_id: TestProject
+ status_allow_list:
+ - draft
+ - open
instances:
- polarion_space: _default
polarion_name: id123
diff --git a/tests/data/documents/mixed_config.yaml b/tests/data/documents/mixed_config.yaml
index a0d30924..f3ca0586 100644
--- a/tests/data/documents/mixed_config.yaml
+++ b/tests/data/documents/mixed_config.yaml
@@ -3,6 +3,10 @@
mixed_authority:
- template_directory: jupyter-notebooks/document_templates
+ project_id: TestProject
+ status_allow_list:
+ - draft
+ - open
sections:
section1: test-icd.html.j2
section2: test-icd.html.j2
@@ -21,6 +25,8 @@ mixed_authority:
section1: test-icd.html.j2
section2: test-icd.html.j2
heading_numbering: True
+ text_work_item_type: myType
+ text_work_item_id_field: myId
work_item_layouts:
componentExchange:
fields_at_start:
diff --git a/tests/data/documents/sections/section1.html.j2 b/tests/data/documents/sections/section1.html.j2
index 575220b9..13b1afb7 100644
--- a/tests/data/documents/sections/section1.html.j2
+++ b/tests/data/documents/sections/section1.html.j2
@@ -6,3 +6,4 @@
{{ heading(3, "New Heading", session) }}
{{ global_param }}
{{ local_param }}
+TestContent
diff --git a/tests/data/documents/sections/section2.html.j2 b/tests/data/documents/sections/section2.html.j2
index ec7a3fb7..0e0630a0 100644
--- a/tests/data/documents/sections/section2.html.j2
+++ b/tests/data/documents/sections/section2.html.j2
@@ -6,3 +6,4 @@
{{ heading(3, "Keep Heading", session) }}
Overwritten: {{ global_param }}
{{ local_param }}
+TestContent
diff --git a/tests/data/documents/templates/document_work_items.html.j2 b/tests/data/documents/templates/document_work_items.html.j2
new file mode 100644
index 00000000..aecd905a
--- /dev/null
+++ b/tests/data/documents/templates/document_work_items.html.j2
@@ -0,0 +1,10 @@
+
+
+This is Text in a text workitem
+
+Text
+
+
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 6d7516f6..7894f405 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -23,8 +23,10 @@
def test_migrate_model_elements(monkeypatch: pytest.MonkeyPatch):
- mock_api = mock.MagicMock(spec=polarion_api.OpenAPIPolarionProjectClient)
- monkeypatch.setattr(polarion_api, "OpenAPIPolarionProjectClient", mock_api)
+ mock_api_client = mock.MagicMock(spec=polarion_api.PolarionClient)
+ monkeypatch.setattr(polarion_api, "PolarionClient", mock_api_client)
+ mock_project_client = mock.MagicMock(spec=polarion_api.ProjectClient)
+ monkeypatch.setattr(polarion_api, "ProjectClient", mock_project_client)
mock_get_polarion_wi_map = mock.MagicMock()
monkeypatch.setattr(
polarion_worker.CapellaPolarionWorker,
@@ -86,8 +88,10 @@ def test_migrate_model_elements(monkeypatch: pytest.MonkeyPatch):
def test_render_documents(monkeypatch: pytest.MonkeyPatch):
- mock_api = mock.MagicMock(spec=polarion_api.OpenAPIPolarionProjectClient)
- monkeypatch.setattr(polarion_api, "OpenAPIPolarionProjectClient", mock_api)
+ mock_api_client = mock.MagicMock(spec=polarion_api.PolarionClient)
+ monkeypatch.setattr(polarion_api, "PolarionClient", mock_api_client)
+ mock_project_client = mock.MagicMock(spec=polarion_api.ProjectClient)
+ monkeypatch.setattr(polarion_api, "ProjectClient", mock_project_client)
mock_get_polarion_wi_map = mock.MagicMock()
monkeypatch.setattr(
polarion_worker.CapellaPolarionWorker,
@@ -95,7 +99,7 @@ def test_render_documents(monkeypatch: pytest.MonkeyPatch):
mock_get_polarion_wi_map,
)
mock_get_document = mock.MagicMock()
- mock_get_document.side_effect = lambda folder, name: (
+ mock_get_document.side_effect = lambda folder, name, project_id: (
polarion_api.Document(
module_folder=folder,
module_name=name,
@@ -113,11 +117,11 @@ def test_render_documents(monkeypatch: pytest.MonkeyPatch):
"get_document",
mock_get_document,
)
- mock_post_documents = mock.MagicMock()
+ mock_create_documents = mock.MagicMock()
monkeypatch.setattr(
polarion_worker.CapellaPolarionWorker,
- "post_documents",
- mock_post_documents,
+ "create_documents",
+ mock_create_documents,
)
mock_update_documents = mock.MagicMock()
monkeypatch.setattr(
@@ -125,12 +129,6 @@ def test_render_documents(monkeypatch: pytest.MonkeyPatch):
"update_documents",
mock_update_documents,
)
- mock_update_work_items = mock.MagicMock()
- monkeypatch.setattr(
- polarion_worker.CapellaPolarionWorker,
- "update_work_items",
- mock_update_work_items,
- )
command: list[str] = [
"--polarion-project-id",
@@ -151,10 +149,26 @@ def test_render_documents(monkeypatch: pytest.MonkeyPatch):
assert result.exit_code == 0
assert mock_get_polarion_wi_map.call_count == 1
- assert mock_get_document.call_count == 6
- assert mock_post_documents.call_count == 1
- assert len(mock_post_documents.call_args.args[0]) == 1
- assert mock_update_documents.call_count == 1
- assert len(mock_update_documents.call_args.args[0]) == 1
- assert mock_update_work_items.call_count == 1
- assert len(mock_update_work_items.call_args.args[0]) == 1
+ assert mock_get_document.call_count == 8
+ assert [call.args[2] for call in mock_get_document.call_args_list] == [
+ None,
+ None,
+ None,
+ "TestProject",
+ None,
+ None,
+ None,
+ "TestProject",
+ ]
+
+ assert mock_create_documents.call_count == 2
+ assert len(mock_create_documents.call_args_list[0].args[0]) == 1
+ assert len(mock_create_documents.call_args_list[1].args[0]) == 1
+ assert mock_create_documents.call_args_list[0].args[1] is None
+ assert mock_create_documents.call_args_list[1].args[1] == "TestProject"
+
+ assert mock_update_documents.call_count == 2
+ assert len(mock_update_documents.call_args_list[0].args[0]) == 1
+ assert len(mock_update_documents.call_args_list[1].args[0]) == 0
+ assert mock_update_documents.call_args_list[0].args[1] is None
+ assert mock_update_documents.call_args_list[1].args[1] == "TestProject"
diff --git a/tests/test_documents.py b/tests/test_documents.py
index 9c56332e..5069b46d 100644
--- a/tests/test_documents.py
+++ b/tests/test_documents.py
@@ -6,9 +6,18 @@
from lxml import etree, html
from capella2polarion import data_models as dm
-from capella2polarion.connectors import polarion_worker
-from capella2polarion.converters import document_config, document_renderer
-from tests.conftest import TEST_COMBINED_DOCUMENT_CONFIG, TEST_DOCUMENT_ROOT
+from capella2polarion.connectors import polarion_repo, polarion_worker
+from capella2polarion.converters import (
+ document_config,
+ document_renderer,
+ text_work_item_provider,
+)
+from tests.conftest import (
+ DOCUMENT_TEMPLATES,
+ DOCUMENT_TEXT_WORK_ITEMS,
+ TEST_COMBINED_DOCUMENT_CONFIG,
+ TEST_DOCUMENT_ROOT,
+)
CLASSES_TEMPLATE = "test-classes.html.j2"
JUPYTER_TEMPLATE_FOLDER = "jupyter-notebooks/document_templates"
@@ -19,28 +28,60 @@
MIXED_AUTHORITY_DOCUMENT = TEST_DOCUMENT_ROOT / "mixed_authority_doc.html"
-def existing_documents() -> dict[tuple[str, str], polarion_api.Document]:
+def existing_documents() -> polarion_repo.DocumentRepository:
return {
- ("_default", "id123"): polarion_api.Document(
- module_folder="_default",
- module_name="id123",
- home_page_content=polarion_api.TextContent(
- type="text/html",
- value=MIXED_AUTHORITY_DOCUMENT.read_text("utf-8"),
+ (None, "_default", "id123"): (
+ polarion_api.Document(
+ module_folder="_default",
+ module_name="id123",
+ status="draft",
+ home_page_content=polarion_api.TextContent(
+ type="text/html",
+ value=MIXED_AUTHORITY_DOCUMENT.read_text("utf-8"),
+ ),
+ rendering_layouts=[
+ polarion_api.RenderingLayout(
+ "Class", "paragraph", type="class"
+ )
+ ],
),
- rendering_layouts=[
- polarion_api.RenderingLayout(
- "Class", "paragraph", type="class"
- )
- ],
+ [],
+ ),
+ (None, "_default", "id1237"): (
+ polarion_api.Document(
+ module_folder="_default",
+ module_name="id1237",
+ status="draft",
+ home_page_content=polarion_api.TextContent(
+ type="text/html",
+ value=MIXED_AUTHORITY_DOCUMENT.read_text("utf-8"),
+ ),
+ ),
+ [],
+ ),
+ ("TestProject", "_default", "id1239"): (
+ polarion_api.Document(
+ module_folder="_default",
+ module_name="id1239",
+ status="in_review",
+ home_page_content=polarion_api.TextContent(
+ type="text/html",
+ value=MIXED_AUTHORITY_DOCUMENT.read_text("utf-8"),
+ ),
+ ),
+ [],
),
- ("_default", "id1237"): polarion_api.Document(
- module_folder="_default",
- module_name="id1237",
- home_page_content=polarion_api.TextContent(
- type="text/html",
- value=MIXED_AUTHORITY_DOCUMENT.read_text("utf-8"),
+ ("TestProject", "_default", "id1240"): (
+ polarion_api.Document(
+ module_folder="_default",
+ module_name="id1240",
+ status="draft",
+ home_page_content=polarion_api.TextContent(
+ type="text/html",
+ value=MIXED_AUTHORITY_DOCUMENT.read_text("utf-8"),
+ ),
),
+ [],
),
}
@@ -67,7 +108,7 @@ def test_create_new_document(
empty_polarion_worker.polarion_data_repo, model
)
- new_doc, wis = renderer.render_document(
+ document_data = renderer.render_document(
JUPYTER_TEMPLATE_FOLDER,
CLASSES_TEMPLATE,
"_default",
@@ -76,10 +117,10 @@ def test_create_new_document(
)
content: list[etree._Element] = html.fromstring(
- new_doc.home_page_content.value
+ document_data.document.home_page_content.value
)
- assert len(wis) == 0
- assert new_doc.rendering_layouts == [
+ assert len(document_data.headings) == 0
+ assert document_data.document.rendering_layouts == [
polarion_api.RenderingLayout(
label="Class", type="class", layouter="section"
)
@@ -138,18 +179,19 @@ def test_update_document(
),
)
- new_doc, wis = renderer.render_document(
+ document_data = renderer.render_document(
JUPYTER_TEMPLATE_FOLDER,
CLASSES_TEMPLATE,
document=old_doc,
+ text_work_items={},
cls="c710f1c2-ede6-444e-9e2b-0ff30d7fd040",
)
content: list[etree._Element] = html.fromstring(
- new_doc.home_page_content.value
+ document_data.document.home_page_content.value
)
- assert len(new_doc.rendering_layouts) == 1
- assert new_doc.rendering_layouts[
+ assert len(document_data.document.rendering_layouts) == 1
+ assert document_data.document.rendering_layouts[
0
].properties == polarion_api.data_models.RenderingProperties(
fields_at_start=["ID"]
@@ -160,9 +202,9 @@ def test_update_document(
assert content[0].tag == "h1"
assert content[1].text == "Data Classes"
assert content[1].tag == "h2"
- assert len(wis) == 1
- assert wis[0].id == "ATSY-16062"
- assert wis[0].title == "Class Document"
+ assert len(document_data.headings) == 1
+ assert document_data.headings[0].id == "ATSY-16062"
+ assert document_data.headings[0].title == "Class Document"
def test_mixed_authority_document(
@@ -180,7 +222,7 @@ def test_mixed_authority_document(
),
)
- new_doc, wis = renderer.update_mixed_authority_document(
+ document_data = renderer.update_mixed_authority_document(
old_doc,
DOCUMENT_SECTIONS,
{
@@ -195,28 +237,160 @@ def test_mixed_authority_document(
"global_param": "Overwrite global param",
},
},
+ text_work_item_provider=text_work_item_provider.TextWorkItemProvider(
+ "MyField",
+ "MyType",
+ [
+ polarion_api.WorkItem(
+ id="EXISTING", additional_attributes={"MyField": "id1"}
+ )
+ ],
+ ),
)
content: list[etree._Element] = html.fromstring(
- new_doc.home_page_content.value
+ document_data.document.home_page_content.value
)
- assert len(content) == 15
+ assert len(document_data.text_work_item_provider.new_text_work_items) == 2
+ assert (
+ document_data.text_work_item_provider.new_text_work_items["id1"].id
+ is None
+ )
+ assert (
+ document_data.text_work_item_provider.new_text_work_items["id2"].id
+ is None
+ )
+ assert len(content) == 17
assert [c.tag for c in content[:3]] == ["h1", "p", "p"]
assert (c4 := content[4]).tag == "h3" and c4.text == "New Heading"
assert content[5].text == "Global Test"
assert content[6].text == "Local Test section 1"
- assert content[8].text == "This will be kept."
- assert content[10].get("id") == (
+ assert content[9].text == "This will be kept."
+ assert content[11].get("id") == (
"polarion_wiki macro name=module-workitem;params=id=ATSY-18305"
)
- assert content[10].tag == "h3"
- assert content[11].text == "Overwritten: Overwrite global param"
- assert content[12].text == "Local Test section 2"
- assert content[14].text == "Some postfix stuff"
- assert len(wis) == 1
- assert wis[0].id == "ATSY-18305"
- assert wis[0].title == "Keep Heading"
+ assert content[11].tag == "h3"
+ assert content[12].text == "Overwritten: Overwrite global param"
+ assert content[13].text == "Local Test section 2"
+ assert content[16].text == "Some postfix stuff"
+ assert len(document_data.headings) == 1
+ assert document_data.headings[0].id == "ATSY-18305"
+ assert document_data.headings[0].title == "Keep Heading"
+
+
+def test_create_full_authority_document_text_work_items(
+ empty_polarion_worker: polarion_worker.CapellaPolarionWorker,
+ model: capellambse.MelodyModel,
+):
+ renderer = document_renderer.DocumentRenderer(
+ empty_polarion_worker.polarion_data_repo, model
+ )
+
+ document_data = renderer.render_document(
+ DOCUMENT_TEMPLATES,
+ DOCUMENT_TEXT_WORK_ITEMS,
+ "_default",
+ "TEST-DOC",
+ text_work_item_provider=text_work_item_provider.TextWorkItemProvider(
+ "MyField",
+ "MyType",
+ ),
+ )
+
+ assert len(document_data.text_work_item_provider.new_text_work_items) == 2
+ assert (
+ document_data.text_work_item_provider.new_text_work_items["id1"].id
+ is None
+ )
+ assert (
+ document_data.text_work_item_provider.new_text_work_items["id1"].type
+ == "MyType"
+ )
+ assert (
+ document_data.text_work_item_provider.new_text_work_items[
+ "id1"
+ ].additional_attributes["MyField"]
+ == "id1"
+ )
+ assert (
+ document_data.text_work_item_provider.new_text_work_items["id2"].id
+ is None
+ )
+ assert (
+ document_data.text_work_item_provider.new_text_work_items["id2"].type
+ == "MyType"
+ )
+ assert (
+ document_data.text_work_item_provider.new_text_work_items[
+ "id2"
+ ].additional_attributes["MyField"]
+ == "id2"
+ )
+
+
+def test_update_full_authority_document_text_work_items(
+ empty_polarion_worker: polarion_worker.CapellaPolarionWorker,
+ model: capellambse.MelodyModel,
+):
+ renderer = document_renderer.DocumentRenderer(
+ empty_polarion_worker.polarion_data_repo, model
+ )
+ old_doc = polarion_api.Document(
+ module_folder="_default",
+ module_name="TEST-DOC",
+ home_page_content=polarion_api.TextContent(
+ type="text/html",
+ value="",
+ ),
+ )
+
+ document_data = renderer.render_document(
+ DOCUMENT_TEMPLATES,
+ DOCUMENT_TEXT_WORK_ITEMS,
+ "_default",
+ "TEST-DOC",
+ document=old_doc,
+ text_work_item_provider=text_work_item_provider.TextWorkItemProvider(
+ "MyField",
+ "MyType",
+ [
+ polarion_api.WorkItem(
+ id="EXISTING", additional_attributes={"MyField": "id1"}
+ )
+ ],
+ ),
+ )
+
+ assert len(document_data.text_work_item_provider.new_text_work_items) == 2
+ assert (
+ document_data.text_work_item_provider.new_text_work_items["id1"].id
+ == "EXISTING"
+ )
+ assert (
+ document_data.text_work_item_provider.new_text_work_items["id1"].type
+ is None
+ )
+ assert (
+ document_data.text_work_item_provider.new_text_work_items[
+ "id1"
+ ].additional_attributes["MyField"]
+ == "id1"
+ )
+ assert (
+ document_data.text_work_item_provider.new_text_work_items["id2"].id
+ is None
+ )
+ assert (
+ document_data.text_work_item_provider.new_text_work_items["id2"].type
+ == "MyType"
+ )
+ assert (
+ document_data.text_work_item_provider.new_text_work_items[
+ "id2"
+ ].additional_attributes["MyField"]
+ == "id2"
+ )
def test_render_all_documents_partially_successfully(
@@ -231,24 +405,49 @@ def test_render_all_documents_partially_successfully(
empty_polarion_worker.polarion_data_repo, model
)
- new_docs, updated_docs, work_items = renderer.render_documents(
- conf, existing_documents()
- )
+ projects_data = renderer.render_documents(conf, existing_documents())
- # There are 6 documents in the config, we expect 3 rendering to fail
- assert len(caplog.records) == 3
+ # There are 8 documents in the config, we expect 4 rendering to fail
+ assert len(caplog.records) == 4
+ # The first tree documents weren't rendered due to an error, the fourth
+ # wasn't rendered because of status restrictions, which is a just warning
+ assert [lr.levelno for lr in caplog.records] == [40, 40, 40, 30]
# For one valid config we did not pass a document, so we expect a new one
- assert len(new_docs) == 1
- # And two updated documents
- assert len(updated_docs) == 2
- # In both existing documents we had 2 headings. In full authority mode
+ assert len(projects_data[None].new_docs) == 1
+ # And three updated documents
+ assert len(projects_data[None].updated_docs) == 2
+ assert len(projects_data["TestProject"].updated_docs) == 1
+ # In all existing documents we had 2 headings. In full authority mode
# both should be updated and in mixed authority mode only one of them as
# the other is outside the rendering area
- assert len(work_items) == 3
- assert len(updated_docs[0].rendering_layouts) == 0
- assert len(updated_docs[1].rendering_layouts) == 1
- assert updated_docs[0].outline_numbering is None
- assert updated_docs[1].outline_numbering is None
+ assert (
+ sum(
+ len(document_data.headings)
+ for document_data in projects_data[None].updated_docs
+ )
+ == 3
+ )
+ assert (
+ sum(
+ len(document_data.headings)
+ for document_data in projects_data["TestProject"].updated_docs
+ )
+ == 2
+ )
+ assert (
+ len(projects_data[None].updated_docs[0].document.rendering_layouts)
+ == 0
+ )
+ assert (
+ len(projects_data[None].updated_docs[1].document.rendering_layouts)
+ == 1
+ )
+ assert (
+ projects_data[None].updated_docs[0].document.outline_numbering is None
+ )
+ assert (
+ projects_data[None].updated_docs[1].document.outline_numbering is None
+ )
def test_render_all_documents_overwrite_headings_layouts(
@@ -262,12 +461,13 @@ def test_render_all_documents_overwrite_headings_layouts(
empty_polarion_worker.polarion_data_repo, model, True, True
)
- _, updated_docs, _ = renderer.render_documents(conf, existing_documents())
+ projects_data = renderer.render_documents(conf, existing_documents())
+ updated_docs = projects_data[None].updated_docs
- assert len(updated_docs[0].rendering_layouts) == 2
- assert len(updated_docs[1].rendering_layouts) == 2
- assert updated_docs[0].outline_numbering is False
- assert updated_docs[1].outline_numbering is False
+ assert len(updated_docs[0].document.rendering_layouts) == 2
+ assert len(updated_docs[1].document.rendering_layouts) == 2
+ assert updated_docs[0].document.outline_numbering is False
+ assert updated_docs[1].document.outline_numbering is False
def test_full_authority_document_config():
@@ -289,6 +489,10 @@ def test_full_authority_document_config():
assert conf.full_authority[0].instances[0].params == {
"interface": "3d21ab4b-7bf6-428b-ba4c-a27bca4e86db"
}
+ assert conf.full_authority[0].project_id == "TestProject"
+ assert conf.full_authority[0].status_allow_list == ["draft", "open"]
+ assert conf.full_authority[1].project_id is None
+ assert conf.full_authority[1].status_allow_list is None
def test_mixed_authority_document_config():
@@ -308,6 +512,8 @@ def test_mixed_authority_document_config():
assert len(conf.mixed_authority[0].instances) == 2
assert conf.mixed_authority[0].instances[0].polarion_space == "_default"
assert conf.mixed_authority[0].instances[0].polarion_name == "id123"
+ assert conf.mixed_authority[0].project_id == "TestProject"
+ assert conf.mixed_authority[0].status_allow_list == ["draft", "open"]
assert conf.mixed_authority[0].instances[0].polarion_title == "Interface23"
assert conf.mixed_authority[0].instances[0].params == {
"interface": "3d21ab4b-7bf6-428b-ba4c-a27bca4e86db"
@@ -315,14 +521,20 @@ def test_mixed_authority_document_config():
assert conf.mixed_authority[1].instances[0].section_params == {
"section1": {"param_1": "Test"}
}
+ assert conf.mixed_authority[1].project_id is None
+ assert conf.mixed_authority[1].status_allow_list is None
+ assert conf.mixed_authority[0].text_work_item_type == "text"
+ assert conf.mixed_authority[0].text_work_item_id_field == "__C2P__id"
+ assert conf.mixed_authority[1].text_work_item_type == "myType"
+ assert conf.mixed_authority[1].text_work_item_id_field == "myId"
def test_combined_config():
with open(TEST_COMBINED_DOCUMENT_CONFIG, "r", encoding="utf-8") as f:
conf = document_config.read_config_file(f)
- assert len(conf.full_authority) == 2
- assert len(conf.mixed_authority) == 2
+ assert len(conf.full_authority) == 3
+ assert len(conf.mixed_authority) == 3
def test_rendering_config():
diff --git a/tests/test_elements.py b/tests/test_elements.py
index fc19f1c5..1c8e88eb 100644
--- a/tests/test_elements.py
+++ b/tests/test_elements.py
@@ -254,15 +254,6 @@ def test_create_diagrams(diagr_base_object: BaseObjectContainer):
cls="diagram",
)
- @staticmethod
- def test_create_diagrams_filters_non_diagram_elements(
- diagr_base_object: BaseObjectContainer,
- ):
- # This test does not make any sense, but it also didn't before
- pw = diagr_base_object.pw
- diagr_base_object.mc.generate_work_items(pw.polarion_data_repo)
- assert pw.client.generate_work_items.call_count == 0
-
@staticmethod
def test_delete_diagrams(diagr_base_object: BaseObjectContainer):
pw = diagr_base_object.pw
@@ -270,10 +261,13 @@ def test_delete_diagrams(diagr_base_object: BaseObjectContainer):
diagr_base_object.mc.generate_work_items(pw.polarion_data_repo)
pw.create_missing_work_items(diagr_base_object.mc.converter_session)
pw.delete_orphaned_work_items(diagr_base_object.mc.converter_session)
- assert pw.client is not None
- assert pw.client.delete_work_items.call_count == 1
- assert pw.client.delete_work_items.call_args[0][0] == ["Diag-1"]
- assert pw.client.generate_work_items.call_count == 0
+ 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.create.call_count == 0
class TestModelElements:
@@ -755,8 +749,8 @@ def test_update_work_items(
polarion_api_get_all_work_items = mock.MagicMock()
polarion_api_get_all_work_items.return_value = polarion_work_item_list
monkeypatch.setattr(
- base_object.pw.client,
- "get_all_work_items",
+ base_object.pw.project_client.work_items,
+ "get_all",
polarion_api_get_all_work_items,
)
@@ -778,24 +772,35 @@ def test_update_work_items(
get_work_item_mock = mock.MagicMock()
get_work_item_mock.return_value = polarion_work_item_list[0]
monkeypatch.setattr(
- base_object.pw.client,
- "get_work_item",
+ base_object.pw.project_client.work_items,
+ "get",
get_work_item_mock,
)
base_object.pw.compare_and_update_work_items(
base_object.mc.converter_session
)
- assert base_object.pw.client is not None
- assert base_object.pw.client.get_all_work_item_links.call_count == 0
- assert base_object.pw.client.delete_work_item_links.call_count == 0
- assert base_object.pw.client.create_work_item_links.call_count == 0
- assert base_object.pw.client.update_work_item.call_count == 1
- assert base_object.pw.client.get_work_item.call_count == 1
assert (
- base_object.pw.client.get_all_work_item_attachments.call_count == 0
+ base_object.pw.project_client.work_items.links.get_all.call_count
+ == 0
)
- work_item = base_object.pw.client.update_work_item.call_args[0][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]
assert isinstance(work_item, data_models.CapellaWorkItem)
assert work_item.id == "Obj-1"
assert work_item.title == "Fake 1"
@@ -821,8 +826,8 @@ def test_update_deleted_work_item(
polarion_api_get_all_work_items = mock.MagicMock()
polarion_api_get_all_work_items.return_value = polarion_work_item_list
monkeypatch.setattr(
- base_object.pw.client,
- "get_all_work_items",
+ base_object.pw.project_client.work_items,
+ "get_all",
polarion_api_get_all_work_items,
)
@@ -846,24 +851,26 @@ def test_update_deleted_work_item(
get_work_item_mock = mock.MagicMock()
get_work_item_mock.return_value = polarion_work_item_list[0]
monkeypatch.setattr(
- base_object.pw.client,
- "get_work_item",
+ base_object.pw.project_client.work_items,
+ "get",
get_work_item_mock,
)
base_object.pw.delete_orphaned_work_items(
base_object.mc.converter_session
)
- assert base_object.pw.client.update_work_item.called is False
+ assert base_object.pw.project_client.work_items.update.called is False
base_object.pw.create_missing_work_items(
base_object.mc.converter_session
)
- assert base_object.pw.client.create_work_items.called is False
+ 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.client.update_work_item.call_args[0][0]
+ work_item = base_object.pw.project_client.work_items.update.call_args[
+ 0
+ ][0]
assert isinstance(work_item, data_models.CapellaWorkItem)
assert work_item.status == "open"
@@ -897,8 +904,7 @@ def test_update_work_items_filters_work_items_with_same_checksum(
base_object.mc.converter_session
)
- assert base_object.pw.client is not None
- assert base_object.pw.client.update_work_item.call_count == 0
+ assert base_object.pw.project_client.work_items.update.call_count == 0
@staticmethod
def test_update_work_items_same_checksum_force(
@@ -931,8 +937,7 @@ def test_update_work_items_same_checksum_force(
base_object.mc.converter_session
)
- assert base_object.pw.client is not None
- assert base_object.pw.client.update_work_item.call_count == 1
+ assert base_object.pw.project_client.work_items.update.call_count == 1
@staticmethod
def test_update_links_with_no_elements(base_object: BaseObjectContainer):
@@ -944,7 +949,10 @@ def test_update_links_with_no_elements(base_object: BaseObjectContainer):
base_object.mc.converter_session
)
- assert base_object.pw.client.get_all_work_item_links.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):
@@ -980,8 +988,7 @@ def test_update_links(base_object: BaseObjectContainer):
)
)
- assert base_object.pw.client is not None
- base_object.pw.client.get_all_work_item_links.side_effect = (
+ base_object.pw.project_client.work_items.links.get_all.side_effect = (
[link],
[],
)
@@ -1001,7 +1008,7 @@ def test_update_links(base_object: BaseObjectContainer):
work_item_1.linked_work_items_truncated = True
work_item_2.linked_work_items_truncated = True
- base_object.pw.client.get_work_item.side_effect = (
+ base_object.pw.project_client.work_items.get.side_effect = (
work_item_1,
work_item_2,
)
@@ -1009,19 +1016,31 @@ def test_update_links(base_object: BaseObjectContainer):
base_object.pw.compare_and_update_work_items(
base_object.mc.converter_session
)
- assert base_object.pw.client is not None
- links = base_object.pw.client.get_all_work_item_links.call_args_list
- assert base_object.pw.client.get_all_work_item_links.call_count == 2
+ 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 [links[0][0][0], links[1][0][0]] == ["Obj-1", "Obj-2"]
- new_links = base_object.pw.client.create_work_item_links.call_args[0][
- 0
- ]
- assert base_object.pw.client.create_work_item_links.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.client.delete_work_item_links.call_count == 1
- assert base_object.pw.client.delete_work_item_links.call_args[0][
+ 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
- ] == [link]
+ ][0] == [link]
@staticmethod
def test_patch_work_item_grouped_links(
@@ -1095,9 +1114,8 @@ def mock_back_link(converter_data, back_links):
base_object.pw.compare_and_update_work_items(
base_object.mc.converter_session
)
- assert base_object.pw.client is not None
update_work_item_calls = (
- base_object.pw.client.update_work_item.call_args_list
+ base_object.pw.project_client.work_items.update.call_args_list
)
assert len(update_work_item_calls) == 3
mock_grouped_links_calls = mock_grouped_links.call_args_list
diff --git a/tests/test_polarion_worker_documents.py b/tests/test_polarion_worker_documents.py
new file mode 100644
index 00000000..714847bb
--- /dev/null
+++ b/tests/test_polarion_worker_documents.py
@@ -0,0 +1,142 @@
+# Copyright DB InfraGO AG and contributors
+# SPDX-License-Identifier: Apache-2.0
+from unittest import mock
+
+import polarion_rest_api_client as polarion_api
+
+from capella2polarion import data_models
+from capella2polarion.connectors import polarion_worker
+from capella2polarion.converters import text_work_item_provider
+
+from .conftest import DOCUMENT_TEMPLATES, DOCUMENT_TEXT_WORK_ITEMS
+
+
+def _set_work_item_id(work_items: list[polarion_api.WorkItem]):
+ for index, work_item in enumerate(work_items):
+ work_item.id = f"id{index}"
+
+
+def test_update_document(
+ empty_polarion_worker: polarion_worker.CapellaPolarionWorker,
+):
+ path = DOCUMENT_TEMPLATES / DOCUMENT_TEXT_WORK_ITEMS
+ document = polarion_api.Document(
+ module_folder="_default",
+ module_name="TEST-DOC",
+ rendering_layouts=[],
+ home_page_content=polarion_api.TextContent(
+ type="text/html",
+ value=path.read_text("utf-8"),
+ ),
+ )
+ document_data = data_models.DocumentData(
+ document,
+ [],
+ text_work_item_provider.TextWorkItemProvider(
+ "MyField",
+ "MyType",
+ [
+ polarion_api.WorkItem(
+ id="EXISTING", additional_attributes={"MyField": "id1"}
+ ),
+ ],
+ ),
+ )
+ document_data.text_work_item_provider.generate_text_work_items(
+ document.home_page_content.value
+ )
+ client = empty_polarion_worker.project_client
+ client.work_items.create.side_effect = _set_work_item_id
+
+ empty_polarion_worker.update_documents([document_data])
+
+ assert document.home_page_content.value.endswith(
+ '\n'
+ ''
+ )
+ assert client.documents.update.call_count == 1
+ assert client.documents.update.call_args.args[0] == [document]
+ assert client.work_items.create.call_count == 1
+ assert len(client.work_items.create.call_args.args[0]) == 1
+ assert client.work_items.update.call_count == 2
+ assert len(client.work_items.update.call_args_list[0].args[0]) == 1
+ assert len(client.work_items.update.call_args_list[1].args[0]) == 0
+
+
+def test_create_document(
+ empty_polarion_worker: polarion_worker.CapellaPolarionWorker,
+):
+ path = DOCUMENT_TEMPLATES / DOCUMENT_TEXT_WORK_ITEMS
+ document = polarion_api.Document(
+ module_folder="_default",
+ module_name="TEST-DOC",
+ rendering_layouts=[],
+ home_page_content=polarion_api.TextContent(
+ type="text/html",
+ value=path.read_text("utf-8"),
+ ),
+ )
+ document_data = data_models.DocumentData(
+ document,
+ [],
+ text_work_item_provider.TextWorkItemProvider(
+ "MyField",
+ "MyType",
+ ),
+ )
+ document_data.text_work_item_provider.generate_text_work_items(
+ document.home_page_content.value
+ )
+ client = empty_polarion_worker.project_client
+ client.work_items.create.side_effect = _set_work_item_id
+
+ empty_polarion_worker.update_documents([document_data])
+
+ assert document.home_page_content.value.endswith(
+ '\n'
+ ''
+ )
+ assert client.documents.update.call_count == 1
+ assert client.documents.update.call_args.args[0] == [document]
+ assert client.work_items.create.call_count == 1
+ assert len(client.work_items.create.call_args.args[0]) == 2
+ assert client.work_items.update.call_count == 2
+ assert len(client.work_items.update.call_args_list[0].args[0]) == 0
+ assert len(client.work_items.update.call_args_list[1].args[0]) == 0
+
+
+def test_use_correct_client(
+ empty_polarion_worker: polarion_worker.CapellaPolarionWorker,
+):
+ empty_polarion_worker.project_client = mock.MagicMock()
+ document = polarion_api.Document(
+ module_folder="_default",
+ module_name="TEST-DOC-A",
+ rendering_layouts=[],
+ home_page_content=polarion_api.TextContent(
+ type="text/html",
+ value="",
+ ),
+ )
+
+ document_data = data_models.DocumentData(
+ document,
+ [],
+ text_work_item_provider.TextWorkItemProvider(),
+ )
+
+ empty_polarion_worker.create_documents([document_data], "OtherProject")
+ empty_polarion_worker.update_documents([document_data], "OtherProject")
+
+ assert len(empty_polarion_worker.project_client.method_calls) == 0
+ assert len(empty_polarion_worker._additional_clients) == 1
+ assert (
+ client := empty_polarion_worker._additional_clients.get("OtherProject")
+ )
+ assert client.documents.update.call_count == 1
+ assert client.documents.create.call_count == 1
+ assert client.work_items.update.call_count == 1
diff --git a/tests/test_workitem_attachments.py b/tests/test_workitem_attachments.py
index 6e51889e..d9a07977 100644
--- a/tests/test_workitem_attachments.py
+++ b/tests/test_workitem_attachments.py
@@ -57,8 +57,10 @@
@pytest.fixture
def worker(monkeypatch: pytest.MonkeyPatch):
- mock_api = mock.MagicMock(spec=polarion_api.OpenAPIPolarionProjectClient)
- monkeypatch.setattr(polarion_api, "OpenAPIPolarionProjectClient", mock_api)
+ mock_api_client = mock.MagicMock(spec=polarion_api.PolarionClient)
+ monkeypatch.setattr(polarion_api, "PolarionClient", mock_api_client)
+ mock_project_client = mock.MagicMock(spec=polarion_api.ProjectClient)
+ monkeypatch.setattr(polarion_api, "ProjectClient", mock_project_client)
return polarion_worker.CapellaPolarionWorker(
polarion_worker.PolarionWorkerParams(
"TEST",
@@ -118,11 +120,13 @@ def test_diagram_attachments_new(
[data_models.CapellaWorkItem(WORKITEM_ID, uuid_capella=TEST_DIAG_UUID)]
)
- worker.client.get_work_item.return_value = data_models.CapellaWorkItem(
- WORKITEM_ID, uuid_capella=TEST_DIAG_UUID
+ worker.project_client.work_items.get.return_value = (
+ data_models.CapellaWorkItem(WORKITEM_ID, uuid_capella=TEST_DIAG_UUID)
+ )
+ worker.project_client.work_items.attachments = mock.MagicMock()
+ worker.project_client.work_items.attachments.create.side_effect = (
+ set_attachment_ids
)
- worker.client.create_work_item_attachments = mock.MagicMock()
- worker.client.create_work_item_attachments.side_effect = set_attachment_ids
converter.converter_session[TEST_DIAG_UUID] = data_session.ConverterData(
"",
@@ -136,15 +140,15 @@ def test_diagram_attachments_new(
converter.converter_session[TEST_DIAG_UUID]
)
- assert worker.client.update_work_item.call_count == 1
- assert worker.client.create_work_item_attachments.call_count == 1
- assert worker.client.get_all_work_item_attachments.call_count == 0
+ assert worker.project_client.work_items.update.call_count == 1
+ assert worker.project_client.work_items.attachments.create.call_count == 1
+ assert worker.project_client.work_items.attachments.get_all.call_count == 0
created_attachments: list[polarion_api.WorkItemAttachment] = (
- worker.client.create_work_item_attachments.call_args.args[0]
+ worker.project_client.work_items.attachments.create.call_args.args[0]
)
work_item: data_models.CapellaWorkItem = (
- worker.client.update_work_item.call_args.args[0]
+ worker.project_client.work_items.update.call_args.args[0]
)
assert len(created_attachments) == 2
@@ -180,11 +184,15 @@ def test_new_diagram(
]
)
- worker.client.get_work_item.return_value = data_models.CapellaWorkItem(
- WORKITEM_ID, uuid_capella=TEST_DIAG_UUID, checksum=checksum
+ worker.project_client.work_items.get.return_value = (
+ data_models.CapellaWorkItem(
+ WORKITEM_ID, uuid_capella=TEST_DIAG_UUID, checksum=checksum
+ )
+ )
+ worker.project_client.work_items.attachments.create = mock.MagicMock()
+ worker.project_client.work_items.attachments.create.side_effect = (
+ set_attachment_ids
)
- worker.client.create_work_item_attachments = mock.MagicMock()
- worker.client.create_work_item_attachments.side_effect = set_attachment_ids
converter.converter_session[TEST_DIAG_UUID] = data_session.ConverterData(
"",
@@ -198,9 +206,9 @@ def test_new_diagram(
converter.converter_session[TEST_DIAG_UUID]
)
- assert worker.client.update_work_item.call_count == 1
- assert worker.client.create_work_item_attachments.call_count == 1
- assert worker.client.update_work_item.call_args.args[
+ assert worker.project_client.work_items.update.call_count == 1
+ assert worker.project_client.work_items.attachments.create.call_count == 1
+ assert worker.project_client.work_items.update.call_args.args[
0
].description == TEST_DIAG_DESCR.format(
title="Diagram",
@@ -233,14 +241,16 @@ def test_diagram_attachments_updated(
),
]
- worker.client.get_work_item.return_value = data_models.CapellaWorkItem(
- WORKITEM_ID,
- uuid_capella=TEST_DIAG_UUID,
- attachments=existing_attachments,
+ worker.project_client.work_items.get.return_value = (
+ data_models.CapellaWorkItem(
+ WORKITEM_ID,
+ uuid_capella=TEST_DIAG_UUID,
+ attachments=existing_attachments,
+ )
)
- worker.client.get_all_work_item_attachments = mock.MagicMock()
- worker.client.get_all_work_item_attachments.return_value = (
+ worker.project_client.work_items.attachments.get_all = mock.MagicMock()
+ worker.project_client.work_items.attachments.get_all.return_value = (
existing_attachments
)
@@ -256,13 +266,13 @@ def test_diagram_attachments_updated(
converter.converter_session[TEST_DIAG_UUID]
)
- assert worker.client.update_work_item.call_count == 1
- assert worker.client.create_work_item_attachments.call_count == 0
- assert worker.client.update_work_item_attachment.call_count == 2
- assert worker.client.get_all_work_item_attachments.call_count == 1
+ assert worker.project_client.work_items.update.call_count == 1
+ assert worker.project_client.work_items.attachments.create.call_count == 0
+ assert worker.project_client.work_items.attachments.update.call_count == 2
+ assert worker.project_client.work_items.attachments.get_all.call_count == 1
work_item: data_models.CapellaWorkItem = (
- worker.client.update_work_item.call_args.args[0]
+ worker.project_client.work_items.update.call_args.args[0]
)
assert work_item.description == TEST_DIAG_DESCR.format(
@@ -292,8 +302,8 @@ def test_diagram_attachments_unchanged_work_item_changed(
)
]
)
- worker.client.get_all_work_item_attachments = mock.MagicMock()
- worker.client.get_all_work_item_attachments.return_value = [
+ worker.project_client.work_items.attachments.get_all = mock.MagicMock()
+ worker.project_client.work_items.attachments.get_all.return_value = [
polarion_api.WorkItemAttachment(
WORKITEM_ID,
"SVG-ATTACHMENT",
@@ -320,12 +330,12 @@ def test_diagram_attachments_unchanged_work_item_changed(
converter.converter_session[TEST_DIAG_UUID]
)
- assert worker.client.update_work_item.call_count == 1
- assert worker.client.create_work_item_attachments.call_count == 0
- assert worker.client.update_work_item_attachment.call_count == 0
+ assert worker.project_client.work_items.update.call_count == 1
+ assert worker.project_client.work_items.attachments.create.call_count == 0
+ assert worker.project_client.work_items.attachments.update.call_count == 0
work_item: data_models.CapellaWorkItem = (
- worker.client.update_work_item.call_args.args[0]
+ worker.project_client.work_items.update.call_args.args[0]
)
assert work_item.description == TEST_DIAG_DESCR.format(
@@ -363,10 +373,10 @@ def test_diagram_attachments_fully_unchanged(
converter.converter_session[TEST_DIAG_UUID]
)
- assert worker.client.update_work_item.call_count == 0
- assert worker.client.create_work_item_attachments.call_count == 0
- assert worker.client.update_work_item_attachment.call_count == 0
- assert worker.client.get_all_work_item_attachments.call_count == 0
+ assert worker.project_client.work_items.update.call_count == 0
+ assert worker.project_client.work_items.attachments.create.call_count == 0
+ assert worker.project_client.work_items.attachments.update.call_count == 0
+ assert worker.project_client.work_items.attachments.get_all.call_count == 0
def test_add_context_diagram(
@@ -385,21 +395,23 @@ def test_add_context_diagram(
model.by_uuid(uuid),
)
- worker.client.create_work_item_attachments = mock.MagicMock()
- worker.client.create_work_item_attachments.side_effect = set_attachment_ids
+ worker.project_client.work_items.attachments.create = mock.MagicMock()
+ worker.project_client.work_items.attachments.create.side_effect = (
+ set_attachment_ids
+ )
converter.generate_work_items(worker.polarion_data_repo, False, True)
worker.compare_and_update_work_item(converter.converter_session[uuid])
- assert worker.client.update_work_item.call_count == 1
- assert worker.client.create_work_item_attachments.call_count == 1
+ assert worker.project_client.work_items.update.call_count == 1
+ assert worker.project_client.work_items.attachments.create.call_count == 1
created_attachments: list[polarion_api.WorkItemAttachment] = (
- worker.client.create_work_item_attachments.call_args.args[0]
+ worker.project_client.work_items.attachments.create.call_args.args[0]
)
work_item: data_models.CapellaWorkItem = (
- worker.client.update_work_item.call_args.args[0]
+ worker.project_client.work_items.update.call_args.args[0]
)
assert len(created_attachments) == 2
@@ -439,8 +451,8 @@ def test_diagram_delete_attachments(
)
]
)
- worker.client.get_all_work_item_attachments = mock.MagicMock()
- worker.client.get_all_work_item_attachments.return_value = [
+ worker.project_client.work_items.attachments.get_all = mock.MagicMock()
+ worker.project_client.work_items.attachments.get_all.return_value = [
polarion_api.WorkItemAttachment(
WORKITEM_ID,
"SVG-ATTACHMENT",
@@ -473,13 +485,13 @@ def test_diagram_delete_attachments(
converter.converter_session[TEST_DIAG_UUID]
)
- assert worker.client.update_work_item.call_count == 1
- assert worker.client.create_work_item_attachments.call_count == 0
- assert worker.client.update_work_item_attachment.call_count == 0
- assert worker.client.delete_work_item_attachment.call_count == 2
+ assert worker.project_client.work_items.update.call_count == 1
+ assert worker.project_client.work_items.attachments.create.call_count == 0
+ assert worker.project_client.work_items.attachments.update.call_count == 0
+ assert worker.project_client.work_items.attachments.delete.call_count == 2
work_item: data_models.CapellaWorkItem = (
- worker.client.update_work_item.call_args.args[0]
+ worker.project_client.work_items.update.call_args.args[0]
)
assert work_item.description is None