test_http.py 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001
  1. # Copyright 2018 New Vector
  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, List, Tuple
  15. from unittest.mock import Mock
  16. from twisted.internet.defer import Deferred
  17. from twisted.test.proto_helpers import MemoryReactor
  18. import synapse.rest.admin
  19. from synapse.logging.context import make_deferred_yieldable
  20. from synapse.push import PusherConfig, PusherConfigException
  21. from synapse.rest.client import login, push_rule, pusher, receipts, room
  22. from synapse.server import HomeServer
  23. from synapse.types import JsonDict
  24. from synapse.util import Clock
  25. from tests.unittest import HomeserverTestCase, override_config
  26. class HTTPPusherTests(HomeserverTestCase):
  27. servlets = [
  28. synapse.rest.admin.register_servlets_for_client_rest_resource,
  29. room.register_servlets,
  30. login.register_servlets,
  31. receipts.register_servlets,
  32. push_rule.register_servlets,
  33. pusher.register_servlets,
  34. ]
  35. user_id = True
  36. hijack_auth = False
  37. def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
  38. self.push_attempts: List[Tuple[Deferred, str, dict]] = []
  39. m = Mock()
  40. def post_json_get_json(url: str, body: JsonDict) -> Deferred:
  41. d: Deferred = Deferred()
  42. self.push_attempts.append((d, url, body))
  43. return make_deferred_yieldable(d)
  44. m.post_json_get_json = post_json_get_json
  45. hs = self.setup_test_homeserver(proxied_blocklisted_http_client=m)
  46. return hs
  47. def test_invalid_configuration(self) -> None:
  48. """Invalid push configurations should be rejected."""
  49. # Register the user who gets notified
  50. user_id = self.register_user("user", "pass")
  51. access_token = self.login("user", "pass")
  52. # Register the pusher
  53. user_tuple = self.get_success(
  54. self.hs.get_datastores().main.get_user_by_access_token(access_token)
  55. )
  56. assert user_tuple is not None
  57. device_id = user_tuple.device_id
  58. def test_data(data: Any) -> None:
  59. self.get_failure(
  60. self.hs.get_pusherpool().add_or_update_pusher(
  61. user_id=user_id,
  62. device_id=device_id,
  63. kind="http",
  64. app_id="m.http",
  65. app_display_name="HTTP Push Notifications",
  66. device_display_name="pushy push",
  67. pushkey="a@example.com",
  68. lang=None,
  69. data=data,
  70. ),
  71. PusherConfigException,
  72. )
  73. # Data must be provided with a URL.
  74. test_data(None)
  75. test_data({})
  76. test_data({"url": 1})
  77. # A bare domain name isn't accepted.
  78. test_data({"url": "example.com"})
  79. # A URL without a path isn't accepted.
  80. test_data({"url": "http://example.com"})
  81. # A url with an incorrect path isn't accepted.
  82. test_data({"url": "http://example.com/foo"})
  83. def test_sends_http(self) -> None:
  84. """
  85. The HTTP pusher will send pushes for each message to a HTTP endpoint
  86. when configured to do so.
  87. """
  88. # Register the user who gets notified
  89. user_id = self.register_user("user", "pass")
  90. access_token = self.login("user", "pass")
  91. # Register the user who sends the message
  92. other_user_id = self.register_user("otheruser", "pass")
  93. other_access_token = self.login("otheruser", "pass")
  94. # Register the pusher
  95. user_tuple = self.get_success(
  96. self.hs.get_datastores().main.get_user_by_access_token(access_token)
  97. )
  98. assert user_tuple is not None
  99. device_id = user_tuple.device_id
  100. self.get_success(
  101. self.hs.get_pusherpool().add_or_update_pusher(
  102. user_id=user_id,
  103. device_id=device_id,
  104. kind="http",
  105. app_id="m.http",
  106. app_display_name="HTTP Push Notifications",
  107. device_display_name="pushy push",
  108. pushkey="a@example.com",
  109. lang=None,
  110. data={"url": "http://example.com/_matrix/push/v1/notify"},
  111. )
  112. )
  113. # Create a room
  114. room = self.helper.create_room_as(user_id, tok=access_token)
  115. # The other user joins
  116. self.helper.join(room=room, user=other_user_id, tok=other_access_token)
  117. # The other user sends some messages
  118. self.helper.send(room, body="Hi!", tok=other_access_token)
  119. self.helper.send(room, body="There!", tok=other_access_token)
  120. # Get the stream ordering before it gets sent
  121. pushers = list(
  122. self.get_success(
  123. self.hs.get_datastores().main.get_pushers_by({"user_name": user_id})
  124. )
  125. )
  126. self.assertEqual(len(pushers), 1)
  127. last_stream_ordering = pushers[0].last_stream_ordering
  128. # Advance time a bit, so the pusher will register something has happened
  129. self.pump()
  130. # It hasn't succeeded yet, so the stream ordering shouldn't have moved
  131. pushers = list(
  132. self.get_success(
  133. self.hs.get_datastores().main.get_pushers_by({"user_name": user_id})
  134. )
  135. )
  136. self.assertEqual(len(pushers), 1)
  137. self.assertEqual(last_stream_ordering, pushers[0].last_stream_ordering)
  138. # One push was attempted to be sent -- it'll be the first message
  139. self.assertEqual(len(self.push_attempts), 1)
  140. self.assertEqual(
  141. self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
  142. )
  143. self.assertEqual(
  144. self.push_attempts[0][2]["notification"]["content"]["body"], "Hi!"
  145. )
  146. # Make the push succeed
  147. self.push_attempts[0][0].callback({})
  148. self.pump()
  149. # The stream ordering has increased
  150. pushers = list(
  151. self.get_success(
  152. self.hs.get_datastores().main.get_pushers_by({"user_name": user_id})
  153. )
  154. )
  155. self.assertEqual(len(pushers), 1)
  156. self.assertTrue(pushers[0].last_stream_ordering > last_stream_ordering)
  157. last_stream_ordering = pushers[0].last_stream_ordering
  158. # Now it'll try and send the second push message, which will be the second one
  159. self.assertEqual(len(self.push_attempts), 2)
  160. self.assertEqual(
  161. self.push_attempts[1][1], "http://example.com/_matrix/push/v1/notify"
  162. )
  163. self.assertEqual(
  164. self.push_attempts[1][2]["notification"]["content"]["body"], "There!"
  165. )
  166. # Make the second push succeed
  167. self.push_attempts[1][0].callback({})
  168. self.pump()
  169. # The stream ordering has increased, again
  170. pushers = list(
  171. self.get_success(
  172. self.hs.get_datastores().main.get_pushers_by({"user_name": user_id})
  173. )
  174. )
  175. self.assertEqual(len(pushers), 1)
  176. self.assertTrue(pushers[0].last_stream_ordering > last_stream_ordering)
  177. def test_sends_high_priority_for_encrypted(self) -> None:
  178. """
  179. The HTTP pusher will send pushes at high priority if they correspond
  180. to an encrypted message.
  181. This will happen both in 1:1 rooms and larger rooms.
  182. """
  183. # Register the user who gets notified
  184. user_id = self.register_user("user", "pass")
  185. access_token = self.login("user", "pass")
  186. # Register the user who sends the message
  187. other_user_id = self.register_user("otheruser", "pass")
  188. other_access_token = self.login("otheruser", "pass")
  189. # Register a third user
  190. yet_another_user_id = self.register_user("yetanotheruser", "pass")
  191. yet_another_access_token = self.login("yetanotheruser", "pass")
  192. # Create a room
  193. room = self.helper.create_room_as(user_id, tok=access_token)
  194. # The other user joins
  195. self.helper.join(room=room, user=other_user_id, tok=other_access_token)
  196. # Register the pusher
  197. user_tuple = self.get_success(
  198. self.hs.get_datastores().main.get_user_by_access_token(access_token)
  199. )
  200. assert user_tuple is not None
  201. device_id = user_tuple.device_id
  202. self.get_success(
  203. self.hs.get_pusherpool().add_or_update_pusher(
  204. user_id=user_id,
  205. device_id=device_id,
  206. kind="http",
  207. app_id="m.http",
  208. app_display_name="HTTP Push Notifications",
  209. device_display_name="pushy push",
  210. pushkey="a@example.com",
  211. lang=None,
  212. data={"url": "http://example.com/_matrix/push/v1/notify"},
  213. )
  214. )
  215. # Send an encrypted event
  216. # I know there'd normally be set-up of an encrypted room first
  217. # but this will do for our purposes
  218. self.helper.send_event(
  219. room,
  220. "m.room.encrypted",
  221. content={
  222. "algorithm": "m.megolm.v1.aes-sha2",
  223. "sender_key": "6lImKbzK51MzWLwHh8tUM3UBBSBrLlgup/OOCGTvumM",
  224. "ciphertext": "AwgAErABoRxwpMipdgiwXgu46rHiWQ0DmRj0qUlPrMraBUDk"
  225. "leTnJRljpuc7IOhsYbLY3uo2WI0ab/ob41sV+3JEIhODJPqH"
  226. "TK7cEZaIL+/up9e+dT9VGF5kRTWinzjkeqO8FU5kfdRjm+3w"
  227. "0sy3o1OCpXXCfO+faPhbV/0HuK4ndx1G+myNfK1Nk/CxfMcT"
  228. "BT+zDS/Df/QePAHVbrr9uuGB7fW8ogW/ulnydgZPRluusFGv"
  229. "J3+cg9LoPpZPAmv5Me3ec7NtdlfN0oDZ0gk3TiNkkhsxDG9Y"
  230. "YcNzl78USI0q8+kOV26Bu5dOBpU4WOuojXZHJlP5lMgdzLLl"
  231. "EQ0",
  232. "session_id": "IigqfNWLL+ez/Is+Duwp2s4HuCZhFG9b9CZKTYHtQ4A",
  233. "device_id": "AHQDUSTAAA",
  234. },
  235. tok=other_access_token,
  236. )
  237. # Advance time a bit, so the pusher will register something has happened
  238. self.pump()
  239. # Make the push succeed
  240. self.push_attempts[0][0].callback({})
  241. self.pump()
  242. # Check our push made it with high priority
  243. self.assertEqual(len(self.push_attempts), 1)
  244. self.assertEqual(
  245. self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
  246. )
  247. self.assertEqual(self.push_attempts[0][2]["notification"]["prio"], "high")
  248. # Add yet another person — we want to make this room not a 1:1
  249. # (as encrypted messages in a 1:1 currently have tweaks applied
  250. # so it doesn't properly exercise the condition of all encrypted
  251. # messages need to be high).
  252. self.helper.join(
  253. room=room, user=yet_another_user_id, tok=yet_another_access_token
  254. )
  255. # Check no push notifications are sent regarding the membership changes
  256. # (that would confuse the test)
  257. self.pump()
  258. self.assertEqual(len(self.push_attempts), 1)
  259. # Send another encrypted event
  260. self.helper.send_event(
  261. room,
  262. "m.room.encrypted",
  263. content={
  264. "ciphertext": "AwgAEoABtEuic/2DF6oIpNH+q/PonzlhXOVho8dTv0tzFr5m"
  265. "9vTo50yabx3nxsRlP2WxSqa8I07YftP+EKWCWJvTkg6o7zXq"
  266. "6CK+GVvLQOVgK50SfvjHqJXN+z1VEqj+5mkZVN/cAgJzoxcH"
  267. "zFHkwDPJC8kQs47IHd8EO9KBUK4v6+NQ1uE/BIak4qAf9aS/"
  268. "kI+f0gjn9IY9K6LXlah82A/iRyrIrxkCkE/n0VfvLhaWFecC"
  269. "sAWTcMLoF6fh1Jpke95mljbmFSpsSd/eEQw",
  270. "device_id": "SRCFTWTHXO",
  271. "session_id": "eMA+bhGczuTz1C5cJR1YbmrnnC6Goni4lbvS5vJ1nG4",
  272. "algorithm": "m.megolm.v1.aes-sha2",
  273. "sender_key": "rC/XSIAiYrVGSuaHMop8/pTZbku4sQKBZwRwukgnN1c",
  274. },
  275. tok=other_access_token,
  276. )
  277. # Advance time a bit, so the pusher will register something has happened
  278. self.pump()
  279. self.assertEqual(len(self.push_attempts), 2)
  280. self.assertEqual(
  281. self.push_attempts[1][1], "http://example.com/_matrix/push/v1/notify"
  282. )
  283. self.assertEqual(self.push_attempts[1][2]["notification"]["prio"], "high")
  284. def test_sends_high_priority_for_one_to_one_only(self) -> None:
  285. """
  286. The HTTP pusher will send pushes at high priority if they correspond
  287. to a message in a one-to-one room.
  288. """
  289. # Register the user who gets notified
  290. user_id = self.register_user("user", "pass")
  291. access_token = self.login("user", "pass")
  292. # Register the user who sends the message
  293. other_user_id = self.register_user("otheruser", "pass")
  294. other_access_token = self.login("otheruser", "pass")
  295. # Register a third user
  296. yet_another_user_id = self.register_user("yetanotheruser", "pass")
  297. yet_another_access_token = self.login("yetanotheruser", "pass")
  298. # Create a room
  299. room = self.helper.create_room_as(user_id, tok=access_token)
  300. # The other user joins
  301. self.helper.join(room=room, user=other_user_id, tok=other_access_token)
  302. # Register the pusher
  303. user_tuple = self.get_success(
  304. self.hs.get_datastores().main.get_user_by_access_token(access_token)
  305. )
  306. assert user_tuple is not None
  307. device_id = user_tuple.device_id
  308. self.get_success(
  309. self.hs.get_pusherpool().add_or_update_pusher(
  310. user_id=user_id,
  311. device_id=device_id,
  312. kind="http",
  313. app_id="m.http",
  314. app_display_name="HTTP Push Notifications",
  315. device_display_name="pushy push",
  316. pushkey="a@example.com",
  317. lang=None,
  318. data={"url": "http://example.com/_matrix/push/v1/notify"},
  319. )
  320. )
  321. # Send a message
  322. self.helper.send(room, body="Hi!", tok=other_access_token)
  323. # Advance time a bit, so the pusher will register something has happened
  324. self.pump()
  325. # Make the push succeed
  326. self.push_attempts[0][0].callback({})
  327. self.pump()
  328. # Check our push made it with high priority — this is a one-to-one room
  329. self.assertEqual(len(self.push_attempts), 1)
  330. self.assertEqual(
  331. self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
  332. )
  333. self.assertEqual(self.push_attempts[0][2]["notification"]["prio"], "high")
  334. # Yet another user joins
  335. self.helper.join(
  336. room=room, user=yet_another_user_id, tok=yet_another_access_token
  337. )
  338. # Check no push notifications are sent regarding the membership changes
  339. # (that would confuse the test)
  340. self.pump()
  341. self.assertEqual(len(self.push_attempts), 1)
  342. # Send another event
  343. self.helper.send(room, body="Welcome!", tok=other_access_token)
  344. # Advance time a bit, so the pusher will register something has happened
  345. self.pump()
  346. self.assertEqual(len(self.push_attempts), 2)
  347. self.assertEqual(
  348. self.push_attempts[1][1], "http://example.com/_matrix/push/v1/notify"
  349. )
  350. # check that this is low-priority
  351. self.assertEqual(self.push_attempts[1][2]["notification"]["prio"], "low")
  352. def test_sends_high_priority_for_mention(self) -> None:
  353. """
  354. The HTTP pusher will send pushes at high priority if they correspond
  355. to a message containing the user's display name.
  356. """
  357. # Register the user who gets notified
  358. user_id = self.register_user("user", "pass")
  359. access_token = self.login("user", "pass")
  360. # Register the user who sends the message
  361. other_user_id = self.register_user("otheruser", "pass")
  362. other_access_token = self.login("otheruser", "pass")
  363. # Register a third user
  364. yet_another_user_id = self.register_user("yetanotheruser", "pass")
  365. yet_another_access_token = self.login("yetanotheruser", "pass")
  366. # Create a room
  367. room = self.helper.create_room_as(user_id, tok=access_token)
  368. # The other users join
  369. self.helper.join(room=room, user=other_user_id, tok=other_access_token)
  370. self.helper.join(
  371. room=room, user=yet_another_user_id, tok=yet_another_access_token
  372. )
  373. # Register the pusher
  374. user_tuple = self.get_success(
  375. self.hs.get_datastores().main.get_user_by_access_token(access_token)
  376. )
  377. assert user_tuple is not None
  378. device_id = user_tuple.device_id
  379. self.get_success(
  380. self.hs.get_pusherpool().add_or_update_pusher(
  381. user_id=user_id,
  382. device_id=device_id,
  383. kind="http",
  384. app_id="m.http",
  385. app_display_name="HTTP Push Notifications",
  386. device_display_name="pushy push",
  387. pushkey="a@example.com",
  388. lang=None,
  389. data={"url": "http://example.com/_matrix/push/v1/notify"},
  390. )
  391. )
  392. # Send a message
  393. self.helper.send(room, body="Oh, user, hello!", tok=other_access_token)
  394. # Advance time a bit, so the pusher will register something has happened
  395. self.pump()
  396. # Make the push succeed
  397. self.push_attempts[0][0].callback({})
  398. self.pump()
  399. # Check our push made it with high priority
  400. self.assertEqual(len(self.push_attempts), 1)
  401. self.assertEqual(
  402. self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
  403. )
  404. self.assertEqual(self.push_attempts[0][2]["notification"]["prio"], "high")
  405. # Send another event, this time with no mention
  406. self.helper.send(room, body="Are you there?", tok=other_access_token)
  407. # Advance time a bit, so the pusher will register something has happened
  408. self.pump()
  409. self.assertEqual(len(self.push_attempts), 2)
  410. self.assertEqual(
  411. self.push_attempts[1][1], "http://example.com/_matrix/push/v1/notify"
  412. )
  413. # check that this is low-priority
  414. self.assertEqual(self.push_attempts[1][2]["notification"]["prio"], "low")
  415. def test_sends_high_priority_for_atroom(self) -> None:
  416. """
  417. The HTTP pusher will send pushes at high priority if they correspond
  418. to a message that contains @room.
  419. """
  420. # Register the user who gets notified
  421. user_id = self.register_user("user", "pass")
  422. access_token = self.login("user", "pass")
  423. # Register the user who sends the message
  424. other_user_id = self.register_user("otheruser", "pass")
  425. other_access_token = self.login("otheruser", "pass")
  426. # Register a third user
  427. yet_another_user_id = self.register_user("yetanotheruser", "pass")
  428. yet_another_access_token = self.login("yetanotheruser", "pass")
  429. # Create a room (as other_user so the power levels are compatible with
  430. # other_user sending @room).
  431. room = self.helper.create_room_as(other_user_id, tok=other_access_token)
  432. # The other users join
  433. self.helper.join(room=room, user=user_id, tok=access_token)
  434. self.helper.join(
  435. room=room, user=yet_another_user_id, tok=yet_another_access_token
  436. )
  437. # Register the pusher
  438. user_tuple = self.get_success(
  439. self.hs.get_datastores().main.get_user_by_access_token(access_token)
  440. )
  441. assert user_tuple is not None
  442. device_id = user_tuple.device_id
  443. self.get_success(
  444. self.hs.get_pusherpool().add_or_update_pusher(
  445. user_id=user_id,
  446. device_id=device_id,
  447. kind="http",
  448. app_id="m.http",
  449. app_display_name="HTTP Push Notifications",
  450. device_display_name="pushy push",
  451. pushkey="a@example.com",
  452. lang=None,
  453. data={"url": "http://example.com/_matrix/push/v1/notify"},
  454. )
  455. )
  456. # Send a message
  457. self.helper.send(
  458. room,
  459. body="@room eeek! There's a spider on the table!",
  460. tok=other_access_token,
  461. )
  462. # Advance time a bit, so the pusher will register something has happened
  463. self.pump()
  464. # Make the push succeed
  465. self.push_attempts[0][0].callback({})
  466. self.pump()
  467. # Check our push made it with high priority
  468. self.assertEqual(len(self.push_attempts), 1)
  469. self.assertEqual(
  470. self.push_attempts[0][1], "http://example.com/_matrix/push/v1/notify"
  471. )
  472. self.assertEqual(self.push_attempts[0][2]["notification"]["prio"], "high")
  473. # Send another event, this time as someone without the power of @room
  474. self.helper.send(
  475. room, body="@room the spider is gone", tok=yet_another_access_token
  476. )
  477. # Advance time a bit, so the pusher will register something has happened
  478. self.pump()
  479. self.assertEqual(len(self.push_attempts), 2)
  480. self.assertEqual(
  481. self.push_attempts[1][1], "http://example.com/_matrix/push/v1/notify"
  482. )
  483. # check that this is low-priority
  484. self.assertEqual(self.push_attempts[1][2]["notification"]["prio"], "low")
  485. def test_push_unread_count_group_by_room(self) -> None:
  486. """
  487. The HTTP pusher will group unread count by number of unread rooms.
  488. """
  489. # Carry out common push count tests and setup
  490. self._test_push_unread_count()
  491. # Carry out our option-value specific test
  492. #
  493. # This push should still only contain an unread count of 1 (for 1 unread room)
  494. self._check_push_attempt(7, 1)
  495. @override_config({"push": {"group_unread_count_by_room": False}})
  496. def test_push_unread_count_message_count(self) -> None:
  497. """
  498. The HTTP pusher will send the total unread message count.
  499. """
  500. # Carry out common push count tests and setup
  501. self._test_push_unread_count()
  502. # Carry out our option-value specific test
  503. #
  504. # We're counting every unread message, so there should now be 3 since the
  505. # last read receipt
  506. self._check_push_attempt(7, 3)
  507. def _test_push_unread_count(self) -> None:
  508. """
  509. Tests that the correct unread count appears in sent push notifications
  510. Note that:
  511. * Sending messages will cause push notifications to go out to relevant users
  512. * Sending a read receipt will cause the HTTP pusher to check whether the unread
  513. count has changed since the last push notification. If so, a "badge update"
  514. notification goes out to the user that sent the receipt
  515. """
  516. # Register the user who gets notified
  517. user_id = self.register_user("user", "pass")
  518. access_token = self.login("user", "pass")
  519. # Register the user who sends the message
  520. other_user_id = self.register_user("other_user", "pass")
  521. other_access_token = self.login("other_user", "pass")
  522. # Create a room (as other_user)
  523. room_id = self.helper.create_room_as(other_user_id, tok=other_access_token)
  524. # The user to get notified joins
  525. self.helper.join(room=room_id, user=user_id, tok=access_token)
  526. # Register the pusher
  527. user_tuple = self.get_success(
  528. self.hs.get_datastores().main.get_user_by_access_token(access_token)
  529. )
  530. assert user_tuple is not None
  531. device_id = user_tuple.device_id
  532. self.get_success(
  533. self.hs.get_pusherpool().add_or_update_pusher(
  534. user_id=user_id,
  535. device_id=device_id,
  536. kind="http",
  537. app_id="m.http",
  538. app_display_name="HTTP Push Notifications",
  539. device_display_name="pushy push",
  540. pushkey="a@example.com",
  541. lang=None,
  542. data={"url": "http://example.com/_matrix/push/v1/notify"},
  543. )
  544. )
  545. # Send a message
  546. response = self.helper.send(
  547. room_id, body="Hello there!", tok=other_access_token
  548. )
  549. first_message_event_id = response["event_id"]
  550. expected_push_attempts = 1
  551. self._check_push_attempt(expected_push_attempts, 1)
  552. self._send_read_request(access_token, first_message_event_id, room_id)
  553. # Unread count has changed. Therefore, ensure that read request triggers
  554. # a push notification.
  555. expected_push_attempts += 1
  556. self.assertEqual(len(self.push_attempts), expected_push_attempts)
  557. # Send another message
  558. response2 = self.helper.send(
  559. room_id, body="How's the weather today?", tok=other_access_token
  560. )
  561. second_message_event_id = response2["event_id"]
  562. expected_push_attempts += 1
  563. self._check_push_attempt(expected_push_attempts, 1)
  564. self._send_read_request(access_token, second_message_event_id, room_id)
  565. expected_push_attempts += 1
  566. self._check_push_attempt(expected_push_attempts, 0)
  567. # If we're grouping by room, sending more messages shouldn't increase the
  568. # unread count, as they're all being sent in the same room. Otherwise, it
  569. # should. Therefore, the last call to _check_push_attempt is done in the
  570. # caller method.
  571. self.helper.send(room_id, body="Hello?", tok=other_access_token)
  572. expected_push_attempts += 1
  573. self._advance_time_and_make_push_succeed(expected_push_attempts)
  574. self.helper.send(room_id, body="Hello??", tok=other_access_token)
  575. expected_push_attempts += 1
  576. self._advance_time_and_make_push_succeed(expected_push_attempts)
  577. self.helper.send(room_id, body="HELLO???", tok=other_access_token)
  578. def _advance_time_and_make_push_succeed(self, expected_push_attempts: int) -> None:
  579. self.pump()
  580. self.push_attempts[expected_push_attempts - 1][0].callback({})
  581. def _check_push_attempt(
  582. self, expected_push_attempts: int, expected_unread_count_last_push: int
  583. ) -> None:
  584. """
  585. Makes sure that the last expected push attempt succeeds and checks whether
  586. it contains the expected unread count.
  587. """
  588. self._advance_time_and_make_push_succeed(expected_push_attempts)
  589. # Check our push made it
  590. self.assertEqual(len(self.push_attempts), expected_push_attempts)
  591. _, push_url, push_body = self.push_attempts[expected_push_attempts - 1]
  592. self.assertEqual(
  593. push_url,
  594. "http://example.com/_matrix/push/v1/notify",
  595. )
  596. # Check that the unread count for the room is 0
  597. #
  598. # The unread count is zero as the user has no read receipt in the room yet
  599. self.assertEqual(
  600. push_body["notification"]["counts"]["unread"],
  601. expected_unread_count_last_push,
  602. )
  603. def _send_read_request(
  604. self, access_token: str, message_event_id: str, room_id: str
  605. ) -> None:
  606. # Now set the user's read receipt position to the first event
  607. #
  608. # This will actually trigger a new notification to be sent out so that
  609. # even if the user does not receive another message, their unread
  610. # count goes down
  611. channel = self.make_request(
  612. "POST",
  613. "/rooms/%s/receipt/m.read/%s" % (room_id, message_event_id),
  614. {},
  615. access_token=access_token,
  616. )
  617. self.assertEqual(channel.code, 200, channel.json_body)
  618. def _make_user_with_pusher(
  619. self, username: str, enabled: bool = True
  620. ) -> Tuple[str, str]:
  621. """Registers a user and creates a pusher for them.
  622. Args:
  623. username: the localpart of the new user's Matrix ID.
  624. enabled: whether to create the pusher in an enabled or disabled state.
  625. """
  626. user_id = self.register_user(username, "pass")
  627. access_token = self.login(username, "pass")
  628. # Register the pusher
  629. self._set_pusher(user_id, access_token, enabled)
  630. return user_id, access_token
  631. def _set_pusher(self, user_id: str, access_token: str, enabled: bool) -> None:
  632. """Creates or updates the pusher for the given user.
  633. Args:
  634. user_id: the user's Matrix ID.
  635. access_token: the access token associated with the pusher.
  636. enabled: whether to enable or disable the pusher.
  637. """
  638. user_tuple = self.get_success(
  639. self.hs.get_datastores().main.get_user_by_access_token(access_token)
  640. )
  641. assert user_tuple is not None
  642. device_id = user_tuple.device_id
  643. self.get_success(
  644. self.hs.get_pusherpool().add_or_update_pusher(
  645. user_id=user_id,
  646. device_id=device_id,
  647. kind="http",
  648. app_id="m.http",
  649. app_display_name="HTTP Push Notifications",
  650. device_display_name="pushy push",
  651. pushkey="a@example.com",
  652. lang=None,
  653. data={"url": "http://example.com/_matrix/push/v1/notify"},
  654. enabled=enabled,
  655. )
  656. )
  657. def test_dont_notify_rule_overrides_message(self) -> None:
  658. """
  659. The override push rule will suppress notification
  660. """
  661. user_id, access_token = self._make_user_with_pusher("user")
  662. other_user_id, other_access_token = self._make_user_with_pusher("otheruser")
  663. # Create a room
  664. room = self.helper.create_room_as(user_id, tok=access_token)
  665. # Disable user notifications for this room -> user
  666. body = {
  667. "conditions": [{"kind": "event_match", "key": "room_id", "pattern": room}],
  668. "actions": ["dont_notify"],
  669. }
  670. channel = self.make_request(
  671. "PUT",
  672. "/pushrules/global/override/best.friend",
  673. body,
  674. access_token=access_token,
  675. )
  676. self.assertEqual(channel.code, 200)
  677. # Check we start with no pushes
  678. self.assertEqual(len(self.push_attempts), 0)
  679. # The other user joins
  680. self.helper.join(room=room, user=other_user_id, tok=other_access_token)
  681. # The other user sends a message (ignored by dont_notify push rule set above)
  682. self.helper.send(room, body="Hi!", tok=other_access_token)
  683. self.assertEqual(len(self.push_attempts), 0)
  684. # The user sends a message back (sends a notification)
  685. self.helper.send(room, body="Hello", tok=access_token)
  686. self.assertEqual(len(self.push_attempts), 1)
  687. @override_config({"experimental_features": {"msc3881_enabled": True}})
  688. def test_disable(self) -> None:
  689. """Tests that disabling a pusher means it's not pushed to anymore."""
  690. user_id, access_token = self._make_user_with_pusher("user")
  691. other_user_id, other_access_token = self._make_user_with_pusher("otheruser")
  692. room = self.helper.create_room_as(user_id, tok=access_token)
  693. self.helper.join(room=room, user=other_user_id, tok=other_access_token)
  694. # Send a message and check that it generated a push.
  695. self.helper.send(room, body="Hi!", tok=other_access_token)
  696. self.assertEqual(len(self.push_attempts), 1)
  697. # Disable the pusher.
  698. self._set_pusher(user_id, access_token, enabled=False)
  699. # Send another message and check that it did not generate a push.
  700. self.helper.send(room, body="Hi!", tok=other_access_token)
  701. self.assertEqual(len(self.push_attempts), 1)
  702. # Get the pushers for the user and check that it is marked as disabled.
  703. channel = self.make_request("GET", "/pushers", access_token=access_token)
  704. self.assertEqual(channel.code, 200)
  705. self.assertEqual(len(channel.json_body["pushers"]), 1)
  706. enabled = channel.json_body["pushers"][0]["org.matrix.msc3881.enabled"]
  707. self.assertFalse(enabled)
  708. self.assertTrue(isinstance(enabled, bool))
  709. @override_config({"experimental_features": {"msc3881_enabled": True}})
  710. def test_enable(self) -> None:
  711. """Tests that enabling a disabled pusher means it gets pushed to."""
  712. # Create the user with the pusher already disabled.
  713. user_id, access_token = self._make_user_with_pusher("user", enabled=False)
  714. other_user_id, other_access_token = self._make_user_with_pusher("otheruser")
  715. room = self.helper.create_room_as(user_id, tok=access_token)
  716. self.helper.join(room=room, user=other_user_id, tok=other_access_token)
  717. # Send a message and check that it did not generate a push.
  718. self.helper.send(room, body="Hi!", tok=other_access_token)
  719. self.assertEqual(len(self.push_attempts), 0)
  720. # Enable the pusher.
  721. self._set_pusher(user_id, access_token, enabled=True)
  722. # Send another message and check that it did generate a push.
  723. self.helper.send(room, body="Hi!", tok=other_access_token)
  724. self.assertEqual(len(self.push_attempts), 1)
  725. # Get the pushers for the user and check that it is marked as enabled.
  726. channel = self.make_request("GET", "/pushers", access_token=access_token)
  727. self.assertEqual(channel.code, 200)
  728. self.assertEqual(len(channel.json_body["pushers"]), 1)
  729. enabled = channel.json_body["pushers"][0]["org.matrix.msc3881.enabled"]
  730. self.assertTrue(enabled)
  731. self.assertTrue(isinstance(enabled, bool))
  732. @override_config({"experimental_features": {"msc3881_enabled": True}})
  733. def test_null_enabled(self) -> None:
  734. """Tests that a pusher that has an 'enabled' column set to NULL (eg pushers
  735. created before the column was introduced) is considered enabled.
  736. """
  737. # We intentionally set 'enabled' to None so that it's stored as NULL in the
  738. # database.
  739. user_id, access_token = self._make_user_with_pusher("user", enabled=None) # type: ignore[arg-type]
  740. channel = self.make_request("GET", "/pushers", access_token=access_token)
  741. self.assertEqual(channel.code, 200)
  742. self.assertEqual(len(channel.json_body["pushers"]), 1)
  743. self.assertTrue(channel.json_body["pushers"][0]["org.matrix.msc3881.enabled"])
  744. def test_update_different_device_access_token_device_id(self) -> None:
  745. """Tests that if we create a pusher from one device, the update it from another
  746. device, the device ID associated with the pusher stays the same.
  747. """
  748. # Create a user with a pusher.
  749. user_id, access_token = self._make_user_with_pusher("user")
  750. # Get the device ID for the current access token, since that's what we store in
  751. # the pushers table.
  752. user_tuple = self.get_success(
  753. self.hs.get_datastores().main.get_user_by_access_token(access_token)
  754. )
  755. assert user_tuple is not None
  756. device_id = user_tuple.device_id
  757. # Generate a new access token, and update the pusher with it.
  758. new_token = self.login("user", "pass")
  759. self._set_pusher(user_id, new_token, enabled=False)
  760. # Get the current list of pushers for the user.
  761. ret = self.get_success(
  762. self.hs.get_datastores().main.get_pushers_by({"user_name": user_id})
  763. )
  764. pushers: List[PusherConfig] = list(ret)
  765. # Check that we still have one pusher, and that the device ID associated with
  766. # it didn't change.
  767. self.assertEqual(len(pushers), 1)
  768. self.assertEqual(pushers[0].device_id, device_id)
  769. @override_config({"experimental_features": {"msc3881_enabled": True}})
  770. def test_device_id(self) -> None:
  771. """Tests that a pusher created with a given device ID shows that device ID in
  772. GET /pushers requests.
  773. """
  774. self.register_user("user", "pass")
  775. access_token = self.login("user", "pass")
  776. # We create the pusher with an HTTP request rather than with
  777. # _make_user_with_pusher so that we can test the device ID is correctly set when
  778. # creating a pusher via an API call.
  779. self.make_request(
  780. method="POST",
  781. path="/pushers/set",
  782. content={
  783. "kind": "http",
  784. "app_id": "m.http",
  785. "app_display_name": "HTTP Push Notifications",
  786. "device_display_name": "pushy push",
  787. "pushkey": "a@example.com",
  788. "lang": "en",
  789. "data": {"url": "http://example.com/_matrix/push/v1/notify"},
  790. },
  791. access_token=access_token,
  792. )
  793. # Look up the user info for the access token so we can compare the device ID.
  794. lookup_result = self.get_success(
  795. self.hs.get_datastores().main.get_user_by_access_token(access_token)
  796. )
  797. assert lookup_result is not None
  798. # Get the user's devices and check it has the correct device ID.
  799. channel = self.make_request("GET", "/pushers", access_token=access_token)
  800. self.assertEqual(channel.code, 200)
  801. self.assertEqual(len(channel.json_body["pushers"]), 1)
  802. self.assertEqual(
  803. channel.json_body["pushers"][0]["org.matrix.msc3881.device_id"],
  804. lookup_result.device_id,
  805. )
  806. @override_config({"push": {"jitter_delay": "10s"}})
  807. def test_jitter(self) -> None:
  808. """Tests that enabling jitter actually delays sending push."""
  809. user_id, access_token = self._make_user_with_pusher("user")
  810. other_user_id, other_access_token = self._make_user_with_pusher("otheruser")
  811. room = self.helper.create_room_as(user_id, tok=access_token)
  812. self.helper.join(room=room, user=other_user_id, tok=other_access_token)
  813. # Send a message and check that it did not generate a push, as it should
  814. # be delayed.
  815. self.helper.send(room, body="Hi!", tok=other_access_token)
  816. self.assertEqual(len(self.push_attempts), 0)
  817. # Now advance time past the max jitter, and assert the message was sent.
  818. self.reactor.advance(15)
  819. self.assertEqual(len(self.push_attempts), 1)
  820. self.push_attempts[0][0].callback({})
  821. # Now we send a bunch of messages and assert that they were all sent
  822. # within the 10s max delay.
  823. for _ in range(10):
  824. self.helper.send(room, body="Hi!", tok=other_access_token)
  825. index = 1
  826. for _ in range(11):
  827. while len(self.push_attempts) > index:
  828. self.push_attempts[index][0].callback({})
  829. self.pump()
  830. index += 1
  831. self.reactor.advance(1)
  832. self.pump()
  833. self.assertEqual(len(self.push_attempts), 11)