debug none sender uuid #4

Merged
Ghasemi merged 1 commit from feature/remove-system-message into master 2026-07-21 08:44:47 -04:00
4 changed files with 28 additions and 25 deletions

View file

@ -156,9 +156,14 @@ class MattermostClient:
return channel_id
def post_message(self, channel_id: str, message: str) -> str:
def post_message(self, channel_id: str, message: str, sender_uuid: UUID) -> str:
"""
Post a message to the given channel using the admin token.
Every post is authored by the single service account, not the real
sender — there are no per-user tokens. `sender_uuid` is stamped into
the post's `props` so it can be recovered later in `get_posts()`;
Mattermost's own `user_id` on the post is always the service account.
Returns the opaque mattermost_post_id string.
"""
try:
@ -166,6 +171,7 @@ class MattermostClient:
options={
"channel_id": channel_id,
"message": message,
"props": {"sender_uuid": str(sender_uuid)},
}
)
return post["id"]

View file

@ -7,7 +7,7 @@ from apps.chat.events.publishers.push import PushPublisher
from apps.chat.events.publishers.websocket import WebSocketPublisher
from apps.chat.exceptions import ConversationClosedError
from apps.chat.integrations.mattermost.client import MattermostClient
from apps.chat.models import Conversation, ConversationStatus, MattermostAccountMapping
from apps.chat.models import Conversation, ConversationStatus
from apps.chat.services.account import AccountService
from apps.chat.services.storage import StorageService
@ -62,7 +62,9 @@ class MessageService:
else:
mm_message = text or ""
post_id = self._mm.post_message(conversation.mattermost_channel_id, mm_message)
post_id = self._mm.post_message(
conversation.mattermost_channel_id, mm_message, sender_uuid
)
event = MessageSentEvent(
chat_uuid=conversation_uuid,
@ -99,19 +101,14 @@ class MessageService:
since=since,
)
mm_user_ids = {p["user_id"] for p in raw_posts if p.get("user_id")}
mappings = {
m.mattermost_user_id: m.user_uuid
for m in MattermostAccountMapping.objects.filter(
mattermost_user_id__in=mm_user_ids
)
} if mm_user_ids else {}
return [self._normalize_post(p) for p in raw_posts]
return [self._normalize_post(p, mappings) for p in raw_posts]
def _normalize_post(self, post: dict, mappings: dict) -> dict:
mm_uid = post.get("user_id")
sender_uuid = mappings.get(mm_uid)
def _normalize_post(self, post: dict) -> dict:
sender_uuid_raw = (post.get("props") or {}).get("sender_uuid")
try:
sender_uuid = UUID(sender_uuid_raw) if sender_uuid_raw else None
except ValueError:
sender_uuid = None
msg = post.get("message", "")
created_ms = post.get("create_at")
created_at = (

View file

@ -109,4 +109,4 @@ def test_send_message_after_reopen_succeeds():
result = svc.send(conv.uuid, sender_uuid, "text", text="hello again")
assert result["post_id"] == "post-1"
mm_client.post_message.assert_called_once_with("ch-close", "hello again")
mm_client.post_message.assert_called_once_with("ch-close", "hello again", sender_uuid)

View file

@ -3,7 +3,7 @@ from unittest.mock import Mock
import pytest
from apps.chat.models import Conversation, MattermostAccountMapping
from apps.chat.models import Conversation
from apps.chat.services.message import MessageService
@ -36,7 +36,7 @@ def test_send_text_message():
result = svc.send(conv.uuid, sender_uuid, "text", text="Hello world")
mm_client.post_message.assert_called_once_with("ch-send-text", "Hello world")
mm_client.post_message.assert_called_once_with("ch-send-text", "Hello world", sender_uuid)
storage.get_download_url.assert_not_called()
ws_publisher.publish.assert_called_once()
@ -62,7 +62,9 @@ def test_send_image_message():
result = svc.send(conv.uuid, sender_uuid, "image", object_key=object_key)
storage.get_download_url.assert_called_once_with(object_key)
mm_client.post_message.assert_called_once_with("ch-send-image", f"image:{object_key}")
mm_client.post_message.assert_called_once_with(
"ch-send-image", f"image:{object_key}", sender_uuid
)
assert result["url"] == download_url
assert result["message_type"] == "image"
@ -71,11 +73,7 @@ def test_send_image_message():
def test_list_messages_normalized():
conv = Conversation.objects.create(mattermost_channel_id="ch-list")
sender_uuid = uuid.uuid4()
mm_user_id = "mm-user-xyz"
MattermostAccountMapping.objects.create(
user_uuid=sender_uuid, mattermost_user_id=mm_user_id
)
mm_user_id = "mm-service-bot" # every post is authored by the service account
object_key = "voice/def456/clip.ogg"
raw_posts = [
@ -83,12 +81,14 @@ def test_list_messages_normalized():
"id": "p1",
"user_id": mm_user_id,
"message": "First message",
"props": {"sender_uuid": str(sender_uuid)},
"create_at": 1700000000000,
},
{
"id": "p2",
"user_id": mm_user_id,
"message": f"voice:{object_key}",
"props": {"sender_uuid": str(sender_uuid)},
"create_at": 1700000001000,
},
{
@ -130,7 +130,7 @@ def test_list_messages_normalized():
system_msg = messages[2]
assert system_msg["post_id"] == "sys1"
assert system_msg["sender_uuid"] == sender_uuid
assert system_msg["sender_uuid"] is None
assert system_msg["mattermost_message_type"] == "system_add_to_channel"