diff --git a/examples/snippets/conversation/messages/list/snippet.py b/examples/snippets/conversation/messages/list/snippet.py new file mode 100644 index 0000000..7f81d1d --- /dev/null +++ b/examples/snippets/conversation/messages/list/snippet.py @@ -0,0 +1,37 @@ +""" +Sinch Python Snippet + +TODO: Update links when v2 is released. +This snippet is available at https://github.com/sinch/sinch-sdk-python/blob/v2.0/docs/snippets/ +""" + +import os +from dotenv import load_dotenv +from sinch import SinchClient + +load_dotenv() + +sinch_client = SinchClient( + project_id=os.environ.get("SINCH_PROJECT_ID") or "MY_PROJECT_ID", + key_id=os.environ.get("SINCH_KEY_ID") or "MY_KEY_ID", + key_secret=os.environ.get("SINCH_KEY_SECRET") or "MY_KEY_SECRET", + conversation_region=os.environ.get("SINCH_CONVERSATION_REGION") or "MY_CONVERSATION_REGION" +) + +# The ID of the Conversation App to list messages from +app_id = "CONVERSATION_APP_ID" + +messages = sinch_client.conversation.messages.list( + app_id=app_id, + page_size=10 +) + +page_counter = 1 +while True: + print(f"Page {page_counter} List of Messages: {messages}") + + if not messages.has_next_page: + break + + messages = messages.next_page() + page_counter += 1 diff --git a/tests/unit/domains/conversation/v1/endpoints/messages/test_list_messages_endpoint.py b/tests/unit/domains/conversation/v1/endpoints/messages/test_list_messages_endpoint.py new file mode 100644 index 0000000..e6eb805 --- /dev/null +++ b/tests/unit/domains/conversation/v1/endpoints/messages/test_list_messages_endpoint.py @@ -0,0 +1,115 @@ +import pytest +from sinch.core.models.http_response import HTTPResponse +from sinch.domains.conversation.api.v1.internal import ListMessagesEndpoint +from sinch.domains.conversation.models.v1.messages.internal import ( + ListMessagesResponse, +) +from sinch.domains.conversation.models.v1.messages.internal.request import ( + ListMessagesRequest, +) +from tests.unit.domains.conversation.v1.models.response.test_conversation_message_response_model import ( + contact_message_response_data, +) + + +@pytest.fixture +def request_data(): + return ListMessagesRequest(page_size=10) + + +@pytest.fixture +def endpoint(request_data): + return ListMessagesEndpoint("test_project_id", request_data) + + +@pytest.fixture +def mock_list_messages_response(contact_message_response_data): + return HTTPResponse( + status_code=200, + body={ + "messages": [contact_message_response_data], + "next_page_token": "token_next_page_abc", + }, + headers={"Content-Type": "application/json"}, + ) + + +def test_build_url_expects_correct_url(endpoint, mock_sinch_client_conversation): + """Test that the URL is built correctly (no path params beyond project_id).""" + assert ( + endpoint.build_url(mock_sinch_client_conversation) + == "https://us.conversation.api.sinch.com/v1/projects/test_project_id/messages" + ) + + +def test_build_query_params_expects_excludes_unset_fields(): + """Test that query params only include non-None fields.""" + request_data = ListMessagesRequest(page_size=10) + endpoint = ListMessagesEndpoint("test_project_id", request_data) + + query_params = endpoint.build_query_params() + + assert query_params["page_size"] == 10 + assert "conversation_id" not in query_params + + +def test_build_query_params_expects_parsed_params(): + """Test that all query param fields are serialized when set.""" + request_data = ListMessagesRequest( + conversation_id="CONV123", + contact_id="CONTACT456", + app_id="APP789", + channel_identity="+46701234567", + page_size=20, + page_token="token_xyz", + view="WITH_METADATA", + messages_source="DISPATCH_SOURCE", + only_recipient_originated=True, + channel="WHATSAPP", + direction="TO_APP", + ) + endpoint = ListMessagesEndpoint("test_project_id", request_data) + + query_params = endpoint.build_query_params() + + assert query_params["conversation_id"] == "CONV123" + assert query_params["contact_id"] == "CONTACT456" + assert query_params["app_id"] == "APP789" + assert query_params["channel_identity"] == "+46701234567" + assert query_params["page_size"] == 20 + assert query_params["page_token"] == "token_xyz" + assert query_params["view"] == "WITH_METADATA" + assert query_params["messages_source"] == "DISPATCH_SOURCE" + assert query_params["only_recipient_originated"] is True + assert query_params["channel"] == "WHATSAPP" + assert query_params["direction"] == "TO_APP" + + +def test_handle_response_expects_list_messages_response( + endpoint, mock_list_messages_response +): + """Test that a successful response is parsed to ListMessagesResponse.""" + result = endpoint.handle_response(mock_list_messages_response) + + assert isinstance(result, ListMessagesResponse) + assert result.next_page_token == "token_next_page_abc" + assert result.messages is not None + assert len(result.messages) == 1 + assert result.messages[0].id == "CAPY123456789ABCDEFGHIJKLMNOP" + + +def test_handle_response_expects_empty_messages_list(): + """Test that response with empty messages list is handled correctly.""" + request_data = ListMessagesRequest(page_size=10) + endpoint = ListMessagesEndpoint("test_project_id", request_data) + mock_response = HTTPResponse( + status_code=200, + body={"messages": [], "next_page_token": None}, + headers={"Content-Type": "application/json"}, + ) + + result = endpoint.handle_response(mock_response) + + assert isinstance(result, ListMessagesResponse) + assert result.messages == [] + assert result.next_page_token is None diff --git a/tests/unit/domains/conversation/v1/models/internal/request/test_list_messages_request.py b/tests/unit/domains/conversation/v1/models/internal/request/test_list_messages_request.py new file mode 100644 index 0000000..03fb01b --- /dev/null +++ b/tests/unit/domains/conversation/v1/models/internal/request/test_list_messages_request.py @@ -0,0 +1,41 @@ +from datetime import datetime, timezone + +from sinch.domains.conversation.models.v1.messages.internal.request import ( + ListMessagesRequest, +) + + +def test_list_messages_request_expects_parsed_input(): + """Test that the model correctly parses input with all parameters.""" + start = datetime(2025, 1, 1, 12, 0, 0, tzinfo=timezone.utc) + end = datetime(2025, 1, 8, 12, 0, 0, tzinfo=timezone.utc) + + request = ListMessagesRequest( + conversation_id="CONV123456789ABCDEFGHIJKLM", + contact_id="CONTACT456789ABCDEFGHIJKLMNOP", + app_id="APP123456789ABCDEFGHIJK", + channel_identity="+46701234567", + start_time=start, + end_time=end, + page_size=50, + page_token="next_page_token_abc", + view="WITH_METADATA", + messages_source="CONVERSATION_SOURCE", + only_recipient_originated=True, + channel="WHATSAPP", + direction="TO_CONTACT", + ) + + assert request.conversation_id == "CONV123456789ABCDEFGHIJKLM" + assert request.contact_id == "CONTACT456789ABCDEFGHIJKLMNOP" + assert request.app_id == "APP123456789ABCDEFGHIJK" + assert request.channel_identity == "+46701234567" + assert request.start_time == start + assert request.end_time == end + assert request.page_size == 50 + assert request.page_token == "next_page_token_abc" + assert request.view == "WITH_METADATA" + assert request.messages_source == "CONVERSATION_SOURCE" + assert request.only_recipient_originated is True + assert request.channel == "WHATSAPP" + assert request.direction == "TO_CONTACT" diff --git a/tests/unit/domains/conversation/v1/models/internal/test_list_messages_response.py b/tests/unit/domains/conversation/v1/models/internal/test_list_messages_response.py new file mode 100644 index 0000000..8ecc2d5 --- /dev/null +++ b/tests/unit/domains/conversation/v1/models/internal/test_list_messages_response.py @@ -0,0 +1,39 @@ +from sinch.domains.conversation.models.v1.messages.internal import ( + ListMessagesResponse, +) +from tests.unit.domains.conversation.v1.models.response.test_conversation_message_response_model import ( + contact_message_response_data, + app_message_response_data, +) + + +def test_list_messages_response_expects_correct_mapping( + contact_message_response_data, + app_message_response_data, +): + """ + Test that response is correctly parsed from dict and + content property returns messages. + """ + data = { + "messages": [contact_message_response_data, app_message_response_data], + "next_page_token": "token_abc", + } + response = ListMessagesResponse.model_validate(data) + + assert response.next_page_token == "token_abc" + assert response.messages is not None + assert len(response.messages) == 2 + assert response.messages[0].id == contact_message_response_data["id"] + assert response.messages[1].id == app_message_response_data["id"] + assert response.content == response.messages + assert len(response.content) == 2 + + +def test_list_messages_response_expects_empty_messages_list(): + """Test that response with empty messages list has content as empty list.""" + response = ListMessagesResponse(messages=[], next_page_token=None) + + assert response.messages == [] + assert response.content == [] + assert response.next_page_token is None