190 lines
6.7 KiB
Python
190 lines
6.7 KiB
Python
from __future__ import annotations
|
|
|
|
from asgiref.sync import async_to_sync
|
|
from django.test import TestCase
|
|
|
|
from core.messaging import history
|
|
from core.models import ChatSession, Message, Person, PersonIdentifier, User
|
|
from core.views.compose import _serialize_message
|
|
|
|
|
|
class ReactionNormalizationTests(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create_user(
|
|
username="react-normalize",
|
|
email="react-normalize@example.com",
|
|
password="pw",
|
|
)
|
|
self.person = Person.objects.create(user=self.user, name="Reaction Person")
|
|
self.identifier = PersonIdentifier.objects.create(
|
|
user=self.user,
|
|
person=self.person,
|
|
service="signal",
|
|
identifier="+15551239999",
|
|
)
|
|
self.session = ChatSession.objects.create(
|
|
user=self.user,
|
|
identifier=self.identifier,
|
|
)
|
|
|
|
def test_apply_reaction_prefers_exact_source_timestamp_match(self):
|
|
near_message = Message.objects.create(
|
|
user=self.user,
|
|
session=self.session,
|
|
ts=1700000000100,
|
|
sender_uuid="author-near",
|
|
text="near",
|
|
source_service="signal",
|
|
source_message_id="1700000000100",
|
|
)
|
|
exact_message = Message.objects.create(
|
|
user=self.user,
|
|
session=self.session,
|
|
ts=1700000000000,
|
|
sender_uuid="author-exact",
|
|
text="exact",
|
|
source_service="signal",
|
|
source_message_id="1700000000000",
|
|
)
|
|
|
|
updated = async_to_sync(history.apply_reaction)(
|
|
self.user,
|
|
self.identifier,
|
|
target_ts=1700000000000,
|
|
emoji="❤️",
|
|
source_service="signal",
|
|
actor="reactor-1",
|
|
target_author="author-exact",
|
|
remove=False,
|
|
payload={"origin": "test"},
|
|
)
|
|
|
|
self.assertEqual(str(exact_message.id), str(updated.id))
|
|
exact_message.refresh_from_db()
|
|
near_message.refresh_from_db()
|
|
self.assertEqual(1, len((exact_message.receipt_payload or {}).get("reactions") or []))
|
|
self.assertEqual(
|
|
"exact_source_message_id_ts",
|
|
str((exact_message.receipt_payload or {}).get("reaction_last_match_strategy") or ""),
|
|
)
|
|
self.assertEqual(0, len((near_message.receipt_payload or {}).get("reactions") or []))
|
|
|
|
def test_remove_without_emoji_is_audited_not_active(self):
|
|
message = Message.objects.create(
|
|
user=self.user,
|
|
session=self.session,
|
|
ts=1700000001000,
|
|
sender_uuid="author-1",
|
|
text="msg",
|
|
source_service="signal",
|
|
source_message_id="1700000001000",
|
|
)
|
|
|
|
async_to_sync(history.apply_reaction)(
|
|
self.user,
|
|
self.identifier,
|
|
target_ts=1700000001000,
|
|
emoji="",
|
|
source_service="whatsapp",
|
|
actor="actor-1",
|
|
remove=True,
|
|
payload={"origin": "test"},
|
|
)
|
|
|
|
message.refresh_from_db()
|
|
payload = dict(message.receipt_payload or {})
|
|
self.assertEqual([], list(payload.get("reactions") or []))
|
|
self.assertEqual(1, len(list(payload.get("reaction_events") or [])))
|
|
|
|
def test_emoji_only_reply_text_is_not_reaction(self):
|
|
anchor = Message.objects.create(
|
|
user=self.user,
|
|
session=self.session,
|
|
ts=1700000002000,
|
|
sender_uuid="author-1",
|
|
text="anchor",
|
|
source_service="signal",
|
|
source_message_id="1700000002000",
|
|
)
|
|
heart_reply = Message.objects.create(
|
|
user=self.user,
|
|
session=self.session,
|
|
ts=1700000003000,
|
|
sender_uuid="author-2",
|
|
text="❤️",
|
|
source_service="signal",
|
|
source_message_id="1700000003000",
|
|
reply_to=anchor,
|
|
reply_source_service="signal",
|
|
reply_source_message_id="1700000002000",
|
|
receipt_payload={},
|
|
)
|
|
|
|
serialized = _serialize_message(heart_reply)
|
|
self.assertEqual("❤️", serialized["text"])
|
|
self.assertEqual([], list(serialized.get("reactions") or []))
|
|
self.assertEqual(str(anchor.id), serialized["reply_to_id"])
|
|
|
|
def test_apply_message_edit_tracks_history_and_updates_text(self):
|
|
message = Message.objects.create(
|
|
user=self.user,
|
|
session=self.session,
|
|
ts=1700000004000,
|
|
sender_uuid="author-3",
|
|
text="before",
|
|
source_service="signal",
|
|
source_message_id="1700000004000",
|
|
)
|
|
|
|
updated = async_to_sync(history.apply_message_edit)(
|
|
self.user,
|
|
self.identifier,
|
|
target_ts=1700000004000,
|
|
new_text="after",
|
|
source_service="signal",
|
|
actor="+15550000000",
|
|
payload={"origin": "test"},
|
|
)
|
|
|
|
self.assertIsNotNone(updated)
|
|
message.refresh_from_db()
|
|
self.assertEqual("after", str(message.text or ""))
|
|
edit_history = list((message.receipt_payload or {}).get("edit_history") or [])
|
|
self.assertEqual(1, len(edit_history))
|
|
self.assertEqual("before", str(edit_history[0].get("previous_text") or ""))
|
|
self.assertEqual("after", str(edit_history[0].get("new_text") or ""))
|
|
|
|
def test_serialize_message_marks_deleted_and_preserves_edit_history(self):
|
|
message = Message.objects.create(
|
|
user=self.user,
|
|
session=self.session,
|
|
ts=1700000005000,
|
|
sender_uuid="author-4",
|
|
text="keep me",
|
|
source_service="signal",
|
|
source_message_id="1700000005000",
|
|
receipt_payload={
|
|
"edit_history": [
|
|
{
|
|
"edited_ts": 1700000005100,
|
|
"source_service": "signal",
|
|
"actor": "+15550000000",
|
|
"previous_text": "old",
|
|
"new_text": "keep me",
|
|
}
|
|
],
|
|
"is_deleted": True,
|
|
"deleted": {
|
|
"deleted_ts": 1700000005200,
|
|
"source_service": "signal",
|
|
"actor": "+15550000000",
|
|
},
|
|
},
|
|
)
|
|
|
|
serialized = _serialize_message(message)
|
|
self.assertTrue(bool(serialized.get("is_deleted")))
|
|
self.assertEqual("(message deleted)", str(serialized.get("display_text") or ""))
|
|
self.assertEqual(1, int(serialized.get("edit_count") or 0))
|
|
self.assertEqual(1, len(list(serialized.get("edit_history") or [])))
|