def setUp(self): self.clock = MockClock() self.hs = Mock() self.hs.get_clock = Mock(return_value=self.clock) self.hs.get_auth = Mock() self.cache = HttpTransactionCache(self.hs) self.mock_http_response = (200, "GOOD JOB!") self.mock_key = "foo"
class HttpTransactionCacheTestCase(unittest.TestCase): def setUp(self): self.clock = MockClock() self.cache = HttpTransactionCache(self.clock) self.mock_http_response = (200, "GOOD JOB!") self.mock_key = "foo" @defer.inlineCallbacks def test_executes_given_function(self): cb = Mock(return_value=defer.succeed(self.mock_http_response)) res = yield self.cache.fetch_or_execute(self.mock_key, cb, "some_arg", keyword="arg") cb.assert_called_once_with("some_arg", keyword="arg") self.assertEqual(res, self.mock_http_response) @defer.inlineCallbacks def test_deduplicates_based_on_key(self): cb = Mock(return_value=defer.succeed(self.mock_http_response)) for i in range(3): # invoke multiple times res = yield self.cache.fetch_or_execute(self.mock_key, cb, "some_arg", keyword="arg", changing_args=i) self.assertEqual(res, self.mock_http_response) # expect only a single call to do the work cb.assert_called_once_with("some_arg", keyword="arg", changing_args=0) @defer.inlineCallbacks def test_cleans_up(self): cb = Mock(return_value=defer.succeed(self.mock_http_response)) yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg") # should NOT have cleaned up yet self.clock.advance_time_msec(CLEANUP_PERIOD_MS / 2) yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg") # still using cache cb.assert_called_once_with("an arg") self.clock.advance_time_msec(CLEANUP_PERIOD_MS) yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg") # no longer using cache self.assertEqual(cb.call_count, 2) self.assertEqual(cb.call_args_list, [call("an arg", ), call("an arg", )])
def test_from_cache(self): clock = MockClock() dns_client_mock = Mock(spec_set=["lookupService"]) dns_client_mock.lookupService = Mock(spec_set=[]) service_name = b"test_service.example.com" entry = Mock(spec_set=["expires", "priority", "weight"]) entry.expires = 999999999 entry.priority = 0 entry.weight = 0 cache = {service_name: [entry]} resolver = SrvResolver(dns_client=dns_client_mock, cache=cache, get_time=clock.time) servers = yield defer.ensureDeferred( resolver.resolve_service(service_name)) self.assertFalse(dns_client_mock.lookupService.called) self.assertEquals(len(servers), 1) self.assertEquals(servers, cache[service_name])
def setUp(self): self.clock = MockClock() self.mock_federation_resource = MockHttpResource() self.mock_http_client = Mock(spec=[]) self.mock_http_client.put_json = DeferredMockCallable() hs_kwargs = {} if hasattr(self, "make_datastore_mock"): hs_kwargs["datastore"] = self.make_datastore_mock() hs = yield setup_test_homeserver( clock=self.clock, handlers=None, resource_for_federation=self.mock_federation_resource, http_client=self.mock_http_client, keyring=Mock(), **hs_kwargs) hs.handlers = JustPresenceHandlers(hs) self.datastore = hs.get_datastore() self.setUp_roommemberhandler_mocks(hs.handlers) self.handler = hs.get_handlers().presence_handler self.event_source = hs.get_event_sources().sources["presence"] self.distributor = hs.get_distributor() self.distributor.declare("user_joined_room") yield self.setUp_users(hs)
def test_cache_expiry(self) -> None: """Test that URL cache files and thumbnails are cleaned up properly on expiry.""" self.preview_url.clock = MockClock() _host, media_id = self._download_image() file_path = self.preview_url.filepaths.url_cache_filepath(media_id) file_dirs = self.preview_url.filepaths.url_cache_filepath_dirs_to_delete( media_id) thumbnail_dir = self.preview_url.filepaths.url_cache_thumbnail_directory( media_id) thumbnail_dirs = self.preview_url.filepaths.url_cache_thumbnail_dirs_to_delete( media_id) self.assertTrue(os.path.isfile(file_path)) self.assertTrue(os.path.isdir(thumbnail_dir)) self.preview_url.clock.advance_time_msec(IMAGE_CACHE_EXPIRY_MS + 1) self.get_success(self.preview_url._expire_url_cache_data()) for path in [file_path] + file_dirs + [thumbnail_dir] + thumbnail_dirs: self.assertFalse( os.path.exists(path), f"{os.path.relpath(path, self.media_store_path)} was not deleted", )
def setUp(self): hs = yield setup_test_homeserver(clock=MockClock()) self.store = PresenceStore(hs) self.u_apple = UserID.from_string("@apple:test") self.u_banana = UserID.from_string("@banana:test")
def test_get_set(self): clock = MockClock() cache = ExpiringCache("test", clock, max_len=1) cache["key"] = "value" self.assertEquals(cache.get("key"), "value") self.assertEquals(cache["key"], "value")
def setUp(self): self.mock_store = Mock() self.mock_as_api = Mock() self.mock_scheduler = Mock() hs = Mock() hs.get_datastore = Mock(return_value=self.mock_store) hs.get_application_service_api = Mock(return_value=self.mock_as_api) hs.get_application_service_scheduler = Mock(return_value=self.mock_scheduler) hs.get_clock.return_value = MockClock() self.handler = ApplicationServicesHandler(hs)
def setUp(self): self.mock_store = Mock() self.mock_as_api = Mock() self.mock_scheduler = Mock() hs = Mock() hs.get_datastore.return_value = self.mock_store self.mock_store.get_received_ts.return_value = make_awaitable(0) self.mock_store.set_appservice_last_pos.return_value = make_awaitable(None) hs.get_application_service_api.return_value = self.mock_as_api hs.get_application_service_scheduler.return_value = self.mock_scheduler hs.get_clock.return_value = MockClock() self.handler = ApplicationServicesHandler(hs)
def test_eviction(self): clock = MockClock() cache = ExpiringCache("test", clock, max_len=2) cache["key"] = "value" cache["key2"] = "value2" self.assertEquals(cache.get("key"), "value") self.assertEquals(cache.get("key2"), "value2") cache["key3"] = "value3" self.assertEquals(cache.get("key"), None) self.assertEquals(cache.get("key2"), "value2") self.assertEquals(cache.get("key3"), "value3")
def setUp(self): db_pool = SQLiteMemoryDbPool() yield db_pool.prepare() hs = HomeServer( "test", clock=MockClock(), db_pool=db_pool, ) self.store = PresenceStore(hs) self.u_apple = hs.parse_userid("@apple:test") self.u_banana = hs.parse_userid("@banana:test")
def setUp(self): db_pool = SQLiteMemoryDbPool() yield db_pool.prepare() hs = HomeServer("test", db_pool=db_pool, clock=MockClock(), config=Mock()) self.as_token = "token1" db_pool.runQuery("INSERT INTO application_services(token) VALUES(?)", (self.as_token, )) db_pool.runQuery("INSERT INTO application_services(token) VALUES(?)", ("token2", )) db_pool.runQuery("INSERT INTO application_services(token) VALUES(?)", ("token3", )) # must be done after inserts self.store = ApplicationServiceStore(hs)
def test_time_eviction(self): clock = MockClock() cache = ExpiringCache("test", clock, expiry_ms=1000) cache["key"] = 1 clock.advance_time(0.5) cache["key2"] = 2 self.assertEqual(cache.get("key"), 1) self.assertEqual(cache.get("key2"), 2) clock.advance_time(0.9) self.assertEqual(cache.get("key"), None) self.assertEqual(cache.get("key2"), 2) clock.advance_time(1) self.assertEqual(cache.get("key"), None) self.assertEqual(cache.get("key2"), None)
def test_iterable_eviction(self): clock = MockClock() cache = ExpiringCache("test", clock, max_len=5, iterable=True) cache["key"] = [1] cache["key2"] = [2, 3] cache["key3"] = [4, 5] self.assertEquals(cache.get("key"), [1]) self.assertEquals(cache.get("key2"), [2, 3]) self.assertEquals(cache.get("key3"), [4, 5]) cache["key4"] = [6, 7] self.assertEquals(cache.get("key"), None) self.assertEquals(cache.get("key2"), None) self.assertEquals(cache.get("key3"), [4, 5]) self.assertEquals(cache.get("key4"), [6, 7])
def setUp(self): self.mock_http_client = Mock(spec=[]) self.mock_http_client.put_json = DeferredMockCallable() self.mock_federation_resource = MockHttpResource() db_pool = SQLiteMemoryDbPool() yield db_pool.prepare() self.mock_config = NonCallableMock() self.mock_config.signing_key = [MockKey()] hs = HomeServer( "test", clock=MockClock(), db_pool=db_pool, handlers=None, resource_for_client=Mock(), resource_for_federation=self.mock_federation_resource, http_client=self.mock_http_client, config=self.mock_config, keyring=Mock(), ) hs.handlers = JustPresenceHandlers(hs) self.store = hs.get_datastore() # Some local users to test with self.u_apple = hs.parse_userid("@apple:test") self.u_banana = hs.parse_userid("@banana:test") yield self.store.create_presence(self.u_apple.localpart) yield self.store.create_presence(self.u_banana.localpart) # ID of a local user that does not exist self.u_durian = hs.parse_userid("@durian:test") # A remote user self.u_cabbage = hs.parse_userid("@cabbage:elsewhere") self.handler = hs.get_handlers().presence_handler self.mock_start = Mock() self.mock_stop = Mock() self.handler.start_polling_presence = self.mock_start self.handler.stop_polling_presence = self.mock_stop
def test_time_eviction(self): clock = MockClock() cache = ExpiringCache("test", clock, expiry_ms=1000) cache["key"] = 1 clock.advance_time(0.5) cache["key2"] = 2 self.assertEquals(cache.get("key"), 1) self.assertEquals(cache.get("key2"), 2) clock.advance_time(0.9) self.assertEquals(cache.get("key"), None) self.assertEquals(cache.get("key2"), 2) clock.advance_time(1) self.assertEquals(cache.get("key"), None) self.assertEquals(cache.get("key2"), None)
def test_from_cache(self): clock = MockClock() dns_client_mock = Mock(spec_set=['lookupService']) dns_client_mock.lookupService = Mock(spec_set=[]) service_name = "test_service.example.com" entry = Mock(spec_set=["expires"]) entry.expires = 999999999 cache = {service_name: [entry]} servers = yield resolve_service(service_name, dns_client=dns_client_mock, cache=cache, clock=clock) self.assertFalse(dns_client_mock.lookupService.called) self.assertEquals(len(servers), 1) self.assertEquals(servers, cache[service_name])
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer: # We need to be able to test advancing time in the homeserver, so we # replace the test homeserver's default clock with a MockClock, which # supports advancing time. return self.setup_test_homeserver(clock=MockClock())
class PresencePushTestCase(unittest.TestCase): """ Tests steady-state presence status updates. They assert that presence state update messages are pushed around the place when users change state, presuming that the watches are all established. These tests are MASSIVELY fragile currently as they poke internals of the presence handler; namely the _local_pushmap and _remote_recvmap. BE WARNED... """ def setUp(self): self.clock = MockClock() self.mock_http_client = Mock(spec=[]) self.mock_http_client.put_json = DeferredMockCallable() self.mock_federation_resource = MockHttpResource() hs = HomeServer( "test", clock=self.clock, db_pool=None, datastore=Mock(spec=[ "set_presence_state", "get_joined_hosts_for_room", # Bits that Federation needs "prep_send_transaction", "delivered_txn", "get_received_txn_response", "set_received_txn_response", ]), handlers=None, resource_for_client=Mock(), resource_for_federation=self.mock_federation_resource, http_client=self.mock_http_client, ) hs.handlers = JustPresenceHandlers(hs) self.datastore = hs.get_datastore() def get_received_txn_response(*args): return defer.succeed(None) self.datastore.get_received_txn_response = get_received_txn_response self.handler = hs.get_handlers().presence_handler self.event_source = hs.get_event_sources().sources["presence"] # Mock the RoomMemberHandler hs.handlers.room_member_handler = Mock(spec=[ "get_rooms_for_user", "get_room_members", ]) self.room_member_handler = hs.handlers.room_member_handler self.room_members = [] def get_rooms_for_user(user): if user in self.room_members: return defer.succeed(["a-room"]) else: return defer.succeed([]) self.room_member_handler.get_rooms_for_user = get_rooms_for_user def get_room_members(room_id): if room_id == "a-room": return defer.succeed(self.room_members) else: return defer.succeed([]) self.room_member_handler.get_room_members = get_room_members def get_room_hosts(room_id): if room_id == "a-room": hosts = set([u.domain for u in self.room_members]) return defer.succeed(hosts) else: return defer.succeed([]) self.datastore.get_joined_hosts_for_room = get_room_hosts def user_rooms_intersect(userlist): room_member_ids = map(lambda u: u.to_string(), self.room_members) shared = all(map(lambda i: i in room_member_ids, userlist)) return defer.succeed(shared) self.datastore.user_rooms_intersect = user_rooms_intersect @defer.inlineCallbacks def fetch_room_distributions_into(room_id, localusers=None, remotedomains=None, ignore_user=None): members = yield get_room_members(room_id) for member in members: if ignore_user is not None and member == ignore_user: continue if member.is_mine: if localusers is not None: localusers.add(member) else: if remotedomains is not None: remotedomains.add(member.domain) self.room_member_handler.fetch_room_distributions_into = ( fetch_room_distributions_into) def get_presence_list(user_localpart, accepted=None): if user_localpart == "apple": return defer.succeed([ { "observed_user_id": "@banana:test" }, { "observed_user_id": "@clementine:test" }, ]) else: return defer.succeed([]) self.datastore.get_presence_list = get_presence_list def is_presence_visible(observer_userid, observed_localpart): if (observed_localpart == "clementine" and observer_userid == "@banana:test"): return False return False self.datastore.is_presence_visible = is_presence_visible self.distributor = hs.get_distributor() self.distributor.declare("user_joined_room") # Some local users to test with self.u_apple = hs.parse_userid("@apple:test") self.u_banana = hs.parse_userid("@banana:test") self.u_clementine = hs.parse_userid("@clementine:test") self.u_durian = hs.parse_userid("@durian:test") self.u_elderberry = hs.parse_userid("@elderberry:test") # Remote user self.u_onion = hs.parse_userid("@onion:farm") self.u_potato = hs.parse_userid("@potato:remote") @defer.inlineCallbacks def test_push_local(self): self.room_members = [self.u_apple, self.u_elderberry] self.datastore.set_presence_state.return_value = defer.succeed( {"state": ONLINE}) # TODO(paul): Gut-wrenching self.handler._user_cachemap[self.u_apple] = UserPresenceCache() self.handler._user_cachemap[self.u_apple].update({"presence": OFFLINE}, serial=0) apple_set = self.handler._local_pushmap.setdefault("apple", set()) apple_set.add(self.u_banana) apple_set.add(self.u_clementine) self.assertEquals(self.event_source.get_current_key(), 0) yield self.handler.set_state(self.u_apple, self.u_apple, {"presence": ONLINE}) # Apple sees self-reflection (events, _) = yield self.event_source.get_new_events_for_user( self.u_apple, 0, None) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals( events, [ { "type": "m.presence", "content": { "user_id": "@apple:test", "presence": ONLINE, "last_active_ago": 0, } }, ], msg="Presence event should be visible to self-reflection") # Banana sees it because of presence subscription (events, _) = yield self.event_source.get_new_events_for_user( self.u_banana, 0, None) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals( events, [ { "type": "m.presence", "content": { "user_id": "@apple:test", "presence": ONLINE, "last_active_ago": 0, } }, ], msg="Presence event should be visible to explicit subscribers") # Elderberry sees it because of same room (events, _) = yield self.event_source.get_new_events_for_user( self.u_elderberry, 0, None) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals( events, [ { "type": "m.presence", "content": { "user_id": "@apple:test", "presence": ONLINE, "last_active_ago": 0, } }, ], msg="Presence event should be visible to other room members") # Durian is not in the room, should not see this event (events, _) = yield self.event_source.get_new_events_for_user( self.u_durian, 0, None) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(events, [], msg="Presence event should not be visible to others") presence = yield self.handler.get_presence_list( observer_user=self.u_apple, accepted=True) self.assertEquals([ { "observed_user": self.u_banana, "presence": OFFLINE }, { "observed_user": self.u_clementine, "presence": OFFLINE }, ], presence) # TODO(paul): Gut-wrenching banana_set = self.handler._local_pushmap.setdefault("banana", set()) banana_set.add(self.u_apple) yield self.handler.set_state(self.u_banana, self.u_banana, {"presence": ONLINE}) self.clock.advance_time(2) presence = yield self.handler.get_presence_list( observer_user=self.u_apple, accepted=True) self.assertEquals([ { "observed_user": self.u_banana, "presence": ONLINE, "last_active_ago": 2000 }, { "observed_user": self.u_clementine, "presence": OFFLINE }, ], presence) (events, _) = yield self.event_source.get_new_events_for_user( self.u_apple, 1, None) self.assertEquals(self.event_source.get_current_key(), 2) self.assertEquals(events, [ { "type": "m.presence", "content": { "user_id": "@banana:test", "presence": ONLINE, "last_active_ago": 2000 } }, ]) @defer.inlineCallbacks def test_push_remote(self): put_json = self.mock_http_client.put_json put_json.expect_call_and_return( call( "farm", path=ANY, # Can't guarantee which txn ID will be which data=_expect_edu("farm", "m.presence", content={ "push": [ { "user_id": "@apple:test", "presence": u"online", "last_active_ago": 0 }, ], }), on_send_callback=ANY, ), defer.succeed((200, "OK"))) put_json.expect_call_and_return( call( "remote", path=ANY, # Can't guarantee which txn ID will be which data=_expect_edu("remote", "m.presence", content={ "push": [ { "user_id": "@apple:test", "presence": u"online", "last_active_ago": 0 }, ], }), on_send_callback=ANY, ), defer.succeed((200, "OK"))) self.room_members = [self.u_apple, self.u_onion] self.datastore.set_presence_state.return_value = defer.succeed( {"state": ONLINE}) # TODO(paul): Gut-wrenching self.handler._user_cachemap[self.u_apple] = UserPresenceCache() self.handler._user_cachemap[self.u_apple].update({"presence": OFFLINE}, serial=0) apple_set = self.handler._remote_sendmap.setdefault("apple", set()) apple_set.add(self.u_potato.domain) yield self.handler.set_state(self.u_apple, self.u_apple, {"presence": ONLINE}) yield put_json.await_calls() @defer.inlineCallbacks def test_recv_remote(self): # TODO(paul): Gut-wrenching potato_set = self.handler._remote_recvmap.setdefault( self.u_potato, set()) potato_set.add(self.u_apple) self.room_members = [self.u_banana, self.u_potato] self.assertEquals(self.event_source.get_current_key(), 0) yield self.mock_federation_resource.trigger( "PUT", "/_matrix/federation/v1/send/1000000/", _make_edu_json("elsewhere", "m.presence", content={ "push": [ { "user_id": "@potato:remote", "presence": "online", "last_active_ago": 1000 }, ], })) (events, _) = yield self.event_source.get_new_events_for_user( self.u_apple, 0, None) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(events, [{ "type": "m.presence", "content": { "user_id": "@potato:remote", "presence": ONLINE, "last_active_ago": 1000, } }]) self.clock.advance_time(2) state = yield self.handler.get_state(self.u_potato, self.u_apple) self.assertEquals({"presence": ONLINE, "last_active_ago": 3000}, state) @defer.inlineCallbacks def test_join_room_local(self): self.room_members = [self.u_apple, self.u_banana] self.assertEquals(self.event_source.get_current_key(), 0) # TODO(paul): Gut-wrenching self.handler._user_cachemap[self.u_clementine] = UserPresenceCache() self.handler._user_cachemap[self.u_clementine].update( { "presence": PresenceState.ONLINE, "last_active": self.clock.time_msec(), }, self.u_clementine) yield self.distributor.fire("user_joined_room", self.u_clementine, "a-room") self.room_members.append(self.u_clementine) (events, _) = yield self.event_source.get_new_events_for_user( self.u_apple, 0, None) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(events, [{ "type": "m.presence", "content": { "user_id": "@clementine:test", "presence": ONLINE, "last_active_ago": 0, } }]) @defer.inlineCallbacks def test_join_room_remote(self): ## Sending local user state to a newly-joined remote user put_json = self.mock_http_client.put_json put_json.expect_call_and_return( call( "remote", path=ANY, # Can't guarantee which txn ID will be which data=_expect_edu("remote", "m.presence", content={ "push": [ { "user_id": "@apple:test", "presence": "online" }, ], }), on_send_callback=ANY, ), defer.succeed((200, "OK"))) put_json.expect_call_and_return( call( "remote", path=ANY, # Can't guarantee which txn ID will be which data=_expect_edu("remote", "m.presence", content={ "push": [ { "user_id": "@banana:test", "presence": "offline" }, ], }), on_send_callback=ANY, ), defer.succeed((200, "OK"))) # TODO(paul): Gut-wrenching self.handler._user_cachemap[self.u_apple] = UserPresenceCache() self.handler._user_cachemap[self.u_apple].update( {"presence": PresenceState.ONLINE}, self.u_apple) self.room_members = [self.u_apple, self.u_banana] yield self.distributor.fire("user_joined_room", self.u_potato, "a-room") yield put_json.await_calls() ## Sending newly-joined local user state to remote users put_json.expect_call_and_return( call( "remote", path="/_matrix/federation/v1/send/1000002/", data=_expect_edu("remote", "m.presence", content={ "push": [ { "user_id": "@clementine:test", "presence": "online" }, ], }), on_send_callback=ANY, ), defer.succeed((200, "OK"))) self.handler._user_cachemap[self.u_clementine] = UserPresenceCache() self.handler._user_cachemap[self.u_clementine].update( {"presence": ONLINE}, self.u_clementine) self.room_members.append(self.u_potato) yield self.distributor.fire("user_joined_room", self.u_clementine, "a-room") put_json.await_calls()
def setUp(self): self.clock = MockClock() self.mock_http_client = Mock(spec=[]) self.mock_http_client.put_json = DeferredMockCallable() self.mock_federation_resource = MockHttpResource() hs = HomeServer( "test", clock=self.clock, db_pool=None, datastore=Mock(spec=[ "set_presence_state", "get_joined_hosts_for_room", # Bits that Federation needs "prep_send_transaction", "delivered_txn", "get_received_txn_response", "set_received_txn_response", ]), handlers=None, resource_for_client=Mock(), resource_for_federation=self.mock_federation_resource, http_client=self.mock_http_client, ) hs.handlers = JustPresenceHandlers(hs) self.datastore = hs.get_datastore() def get_received_txn_response(*args): return defer.succeed(None) self.datastore.get_received_txn_response = get_received_txn_response self.handler = hs.get_handlers().presence_handler self.event_source = hs.get_event_sources().sources["presence"] # Mock the RoomMemberHandler hs.handlers.room_member_handler = Mock(spec=[ "get_rooms_for_user", "get_room_members", ]) self.room_member_handler = hs.handlers.room_member_handler self.room_members = [] def get_rooms_for_user(user): if user in self.room_members: return defer.succeed(["a-room"]) else: return defer.succeed([]) self.room_member_handler.get_rooms_for_user = get_rooms_for_user def get_room_members(room_id): if room_id == "a-room": return defer.succeed(self.room_members) else: return defer.succeed([]) self.room_member_handler.get_room_members = get_room_members def get_room_hosts(room_id): if room_id == "a-room": hosts = set([u.domain for u in self.room_members]) return defer.succeed(hosts) else: return defer.succeed([]) self.datastore.get_joined_hosts_for_room = get_room_hosts def user_rooms_intersect(userlist): room_member_ids = map(lambda u: u.to_string(), self.room_members) shared = all(map(lambda i: i in room_member_ids, userlist)) return defer.succeed(shared) self.datastore.user_rooms_intersect = user_rooms_intersect @defer.inlineCallbacks def fetch_room_distributions_into(room_id, localusers=None, remotedomains=None, ignore_user=None): members = yield get_room_members(room_id) for member in members: if ignore_user is not None and member == ignore_user: continue if member.is_mine: if localusers is not None: localusers.add(member) else: if remotedomains is not None: remotedomains.add(member.domain) self.room_member_handler.fetch_room_distributions_into = ( fetch_room_distributions_into) def get_presence_list(user_localpart, accepted=None): if user_localpart == "apple": return defer.succeed([ { "observed_user_id": "@banana:test" }, { "observed_user_id": "@clementine:test" }, ]) else: return defer.succeed([]) self.datastore.get_presence_list = get_presence_list def is_presence_visible(observer_userid, observed_localpart): if (observed_localpart == "clementine" and observer_userid == "@banana:test"): return False return False self.datastore.is_presence_visible = is_presence_visible self.distributor = hs.get_distributor() self.distributor.declare("user_joined_room") # Some local users to test with self.u_apple = hs.parse_userid("@apple:test") self.u_banana = hs.parse_userid("@banana:test") self.u_clementine = hs.parse_userid("@clementine:test") self.u_durian = hs.parse_userid("@durian:test") self.u_elderberry = hs.parse_userid("@elderberry:test") # Remote user self.u_onion = hs.parse_userid("@onion:farm") self.u_potato = hs.parse_userid("@potato:remote")
def setUp(self): self.clock = MockClock() self.cache = HttpTransactionCache(self.clock) self.mock_http_response = (200, "GOOD JOB!") self.mock_key = "foo"
class HttpTransactionCacheTestCase(unittest.TestCase): def setUp(self): self.clock = MockClock() self.cache = HttpTransactionCache(self.clock) self.mock_http_response = (200, "GOOD JOB!") self.mock_key = "foo" @defer.inlineCallbacks def test_executes_given_function(self): cb = Mock(return_value=defer.succeed(self.mock_http_response)) res = yield self.cache.fetch_or_execute(self.mock_key, cb, "some_arg", keyword="arg") cb.assert_called_once_with("some_arg", keyword="arg") self.assertEqual(res, self.mock_http_response) @defer.inlineCallbacks def test_deduplicates_based_on_key(self): cb = Mock(return_value=defer.succeed(self.mock_http_response)) for i in range(3): # invoke multiple times res = yield self.cache.fetch_or_execute(self.mock_key, cb, "some_arg", keyword="arg", changing_args=i) self.assertEqual(res, self.mock_http_response) # expect only a single call to do the work cb.assert_called_once_with("some_arg", keyword="arg", changing_args=0) @defer.inlineCallbacks def test_logcontexts_with_async_result(self): @defer.inlineCallbacks def cb(): yield Clock(reactor).sleep(0) defer.returnValue("yay") @defer.inlineCallbacks def test(): with LoggingContext("c") as c1: res = yield self.cache.fetch_or_execute(self.mock_key, cb) self.assertIs(LoggingContext.current_context(), c1) self.assertEqual(res, "yay") # run the test twice in parallel d = defer.gatherResults([test(), test()]) self.assertIs(LoggingContext.current_context(), LoggingContext.sentinel) yield d self.assertIs(LoggingContext.current_context(), LoggingContext.sentinel) @defer.inlineCallbacks def test_does_not_cache_exceptions(self): """Checks that, if the callback throws an exception, it is called again for the next request. """ called = [False] def cb(): if called[0]: # return a valid result the second time return defer.succeed(self.mock_http_response) called[0] = True raise Exception("boo") with LoggingContext("test") as test_context: try: yield self.cache.fetch_or_execute(self.mock_key, cb) except Exception as e: self.assertEqual(e.message, "boo") self.assertIs(LoggingContext.current_context(), test_context) res = yield self.cache.fetch_or_execute(self.mock_key, cb) self.assertEqual(res, self.mock_http_response) self.assertIs(LoggingContext.current_context(), test_context) @defer.inlineCallbacks def test_does_not_cache_failures(self): """Checks that, if the callback returns a failure, it is called again for the next request. """ called = [False] def cb(): if called[0]: # return a valid result the second time return defer.succeed(self.mock_http_response) called[0] = True return defer.fail(Exception("boo")) with LoggingContext("test") as test_context: try: yield self.cache.fetch_or_execute(self.mock_key, cb) except Exception as e: self.assertEqual(e.message, "boo") self.assertIs(LoggingContext.current_context(), test_context) res = yield self.cache.fetch_or_execute(self.mock_key, cb) self.assertEqual(res, self.mock_http_response) self.assertIs(LoggingContext.current_context(), test_context) @defer.inlineCallbacks def test_cleans_up(self): cb = Mock(return_value=defer.succeed(self.mock_http_response)) yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg") # should NOT have cleaned up yet self.clock.advance_time_msec(CLEANUP_PERIOD_MS / 2) yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg") # still using cache cb.assert_called_once_with("an arg") self.clock.advance_time_msec(CLEANUP_PERIOD_MS) yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg") # no longer using cache self.assertEqual(cb.call_count, 2) self.assertEqual(cb.call_args_list, [call("an arg", ), call("an arg", )])
def setUp(self): db_pool = SQLiteMemoryDbPool() yield db_pool.prepare() hs = HomeServer( "test", clock=MockClock(), db_pool=db_pool, handlers=None, resource_for_federation=Mock(), http_client=None, ) hs.handlers = JustPresenceHandlers(hs) self.store = hs.get_datastore() # Mock the RoomMemberHandler room_member_handler = Mock(spec=[]) hs.handlers.room_member_handler = room_member_handler # Some local users to test with self.u_apple = hs.parse_userid("@apple:test") self.u_banana = hs.parse_userid("@banana:test") self.u_clementine = hs.parse_userid("@clementine:test") yield self.store.create_presence(self.u_apple.localpart) yield self.store.set_presence_state(self.u_apple.localpart, { "state": ONLINE, "status_msg": "Online" }) self.handler = hs.get_handlers().presence_handler self.room_members = [] def get_rooms_for_user(user): if user in self.room_members: return defer.succeed(["a-room"]) else: return defer.succeed([]) room_member_handler.get_rooms_for_user = get_rooms_for_user def get_room_members(room_id): if room_id == "a-room": return defer.succeed(self.room_members) else: return defer.succeed([]) room_member_handler.get_room_members = get_room_members def user_rooms_intersect(userlist): room_member_ids = map(lambda u: u.to_string(), self.room_members) shared = all(map(lambda i: i in room_member_ids, userlist)) return defer.succeed(shared) self.store.user_rooms_intersect = user_rooms_intersect self.mock_start = Mock() self.mock_stop = Mock() self.handler.start_polling_presence = self.mock_start self.handler.stop_polling_presence = self.mock_stop
def setUp(self): self.mock_http_client = Mock(spec=[]) self.mock_http_client.put_json = DeferredMockCallable() self.mock_federation_resource = MockHttpResource() hs = HomeServer( "test", clock=MockClock(), db_pool=None, datastore=Mock(spec=[ # Bits that Federation needs "prep_send_transaction", "delivered_txn", "get_received_txn_response", "set_received_txn_response", ]), handlers=None, resource_for_client=Mock(), resource_for_federation=self.mock_federation_resource, http_client=self.mock_http_client, ) hs.handlers = JustPresenceHandlers(hs) self.datastore = hs.get_datastore() def get_received_txn_response(*args): return defer.succeed(None) self.datastore.get_received_txn_response = get_received_txn_response self.mock_update_client = Mock() def update(*args, **kwargs): # print "mock_update_client: Args=%s, kwargs=%s" %(args, kwargs,) return defer.succeed(None) self.mock_update_client.side_effect = update self.handler = hs.get_handlers().presence_handler self.handler.push_update_to_clients = self.mock_update_client hs.handlers.room_member_handler = Mock(spec=[ "get_rooms_for_user", ]) # For this test no users are ever in rooms def get_rooms_for_user(user): return defer.succeed([]) hs.handlers.room_member_handler.get_rooms_for_user = get_rooms_for_user # Mocked database state # Local users always start offline self.current_user_state = { "apple": OFFLINE, "banana": OFFLINE, "clementine": OFFLINE, "fig": OFFLINE, } def get_presence_state(user_localpart): return defer.succeed({ "state": self.current_user_state[user_localpart], "status_msg": None, "mtime": 123456000 }) self.datastore.get_presence_state = get_presence_state def set_presence_state(user_localpart, new_state): was = self.current_user_state[user_localpart] self.current_user_state[user_localpart] = new_state["state"] return defer.succeed({"state": was}) self.datastore.set_presence_state = set_presence_state def get_presence_list(user_localpart, accepted): return defer.succeed([{ "observed_user_id": u } for u in self.PRESENCE_LIST[user_localpart]]) self.datastore.get_presence_list = get_presence_list def is_presence_visible(observed_localpart, observer_userid): return True self.datastore.is_presence_visible = is_presence_visible # Local users self.u_apple = hs.parse_userid("@apple:test") self.u_banana = hs.parse_userid("@banana:test") self.u_clementine = hs.parse_userid("@clementine:test") self.u_fig = hs.parse_userid("@fig:test") # Remote users self.u_potato = hs.parse_userid("@potato:remote")
def setup_test_homeserver( cleanup_func, name="test", config=None, reactor=None, homeserver_to_use: Type[HomeServer] = TestHomeServer, **kwargs, ): """ Setup a homeserver suitable for running tests against. Keyword arguments are passed to the Homeserver constructor. If no datastore is supplied, one is created and given to the homeserver. Args: cleanup_func : The function used to register a cleanup routine for after the test. Calling this method directly is deprecated: you should instead derive from HomeserverTestCase. """ if reactor is None: from twisted.internet import reactor if config is None: config = default_config(name, parse=True) config.ldap_enabled = False if "clock" not in kwargs: kwargs["clock"] = MockClock() if USE_POSTGRES_FOR_TESTS: test_db = "synapse_test_%s" % uuid.uuid4().hex database_config = { "name": "psycopg2", "args": { "database": test_db, "host": POSTGRES_HOST, "password": POSTGRES_PASSWORD, "user": POSTGRES_USER, "cp_min": 1, "cp_max": 5, }, } else: if SQLITE_PERSIST_DB: # The current working directory is in _trial_temp, so this gets created within that directory. test_db_location = os.path.abspath("test.db") logger.debug("Will persist db to %s", test_db_location) # Ensure each test gets a clean database. try: os.remove(test_db_location) except FileNotFoundError: pass else: logger.debug("Removed existing DB at %s", test_db_location) else: test_db_location = ":memory:" database_config = { "name": "sqlite3", "args": { "database": test_db_location, "cp_min": 1, "cp_max": 1 }, } if "db_txn_limit" in kwargs: database_config["txn_limit"] = kwargs["db_txn_limit"] database = DatabaseConnectionConfig("master", database_config) config.database.databases = [database] db_engine = create_engine(database.config) # Create the database before we actually try and connect to it, based off # the template database we generate in setupdb() if isinstance(db_engine, PostgresEngine): db_conn = db_engine.module.connect( database=POSTGRES_BASE_DB, user=POSTGRES_USER, host=POSTGRES_HOST, password=POSTGRES_PASSWORD, ) db_conn.autocommit = True cur = db_conn.cursor() cur.execute("DROP DATABASE IF EXISTS %s;" % (test_db, )) cur.execute("CREATE DATABASE %s WITH TEMPLATE %s;" % (test_db, POSTGRES_BASE_DB)) cur.close() db_conn.close() hs = homeserver_to_use( name, config=config, version_string="Synapse/tests", reactor=reactor, ) # Install @cache_in_self attributes for key, val in kwargs.items(): setattr(hs, "_" + key, val) # Mock TLS hs.tls_server_context_factory = Mock() hs.tls_client_options_factory = Mock() hs.setup() if homeserver_to_use == TestHomeServer: hs.setup_background_tasks() if isinstance(db_engine, PostgresEngine): database = hs.get_datastores().databases[0] # We need to do cleanup on PostgreSQL def cleanup(): import psycopg2 # Close all the db pools database._db_pool.close() dropped = False # Drop the test database db_conn = db_engine.module.connect( database=POSTGRES_BASE_DB, user=POSTGRES_USER, host=POSTGRES_HOST, password=POSTGRES_PASSWORD, ) db_conn.autocommit = True cur = db_conn.cursor() # Try a few times to drop the DB. Some things may hold on to the # database for a few more seconds due to flakiness, preventing # us from dropping it when the test is over. If we can't drop # it, warn and move on. for _ in range(5): try: cur.execute("DROP DATABASE IF EXISTS %s;" % (test_db, )) db_conn.commit() dropped = True except psycopg2.OperationalError as e: warnings.warn("Couldn't drop old db: " + str(e), category=UserWarning) time.sleep(0.5) cur.close() db_conn.close() if not dropped: warnings.warn("Failed to drop old DB.", category=UserWarning) if not LEAVE_DB: # Register the cleanup hook cleanup_func(cleanup) # bcrypt is far too slow to be doing in unit tests # Need to let the HS build an auth handler and then mess with it # because AuthHandler's constructor requires the HS, so we can't make one # beforehand and pass it in to the HS's constructor (chicken / egg) async def hash(p): return hashlib.md5(p.encode("utf8")).hexdigest() hs.get_auth_handler().hash = hash async def validate_hash(p, h): return hashlib.md5(p.encode("utf8")).hexdigest() == h hs.get_auth_handler().validate_hash = validate_hash # Make the threadpool and database transactions synchronous for testing. _make_test_homeserver_synchronous(hs) return hs
class MockClockTestCase(unittest.TestCase): def setUp(self): self.clock = MockClock() def test_advance_time(self): start_time = self.clock.time() self.clock.advance_time(20) self.assertEquals(20, self.clock.time() - start_time) def test_later(self): invoked = [0, 0] def _cb0(): invoked[0] = 1 self.clock.call_later(10, _cb0) def _cb1(): invoked[1] = 1 self.clock.call_later(20, _cb1) self.assertFalse(invoked[0]) self.clock.advance_time(15) self.assertTrue(invoked[0]) self.assertFalse(invoked[1]) self.clock.advance_time(5) self.assertTrue(invoked[1]) def test_cancel_later(self): invoked = [0, 0] def _cb0(): invoked[0] = 1 t0 = self.clock.call_later(10, _cb0) def _cb1(): invoked[1] = 1 self.clock.call_later(20, _cb1) self.clock.cancel_call_later(t0) self.clock.advance_time(30) self.assertFalse(invoked[0]) self.assertTrue(invoked[1])
class HttpTransactionCacheTestCase(unittest.TestCase): def setUp(self): self.clock = MockClock() self.cache = HttpTransactionCache(self.clock) self.mock_http_response = (200, "GOOD JOB!") self.mock_key = "foo" @defer.inlineCallbacks def test_executes_given_function(self): cb = Mock( return_value=defer.succeed(self.mock_http_response) ) res = yield self.cache.fetch_or_execute( self.mock_key, cb, "some_arg", keyword="arg" ) cb.assert_called_once_with("some_arg", keyword="arg") self.assertEqual(res, self.mock_http_response) @defer.inlineCallbacks def test_deduplicates_based_on_key(self): cb = Mock( return_value=defer.succeed(self.mock_http_response) ) for i in range(3): # invoke multiple times res = yield self.cache.fetch_or_execute( self.mock_key, cb, "some_arg", keyword="arg", changing_args=i ) self.assertEqual(res, self.mock_http_response) # expect only a single call to do the work cb.assert_called_once_with("some_arg", keyword="arg", changing_args=0) @defer.inlineCallbacks def test_cleans_up(self): cb = Mock( return_value=defer.succeed(self.mock_http_response) ) yield self.cache.fetch_or_execute( self.mock_key, cb, "an arg" ) # should NOT have cleaned up yet self.clock.advance_time_msec(CLEANUP_PERIOD_MS / 2) yield self.cache.fetch_or_execute( self.mock_key, cb, "an arg" ) # still using cache cb.assert_called_once_with("an arg") self.clock.advance_time_msec(CLEANUP_PERIOD_MS) yield self.cache.fetch_or_execute( self.mock_key, cb, "an arg" ) # no longer using cache self.assertEqual(cb.call_count, 2) self.assertEqual( cb.call_args_list, [call("an arg",), call("an arg",)] )
def setUp(self): self.clock = MockClock() self.mock_http_client = Mock(spec=[]) self.mock_http_client.put_json = DeferredMockCallable() self.mock_federation_resource = MockHttpResource() self.mock_config = NonCallableMock() self.mock_config.signing_key = [MockKey()] hs = HomeServer("test", clock=self.clock, db_pool=None, datastore=Mock(spec=[ "set_presence_state", "get_joined_hosts_for_room", # Bits that Federation needs "prep_send_transaction", "delivered_txn", "get_received_txn_response", "set_received_txn_response", ]), handlers=None, resource_for_client=Mock(), resource_for_federation=self.mock_federation_resource, http_client=self.mock_http_client, config=self.mock_config, keyring=Mock(), ) hs.handlers = JustPresenceHandlers(hs) self.datastore = hs.get_datastore() def get_received_txn_response(*args): return defer.succeed(None) self.datastore.get_received_txn_response = get_received_txn_response self.handler = hs.get_handlers().presence_handler self.event_source = hs.get_event_sources().sources["presence"] # Mock the RoomMemberHandler hs.handlers.room_member_handler = Mock(spec=[ "get_rooms_for_user", "get_room_members", ]) self.room_member_handler = hs.handlers.room_member_handler self.room_members = [] def get_rooms_for_user(user): if user in self.room_members: return defer.succeed(["a-room"]) else: return defer.succeed([]) self.room_member_handler.get_rooms_for_user = get_rooms_for_user def get_room_members(room_id): if room_id == "a-room": return defer.succeed(self.room_members) else: return defer.succeed([]) self.room_member_handler.get_room_members = get_room_members def get_room_hosts(room_id): if room_id == "a-room": hosts = set([u.domain for u in self.room_members]) return defer.succeed(hosts) else: return defer.succeed([]) self.datastore.get_joined_hosts_for_room = get_room_hosts def user_rooms_intersect(userlist): room_member_ids = map(lambda u: u.to_string(), self.room_members) shared = all(map(lambda i: i in room_member_ids, userlist)) return defer.succeed(shared) self.datastore.user_rooms_intersect = user_rooms_intersect @defer.inlineCallbacks def fetch_room_distributions_into(room_id, localusers=None, remotedomains=None, ignore_user=None): members = yield get_room_members(room_id) for member in members: if ignore_user is not None and member == ignore_user: continue if member.is_mine: if localusers is not None: localusers.add(member) else: if remotedomains is not None: remotedomains.add(member.domain) self.room_member_handler.fetch_room_distributions_into = ( fetch_room_distributions_into) def get_presence_list(user_localpart, accepted=None): if user_localpart == "apple": return defer.succeed([ {"observed_user_id": "@banana:test"}, {"observed_user_id": "@clementine:test"}, ]) else: return defer.succeed([]) self.datastore.get_presence_list = get_presence_list def is_presence_visible(observer_userid, observed_localpart): if (observed_localpart == "clementine" and observer_userid == "@banana:test"): return False return False self.datastore.is_presence_visible = is_presence_visible self.distributor = hs.get_distributor() self.distributor.declare("user_joined_room") # Some local users to test with self.u_apple = hs.parse_userid("@apple:test") self.u_banana = hs.parse_userid("@banana:test") self.u_clementine = hs.parse_userid("@clementine:test") self.u_durian = hs.parse_userid("@durian:test") self.u_elderberry = hs.parse_userid("@elderberry:test") # Remote user self.u_onion = hs.parse_userid("@onion:farm") self.u_potato = hs.parse_userid("@potato:remote")
class HttpTransactionCacheTestCase(unittest.TestCase): def setUp(self): self.clock = MockClock() self.hs = Mock() self.hs.get_clock = Mock(return_value=self.clock) self.hs.get_auth = Mock() self.cache = HttpTransactionCache(self.hs) self.mock_http_response = (200, "GOOD JOB!") self.mock_key = "foo" @defer.inlineCallbacks def test_executes_given_function(self): cb = Mock(return_value=defer.succeed(self.mock_http_response)) res = yield self.cache.fetch_or_execute( self.mock_key, cb, "some_arg", keyword="arg" ) cb.assert_called_once_with("some_arg", keyword="arg") self.assertEqual(res, self.mock_http_response) @defer.inlineCallbacks def test_deduplicates_based_on_key(self): cb = Mock(return_value=defer.succeed(self.mock_http_response)) for i in range(3): # invoke multiple times res = yield self.cache.fetch_or_execute( self.mock_key, cb, "some_arg", keyword="arg", changing_args=i ) self.assertEqual(res, self.mock_http_response) # expect only a single call to do the work cb.assert_called_once_with("some_arg", keyword="arg", changing_args=0) @defer.inlineCallbacks def test_logcontexts_with_async_result(self): @defer.inlineCallbacks def cb(): yield Clock(reactor).sleep(0) defer.returnValue("yay") @defer.inlineCallbacks def test(): with LoggingContext("c") as c1: res = yield self.cache.fetch_or_execute(self.mock_key, cb) self.assertIs(LoggingContext.current_context(), c1) self.assertEqual(res, "yay") # run the test twice in parallel d = defer.gatherResults([test(), test()]) self.assertIs(LoggingContext.current_context(), LoggingContext.sentinel) yield d self.assertIs(LoggingContext.current_context(), LoggingContext.sentinel) @defer.inlineCallbacks def test_does_not_cache_exceptions(self): """Checks that, if the callback throws an exception, it is called again for the next request. """ called = [False] def cb(): if called[0]: # return a valid result the second time return defer.succeed(self.mock_http_response) called[0] = True raise Exception("boo") with LoggingContext("test") as test_context: try: yield self.cache.fetch_or_execute(self.mock_key, cb) except Exception as e: self.assertEqual(e.args[0], "boo") self.assertIs(LoggingContext.current_context(), test_context) res = yield self.cache.fetch_or_execute(self.mock_key, cb) self.assertEqual(res, self.mock_http_response) self.assertIs(LoggingContext.current_context(), test_context) @defer.inlineCallbacks def test_does_not_cache_failures(self): """Checks that, if the callback returns a failure, it is called again for the next request. """ called = [False] def cb(): if called[0]: # return a valid result the second time return defer.succeed(self.mock_http_response) called[0] = True return defer.fail(Exception("boo")) with LoggingContext("test") as test_context: try: yield self.cache.fetch_or_execute(self.mock_key, cb) except Exception as e: self.assertEqual(e.args[0], "boo") self.assertIs(LoggingContext.current_context(), test_context) res = yield self.cache.fetch_or_execute(self.mock_key, cb) self.assertEqual(res, self.mock_http_response) self.assertIs(LoggingContext.current_context(), test_context) @defer.inlineCallbacks def test_cleans_up(self): cb = Mock(return_value=defer.succeed(self.mock_http_response)) yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg") # should NOT have cleaned up yet self.clock.advance_time_msec(CLEANUP_PERIOD_MS / 2) yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg") # still using cache cb.assert_called_once_with("an arg") self.clock.advance_time_msec(CLEANUP_PERIOD_MS) yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg") # no longer using cache self.assertEqual(cb.call_count, 2) self.assertEqual(cb.call_args_list, [call("an arg"), call("an arg")])
def setUp(self): self.clock = MockClock()
class PresencePushTestCase(unittest.TestCase): """ Tests steady-state presence status updates. They assert that presence state update messages are pushed around the place when users change state, presuming that the watches are all established. These tests are MASSIVELY fragile currently as they poke internals of the presence handler; namely the _local_pushmap and _remote_recvmap. BE WARNED... """ def setUp(self): self.clock = MockClock() self.mock_http_client = Mock(spec=[]) self.mock_http_client.put_json = DeferredMockCallable() self.mock_federation_resource = MockHttpResource() self.mock_config = NonCallableMock() self.mock_config.signing_key = [MockKey()] hs = HomeServer("test", clock=self.clock, db_pool=None, datastore=Mock(spec=[ "set_presence_state", "get_joined_hosts_for_room", # Bits that Federation needs "prep_send_transaction", "delivered_txn", "get_received_txn_response", "set_received_txn_response", ]), handlers=None, resource_for_client=Mock(), resource_for_federation=self.mock_federation_resource, http_client=self.mock_http_client, config=self.mock_config, keyring=Mock(), ) hs.handlers = JustPresenceHandlers(hs) self.datastore = hs.get_datastore() def get_received_txn_response(*args): return defer.succeed(None) self.datastore.get_received_txn_response = get_received_txn_response self.handler = hs.get_handlers().presence_handler self.event_source = hs.get_event_sources().sources["presence"] # Mock the RoomMemberHandler hs.handlers.room_member_handler = Mock(spec=[ "get_rooms_for_user", "get_room_members", ]) self.room_member_handler = hs.handlers.room_member_handler self.room_members = [] def get_rooms_for_user(user): if user in self.room_members: return defer.succeed(["a-room"]) else: return defer.succeed([]) self.room_member_handler.get_rooms_for_user = get_rooms_for_user def get_room_members(room_id): if room_id == "a-room": return defer.succeed(self.room_members) else: return defer.succeed([]) self.room_member_handler.get_room_members = get_room_members def get_room_hosts(room_id): if room_id == "a-room": hosts = set([u.domain for u in self.room_members]) return defer.succeed(hosts) else: return defer.succeed([]) self.datastore.get_joined_hosts_for_room = get_room_hosts def user_rooms_intersect(userlist): room_member_ids = map(lambda u: u.to_string(), self.room_members) shared = all(map(lambda i: i in room_member_ids, userlist)) return defer.succeed(shared) self.datastore.user_rooms_intersect = user_rooms_intersect @defer.inlineCallbacks def fetch_room_distributions_into(room_id, localusers=None, remotedomains=None, ignore_user=None): members = yield get_room_members(room_id) for member in members: if ignore_user is not None and member == ignore_user: continue if member.is_mine: if localusers is not None: localusers.add(member) else: if remotedomains is not None: remotedomains.add(member.domain) self.room_member_handler.fetch_room_distributions_into = ( fetch_room_distributions_into) def get_presence_list(user_localpart, accepted=None): if user_localpart == "apple": return defer.succeed([ {"observed_user_id": "@banana:test"}, {"observed_user_id": "@clementine:test"}, ]) else: return defer.succeed([]) self.datastore.get_presence_list = get_presence_list def is_presence_visible(observer_userid, observed_localpart): if (observed_localpart == "clementine" and observer_userid == "@banana:test"): return False return False self.datastore.is_presence_visible = is_presence_visible self.distributor = hs.get_distributor() self.distributor.declare("user_joined_room") # Some local users to test with self.u_apple = hs.parse_userid("@apple:test") self.u_banana = hs.parse_userid("@banana:test") self.u_clementine = hs.parse_userid("@clementine:test") self.u_durian = hs.parse_userid("@durian:test") self.u_elderberry = hs.parse_userid("@elderberry:test") # Remote user self.u_onion = hs.parse_userid("@onion:farm") self.u_potato = hs.parse_userid("@potato:remote") @defer.inlineCallbacks def test_push_local(self): self.room_members = [self.u_apple, self.u_elderberry] self.datastore.set_presence_state.return_value = defer.succeed( {"state": ONLINE} ) # TODO(paul): Gut-wrenching self.handler._user_cachemap[self.u_apple] = UserPresenceCache() self.handler._user_cachemap[self.u_apple].update( {"presence": OFFLINE}, serial=0 ) apple_set = self.handler._local_pushmap.setdefault("apple", set()) apple_set.add(self.u_banana) apple_set.add(self.u_clementine) self.assertEquals(self.event_source.get_current_key(), 0) yield self.handler.set_state(self.u_apple, self.u_apple, {"presence": ONLINE} ) # Apple sees self-reflection (events, _) = yield self.event_source.get_new_events_for_user( self.u_apple, 0, None ) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(events, [ {"type": "m.presence", "content": { "user_id": "@apple:test", "presence": ONLINE, "last_active_ago": 0, }}, ], msg="Presence event should be visible to self-reflection" ) config = SourcePaginationConfig(from_key=1, to_key=0) (chunk, _) = yield self.event_source.get_pagination_rows( self.u_apple, config, None ) self.assertEquals(chunk, [ {"type": "m.presence", "content": { "user_id": "@apple:test", "presence": ONLINE, "last_active_ago": 0, }}, ] ) # Banana sees it because of presence subscription (events, _) = yield self.event_source.get_new_events_for_user( self.u_banana, 0, None ) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(events, [ {"type": "m.presence", "content": { "user_id": "@apple:test", "presence": ONLINE, "last_active_ago": 0, }}, ], msg="Presence event should be visible to explicit subscribers" ) # Elderberry sees it because of same room (events, _) = yield self.event_source.get_new_events_for_user( self.u_elderberry, 0, None ) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(events, [ {"type": "m.presence", "content": { "user_id": "@apple:test", "presence": ONLINE, "last_active_ago": 0, }}, ], msg="Presence event should be visible to other room members" ) # Durian is not in the room, should not see this event (events, _) = yield self.event_source.get_new_events_for_user( self.u_durian, 0, None ) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(events, [], msg="Presence event should not be visible to others" ) presence = yield self.handler.get_presence_list( observer_user=self.u_apple, accepted=True) self.assertEquals( [ {"observed_user": self.u_banana, "presence": OFFLINE}, {"observed_user": self.u_clementine, "presence": OFFLINE}, ], presence ) # TODO(paul): Gut-wrenching banana_set = self.handler._local_pushmap.setdefault("banana", set()) banana_set.add(self.u_apple) yield self.handler.set_state(self.u_banana, self.u_banana, {"presence": ONLINE} ) self.clock.advance_time(2) presence = yield self.handler.get_presence_list( observer_user=self.u_apple, accepted=True) self.assertEquals([ {"observed_user": self.u_banana, "presence": ONLINE, "last_active_ago": 2000}, {"observed_user": self.u_clementine, "presence": OFFLINE}, ], presence) (events, _) = yield self.event_source.get_new_events_for_user( self.u_apple, 1, None ) self.assertEquals(self.event_source.get_current_key(), 2) self.assertEquals(events, [ {"type": "m.presence", "content": { "user_id": "@banana:test", "presence": ONLINE, "last_active_ago": 2000 }}, ] ) @defer.inlineCallbacks def test_push_remote(self): put_json = self.mock_http_client.put_json put_json.expect_call_and_return( call("farm", path=ANY, # Can't guarantee which txn ID will be which data=_expect_edu("farm", "m.presence", content={ "push": [ {"user_id": "@apple:test", "presence": u"online", "last_active_ago": 0}, ], } ), json_data_callback=ANY, ), defer.succeed((200, "OK")) ) put_json.expect_call_and_return( call("remote", path=ANY, # Can't guarantee which txn ID will be which data=_expect_edu("remote", "m.presence", content={ "push": [ {"user_id": "@apple:test", "presence": u"online", "last_active_ago": 0}, ], } ), json_data_callback=ANY, ), defer.succeed((200, "OK")) ) self.room_members = [self.u_apple, self.u_onion] self.datastore.set_presence_state.return_value = defer.succeed( {"state": ONLINE} ) # TODO(paul): Gut-wrenching self.handler._user_cachemap[self.u_apple] = UserPresenceCache() self.handler._user_cachemap[self.u_apple].update( {"presence": OFFLINE}, serial=0 ) apple_set = self.handler._remote_sendmap.setdefault("apple", set()) apple_set.add(self.u_potato.domain) yield self.handler.set_state(self.u_apple, self.u_apple, {"presence": ONLINE} ) yield put_json.await_calls() @defer.inlineCallbacks def test_recv_remote(self): # TODO(paul): Gut-wrenching potato_set = self.handler._remote_recvmap.setdefault(self.u_potato, set()) potato_set.add(self.u_apple) self.room_members = [self.u_banana, self.u_potato] self.assertEquals(self.event_source.get_current_key(), 0) yield self.mock_federation_resource.trigger("PUT", "/_matrix/federation/v1/send/1000000/", _make_edu_json("elsewhere", "m.presence", content={ "push": [ {"user_id": "@potato:remote", "presence": "online", "last_active_ago": 1000}, ], } ) ) (events, _) = yield self.event_source.get_new_events_for_user( self.u_apple, 0, None ) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(events, [ {"type": "m.presence", "content": { "user_id": "@potato:remote", "presence": ONLINE, "last_active_ago": 1000, }} ] ) self.clock.advance_time(2) state = yield self.handler.get_state(self.u_potato, self.u_apple) self.assertEquals( {"presence": ONLINE, "last_active_ago": 3000}, state ) @defer.inlineCallbacks def test_join_room_local(self): self.room_members = [self.u_apple, self.u_banana] self.assertEquals(self.event_source.get_current_key(), 0) # TODO(paul): Gut-wrenching self.handler._user_cachemap[self.u_clementine] = UserPresenceCache() self.handler._user_cachemap[self.u_clementine].update( { "presence": PresenceState.ONLINE, "last_active": self.clock.time_msec(), }, self.u_clementine ) yield self.distributor.fire("user_joined_room", self.u_clementine, "a-room" ) self.room_members.append(self.u_clementine) (events, _) = yield self.event_source.get_new_events_for_user( self.u_apple, 0, None ) self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(events, [ {"type": "m.presence", "content": { "user_id": "@clementine:test", "presence": ONLINE, "last_active_ago": 0, }} ] ) @defer.inlineCallbacks def test_join_room_remote(self): ## Sending local user state to a newly-joined remote user put_json = self.mock_http_client.put_json put_json.expect_call_and_return( call("remote", path=ANY, # Can't guarantee which txn ID will be which data=_expect_edu("remote", "m.presence", content={ "push": [ {"user_id": "@apple:test", "presence": "online"}, ], } ), json_data_callback=ANY, ), defer.succeed((200, "OK")) ) put_json.expect_call_and_return( call("remote", path=ANY, # Can't guarantee which txn ID will be which data=_expect_edu("remote", "m.presence", content={ "push": [ {"user_id": "@banana:test", "presence": "offline"}, ], } ), json_data_callback=ANY, ), defer.succeed((200, "OK")) ) # TODO(paul): Gut-wrenching self.handler._user_cachemap[self.u_apple] = UserPresenceCache() self.handler._user_cachemap[self.u_apple].update( {"presence": PresenceState.ONLINE}, self.u_apple) self.room_members = [self.u_apple, self.u_banana] yield self.distributor.fire("user_joined_room", self.u_potato, "a-room" ) yield put_json.await_calls() ## Sending newly-joined local user state to remote users put_json.expect_call_and_return( call("remote", path="/_matrix/federation/v1/send/1000002/", data=_expect_edu("remote", "m.presence", content={ "push": [ {"user_id": "@clementine:test", "presence": "online"}, ], } ), json_data_callback=ANY, ), defer.succeed((200, "OK")) ) self.handler._user_cachemap[self.u_clementine] = UserPresenceCache() self.handler._user_cachemap[self.u_clementine].update( {"presence": ONLINE}, self.u_clementine) self.room_members.append(self.u_potato) yield self.distributor.fire("user_joined_room", self.u_clementine, "a-room" ) put_json.await_calls()