Skip to content

Commit ec60985

Browse files
authored
MPT-19029 Add helpdesk chat links API services and tests (#237)
## Summary - Implement chat links datapoints for Helpdesk chats: - `GET/POST /public/v1/helpdesk/chats/{chat_id}/links` - `PUT/DELETE /public/v1/helpdesk/chats/{chat_id}/links/{id}` - Add sync and async `chat_links` services and wire nested accessors via `chats.links(chat_id)` - Add unit tests for links service endpoint/mixins and chats-to-links wiring - Add e2e test scaffolding for helpdesk chat links (sync and async) following existing chat messages structure ## Testing - `uv run pytest tests/unit/resources/helpdesk/test_chat_links.py tests/unit/resources/helpdesk/test_chats.py` - `make check` - E2E tests: Not run (not requested) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> Closes [MPT-19029](https://softwareone.atlassian.net/browse/MPT-19029) ## Changes - Added new `ChatLink` model and `ChatLinksServiceConfig` for helpdesk chat links API endpoint `/public/v1/helpdesk/chats/{chat_id}/links` - Implemented `ChatLinksService` and `AsyncChatLinksService` with full CRUD operations (Create, Update, Delete, Collection) - Added `links(chat_id: str)` accessor methods to `ChatsService` and `AsyncChatsService` for nested access to chat link resources - Added comprehensive unit tests for chat links services verifying endpoint paths and available operations - Enhanced chat service tests to validate both messages and links property accessors with parameterized test coverage for sync and async variants - Added end-to-end test scaffolding with fixtures for chat link operations (create, update, delete, list) in both synchronous and asynchronous variants <!-- end of auto-generated comment: release notes by coderabbit.ai --> [MPT-19029]: https://softwareone.atlassian.net/browse/MPT-19029?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2 parents b3df508 + f9633bd commit ec60985

File tree

8 files changed

+262
-6
lines changed

8 files changed

+262
-6
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from mpt_api_client.http import AsyncService, Service
2+
from mpt_api_client.http.mixins import (
3+
AsyncCollectionMixin,
4+
AsyncCreateMixin,
5+
AsyncDeleteMixin,
6+
AsyncUpdateMixin,
7+
CollectionMixin,
8+
CreateMixin,
9+
DeleteMixin,
10+
UpdateMixin,
11+
)
12+
from mpt_api_client.models import Model
13+
14+
15+
class ChatLink(Model):
16+
"""Helpdesk Chat Link resource."""
17+
18+
19+
class ChatLinksServiceConfig:
20+
"""Helpdesk Chat Links service configuration."""
21+
22+
_endpoint = "/public/v1/helpdesk/chats/{chat_id}/links"
23+
_model_class = ChatLink
24+
_collection_key = "data"
25+
26+
27+
class ChatLinksService(
28+
CreateMixin[ChatLink],
29+
UpdateMixin[ChatLink],
30+
DeleteMixin,
31+
CollectionMixin[ChatLink],
32+
Service[ChatLink],
33+
ChatLinksServiceConfig,
34+
):
35+
"""Helpdesk Chat Links service."""
36+
37+
38+
class AsyncChatLinksService(
39+
AsyncCreateMixin[ChatLink],
40+
AsyncUpdateMixin[ChatLink],
41+
AsyncDeleteMixin,
42+
AsyncCollectionMixin[ChatLink],
43+
AsyncService[ChatLink],
44+
ChatLinksServiceConfig,
45+
):
46+
"""Async Helpdesk Chat Links service."""

mpt_api_client/resources/helpdesk/chats.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
UpdateMixin,
1111
)
1212
from mpt_api_client.models import Model
13+
from mpt_api_client.resources.helpdesk.chat_links import (
14+
AsyncChatLinksService,
15+
ChatLinksService,
16+
)
1317
from mpt_api_client.resources.helpdesk.chat_messages import (
1418
AsyncChatMessagesService,
1519
ChatMessagesService,
@@ -44,6 +48,10 @@ def messages(self, chat_id: str) -> ChatMessagesService:
4448
http_client=self.http_client, endpoint_params={"chat_id": chat_id}
4549
)
4650

51+
def links(self, chat_id: str) -> ChatLinksService:
52+
"""Return chat links service."""
53+
return ChatLinksService(http_client=self.http_client, endpoint_params={"chat_id": chat_id})
54+
4755

