260 lines
9.1 KiB
Python
260 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"),
|
|
)
|