You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

217 lines
8.3 KiB

  1. # Copyright 2022 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from typing import Any
  15. from unittest.mock import patch
  16. from twisted.test.proto_helpers import MemoryReactor
  17. from synapse.api.constants import EventContentFields
  18. from synapse.api.room_versions import RoomVersions
  19. from synapse.push.bulk_push_rule_evaluator import BulkPushRuleEvaluator
  20. from synapse.rest import admin
  21. from synapse.rest.client import login, register, room
  22. from synapse.server import HomeServer
  23. from synapse.types import create_requester
  24. from synapse.util import Clock
  25. from tests.test_utils import simple_async_mock
  26. from tests.unittest import HomeserverTestCase, override_config
  27. class TestBulkPushRuleEvaluator(HomeserverTestCase):
  28. servlets = [
  29. admin.register_servlets_for_client_rest_resource,
  30. room.register_servlets,
  31. login.register_servlets,
  32. register.register_servlets,
  33. ]
  34. def prepare(
  35. self, reactor: MemoryReactor, clock: Clock, homeserver: HomeServer
  36. ) -> None:
  37. # Create a new user and room.
  38. self.alice = self.register_user("alice", "pass")
  39. self.token = self.login(self.alice, "pass")
  40. self.requester = create_requester(self.alice)
  41. self.room_id = self.helper.create_room_as(
  42. self.alice, room_version=RoomVersions.V9.identifier, tok=self.token
  43. )
  44. self.event_creation_handler = self.hs.get_event_creation_handler()
  45. def test_action_for_event_by_user_handles_noninteger_power_levels(self) -> None:
  46. """We should convert floats and strings to integers before passing to Rust.
  47. Reproduces #14060.
  48. A lack of validation: the gift that keeps on giving.
  49. """
  50. # Alter the power levels in that room to include stringy and floaty levels.
  51. # We need to suppress the validation logic or else it will reject these dodgy
  52. # values. (Presumably this validation was not always present.)
  53. with patch("synapse.events.validator.validate_canonicaljson"), patch(
  54. "synapse.events.validator.jsonschema.validate"
  55. ):
  56. self.helper.send_state(
  57. self.room_id,
  58. "m.room.power_levels",
  59. {
  60. "users": {self.alice: "100"}, # stringy
  61. "notifications": {"room": 100.0}, # float
  62. },
  63. self.token,
  64. state_key="",
  65. )
  66. # Create a new message event, and try to evaluate it under the dodgy
  67. # power level event.
  68. event, context = self.get_success(
  69. self.event_creation_handler.create_event(
  70. self.requester,
  71. {
  72. "type": "m.room.message",
  73. "room_id": self.room_id,
  74. "content": {
  75. "msgtype": "m.text",
  76. "body": "helo",
  77. },
  78. "sender": self.alice,
  79. },
  80. )
  81. )
  82. bulk_evaluator = BulkPushRuleEvaluator(self.hs)
  83. # should not raise
  84. self.get_success(bulk_evaluator.action_for_events_by_user([(event, context)]))
  85. @override_config({"push": {"enabled": False}})
  86. def test_action_for_event_by_user_disabled_by_config(self) -> None:
  87. """Ensure that push rules are not calculated when disabled in the config"""
  88. # Create a new message event which should cause a notification.
  89. event, context = self.get_success(
  90. self.event_creation_handler.create_event(
  91. self.requester,
  92. {
  93. "type": "m.room.message",
  94. "room_id": self.room_id,
  95. "content": {
  96. "msgtype": "m.text",
  97. "body": "helo",
  98. },
  99. "sender": self.alice,
  100. },
  101. )
  102. )
  103. bulk_evaluator = BulkPushRuleEvaluator(self.hs)
  104. # Mock the method which calculates push rules -- we do this instead of
  105. # e.g. checking the results in the database because we want to ensure
  106. # that code isn't even running.
  107. bulk_evaluator._action_for_event_by_user = simple_async_mock() # type: ignore[assignment]
  108. # Ensure no actions are generated!
  109. self.get_success(bulk_evaluator.action_for_events_by_user([(event, context)]))
  110. bulk_evaluator._action_for_event_by_user.assert_not_called()
  111. @override_config({"experimental_features": {"msc3952_intentional_mentions": True}})
  112. def test_mentions(self) -> None:
  113. """Test the behavior of an event which includes invalid mentions."""
  114. bulk_evaluator = BulkPushRuleEvaluator(self.hs)
  115. sentinel = object()
  116. def create_and_process(mentions: Any = sentinel) -> bool:
  117. """Returns true iff the `mentions` trigger an event push action."""
  118. content = {}
  119. if mentions is not sentinel:
  120. content[EventContentFields.MSC3952_MENTIONS] = mentions
  121. # Create a new message event which should cause a notification.
  122. event, context = self.get_success(
  123. self.event_creation_handler.create_event(
  124. self.requester,
  125. {
  126. "type": "test",
  127. "room_id": self.room_id,
  128. "content": content,
  129. "sender": f"@bob:{self.hs.hostname}",
  130. },
  131. )
  132. )
  133. # Ensure no actions are generated!
  134. self.get_success(
  135. bulk_evaluator.action_for_events_by_user([(event, context)])
  136. )
  137. # If any actions are generated for this event, return true.
  138. result = self.get_success(
  139. self.hs.get_datastores().main.db_pool.simple_select_list(
  140. table="event_push_actions_staging",
  141. keyvalues={"event_id": event.event_id},
  142. retcols=("*",),
  143. desc="get_event_push_actions_staging",
  144. )
  145. )
  146. return len(result) > 0
  147. # Not including the mentions field should not notify.
  148. self.assertFalse(create_and_process())
  149. # An empty mentions field should not notify.
  150. self.assertFalse(create_and_process({}))
  151. # Non-dict mentions should be ignored.
  152. mentions: Any
  153. for mentions in (None, True, False, 1, "foo", []):
  154. self.assertFalse(create_and_process(mentions))
  155. # A non-list should be ignored.
  156. for mentions in (None, True, False, 1, "foo", {}):
  157. self.assertFalse(create_and_process({"user_ids": mentions}))
  158. # The Matrix ID appearing anywhere in the list should notify.
  159. self.assertTrue(create_and_process({"user_ids": [self.alice]}))
  160. self.assertTrue(create_and_process({"user_ids": ["@another:test", self.alice]}))
  161. # Duplicate user IDs should notify.
  162. self.assertTrue(create_and_process({"user_ids": [self.alice, self.alice]}))
  163. # Invalid entries in the list are ignored.
  164. self.assertFalse(create_and_process({"user_ids": [None, True, False, {}, []]}))
  165. self.assertTrue(
  166. create_and_process({"user_ids": [None, True, False, {}, [], self.alice]})
  167. )
  168. # Room mentions from those without power should not notify.
  169. self.assertFalse(create_and_process({"room": True}))
  170. # Room mentions from those with power should notify.
  171. self.helper.send_state(
  172. self.room_id,
  173. "m.room.power_levels",
  174. {"notifications": {"room": 0}},
  175. self.token,
  176. state_key="",
  177. )
  178. self.assertTrue(create_and_process({"room": True}))
  179. # Invalid data should not notify.
  180. for mentions in (None, False, 1, "foo", [], {}):
  181. self.assertFalse(create_and_process({"room": mentions}))