diff --git a/src/aula/models/message.py b/src/aula/models/message.py index 71b13d9..01e70e8 100644 --- a/src/aula/models/message.py +++ b/src/aula/models/message.py @@ -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 @@ -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: @@ -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, ) diff --git a/tests/models/test_message.py b/tests/models/test_message.py index fee5a04..27aa36e 100644 --- a/tests/models/test_message.py +++ b/tests/models/test_message.py @@ -1,5 +1,9 @@ """Tests for aula.models.message.""" +import datetime + +import pytest + from aula.models.message import Message @@ -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": "

Hej

"}, + "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)