Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/aula/models/message.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import datetime
from dataclasses import dataclass, field
from typing import Any

from ..utils.dates import parse_api_datetime
from ..utils.html import html_to_markdown, html_to_plain
from ..utils.mapping import get_in
from .attachment import Attachment, parse_attachments
Expand All @@ -13,6 +15,10 @@ class Message(AulaDataClass):
content_html: str
attachments: list[Attachment] = field(default_factory=list)
_raw: dict | None = field(default=None, repr=False)
# Keyword-only, so the positional order above (and ``_raw`` in it) stays
# what it was before these were added.
sender_name: str | None = field(default=None, kw_only=True)
send_datetime: datetime.datetime | None = field(default=None, kw_only=True)

@property
def content(self) -> str:
Expand Down Expand Up @@ -40,5 +46,7 @@ def from_dict(cls, data: dict[str, Any]) -> Message:
id=str(message_id) if message_id is not None else "",
content_html=text if isinstance(text, str) else "",
attachments=parse_attachments(data.get("attachments")),
sender_name=get_in(data, "sender.fullName", default=None) or None,
send_datetime=parse_api_datetime(data.get("sendDateTime")),
_raw=data,
)
48 changes: 48 additions & 0 deletions tests/models/test_message.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Tests for aula.models.message."""

import datetime

import pytest

from aula.models.message import Message


Expand Down Expand Up @@ -79,3 +83,47 @@ def test_message_from_dict_without_attachments():
msg = Message.from_dict({"id": "m3", "text": {"html": ""}})
assert msg.attachments == []
assert msg.has_attachments is False


def test_message_from_dict_sender_and_send_time():
data = {
"id": "m2",
"text": {"html": "<p>Hej</p>"},
"sender": {"fullName": "Anne Lærer", "shortName": "AL"},
"sendDateTime": "2026-09-25T08:15:00+02:00",
}
msg = Message.from_dict(data)
assert msg.sender_name == "Anne Lærer"
assert msg.send_datetime == datetime.datetime.fromisoformat("2026-09-25T08:15:00+02:00")
result = dict(msg)
assert result["sender_name"] == "Anne Lærer"
assert "send_datetime" in result


def test_message_from_dict_without_sender():
msg = Message.from_dict({"id": "m3", "text": "plain"})
assert msg.sender_name is None
assert msg.send_datetime is None


@pytest.mark.parametrize(
"sender",
[None, {}, {"fullName": None}, {"fullName": ""}, "not a dict"],
)
def test_message_from_dict_sender_missing_or_empty(sender):
msg = Message.from_dict({"id": "m4", "text": "x", "sender": sender})
assert msg.sender_name is None


@pytest.mark.parametrize("value", [None, "", "not a date", 12345])
def test_message_from_dict_send_datetime_unreadable(value):
msg = Message.from_dict({"id": "m5", "text": "x", "sendDateTime": value})
assert msg.send_datetime is None


def test_message_positional_arguments_unchanged():
"""``_raw`` is still the fourth positional field: the new ones are keyword-only."""
msg = Message("1", "text", [], {"original": True})
assert msg._raw == {"original": True}
assert msg.sender_name is None
assert "_raw" not in dict(msg)
Loading