test_appservice.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015, 2016 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import tempfile
  16. from synapse.config._base import ConfigError
  17. from tests import unittest
  18. from twisted.internet import defer
  19. from tests.utils import setup_test_homeserver
  20. from synapse.appservice import ApplicationService, ApplicationServiceState
  21. from synapse.storage.appservice import (
  22. ApplicationServiceStore, ApplicationServiceTransactionStore
  23. )
  24. import json
  25. import os
  26. import yaml
  27. from mock import Mock
  28. class ApplicationServiceStoreTestCase(unittest.TestCase):
  29. @defer.inlineCallbacks
  30. def setUp(self):
  31. self.as_yaml_files = []
  32. config = Mock(
  33. app_service_config_files=self.as_yaml_files,
  34. event_cache_size=1,
  35. )
  36. hs = yield setup_test_homeserver(config=config)
  37. self.as_token = "token1"
  38. self.as_url = "some_url"
  39. self.as_id = "as1"
  40. self._add_appservice(
  41. self.as_token,
  42. self.as_id,
  43. self.as_url,
  44. "some_hs_token",
  45. "bob"
  46. )
  47. self._add_appservice("token2", "as2", "some_url", "some_hs_token", "bob")
  48. self._add_appservice("token3", "as3", "some_url", "some_hs_token", "bob")
  49. # must be done after inserts
  50. self.store = ApplicationServiceStore(hs)
  51. def tearDown(self):
  52. # TODO: suboptimal that we need to create files for tests!
  53. for f in self.as_yaml_files:
  54. try:
  55. os.remove(f)
  56. except:
  57. pass
  58. def _add_appservice(self, as_token, id, url, hs_token, sender):
  59. as_yaml = dict(url=url, as_token=as_token, hs_token=hs_token,
  60. id=id, sender_localpart=sender, namespaces={})
  61. # use the token as the filename
  62. with open(as_token, 'w') as outfile:
  63. outfile.write(yaml.dump(as_yaml))
  64. self.as_yaml_files.append(as_token)
  65. @defer.inlineCallbacks
  66. def test_retrieve_unknown_service_token(self):
  67. service = yield self.store.get_app_service_by_token("invalid_token")
  68. self.assertEquals(service, None)
  69. @defer.inlineCallbacks
  70. def test_retrieval_of_service(self):
  71. stored_service = yield self.store.get_app_service_by_token(
  72. self.as_token
  73. )
  74. self.assertEquals(stored_service.token, self.as_token)
  75. self.assertEquals(stored_service.id, self.as_id)
  76. self.assertEquals(stored_service.url, self.as_url)
  77. self.assertEquals(
  78. stored_service.namespaces[ApplicationService.NS_ALIASES],
  79. []
  80. )
  81. self.assertEquals(
  82. stored_service.namespaces[ApplicationService.NS_ROOMS],
  83. []
  84. )
  85. self.assertEquals(
  86. stored_service.namespaces[ApplicationService.NS_USERS],
  87. []
  88. )
  89. @defer.inlineCallbacks
  90. def test_retrieval_of_all_services(self):
  91. services = yield self.store.get_app_services()
  92. self.assertEquals(len(services), 3)
  93. class ApplicationServiceTransactionStoreTestCase(unittest.TestCase):
  94. @defer.inlineCallbacks
  95. def setUp(self):
  96. self.as_yaml_files = []
  97. config = Mock(
  98. app_service_config_files=self.as_yaml_files,
  99. event_cache_size=1,
  100. )
  101. hs = yield setup_test_homeserver(config=config)
  102. self.db_pool = hs.get_db_pool()
  103. self.as_list = [
  104. {
  105. "token": "token1",
  106. "url": "https://matrix-as.org",
  107. "id": "id_1"
  108. },
  109. {
  110. "token": "alpha_tok",
  111. "url": "https://alpha.com",
  112. "id": "id_alpha"
  113. },
  114. {
  115. "token": "beta_tok",
  116. "url": "https://beta.com",
  117. "id": "id_beta"
  118. },
  119. {
  120. "token": "gamma_tok",
  121. "url": "https://gamma.com",
  122. "id": "id_gamma"
  123. },
  124. ]
  125. for s in self.as_list:
  126. yield self._add_service(s["url"], s["token"], s["id"])
  127. self.as_yaml_files = []
  128. self.store = TestTransactionStore(hs)
  129. def _add_service(self, url, as_token, id):
  130. as_yaml = dict(url=url, as_token=as_token, hs_token="something",
  131. id=id, sender_localpart="a_sender", namespaces={})
  132. # use the token as the filename
  133. with open(as_token, 'w') as outfile:
  134. outfile.write(yaml.dump(as_yaml))
  135. self.as_yaml_files.append(as_token)
  136. def _set_state(self, id, state, txn=None):
  137. return self.db_pool.runQuery(
  138. "INSERT INTO application_services_state(as_id, state, last_txn) "
  139. "VALUES(?,?,?)",
  140. (id, state, txn)
  141. )
  142. def _insert_txn(self, as_id, txn_id, events):
  143. return self.db_pool.runQuery(
  144. "INSERT INTO application_services_txns(as_id, txn_id, event_ids) "
  145. "VALUES(?,?,?)",
  146. (as_id, txn_id, json.dumps([e.event_id for e in events]))
  147. )
  148. def _set_last_txn(self, as_id, txn_id):
  149. return self.db_pool.runQuery(
  150. "INSERT INTO application_services_state(as_id, last_txn, state) "
  151. "VALUES(?,?,?)",
  152. (as_id, txn_id, ApplicationServiceState.UP)
  153. )
  154. @defer.inlineCallbacks
  155. def test_get_appservice_state_none(self):
  156. service = Mock(id=999)
  157. state = yield self.store.get_appservice_state(service)
  158. self.assertEquals(None, state)
  159. @defer.inlineCallbacks
  160. def test_get_appservice_state_up(self):
  161. yield self._set_state(
  162. self.as_list[0]["id"], ApplicationServiceState.UP
  163. )
  164. service = Mock(id=self.as_list[0]["id"])
  165. state = yield self.store.get_appservice_state(service)
  166. self.assertEquals(ApplicationServiceState.UP, state)
  167. @defer.inlineCallbacks
  168. def test_get_appservice_state_down(self):
  169. yield self._set_state(
  170. self.as_list[0]["id"], ApplicationServiceState.UP
  171. )
  172. yield self._set_state(
  173. self.as_list[1]["id"], ApplicationServiceState.DOWN
  174. )
  175. yield self._set_state(
  176. self.as_list[2]["id"], ApplicationServiceState.DOWN
  177. )
  178. service = Mock(id=self.as_list[1]["id"])
  179. state = yield self.store.get_appservice_state(service)
  180. self.assertEquals(ApplicationServiceState.DOWN, state)
  181. @defer.inlineCallbacks
  182. def test_get_appservices_by_state_none(self):
  183. services = yield self.store.get_appservices_by_state(
  184. ApplicationServiceState.DOWN
  185. )
  186. self.assertEquals(0, len(services))
  187. @defer.inlineCallbacks
  188. def test_set_appservices_state_down(self):
  189. service = Mock(id=self.as_list[1]["id"])
  190. yield self.store.set_appservice_state(
  191. service,
  192. ApplicationServiceState.DOWN
  193. )
  194. rows = yield self.db_pool.runQuery(
  195. "SELECT as_id FROM application_services_state WHERE state=?",
  196. (ApplicationServiceState.DOWN,)
  197. )
  198. self.assertEquals(service.id, rows[0][0])
  199. @defer.inlineCallbacks
  200. def test_set_appservices_state_multiple_up(self):
  201. service = Mock(id=self.as_list[1]["id"])
  202. yield self.store.set_appservice_state(
  203. service,
  204. ApplicationServiceState.UP
  205. )
  206. yield self.store.set_appservice_state(
  207. service,
  208. ApplicationServiceState.DOWN
  209. )
  210. yield self.store.set_appservice_state(
  211. service,
  212. ApplicationServiceState.UP
  213. )
  214. rows = yield self.db_pool.runQuery(
  215. "SELECT as_id FROM application_services_state WHERE state=?",
  216. (ApplicationServiceState.UP,)
  217. )
  218. self.assertEquals(service.id, rows[0][0])
  219. @defer.inlineCallbacks
  220. def test_create_appservice_txn_first(self):
  221. service = Mock(id=self.as_list[0]["id"])
  222. events = [Mock(event_id="e1"), Mock(event_id="e2")]
  223. txn = yield self.store.create_appservice_txn(service, events)
  224. self.assertEquals(txn.id, 1)
  225. self.assertEquals(txn.events, events)
  226. self.assertEquals(txn.service, service)
  227. @defer.inlineCallbacks
  228. def test_create_appservice_txn_older_last_txn(self):
  229. service = Mock(id=self.as_list[0]["id"])
  230. events = [Mock(event_id="e1"), Mock(event_id="e2")]
  231. yield self._set_last_txn(service.id, 9643) # AS is falling behind
  232. yield self._insert_txn(service.id, 9644, events)
  233. yield self._insert_txn(service.id, 9645, events)
  234. txn = yield self.store.create_appservice_txn(service, events)
  235. self.assertEquals(txn.id, 9646)
  236. self.assertEquals(txn.events, events)
  237. self.assertEquals(txn.service, service)
  238. @defer.inlineCallbacks
  239. def test_create_appservice_txn_up_to_date_last_txn(self):
  240. service = Mock(id=self.as_list[0]["id"])
  241. events = [Mock(event_id="e1"), Mock(event_id="e2")]
  242. yield self._set_last_txn(service.id, 9643)
  243. txn = yield self.store.create_appservice_txn(service, events)
  244. self.assertEquals(txn.id, 9644)
  245. self.assertEquals(txn.events, events)
  246. self.assertEquals(txn.service, service)
  247. @defer.inlineCallbacks
  248. def test_create_appservice_txn_up_fuzzing(self):
  249. service = Mock(id=self.as_list[0]["id"])
  250. events = [Mock(event_id="e1"), Mock(event_id="e2")]
  251. yield self._set_last_txn(service.id, 9643)
  252. # dump in rows with higher IDs to make sure the queries aren't wrong.
  253. yield self._set_last_txn(self.as_list[1]["id"], 119643)
  254. yield self._set_last_txn(self.as_list[2]["id"], 9)
  255. yield self._set_last_txn(self.as_list[3]["id"], 9643)
  256. yield self._insert_txn(self.as_list[1]["id"], 119644, events)
  257. yield self._insert_txn(self.as_list[1]["id"], 119645, events)
  258. yield self._insert_txn(self.as_list[1]["id"], 119646, events)
  259. yield self._insert_txn(self.as_list[2]["id"], 10, events)
  260. yield self._insert_txn(self.as_list[3]["id"], 9643, events)
  261. txn = yield self.store.create_appservice_txn(service, events)
  262. self.assertEquals(txn.id, 9644)
  263. self.assertEquals(txn.events, events)
  264. self.assertEquals(txn.service, service)
  265. @defer.inlineCallbacks
  266. def test_complete_appservice_txn_first_txn(self):
  267. service = Mock(id=self.as_list[0]["id"])
  268. events = [Mock(event_id="e1"), Mock(event_id="e2")]
  269. txn_id = 1
  270. yield self._insert_txn(service.id, txn_id, events)
  271. yield self.store.complete_appservice_txn(txn_id=txn_id, service=service)
  272. res = yield self.db_pool.runQuery(
  273. "SELECT last_txn FROM application_services_state WHERE as_id=?",
  274. (service.id,)
  275. )
  276. self.assertEquals(1, len(res))
  277. self.assertEquals(txn_id, res[0][0])
  278. res = yield self.db_pool.runQuery(
  279. "SELECT * FROM application_services_txns WHERE txn_id=?",
  280. (txn_id,)
  281. )
  282. self.assertEquals(0, len(res))
  283. @defer.inlineCallbacks
  284. def test_complete_appservice_txn_existing_in_state_table(self):
  285. service = Mock(id=self.as_list[0]["id"])
  286. events = [Mock(event_id="e1"), Mock(event_id="e2")]
  287. txn_id = 5
  288. yield self._set_last_txn(service.id, 4)
  289. yield self._insert_txn(service.id, txn_id, events)
  290. yield self.store.complete_appservice_txn(txn_id=txn_id, service=service)
  291. res = yield self.db_pool.runQuery(
  292. "SELECT last_txn, state FROM application_services_state WHERE "
  293. "as_id=?",
  294. (service.id,)
  295. )
  296. self.assertEquals(1, len(res))
  297. self.assertEquals(txn_id, res[0][0])
  298. self.assertEquals(ApplicationServiceState.UP, res[0][1])
  299. res = yield self.db_pool.runQuery(
  300. "SELECT * FROM application_services_txns WHERE txn_id=?",
  301. (txn_id,)
  302. )
  303. self.assertEquals(0, len(res))
  304. @defer.inlineCallbacks
  305. def test_get_oldest_unsent_txn_none(self):
  306. service = Mock(id=self.as_list[0]["id"])
  307. txn = yield self.store.get_oldest_unsent_txn(service)
  308. self.assertEquals(None, txn)
  309. @defer.inlineCallbacks
  310. def test_get_oldest_unsent_txn(self):
  311. service = Mock(id=self.as_list[0]["id"])
  312. events = [Mock(event_id="e1"), Mock(event_id="e2")]
  313. other_events = [Mock(event_id="e5"), Mock(event_id="e6")]
  314. # we aren't testing store._base stuff here, so mock this out
  315. self.store.get_events = Mock(return_value=events)
  316. yield self._insert_txn(self.as_list[1]["id"], 9, other_events)
  317. yield self._insert_txn(service.id, 10, events)
  318. yield self._insert_txn(service.id, 11, other_events)
  319. yield self._insert_txn(service.id, 12, other_events)
  320. txn = yield self.store.get_oldest_unsent_txn(service)
  321. self.assertEquals(service, txn.service)
  322. self.assertEquals(10, txn.id)
  323. self.assertEquals(events, txn.events)
  324. @defer.inlineCallbacks
  325. def test_get_appservices_by_state_single(self):
  326. yield self._set_state(
  327. self.as_list[0]["id"], ApplicationServiceState.DOWN
  328. )
  329. yield self._set_state(
  330. self.as_list[1]["id"], ApplicationServiceState.UP
  331. )
  332. services = yield self.store.get_appservices_by_state(
  333. ApplicationServiceState.DOWN
  334. )
  335. self.assertEquals(1, len(services))
  336. self.assertEquals(self.as_list[0]["id"], services[0].id)
  337. @defer.inlineCallbacks
  338. def test_get_appservices_by_state_multiple(self):
  339. yield self._set_state(
  340. self.as_list[0]["id"], ApplicationServiceState.DOWN
  341. )
  342. yield self._set_state(
  343. self.as_list[1]["id"], ApplicationServiceState.UP
  344. )
  345. yield self._set_state(
  346. self.as_list[2]["id"], ApplicationServiceState.DOWN
  347. )
  348. yield self._set_state(
  349. self.as_list[3]["id"], ApplicationServiceState.UP
  350. )
  351. services = yield self.store.get_appservices_by_state(
  352. ApplicationServiceState.DOWN
  353. )
  354. self.assertEquals(2, len(services))
  355. self.assertEquals(
  356. set([self.as_list[2]["id"], self.as_list[0]["id"]]),
  357. set([services[0].id, services[1].id])
  358. )
  359. # required for ApplicationServiceTransactionStoreTestCase tests
  360. class TestTransactionStore(ApplicationServiceTransactionStore,
  361. ApplicationServiceStore):
  362. def __init__(self, hs):
  363. super(TestTransactionStore, self).__init__(hs)
  364. class ApplicationServiceStoreConfigTestCase(unittest.TestCase):
  365. def _write_config(self, suffix, **kwargs):
  366. vals = {
  367. "id": "id" + suffix,
  368. "url": "url" + suffix,
  369. "as_token": "as_token" + suffix,
  370. "hs_token": "hs_token" + suffix,
  371. "sender_localpart": "sender_localpart" + suffix,
  372. "namespaces": {},
  373. }
  374. vals.update(kwargs)
  375. _, path = tempfile.mkstemp(prefix="as_config")
  376. with open(path, "w") as f:
  377. f.write(yaml.dump(vals))
  378. return path
  379. @defer.inlineCallbacks
  380. def test_unique_works(self):
  381. f1 = self._write_config(suffix="1")
  382. f2 = self._write_config(suffix="2")
  383. config = Mock(app_service_config_files=[f1, f2], event_cache_size=1)
  384. hs = yield setup_test_homeserver(config=config, datastore=Mock())
  385. ApplicationServiceStore(hs)
  386. @defer.inlineCallbacks
  387. def test_duplicate_ids(self):
  388. f1 = self._write_config(id="id", suffix="1")
  389. f2 = self._write_config(id="id", suffix="2")
  390. config = Mock(app_service_config_files=[f1, f2], event_cache_size=1)
  391. hs = yield setup_test_homeserver(config=config, datastore=Mock())
  392. with self.assertRaises(ConfigError) as cm:
  393. ApplicationServiceStore(hs)
  394. e = cm.exception
  395. self.assertIn(f1, e.message)
  396. self.assertIn(f2, e.message)
  397. self.assertIn("id", e.message)
  398. @defer.inlineCallbacks
  399. def test_duplicate_as_tokens(self):
  400. f1 = self._write_config(as_token="as_token", suffix="1")
  401. f2 = self._write_config(as_token="as_token", suffix="2")
  402. config = Mock(app_service_config_files=[f1, f2], event_cache_size=1)
  403. hs = yield setup_test_homeserver(config=config, datastore=Mock())
  404. with self.assertRaises(ConfigError) as cm:
  405. ApplicationServiceStore(hs)
  406. e = cm.exception
  407. self.assertIn(f1, e.message)
  408. self.assertIn(f2, e.message)
  409. self.assertIn("as_token", e.message)