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])
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_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)
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()
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()