4856
class AsyncChatsService(
4957
AsyncCreateMixin[Chat],
@@ -60,3 +68,9 @@ def messages(self, chat_id: str) -> AsyncChatMessagesService:
6068
return AsyncChatMessagesService(
6169
http_client=self.http_client, endpoint_params={"chat_id": chat_id}
6270
)
71+
72+
def links(self, chat_id: str) -> AsyncChatLinksService:
73+
"""Return async chat links service."""
74+
return AsyncChatLinksService(
75+
http_client=self.http_client, endpoint_params={"chat_id": chat_id}
76+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import pytest
2+
3+
from tests.e2e.helper import (
4+
async_create_fixture_resource_and_delete,
5+
create_fixture_resource_and_delete,
6+
)
7+
8+
9+
@pytest.fixture
10+
def chat_links_service(mpt_ops, chat_id):
11+
return mpt_ops.helpdesk.chats.links(chat_id)
12+
13+
14+
@pytest.fixture
15+
def async_chat_links_service(async_mpt_ops, chat_id):
16+
return async_mpt_ops.helpdesk.chats.links(chat_id)
17+
18+
19+
@pytest.fixture
20+
def chat_link_data(short_uuid):
21+
return {
22+
"uri": f"https://example.com/e2e-link-{short_uuid}",
23+
"name": f"e2e link - {short_uuid}",
24+
}
25+
26+
27+
@pytest.fixture
28+
def created_chat_link(chat_links_service, chat_link_data):
29+
with create_fixture_resource_and_delete(chat_links_service, chat_link_data) as chat_link:
30+
yield chat_link
31+
32+
33+
@pytest.fixture
34+
async def async_created_chat_link(async_chat_links_service, chat_link_data):
35+
async with async_create_fixture_resource_and_delete(
36+
async_chat_links_service, chat_link_data
37+
) as chat_link:
38+
yield chat_link
39+
40+
41+
@pytest.fixture
42+
def invalid_chat_link_id():
43+
return "LNK-0000-0000-0000"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import pytest
2+
3+
from mpt_api_client.exceptions import MPTAPIError
4+
5+
pytestmark = [pytest.mark.flaky]
6+
7+
8+
async def test_list_chat_links(async_chat_links_service):
9+
result = await async_chat_links_service.fetch_page(limit=1)
10+
11+
assert len(result) > 0
12+
13+
14+
def test_create_chat_link(async_created_chat_link, chat_link_data): # noqa: AAA01
15+
assert async_created_chat_link.id is not None
16+
assert async_created_chat_link.to_dict().get("uri") == chat_link_data["uri"]
17+
18+
19+
async def test_update_chat_link_name(async_chat_links_service, async_created_chat_link, short_uuid):
20+
new_name = f"e2e updated link - {short_uuid}"
21+
22+
result = await async_chat_links_service.update(
23+
async_created_chat_link.id,
24+
{"name": new_name},
25+
)
26+
27+
assert result.id == async_created_chat_link.id
28+
assert result.to_dict().get("name") == new_name
29+
30+
31+
async def test_delete_chat_link(async_chat_links_service, async_created_chat_link):
32+
result = async_created_chat_link
33+
34+
await async_chat_links_service.delete(result.id)
35+
36+
37+
async def test_update_chat_link_not_found(async_chat_links_service, invalid_chat_link_id):
38+
with pytest.raises(MPTAPIError, match=r"404 Not Found"):
39+
await async_chat_links_service.update(
40+
invalid_chat_link_id,
41+
{"name": "updated name"},
42+
)
43+
44+
45+
async def test_delete_chat_link_not_found(async_chat_links_service, invalid_chat_link_id):
46+
with pytest.raises(MPTAPIError, match=r"404 Not Found"):
47+
await async_chat_links_service.delete(invalid_chat_link_id)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import pytest
2+
3+
from mpt_api_client.exceptions import MPTAPIError
4+
5+
pytestmark = [pytest.mark.flaky]
6+
7+
8+
def test_list_chat_links(chat_links_service):
9+
result = chat_links_service.fetch_page(limit=1)
10+
11+
assert len(result) > 0
12+
13+
14+
def test_create_chat_link(created_chat_link, chat_link_data): # noqa: AAA01
15+
assert created_chat_link.id is not None
16+
assert created_chat_link.to_dict().get("uri") == chat_link_data["uri"]
17+
18+
19+
def test_update_chat_link_name(chat_links_service, created_chat_link, short_uuid):
20+
new_name = f"e2e updated link - {short_uuid}"
21+
22+
result = chat_links_service.update(created_chat_link.id, {"name": new_name})
23+
24+
assert result.id == created_chat_link.id
25+
assert result.to_dict().get("name") == new_name
26+
27+
28+
def test_delete_chat_link(chat_links_service, created_chat_link):
29+
result = created_chat_link
30+
31+
chat_links_service.delete(result.id)
32+
33+
34+
def test_update_chat_link_not_found(chat_links_service, invalid_chat_link_id):
35+
with pytest.raises(MPTAPIError, match=r"404 Not Found"):
36+
chat_links_service.update(invalid_chat_link_id, {"name": "updated name"})
37+
38+
39+
def test_delete_chat_link_not_found(chat_links_service, invalid_chat_link_id):
40+
with pytest.raises(MPTAPIError, match=r"404 Not Found"):
41+
chat_links_service.delete(invalid_chat_link_id)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import pytest
2+
3+
from mpt_api_client.resources.helpdesk.chat_links import (
4+
AsyncChatLinksService,
5+
ChatLinksService,
6+
)
7+
8+
9+
@pytest.fixture
10+
def chat_links_service(http_client) -> ChatLinksService:
11+
return ChatLinksService(
12+
http_client=http_client, endpoint_params={"chat_id": "CHT-0000-0000-0001"}
13+
)
14+
15+
16+
@pytest.fixture
17+
def async_chat_links_service(async_http_client) -> AsyncChatLinksService:
18+
return AsyncChatLinksService(
19+
http_client=async_http_client, endpoint_params={"chat_id": "CHT-0000-0000-0001"}
20+
)
21+
22+
23+
def test_endpoint(chat_links_service) -> None:
24+
result = chat_links_service.path == "/public/v1/helpdesk/chats/CHT-0000-0000-0001/links"
25+
26+
assert result is True
27+
28+
29+
def test_async_endpoint(async_chat_links_service) -> None:
30+
result = async_chat_links_service.path == "/public/v1/helpdesk/chats/CHT-0000-0000-0001/links"
31+
32+
assert result is True
33+
34+
35+
@pytest.mark.parametrize("method", ["create", "update", "delete", "iterate"])
36+
def test_methods_present(chat_links_service, method: str) -> None:
37+
result = hasattr(chat_links_service, method)
38+
39+
assert result is True
40+
41+
42+
@pytest.mark.parametrize("method", ["create", "update", "delete", "iterate"])
43+
def test_async_methods_present(async_chat_links_service, method: str) -> None:
44+
result = hasattr(async_chat_links_service, method)
45+
46+
assert result is True

tests/unit/resources/helpdesk/test_chats.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import pytest
22

3+
from mpt_api_client.resources.helpdesk.chat_links import (
4+
AsyncChatLinksService,
5+
ChatLinksService,
6+
)
37
from mpt_api_client.resources.helpdesk.chat_messages import (
48
AsyncChatMessagesService,
59
ChatMessagesService,
@@ -37,15 +41,29 @@ def test_async_mixins_present(async_chats_service, method):
3741
assert result is True
3842

3943

40-
def test_messages_service(chats_service):
41-
result = chats_service.messages("CHT-0000-0000-0001")
44+
@pytest.mark.parametrize(
45+
("service_method", "expected_service_class"),
46+
[
47+
("messages", ChatMessagesService),
48+
("links", ChatLinksService),
49+
],
50+
)
51+
def test_property_services(chats_service, service_method, expected_service_class):
52+
result = getattr(chats_service, service_method)("CHT-0000-0000-0001")
4253

43-
assert isinstance(result, ChatMessagesService)
54+
assert isinstance(result, expected_service_class)
4455
assert result.endpoint_params == {"chat_id": "CHT-0000-0000-0001"}
4556

4657

47-
def test_async_messages_service(async_chats_service):
48-
result = async_chats_service.messages("CHT-0000-0000-0001")
58+
@pytest.mark.parametrize(
59+
("service_method", "expected_service_class"),
60+
[
61+
("messages", AsyncChatMessagesService),
62+
("links", AsyncChatLinksService),
63+
],
64+
)
65+
def test_async_property_services(async_chats_service, service_method, expected_service_class):
66+
result = getattr(async_chats_service, service_method)("CHT-0000-0000-0001")
4967

50-
assert isinstance(result, AsyncChatMessagesService)
68+
assert isinstance(result, expected_service_class)
5169
assert result.endpoint_params == {"chat_id": "CHT-0000-0000-0001"}

0 commit comments

Comments
 (0)