diff --git a/mpt_api_client/resources/helpdesk/chat_answer_parameters.py b/mpt_api_client/resources/helpdesk/chat_answer_parameters.py new file mode 100644 index 0000000..5d62fdc --- /dev/null +++ b/mpt_api_client/resources/helpdesk/chat_answer_parameters.py @@ -0,0 +1,31 @@ +from mpt_api_client.http import AsyncService, Service +from mpt_api_client.http.mixins import AsyncCollectionMixin, CollectionMixin +from mpt_api_client.models import Model + + +class ChatAnswerParameter(Model): + """Helpdesk chat answer parameter resource.""" + + +class ChatAnswerParametersServiceConfig: + """Helpdesk chat answer parameters service configuration.""" + + _endpoint = "/public/v1/helpdesk/chats/{chat_id}/answers/{answer_id}/parameters" + _model_class = ChatAnswerParameter + _collection_key = "data" + + +class ChatAnswerParametersService( + CollectionMixin[ChatAnswerParameter], + Service[ChatAnswerParameter], + ChatAnswerParametersServiceConfig, +): + """Helpdesk chat answer parameters service.""" + + +class AsyncChatAnswerParametersService( + AsyncCollectionMixin[ChatAnswerParameter], + AsyncService[ChatAnswerParameter], + ChatAnswerParametersServiceConfig, +): + """Async helpdesk chat answer parameters service.""" diff --git a/mpt_api_client/resources/helpdesk/chat_answers.py b/mpt_api_client/resources/helpdesk/chat_answers.py new file mode 100644 index 0000000..7f05935 --- /dev/null +++ b/mpt_api_client/resources/helpdesk/chat_answers.py @@ -0,0 +1,110 @@ +from mpt_api_client.http import AsyncService, Service, mixins +from mpt_api_client.models import Model, ResourceData +from mpt_api_client.resources.helpdesk.chat_answer_parameters import ( + AsyncChatAnswerParametersService, + ChatAnswerParametersService, +) + + +class ChatAnswer(Model): + """Helpdesk Chat Answer resource.""" + + +class ChatAnswersServiceConfig: + """Helpdesk Chat Answers service configuration.""" + + _endpoint = "/public/v1/helpdesk/chats/{chat_id}/answers" + _model_class = ChatAnswer + _collection_key = "data" + + +class ChatAnswersService( + mixins.CreateMixin[ChatAnswer], + mixins.UpdateMixin[ChatAnswer], + mixins.DeleteMixin, + mixins.GetMixin[ChatAnswer], + mixins.CollectionMixin[ChatAnswer], + Service[ChatAnswer], + ChatAnswersServiceConfig, +): + """Helpdesk Chat Answers service.""" + + def submit(self, resource_id: str, resource_data: ResourceData | None = None) -> ChatAnswer: + """Switch answer to submitted state.""" + return self._resource_action(resource_id, "POST", "submit", json=resource_data) + + def accept(self, resource_id: str, resource_data: ResourceData | None = None) -> ChatAnswer: + """Switch answer to accepted state.""" + return self._resource_action(resource_id, "POST", "accept", json=resource_data) + + def query(self, resource_id: str, resource_data: ResourceData | None = None) -> ChatAnswer: + """Switch answer to query state.""" + return self._resource_action(resource_id, "POST", "query", json=resource_data) + + def validate(self, resource_id: str, resource_data: ResourceData | None = None) -> ChatAnswer: + """Validate answer.""" + return self._resource_action(resource_id, "POST", "validate", json=resource_data) + + def parameters(self, answer_id: str) -> ChatAnswerParametersService: # noqa: WPS110 + """Return chat answer parameters service.""" + return ChatAnswerParametersService( + http_client=self.http_client, + endpoint_params={ + "chat_id": self.endpoint_params["chat_id"], + "answer_id": answer_id, + }, + ) + + +class AsyncChatAnswersService( + mixins.AsyncCreateMixin[ChatAnswer], + mixins.AsyncUpdateMixin[ChatAnswer], + mixins.AsyncDeleteMixin, + mixins.AsyncGetMixin[ChatAnswer], + mixins.AsyncCollectionMixin[ChatAnswer], + AsyncService[ChatAnswer], + ChatAnswersServiceConfig, +): + """Async Helpdesk Chat Answers service.""" + + async def submit( + self, + resource_id: str, + resource_data: ResourceData | None = None, + ) -> ChatAnswer: + """Switch answer to submitted state.""" + return await self._resource_action(resource_id, "POST", "submit", json=resource_data) + + async def accept( + self, + resource_id: str, + resource_data: ResourceData | None = None, + ) -> ChatAnswer: + """Switch answer to accepted state.""" + return await self._resource_action(resource_id, "POST", "accept", json=resource_data) + + async def query( + self, + resource_id: str, + resource_data: ResourceData | None = None, + ) -> ChatAnswer: + """Switch answer to query state.""" + return await self._resource_action(resource_id, "POST", "query", json=resource_data) + + async def validate( + self, + resource_id: str, + resource_data: ResourceData | None = None, + ) -> ChatAnswer: + """Validate answer.""" + return await self._resource_action(resource_id, "POST", "validate", json=resource_data) + + def parameters(self, answer_id: str) -> AsyncChatAnswerParametersService: # noqa: WPS110 + """Return async chat answer parameters service.""" + return AsyncChatAnswerParametersService( + http_client=self.http_client, + endpoint_params={ + "chat_id": self.endpoint_params["chat_id"], + "answer_id": answer_id, + }, + ) diff --git a/mpt_api_client/resources/helpdesk/chats.py b/mpt_api_client/resources/helpdesk/chats.py index 297794e..fefb536 100644 --- a/mpt_api_client/resources/helpdesk/chats.py +++ b/mpt_api_client/resources/helpdesk/chats.py @@ -10,6 +10,10 @@ UpdateMixin, ) from mpt_api_client.models import Model +from mpt_api_client.resources.helpdesk.chat_answers import ( + AsyncChatAnswersService, + ChatAnswersService, +) from mpt_api_client.resources.helpdesk.chat_attachments import ( AsyncChatAttachmentsService, ChatAttachmentsService, @@ -72,6 +76,12 @@ def participants(self, chat_id: str) -> ChatParticipantsService: http_client=self.http_client, endpoint_params={"chat_id": chat_id} ) + def answers(self, chat_id: str) -> ChatAnswersService: + """Return chat answers service.""" + return ChatAnswersService( + http_client=self.http_client, endpoint_params={"chat_id": chat_id} + ) + class AsyncChatsService( AsyncCreateMixin[Chat], @@ -106,3 +116,9 @@ def participants(self, chat_id: str) -> AsyncChatParticipantsService: return AsyncChatParticipantsService( http_client=self.http_client, endpoint_params={"chat_id": chat_id} ) + + def answers(self, chat_id: str) -> AsyncChatAnswersService: + """Return async chat answers service.""" + return AsyncChatAnswersService( + http_client=self.http_client, endpoint_params={"chat_id": chat_id} + ) diff --git a/tests/e2e/helpdesk/chats/answers/__init__.py b/tests/e2e/helpdesk/chats/answers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/e2e/helpdesk/chats/answers/conftest.py b/tests/e2e/helpdesk/chats/answers/conftest.py new file mode 100644 index 0000000..69bdf22 --- /dev/null +++ b/tests/e2e/helpdesk/chats/answers/conftest.py @@ -0,0 +1,42 @@ +import pytest + +from tests.e2e.helper import ( + async_create_fixture_resource_and_delete, + create_fixture_resource_and_delete, +) + + +@pytest.fixture +def chat_answers_service(mpt_ops, chat_id): + return mpt_ops.helpdesk.chats.answers(chat_id) + + +@pytest.fixture +def async_chat_answers_service(async_mpt_ops, chat_id): + return async_mpt_ops.helpdesk.chats.answers(chat_id) + + +@pytest.fixture +def chat_answer_data(short_uuid): + return { + "name": f"e2e answer {short_uuid}", + } + + +@pytest.fixture +def created_chat_answer(chat_answers_service, chat_answer_data): + with create_fixture_resource_and_delete(chat_answers_service, chat_answer_data) as chat_answer: + yield chat_answer + + +@pytest.fixture +async def async_created_chat_answer(async_chat_answers_service, chat_answer_data): + async with async_create_fixture_resource_and_delete( + async_chat_answers_service, chat_answer_data + ) as chat_answer: + yield chat_answer + + +@pytest.fixture +def invalid_chat_answer_id(): + return "ANS-0000-0000" diff --git a/tests/e2e/helpdesk/chats/answers/parameters/__init__.py b/tests/e2e/helpdesk/chats/answers/parameters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/e2e/helpdesk/chats/answers/parameters/conftest.py b/tests/e2e/helpdesk/chats/answers/parameters/conftest.py new file mode 100644 index 0000000..cebf39c --- /dev/null +++ b/tests/e2e/helpdesk/chats/answers/parameters/conftest.py @@ -0,0 +1,16 @@ +import pytest + + +@pytest.fixture +def chat_answer_parameters_service(mpt_ops, chat_id, created_chat_answer): + return mpt_ops.helpdesk.chats.answers(chat_id).parameters(created_chat_answer.id) + + +@pytest.fixture +def async_chat_answer_parameters_service(async_mpt_ops, chat_id, async_created_chat_answer): + return async_mpt_ops.helpdesk.chats.answers(chat_id).parameters(async_created_chat_answer.id) + + +@pytest.fixture +def invalid_chat_answer_parameter_id(): + return "PAR-0000-0000" diff --git a/tests/e2e/helpdesk/chats/answers/parameters/test_async_parameters.py b/tests/e2e/helpdesk/chats/answers/parameters/test_async_parameters.py new file mode 100644 index 0000000..8afb281 --- /dev/null +++ b/tests/e2e/helpdesk/chats/answers/parameters/test_async_parameters.py @@ -0,0 +1,28 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError + +pytestmark = [ + pytest.mark.flaky, + pytest.mark.skip(reason="Unskip after MPT-19124 completed"), +] + + +async def test_list_chat_answer_parameters(async_chat_answer_parameters_service): + result = await async_chat_answer_parameters_service.fetch_page(limit=20) + + assert len(result) >= 0 + + +async def test_iterate_chat_answer_parameters(async_chat_answer_parameters_service): + iterator = async_chat_answer_parameters_service.iterate(limit=20) + result = await anext(iterator, None) + + assert result is None or result.id is not None + + +async def test_not_found(async_mpt_ops, chat_id): + service = async_mpt_ops.helpdesk.chats.answers(chat_id).parameters("ANS-0000-0000") + + with pytest.raises(MPTAPIError): + await service.fetch_page(limit=20) diff --git a/tests/e2e/helpdesk/chats/answers/parameters/test_sync_parameters.py b/tests/e2e/helpdesk/chats/answers/parameters/test_sync_parameters.py new file mode 100644 index 0000000..031fec0 --- /dev/null +++ b/tests/e2e/helpdesk/chats/answers/parameters/test_sync_parameters.py @@ -0,0 +1,29 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError + +pytestmark = [ + pytest.mark.flaky, + pytest.mark.skip(reason="Unskip after MPT-19124 completed"), +] + + +def test_list_chat_answer_parameters(chat_answer_parameters_service): + result = chat_answer_parameters_service.fetch_page(limit=20) + + assert len(result) >= 0 + + +def test_iterate_chat_answer_parameters(chat_answer_parameters_service): + iterator = chat_answer_parameters_service.iterate(limit=20) + + result = next(iterator, None) + + assert result is None or result.id is not None + + +def test_not_found(mpt_ops, chat_id): + service = mpt_ops.helpdesk.chats.answers(chat_id).parameters("ANS-0000-0000") + + with pytest.raises(MPTAPIError): + service.fetch_page(limit=20) diff --git a/tests/e2e/helpdesk/chats/answers/test_async_answers.py b/tests/e2e/helpdesk/chats/answers/test_async_answers.py new file mode 100644 index 0000000..50bdd43 --- /dev/null +++ b/tests/e2e/helpdesk/chats/answers/test_async_answers.py @@ -0,0 +1,75 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError + +pytestmark = [ + pytest.mark.flaky, + pytest.mark.skip(reason="Unskip after MPT-19124 completed"), +] + + +async def test_get_chat_answer(async_chat_answers_service, async_created_chat_answer): + result = await async_chat_answers_service.get(async_created_chat_answer.id) + + assert result.id == async_created_chat_answer.id + + +async def test_list_chat_answers(async_chat_answers_service): + result = await async_chat_answers_service.fetch_page(limit=1) + + assert len(result) > 0 + + +def test_create_chat_answer(async_created_chat_answer): + result = async_created_chat_answer + + assert result is not None + + +async def test_update_chat_answer( + async_chat_answers_service, async_created_chat_answer, short_uuid +): + update_data = {"name": f"e2e updated answer {short_uuid}"} + + result = await async_chat_answers_service.update(async_created_chat_answer.id, update_data) + + assert result.id == async_created_chat_answer.id + assert result.to_dict().get("name") == update_data["name"] + + +async def test_submit_chat_answer(async_chat_answers_service, async_created_chat_answer): + result = await async_chat_answers_service.submit(async_created_chat_answer.id) + + assert result is not None + + +async def test_query_chat_answer(async_chat_answers_service, async_created_chat_answer): + submitted_chat_answer = await async_chat_answers_service.submit(async_created_chat_answer.id) + + result = await async_chat_answers_service.query(submitted_chat_answer.id) + + assert result is not None + + +async def test_validate_chat_answer(async_chat_answers_service, async_created_chat_answer): + result = await async_chat_answers_service.validate( + async_created_chat_answer.id, + {"parameters": []}, + ) + + assert result is not None + + +async def test_accept_chat_answer(async_chat_answers_service, async_created_chat_answer): + result = await async_chat_answers_service.accept(async_created_chat_answer.id) + + assert result is not None + + +async def test_delete_chat_answer(async_chat_answers_service, async_created_chat_answer): + await async_chat_answers_service.delete(async_created_chat_answer.id) # act + + +async def test_not_found(async_chat_answers_service, invalid_chat_answer_id): + with pytest.raises(MPTAPIError): + await async_chat_answers_service.get(invalid_chat_answer_id) diff --git a/tests/e2e/helpdesk/chats/answers/test_sync_answers.py b/tests/e2e/helpdesk/chats/answers/test_sync_answers.py new file mode 100644 index 0000000..9c71b51 --- /dev/null +++ b/tests/e2e/helpdesk/chats/answers/test_sync_answers.py @@ -0,0 +1,70 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError + +pytestmark = [ + pytest.mark.flaky, + pytest.mark.skip(reason="Unskip after MPT-19124 completed"), +] + + +def test_get_chat_answer(chat_answers_service, created_chat_answer): + result = chat_answers_service.get(created_chat_answer.id) + + assert result.id == created_chat_answer.id + + +def test_list_chat_answers(chat_answers_service): + result = chat_answers_service.fetch_page(limit=1) + + assert len(result) > 0 + + +def test_create_chat_answer(created_chat_answer): + result = created_chat_answer + + assert result is not None + + +def test_update_chat_answer(chat_answers_service, created_chat_answer, short_uuid): + update_data = {"name": f"e2e updated answer {short_uuid}"} + + result = chat_answers_service.update(created_chat_answer.id, update_data) + + assert result.id == created_chat_answer.id + assert result.to_dict().get("name") == update_data["name"] + + +def test_submit_chat_answer(chat_answers_service, created_chat_answer): + result = chat_answers_service.submit(created_chat_answer.id) + + assert result is not None + + +def test_query_chat_answer(chat_answers_service, created_chat_answer): + submitted_chat_answer = chat_answers_service.submit(created_chat_answer.id) + + result = chat_answers_service.query(submitted_chat_answer.id) + + assert result is not None + + +def test_validate_chat_answer(chat_answers_service, created_chat_answer): + result = chat_answers_service.validate(created_chat_answer.id, {"parameters": []}) + + assert result is not None + + +def test_accept_chat_answer(chat_answers_service, created_chat_answer): + result = chat_answers_service.accept(created_chat_answer.id) + + assert result is not None + + +def test_delete_chat_answer(chat_answers_service, created_chat_answer): + chat_answers_service.delete(created_chat_answer.id) # act + + +def test_not_found(chat_answers_service, invalid_chat_answer_id): + with pytest.raises(MPTAPIError): + chat_answers_service.get(invalid_chat_answer_id) diff --git a/tests/unit/resources/helpdesk/test_chat_answer_parameters.py b/tests/unit/resources/helpdesk/test_chat_answer_parameters.py new file mode 100644 index 0000000..fc047d8 --- /dev/null +++ b/tests/unit/resources/helpdesk/test_chat_answer_parameters.py @@ -0,0 +1,68 @@ +import pytest + +from mpt_api_client.resources.helpdesk.chat_answer_parameters import ( + AsyncChatAnswerParametersService, + ChatAnswerParametersService, +) + + +@pytest.fixture +def chat_answer_parameters_service(http_client) -> ChatAnswerParametersService: + return ChatAnswerParametersService( + http_client=http_client, + endpoint_params={"chat_id": "CHT-0000-0000-0001", "answer_id": "ANS-0000-0000"}, + ) + + +@pytest.fixture +def async_chat_answer_parameters_service(async_http_client) -> AsyncChatAnswerParametersService: + return AsyncChatAnswerParametersService( + http_client=async_http_client, + endpoint_params={"chat_id": "CHT-0000-0000-0001", "answer_id": "ANS-0000-0000"}, + ) + + +def test_endpoint(chat_answer_parameters_service) -> None: + result = ( + chat_answer_parameters_service.path + == "/public/v1/helpdesk/chats/CHT-0000-0000-0001/answers/ANS-0000-0000/parameters" + ) + + assert result is True + + +def test_async_endpoint(async_chat_answer_parameters_service) -> None: + result = ( + async_chat_answer_parameters_service.path + == "/public/v1/helpdesk/chats/CHT-0000-0000-0001/answers/ANS-0000-0000/parameters" + ) + + assert result is True + + +@pytest.mark.parametrize("method", ["fetch_page", "iterate"]) +def test_methods_present(chat_answer_parameters_service, method: str) -> None: + result = hasattr(chat_answer_parameters_service, method) + + assert result is True + + +@pytest.mark.parametrize("method", ["get", "create", "update", "delete"]) +def test_methods_absent(chat_answer_parameters_service, method: str) -> None: + result = hasattr(chat_answer_parameters_service, method) + + assert result is False + + +@pytest.mark.parametrize("method", ["fetch_page", "iterate"]) +def test_async_methods_present(async_chat_answer_parameters_service, method: str) -> None: + result = hasattr(async_chat_answer_parameters_service, method) + + assert result is True + + +@pytest.mark.parametrize("method", ["get", "create", "update", "delete"]) +def test_async_methods_absent(async_chat_answer_parameters_service, method: str) -> None: + result = hasattr(async_chat_answer_parameters_service, method) + + assert result is False diff --git a/tests/unit/resources/helpdesk/test_chat_answers.py b/tests/unit/resources/helpdesk/test_chat_answers.py new file mode 100644 index 0000000..b1fbd90 --- /dev/null +++ b/tests/unit/resources/helpdesk/test_chat_answers.py @@ -0,0 +1,146 @@ +import httpx +import pytest +import respx + +from mpt_api_client.resources.helpdesk.chat_answer_parameters import ( + AsyncChatAnswerParametersService, + ChatAnswerParametersService, +) +from mpt_api_client.resources.helpdesk.chat_answers import ( + AsyncChatAnswersService, + ChatAnswer, + ChatAnswersService, +) + + +@pytest.fixture +def chat_answers_service(http_client) -> ChatAnswersService: + return ChatAnswersService( + http_client=http_client, endpoint_params={"chat_id": "CHT-0000-0000-0001"} + ) + + +@pytest.fixture +def async_chat_answers_service(async_http_client) -> AsyncChatAnswersService: + return AsyncChatAnswersService( + http_client=async_http_client, + endpoint_params={"chat_id": "CHT-0000-0000-0001"}, + ) + + +def test_endpoint(chat_answers_service) -> None: + result = chat_answers_service.path == "/public/v1/helpdesk/chats/CHT-0000-0000-0001/answers" + + assert result is True + + +def test_async_endpoint(async_chat_answers_service) -> None: + result = ( + async_chat_answers_service.path == "/public/v1/helpdesk/chats/CHT-0000-0000-0001/answers" + ) + + assert result is True + + +@pytest.mark.parametrize( + "method", + [ + "get", + "create", + "update", + "delete", + "fetch_page", + "iterate", + "submit", + "accept", + "query", + "validate", + "parameters", + ], +) +def test_methods_present(chat_answers_service, method: str) -> None: + result = hasattr(chat_answers_service, method) + + assert result is True + + +@pytest.mark.parametrize( + "method", + [ + "get", + "create", + "update", + "delete", + "fetch_page", + "iterate", + "submit", + "accept", + "query", + "validate", + "parameters", + ], +) +def test_async_methods_present(async_chat_answers_service, method: str) -> None: + result = hasattr(async_chat_answers_service, method) + + assert result is True + + +def test_parameters_service(chat_answers_service) -> None: + result = chat_answers_service.parameters("ANS-1234-5678") + + assert isinstance(result, ChatAnswerParametersService) + assert result.endpoint_params == {"chat_id": "CHT-0000-0000-0001", "answer_id": "ANS-1234-5678"} + + +def test_async_parameters_service(async_chat_answers_service) -> None: + result = async_chat_answers_service.parameters("ANS-1234-5678") + + assert isinstance(result, AsyncChatAnswerParametersService) + assert result.endpoint_params == {"chat_id": "CHT-0000-0000-0001", "answer_id": "ANS-1234-5678"} + + +@pytest.mark.parametrize("action", ["submit", "accept", "query", "validate"]) +def test_custom_resource_actions(chat_answers_service, action): + response_expected_data = {"id": "ANS-1234-5678", "status": "Updated"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/helpdesk/chats/CHT-0000-0000-0001/answers/ANS-1234-5678/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = getattr(chat_answers_service, action)("ANS-1234-5678") + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == b"" + assert result.to_dict() == response_expected_data + assert isinstance(result, ChatAnswer) + + +@pytest.mark.parametrize("action", ["submit", "accept", "query", "validate"]) +async def test_async_custom_resource_actions(async_chat_answers_service, action): + response_expected_data = {"id": "ANS-1234-5678", "status": "Updated"} + with respx.mock: + mock_route = respx.post( + f"https://api.example.com/public/v1/helpdesk/chats/CHT-0000-0000-0001/answers/ANS-1234-5678/{action}" + ).mock( + return_value=httpx.Response( + status_code=httpx.codes.OK, + headers={"content-type": "application/json"}, + json=response_expected_data, + ) + ) + + result = await getattr(async_chat_answers_service, action)("ANS-1234-5678") + + assert mock_route.call_count == 1 + request = mock_route.calls[0].request + assert request.content == b"" + assert result.to_dict() == response_expected_data + assert isinstance(result, ChatAnswer) diff --git a/tests/unit/resources/helpdesk/test_chats.py b/tests/unit/resources/helpdesk/test_chats.py index 82dfd8e..e29eb88 100644 --- a/tests/unit/resources/helpdesk/test_chats.py +++ b/tests/unit/resources/helpdesk/test_chats.py @@ -1,5 +1,9 @@ import pytest +from mpt_api_client.resources.helpdesk.chat_answers import ( + AsyncChatAnswersService, + ChatAnswersService, +) from mpt_api_client.resources.helpdesk.chat_attachments import ( AsyncChatAttachmentsService, ChatAttachmentsService, @@ -55,6 +59,7 @@ def test_async_mixins_present(async_chats_service, method): ("messages", ChatMessagesService), ("links", ChatLinksService), ("participants", ChatParticipantsService), + ("answers", ChatAnswersService), ], ) def test_property_services(chats_service, service_method, expected_service_class): @@ -77,6 +82,7 @@ def test_attachments_service(chats_service): ("messages", AsyncChatMessagesService), ("links", AsyncChatLinksService), ("participants", AsyncChatParticipantsService), + ("answers", AsyncChatAnswersService), ], ) def test_async_property_services(async_chats_service, service_method, expected_service_class):