From f49fa66eb69305661a40afada24031fde4a019dc Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Sun, 20 Sep 2026 16:51:02 +0530 Subject: [PATCH] test(matrix): keep two invariant tests for the pending-invite gate Trim the pending-invite allowlist tests to the fail-closed invariant (parametrized: non-allowed inviter, missing invite_state, empty events, member event for another user, empty sender -> no join, nothing recorded in m.direct) and the positive invariant (allow-listed inviter joined and the direct invite recorded via _record_dm_room). Dropped: the exact-log-text assertion for the allow-all direct-invite warning (change-detector), the empty-allowlist / unknown-bot-user-id / allow-all cases (already covered by _is_authorized_user's own tests and the fail-closed parametrization), and the three additions to test_matrix_dm_invite_recording.py, whose positive path is the same one test_allowed_inviter_is_joined asserts. --- .../test_matrix_dm_invite_recording.py | 103 ------------------ .../test_matrix_pending_invite_auth.py | 69 ------------ 2 files changed, 172 deletions(-) diff --git a/tests/gateway/test_matrix_dm_invite_recording.py b/tests/gateway/test_matrix_dm_invite_recording.py index ed8c8eae01..9ebecfae9d 100644 --- a/tests/gateway/test_matrix_dm_invite_recording.py +++ b/tests/gateway/test_matrix_dm_invite_recording.py @@ -101,109 +101,6 @@ class TestOnInviteRecordsDM: adapter._record_dm_room.assert_not_awaited() -# --------------------------------------------------------------------------- -# _schedule_pending_invite_joins DM recording -# --------------------------------------------------------------------------- - - -def _member_invite_event( - state_key="@hermes:example.org", - sender="@alice:example.org", - is_direct=True, - membership="invite", -): - """Create a stripped m.room.member event as found in invite_state.""" - return { - "type": "m.room.member", - "state_key": state_key, - "sender": sender, - "content": {"membership": membership, "is_direct": is_direct}, - } - - -def _invite_sync_data(room_id="!dm_room:example.org", invite_state=None): - """Create a sync payload with one pending invite room.""" - room = {} if invite_state is None else {"invite_state": invite_state} - return {"rooms": {"invite": {room_id: room}}, "next_batch": "s1"} - - -class TestPendingInviteReconciliationRecordsDM: - """_schedule_pending_invite_joins threads the DM signal from invite_state. - - After a gateway restart a direct invite is reconciled from sync's - ``rooms.invite`` rather than a live invite event. The stripped - ``m.room.member`` event in ``invite_state`` still carries ``is_direct`` - and the inviter; without threading them through, the room is never - recorded in ``m.direct`` and is misclassified as a group (surfaced by - the triage of #62493). - """ - - @staticmethod - async def _drain_invite_tasks(adapter): - """Await any tasks _schedule_invite_join spawned.""" - for task in list(adapter._invite_join_tasks.values()): - await task - - @pytest.mark.asyncio - async def test_reconciled_direct_invite_records_dm(self): - adapter = _make_adapter() - adapter._join_room_by_id = AsyncMock(return_value=True) - adapter._record_dm_room = AsyncMock() - - sync_data = _invite_sync_data( - invite_state={"events": [_member_invite_event(is_direct=True)]} - ) - adapter._schedule_pending_invite_joins(sync_data) - await self._drain_invite_tasks(adapter) - - adapter._join_room_by_id.assert_awaited_once_with("!dm_room:example.org") - adapter._record_dm_room.assert_awaited_once_with( - "!dm_room:example.org", "@alice:example.org" - ) - - @pytest.mark.asyncio - async def test_reconciled_direct_invite_ends_up_classified_as_dm(self): - """End to end: the reconciled room lands in _dm_rooms as a DM.""" - adapter = _make_adapter() - adapter._join_room_by_id = AsyncMock(return_value=True) - adapter._client = MagicMock() - adapter._client.get_account_data = AsyncMock( - side_effect=Exception("M_NOT_FOUND") - ) - adapter._client.set_account_data = AsyncMock() - - sync_data = _invite_sync_data( - invite_state={"events": [_member_invite_event(is_direct=True)]} - ) - adapter._schedule_pending_invite_joins(sync_data) - await self._drain_invite_tasks(adapter) - - adapter._client.set_account_data.assert_awaited_once_with( - "m.direct", {"@alice:example.org": ["!dm_room:example.org"]} - ) - assert adapter._dm_rooms.get("!dm_room:example.org") is True - - @pytest.mark.asyncio - async def test_reconciled_non_direct_invite_does_not_record(self): - """A pending invite without the is_direct flag joins (the inviter - is allow-listed) but records nothing in m.direct. Invites whose - inviter cannot be read from the stripped state at all are covered - by test_matrix_pending_invite_auth.py: they are rejected outright - by the inviter allowlist gate, so no join happens either.""" - adapter = _make_adapter() - adapter._join_room_by_id = AsyncMock(return_value=True) - adapter._record_dm_room = AsyncMock() - - sync_data = _invite_sync_data( - invite_state={"events": [_member_invite_event(is_direct=False)]} - ) - adapter._schedule_pending_invite_joins(sync_data) - await self._drain_invite_tasks(adapter) - - adapter._join_room_by_id.assert_awaited_once_with("!dm_room:example.org") - adapter._record_dm_room.assert_not_awaited() - - # --------------------------------------------------------------------------- # _record_dm_room # --------------------------------------------------------------------------- diff --git a/tests/gateway/test_matrix_pending_invite_auth.py b/tests/gateway/test_matrix_pending_invite_auth.py index eb4a998b52..b7feb2a451 100644 --- a/tests/gateway/test_matrix_pending_invite_auth.py +++ b/tests/gateway/test_matrix_pending_invite_auth.py @@ -8,7 +8,6 @@ state, or an invite from an arbitrary federated user that arrives while the gateway is down gets auto-joined on restart. """ -import logging import time from unittest.mock import AsyncMock @@ -87,19 +86,6 @@ class TestPendingInviteAuthorization: "!pending_room:example.org", "@alice:example.org" ) - @pytest.mark.asyncio - async def test_unknown_bot_user_id_fails_closed(self): - adapter = _make_adapter() - adapter._user_id = "" - - sync_data = _invite_sync_data(invite_state={"events": [_member_invite_event()]}) - adapter._schedule_pending_invite_joins(sync_data) - await _drain_invite_tasks(adapter) - - adapter._join_room_by_id.assert_not_awaited() - adapter._record_dm_room.assert_not_awaited() - assert adapter._invite_join_tasks == {} - @pytest.mark.parametrize( "invite_state", [ @@ -133,58 +119,3 @@ class TestPendingInviteAuthorization: adapter._join_room_by_id.assert_not_awaited() adapter._record_dm_room.assert_not_awaited() assert adapter._invite_join_tasks == {} - - @pytest.mark.asyncio - async def test_empty_allowlist_fails_closed(self): - """With no allowlist configured, _on_invite rejects every invite; - reconciliation must do the same.""" - adapter = _make_adapter() - adapter._allowed_user_ids = set() - - sync_data = _invite_sync_data(invite_state={"events": [_member_invite_event()]}) - adapter._schedule_pending_invite_joins(sync_data) - await _drain_invite_tasks(adapter) - - adapter._join_room_by_id.assert_not_awaited() - adapter._record_dm_room.assert_not_awaited() - - @pytest.mark.asyncio - async def test_allow_all_env_bypasses_gate(self, monkeypatch): - """GATEWAY_ALLOW_ALL_USERS disables the gate, exactly as it does - for _on_invite, even when the inviter is unknown.""" - monkeypatch.setenv("GATEWAY_ALLOW_ALL_USERS", "true") - adapter = _make_adapter() - adapter._allowed_user_ids = set() - - sync_data = _invite_sync_data(invite_state=None) - adapter._schedule_pending_invite_joins(sync_data) - await _drain_invite_tasks(adapter) - - adapter._join_room_by_id.assert_awaited_once_with("!pending_room:example.org") - adapter._record_dm_room.assert_not_awaited() - - @pytest.mark.asyncio - async def test_allow_all_logs_direct_invite_without_inviter( - self, monkeypatch, caplog - ): - monkeypatch.setenv("GATEWAY_ALLOW_ALL_USERS", "true") - adapter = _make_adapter() - adapter._allowed_user_ids = set() - - sync_data = _invite_sync_data( - invite_state={"events": [_member_invite_event(sender="")]} - ) - with caplog.at_level( - logging.WARNING, - logger="plugins.platforms.matrix.adapter", - ): - adapter._schedule_pending_invite_joins(sync_data) - await _drain_invite_tasks(adapter) - - adapter._join_room_by_id.assert_awaited_once_with("!pending_room:example.org") - adapter._record_dm_room.assert_not_awaited() - assert [record.getMessage() for record in caplog.records] == [ - "Matrix: joining direct invite to !pending_room:example.org " - "without recording it in m.direct because the invite state " - "has no inviter" - ]