From f1c4dfb08b530f2bfaf9c6723ce69ccd231a3370 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 12 Jun 2024 04:27:46 -0600 Subject: Add report room API (MSC4151) (#17270) https://github.com/matrix-org/matrix-spec-proposals/pull/4151 This is intended to be enabled by default for immediate use. When FCP is complete, the unstable endpoint will be dropped and stable endpoint supported instead - no backwards compatibility is expected for the unstable endpoint. --- changelog.d/17270.feature | 1 + synapse/config/experimental.py | 3 + synapse/rest/__init__.py | 4 +- synapse/rest/client/report_event.py | 99 --------- synapse/rest/client/reporting.py | 154 ++++++++++++++ synapse/rest/client/versions.py | 2 + synapse/storage/databases/main/room.py | 32 +++ .../schema/main/delta/85/06_add_room_reports.sql | 20 ++ tests/rest/admin/test_event_reports.py | 6 +- tests/rest/client/test_report_event.py | 141 ------------- tests/rest/client/test_reporting.py | 230 +++++++++++++++++++++ 11 files changed, 447 insertions(+), 245 deletions(-) create mode 100644 changelog.d/17270.feature delete mode 100644 synapse/rest/client/report_event.py create mode 100644 synapse/rest/client/reporting.py create mode 100644 synapse/storage/schema/main/delta/85/06_add_room_reports.sql delete mode 100644 tests/rest/client/test_report_event.py create mode 100644 tests/rest/client/test_reporting.py diff --git a/changelog.d/17270.feature b/changelog.d/17270.feature new file mode 100644 index 0000000000..4ea5e7be85 --- /dev/null +++ b/changelog.d/17270.feature @@ -0,0 +1 @@ +Add support for the unstable [MSC4151](https://github.com/matrix-org/matrix-spec-proposals/pull/4151) report room API. diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index 75fe6d7b24..5fe5b951dd 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -443,3 +443,6 @@ class ExperimentalConfig(Config): self.msc3916_authenticated_media_enabled = experimental.get( "msc3916_authenticated_media_enabled", False ) + + # MSC4151: Report room API (Client-Server API) + self.msc4151_enabled: bool = experimental.get("msc4151_enabled", False) diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py index 534dc0e276..0024ccf708 100644 --- a/synapse/rest/__init__.py +++ b/synapse/rest/__init__.py @@ -53,7 +53,7 @@ from synapse.rest.client import ( register, relations, rendezvous, - report_event, + reporting, room, room_keys, room_upgrade_rest_servlet, @@ -128,7 +128,7 @@ class ClientRestResource(JsonResource): tags.register_servlets(hs, client_resource) account_data.register_servlets(hs, client_resource) if is_main_process: - report_event.register_servlets(hs, client_resource) + reporting.register_servlets(hs, client_resource) openid.register_servlets(hs, client_resource) notifications.register_servlets(hs, client_resource) devices.register_servlets(hs, client_resource) diff --git a/synapse/rest/client/report_event.py b/synapse/rest/client/report_event.py deleted file mode 100644 index 447281931e..0000000000 --- a/synapse/rest/client/report_event.py +++ /dev/null @@ -1,99 +0,0 @@ -# -# This file is licensed under the Affero General Public License (AGPL) version 3. -# -# Copyright 2016 OpenMarket Ltd -# Copyright (C) 2023 New Vector, 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: -# . -# -# Originally licensed under the Apache License, Version 2.0: -# . -# -# [This file includes modifications made by New Vector Limited] -# -# - -import logging -from http import HTTPStatus -from typing import TYPE_CHECKING, Tuple - -from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError -from synapse.http.server import HttpServer -from synapse.http.servlet import RestServlet, parse_json_object_from_request -from synapse.http.site import SynapseRequest -from synapse.types import JsonDict - -from ._base import client_patterns - -if TYPE_CHECKING: - from synapse.server import HomeServer - -logger = logging.getLogger(__name__) - - -class ReportEventRestServlet(RestServlet): - PATTERNS = client_patterns("/rooms/(?P[^/]*)/report/(?P[^/]*)$") - - def __init__(self, hs: "HomeServer"): - super().__init__() - self.hs = hs - self.auth = hs.get_auth() - self.clock = hs.get_clock() - self.store = hs.get_datastores().main - self._event_handler = self.hs.get_event_handler() - - async def on_POST( - self, request: SynapseRequest, room_id: str, event_id: str - ) -> Tuple[int, JsonDict]: - requester = await self.auth.get_user_by_req(request) - user_id = requester.user.to_string() - - body = parse_json_object_from_request(request) - - if not isinstance(body.get("reason", ""), str): - raise SynapseError( - HTTPStatus.BAD_REQUEST, - "Param 'reason' must be a string", - Codes.BAD_JSON, - ) - if type(body.get("score", 0)) is not int: # noqa: E721 - raise SynapseError( - HTTPStatus.BAD_REQUEST, - "Param 'score' must be an integer", - Codes.BAD_JSON, - ) - - try: - event = await self._event_handler.get_event( - requester.user, room_id, event_id, show_redacted=False - ) - except AuthError: - # The event exists, but this user is not allowed to access this event. - event = None - - if event is None: - raise NotFoundError( - "Unable to report event: " - "it does not exist or you aren't able to see it." - ) - - await self.store.add_event_report( - room_id=room_id, - event_id=event_id, - user_id=user_id, - reason=body.get("reason"), - content=body, - received_ts=self.clock.time_msec(), - ) - - return 200, {} - - -def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: - ReportEventRestServlet(hs).register(http_server) diff --git a/synapse/rest/client/reporting.py b/synapse/rest/client/reporting.py new file mode 100644 index 0000000000..a95b83b14d --- /dev/null +++ b/synapse/rest/client/reporting.py @@ -0,0 +1,154 @@ +# +# This file is licensed under the Affero General Public License (AGPL) version 3. +# +# Copyright 2016 OpenMarket Ltd +# Copyright (C) 2023 New Vector, 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: +# . +# +# Originally licensed under the Apache License, Version 2.0: +# . +# +# [This file includes modifications made by New Vector Limited] +# +# + +import logging +from http import HTTPStatus +from typing import TYPE_CHECKING, Tuple + +from synapse._pydantic_compat import HAS_PYDANTIC_V2 +from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError +from synapse.http.server import HttpServer +from synapse.http.servlet import ( + RestServlet, + parse_and_validate_json_object_from_request, + parse_json_object_from_request, +) +from synapse.http.site import SynapseRequest +from synapse.types import JsonDict +from synapse.types.rest import RequestBodyModel + +from ._base import client_patterns + +if TYPE_CHECKING: + from synapse.server import HomeServer + +if TYPE_CHECKING or HAS_PYDANTIC_V2: + from pydantic.v1 import StrictStr +else: + from pydantic import StrictStr + +logger = logging.getLogger(__name__) + + +class ReportEventRestServlet(RestServlet): + PATTERNS = client_patterns("/rooms/(?P[^/]*)/report/(?P[^/]*)$") + + def __init__(self, hs: "HomeServer"): + super().__init__() + self.hs = hs + self.auth = hs.get_auth() + self.clock = hs.get_clock() + self.store = hs.get_datastores().main + self._event_handler = self.hs.get_event_handler() + + async def on_POST( + self, request: SynapseRequest, room_id: str, event_id: str + ) -> Tuple[int, JsonDict]: + requester = await self.auth.get_user_by_req(request) + user_id = requester.user.to_string() + + body = parse_json_object_from_request(request) + + if not isinstance(body.get("reason", ""), str): + raise SynapseError( + HTTPStatus.BAD_REQUEST, + "Param 'reason' must be a string", + Codes.BAD_JSON, + ) + if type(body.get("score", 0)) is not int: # noqa: E721 + raise SynapseError( + HTTPStatus.BAD_REQUEST, + "Param 'score' must be an integer", + Codes.BAD_JSON, + ) + + try: + event = await self._event_handler.get_event( + requester.user, room_id, event_id, show_redacted=False + ) + except AuthError: + # The event exists, but this user is not allowed to access this event. + event = None + + if event is None: + raise NotFoundError( + "Unable to report event: " + "it does not exist or you aren't able to see it." + ) + + await self.store.add_event_report( + room_id=room_id, + event_id=event_id, + user_id=user_id, + reason=body.get("reason"), + content=body, + received_ts=self.clock.time_msec(), + ) + + return 200, {} + + +class ReportRoomRestServlet(RestServlet): + # https://github.com/matrix-org/matrix-spec-proposals/pull/4151 + PATTERNS = client_patterns( + "/org.matrix.msc4151/rooms/(?P[^/]*)/report$", + releases=[], + v1=False, + unstable=True, + ) + + def __init__(self, hs: "HomeServer"): + super().__init__() + self.hs = hs + self.auth = hs.get_auth() + self.clock = hs.get_clock() + self.store = hs.get_datastores().main + + class PostBody(RequestBodyModel): + reason: StrictStr + + async def on_POST( + self, request: SynapseRequest, room_id: str + ) -> Tuple[int, JsonDict]: + requester = await self.auth.get_user_by_req(request) + user_id = requester.user.to_string() + + body = parse_and_validate_json_object_from_request(request, self.PostBody) + + room = await self.store.get_room(room_id) + if room is None: + raise NotFoundError("Room does not exist") + + await self.store.add_room_report( + room_id=room_id, + user_id=user_id, + reason=body.reason, + received_ts=self.clock.time_msec(), + ) + + return 200, {} + + +def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: + ReportEventRestServlet(hs).register(http_server) + + if hs.config.experimental.msc4151_enabled: + ReportRoomRestServlet(hs).register(http_server) diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index 56de6906d0..f428158139 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -149,6 +149,8 @@ class VersionsRestServlet(RestServlet): is not None ) ), + # MSC4151: Report room API (Client-Server API) + "org.matrix.msc4151": self.config.experimental.msc4151_enabled, }, }, ) diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py index 616c941687..b8a71c803e 100644 --- a/synapse/storage/databases/main/room.py +++ b/synapse/storage/databases/main/room.py @@ -2207,6 +2207,7 @@ class RoomStore(RoomBackgroundUpdateStore, RoomWorkerStore): super().__init__(database, db_conn, hs) self._event_reports_id_gen = IdGenerator(db_conn, "event_reports", "id") + self._room_reports_id_gen = IdGenerator(db_conn, "room_reports", "id") self._instance_name = hs.get_instance_name() @@ -2416,6 +2417,37 @@ class RoomStore(RoomBackgroundUpdateStore, RoomWorkerStore): ) return next_id + async def add_room_report( + self, + room_id: str, + user_id: str, + reason: str, + received_ts: int, + ) -> int: + """Add a room report + + Args: + room_id: The room ID being reported. + user_id: User who reports the room. + reason: Description that the user specifies. + received_ts: Time when the user submitted the report (milliseconds). + Returns: + Id of the room report. + """ + next_id = self._room_reports_id_gen.get_next() + await self.db_pool.simple_insert( + table="room_reports", + values={ + "id": next_id, + "received_ts": received_ts, + "room_id": room_id, + "user_id": user_id, + "reason": reason, + }, + desc="add_room_report", + ) + return next_id + async def block_room(self, room_id: str, user_id: str) -> None: """Marks the room as blocked. diff --git a/synapse/storage/schema/main/delta/85/06_add_room_reports.sql b/synapse/storage/schema/main/delta/85/06_add_room_reports.sql new file mode 100644 index 0000000000..f7b45276cf --- /dev/null +++ b/synapse/storage/schema/main/delta/85/06_add_room_reports.sql @@ -0,0 +1,20 @@ +-- +-- This file is licensed under the Affero General Public License (AGPL) version 3. +-- +-- Copyright (C) 2024 New Vector, 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: +-- . + +CREATE TABLE room_reports ( + id BIGINT NOT NULL PRIMARY KEY, + received_ts BIGINT NOT NULL, + room_id TEXT NOT NULL, + user_id TEXT NOT NULL, + reason TEXT NOT NULL +); diff --git a/tests/rest/admin/test_event_reports.py b/tests/rest/admin/test_event_reports.py index a0f978911a..feb410a11d 100644 --- a/tests/rest/admin/test_event_reports.py +++ b/tests/rest/admin/test_event_reports.py @@ -24,7 +24,7 @@ from twisted.test.proto_helpers import MemoryReactor import synapse.rest.admin from synapse.api.errors import Codes -from synapse.rest.client import login, report_event, room +from synapse.rest.client import login, reporting, room from synapse.server import HomeServer from synapse.types import JsonDict from synapse.util import Clock @@ -37,7 +37,7 @@ class EventReportsTestCase(unittest.HomeserverTestCase): synapse.rest.admin.register_servlets, login.register_servlets, room.register_servlets, - report_event.register_servlets, + reporting.register_servlets, ] def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: @@ -453,7 +453,7 @@ class EventReportDetailTestCase(unittest.HomeserverTestCase): synapse.rest.admin.register_servlets, login.register_servlets, room.register_servlets, - report_event.register_servlets, + reporting.register_servlets, ] def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: diff --git a/tests/rest/client/test_report_event.py b/tests/rest/client/test_report_event.py deleted file mode 100644 index 5903771e52..0000000000 --- a/tests/rest/client/test_report_event.py +++ /dev/null @@ -1,141 +0,0 @@ -# -# This file is licensed under the Affero General Public License (AGPL) version 3. -# -# Copyright 2021 Callum Brown -# Copyright (C) 2023 New Vector, 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: -# . -# -# Originally licensed under the Apache License, Version 2.0: -# . -# -# [This file includes modifications made by New Vector Limited] -# -# - -from twisted.test.proto_helpers import MemoryReactor - -import synapse.rest.admin -from synapse.rest.client import login, report_event, room -from synapse.server import HomeServer -from synapse.types import JsonDict -from synapse.util import Clock - -from tests import unittest - - -class ReportEventTestCase(unittest.HomeserverTestCase): - servlets = [ - synapse.rest.admin.register_servlets, - login.register_servlets, - room.register_servlets, - report_event.register_servlets, - ] - - def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: - self.admin_user = self.register_user("admin", "pass", admin=True) - self.admin_user_tok = self.login("admin", "pass") - self.other_user = self.register_user("user", "pass") - self.other_user_tok = self.login("user", "pass") - - self.room_id = self.helper.create_room_as( - self.other_user, tok=self.other_user_tok, is_public=True - ) - self.helper.join(self.room_id, user=self.admin_user, tok=self.admin_user_tok) - resp = self.helper.send(self.room_id, tok=self.admin_user_tok) - self.event_id = resp["event_id"] - self.report_path = f"rooms/{self.room_id}/report/{self.event_id}" - - def test_reason_str_and_score_int(self) -> None: - data = {"reason": "this makes me sad", "score": -100} - self._assert_status(200, data) - - def test_no_reason(self) -> None: - data = {"score": 0} - self._assert_status(200, data) - - def test_no_score(self) -> None: - data = {"reason": "this makes me sad"} - self._assert_status(200, data) - - def test_no_reason_and_no_score(self) -> None: - data: JsonDict = {} - self._assert_status(200, data) - - def test_reason_int_and_score_str(self) -> None: - data = {"reason": 10, "score": "string"} - self._assert_status(400, data) - - def test_reason_zero_and_score_blank(self) -> None: - data = {"reason": 0, "score": ""} - self._assert_status(400, data) - - def test_reason_and_score_null(self) -> None: - data = {"reason": None, "score": None} - self._assert_status(400, data) - - def test_cannot_report_nonexistent_event(self) -> None: - """ - Tests that we don't accept event reports for events which do not exist. - """ - channel = self.make_request( - "POST", - f"rooms/{self.room_id}/report/$nonsenseeventid:test", - {"reason": "i am very sad"}, - access_token=self.other_user_tok, - ) - self.assertEqual(404, channel.code, msg=channel.result["body"]) - self.assertEqual( - "Unable to report event: it does not exist or you aren't able to see it.", - channel.json_body["error"], - msg=channel.result["body"], - ) - - def test_cannot_report_event_if_not_in_room(self) -> None: - """ - Tests that we don't accept event reports for events that exist, but for which - the reporter should not be able to view (because they are not in the room). - """ - # Have the admin user create a room (the "other" user will not join this room). - new_room_id = self.helper.create_room_as(tok=self.admin_user_tok) - - # Have the admin user send an event in this room. - response = self.helper.send_event( - new_room_id, - "m.room.message", - content={ - "msgtype": "m.text", - "body": "This event has some bad words in it! Flip!", - }, - tok=self.admin_user_tok, - ) - event_id = response["event_id"] - - # Have the "other" user attempt to report it. Perhaps they found the event ID - # in a screenshot or something... - channel = self.make_request( - "POST", - f"rooms/{new_room_id}/report/{event_id}", - {"reason": "I'm not in this room but I have opinions anyways!"}, - access_token=self.other_user_tok, - ) - - # The "other" user is not in the room, so their report should be rejected. - self.assertEqual(404, channel.code, msg=channel.result["body"]) - self.assertEqual( - "Unable to report event: it does not exist or you aren't able to see it.", - channel.json_body["error"], - msg=channel.result["body"], - ) - - def _assert_status(self, response_status: int, data: JsonDict) -> None: - channel = self.make_request( - "POST", self.report_path, data, access_token=self.other_user_tok - ) - self.assertEqual(response_status, channel.code, msg=channel.result["body"]) diff --git a/tests/rest/client/test_reporting.py b/tests/rest/client/test_reporting.py new file mode 100644 index 0000000000..009deb9cb0 --- /dev/null +++ b/tests/rest/client/test_reporting.py @@ -0,0 +1,230 @@ +# +# This file is licensed under the Affero General Public License (AGPL) version 3. +# +# Copyright 2021 Callum Brown +# Copyright (C) 2023 New Vector, 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: +# . +# +# Originally licensed under the Apache License, Version 2.0: +# . +# +# [This file includes modifications made by New Vector Limited] +# +# + +from twisted.test.proto_helpers import MemoryReactor + +import synapse.rest.admin +from synapse.rest.client import login, reporting, room +from synapse.server import HomeServer +from synapse.types import JsonDict +from synapse.util import Clock + +from tests import unittest + + +class ReportEventTestCase(unittest.HomeserverTestCase): + servlets = [ + synapse.rest.admin.register_servlets, + login.register_servlets, + room.register_servlets, + reporting.register_servlets, + ] + + def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: + self.admin_user = self.register_user("admin", "pass", admin=True) + self.admin_user_tok = self.login("admin", "pass") + self.other_user = self.register_user("user", "pass") + self.other_user_tok = self.login("user", "pass") + + self.room_id = self.helper.create_room_as( + self.other_user, tok=self.other_user_tok, is_public=True + ) + self.helper.join(self.room_id, user=self.admin_user, tok=self.admin_user_tok) + resp = self.helper.send(self.room_id, tok=self.admin_user_tok) + self.event_id = resp["event_id"] + self.report_path = f"rooms/{self.room_id}/report/{self.event_id}" + + def test_reason_str_and_score_int(self) -> None: + data = {"reason": "this makes me sad", "score": -100} + self._assert_status(200, data) + + def test_no_reason(self) -> None: + data = {"score": 0} + self._assert_status(200, data) + + def test_no_score(self) -> None: + data = {"reason": "this makes me sad"} + self._assert_status(200, data) + + def test_no_reason_and_no_score(self) -> None: + data: JsonDict = {} + self._assert_status(200, data) + + def test_reason_int_and_score_str(self) -> None: + data = {"reason": 10, "score": "string"} + self._assert_status(400, data) + + def test_reason_zero_and_score_blank(self) -> None: + data = {"reason": 0, "score": ""} + self._assert_status(400, data) + + def test_reason_and_score_null(self) -> None: + data = {"reason": None, "score": None} + self._assert_status(400, data) + + def test_cannot_report_nonexistent_event(self) -> None: + """ + Tests that we don't accept event reports for events which do not exist. + """ + channel = self.make_request( + "POST", + f"rooms/{self.room_id}/report/$nonsenseeventid:test", + {"reason": "i am very sad"}, + access_token=self.other_user_tok, + ) + self.assertEqual(404, channel.code, msg=channel.result["body"]) + self.assertEqual( + "Unable to report event: it does not exist or you aren't able to see it.", + channel.json_body["error"], + msg=channel.result["body"], + ) + + def test_cannot_report_event_if_not_in_room(self) -> None: + """ + Tests that we don't accept event reports for events that exist, but for which + the reporter should not be able to view (because they are not in the room). + """ + # Have the admin user create a room (the "other" user will not join this room). + new_room_id = self.helper.create_room_as(tok=self.admin_user_tok) + + # Have the admin user send an event in this room. + response = self.helper.send_event( + new_room_id, + "m.room.message", + content={ + "msgtype": "m.text", + "body": "This event has some bad words in it! Flip!", + }, + tok=self.admin_user_tok, + ) + event_id = response["event_id"] + + # Have the "other" user attempt to report it. Perhaps they found the event ID + # in a screenshot or something... + channel = self.make_request( + "POST", + f"rooms/{new_room_id}/report/{event_id}", + {"reason": "I'm not in this room but I have opinions anyways!"}, + access_token=self.other_user_tok, + ) + + # The "other" user is not in the room, so their report should be rejected. + self.assertEqual(404, channel.code, msg=channel.result["body"]) + self.assertEqual( + "Unable to report event: it does not exist or you aren't able to see it.", + channel.json_body["error"], + msg=channel.result["body"], + ) + + def _assert_status(self, response_status: int, data: JsonDict) -> None: + channel = self.make_request( + "POST", self.report_path, data, access_token=self.other_user_tok + ) + self.assertEqual(response_status, channel.code, msg=channel.result["body"]) + + +class ReportRoomTestCase(unittest.HomeserverTestCase): + servlets = [ + synapse.rest.admin.register_servlets, + login.register_servlets, + room.register_servlets, + reporting.register_servlets, + ] + + def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: + self.other_user = self.register_user("user", "pass") + self.other_user_tok = self.login("user", "pass") + + self.room_id = self.helper.create_room_as( + self.other_user, tok=self.other_user_tok, is_public=True + ) + self.report_path = ( + f"/_matrix/client/unstable/org.matrix.msc4151/rooms/{self.room_id}/report" + ) + + @unittest.override_config( + { + "experimental_features": {"msc4151_enabled": True}, + } + ) + def test_reason_str(self) -> None: + data = {"reason": "this makes me sad"} + self._assert_status(200, data) + + @unittest.override_config( + { + "experimental_features": {"msc4151_enabled": True}, + } + ) + def test_no_reason(self) -> None: + data = {"not_reason": "for typechecking"} + self._assert_status(400, data) + + @unittest.override_config( + { + "experimental_features": {"msc4151_enabled": True}, + } + ) + def test_reason_nonstring(self) -> None: + data = {"reason": 42} + self._assert_status(400, data) + + @unittest.override_config( + { + "experimental_features": {"msc4151_enabled": True}, + } + ) + def test_reason_null(self) -> None: + data = {"reason": None} + self._assert_status(400, data) + + @unittest.override_config( + { + "experimental_features": {"msc4151_enabled": True}, + } + ) + def test_cannot_report_nonexistent_room(self) -> None: + """ + Tests that we don't accept event reports for rooms which do not exist. + """ + channel = self.make_request( + "POST", + "/_matrix/client/unstable/org.matrix.msc4151/rooms/!bloop:example.org/report", + {"reason": "i am very sad"}, + access_token=self.other_user_tok, + shorthand=False, + ) + self.assertEqual(404, channel.code, msg=channel.result["body"]) + self.assertEqual( + "Room does not exist", + channel.json_body["error"], + msg=channel.result["body"], + ) + + def _assert_status(self, response_status: int, data: JsonDict) -> None: + channel = self.make_request( + "POST", + self.report_path, + data, + access_token=self.other_user_tok, + shorthand=False, + ) + self.assertEqual(response_status, channel.code, msg=channel.result["body"]) -- cgit 1.4.1