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/cases.py b/mpt_api_client/resources/helpdesk/cases.py new file mode 100644 index 00000000..efeb4cc7 --- /dev/null +++ b/mpt_api_client/resources/helpdesk/cases.py @@ -0,0 +1,118 @@ +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, ResourceData + + +class Case(Model): + """Helpdesk case resource.""" + + +class CasesServiceConfig: + """Cases service configuration.""" + + _endpoint = "/public/v1/helpdesk/cases" + _model_class = Case + _collection_key = "data" + + +class CasesService( + CollectionMixin[Case], + CreateMixin[Case], + GetMixin[Case], + UpdateMixin[Case], + Service[Case], + CasesServiceConfig, +): + """Cases service.""" + + def complete(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: + """Switch case to complete state. + + Args: + resource_id: Case resource ID + resource_data: Case data will be updated + + Returns: + Updated case resource + """ + return self._resource_action(resource_id, "POST", "complete", json=resource_data) + + def process(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: + """Switch case to process state. + + Args: + resource_id: Case resource ID + resource_data: Case data will be updated + + Returns: + Updated case resource + """ + return self._resource_action(resource_id, "POST", "process", json=resource_data) + + def query(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: + """Switch case to query state. + + Args: + resource_id: Case resource ID + resource_data: Case data will be updated + + Returns: + Updated case resource + """ + return self._resource_action(resource_id, "POST", "query", json=resource_data) + + +class AsyncCasesService( + AsyncCollectionMixin[Case], + AsyncCreateMixin[Case], + AsyncGetMixin[Case], + AsyncUpdateMixin[Case], + AsyncService[Case], + CasesServiceConfig, +): + """Async cases service.""" + + async def complete(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: + """Switch case to complete state. + + Args: + resource_id: Case resource ID + resource_data: Case data will be updated + + Returns: + Updated case resource + """ + return await self._resource_action(resource_id, "POST", "complete", json=resource_data) + + async def process(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: + """Switch case to process state. + + Args: + resource_id: Case resource ID + resource_data: Case data will be updated + + Returns: + Updated case resource + """ + return await self._resource_action(resource_id, "POST", "process", json=resource_data) + + async def query(self, resource_id: str, resource_data: ResourceData | None = None) -> Case: + """Switch case to query state. + + Args: + resource_id: Case resource ID + resource_data: Case data will be updated + + Returns: + Updated case resource + """ + return await self._resource_action(resource_id, "POST", "query", json=resource_data) diff --git a/mpt_api_client/resources/helpdesk/helpdesk.py b/mpt_api_client/resources/helpdesk/helpdesk.py new file mode 100644 index 00000000..d68602a1 --- /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.cases import AsyncCasesService, CasesService + + +class Helpdesk: + """Helpdesk MPT API Module.""" + + def __init__(self, http_client: HTTPClient): + self.http_client = http_client + + @property + def cases(self) -> CasesService: + """Cases service.""" + return CasesService(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 cases(self) -> AsyncCasesService: + """Cases service.""" + return AsyncCasesService(http_client=self.http_client) diff --git a/tests/e2e/helpdesk/cases/conftest.py b/tests/e2e/helpdesk/cases/conftest.py new file mode 100644 index 00000000..ca3e9398 --- /dev/null +++ b/tests/e2e/helpdesk/cases/conftest.py @@ -0,0 +1,37 @@ +import pytest + + +def _queue_id_from_case(case): + case_data = case.to_dict() + queue_data = case_data.get("queue") + if not isinstance(queue_data, dict): + return None + + queue_id = queue_data.get("id") + if not isinstance(queue_id, str): + return None + + return queue_id + + +@pytest.fixture +def queue_id(mpt_ops): + cases = mpt_ops.helpdesk.cases.fetch_page(limit=1) + if not cases: + pytest.skip("No support case available to infer queue for create test.") + + queue_id = _queue_id_from_case(cases[0]) + if queue_id is None: + pytest.skip("No queue id available to create support case.") + + return queue_id + + +@pytest.fixture +def case_data(queue_id): + return {"queue": {"id": queue_id}} + + +@pytest.fixture +def case_update_data(): + return {"awaiting": True} diff --git a/tests/e2e/helpdesk/cases/test_async_cases.py b/tests/e2e/helpdesk/cases/test_async_cases.py new file mode 100644 index 00000000..afe795e1 --- /dev/null +++ b/tests/e2e/helpdesk/cases/test_async_cases.py @@ -0,0 +1,48 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError + +pytestmark = [pytest.mark.flaky] + + +@pytest.fixture +async def created_case(async_mpt_ops, case_data): + service = async_mpt_ops.helpdesk.cases + try: + return await service.create(case_data) + except MPTAPIError as error: + pytest.skip(f"Support case create is not available in this environment: {error.title}") + + +def test_create_case(created_case): + result = created_case.id + + assert result is not None + + +async def test_get_case(async_mpt_ops, created_case): + service = async_mpt_ops.helpdesk.cases + + result = await service.get(created_case.id) + + assert result.id == created_case.id + + +async def test_update_case( + async_mpt_ops, + created_case, + case_update_data, +): + service = async_mpt_ops.helpdesk.cases + + result = await service.update(created_case.id, case_update_data) + + assert result.id == created_case.id + + +async def test_list_cases(async_mpt_ops): + service = async_mpt_ops.helpdesk.cases + + result = await service.fetch_page(limit=1) + + assert result is not None diff --git a/tests/e2e/helpdesk/cases/test_sync_cases.py b/tests/e2e/helpdesk/cases/test_sync_cases.py new file mode 100644 index 00000000..bbf03885 --- /dev/null +++ b/tests/e2e/helpdesk/cases/test_sync_cases.py @@ -0,0 +1,44 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError + +pytestmark = [pytest.mark.flaky] + + +@pytest.fixture +def created_case(mpt_ops, case_data): + service = mpt_ops.helpdesk.cases + try: + return service.create(case_data) + except MPTAPIError as error: + pytest.skip(f"Support case create is not available in this environment: {error.title}") + + +def test_create_case(created_case): + result = created_case.id + + assert result is not None + + +def test_get_case(mpt_ops, created_case): + service = mpt_ops.helpdesk.cases + + result = service.get(created_case.id) + + assert result.id == created_case.id + + +def test_update_case(mpt_ops, created_case, case_update_data): + service = mpt_ops.helpdesk.cases + + result = service.update(created_case.id, case_update_data) + + assert result.id == created_case.id + + +def test_list_cases(mpt_ops): + service = mpt_ops.helpdesk.cases + + result = service.fetch_page(limit=1) + + assert result is not None diff --git a/tests/unit/resources/helpdesk/test_cases.py b/tests/unit/resources/helpdesk/test_cases.py new file mode 100644 index 00000000..fc24eb39 --- /dev/null +++ b/tests/unit/resources/helpdesk/test_cases.py @@ -0,0 +1,43 @@ +import pytest + +from mpt_api_client.resources.helpdesk.cases import AsyncCasesService, CasesService + + +@pytest.fixture +def cases_service(http_client): + return CasesService(http_client=http_client) + + +@pytest.fixture +def async_cases_service(async_http_client): + return AsyncCasesService(http_client=async_http_client) + + +@pytest.mark.parametrize( + "method", ["complete", "create", "get", "iterate", "process", "query", "update"] +) +def test_mixins_present(cases_service, method): + result = hasattr(cases_service, method) + + assert result is True + + +@pytest.mark.parametrize( + "method", ["complete", "create", "get", "iterate", "process", "query", "update"] +) +def test_async_mixins_present(async_cases_service, method): + result = hasattr(async_cases_service, method) + + assert result is True + + +def test_endpoint(cases_service): + result = cases_service.path == "/public/v1/helpdesk/cases" + + assert result is True + + +def test_async_endpoint(async_cases_service): + result = async_cases_service.path == "/public/v1/helpdesk/cases" + + 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..5e08db8a --- /dev/null +++ b/tests/unit/resources/helpdesk/test_helpdesk.py @@ -0,0 +1,38 @@ +import pytest + +from mpt_api_client.resources import AsyncHelpdesk, Helpdesk +from mpt_api_client.resources.helpdesk.cases import AsyncCasesService, CasesService + + +@pytest.fixture +def helpdesk(http_client): + return Helpdesk(http_client=http_client) + + +@pytest.fixture +def async_helpdesk(async_http_client): + return AsyncHelpdesk(http_client=async_http_client) + + +@pytest.mark.parametrize( + ("attr_name", "expected"), + [ + ("cases", CasesService), + ], +) +def test_helpdesk_properties(helpdesk, attr_name, expected): + result = getattr(helpdesk, attr_name) + + assert isinstance(result, expected) + + +@pytest.mark.parametrize( + ("attr_name", "expected"), + [ + ("cases", AsyncCasesService), + ], +) +def test_async_helpdesk_properties(async_helpdesk, attr_name, expected): + result = getattr(async_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: