tests: add make_test_event helper for stricter event construction

Prepare for porting the event class into Rust, where the constructor
strictly validates that all format-required fields (depth, hashes,
origin_server_ts, auth_events, prev_events, ...) are present. Most
tests build minimal dicts that omit these fields because they only
care about the fields the test exercises. Introduce make_test_event
and make_test_pdu_event, which layer format-version-aware defaults
on top of caller-supplied fields so individual tests don't need to
spell out every required key.
This commit is contained in:
Erik Johnston
2026-05-14 11:28:34 +01:00
parent ace8447037
commit 95e146a0a0
+140
View File
@@ -0,0 +1,140 @@
#
# This file is licensed under the Affero General Public License (AGPL) version 3.
#
# Copyright (C) 2026 Element Creations Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# See the GNU Affero General Public License for more details:
# <https://www.gnu.org/licenses/agpl-3.0.html>.
#
"""Test-only helpers for building events.
The Rust `Event` constructor strictly validates that all format-required
fields are present on the event dict. Most production code paths always
supply these, but tests routinely build minimal dicts that omit fields
like `depth`, `hashes`, `origin_server_ts`, `auth_events`, or
`prev_events`. This module provides `make_test_event`, which fills in
sensible defaults for the required fields based on the event format
version, so individual tests only need to specify the fields they
actually care about.
"""
from typing import Any
from synapse.api.room_versions import (
EventFormatVersions,
RoomVersion,
RoomVersions,
)
from synapse.events import EventBase, make_event_from_dict
from synapse.federation.federation_base import event_from_pdu_json
from synapse.types import JsonDict
def default_event_fields(room_version: RoomVersion) -> JsonDict:
"""Return the default values for every field required by `room_version`.
Tests can call this directly when they need to merge defaults into a
builder (e.g. inside another helper) rather than constructing the
event up-front.
"""
defaults: JsonDict = {
"type": "m.test",
"sender": "@test:test",
"content": {},
"depth": 1,
"origin_server_ts": 1,
"hashes": {"sha256": ""},
}
if room_version.event_format == EventFormatVersions.ROOM_V1_V2:
# V1 events store auth/prev as `[(event_id, hashes)]` pairs and
# carry an explicit `event_id` and `room_id`.
defaults["auth_events"] = []
defaults["prev_events"] = []
defaults["room_id"] = "!test:test"
defaults["event_id"] = "$test:test"
elif room_version.event_format in (
EventFormatVersions.ROOM_V3,
EventFormatVersions.ROOM_V4_PLUS,
):
# V2/V3 and V4 share the flat auth/prev list shape. V2/V3 always
# carry a `room_id`; V4 makes it optional on create events but
# required otherwise, so callers building non-create events
# supply it explicitly.
defaults["auth_events"] = []
defaults["prev_events"] = []
defaults["room_id"] = "!test:test"
else:
# V11 Hydra+ and VMSC4242 derive the room_id from the create
# event's ID, so we never default it here — providing one would
# break auth-event derivation for create events on these
# versions. Callers supply room_id explicitly on non-create
# events.
defaults["auth_events"] = []
defaults["prev_events"] = []
if room_version.msc4242_state_dags:
defaults["prev_state_events"] = []
return defaults
def make_test_event(
event_dict: JsonDict | None = None,
room_version: RoomVersion = RoomVersions.V1,
internal_metadata_dict: JsonDict | None = None,
rejected_reason: str | None = None,
**fields: Any,
) -> EventBase:
"""Build an `EventBase` with defaults for the strict-required fields.
Pass an `event_dict` and/or `**fields` keyword arguments — both are
merged on top of the format-version defaults from
`default_event_fields`. Explicit values win over defaults, and
`**fields` wins over `event_dict` so call sites can override a
shared base dict with one-off tweaks.
Args:
event_dict: Explicit event fields. Wins over defaults; loses to
`**fields`.
room_version: Determines which format-specific defaults apply.
internal_metadata_dict: Forwarded to `make_event_from_dict`.
rejected_reason: Forwarded to `make_event_from_dict`.
**fields: Additional event fields. Wins over `event_dict`.
Returns:
The constructed `EventBase`.
"""
merged: JsonDict = {
**default_event_fields(room_version),
**(event_dict or {}),
**fields,
}
return make_event_from_dict(
merged,
room_version=room_version,
internal_metadata_dict=internal_metadata_dict,
rejected_reason=rejected_reason,
)
def make_test_pdu_event(
pdu: JsonDict,
room_version: RoomVersion,
received_time: int | None = None,
) -> EventBase:
"""Wrapper around `event_from_pdu_json` for test PDU dicts.
Federation-side test fixtures often omit fields the strict Rust ctor
requires (e.g. `hashes`, `auth_events`, `prev_events`, `depth`)
because those tests focus on transport/auth flow rather than event
well-formedness. This helper layers in the same format-version
defaults as `make_test_event` before delegating.
"""
pdu = {**default_event_fields(room_version), **pdu}
return event_from_pdu_json(pdu, room_version, received_time=received_time)