From bf74d4163e5341567fa7799e2219e844e076a16f Mon Sep 17 00:00:00 2001 From: Lukasz Lancucki Date: Fri, 13 Mar 2026 09:43:28 +0000 Subject: [PATCH] feat(helpdesk): implement helpdesk chat API with sync and async support, including E2E and unit tests --- e2e_config.test.json | 1 + mpt_api_client/mpt_client.py | 12 +++++ mpt_api_client/resources/__init__.py | 3 ++ mpt_api_client/resources/helpdesk/__init__.py | 3 ++ mpt_api_client/resources/helpdesk/chats.py | 46 +++++++++++++++++++ mpt_api_client/resources/helpdesk/helpdesk.py | 26 +++++++++++ tests/e2e/helpdesk/__init__.py | 0 tests/e2e/helpdesk/chats/__init__.py | 0 tests/e2e/helpdesk/chats/conftest.py | 11 +++++ tests/e2e/helpdesk/chats/test_async_chats.py | 36 +++++++++++++++ tests/e2e/helpdesk/chats/test_sync_chats.py | 36 +++++++++++++++ tests/unit/resources/helpdesk/__init__.py | 0 tests/unit/resources/helpdesk/test_chats.py | 33 +++++++++++++ .../unit/resources/helpdesk/test_helpdesk.py | 46 +++++++++++++++++++ tests/unit/test_mpt_client.py | 4 ++ 15 files changed, 257 insertions(+) create mode 100644 mpt_api_client/resources/helpdesk/__init__.py create mode 100644 mpt_api_client/resources/helpdesk/chats.py create mode 100644 mpt_api_client/resources/helpdesk/helpdesk.py create mode 100644 tests/e2e/helpdesk/__init__.py create mode 100644 tests/e2e/helpdesk/chats/__init__.py create mode 100644 tests/e2e/helpdesk/chats/conftest.py create mode 100644 tests/e2e/helpdesk/chats/test_async_chats.py create mode 100644 tests/e2e/helpdesk/chats/test_sync_chats.py create mode 100644 tests/unit/resources/helpdesk/__init__.py create mode 100644 tests/unit/resources/helpdesk/test_chats.py create mode 100644 tests/unit/resources/helpdesk/test_helpdesk.py diff --git a/e2e_config.test.json b/e2e_config.test.json index f7661d9f..a24bea2c 100644 --- a/e2e_config.test.json +++ b/e2e_config.test.json @@ -56,6 +56,7 @@ "commerce.product.listing.id": "LST-5489-0806", "commerce.product.template.id": "TPL-1767-7355-0002", "commerce.user.id": "USR-4303-2348", + "helpdesk.chat.id": "CHT-5064-0262-3671", "commerce.subscription.agreement.id": "AGR-2473-3299-1721", "commerce.subscription.id": "SUB-3678-1831-2188", "commerce.subscription.product.item.id": "ITM-1767-7355-0001", diff --git a/mpt_api_client/mpt_client.py b/mpt_api_client/mpt_client.py index e6391903..5fcb4105 100644 --- a/mpt_api_client/mpt_client.py +++ b/mpt_api_client/mpt_client.py @@ -8,11 +8,13 @@ AsyncBilling, AsyncCatalog, AsyncCommerce, + AsyncHelpdesk, AsyncNotifications, Audit, Billing, Catalog, Commerce, + Helpdesk, Notifications, ) @@ -70,6 +72,11 @@ def notifications(self) -> AsyncNotifications: """Notifications MPT API Client.""" return AsyncNotifications(http_client=self.http_client) + @property + def helpdesk(self) -> AsyncHelpdesk: + """Helpdesk MPT API Client.""" + return AsyncHelpdesk(http_client=self.http_client) + class MPTClient: """MPT API Client.""" @@ -128,3 +135,8 @@ def accounts(self) -> Accounts: def notifications(self) -> Notifications: """Notifications MPT API Client.""" return Notifications(http_client=self.http_client) + + @property + def helpdesk(self) -> Helpdesk: + """Helpdesk MPT API Client.""" + return Helpdesk(http_client=self.http_client) diff --git a/mpt_api_client/resources/__init__.py b/mpt_api_client/resources/__init__.py index 02316061..1f53f1c0 100644 --- a/mpt_api_client/resources/__init__.py +++ b/mpt_api_client/resources/__init__.py @@ -3,6 +3,7 @@ from mpt_api_client.resources.billing import AsyncBilling, Billing from mpt_api_client.resources.catalog import AsyncCatalog, Catalog from mpt_api_client.resources.commerce import AsyncCommerce, Commerce +from mpt_api_client.resources.helpdesk import AsyncHelpdesk, Helpdesk from mpt_api_client.resources.notifications import AsyncNotifications, Notifications __all__ = [ # noqa: WPS410 @@ -12,10 +13,12 @@ "AsyncBilling", "AsyncCatalog", "AsyncCommerce", + "AsyncHelpdesk", "AsyncNotifications", "Audit", "Billing", "Catalog", "Commerce", + "Helpdesk", "Notifications", ] diff --git a/mpt_api_client/resources/helpdesk/__init__.py b/mpt_api_client/resources/helpdesk/__init__.py new file mode 100644 index 00000000..f4be37c2 --- /dev/null +++ b/mpt_api_client/resources/helpdesk/__init__.py @@ -0,0 +1,3 @@ +from mpt_api_client.resources.helpdesk.helpdesk import AsyncHelpdesk, Helpdesk + +__all__ = ["AsyncHelpdesk", "Helpdesk"] # noqa: WPS410 diff --git a/mpt_api_client/resources/helpdesk/chats.py b/mpt_api_client/resources/helpdesk/chats.py new file mode 100644 index 00000000..f2360efb --- /dev/null +++ b/mpt_api_client/resources/helpdesk/chats.py @@ -0,0 +1,46 @@ +from mpt_api_client.http import AsyncService, Service +from mpt_api_client.http.mixins import ( + AsyncCollectionMixin, + AsyncCreateMixin, + AsyncGetMixin, + AsyncUpdateMixin, + CollectionMixin, + CreateMixin, + GetMixin, + UpdateMixin, +) +from mpt_api_client.models import Model + + +class Chat(Model): + """Helpdesk Chat resource.""" + + +class ChatsServiceConfig: + """Helpdesk Chats service configuration.""" + + _endpoint = "/public/v1/helpdesk/chats" + _model_class = Chat + _collection_key = "data" + + +class ChatsService( + CreateMixin[Chat], + UpdateMixin[Chat], + GetMixin[Chat], + CollectionMixin[Chat], + Service[Chat], + ChatsServiceConfig, +): + """Helpdesk Chats service.""" + + +class AsyncChatsService( + AsyncCreateMixin[Chat], + AsyncUpdateMixin[Chat], + AsyncGetMixin[Chat], + AsyncCollectionMixin[Chat], + AsyncService[Chat], + ChatsServiceConfig, +): + """Async Helpdesk Chats service.""" diff --git a/mpt_api_client/resources/helpdesk/helpdesk.py b/mpt_api_client/resources/helpdesk/helpdesk.py new file mode 100644 index 00000000..4d3541d4 --- /dev/null +++ b/mpt_api_client/resources/helpdesk/helpdesk.py @@ -0,0 +1,26 @@ +from mpt_api_client.http import AsyncHTTPClient, HTTPClient +from mpt_api_client.resources.helpdesk.chats import AsyncChatsService, ChatsService + + +class Helpdesk: + """Helpdesk MPT API Module.""" + + def __init__(self, http_client: HTTPClient): + self.http_client = http_client + + @property + def chats(self) -> ChatsService: + """Chats service.""" + return ChatsService(http_client=self.http_client) + + +class AsyncHelpdesk: + """Async Helpdesk MPT API Module.""" + + def __init__(self, http_client: AsyncHTTPClient): + self.http_client = http_client + + @property + def chats(self) -> AsyncChatsService: + """Async Chats service.""" + return AsyncChatsService(http_client=self.http_client) diff --git a/tests/e2e/helpdesk/__init__.py b/tests/e2e/helpdesk/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/helpdesk/chats/__init__.py b/tests/e2e/helpdesk/chats/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e/helpdesk/chats/conftest.py b/tests/e2e/helpdesk/chats/conftest.py new file mode 100644 index 00000000..2637d735 --- /dev/null +++ b/tests/e2e/helpdesk/chats/conftest.py @@ -0,0 +1,11 @@ +import pytest + + +@pytest.fixture +def chat_id(e2e_config): + return e2e_config["helpdesk.chat.id"] + + +@pytest.fixture +def invalid_chat_id(): + return "CHT-0000-0000-0000" diff --git a/tests/e2e/helpdesk/chats/test_async_chats.py b/tests/e2e/helpdesk/chats/test_async_chats.py new file mode 100644 index 00000000..830e9bba --- /dev/null +++ b/tests/e2e/helpdesk/chats/test_async_chats.py @@ -0,0 +1,36 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError + + +async def test_get_chat(async_mpt_ops, chat_id): + service = async_mpt_ops.helpdesk.chats + + result = await service.get(chat_id) + + assert result.id == chat_id + + +async def test_list_chats(async_mpt_ops): + service = async_mpt_ops.helpdesk.chats + + result = await service.fetch_page(limit=1) + + assert len(result) > 0 + + +async def test_update_chat(async_mpt_ops, chat_id, short_uuid): + service = async_mpt_ops.helpdesk.chats + new_description = f"e2e update {short_uuid}" + + result = await service.update(chat_id, {"description": new_description}) + + assert result.id == chat_id + assert result.to_dict().get("description") == new_description + + +async def test_not_found(async_mpt_ops, invalid_chat_id): + service = async_mpt_ops.helpdesk.chats + + with pytest.raises(MPTAPIError): + await service.get(invalid_chat_id) diff --git a/tests/e2e/helpdesk/chats/test_sync_chats.py b/tests/e2e/helpdesk/chats/test_sync_chats.py new file mode 100644 index 00000000..787f90ea --- /dev/null +++ b/tests/e2e/helpdesk/chats/test_sync_chats.py @@ -0,0 +1,36 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError + + +def test_get_chat(mpt_ops, chat_id): + service = mpt_ops.helpdesk.chats + + result = service.get(chat_id) + + assert result.id == chat_id + + +def test_list_chats(mpt_ops): + service = mpt_ops.helpdesk.chats + + result = service.fetch_page(limit=1) + + assert len(result) > 0 + + +def test_update_chat(mpt_ops, chat_id, short_uuid): + service = mpt_ops.helpdesk.chats + new_description = f"e2e update {short_uuid}" + + result = service.update(chat_id, {"description": new_description}) + + assert result.id == chat_id + assert result.to_dict().get("description") == new_description + + +def test_not_found(mpt_ops, invalid_chat_id): + service = mpt_ops.helpdesk.chats + + with pytest.raises(MPTAPIError): + service.get(invalid_chat_id) diff --git a/tests/unit/resources/helpdesk/__init__.py b/tests/unit/resources/helpdesk/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/resources/helpdesk/test_chats.py b/tests/unit/resources/helpdesk/test_chats.py new file mode 100644 index 00000000..47cf24ad --- /dev/null +++ b/tests/unit/resources/helpdesk/test_chats.py @@ -0,0 +1,33 @@ +import pytest + +from mpt_api_client.resources.helpdesk.chats import AsyncChatsService, ChatsService + + +@pytest.fixture +def chats_service(http_client): + return ChatsService(http_client=http_client) + + +@pytest.fixture +def async_chats_service(async_http_client): + return AsyncChatsService(http_client=async_http_client) + + +@pytest.mark.parametrize( + "method", + ["get", "create", "update", "iterate"], +) +def test_mixins_present(chats_service, method): + result = hasattr(chats_service, method) + + assert result is True + + +@pytest.mark.parametrize( + "method", + ["get", "create", "update", "iterate"], +) +def test_async_mixins_present(async_chats_service, method): + result = hasattr(async_chats_service, method) + + assert result is True diff --git a/tests/unit/resources/helpdesk/test_helpdesk.py b/tests/unit/resources/helpdesk/test_helpdesk.py new file mode 100644 index 00000000..d4113f92 --- /dev/null +++ b/tests/unit/resources/helpdesk/test_helpdesk.py @@ -0,0 +1,46 @@ +import pytest + +from mpt_api_client.resources.helpdesk import AsyncHelpdesk, Helpdesk +from mpt_api_client.resources.helpdesk.chats import AsyncChatsService, ChatsService + + +def test_helpdesk_init(http_client): + result = Helpdesk(http_client=http_client) + + assert isinstance(result, Helpdesk) + assert result.http_client is http_client + + +def test_async_helpdesk_init(async_http_client): + result = AsyncHelpdesk(http_client=async_http_client) + + assert isinstance(result, AsyncHelpdesk) + assert result.http_client is async_http_client + + +@pytest.mark.parametrize( + ("attr_name", "expected"), + [ + ("chats", ChatsService), + ], +) +def test_helpdesk_properties(http_client, attr_name, expected): + helpdesk = Helpdesk(http_client=http_client) + + result = getattr(helpdesk, attr_name) + + assert isinstance(result, expected) + + +@pytest.mark.parametrize( + ("attr_name", "expected"), + [ + ("chats", AsyncChatsService), + ], +) +def test_async_helpdesk_properties(async_http_client, attr_name, expected): + helpdesk = AsyncHelpdesk(http_client=async_http_client) + + result = getattr(helpdesk, attr_name) + + assert isinstance(result, expected) diff --git a/tests/unit/test_mpt_client.py b/tests/unit/test_mpt_client.py index 5001f438..4cccab67 100644 --- a/tests/unit/test_mpt_client.py +++ b/tests/unit/test_mpt_client.py @@ -9,11 +9,13 @@ AsyncBilling, AsyncCatalog, AsyncCommerce, + AsyncHelpdesk, AsyncNotifications, Audit, Billing, Catalog, Commerce, + Helpdesk, Notifications, ) from tests.unit.conftest import API_TOKEN, API_URL @@ -32,6 +34,7 @@ def get_mpt_client(): ("billing", Billing), ("accounts", Accounts), ("notifications", Notifications), + ("helpdesk", Helpdesk), ], ) def test_mpt_client(resource_name: str, expected_type: type) -> None: @@ -62,6 +65,7 @@ def test_mpt_client_env(monkeypatch: pytest.MonkeyPatch) -> None: ("billing", AsyncBilling), ("accounts", AsyncAccounts), ("notifications", AsyncNotifications), + ("helpdesk", AsyncHelpdesk), ], ) def test_async_mpt_client(resource_name: str, expected_type: type) -> None: