fix(M3): immuabilité et validation des modèles — correction des écarts FIXME_M3

Immuabilité :
- Collections des modèles frozen : list → tuple (Lesson, TheoreticalLesson,
  SchoolEvent, AgendaDiff, Homework, XmppMessage, ExternalInfo)
- Gel de HomeworkBlock et ExternalInfo (sous-modèles imbriqués dans contrats)
- Valeurs par défaut immuables : Field(default=(), ...)

Validation de domaine :
- TheoreticalLesson.day_of_week : Field(ge=0, le=6)
- Lesson/TheoreticalLesson : start < end (validateur de modèle)
- SchoolEvent : from_date <= to_date
- AgendaChange : cohérence type/payload (ADDED/MODIFIED → lesson,
  REMOVED → theoretical_lesson)
- CalDAVSyncResult : compteurs Field(ge=0) sur added/updated/removed

Tests : 66 tests (construction/sérialisation, immuabilité, invariants)
couvrant le gel, la mutabilité légitime, et les cas invalides par invariant.

Co-authored-by: opencode/coder <coder@agents.invalid>
Co-authored-by: opencode/test-engineer <test-engineer@agents.invalid>
This commit is contained in:
2026-09-06 12:36:27 +02:00
parent 2f6184df0b
commit 292aef4f5b
9 changed files with 1155 additions and 27 deletions

View File

@@ -26,17 +26,22 @@ class BlogArticle(BaseModel):
class ExternalInfo(BaseModel):
"""Agrège les informations externes (blog, messages Pronote) pour le message XMPP."""
"""Agrège les informations externes (blog, messages Pronote) pour le message XMPP.
blog_articles: list[BlogArticle] = Field(
default_factory=list,
Modèle figé (``frozen``) : les instances sont immuables après création.
"""
model_config = ConfigDict(frozen=True)
blog_articles: tuple[BlogArticle, ...] = Field(
default=(),
description="Liste des nouveaux articles du blog",
)
pronote_messages: list[Message] = Field(
default_factory=list,
pronote_messages: tuple[Message, ...] = Field(
default=(),
description="Liste des messages Pronote",
)
other_info: list[str] = Field(
default_factory=list,
other_info: tuple[str, ...] = Field(
default=(),
description="Autres informations (extensible)",
)