Files
GIA/core/tests/test_compose_react.py

258 lines
9.1 KiB
Python

from __future__ import annotations
from unittest.mock import AsyncMock, patch
from django.test import TestCase
from django.urls import reverse
from core.models import ChatSession, Message, Person, PersonIdentifier, User
class ComposeReactTests(TestCase):
def setUp(self):
self.user = User.objects.create_user("compose-react", "react@example.com", "pw")
self.client.force_login(self.user)
def _build_message(self, *, service: str, identifier: str, source_message_id: str = ""):
person = Person.objects.create(user=self.user, name=f"{service} person")
person_identifier = PersonIdentifier.objects.create(
user=self.user,
person=person,
service=service,
identifier=identifier,
)
session = ChatSession.objects.create(
user=self.user,
identifier=person_identifier,
)
message = Message.objects.create(
user=self.user,
session=session,
ts=1770000000123,
sender_uuid="sender-1",
text="hello",
source_service=service,
source_message_id=source_message_id or "",
source_chat_id=identifier,
receipt_payload={},
)
return person, person_identifier, message
@patch("core.views.compose.history.apply_reaction", new_callable=AsyncMock)
@patch("core.views.compose.transport.send_reaction", new_callable=AsyncMock)
def test_signal_react_uses_numeric_source_message_id_timestamp(
self, mocked_send_reaction, mocked_apply_reaction
):
person, _, message = self._build_message(
service="signal",
identifier="+15551230000",
source_message_id="1771234567000",
)
mocked_send_reaction.return_value = True
mocked_apply_reaction.return_value = message
response = self.client.post(
reverse("compose_react"),
{
"service": "signal",
"identifier": "+15551230000",
"person": str(person.id),
"message_id": str(message.id),
"emoji": "👍",
},
)
self.assertEqual(200, response.status_code)
payload = response.json()
self.assertTrue(payload["ok"])
self.assertEqual("👍", payload["emoji"])
self.assertFalse(payload["remove"])
mocked_send_reaction.assert_awaited_once()
_, kwargs = mocked_send_reaction.await_args
self.assertEqual("signal", mocked_send_reaction.await_args.args[0])
self.assertEqual("+15551230000", mocked_send_reaction.await_args.args[1])
self.assertEqual(1771234567000, kwargs["target_timestamp"])
self.assertEqual("sender-1", kwargs["target_author"])
self.assertEqual("", kwargs["target_message_id"])
@patch("core.views.compose.history.apply_reaction", new_callable=AsyncMock)
@patch("core.views.compose.transport.send_reaction", new_callable=AsyncMock)
def test_whatsapp_react_uses_upstream_message_id(
self, mocked_send_reaction, mocked_apply_reaction
):
person, _, message = self._build_message(
service="whatsapp",
identifier="12345@s.whatsapp.net",
source_message_id="wamid.ABC123",
)
mocked_send_reaction.return_value = True
mocked_apply_reaction.return_value = message
response = self.client.post(
reverse("compose_react"),
{
"service": "whatsapp",
"identifier": "12345@s.whatsapp.net",
"person": str(person.id),
"message_id": str(message.id),
"emoji": "❤️",
},
)
self.assertEqual(200, response.status_code)
payload = response.json()
self.assertTrue(payload["ok"])
self.assertEqual("wamid.ABC123", payload["target_upstream_id"])
mocked_send_reaction.assert_awaited_once()
_, kwargs = mocked_send_reaction.await_args
self.assertEqual("wamid.ABC123", kwargs["target_message_id"])
@patch("core.views.compose.transport.send_reaction", new_callable=AsyncMock)
def test_toggle_removes_existing_actor_reaction(self, mocked_send_reaction):
person, _, message = self._build_message(
service="signal",
identifier="+15551230000",
source_message_id="1771234567000",
)
message.receipt_payload = {
"reactions": [
{
"emoji": "👍",
"actor": f"web:{self.user.id}:signal",
"source_service": "signal",
"removed": False,
}
]
}
message.save(update_fields=["receipt_payload"])
mocked_send_reaction.return_value = True
with patch(
"core.views.compose.history.apply_reaction",
new=AsyncMock(return_value=message),
):
response = self.client.post(
reverse("compose_react"),
{
"service": "signal",
"identifier": "+15551230000",
"person": str(person.id),
"message_id": str(message.id),
"emoji": "👍",
},
)
self.assertEqual(200, response.status_code)
payload = response.json()
self.assertTrue(payload["ok"])
self.assertTrue(payload["remove"])
_, kwargs = mocked_send_reaction.await_args
self.assertTrue(kwargs["remove"])
def test_unsupported_service_returns_error(self):
response = self.client.post(
reverse("compose_react"),
{
"service": "xmpp",
"identifier": "someone@example.com",
"message_id": "does-not-matter",
"emoji": "👍",
},
)
self.assertEqual(200, response.status_code)
self.assertEqual(
{"ok": False, "error": "service_not_supported"},
response.json(),
)
def test_missing_whatsapp_target_returns_error(self):
person, _, message = self._build_message(
service="whatsapp",
identifier="12345@s.whatsapp.net",
source_message_id="",
)
response = self.client.post(
reverse("compose_react"),
{
"service": "whatsapp",
"identifier": "12345@s.whatsapp.net",
"person": str(person.id),
"message_id": str(message.id),
"emoji": "😂",
},
)
self.assertEqual(200, response.status_code)
self.assertEqual(
{"ok": False, "error": "whatsapp_target_unresolvable"},
response.json(),
)
@patch("core.views.compose.transport.send_reaction", new_callable=AsyncMock)
def test_whatsapp_web_local_message_without_bridge_is_unresolvable(
self, mocked_send_reaction
):
person, _, message = self._build_message(
service="whatsapp",
identifier="12345@s.whatsapp.net",
source_message_id="1771234567000",
)
message.source_service = "web"
message.save(update_fields=["source_service"])
response = self.client.post(
reverse("compose_react"),
{
"service": "whatsapp",
"identifier": "12345@s.whatsapp.net",
"person": str(person.id),
"message_id": str(message.id),
"emoji": "😮",
},
)
self.assertEqual(200, response.status_code)
self.assertEqual(
{"ok": False, "error": "whatsapp_target_unresolvable"},
response.json(),
)
mocked_send_reaction.assert_not_awaited()
def test_compose_page_renders_reaction_actions_for_signal(self):
person, _, _ = self._build_message(
service="signal",
identifier="+15551230000",
source_message_id="1771234567000",
)
response = self.client.get(
reverse("compose_page"),
{
"service": "signal",
"identifier": "+15551230000",
"person": str(person.id),
},
)
self.assertEqual(200, response.status_code)
content = response.content.decode("utf-8")
self.assertIn("data-react-url=", content)
self.assertIn("compose-reaction-actions", content)
def test_compose_page_hides_reaction_actions_for_unsupported_service(self):
person, _, _ = self._build_message(
service="xmpp",
identifier="person@example.com",
source_message_id="msg-1",
)
response = self.client.get(
reverse("compose_page"),
{
"service": "xmpp",
"identifier": "person@example.com",
"person": str(person.id),
},
)
self.assertEqual(200, response.status_code)
self.assertNotIn(
'class="compose-reaction-actions"',
response.content.decode("utf-8"),
)