diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 07c15fda..d267ed5b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: python -m pip install -U pip - name: Install pre-commit run: |- - python -m pip install pre-commit types-docutils + python -m pip install 'pre-commit>=3,<4' types-docutils - name: Run Pre-Commit run: |- pre-commit run --all-files diff --git a/polarion_rest_api_client/__init__.py b/polarion_rest_api_client/__init__.py index 4ddfb99c..b021235c 100644 --- a/polarion_rest_api_client/__init__.py +++ b/polarion_rest_api_client/__init__.py @@ -13,4 +13,3 @@ from polarion_rest_api_client.clients.projects import ProjectClient from polarion_rest_api_client.data_models import * from polarion_rest_api_client.errors import * -from polarion_rest_api_client.old_client import OpenAPIPolarionProjectClient diff --git a/polarion_rest_api_client/client.py b/polarion_rest_api_client/client.py index 44acdd39..83b49aab 100644 --- a/polarion_rest_api_client/client.py +++ b/polarion_rest_api_client/client.py @@ -115,9 +115,6 @@ def generate_project_client( self, project_id: str, delete_status: str | None = None, - add_work_item_checksum: bool = False, ): """Return a client for a specific project.""" - return projects.ProjectClient( - project_id, self, delete_status, add_work_item_checksum - ) + return projects.ProjectClient(project_id, self, delete_status) diff --git a/polarion_rest_api_client/clients/projects.py b/polarion_rest_api_client/clients/projects.py index 9e4a04de..2b65fc3d 100644 --- a/polarion_rest_api_client/clients/projects.py +++ b/polarion_rest_api_client/clients/projects.py @@ -20,12 +20,11 @@ def __init__( project_id: str, client: "polarion_client.PolarionClient", delete_status: str | None = None, - add_work_item_checksum: bool = False, ): super().__init__(project_id, client) self.work_items = work_items.WorkItems( - project_id, client, delete_status, add_work_item_checksum + project_id, client, delete_status ) self.test_runs = test_runs.TestRuns(project_id, client) self.documents = documents.Documents(project_id, client) diff --git a/polarion_rest_api_client/clients/test_runs.py b/polarion_rest_api_client/clients/test_runs.py index fcf134ad..651adf6a 100644 --- a/polarion_rest_api_client/clients/test_runs.py +++ b/polarion_rest_api_client/clients/test_runs.py @@ -145,6 +145,14 @@ def _create(self, items: list[dm.TestRun]): ) self._raise_on_error(response) + assert ( + isinstance(response.parsed, api_models.TestrunsListPostResponse) + and response.parsed.data + ) + if response.parsed and response.parsed.data: + for i, data in enumerate(response.parsed.data): + assert data.id + items[i].id = data.id.split("/")[-1] def _delete(self, items: dm.TestRun | list[dm.TestRun]): raise NotImplementedError diff --git a/polarion_rest_api_client/clients/work_items.py b/polarion_rest_api_client/clients/work_items.py index 3338c4f6..c84cc8c4 100644 --- a/polarion_rest_api_client/clients/work_items.py +++ b/polarion_rest_api_client/clients/work_items.py @@ -43,14 +43,12 @@ def __init__( project_id: str, client: "polarion_client.PolarionClient", delete_status: str | None = None, - add_work_item_checksum: bool = False, ): super().__init__(project_id, client, delete_status) self.attachments = work_item_attachments.WorkItemAttachments( project_id, client ) self.links = work_item_links.WorkItemLinks(project_id, client) - self.add_work_item_checksum = add_work_item_checksum def _update(self, to_update: list[dm.WorkItem] | dm.WorkItem): assert not isinstance(to_update, list), "Expected only one item" @@ -312,9 +310,9 @@ def _build_work_item_post_request( type=work_item.type, description=api_models.WorkitemsListPostRequestDataItemAttributesDescription( # pylint: disable=line-too-long type=api_models.WorkitemsListPostRequestDataItemAttributesDescriptionType( # pylint: disable=line-too-long - work_item.description_type + work_item.description.type ), - value=work_item.description, + value=work_item.description.value or oa_types.UNSET, ), status=work_item.status, title=work_item.title, @@ -322,11 +320,6 @@ def _build_work_item_post_request( attrs.additional_properties.update(work_item.additional_attributes) - if self.add_work_item_checksum: - attrs.additional_properties["checksum"] = ( - work_item.calculate_checksum() - ) - return api_models.WorkitemsListPostRequestDataItem( type=api_models.WorkitemsListPostRequestDataItemType.WORKITEMS, attributes=attrs, @@ -361,9 +354,9 @@ def _build_work_item_patch_request( if work_item.description is not None: attrs.description = api_models.WorkitemsSinglePatchRequestDataAttributesDescription( # pylint: disable=line-too-long type=api_models.WorkitemsSinglePatchRequestDataAttributesDescriptionType( # pylint: disable=line-too-long - work_item.description_type + work_item.description.type ), - value=work_item.description, + value=work_item.description.value or oa_types.UNSET, ) if work_item.status is not None: @@ -371,11 +364,6 @@ def _build_work_item_patch_request( attrs.additional_properties.update(work_item.additional_attributes) - if self.add_work_item_checksum: - attrs.additional_properties["checksum"] = ( - work_item.get_current_checksum() - ) - return api_models.WorkitemsSinglePatchRequest( data=api_models.WorkitemsSinglePatchRequestData( type=api_models.WorkitemsSinglePatchRequestDataType.WORKITEMS, @@ -399,11 +387,9 @@ def _post_work_item_batch( isinstance(response.parsed, api_models.WorkitemsListPostResponse) and response.parsed.data ) - counter = 0 - for work_item_res in response.parsed.data: + for index, work_item_res in enumerate(response.parsed.data): assert work_item_res.id - work_item_objs[counter].id = work_item_res.id.split("/")[-1] - counter += 1 + work_item_objs[index].id = work_item_res.id.split("/")[-1] def _calculate_post_work_item_request_sizes( self, @@ -480,25 +466,23 @@ def _generate_work_item( if attachment.id ] - desc_type = None - desc = None + description = None if work_item.attributes.description: - desc_type = self.unset_to_none( - work_item.attributes.description.type + description = dm.TextContent( + self.unset_to_none(work_item.attributes.description.type), + self.unset_to_none(work_item.attributes.description.value), ) - desc = self.unset_to_none(work_item.attributes.description.value) return work_item_cls( work_item_id, - self.unset_to_none(work_item.attributes.title), - desc_type, - desc, - self.unset_to_none(work_item.attributes.type), - self.unset_to_none(work_item.attributes.status), - work_item.attributes.additional_properties, - links, - attachments, - links_truncated, - attachments_truncated, - home_document, + title=self.unset_to_none(work_item.attributes.title), + description=description, + type=self.unset_to_none(work_item.attributes.type), + status=self.unset_to_none(work_item.attributes.status), + additional_attributes=work_item.attributes.additional_properties, + linked_work_items=links, + attachments=attachments, + linked_work_items_truncated=links_truncated, + attachments_truncated=attachments_truncated, + home_document=home_document, ) diff --git a/polarion_rest_api_client/data_models.py b/polarion_rest_api_client/data_models.py index 9d2ae75e..c0da8759 100644 --- a/polarion_rest_api_client/data_models.py +++ b/polarion_rest_api_client/data_models.py @@ -3,17 +3,16 @@ """Data model classes returned by the client.""" from __future__ import annotations -import base64 import dataclasses import datetime import enum -import hashlib -import json import typing as t +import warnings __all__ = [ "Document", "DocumentReference", + "HtmlContent", "Layouter", "RenderingLayout", "RenderingProperties", @@ -44,18 +43,6 @@ class StatusItem: id: str | None = None type: str | None = None status: str | None = None - _checksum: str | None = dataclasses.field(init=False, default=None) - - def __eq__(self, other: object) -> bool: - """Compare only StatusItem attributes.""" - if not isinstance(other, StatusItem): - return NotImplemented - if self.get_current_checksum() is None: - self.calculate_checksum() - if other.get_current_checksum() is None: - other.calculate_checksum() - - return self.get_current_checksum() == other.get_current_checksum() def to_dict(self) -> dict[str, t.Any]: """Return the content of the StatusItem as dictionary.""" @@ -63,28 +50,8 @@ def to_dict(self) -> dict[str, t.Any]: "id": self.id, "type": self.type, "status": self.status, - "checksum": self._checksum, } - def calculate_checksum(self) -> str: - """Calculate and return a checksum for this StatusItem. - - In addition, the checksum will be written to self._checksum. - """ - data = self.to_dict() - del data["checksum"] - del data["id"] - - data = dict(sorted(data.items())) - - converted = json.dumps(data).encode("utf8") - self._checksum = hashlib.sha256(converted).hexdigest() - return self._checksum - - def get_current_checksum(self) -> str | None: - """Return the checksum currently set without calculation.""" - return self._checksum - @dataclasses.dataclass class DocumentReference: @@ -98,8 +65,7 @@ class WorkItem(StatusItem): """A data class containing all relevant data of a Polarion WorkItem.""" title: str | None = None - description_type: str | None = None - description: str | None = None + description: TextContent | None = None additional_attributes: dict[str, t.Any] = {} linked_work_items: list[WorkItemLink] = [] attachments: list[WorkItemAttachment] = [] @@ -110,9 +76,10 @@ class WorkItem(StatusItem): def __init__( self, id: str | None = None, + *, title: str | None = None, description_type: str | None = None, - description: str | None = None, + description: TextContent | str | None = None, type: str | None = None, status: str | None = None, additional_attributes: dict[str, t.Any] | None = None, @@ -125,10 +92,25 @@ def __init__( ): super().__init__(id, type, status) self.title = title - self.description_type = description_type + if description_type or isinstance(description, str): + warnings.warn( + "Using description as str or description_type is " + "deprecated. Use TextContent instead.", + DeprecationWarning, + stacklevel=2, + ) + + assert description_type, ( + "You have to set a description_type when using a string as" + " description" + ) + assert not isinstance(description, TextContent), ( + "Don't use description_type when setting description as " + "TextContent" + ) + description = TextContent(description_type, description) self.description = description self.additional_attributes = (additional_attributes or {}) | kwargs - self._checksum = self.additional_attributes.pop("checksum", None) self.linked_work_items = linked_work_items or [] self.attachments = attachments or [] self.linked_work_items_truncated = linked_work_items_truncated @@ -137,28 +119,17 @@ def __init__( def __getattribute__(self, item: str) -> t.Any: """Return all non WorkItem attributes from additional_properties.""" - if item.startswith("__") or item in dir(WorkItem): + if item.startswith("__") or item in dir(self.__class__): return super().__getattribute__(item) return self.additional_attributes.get(item) def __setattr__(self, key: str, value: t.Any): """Set all non WorkItem attributes in additional_properties.""" - if key in dir(WorkItem): + if key in dir(self.__class__): super().__setattr__(key, value) else: self.additional_attributes[key] = value - def __eq__(self, other: object) -> bool: - """Compare only WorkItem attributes.""" - if not isinstance(other, WorkItem): - return NotImplemented - if self.get_current_checksum() is None: - self.calculate_checksum() - if other.get_current_checksum() is None: - other.calculate_checksum() - - return self.get_current_checksum() == other.get_current_checksum() - def to_dict(self) -> dict[str, t.Any]: """Return the content of the WorkItem as dictionary.""" sorted_links = sorted( @@ -173,14 +144,12 @@ def to_dict(self) -> dict[str, t.Any]: return { "id": self.id, "title": self.title, - "description_type": self.description_type, "description": self.description, "type": self.type, "status": self.status, "additional_attributes": dict( sorted(self.additional_attributes.items()) ), - "checksum": self._checksum, "linked_work_items": [ dataclasses.asdict(lwi) for lwi in sorted_links ], @@ -194,31 +163,6 @@ def to_dict(self) -> dict[str, t.Any]: ), } - def calculate_checksum(self) -> str: - """Calculate and return a checksum for this WorkItem. - - In addition, the checksum will be written to self._checksum. - """ - data = self.to_dict() - del data["checksum"] - del data["id"] - - for attachment in data["attachments"]: - try: - attachment["content_bytes"] = base64.b64encode( - attachment["content_bytes"] - ).decode("utf8") - except TypeError: - pass - - del attachment["id"] - - data = dict(sorted(data.items())) - - converted = json.dumps(data).encode("utf8") - self._checksum = hashlib.sha256(converted).hexdigest() - return self._checksum - @dataclasses.dataclass class WorkItemLink: @@ -394,12 +338,36 @@ class TestRecord: ) -@dataclasses.dataclass -class TextContent: - """A data class for home_page_content of a Polarion Document.""" +class TextContent(dict): + """A data class for text content in Polarion.""" - type: str | None = None - value: str | None = None + def __init__(self, type: str | None, value: str | None): + super().__init__(type=type, value=value) + + @property + def type(self): + """Return type of the TextContent.""" + return self["type"] + + @type.setter + def type(self, type: str): + self["type"] = type + + @property + def value(self): + """Return value of the TextContent.""" + return self["value"] + + @value.setter + def value(self, value: str): + self["value"] = value + + +class HtmlContent(TextContent): + """A specialized class for HTML based TextContent.""" + + def __init__(self, value: str): + super().__init__(type="text/html", value=value) class SelectTestCasesBy(str, enum.Enum): diff --git a/polarion_rest_api_client/old_client.py b/polarion_rest_api_client/old_client.py deleted file mode 100644 index 62e2fae8..00000000 --- a/polarion_rest_api_client/old_client.py +++ /dev/null @@ -1,375 +0,0 @@ -# Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 -"""The old client, which is deprecated, but uses the new client.""" -from __future__ import annotations - -import typing as t -import warnings - -from polarion_rest_api_client import client -from polarion_rest_api_client import data_models as dm -from polarion_rest_api_client.client import WorkItemType - - -class OpenAPIPolarionProjectClient(t.Generic[WorkItemType]): - """A Polarion Project Client using an auto generated OpenAPI-Client.""" - - delete_status: str = "deleted" - - @t.overload - def __init__( - self: "OpenAPIPolarionProjectClient[client.WorkItemType]", - project_id: str, - delete_polarion_work_items: bool, - polarion_api_endpoint: str, - polarion_access_token: str, - *, - custom_work_item: type[client.WorkItemType], - batch_size: int = ..., - page_size: int = ..., - add_work_item_checksum: bool = False, - max_content_size: int = ..., - httpx_args: t.Optional[dict[str, t.Any]] = ..., - ): ... - - @t.overload - def __init__( - self: "OpenAPIPolarionProjectClient[dm.WorkItem]", - project_id: str, - delete_polarion_work_items: bool, - polarion_api_endpoint: str, - polarion_access_token: str, - *, - batch_size: int = ..., - page_size: int = ..., - add_work_item_checksum: bool = False, - max_content_size: int = ..., - httpx_args: t.Optional[dict[str, t.Any]] = ..., - ): ... - - def __init__( - self, - project_id: str, - delete_polarion_work_items: bool, - polarion_api_endpoint: str, - polarion_access_token: str, - *, - custom_work_item=dm.WorkItem, - batch_size: int = 100, - page_size: int = 100, - add_work_item_checksum: bool = False, - max_content_size: int = 2 * 1024**2, - httpx_args: t.Optional[dict[str, t.Any]] = None, - ): - """Initialize the client for project and endpoint using a token. - - Parameters - ---------- - project_id : str - ID of the project to create a client for. - delete_polarion_work_items : bool - Flag indicating whether to delete work items or set a status. - polarion_api_endpoint : str - The URL of the Polarion API endpoint. - polarion_access_token : str - A personal access token to access the API. - custom_work_item : default dm.WorkItem - Custom WorkItem class with additional attributes. - batch_size : int, default 100 - Maximum amount of items created in one POST request. - page_size : int, default 100 - Default size of a page when getting items from the API. - add_work_item_checksum : bool, default False - Flag whether post WorkItem checksums. - max_content_size : int, default 2 * 1024**2 - Maximum content-length of the API (default: 2MB). - httpx_args: t.Optional[dict[str, t.Any]], default None - Additional parameters, which will be passed to the httpx client. - """ - warnings.warn( - f"{self.__class__.__name__} will be deprecated.", - DeprecationWarning, - stacklevel=2, - ) - - polarion_client = client.PolarionClient( - polarion_api_endpoint, - polarion_access_token, - httpx_args, - batch_size, - page_size, - max_content_size, - ) - self.project_client = polarion_client.generate_project_client( - project_id, - None if delete_polarion_work_items else "deleted", - add_work_item_checksum, - ) - self.project_id = project_id - self.custom_work_item = custom_work_item - - def project_exists(self) -> bool: - """Return True if self.project_id exists and False if not.""" - return self.project_client.exists() - - def delete_work_item_attachment( - self, work_item_attachment: dm.WorkItemAttachment - ): - """Delete the given work item attachment.""" - self.project_client.work_items.attachments.delete(work_item_attachment) - - def update_work_item_attachment( - self, work_item_attachment: dm.WorkItemAttachment - ): - """Update the given work item attachment in Polarion.""" - self.project_client.work_items.attachments.update(work_item_attachment) - - def create_work_item_attachments( - self, - work_item_attachments: list[dm.WorkItemAttachment], - ): - """Create the given work item attachment in Polarion.""" - self.project_client.work_items.attachments.create( - work_item_attachments - ) - - def get_work_item( - self, - work_item_id: str, - ) -> client.WorkItemType | None: - """Return one specific work item with all fields. - - This also includes all linked work items and attachments. If - there are to many of these to get them in one request, the - truncated flags for linked_work_items and attachments will be - set to True. - """ - return self.project_client.work_items.get( - work_item_id, self.custom_work_item - ) - - def get_document( - self, - space_id: str, - document_name: str, - fields: dict[str, str] | None = None, - include: str | None = None, - revision: str | None = None, - ) -> dm.Document | None: - """Return the document with the given document_name and space_id.""" - return self.project_client.documents.get( - space_id, - document_name, - fields=fields, - include=include, - revision=revision, - ) - - def create_work_items(self, work_items: list[client.WorkItemType]): - """Create the given list of work items.""" - self.project_client.work_items.create(work_items) - - def update_work_item(self, work_item: client.WorkItemType): - """Update the given work item in Polarion. - - Only fields not set to None will be updated in Polarion. None - fields will stay untouched. - """ - self.project_client.work_items.update(work_item) - - def get_work_item_links( - self, - work_item_id: str, - fields: dict[str, str] | None = None, - include: str | None = None, - page_size: int = 100, - page_number: int = 1, - ) -> tuple[list[dm.WorkItemLink], bool]: - """Get the work item links for the given work item on a page. - - In addition, a flag whether a next page is available is - returned. Define a fields dictionary as described in the - Polarion API documentation to get certain fields. - """ - return self.project_client.work_items.links.get_multi( - work_item_id, - page_size=page_size, - page_number=page_number, - fields=fields, - include=include, - ) - - def get_test_records( - self, - test_run_id: str, - fields: dict[str, str] | None = None, - page_size: int = 100, - page_number: int = 1, - ) -> tuple[list[dm.TestRecord], bool]: - """Return the test records on a defined page matching the given query. - - In addition, a flag whether a next page is available is - returned. Define a fields dictionary as described in the - Polarion API documentation to get certain fields. - """ - return self.project_client.test_runs.records.get_multi( - test_run_id, - fields=fields, - page_size=page_size, - page_number=page_number, - ) - - def get_test_runs( - self, - query: str = "", - fields: dict[str, str] | None = None, - page_size: int = 100, - page_number: int = 1, - ) -> tuple[list[dm.TestRun], bool]: - """Return the test runs on a defined page matching the given query. - - In addition, a flag whether a next page is available is - returned. Define a fields dictionary as described in the - Polarion API documentation to get certain fields. - """ - return self.project_client.test_runs.get_multi( - query, fields=fields, page_size=page_size, page_number=page_number - ) - - def create_test_runs(self, test_runs: list[dm.TestRun]): - """Create the given list of test runs.""" - self.project_client.test_runs.create(test_runs) - - def update_test_run(self, test_run: dm.TestRun): - """Create the given list of test runs.""" - self.project_client.test_runs.update(test_run) - - def create_test_records( - self, - test_run_id: str, - test_records: list[dm.TestRecord], - ): - """Create the given list of test records.""" - for rec in test_records: - rec.test_run_id = test_run_id - self.project_client.test_runs.records.create(test_records) - - def update_test_record(self, test_run_id: str, test_record: dm.TestRecord): - """Create the given list of test records.""" - test_record.test_run_id = test_run_id - self.project_client.test_runs.records.update(test_record) - - def get_all_work_item_attachments( - self, work_item_id: str, fields: dict[str, str] | None = None - ) -> list[dm.WorkItemAttachment]: - """Get all work item attachments for a given work item. - - Will handle pagination automatically. Define a fields dictionary - as described in the Polarion API documentation to get certain - fields. - """ - return self.project_client.work_items.attachments.get_all( - work_item_id, fields=fields - ) - - def create_work_item_attachment( - self, work_item_attachment: dm.WorkItemAttachment - ): - """Update the given work item attachment in Polarion.""" - self.project_client.work_items.attachments.create(work_item_attachment) - - def get_all_work_items( - self, query: str = "", fields: dict[str, str] | None = None - ) -> list[WorkItemType]: - """Get all work items matching the given query. - - Will handle pagination automatically. Define a fields dictionary - as described in the Polarion API documentation to get certain - fields. - """ - return self.project_client.work_items.get_all( - query, fields=fields, work_item_cls=self.custom_work_item - ) - - def create_work_item(self, work_item: WorkItemType): - """Create a single given work item.""" - self.create_work_items([work_item]) - - def delete_work_items(self, work_item_ids: list[str]): - """Delete or mark the defined work items as deleted.""" - self.project_client.work_items.delete( - [dm.WorkItem(wid) for wid in work_item_ids] - ) - - def delete_work_item(self, work_item_id: str): - """Delete or mark the defined work item as deleted.""" - return self.delete_work_items([work_item_id]) - - def get_all_work_item_links( - self, - work_item_id: str, - fields: dict[str, str] | None = None, - include: str | None = None, - ) -> list[dm.WorkItemLink]: - """Get all work item links for the given work item. - - Define a fields dictionary as described in the Polarion API - documentation to get certain fields. - """ - return self.project_client.work_items.links.get_all( - work_item_id=work_item_id, - fields=fields, - include=include, - ) - - def create_work_item_links(self, work_item_links: list[dm.WorkItemLink]): - """Create the links between the work items in work_item_links.""" - self.project_client.work_items.links.create(work_item_links) - - def create_work_item_link(self, work_item_link: dm.WorkItemLink): - """Create the link between the work items in work_item_link.""" - self.create_work_item_links([work_item_link]) - - def delete_work_item_links(self, work_item_links: list[dm.WorkItemLink]): - """Delete the links between the work items in work_item_link.""" - self.project_client.work_items.links.delete(work_item_links) - - def delete_work_item_link(self, work_item_link: dm.WorkItemLink): - """Delete the links between the work items in work_item_link.""" - self.delete_work_item_links([work_item_link]) - - def get_all_test_runs( - self, - query: str = "", - fields: dict[str, str] | None = None, - ) -> list[dm.TestRun]: - """Get all test runs matching the given query. - - Will handle pagination automatically. Define a fields dictionary - as described in the Polarion API documentation to get certain - fields. - """ - return self.project_client.test_runs.get_all(query, fields=fields) - - def get_all_test_records( - self, - test_run_id: str, - fields: dict[str, str] | None = None, - ) -> list[dm.TestRecord]: - """Get all test records matching the given query. - - Will handle pagination automatically. Define a fields dictionary - as described in the Polarion API documentation to get certain - fields. - """ - return self.project_client.test_runs.records.get_all( - test_run_id, fields=fields - ) - - def create_test_run(self, test_run: dm.TestRun): - """Create the given test run.""" - self.create_test_runs([test_run]) - - def create_test_record(self, test_run_id: str, test_record: dm.TestRecord): - """Create the given list of test records.""" - self.create_test_records(test_run_id, [test_record]) diff --git a/tests/conftest.py b/tests/conftest.py index 506f24f4..f98a385b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,19 +13,6 @@ @pytest.fixture(name="client") def fixture_client(): - base_classes._max_sleep = 0 - base_classes._min_sleep = 0 - yield polarion_api.OpenAPIPolarionProjectClient( - project_id="PROJ", - delete_polarion_work_items=False, - polarion_api_endpoint="http://127.0.0.1/api", - polarion_access_token="PAT123", - batch_size=3, - ) - - -@pytest.fixture(name="new_client") -def fixture_new_client(): base_classes._max_sleep = 0 base_classes._min_sleep = 0 client = polarion_api.PolarionClient( @@ -38,26 +25,13 @@ def fixture_new_client(): ) -@pytest.fixture(name="client_custom_work_item") -def fixture_client_custom_work_item(): - yield polarion_api.OpenAPIPolarionProjectClient( - project_id="PROJ", - delete_polarion_work_items=False, - polarion_api_endpoint="http://127.0.0.1/api", - polarion_access_token="PAT123", - batch_size=3, - custom_work_item=CustomWorkItem, - ) - - @pytest.fixture(name="work_item") def fixture_dummy_work_item(): return polarion_api.WorkItem( title="Title", - description_type="text/html", - description="My text value", - status="open", + description=polarion_api.HtmlContent("My text value"), type="task", + status="open", additional_attributes={"capella_uuid": "asdfg"}, ) @@ -66,9 +40,8 @@ def fixture_dummy_work_item(): def fixture_dummy_work_item_patch(): return polarion_api.WorkItem( id="MyWorkItemId", - description_type="text/html", - description="My text value", title="Title", + description=polarion_api.HtmlContent("My text value"), status="open", additional_attributes={"capella_uuid": "qwertz"}, ) diff --git a/tests/test_client_documents.py b/tests/test_client_documents.py index 2000deae..a6bc55eb 100644 --- a/tests/test_client_documents.py +++ b/tests/test_client_documents.py @@ -17,13 +17,13 @@ def test_get_document_with_all_fields( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_DOCUMENT_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(json=json.load(f)) - document = client.get_document( + document = client.documents.get( "MySpaceId", "MyDocumentName", {"fields[documents]": "@all"} ) @@ -60,7 +60,7 @@ def test_get_document_with_all_fields( def test_create_new_document( - new_client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock ): document = polarion_api.Document( module_folder="folder", @@ -108,7 +108,7 @@ def test_create_new_document( ] }, ) - new_client.documents.create(document) + client.documents.create(document) with open(TEST_DOCUMENT_POST_REQUEST, "r", encoding="utf-8") as f: expected_request = json.load(f) @@ -123,7 +123,7 @@ def test_create_new_document( def test_update_document( - new_client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock ): document = polarion_api.Document( module_folder="folder", @@ -166,7 +166,7 @@ def test_update_document( httpx_mock.add_response(204) httpx_mock.add_response(204) - new_client.documents.update([document, document2]) + client.documents.update([document, document2]) with open(TEST_DOCUMENT_PATCH_REQUEST, "r", encoding="utf-8") as f: expected_request = json.load(f) diff --git a/tests/test_client_error_responses.py b/tests/test_client_error_responses.py index f3901206..ca11fa88 100644 --- a/tests/test_client_error_responses.py +++ b/tests/test_client_error_responses.py @@ -13,14 +13,17 @@ def test_faulty_error_message( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_FAULTS_ERROR_RESPONSES, encoding="utf8") as f: - httpx_mock.add_response(400, json=json.load(f)) + response = json.load(f) + + httpx_mock.add_response(400, json=response) + httpx_mock.add_response(400, json=response) with pytest.raises(polarion_api.PolarionApiException) as e_info: - client.get_document( + client.documents.get( "MySpaceId", "MyDocumentName", {"fields[documents]": "@all"} ) @@ -35,14 +38,14 @@ def test_faulty_error_message( def test_dont_retry_on_404( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_FAULTS_ERROR_RESPONSES, encoding="utf8") as f: httpx_mock.add_response(404, json=json.load(f)) with pytest.raises(polarion_api.PolarionApiException) as e_info: - client.get_document( + client.documents.get( "MySpaceId", "MyDocumentName", {"fields[documents]": "@all"} ) diff --git a/tests/test_client_general.py b/tests/test_client_general.py index a82cd367..f55ca78c 100644 --- a/tests/test_client_general.py +++ b/tests/test_client_general.py @@ -3,7 +3,6 @@ from __future__ import annotations import json -import warnings import pytest_httpx @@ -12,7 +11,7 @@ def test_api_authentication( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_PROJECT_RESPONSE_JSON, encoding="utf8") as f: @@ -21,33 +20,23 @@ def test_api_authentication( json=json.load(f), ) - assert client.project_exists() + assert client.exists() def test_check_existing_project( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_PROJECT_RESPONSE_JSON, encoding="utf8") as f: httpx_mock.add_response(json=json.load(f)) - assert client.project_exists() + assert client.exists() def test_check_non_existing_project( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(status_code=404, json={}) - assert not client.project_exists() - - -def test_check_deprecation_warning(): - with warnings.catch_warnings(record=True) as w: - polarion_api.OpenAPIPolarionProjectClient( - "P", False, "http://localhost", "123" - ) - - assert len(w) == 1 - assert issubclass(w[-1].category, DeprecationWarning) + assert not client.exists() diff --git a/tests/test_client_testrecords.py b/tests/test_client_testrecords.py index bc33fb9f..d6ecd25e 100644 --- a/tests/test_client_testrecords.py +++ b/tests/test_client_testrecords.py @@ -17,7 +17,7 @@ def test_get_test_records_multi_page( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_TREC_NEXT_RESPONSE, encoding="utf8") as f: @@ -28,7 +28,9 @@ def test_get_test_records_multi_page( with open(TEST_TREC_NO_NEXT_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(json=json.load(f)) - test_records = client.get_all_test_records("123", {"test_records": "@all"}) + test_records = client.test_runs.records.get_all( + "123", fields={"test_records": "@all"} + ) query = { "page[size]": "100", @@ -56,7 +58,7 @@ def test_get_test_records_multi_page( def test_create_test_records( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_TREC_CREATED_RESPONSE, encoding="utf8") as f: @@ -85,7 +87,7 @@ def test_create_test_records( comment=polarion_api.TextContent("text/html", "My text value 2"), ) - client.create_test_records(test_run_id, [tr_1, tr_2]) + client.test_runs.records.create([tr_1, tr_2]) reqs = httpx_mock.get_requests() assert len(reqs) == 1 @@ -94,16 +96,13 @@ def test_create_test_records( expected_req = json.load(f) assert req_data == expected_req - assert ( - reqs[0].url.path == f"/api/projects/{client.project_id}/testruns" - f"/{test_run_id}/testrecords" - ) + assert reqs[0].url.path.endswith(f"/testruns/{test_run_id}/testrecords") assert tr_1.iteration == 0 assert tr_2.iteration == 1 def test_update_test_record( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) @@ -122,7 +121,7 @@ def test_update_test_record( comment=polarion_api.TextContent("text/html", "My text value"), ) - client.update_test_record(test_run_id, tr_1) + client.test_runs.records.update(tr_1) reqs = httpx_mock.get_requests() assert len(reqs) == 1 @@ -131,7 +130,7 @@ def test_update_test_record( expected_req = json.load(f) assert req_data == expected_req - assert reqs[0].url.path == ( - f"/api/projects/{client.project_id}/testruns/{test_run_id}" + assert reqs[0].url.path.endswith( + f"/testruns/{test_run_id}" f"/testrecords/{work_item_project}/{work_item_id}/4" ) diff --git a/tests/test_client_testruns.py b/tests/test_client_testruns.py index 766a5984..37d29955 100644 --- a/tests/test_client_testruns.py +++ b/tests/test_client_testruns.py @@ -18,7 +18,7 @@ def test_get_test_runs_multi_page( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_TRUN_NEXT_RESPONSE, encoding="utf8") as f: @@ -29,7 +29,7 @@ def test_get_test_runs_multi_page( with open(TEST_TRUN_NO_NEXT_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(json=json.load(f)) - test_runs = client.get_all_test_runs("123", {"test_runs": "@all"}) + test_runs = client.test_runs.get_all("123", fields={"test_runs": "@all"}) query = { "page[size]": "100", @@ -60,7 +60,7 @@ def test_get_test_runs_multi_page( def test_create_test_runs( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_TRUN_CREATED_RESPONSE, encoding="utf8") as f: @@ -99,7 +99,7 @@ def test_create_test_runs( {}, ) - client.create_test_runs([tr_1, tr_2]) + client.test_runs.create([tr_1, tr_2]) reqs = httpx_mock.get_requests() assert len(reqs) == 1 @@ -108,11 +108,12 @@ def test_create_test_runs( expected_req = json.load(f) assert req_data == expected_req - assert reqs[0].url.path == f"/api/projects/{client.project_id}/testruns" + assert tr_1.id == "MyTestRunId" + assert tr_2.id == "MyTestRunId2" def test_update_test_run( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) @@ -127,7 +128,7 @@ def test_update_test_run( use_report_from_template=False, ) - client.update_test_run(tr) + client.test_runs.update(tr) reqs = httpx_mock.get_requests() assert len(reqs) == 1 @@ -136,14 +137,11 @@ def test_update_test_run( expected_req = json.load(f) assert req_data == expected_req - assert ( - reqs[0].url.path - == f"/api/projects/{client.project_id}/testruns/{test_run_id}" - ) + assert reqs[0].url.path.endswith(f"/testruns/{test_run_id}") def test_update_test_run_fully( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) @@ -164,7 +162,7 @@ def test_update_test_run_fully( {}, ) - client.update_test_run(tr) + client.test_runs.update(tr) reqs = httpx_mock.get_requests() assert len(reqs) == 1 diff --git a/tests/test_client_workitemattachments.py b/tests/test_client_workitemattachments.py index 5eb0bf1d..3146b854 100644 --- a/tests/test_client_workitemattachments.py +++ b/tests/test_client_workitemattachments.py @@ -4,10 +4,12 @@ from __future__ import annotations import copy +import email import json +from email import message +import httpx import pytest_httpx -from httpx import _multipart import polarion_rest_api_client as polarion_api from tests.conftest import ( @@ -18,8 +20,23 @@ ) +def _extract_data_from_request( + req: httpx.Request, +) -> dict[str, list[message.Message]]: + headers = f"Content-Type: {req.headers['Content-Type']}\r\n\r\n" + msg = email.message_from_bytes( + headers.encode(req.headers.encoding) + req.content + ) + fields: dict[str, list[message.Message]] = {} + for part in msg.walk(): + field_name = part.get_param("name", header="Content-Disposition") + if isinstance(field_name, str): + fields.setdefault(field_name, []).append(part) + return fields + + def test_get_work_item_attachments_single_page( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open( @@ -28,7 +45,7 @@ def test_get_work_item_attachments_single_page( ) as f: httpx_mock.add_response(json=json.load(f)) - work_item_attachments = client.get_all_work_item_attachments( + work_item_attachments = client.work_items.attachments.get_all( "MyWorkItemId", fields={"fields[workitem_attachments]": "id,title"}, ) @@ -49,7 +66,7 @@ def test_get_work_item_attachments_single_page( def test_get_work_item_attachments_multi_page( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open( @@ -63,7 +80,7 @@ def test_get_work_item_attachments_multi_page( ) as f: httpx_mock.add_response(json=json.load(f)) - work_items_attachments = client.get_all_work_item_attachments( + work_items_attachments = client.work_items.attachments.get_all( "MyWorkItemId" ) @@ -83,12 +100,12 @@ def test_get_work_item_attachments_multi_page( def test_delete_work_item_attachment( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) - client.delete_work_item_attachment( + client.work_items.attachments.delete( polarion_api.WorkItemAttachment("MyWorkItemId", "Attachment", "Title") ) @@ -101,39 +118,30 @@ def test_delete_work_item_attachment( def test_create_single_work_item_attachment( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item_attachment: polarion_api.WorkItemAttachment, ): with open(TEST_WIA_CREATED_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(201, json=json.load(f)) - client.create_work_item_attachment(work_item_attachment) + client.work_items.attachments.create(work_item_attachment) req = httpx_mock.get_request() + fields = _extract_data_from_request(req) + resource = fields["resource"][0] + files = fields["files"] assert req is not None and req.method == "POST" assert req.url.path.endswith("PROJ/workitems/MyWorkItemId/attachments") assert work_item_attachment.id == "MyAttachmentId" + assert work_item_attachment.content_bytes is not None - body = req.stream - assert isinstance(body, _multipart.MultipartStream) - assert len(body.fields) == 2 - - resource_index = None - files_index = None - for i in range(2): - if body.fields[i].name == "resource": - resource_index = i - if body.fields[i].name == "files": - files_index = i + assert resource is not None + assert len(files) == 1 - assert resource_index is not None - assert files_index is not None - - assert body.fields[resource_index].headers["Content-Type"] == "text/plain" - assert body.fields[resource_index].name == "resource" - assert body.fields[resource_index].file == json.dumps( + assert resource.get_content_type() == "text/plain" + assert resource.get_payload() == json.dumps( { "data": [ { @@ -142,19 +150,17 @@ def test_create_single_work_item_attachment( } ] } - ).encode("utf-8") - - assert body.fields[files_index].headers["Content-Type"] == "text/plain" - assert body.fields[files_index].filename == "test.json" - assert body.fields[files_index].name == "files" - assert ( - body.fields[files_index].file.read() - == work_item_attachment.content_bytes + ) + + assert files[0].get_content_type() == "text/plain" + assert files[0].get_filename() == "test.json" + assert files[0].get_payload() == work_item_attachment.content_bytes.decode( + "utf-8" ) def test_create_multiple_work_item_attachments( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item_attachment: polarion_api.WorkItemAttachment, ): @@ -167,7 +173,7 @@ def test_create_multiple_work_item_attachments( copy.deepcopy(work_item_attachment), ] - client.create_work_item_attachments(work_item_attachments) + client.work_items.attachments.create(work_item_attachments) req = httpx_mock.get_request() @@ -178,22 +184,22 @@ def test_create_multiple_work_item_attachments( assert work_item_attachments[1].id == "MyAttachmentId2" assert work_item_attachments[2].id == "MyAttachmentId3" - body = req.stream - assert isinstance(body, _multipart.MultipartStream) - assert len(body.fields) == 4 + fields = _extract_data_from_request(req) + resource = fields["resource"][0] + files = fields["files"] - for i in range(0, 3): - assert body.fields[i].headers["Content-Type"] == "text/plain" - assert body.fields[i].filename == "test.json" - assert body.fields[i].name == "files" - assert ( - body.fields[i].file.read() - == work_item_attachments[i - 1].content_bytes - ) + assert len(files) == 3 - assert body.fields[3].headers["Content-Type"] == "text/plain" - assert body.fields[3].name == "resource" - assert body.fields[3].file == json.dumps( + for index, file in enumerate(files): + assert ( + content := work_item_attachments[index - 1].content_bytes + ) is not None + assert file.get_content_type() == "text/plain" + assert file.get_filename() == "test.json" + assert content.decode("utf-8") + + assert resource.get_content_type() == "text/plain" + assert resource.get_payload() == json.dumps( { "data": 3 * [ @@ -203,11 +209,11 @@ def test_create_multiple_work_item_attachments( } ] } - ).encode("utf-8") + ) def test_update_work_item_attachment_title( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item_attachment: polarion_api.WorkItemAttachment, ): @@ -219,17 +225,16 @@ def test_update_work_item_attachment_title( work_item_attachment.content_bytes = None work_item_attachment.file_name = None - client.update_work_item_attachment(work_item_attachment) + client.work_items.attachments.update(work_item_attachment) req = httpx_mock.get_request() - body = req.stream - assert isinstance(body, _multipart.MultipartStream) - assert len(body.fields) == 1 + fields = _extract_data_from_request(req) + resource = fields["resource"][0] - assert body.fields[0].headers["Content-Type"] == "text/plain" - assert body.fields[0].name == "resource" - assert body.fields[0].file == json.dumps( + assert "files" not in fields + assert resource.get_content_type() == "text/plain" + assert resource.get_payload() == json.dumps( { "data": { "type": "workitem_attachments", @@ -237,11 +242,11 @@ def test_update_work_item_attachment_title( "attributes": {"title": "TestTitle"}, } } - ).encode("utf-8") + ) def test_update_work_item_attachment_content( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item_attachment: polarion_api.WorkItemAttachment, ): @@ -250,17 +255,16 @@ def test_update_work_item_attachment_content( work_item_attachment.id = "MyAttachmentId" work_item_attachment.title = None - client.update_work_item_attachment(work_item_attachment) + client.work_items.attachments.update(work_item_attachment) req = httpx_mock.get_request() - body = req.stream - assert isinstance(body, _multipart.MultipartStream) - assert len(body.fields) == 2 + fields = _extract_data_from_request(req) + resource = fields["resource"][0] + content = fields["content"][0] - assert body.fields[0].headers["Content-Type"] == "text/plain" - assert body.fields[0].name == "resource" - assert body.fields[0].file == json.dumps( + assert resource.get_content_type() == "text/plain" + assert resource.get_payload() == json.dumps( { "data": { "type": "workitem_attachments", @@ -268,9 +272,11 @@ def test_update_work_item_attachment_content( "attributes": {}, } } - ).encode("utf-8") + ) - assert body.fields[1].headers["Content-Type"] == "text/plain" - assert body.fields[1].filename == "test.json" - assert body.fields[1].name == "content" - assert body.fields[1].file.read() == work_item_attachment.content_bytes + assert content.get_content_type() == "text/plain" + assert content.get_filename() == "test.json" + assert work_item_attachment.content_bytes is not None + assert content.get_payload() == work_item_attachment.content_bytes.decode( + "utf-8" + ) diff --git a/tests/test_client_workitemlinks.py b/tests/test_client_workitemlinks.py index 183b05ba..ca2de0f7 100644 --- a/tests/test_client_workitemlinks.py +++ b/tests/test_client_workitemlinks.py @@ -22,7 +22,7 @@ def test_get_work_item_links_single_page( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open( @@ -31,7 +31,7 @@ def test_get_work_item_links_single_page( ) as f: httpx_mock.add_response(json=json.load(f)) - work_item_links = client.get_all_work_item_links( + work_item_links = client.work_items.links.get_all( "MyWorkItemId", include="workitem", fields={"fields[linkedworkitems]": "id,role"}, @@ -54,7 +54,7 @@ def test_get_work_item_links_single_page( def test_get_work_item_links_multi_page( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open( @@ -68,7 +68,7 @@ def test_get_work_item_links_multi_page( ) as f: httpx_mock.add_response(json=json.load(f)) - work_items = client.get_all_work_item_links("MyWorkItemId") + work_items = client.work_items.links.get_all("MyWorkItemId") query = { "fields[linkedworkitems]": "id,role,suspect", "page[size]": "100", @@ -87,12 +87,12 @@ def test_get_work_item_links_multi_page( def test_delete_work_item_link( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) - client.delete_work_item_link( + client.work_items.links.delete( polarion_api.WorkItemLink( "MyWorkItemId", "MyWorkItemId2", "parent", True, "MyProjectId" ) @@ -106,12 +106,12 @@ def test_delete_work_item_link( def test_delete_work_item_links( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) - client.delete_work_item_links( + client.work_items.links.delete( [ polarion_api.WorkItemLink( "MyWorkItemId", "MyWorkItemId2", "parent", True, "MyProjectId" @@ -132,12 +132,13 @@ def test_delete_work_item_links( def test_delete_work_item_links_multi_primary( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) + httpx_mock.add_response(204) - client.delete_work_item_links( + client.work_items.links.delete( [ polarion_api.WorkItemLink( "MyWorkItemId", "MyWorkItemId2", "parent", True, "MyProjectId" @@ -163,13 +164,13 @@ def test_delete_work_item_links_multi_primary( def test_create_work_item_link( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_WIL_CREATED_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(201, json=json.load(f)) - client.create_work_item_link( + client.work_items.links.create( polarion_api.WorkItemLink( "MyWorkItemId", "MyWorkItemId2", "relates_to", True ) @@ -186,7 +187,7 @@ def test_create_work_item_link( def test_create_work_item_links_different_primaries( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_WIL_CREATED_RESPONSE, encoding="utf8") as f: @@ -195,7 +196,7 @@ def test_create_work_item_links_different_primaries( httpx_mock.add_response(201, json=content) httpx_mock.add_response(201, json=content) - client.create_work_item_links( + client.work_items.links.create( [ polarion_api.WorkItemLink( "MyWorkItemId", "MyWorkItemId2", "relates_to", True @@ -227,13 +228,13 @@ def test_create_work_item_links_different_primaries( def test_create_work_item_links_same_primaries( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_WIL_CREATED_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(201, json=json.load(f)) - client.create_work_item_links( + client.work_items.links.create( [ polarion_api.WorkItemLink( "MyWorkItemId", @@ -258,7 +259,7 @@ def test_create_work_item_links_same_primaries( def test_get_work_item_links_error_first_request( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, caplog: pytest.LogCaptureFixture, ): @@ -269,7 +270,7 @@ def test_get_work_item_links_error_first_request( ) as f: httpx_mock.add_response(json=json.load(f)) - work_item_links = client.get_all_work_item_links( + work_item_links = client.work_items.links.get_all( "MyWorkItemId", ) diff --git a/tests/test_client_workitems.py b/tests/test_client_workitems.py index fc35bae7..e2867388 100644 --- a/tests/test_client_workitems.py +++ b/tests/test_client_workitems.py @@ -8,7 +8,6 @@ import pytest import pytest_httpx -import pytest_mock as mock import polarion_rest_api_client as polarion_api from tests.conftest import ( @@ -32,13 +31,13 @@ def test_get_one_work_item( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_WI_SINGLE_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(json=json.load(f)) - work_item = client.get_work_item("MyWorkItemId") + work_item = client.work_items.get("MyWorkItemId") query = { "fields[workitems]": "@all", @@ -55,18 +54,19 @@ def test_get_one_work_item( assert "test_custom_field" in work_item.additional_attributes assert work_item.attachments_truncated is True assert work_item.linked_work_items_truncated is True + assert work_item.home_document is not None assert work_item.home_document.module_folder == "MySpaceId" assert work_item.home_document.module_name == "MyDocumentId" def test_get_one_work_item_not_truncated( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_WI_NOT_TRUNCATED_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(json=json.load(f)) - work_item = client.get_work_item("MyWorkItemId") + work_item = client.work_items.get("MyWorkItemId") query = { "fields[workitems]": "@all", @@ -87,7 +87,7 @@ def test_get_one_work_item_not_truncated( def test_get_all_work_items_multi_page( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_WI_NEXT_PAGE_RESPONSE, encoding="utf8") as f: @@ -95,9 +95,9 @@ def test_get_all_work_items_multi_page( with open(TEST_WI_NO_NEXT_PAGE_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(json=json.load(f)) - work_items = client.get_all_work_items( + work_items = client.work_items.get_all( "", - {"fields[workitems]": "id"}, + fields={"fields[workitems]": "id"}, ) query = { @@ -118,17 +118,15 @@ def test_get_all_work_items_multi_page( def test_get_all_work_items_single_page( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_WI_NO_NEXT_PAGE_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(json=json.load(f)) - client.project_client._client.default_fields.workitems = ( - "@basic,description" - ) + client._client.default_fields.workitems = "@basic,description" - work_items = client.get_all_work_items("") + work_items = client.work_items.get_all("") query = { "fields[workitems]": "@basic,description", @@ -143,13 +141,12 @@ def test_get_all_work_items_single_page( assert dict(reqs[0].url.params) == query assert work_items[0] == polarion_api.WorkItem( "MyWorkItemId2", - "Title", - "text/html", - "My text value", - "task", - "open", - {"capella_uuid": "asdfgh", "checksum": "123"}, - [ + title="Title", + description=polarion_api.HtmlContent("My text value"), + type="task", + status="open", + additional_attributes={"capella_uuid": "asdfgh", "checksum": "123"}, + linked_work_items=[ polarion_api.WorkItemLink( "MyWorkItemId2", "MyLinkedWorkItemId", @@ -158,16 +155,17 @@ def test_get_all_work_items_single_page( "MyProjectId", ) ], - [polarion_api.WorkItemAttachment("MyWorkItemId2", "MyAttachmentId")], + attachments=[ + polarion_api.WorkItemAttachment("MyWorkItemId2", "MyAttachmentId") + ], ) - assert "checksum" not in work_items[0].additional_attributes - assert work_items[0].get_current_checksum() == "123" assert work_items[0].home_document.module_folder == "MySpaceId" assert work_items[0].home_document.module_name == "MyDocumentId" + assert "checksum" in work_items[0].additional_attributes def test_get_all_work_items_faulty_item( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_WI_ERROR_NEXT_PAGE_RESPONSE, encoding="utf8") as f: @@ -176,7 +174,7 @@ def test_get_all_work_items_faulty_item( with open(TEST_WI_NO_NEXT_PAGE_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(json=json.load(f)) - work_items = client.get_all_work_items("") + work_items = client.work_items.get_all("") reqs = httpx_mock.get_requests() assert reqs[0].method == "GET" assert len(work_items) == 1 @@ -184,14 +182,14 @@ def test_get_all_work_items_faulty_item( def test_create_work_item( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item: polarion_api.WorkItem, ): with open(TEST_WI_CREATED_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(201, json=json.load(f)) - client.create_work_item(work_item) + client.work_items.create(work_item) req = httpx_mock.get_request() assert req is not None and req.method == "POST" @@ -201,29 +199,8 @@ def test_create_work_item( assert json.loads(req.content.decode()) == expected -def test_create_work_item_checksum( - client: polarion_api.OpenAPIPolarionProjectClient, - httpx_mock: pytest_httpx.HTTPXMock, - work_item: polarion_api.WorkItem, -): - with open(TEST_WI_CREATED_RESPONSE, encoding="utf8") as f: - httpx_mock.add_response(201, json=json.load(f)) - - checksum = work_item.calculate_checksum() - - client.project_client.work_items.add_work_item_checksum = True - client.create_work_item(work_item) - - req = httpx_mock.get_request() - with open(TEST_WI_POST_REQUEST, encoding="utf8") as f: - expected = json.load(f) - - expected["data"][0]["attributes"]["checksum"] = checksum - assert json.loads(req.content.decode()) == expected - - def test_create_work_items_successfully( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item: polarion_api.WorkItem, ): @@ -233,7 +210,7 @@ def test_create_work_items_successfully( mock_response["data"] *= 3 httpx_mock.add_response(201, json=mock_response) - client.create_work_items(3 * [work_item]) + client.work_items.create(3 * [work_item]) req = httpx_mock.get_request() @@ -245,7 +222,7 @@ def test_create_work_items_successfully( def test_create_work_item_in_document( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item: polarion_api.WorkItem, ): @@ -257,7 +234,7 @@ def test_create_work_item_in_document( work_item.home_document = polarion_api.DocumentReference( "space", "document" ) - client.create_work_items([work_item]) + client.work_items.create(work_item) req = httpx_mock.get_request() @@ -269,7 +246,7 @@ def test_create_work_item_in_document( def test_create_work_items_batch_exceed_successfully( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item: polarion_api.WorkItem, ): @@ -280,7 +257,7 @@ def test_create_work_items_batch_exceed_successfully( httpx_mock.add_response(201, json=mock_response) httpx_mock.add_response(201, json=mock_response) - client.create_work_items(6 * [work_item]) + client.work_items.create(6 * [work_item]) reqs = httpx_mock.get_requests() @@ -295,7 +272,7 @@ def test_create_work_items_batch_exceed_successfully( def test_create_work_items_slit_by_content_size_successfully( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item: polarion_api.WorkItem, ): @@ -312,10 +289,9 @@ def test_create_work_items_slit_by_content_size_successfully( work_item_long = polarion_api.WorkItem( title="Title", - description_type="text/html", - description="AB" * 512 * 1024, - status="open", + description=polarion_api.HtmlContent("AB" * 512 * 1024), type="task", + status="open", additional_attributes={"capella_uuid": "asdfg"}, ) @@ -328,7 +304,7 @@ def test_create_work_items_slit_by_content_size_successfully( copy.deepcopy(work_item_long), ] - client.create_work_items(work_items) + client.work_items.create(work_items) reqs = httpx_mock.get_requests() assert len(reqs) == 3 @@ -343,20 +319,19 @@ def test_create_work_items_slit_by_content_size_successfully( def test_create_work_items_content_exceed_error( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item: polarion_api.WorkItem, ): work_item_long = polarion_api.WorkItem( title="Title", - description_type="text/html", - description="AB" * 1024 * 1024, - status="open", + description=polarion_api.HtmlContent("AB" * 1024 * 1024), type="task", + status="open", additional_attributes={"capella_uuid": "asdfg"}, ) with pytest.raises(polarion_api.PolarionWorkItemException) as exc_info: - client.create_work_items(3 * [work_item, work_item_long]) + client.work_items.create(3 * [work_item, work_item_long]) assert exc_info.value.work_item == work_item_long assert ( @@ -367,7 +342,7 @@ def test_create_work_items_content_exceed_error( def test_create_work_items_failed( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item: polarion_api.WorkItem, ): @@ -376,10 +351,13 @@ def test_create_work_items_failed( " : BEGIN_OBJECT (at $.data)" ) with open(TEST_ERROR_RESPONSE, encoding="utf8") as f: - httpx_mock.add_response(400, json=json.load(f)) + response = json.load(f) + + httpx_mock.add_response(400, json=response) + httpx_mock.add_response(400, json=response) with pytest.raises(polarion_api.PolarionApiException) as exc_info: - client.create_work_items(3 * [work_item]) + client.work_items.create(3 * [work_item]) assert exc_info.type is polarion_api.PolarionApiException assert exc_info.value.args[0] == 400 @@ -388,14 +366,15 @@ def test_create_work_items_failed( def test_create_work_items_failed_no_error( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item: polarion_api.WorkItem, ): httpx_mock.add_response(501, content=b"asdfg") + httpx_mock.add_response(501, content=b"asdfg") with pytest.raises(polarion_api.PolarionApiBaseException) as exc_info: - client.create_work_items(3 * [work_item]) + client.work_items.create(3 * [work_item]) assert exc_info.type is polarion_api.PolarionApiUnexpectedException assert exc_info.value.args[0] == 501 @@ -403,13 +382,13 @@ def test_create_work_items_failed_no_error( def test_update_work_item_completely( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, work_item_patch: polarion_api.WorkItem, ): httpx_mock.add_response(204) - client.update_work_item(work_item_patch) + client.work_items.update(work_item_patch) req = httpx_mock.get_request() @@ -420,40 +399,16 @@ def test_update_work_item_completely( assert json.loads(req.content.decode()) == json.load(f) -def test_update_work_item_completely_checksum( - client: polarion_api.OpenAPIPolarionProjectClient, - httpx_mock: pytest_httpx.HTTPXMock, - work_item_patch: polarion_api.WorkItem, - mocker: mock.MockerFixture, -): - httpx_mock.add_response(204) - - spy = mocker.spy(work_item_patch, "calculate_checksum") - - checksum = work_item_patch.calculate_checksum() - client.project_client.work_items.add_work_item_checksum = True - client.update_work_item(work_item_patch) - - req = httpx_mock.get_request() - with open(TEST_WI_PATCH_COMPLETELY_REQUEST, encoding="utf8") as f: - request = json.load(f) - - request["data"]["attributes"]["checksum"] = checksum - assert json.loads(req.content.decode()) == request - spy.assert_called_once() - - def test_update_work_item_description( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) - client.update_work_item( + client.work_items.update( polarion_api.WorkItem( id="MyWorkItemId", - description_type="text/html", - description="My text value", + description=polarion_api.HtmlContent("My text value"), ) ) @@ -466,16 +421,13 @@ def test_update_work_item_description( def test_update_work_item_title( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) - client.update_work_item( - polarion_api.WorkItem( - id="MyWorkItemId", - title="Title", - ) + client.work_items.update( + polarion_api.WorkItem(id="MyWorkItemId", title="Title") ) req = httpx_mock.get_request() @@ -487,16 +439,13 @@ def test_update_work_item_title( def test_update_work_item_status( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) - client.update_work_item( - polarion_api.WorkItem( - id="MyWorkItemId", - status="open", - ) + client.work_items.update( + polarion_api.WorkItem(id="MyWorkItemId", status="open") ) req = httpx_mock.get_request() @@ -510,17 +459,13 @@ def test_update_work_item_status( def test_update_work_item_type( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) - client.update_work_item( - polarion_api.WorkItem( - id="MyWorkItemId", - type="newType", - status="open", - ) + client.work_items.update( + polarion_api.WorkItem(id="MyWorkItemId", type="newType", status="open") ) req = httpx_mock.get_request() @@ -533,12 +478,12 @@ def test_update_work_item_type( def test_delete_work_item_status_mode( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) - client.delete_work_item("MyWorkItemId") + client.work_items.delete(polarion_api.WorkItem("MyWorkItemId")) req = httpx_mock.get_request() assert req is not None and req.method == "PATCH" @@ -547,14 +492,14 @@ def test_delete_work_item_status_mode( def test_delete_work_item_delete_mode( - client: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): httpx_mock.add_response(204) - client.project_client.work_items.delete_status = None + client.work_items.delete_status = None - client.delete_work_item("MyWorkItemId") + client.work_items.delete(polarion_api.WorkItem("MyWorkItemId")) req = httpx_mock.get_request() assert req is not None and req.method == "DELETE" diff --git a/tests/test_client_workitems_custom.py b/tests/test_client_workitems_custom.py index 1228ebaf..ca795f10 100644 --- a/tests/test_client_workitems_custom.py +++ b/tests/test_client_workitems_custom.py @@ -17,28 +17,27 @@ def test_get_all_work_items_single_page_custom_work_item( - client_custom_work_item: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_WI_NO_NEXT_PAGE_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(json=json.load(f)) - work_items = client_custom_work_item.get_all_work_items("") + work_items = client.work_items.get_all("", work_item_cls=CustomWorkItem) assert isinstance(work_items[0], CustomWorkItem) assert work_items[0].capella_uuid == "asdfgh" def test_create_work_item_custom_work_item( - client_custom_work_item: polarion_api.OpenAPIPolarionProjectClient, + client: polarion_api.ProjectClient, httpx_mock: pytest_httpx.HTTPXMock, ): with open(TEST_WI_CREATED_RESPONSE, encoding="utf8") as f: httpx_mock.add_response(201, json=json.load(f)) work_item = CustomWorkItem( title="Title", - description_type="text/html", - description="My text value", + description=polarion_api.HtmlContent("My text value"), status="open", type="task", capella_uuid="asdfgh", @@ -46,7 +45,7 @@ def test_create_work_item_custom_work_item( work_item.capella_uuid = "asdfg" - client_custom_work_item.create_work_item(work_item) + client.work_items.create(work_item) req = httpx_mock.get_request() assert req.method == "POST"