From 8b87dfb3e843a381bee0ffff0a87a1e03ec4e336 Mon Sep 17 00:00:00 2001 From: marcell Date: Thu, 9 Oct 2025 18:59:00 -0300 Subject: [PATCH 1/3] creating first tests for channels crud --- Makefile | 2 +- src/accounts/tests.py | 1 - {tests => src/accounts/tests}/__init__.py | 0 src/accounts/tests/helpers.py | 38 ++++++ src/accounts/typing.py | 16 +++ src/assets/helpers.py | 35 +++++ .../migrations/0002_alter_channel_name.py | 25 ++++ src/channels/models.py | 2 +- src/channels/serializers.py | 2 +- src/channels/tests.py | 1 - src/channels/tests/helpers.py | 3 +- src/channels/tests/test_channel_view_set.py | 125 ++++++++++++++++++ src/channels/urls.py | 4 +- src/messaging/tests.py | 1 - 14 files changed, 244 insertions(+), 11 deletions(-) delete mode 100644 src/accounts/tests.py rename {tests => src/accounts/tests}/__init__.py (100%) create mode 100644 src/accounts/tests/helpers.py create mode 100644 src/accounts/typing.py create mode 100644 src/assets/helpers.py create mode 100644 src/channels/migrations/0002_alter_channel_name.py delete mode 100644 src/channels/tests.py delete mode 100644 src/messaging/tests.py diff --git a/Makefile b/Makefile index 35bc019..b7165f4 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ pre_commit: test: @echo "🧪 Executando os testes..." - poetry run python src/manage.py test + poetry run python src/manage.py test src @echo "✅ Testes concluídos." worker: diff --git a/src/accounts/tests.py b/src/accounts/tests.py deleted file mode 100644 index a39b155..0000000 --- a/src/accounts/tests.py +++ /dev/null @@ -1 +0,0 @@ -# Create your tests here. diff --git a/tests/__init__.py b/src/accounts/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to src/accounts/tests/__init__.py diff --git a/src/accounts/tests/helpers.py b/src/accounts/tests/helpers.py new file mode 100644 index 0000000..0e567fe --- /dev/null +++ b/src/accounts/tests/helpers.py @@ -0,0 +1,38 @@ +from typing import Unpack + +from django.contrib.auth.hashers import make_password +from model_bakery import baker + +from accounts.models import ( + HumanAgent, + User, +) +from accounts.typing import ( + AuthModel, + HumanAgentDTO, + UserDTO, +) +from config.faker import fake + + +def sample_user( + model: type[AuthModel] = User, + prepare=False, + **kwargs: Unpack[UserDTO], +) -> AuthModel | list[AuthModel]: + kwargs.setdefault("name", fake.name) + kwargs.setdefault("email", fake.ascii_email) + + kwargs["password"] = make_password(kwargs.get("password", "password")) + + if prepare: + return baker.prepare(User, **kwargs) + + return baker.make(User, **kwargs) + +def sample_human_agent( + prepare=False, + **kwargs: Unpack[HumanAgentDTO], +) -> HumanAgent | list[HumanAgent]: + + return sample_user(HumanAgent, prepare, **kwargs) diff --git a/src/accounts/typing.py b/src/accounts/typing.py new file mode 100644 index 0000000..339724d --- /dev/null +++ b/src/accounts/typing.py @@ -0,0 +1,16 @@ +from typing import TypedDict, TypeVar +from django.contrib.auth.models import User + +AuthModel = TypeVar("AuthModel", bound=User) + +class UserDTO(TypedDict, total=False): + email: str + name: str + is_active: bool + is_superuser: bool + is_staff: bool + password: str + + +class HumanAgentDTO(UserDTO, total=False): + pass \ No newline at end of file diff --git a/src/assets/helpers.py b/src/assets/helpers.py new file mode 100644 index 0000000..1e758c9 --- /dev/null +++ b/src/assets/helpers.py @@ -0,0 +1,35 @@ +import base64 + +from rest_framework.test import APIClient as _APIClient +from rest_framework_simplejwt.serializers import ( + TokenObtainPairSerializer as _TokenObtainPairSerializer, +) + + +def configure_api_client( # noqa + client: _APIClient, + set_header_version=True, + basic_auth=False, + access_token=None, + user=None, + password=None, +): + headers = {} + if set_header_version: + headers["HTTP_ACCEPT"] = f"application/json; version=v1_web" + + if access_token is not None and not basic_auth: + headers["HTTP_AUTHORIZATION"] = f"Bearer {access_token}" + + if user is not None and not basic_auth: + access = str(_TokenObtainPairSerializer.get_token(user).access_token) + headers["HTTP_AUTHORIZATION"] = f"Bearer {access}" + client.force_authenticate(user=user) + + if basic_auth and password: + access = base64.b64encode(str.encode(f"{user.email}:{password}")).decode( + "iso-8859-1", + ) + headers["HTTP_AUTHORIZATION"] = f"Basic {access}" + + client.credentials(**headers) \ No newline at end of file diff --git a/src/channels/migrations/0002_alter_channel_name.py b/src/channels/migrations/0002_alter_channel_name.py new file mode 100644 index 0000000..b9b62d6 --- /dev/null +++ b/src/channels/migrations/0002_alter_channel_name.py @@ -0,0 +1,25 @@ +# Generated by Django 5.2.7 on 2025-10-09 21:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("channels", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="channel", + name="name", + field=models.CharField( + choices=[ + ("whatsapp", "WhatsApp"), + ("telegram", "Telegram"), + ("mock", "Mock Channel"), + ], + max_length=20, + ), + ), + ] diff --git a/src/channels/models.py b/src/channels/models.py index 3854148..226fe89 100644 --- a/src/channels/models.py +++ b/src/channels/models.py @@ -8,7 +8,7 @@ class ChannelType(models.TextChoices): TELEGRAM = "telegram", "Telegram" MOCK = "mock", "Mock Channel" - name = models.CharField(max_length=20, choices=ChannelType.choices, unique=True) + name = models.CharField(max_length=20, choices=ChannelType.choices) config = models.JSONField( default=dict, help_text="Configurações específicas do canal em formato JSON" ) diff --git a/src/channels/serializers.py b/src/channels/serializers.py index 47155a6..6b83c6d 100644 --- a/src/channels/serializers.py +++ b/src/channels/serializers.py @@ -21,7 +21,7 @@ def validate(self, data): if getattr(self, "instance", None) is not None: qs = qs.exclude(pk=self.instance.pk) - duplicate_exists = qs.filter(_config__token=token).exists() + duplicate_exists = qs.filter(config__token=token).exists() if duplicate_exists: raise serializers.ValidationError( diff --git a/src/channels/tests.py b/src/channels/tests.py deleted file mode 100644 index a39b155..0000000 --- a/src/channels/tests.py +++ /dev/null @@ -1 +0,0 @@ -# Create your tests here. diff --git a/src/channels/tests/helpers.py b/src/channels/tests/helpers.py index f10a738..7e73c05 100644 --- a/src/channels/tests/helpers.py +++ b/src/channels/tests/helpers.py @@ -8,7 +8,7 @@ def sample_channel( - prepare=False, + prepare: bool = False, **kwargs: Unpack[ChannelsDTO], ) -> Channel | list[Channel]: kwargs.setdefault("name", fake.random_element(Channel.ChannelType.values)) @@ -17,5 +17,4 @@ def sample_channel( if prepare: return baker.prepare(Channel, **kwargs) - return baker.make(Channel, **kwargs) diff --git a/src/channels/tests/test_channel_view_set.py b/src/channels/tests/test_channel_view_set.py index e69de29..aaffd5f 100644 --- a/src/channels/tests/test_channel_view_set.py +++ b/src/channels/tests/test_channel_view_set.py @@ -0,0 +1,125 @@ +from accounts.tests.helpers import sample_human_agent +from assets.helpers import configure_api_client + +from rest_framework.test import APITestCase +from django.urls import reverse +from channels.models import Channel +from rest_framework import status +from rest_framework.response import Response +from .helpers import sample_channel +from config.faker import fake + + +class TestChannelViewSet(APITestCase): + def create(self, payload: dict = None, **args) -> tuple[Response, dict]: + url = reverse("channel-list") + + if payload is None: + payload = { + "name": Channel.ChannelType.TELEGRAM, + "config": {"token": fake.uuid4()}, + } + + response = self.client.post(url, payload, format="json", **args) + return response, payload + + def retrieve(self, channel_id: int = None, **kwargs) -> Response: + if channel_id is None: + channel = sample_channel() + channel_id = channel.id + url = reverse("channel-detail", kwargs={"pk": channel_id}) + + return self.client.get(url, format="json", **kwargs) + + def list(self, filters=None, **kwargs) -> Response: + sample_channel(_quantity=3) + url = reverse("channel-list") + + return self.client.get(url, filters) + + def update( + self, channel: Channel = None, payload: dict = None, **kwargs + ) -> tuple[Response, Channel, dict]: + if channel is None: + channel = sample_channel() + url = reverse("channel-detail", kwargs={"pk": channel.id}) + + if payload is None: + payload = { + "name": Channel.ChannelType.TELEGRAM, + "config": {"token": fake.uuid4()}, + } + + response = self.client.put(url, payload, format="json", **kwargs) + + channel.refresh_from_db() + + return response, channel, payload + + def delete(self, channel: Channel = None, **kwargs) -> tuple[Response, Channel]: + if channel is None: + channel = sample_channel() + url = reverse("channel-detail", kwargs={"pk": channel.id}) + + response = self.client.delete(url, format="json", **kwargs) + + channel.refresh_from_db() + + return response, channel + + +class TestChannelViewSetUnauthenticated(TestChannelViewSet): + def test_create_unauthenticated(self): + response, _ = self.create() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_retrieve_unauthenticated(self): + response = self.retrieve() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_list_unauthenticated(self): + response = self.list() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_update_unauthenticated(self): + response, _, _ = self.update() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_delete_unauthenticated(self): + response, _ = self.delete() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + +class TestChannelViewSetAuthenticated(TestChannelViewSet): + def setUp(self) -> None: + self.user = sample_human_agent() + configure_api_client(self.client, user=self.user) + + def test_create_authenticated(self): + response, payload = self.create() + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertIn("id", response.data) + self.assertEqual(response.data["name"], payload["name"]) + self.assertEqual(response.data["config"], payload["config"]) + self.assertTrue(response.data["is_active"]) + + def test_retrieve_authenticated(self): + response = self.retrieve() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_list_authenticated(self): + response = self.list() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["results"]), 3) + + def test_update_authenticated(self): + response, channel, payload = self.update() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(channel.name, payload["name"]) + self.assertEqual(channel.config, payload["config"]) + self.assertTrue(channel.is_active) + + def test_delete_authenticated(self): + response, channel = self.delete() + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertTrue(channel.is_removed) diff --git a/src/channels/urls.py b/src/channels/urls.py index 5222ae2..e16e80d 100644 --- a/src/channels/urls.py +++ b/src/channels/urls.py @@ -8,11 +8,9 @@ TelegramWebhookView, ) -app_name = "channels" - router = routers.DefaultRouter() -router.register("channels", viewset=ChannelsViewSet, basename="channels") +router.register("channels", viewset=ChannelsViewSet, basename="channel") urlpatterns = [ path("telegram-webhook/", TelegramWebhookView.as_view(), name="telegram-webhook"), diff --git a/src/messaging/tests.py b/src/messaging/tests.py deleted file mode 100644 index a39b155..0000000 --- a/src/messaging/tests.py +++ /dev/null @@ -1 +0,0 @@ -# Create your tests here. From a22c2fd9eb2f130537c4790ed903b3852899477a Mon Sep 17 00:00:00 2001 From: marcell Date: Thu, 9 Oct 2025 19:32:32 -0300 Subject: [PATCH 2/3] adding tests for all others views --- src/accounts/tests/helpers.py | 8 +- src/accounts/tests/test_contact_view_set.py | 61 +++++++++ .../tests/test_human_agent_view_set.py | 127 ++++++++++++++++++ src/accounts/tests/test_register_view.py | 34 +++++ src/accounts/typing.py | 3 +- src/accounts/urls.py | 2 - src/accounts/views.py | 4 +- src/assets/helpers.py | 4 +- src/channels/tests/test_mock_webhook_view.py | 47 +++++++ src/channels/tests/test_send_message_view.py | 45 +++++++ .../tests/test_telegram_webhook_view.py | 51 +++++++ src/config/settings.py | 1 + src/config/urls.py | 1 + src/messaging/tests/__init__.py | 2 + src/messaging/tests/test_message_view_set.py | 90 +++++++++++++ src/messaging/urls.py | 2 - src/messaging/views.py | 1 - 17 files changed, 469 insertions(+), 14 deletions(-) create mode 100644 src/accounts/tests/test_contact_view_set.py create mode 100644 src/accounts/tests/test_human_agent_view_set.py create mode 100644 src/accounts/tests/test_register_view.py create mode 100644 src/channels/tests/test_mock_webhook_view.py create mode 100644 src/channels/tests/test_send_message_view.py create mode 100644 src/channels/tests/test_telegram_webhook_view.py create mode 100644 src/messaging/tests/__init__.py create mode 100644 src/messaging/tests/test_message_view_set.py diff --git a/src/accounts/tests/helpers.py b/src/accounts/tests/helpers.py index 0e567fe..4389a58 100644 --- a/src/accounts/tests/helpers.py +++ b/src/accounts/tests/helpers.py @@ -17,7 +17,7 @@ def sample_user( model: type[AuthModel] = User, - prepare=False, + prepare: bool = False, **kwargs: Unpack[UserDTO], ) -> AuthModel | list[AuthModel]: kwargs.setdefault("name", fake.name) @@ -26,13 +26,13 @@ def sample_user( kwargs["password"] = make_password(kwargs.get("password", "password")) if prepare: - return baker.prepare(User, **kwargs) + return baker.prepare(model, **kwargs) + + return baker.make(model, **kwargs) - return baker.make(User, **kwargs) def sample_human_agent( prepare=False, **kwargs: Unpack[HumanAgentDTO], ) -> HumanAgent | list[HumanAgent]: - return sample_user(HumanAgent, prepare, **kwargs) diff --git a/src/accounts/tests/test_contact_view_set.py b/src/accounts/tests/test_contact_view_set.py new file mode 100644 index 0000000..f11a870 --- /dev/null +++ b/src/accounts/tests/test_contact_view_set.py @@ -0,0 +1,61 @@ +from assets.helpers import configure_api_client + +from rest_framework.test import APITestCase +from django.urls import reverse +from rest_framework import status +from rest_framework.response import Response + +from accounts.tests.helpers import sample_human_agent +from accounts.models import Contact +from channels.tests.helpers import sample_channel +from config.faker import fake + + +class TestContactViewSet(APITestCase): + def retrieve(self, contact_id: int = None, **kwargs) -> Response: + if contact_id is None: + contact = Contact.objects.create( + name=fake.name(), + channel=sample_channel(), + channel_identifier=str(fake.random_int()), + ) + contact_id = contact.id + url = reverse("contact-detail", kwargs={"pk": contact_id}) + return self.client.get(url, format="json", **kwargs) + + def list(self, filters=None, **kwargs) -> Response: + Contact.objects.create( + name=fake.name(), channel=sample_channel(), channel_identifier=str(fake.random_int()) + ) + Contact.objects.create( + name=fake.name(), channel=sample_channel(), channel_identifier=str(fake.random_int()) + ) + url = reverse("contact-list") + return self.client.get(url, filters) + + +class TestContactViewSetUnauthenticated(TestContactViewSet): + def test_retrieve_unauthenticated(self): + response = self.retrieve() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_list_unauthenticated(self): + response = self.list() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + +class TestContactViewSetAuthenticated(TestContactViewSet): + def setUp(self) -> None: + self.user = sample_human_agent() + configure_api_client(self.client, user=self.user) + + def test_retrieve_authenticated(self): + response = self.retrieve() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_list_authenticated(self): + response = self.list() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["results"]), 2) + + diff --git a/src/accounts/tests/test_human_agent_view_set.py b/src/accounts/tests/test_human_agent_view_set.py new file mode 100644 index 0000000..15307a6 --- /dev/null +++ b/src/accounts/tests/test_human_agent_view_set.py @@ -0,0 +1,127 @@ +from assets.helpers import configure_api_client + +from rest_framework.test import APITestCase +from django.urls import reverse +from rest_framework import status +from rest_framework.response import Response + +from accounts.tests.helpers import sample_human_agent +from accounts.models import HumanAgent +from config.faker import fake + + +class TestHumanAgentViewSet(APITestCase): + def create(self, payload: dict = None, **args) -> tuple[Response, dict]: + url = reverse("human-agent-list") + + if payload is None: + payload = { + "name": fake.name(), + "email": fake.ascii_email(), + } + + response = self.client.post(url, payload, format="json", **args) + return response, payload + + def retrieve(self, agent_id: int = None, **kwargs) -> Response: + if agent_id is None: + agent = sample_human_agent() + agent_id = agent.id + url = reverse("human-agent-detail", kwargs={"pk": agent_id}) + return self.client.get(url, format="json", **kwargs) + + def list(self, **kwargs) -> Response: + sample_human_agent(_quantity=3) + url = reverse("human-agent-list") + return self.client.get(url, **kwargs) + + def update(self, agent: HumanAgent = None, payload: dict = None, **kwargs) -> tuple[Response, HumanAgent, dict]: + if agent is None: + agent = sample_human_agent() + url = reverse("human-agent-detail", kwargs={"pk": agent.id}) + + if payload is None: + payload = { + "name": fake.name(), + "email": fake.ascii_email(), + } + + response = self.client.put(url, payload, format="json", **kwargs) + agent.refresh_from_db() + return response, agent, payload + + def delete(self, agent: HumanAgent = None, **kwargs) -> tuple[Response, HumanAgent]: + if agent is None: + agent = sample_human_agent() + url = reverse("human-agent-detail", kwargs={"pk": agent.id}) + response = self.client.delete(url, format="json", **kwargs) + agent.refresh_from_db() + return response, agent + + +class TestHumanAgentViewSetUnauthenticated(TestHumanAgentViewSet): + def test_create_unauthenticated(self): + response, _ = self.create() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_retrieve_unauthenticated(self): + response = self.retrieve() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_list_unauthenticated(self): + response = self.list() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_update_unauthenticated(self): + response, _, _ = self.update() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_delete_unauthenticated(self): + response, _ = self.delete() + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + +class TestHumanAgentViewSetAuthenticated(TestHumanAgentViewSet): + def setUp(self) -> None: + self.user = sample_human_agent() + configure_api_client(self.client, user=self.user) + + def test_create_authenticated(self): + response, payload = self.create() + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertIn("id", response.data) + self.assertEqual(response.data["name"], payload["name"]) + self.assertEqual(response.data["email"], payload["email"]) + + def test_retrieve_authenticated(self): + response = self.retrieve() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_list_authenticated(self): + response = self.list() + self.assertEqual(response.status_code, status.HTTP_200_OK) + # 3 created + 1 authenticated user in setUp + self.assertEqual(len(response.data["results"]), 4) + + def test_update_self_authenticated(self): + response, agent, payload = self.update(agent=self.user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(agent.name, payload["name"]) + self.assertEqual(agent.email, payload["email"]) + + def test_update_other_returns_404(self): + other = sample_human_agent() + response, _, _ = self.update(agent=other) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_delete_self_authenticated(self): + response, agent = self.delete(agent=self.user) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertTrue(agent.is_removed) + + def test_delete_other_returns_404(self): + other = sample_human_agent() + response, _ = self.delete(agent=other) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + diff --git a/src/accounts/tests/test_register_view.py b/src/accounts/tests/test_register_view.py new file mode 100644 index 0000000..906340d --- /dev/null +++ b/src/accounts/tests/test_register_view.py @@ -0,0 +1,34 @@ +from rest_framework.test import APITestCase +from django.urls import reverse +from rest_framework import status + +from accounts.models import HumanAgent +from config.faker import fake + + +class TestRegisterHumanAgentView(APITestCase): + def url(self) -> str: + return reverse("register-human-agent") + + def payload(self, **overrides) -> dict: + data = { + "name": fake.name(), + "email": fake.ascii_email(), + "password": "password-strong", + } + data.update(overrides) + return data + + def test_register_success(self): + response = self.client.post(self.url(), self.payload(), format="json") + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertIn("id", response.data) + self.assertIn("email", response.data) + self.assertTrue(HumanAgent.objects.filter(id=response.data["id"]).exists()) + + def test_register_missing_password(self): + data = self.payload() + data.pop("password") + response = self.client.post(self.url(), data, format="json") + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + diff --git a/src/accounts/typing.py b/src/accounts/typing.py index 339724d..8155fc1 100644 --- a/src/accounts/typing.py +++ b/src/accounts/typing.py @@ -3,6 +3,7 @@ AuthModel = TypeVar("AuthModel", bound=User) + class UserDTO(TypedDict, total=False): email: str name: str @@ -13,4 +14,4 @@ class UserDTO(TypedDict, total=False): class HumanAgentDTO(UserDTO, total=False): - pass \ No newline at end of file + pass diff --git a/src/accounts/urls.py b/src/accounts/urls.py index 048c9ae..53bb74f 100644 --- a/src/accounts/urls.py +++ b/src/accounts/urls.py @@ -5,8 +5,6 @@ from .views import ContactViewSet, HumanAgentViewSet, RegisterHumanAgentView -app_name = "accounts" - router = DefaultRouter() router.register("human-agents", HumanAgentViewSet, basename="human-agent") diff --git a/src/accounts/views.py b/src/accounts/views.py index 1494972..aba23df 100644 --- a/src/accounts/views.py +++ b/src/accounts/views.py @@ -1,5 +1,6 @@ from __future__ import annotations +from rest_framework.filters import OrderingFilter from rest_framework import status from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.response import Response @@ -76,8 +77,7 @@ class ContactViewSet(GetCacheMixin, ReadOnlyModelViewSet): queryset = Contact.objects.select_related("channel").all() serializer_class = ContactSerializer permission_classes = [IsAuthenticated] - filter_backends = [DjangoFilterBackend] + filter_backends = [DjangoFilterBackend, OrderingFilter] filterset_fields = ["channel"] ordering = ["-created"] ordering_fields = ["created", "modified"] - search_fields = ["name", "channel_identifier"] diff --git a/src/assets/helpers.py b/src/assets/helpers.py index 1e758c9..3394a52 100644 --- a/src/assets/helpers.py +++ b/src/assets/helpers.py @@ -16,7 +16,7 @@ def configure_api_client( # noqa ): headers = {} if set_header_version: - headers["HTTP_ACCEPT"] = f"application/json; version=v1_web" + headers["HTTP_ACCEPT"] = "application/json; version=v1_web" if access_token is not None and not basic_auth: headers["HTTP_AUTHORIZATION"] = f"Bearer {access_token}" @@ -32,4 +32,4 @@ def configure_api_client( # noqa ) headers["HTTP_AUTHORIZATION"] = f"Basic {access}" - client.credentials(**headers) \ No newline at end of file + client.credentials(**headers) diff --git a/src/channels/tests/test_mock_webhook_view.py b/src/channels/tests/test_mock_webhook_view.py new file mode 100644 index 0000000..3741749 --- /dev/null +++ b/src/channels/tests/test_mock_webhook_view.py @@ -0,0 +1,47 @@ +from accounts.tests.helpers import sample_human_agent +from assets.helpers import configure_api_client + +from rest_framework.test import APITestCase +from django.urls import reverse +from channels.models import Channel +from rest_framework import status +from config.faker import fake + + +class _BaseMockWebhookView(APITestCase): + def setUp(self) -> None: + self.channel = Channel.objects.create( + name=Channel.ChannelType.MOCK, + config={"token": fake.uuid4()}, + is_active=True, + ) + + def url(self) -> str: + return reverse("mock-webhook") + + def payload(self, text: str = None, channel_identifier: str | None = None) -> dict: + return { + "channel_identifier": channel_identifier or str(fake.random_int()), + "content": text or fake.sentence(), + } + +class TestMockWebhookViewUnauthenticated(_BaseMockWebhookView): + def test_requires_authentication(self): + response = self.client.post(self.url(), self.payload(), format="json") + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + +class TestMockWebhookViewAuthenticated(_BaseMockWebhookView): + def setUp(self) -> None: + super().setUp() + self.user = sample_human_agent() + configure_api_client(self.client, user=self.user) + + def test_ignores_invalid_payload(self): + response = self.client.post(self.url(), {}, format="json") + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + def test_persists_inbound_message(self): + response = self.client.post(self.url(), self.payload(), format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + diff --git a/src/channels/tests/test_send_message_view.py b/src/channels/tests/test_send_message_view.py new file mode 100644 index 0000000..e5c1523 --- /dev/null +++ b/src/channels/tests/test_send_message_view.py @@ -0,0 +1,45 @@ +from accounts.tests.helpers import sample_human_agent +from assets.helpers import configure_api_client + +from rest_framework.test import APITestCase +from django.urls import reverse +from channels.models import Channel +from rest_framework import status +from config.faker import fake +from accounts.models import Contact + + +class _BaseSendMessageView(APITestCase): + def setUp(self) -> None: + self.channel = Channel.objects.create( + name=Channel.ChannelType.MOCK, + config={"token": fake.uuid4()}, + is_active=True, + ) + self.contact = Contact.objects.create( + name="Test Contact", + channel=self.channel, + channel_identifier=str(fake.random_int()), + ) + + def url(self) -> str: + return reverse("send-message") + +class TestSendMessageViewUnauthenticated(_BaseSendMessageView): + def test_requires_authentication(self): + response = self.client.post(self.url(), {}, format="json") + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + +class TestSendMessageViewAuthenticated(_BaseSendMessageView): + def setUp(self) -> None: + super().setUp() + self.user = sample_human_agent() + configure_api_client(self.client, user=self.user) + + def test_sends_message_successfully(self): + payload = {"contact_id": self.contact.id, "content": "Hello!"} + response = self.client.post(self.url(), payload, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("ok", response.data) + self.assertTrue(response.data["ok"]) + diff --git a/src/channels/tests/test_telegram_webhook_view.py b/src/channels/tests/test_telegram_webhook_view.py new file mode 100644 index 0000000..a0ec614 --- /dev/null +++ b/src/channels/tests/test_telegram_webhook_view.py @@ -0,0 +1,51 @@ +from accounts.tests.helpers import sample_human_agent +from assets.helpers import configure_api_client + +from rest_framework.test import APITestCase +from django.urls import reverse +from channels.models import Channel +from rest_framework import status +from config.faker import fake + + +class _BaseTelegramWebhookView(APITestCase): + def setUp(self) -> None: + self.channel = Channel.objects.create( + name=Channel.ChannelType.TELEGRAM, + config={"token": fake.uuid4()}, + is_active=True, + ) + + def url(self) -> str: + return reverse("telegram-webhook") + + def payload(self, text: str = None, chat_id: int | None = None) -> dict: + return { + "update_id": fake.random_int(), + "message": { + "message_id": fake.random_int(), + "chat": {"id": chat_id or fake.random_int()}, + "text": text or fake.sentence(), + }, + } + +class TestTelegramWebhookViewUnauthenticated(_BaseTelegramWebhookView): + def test_requires_authentication(self): + response = self.client.post(self.url(), self.payload(), format="json") + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + +class TestTelegramWebhookViewAuthenticated(_BaseTelegramWebhookView): + def setUp(self) -> None: + super().setUp() + self.user = sample_human_agent() + configure_api_client(self.client, user=self.user) + + def test_ignores_non_message_updates(self): + response = self.client.post(self.url(), {"callback_query": {}}, format="json") + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + def test_persists_inbound_message(self): + response = self.client.post(self.url(), self.payload(), format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + diff --git a/src/config/settings.py b/src/config/settings.py index 0fe4f32..2c17e2c 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -49,6 +49,7 @@ "assets", # libs "rest_framework", + "django_filters", "rest_framework_simplejwt", "model_utils", "polymorphic", diff --git a/src/config/urls.py b/src/config/urls.py index 6c43574..a1a81f9 100644 --- a/src/config/urls.py +++ b/src/config/urls.py @@ -31,6 +31,7 @@ path("admin/", admin.site.urls), path("api/channels/", include("channels.urls")), path("api/accounts/", include("accounts.urls")), + path("api/messaging/", include("messaging.urls")), path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), path("api/schema/", SpectacularAPIView.as_view(), name="schema"), diff --git a/src/messaging/tests/__init__.py b/src/messaging/tests/__init__.py new file mode 100644 index 0000000..5986b85 --- /dev/null +++ b/src/messaging/tests/__init__.py @@ -0,0 +1,2 @@ +# Ensures messaging.tests is a package for test discovery + diff --git a/src/messaging/tests/test_message_view_set.py b/src/messaging/tests/test_message_view_set.py new file mode 100644 index 0000000..4c3a4e1 --- /dev/null +++ b/src/messaging/tests/test_message_view_set.py @@ -0,0 +1,90 @@ +from assets.helpers import configure_api_client + +from rest_framework.test import APITestCase +from django.urls import reverse +from rest_framework import status + +from accounts.tests.helpers import sample_human_agent +from accounts.models import Contact +from channels.tests.helpers import sample_channel +from messaging.models import Message +from config.faker import fake + + +class TestMessageViewSet(APITestCase): + def setUp(self) -> None: + self.user = sample_human_agent() + configure_api_client(self.client, user=self.user) + self.channel = sample_channel() + self.contact_a = Contact.objects.create( + name=fake.name(), channel=self.channel, channel_identifier=str(fake.random_int()) + ) + self.contact_b = Contact.objects.create( + name=fake.name(), channel=self.channel, channel_identifier=str(fake.random_int()) + ) + + Message.objects.create( + contact=self.contact_a, + content="hello alpha", + direction=Message.MessageDirection.INBOUND, + ) + Message.objects.create( + contact=self.contact_a, + content="bravo world", + direction=Message.MessageDirection.OUTBOUND, + ) + Message.objects.create( + contact=self.contact_b, + content="charlie search", + direction=Message.MessageDirection.INBOUND, + ) + + def list(self, params=None): + url = reverse("message-list") + return self.client.get(url, params) + + def retrieve(self, pk: int): + url = reverse("message-detail", kwargs={"pk": pk}) + return self.client.get(url) + + def test_retrieve_authenticated(self): + any_id = Message.objects.first().id + response = self.retrieve(any_id) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_filter_by_contact(self): + response = self.list({"contact": self.contact_a.id}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + for item in response.data["results"]: + self.assertEqual(item["contact"], self.contact_a.id) + + def test_filter_by_agent(self): + response = self.list({"agent": self.user.id}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["results"]), 0) + + def test_ordering_created(self): + response = self.list({"ordering": "created"}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + results = response.data["results"] + self.assertLessEqual(results[0]["created"], results[-1]["created"]) + + def test_ordering_modified_desc(self): + response = self.list({"ordering": "-modified"}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + results = response.data["results"] + self.assertGreaterEqual(results[0]["modified"], results[-1]["modified"]) + + +class TestMessageViewSetUnauthenticated(APITestCase): + def test_list_unauthenticated(self): + url = reverse("message-list") + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_retrieve_unauthenticated(self): + url = reverse("message-detail", kwargs={"pk": 1}) + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + diff --git a/src/messaging/urls.py b/src/messaging/urls.py index a190cb1..622e4be 100644 --- a/src/messaging/urls.py +++ b/src/messaging/urls.py @@ -5,8 +5,6 @@ from .views import MessageViewSet -app_name = "messaging" - router = DefaultRouter() router.register("messages", MessageViewSet, basename="message") diff --git a/src/messaging/views.py b/src/messaging/views.py index 97eda08..3540447 100644 --- a/src/messaging/views.py +++ b/src/messaging/views.py @@ -30,4 +30,3 @@ class MessageViewSet(GetCacheMixin, ReadOnlyModelViewSet): filterset_fields = ["contact", "agent"] ordering = ["-created"] ordering_fields = ["created", "modified"] - search_fields = ["content"] From ff7e9dfe11473405e03e568724f63b071e334b3d Mon Sep 17 00:00:00 2001 From: marcell Date: Thu, 9 Oct 2025 19:57:39 -0300 Subject: [PATCH 3/3] adding lazy caching and cache invalidation tests, and also tests for the the other endpoints --- src/accounts/tests/test_contact_view_set.py | 10 +- .../tests/test_human_agent_view_set.py | 6 +- src/accounts/tests/test_register_view.py | 1 - src/channels/tests/test_channel_view_set.py | 66 +++++++++++ src/channels/tests/test_mock_webhook_view.py | 2 +- src/channels/tests/test_send_message_view.py | 3 +- .../tests/test_telegram_webhook_view.py | 2 +- src/messaging/tests/__init__.py | 1 - src/messaging/tests/test_message_view_set.py | 103 +++++++++++++++++- 9 files changed, 176 insertions(+), 18 deletions(-) diff --git a/src/accounts/tests/test_contact_view_set.py b/src/accounts/tests/test_contact_view_set.py index f11a870..39dedbc 100644 --- a/src/accounts/tests/test_contact_view_set.py +++ b/src/accounts/tests/test_contact_view_set.py @@ -25,10 +25,14 @@ def retrieve(self, contact_id: int = None, **kwargs) -> Response: def list(self, filters=None, **kwargs) -> Response: Contact.objects.create( - name=fake.name(), channel=sample_channel(), channel_identifier=str(fake.random_int()) + name=fake.name(), + channel=sample_channel(), + channel_identifier=str(fake.random_int()), ) Contact.objects.create( - name=fake.name(), channel=sample_channel(), channel_identifier=str(fake.random_int()) + name=fake.name(), + channel=sample_channel(), + channel_identifier=str(fake.random_int()), ) url = reverse("contact-list") return self.client.get(url, filters) @@ -57,5 +61,3 @@ def test_list_authenticated(self): response = self.list() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data["results"]), 2) - - diff --git a/src/accounts/tests/test_human_agent_view_set.py b/src/accounts/tests/test_human_agent_view_set.py index 15307a6..bcfa3f6 100644 --- a/src/accounts/tests/test_human_agent_view_set.py +++ b/src/accounts/tests/test_human_agent_view_set.py @@ -35,7 +35,9 @@ def list(self, **kwargs) -> Response: url = reverse("human-agent-list") return self.client.get(url, **kwargs) - def update(self, agent: HumanAgent = None, payload: dict = None, **kwargs) -> tuple[Response, HumanAgent, dict]: + def update( + self, agent: HumanAgent = None, payload: dict = None, **kwargs + ) -> tuple[Response, HumanAgent, dict]: if agent is None: agent = sample_human_agent() url = reverse("human-agent-detail", kwargs={"pk": agent.id}) @@ -123,5 +125,3 @@ def test_delete_other_returns_404(self): other = sample_human_agent() response, _ = self.delete(agent=other) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - diff --git a/src/accounts/tests/test_register_view.py b/src/accounts/tests/test_register_view.py index 906340d..e6b5893 100644 --- a/src/accounts/tests/test_register_view.py +++ b/src/accounts/tests/test_register_view.py @@ -31,4 +31,3 @@ def test_register_missing_password(self): data.pop("password") response = self.client.post(self.url(), data, format="json") self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - diff --git a/src/channels/tests/test_channel_view_set.py b/src/channels/tests/test_channel_view_set.py index aaffd5f..d20a310 100644 --- a/src/channels/tests/test_channel_view_set.py +++ b/src/channels/tests/test_channel_view_set.py @@ -1,4 +1,5 @@ from accounts.tests.helpers import sample_human_agent +from django.core.cache import cache from assets.helpers import configure_api_client from rest_framework.test import APITestCase @@ -123,3 +124,68 @@ def test_delete_authenticated(self): response, channel = self.delete() self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertTrue(channel.is_removed) + + def test_cache_set_after_list(self): + cache.clear() + Channel.objects.all().delete() + # Use helper to perform initial list (seeds 3 and caches result) + response = self.list() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["results"]), 3) + # Create directly in DB (no view invalidation) + Channel.objects.create( + name=fake.uuid4(), config={"token": fake.uuid4()}, is_active=True + ) + # Cached response should still reflect the cached 3 results + url = reverse("channel-list") + response_cached = self.client.get(url) + self.assertEqual(response_cached.status_code, status.HTTP_200_OK) + self.assertEqual(len(response_cached.data["results"]), 3) + + def test_cache_invalidated_on_create(self): + cache.clear() + Channel.objects.all().delete() + # Cache list using helper (seeds 3, caches response) + first_list = self.list() + self.assertEqual(first_list.status_code, status.HTTP_200_OK) + self.assertEqual(len(first_list.data["results"]), 3) + # Create via API using helper (triggers invalidation) + resp_create, _ = self.create() + self.assertEqual(resp_create.status_code, status.HTTP_201_CREATED) + # List again (direct GET to avoid reseeding) should now include the new item + url = reverse("channel-list") + response_after = self.client.get(url) + self.assertEqual(response_after.status_code, status.HTTP_200_OK) + self.assertEqual(len(response_after.data["results"]), 4) + + def test_cache_invalidated_on_update(self): + cache.clear() + # Create channel and cache list using helper + channel = sample_channel() + self.list() + # Update via API using helper + new_name = Channel.ChannelType.TELEGRAM + update_payload = {"name": new_name, "config": {"token": fake.uuid4()}} + resp_upd, _, _ = self.update(channel=channel, payload=update_payload) + self.assertEqual(resp_upd.status_code, status.HTTP_200_OK) + # List again (direct GET) should contain updated name + url = reverse("channel-list") + resp2 = self.client.get(url) + self.assertEqual(resp2.status_code, status.HTTP_200_OK) + names = [item["name"] for item in resp2.data["results"]] + self.assertIn(new_name, names) + + def test_cache_invalidated_on_delete(self): + cache.clear() + # Create and cache list using helpers + channel = sample_channel() + self.list() + # Delete via API using helper + resp_del, _ = self.delete(channel=channel) + self.assertEqual(resp_del.status_code, status.HTTP_204_NO_CONTENT) + # List again (direct GET) should not include the deleted channel + url = reverse("channel-list") + resp2 = self.client.get(url) + self.assertEqual(resp2.status_code, status.HTTP_200_OK) + ids = [item["id"] for item in resp2.data["results"]] + self.assertNotIn(channel.id, ids) diff --git a/src/channels/tests/test_mock_webhook_view.py b/src/channels/tests/test_mock_webhook_view.py index 3741749..d31d410 100644 --- a/src/channels/tests/test_mock_webhook_view.py +++ b/src/channels/tests/test_mock_webhook_view.py @@ -25,6 +25,7 @@ def payload(self, text: str = None, channel_identifier: str | None = None) -> di "content": text or fake.sentence(), } + class TestMockWebhookViewUnauthenticated(_BaseMockWebhookView): def test_requires_authentication(self): response = self.client.post(self.url(), self.payload(), format="json") @@ -44,4 +45,3 @@ def test_ignores_invalid_payload(self): def test_persists_inbound_message(self): response = self.client.post(self.url(), self.payload(), format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) - diff --git a/src/channels/tests/test_send_message_view.py b/src/channels/tests/test_send_message_view.py index e5c1523..fbabff9 100644 --- a/src/channels/tests/test_send_message_view.py +++ b/src/channels/tests/test_send_message_view.py @@ -25,11 +25,13 @@ def setUp(self) -> None: def url(self) -> str: return reverse("send-message") + class TestSendMessageViewUnauthenticated(_BaseSendMessageView): def test_requires_authentication(self): response = self.client.post(self.url(), {}, format="json") self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + class TestSendMessageViewAuthenticated(_BaseSendMessageView): def setUp(self) -> None: super().setUp() @@ -42,4 +44,3 @@ def test_sends_message_successfully(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn("ok", response.data) self.assertTrue(response.data["ok"]) - diff --git a/src/channels/tests/test_telegram_webhook_view.py b/src/channels/tests/test_telegram_webhook_view.py index a0ec614..57f1bd9 100644 --- a/src/channels/tests/test_telegram_webhook_view.py +++ b/src/channels/tests/test_telegram_webhook_view.py @@ -29,6 +29,7 @@ def payload(self, text: str = None, chat_id: int | None = None) -> dict: }, } + class TestTelegramWebhookViewUnauthenticated(_BaseTelegramWebhookView): def test_requires_authentication(self): response = self.client.post(self.url(), self.payload(), format="json") @@ -48,4 +49,3 @@ def test_ignores_non_message_updates(self): def test_persists_inbound_message(self): response = self.client.post(self.url(), self.payload(), format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) - diff --git a/src/messaging/tests/__init__.py b/src/messaging/tests/__init__.py index 5986b85..b2670d4 100644 --- a/src/messaging/tests/__init__.py +++ b/src/messaging/tests/__init__.py @@ -1,2 +1 @@ # Ensures messaging.tests is a package for test discovery - diff --git a/src/messaging/tests/test_message_view_set.py b/src/messaging/tests/test_message_view_set.py index 4c3a4e1..578d4f9 100644 --- a/src/messaging/tests/test_message_view_set.py +++ b/src/messaging/tests/test_message_view_set.py @@ -1,4 +1,5 @@ from assets.helpers import configure_api_client +from django.core.cache import cache from rest_framework.test import APITestCase from django.urls import reverse @@ -7,6 +8,7 @@ from accounts.tests.helpers import sample_human_agent from accounts.models import Contact from channels.tests.helpers import sample_channel +from channels.models import Channel from messaging.models import Message from config.faker import fake @@ -17,10 +19,14 @@ def setUp(self) -> None: configure_api_client(self.client, user=self.user) self.channel = sample_channel() self.contact_a = Contact.objects.create( - name=fake.name(), channel=self.channel, channel_identifier=str(fake.random_int()) + name=fake.name(), + channel=self.channel, + channel_identifier=str(fake.random_int()), ) self.contact_b = Contact.objects.create( - name=fake.name(), channel=self.channel, channel_identifier=str(fake.random_int()) + name=fake.name(), + channel=self.channel, + channel_identifier=str(fake.random_int()), ) Message.objects.create( @@ -67,13 +73,100 @@ def test_ordering_created(self): response = self.list({"ordering": "created"}) self.assertEqual(response.status_code, status.HTTP_200_OK) results = response.data["results"] - self.assertLessEqual(results[0]["created"], results[-1]["created"]) + self.assertLessEqual(results[0]["created"], results[-1]["created"]) def test_ordering_modified_desc(self): response = self.list({"ordering": "-modified"}) self.assertEqual(response.status_code, status.HTTP_200_OK) results = response.data["results"] - self.assertGreaterEqual(results[0]["modified"], results[-1]["modified"]) + self.assertGreaterEqual(results[0]["modified"], results[-1]["modified"]) + + def test_cache_invalidated_after_send_message(self): + cache.clear() + # Initial list caches current messages + first = self.list() + self.assertEqual(first.status_code, status.HTTP_200_OK) + initial_count = len(first.data["results"]) + + # Create a contact under a MOCK channel and send message via API + mock_channel = Channel.objects.create( + name=Channel.ChannelType.MOCK, + config={"token": fake.uuid4()}, + is_active=True, + ) + contact = Contact.objects.create( + name=fake.name(), + channel=mock_channel, + channel_identifier=str(fake.random_int()), + ) + send_url = reverse("send-message") + resp = self.client.post( + send_url, + {"contact_id": contact.id, "content": "hello from test"}, + format="json", + ) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + + # List again should reflect the new message (cache invalidated by view) + after = self.list() + self.assertEqual(after.status_code, status.HTTP_200_OK) + self.assertEqual(len(after.data["results"]), initial_count + 1) + + def test_cache_invalidated_after_mock_webhook(self): + cache.clear() + first = self.list() + self.assertEqual(first.status_code, status.HTTP_200_OK) + initial_count = len(first.data["results"]) + + # Ensure a single active MOCK channel + Channel.objects.filter(name=Channel.ChannelType.MOCK).update(is_active=False) + Channel.objects.create( + name=Channel.ChannelType.MOCK, + config={"token": fake.uuid4()}, + is_active=True, + ) + webhook_url = reverse("mock-webhook") + payload = { + "channel_identifier": str(fake.random_int()), + "content": "inbound mock", + } + resp = self.client.post(webhook_url, payload, format="json") + self.assertEqual(resp.status_code, status.HTTP_200_OK) + + after = self.list() + self.assertEqual(after.status_code, status.HTTP_200_OK) + self.assertEqual(len(after.data["results"]), initial_count + 1) + + def test_cache_invalidated_after_telegram_webhook(self): + cache.clear() + first = self.list() + self.assertEqual(first.status_code, status.HTTP_200_OK) + initial_count = len(first.data["results"]) + + # Ensure a single active TELEGRAM channel + Channel.objects.filter(name=Channel.ChannelType.TELEGRAM).update( + is_active=False + ) + Channel.objects.create( + name=Channel.ChannelType.TELEGRAM, + config={"token": fake.uuid4()}, + is_active=True, + ) + webhook_url = reverse("telegram-webhook") + payload = { + "update_id": fake.random_int(), + "message": { + "message_id": fake.random_int(), + "chat": {"id": fake.random_int()}, + "text": "inbound telegram", + }, + } + resp = self.client.post(webhook_url, payload, format="json") + self.assertEqual(resp.status_code, status.HTTP_200_OK) + + after = self.list() + self.assertEqual(after.status_code, status.HTTP_200_OK) + self.assertEqual(len(after.data["results"]), initial_count + 1) class TestMessageViewSetUnauthenticated(APITestCase): @@ -86,5 +179,3 @@ def test_retrieve_unauthenticated(self): url = reverse("message-detail", kwargs={"pk": 1}) response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - -