forked from MirrorHub/synapse
Merge branch 'develop' of github.com:matrix-org/synapse into events_refactor
Conflicts: tests/handlers/test_room.py
This commit is contained in:
commit
57e0e619f3
13 changed files with 449 additions and 56 deletions
|
@ -1,3 +1,9 @@
|
||||||
|
Changes in synapse 0.5.4a (2014-12-13)
|
||||||
|
======================================
|
||||||
|
|
||||||
|
* Fix bug while generating the error message when a file path specified in
|
||||||
|
the config doesn't exist.
|
||||||
|
|
||||||
Changes in synapse 0.5.4 (2014-12-03)
|
Changes in synapse 0.5.4 (2014-12-03)
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
0.5.4
|
0.5.4a
|
||||||
|
|
|
@ -16,4 +16,4 @@
|
||||||
""" This is a reference implementation of a synapse home server.
|
""" This is a reference implementation of a synapse home server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.5.4"
|
__version__ = "0.5.4a"
|
||||||
|
|
|
@ -44,9 +44,9 @@ class Config(object):
|
||||||
)
|
)
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
raise ConfigError(
|
raise ConfigError(
|
||||||
"File % config for %s doesn't exist."
|
"File %s config for %s doesn't exist."
|
||||||
" Try running again with --generate-config"
|
" Try running again with --generate-config"
|
||||||
% (config_name,)
|
% (file_path, config_name,)
|
||||||
)
|
)
|
||||||
return cls.abspath(file_path)
|
return cls.abspath(file_path)
|
||||||
|
|
||||||
|
|
|
@ -244,6 +244,7 @@ class RoomMemberHandler(BaseHandler):
|
||||||
|
|
||||||
self.distributor = hs.get_distributor()
|
self.distributor = hs.get_distributor()
|
||||||
self.distributor.declare("user_joined_room")
|
self.distributor.declare("user_joined_room")
|
||||||
|
self.distributor.declare("user_left_room")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_room_members(self, room_id, membership=Membership.JOIN):
|
def get_room_members(self, room_id, membership=Membership.JOIN):
|
||||||
|
@ -370,6 +371,12 @@ class RoomMemberHandler(BaseHandler):
|
||||||
do_auth=do_auth,
|
do_auth=do_auth,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if prev_state and prev_state.membership == Membership.JOIN:
|
||||||
|
user = self.hs.parse_userid(event.user_id)
|
||||||
|
self.distributor.fire(
|
||||||
|
"user_left_room", user=user, room_id=event.room_id
|
||||||
|
)
|
||||||
|
|
||||||
defer.returnValue({"room_id": room_id})
|
defer.returnValue({"room_id": room_id})
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
|
|
@ -43,7 +43,23 @@ class TypingNotificationHandler(BaseHandler):
|
||||||
|
|
||||||
self.federation.register_edu_handler("m.typing", self._recv_edu)
|
self.federation.register_edu_handler("m.typing", self._recv_edu)
|
||||||
|
|
||||||
self._member_typing_until = {}
|
hs.get_distributor().observe("user_left_room", self.user_left_room)
|
||||||
|
|
||||||
|
self._member_typing_until = {} # clock time we expect to stop
|
||||||
|
self._member_typing_timer = {} # deferreds to manage theabove
|
||||||
|
|
||||||
|
# map room IDs to serial numbers
|
||||||
|
self._room_serials = {}
|
||||||
|
self._latest_room_serial = 0
|
||||||
|
# map room IDs to sets of users currently typing
|
||||||
|
self._room_typing = {}
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Cancels all the pending timers.
|
||||||
|
Normally this shouldn't be needed, but it's required from unit tests
|
||||||
|
to avoid a "Reactor was unclean" warning."""
|
||||||
|
for t in self._member_typing_timer.values():
|
||||||
|
self.clock.cancel_call_later(t)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def started_typing(self, target_user, auth_user, room_id, timeout):
|
def started_typing(self, target_user, auth_user, room_id, timeout):
|
||||||
|
@ -53,12 +69,24 @@ class TypingNotificationHandler(BaseHandler):
|
||||||
if target_user != auth_user:
|
if target_user != auth_user:
|
||||||
raise AuthError(400, "Cannot set another user's typing state")
|
raise AuthError(400, "Cannot set another user's typing state")
|
||||||
|
|
||||||
|
yield self.auth.check_joined_room(room_id, target_user.to_string())
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"%s has started typing in %s", target_user.to_string(), room_id
|
||||||
|
)
|
||||||
|
|
||||||
until = self.clock.time_msec() + timeout
|
until = self.clock.time_msec() + timeout
|
||||||
member = RoomMember(room_id=room_id, user=target_user)
|
member = RoomMember(room_id=room_id, user=target_user)
|
||||||
|
|
||||||
was_present = member in self._member_typing_until
|
was_present = member in self._member_typing_until
|
||||||
|
|
||||||
|
if member in self._member_typing_timer:
|
||||||
|
self.clock.cancel_call_later(self._member_typing_timer[member])
|
||||||
|
|
||||||
self._member_typing_until[member] = until
|
self._member_typing_until[member] = until
|
||||||
|
self._member_typing_timer[member] = self.clock.call_later(
|
||||||
|
timeout / 1000, lambda: self._stopped_typing(member)
|
||||||
|
)
|
||||||
|
|
||||||
if was_present:
|
if was_present:
|
||||||
# No point sending another notification
|
# No point sending another notification
|
||||||
|
@ -78,18 +106,39 @@ class TypingNotificationHandler(BaseHandler):
|
||||||
if target_user != auth_user:
|
if target_user != auth_user:
|
||||||
raise AuthError(400, "Cannot set another user's typing state")
|
raise AuthError(400, "Cannot set another user's typing state")
|
||||||
|
|
||||||
|
yield self.auth.check_joined_room(room_id, target_user.to_string())
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"%s has stopped typing in %s", target_user.to_string(), room_id
|
||||||
|
)
|
||||||
|
|
||||||
member = RoomMember(room_id=room_id, user=target_user)
|
member = RoomMember(room_id=room_id, user=target_user)
|
||||||
|
|
||||||
|
yield self._stopped_typing(member)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def user_left_room(self, user, room_id):
|
||||||
|
if user.is_mine:
|
||||||
|
member = RoomMember(room_id=room_id, user=user)
|
||||||
|
yield self._stopped_typing(member)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _stopped_typing(self, member):
|
||||||
if member not in self._member_typing_until:
|
if member not in self._member_typing_until:
|
||||||
# No point
|
# No point
|
||||||
defer.returnValue(None)
|
defer.returnValue(None)
|
||||||
|
|
||||||
yield self._push_update(
|
yield self._push_update(
|
||||||
room_id=room_id,
|
room_id=member.room_id,
|
||||||
user=target_user,
|
user=member.user,
|
||||||
typing=False,
|
typing=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
del self._member_typing_until[member]
|
||||||
|
|
||||||
|
self.clock.cancel_call_later(self._member_typing_timer[member])
|
||||||
|
del self._member_typing_timer[member]
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _push_update(self, room_id, user, typing):
|
def _push_update(self, room_id, user, typing):
|
||||||
localusers = set()
|
localusers = set()
|
||||||
|
@ -97,16 +146,14 @@ class TypingNotificationHandler(BaseHandler):
|
||||||
|
|
||||||
rm_handler = self.homeserver.get_handlers().room_member_handler
|
rm_handler = self.homeserver.get_handlers().room_member_handler
|
||||||
yield rm_handler.fetch_room_distributions_into(
|
yield rm_handler.fetch_room_distributions_into(
|
||||||
room_id, localusers=localusers, remotedomains=remotedomains,
|
room_id, localusers=localusers, remotedomains=remotedomains
|
||||||
ignore_user=user
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for u in localusers:
|
if localusers:
|
||||||
self.push_update_to_clients(
|
self._push_update_local(
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
observer_user=u,
|
user=user,
|
||||||
observed_user=user,
|
typing=typing
|
||||||
typing=typing,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
deferreds = []
|
deferreds = []
|
||||||
|
@ -135,29 +182,67 @@ class TypingNotificationHandler(BaseHandler):
|
||||||
room_id, localusers=localusers
|
room_id, localusers=localusers
|
||||||
)
|
)
|
||||||
|
|
||||||
for u in localusers:
|
if localusers:
|
||||||
self.push_update_to_clients(
|
self._push_update_local(
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
observer_user=u,
|
user=user,
|
||||||
observed_user=user,
|
|
||||||
typing=content["typing"]
|
typing=content["typing"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def push_update_to_clients(self, room_id, observer_user, observed_user,
|
def _push_update_local(self, room_id, user, typing):
|
||||||
typing):
|
if room_id not in self._room_serials:
|
||||||
# TODO(paul) steal this from presence.py
|
self._room_serials[room_id] = 0
|
||||||
pass
|
self._room_typing[room_id] = set()
|
||||||
|
|
||||||
|
room_set = self._room_typing[room_id]
|
||||||
|
if typing:
|
||||||
|
room_set.add(user)
|
||||||
|
elif user in room_set:
|
||||||
|
room_set.remove(user)
|
||||||
|
|
||||||
|
self._latest_room_serial += 1
|
||||||
|
self._room_serials[room_id] = self._latest_room_serial
|
||||||
|
|
||||||
|
self.notifier.on_new_user_event(rooms=[room_id])
|
||||||
|
|
||||||
|
|
||||||
class TypingNotificationEventSource(object):
|
class TypingNotificationEventSource(object):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
|
self._handler = None
|
||||||
|
|
||||||
|
def handler(self):
|
||||||
|
# Avoid cyclic dependency in handler setup
|
||||||
|
if not self._handler:
|
||||||
|
self._handler = self.hs.get_handlers().typing_notification_handler
|
||||||
|
return self._handler
|
||||||
|
|
||||||
|
def _make_event_for(self, room_id):
|
||||||
|
typing = self.handler()._room_typing[room_id]
|
||||||
|
return {
|
||||||
|
"type": "m.typing",
|
||||||
|
"room_id": room_id,
|
||||||
|
"content": {
|
||||||
|
"user_ids": [u.to_string() for u in typing],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def get_new_events_for_user(self, user, from_key, limit):
|
def get_new_events_for_user(self, user, from_key, limit):
|
||||||
return ([], from_key)
|
from_key = int(from_key)
|
||||||
|
handler = self.handler()
|
||||||
|
|
||||||
|
events = []
|
||||||
|
for room_id in handler._room_serials:
|
||||||
|
if handler._room_serials[room_id] <= from_key:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# TODO: check if user is in room
|
||||||
|
events.append(self._make_event_for(room_id))
|
||||||
|
|
||||||
|
return (events, handler._latest_room_serial)
|
||||||
|
|
||||||
def get_current_key(self):
|
def get_current_key(self):
|
||||||
return 0
|
return self.handler()._latest_room_serial
|
||||||
|
|
||||||
def get_pagination_rows(self, user, pagination_config, key):
|
def get_pagination_rows(self, user, pagination_config, key):
|
||||||
return ([], pagination_config.from_key)
|
return ([], pagination_config.from_key)
|
||||||
|
|
|
@ -26,7 +26,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MediaRepositoryResource(Resource):
|
class MediaRepositoryResource(Resource):
|
||||||
"""Profiles file uploading and downloading.
|
"""File uploading and downloading.
|
||||||
|
|
||||||
Uploads are POSTed to a resource which returns a token which is used to GET
|
Uploads are POSTed to a resource which returns a token which is used to GET
|
||||||
the download::
|
the download::
|
||||||
|
@ -39,9 +39,9 @@ class MediaRepositoryResource(Resource):
|
||||||
<= HTTP/1.1 200 OK
|
<= HTTP/1.1 200 OK
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{ "token": <media-id> }
|
{ "content-uri": "mxc://<server-name>/<media-id>" }
|
||||||
|
|
||||||
=> GET /_matrix/media/v1/download/<media-id> HTTP/1.1
|
=> GET /_matrix/media/v1/download/<server-name>/<media-id> HTTP/1.1
|
||||||
|
|
||||||
<= HTTP/1.1 200 OK
|
<= HTTP/1.1 200 OK
|
||||||
Content-Type: <media-type>
|
Content-Type: <media-type>
|
||||||
|
@ -52,8 +52,8 @@ class MediaRepositoryResource(Resource):
|
||||||
Clients can get thumbnails by supplying a desired width and height and
|
Clients can get thumbnails by supplying a desired width and height and
|
||||||
thumbnailing method::
|
thumbnailing method::
|
||||||
|
|
||||||
=> GET /_matrix/media/v1
|
=> GET /_matrix/media/v1/thumbnail/<server_name>
|
||||||
/thumbnail/<media-id>?width=<w>&height=<h>&method=<m> HTTP/1.1
|
/<media-id>?width=<w>&height=<h>&method=<m> HTTP/1.1
|
||||||
|
|
||||||
<= HTTP/1.1 200 OK
|
<= HTTP/1.1 200 OK
|
||||||
Content-Type: image/jpeg or image/png
|
Content-Type: image/jpeg or image/png
|
||||||
|
|
|
@ -95,8 +95,10 @@ class UploadResource(BaseMediaResource):
|
||||||
|
|
||||||
yield self._generate_local_thumbnails(media_id, media_info)
|
yield self._generate_local_thumbnails(media_id, media_info)
|
||||||
|
|
||||||
|
content_uri = "mxc://%s/%s" % (self.server_name, media_id)
|
||||||
|
|
||||||
respond_with_json(
|
respond_with_json(
|
||||||
request, 200, {"content_token": media_id}, send_cors=True
|
request, 200, {"content_uri": content_uri}, send_cors=True
|
||||||
)
|
)
|
||||||
except CodeMessageException as e:
|
except CodeMessageException as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
|
@ -466,6 +466,37 @@ class RoomRedactEventRestServlet(RestServlet):
|
||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
|
||||||
|
class RoomTypingRestServlet(RestServlet):
|
||||||
|
PATTERN = client_path_pattern("/rooms/(?P<room_id>[^/]*)/typing/(?P<user_id>[^/]*)$")
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def on_PUT(self, request, room_id, user_id):
|
||||||
|
auth_user = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
|
room_id = urllib.unquote(room_id)
|
||||||
|
target_user = self.hs.parse_userid(urllib.unquote(user_id))
|
||||||
|
|
||||||
|
content = _parse_json(request)
|
||||||
|
|
||||||
|
typing_handler = self.handlers.typing_notification_handler
|
||||||
|
|
||||||
|
if content["typing"]:
|
||||||
|
yield typing_handler.started_typing(
|
||||||
|
target_user=target_user,
|
||||||
|
auth_user=auth_user,
|
||||||
|
room_id=room_id,
|
||||||
|
timeout=content.get("timeout", 30000),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
yield typing_handler.stopped_typing(
|
||||||
|
target_user=target_user,
|
||||||
|
auth_user=auth_user,
|
||||||
|
room_id=room_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
defer.returnValue((200, {}))
|
||||||
|
|
||||||
|
|
||||||
def _parse_json(request):
|
def _parse_json(request):
|
||||||
try:
|
try:
|
||||||
content = json.loads(request.content.read())
|
content = json.loads(request.content.read())
|
||||||
|
@ -521,3 +552,4 @@ def register_servlets(hs, http_server):
|
||||||
RoomStateRestServlet(hs).register(http_server)
|
RoomStateRestServlet(hs).register(http_server)
|
||||||
RoomInitialSyncRestServlet(hs).register(http_server)
|
RoomInitialSyncRestServlet(hs).register(http_server)
|
||||||
RoomRedactEventRestServlet(hs).register(http_server)
|
RoomRedactEventRestServlet(hs).register(http_server)
|
||||||
|
RoomTypingRestServlet(hs).register(http_server)
|
||||||
|
|
|
@ -247,6 +247,60 @@ class RoomMemberHandlerTestCase(unittest.TestCase):
|
||||||
|
|
||||||
return builder.build()
|
return builder.build()
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_simple_leave(self):
|
||||||
|
room_id = "!foo:red"
|
||||||
|
user_id = "@bob:red"
|
||||||
|
user = self.hs.parse_userid(user_id)
|
||||||
|
|
||||||
|
builder = self.hs.get_event_builder_factory().new({
|
||||||
|
"type": RoomMemberEvent.TYPE,
|
||||||
|
"sender": user_id,
|
||||||
|
"state_key": user_id,
|
||||||
|
"room_id": room_id,
|
||||||
|
"content": {"membership": Membership.LEAVE},
|
||||||
|
})
|
||||||
|
|
||||||
|
self.datastore.get_latest_events_in_room.return_value = (
|
||||||
|
defer.succeed([])
|
||||||
|
)
|
||||||
|
|
||||||
|
def annotate(_, ctx):
|
||||||
|
ctx.current_state = {
|
||||||
|
(RoomMemberEvent.TYPE, "@bob:red"): self._create_member(
|
||||||
|
user_id="@bob:red",
|
||||||
|
room_id=room_id,
|
||||||
|
membership=Membership.INVITE
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return defer.succeed(True)
|
||||||
|
|
||||||
|
self.state_handler.annotate_context_with_state.side_effect = annotate
|
||||||
|
|
||||||
|
def add_auth(_, ctx):
|
||||||
|
ctx.auth_events = ctx.current_state[
|
||||||
|
(RoomMemberEvent.TYPE, "@bob:red")
|
||||||
|
]
|
||||||
|
|
||||||
|
return defer.succeed(True)
|
||||||
|
self.auth.add_auth_events.side_effect = add_auth
|
||||||
|
|
||||||
|
room_handler = self.room_member_handler
|
||||||
|
event, context = yield room_handler._create_new_client_event(
|
||||||
|
builder
|
||||||
|
)
|
||||||
|
|
||||||
|
leave_signal_observer = Mock()
|
||||||
|
self.distributor.observe("user_left_room", leave_signal_observer)
|
||||||
|
|
||||||
|
# Actual invocation
|
||||||
|
yield room_handler.change_membership(event, context)
|
||||||
|
|
||||||
|
leave_signal_observer.assert_called_with(
|
||||||
|
user=user, room_id=room_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RoomCreationTest(unittest.TestCase):
|
class RoomCreationTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import json
|
||||||
|
|
||||||
from ..utils import MockHttpResource, MockClock, DeferredMockCallable, MockKey
|
from ..utils import MockHttpResource, MockClock, DeferredMockCallable, MockKey
|
||||||
|
|
||||||
|
from synapse.api.errors import AuthError
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.handlers.typing import TypingNotificationHandler
|
from synapse.handlers.typing import TypingNotificationHandler
|
||||||
|
|
||||||
|
@ -65,7 +66,13 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
self.mock_config = Mock()
|
self.mock_config = Mock()
|
||||||
self.mock_config.signing_key = [MockKey()]
|
self.mock_config.signing_key = [MockKey()]
|
||||||
|
|
||||||
|
mock_notifier = Mock(spec=["on_new_user_event"])
|
||||||
|
self.on_new_user_event = mock_notifier.on_new_user_event
|
||||||
|
|
||||||
|
self.auth = Mock(spec=[])
|
||||||
|
|
||||||
hs = HomeServer("test",
|
hs = HomeServer("test",
|
||||||
|
auth=self.auth,
|
||||||
clock=self.clock,
|
clock=self.clock,
|
||||||
db_pool=None,
|
db_pool=None,
|
||||||
datastore=Mock(spec=[
|
datastore=Mock(spec=[
|
||||||
|
@ -77,6 +84,7 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
"get_destination_retry_timings",
|
"get_destination_retry_timings",
|
||||||
]),
|
]),
|
||||||
handlers=None,
|
handlers=None,
|
||||||
|
notifier=mock_notifier,
|
||||||
resource_for_client=Mock(),
|
resource_for_client=Mock(),
|
||||||
resource_for_federation=self.mock_federation_resource,
|
resource_for_federation=self.mock_federation_resource,
|
||||||
http_client=self.mock_http_client,
|
http_client=self.mock_http_client,
|
||||||
|
@ -85,11 +93,9 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
)
|
)
|
||||||
hs.handlers = JustTypingNotificationHandlers(hs)
|
hs.handlers = JustTypingNotificationHandlers(hs)
|
||||||
|
|
||||||
self.mock_update_client = Mock()
|
|
||||||
self.mock_update_client.return_value = defer.succeed(None)
|
|
||||||
|
|
||||||
self.handler = hs.get_handlers().typing_notification_handler
|
self.handler = hs.get_handlers().typing_notification_handler
|
||||||
self.handler.push_update_to_clients = self.mock_update_client
|
|
||||||
|
self.event_source = hs.get_event_sources().sources["typing"]
|
||||||
|
|
||||||
self.datastore = hs.get_datastore()
|
self.datastore = hs.get_datastore()
|
||||||
self.datastore.get_destination_retry_timings.return_value = (
|
self.datastore.get_destination_retry_timings.return_value = (
|
||||||
|
@ -140,6 +146,12 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
self.room_member_handler.fetch_room_distributions_into = (
|
self.room_member_handler.fetch_room_distributions_into = (
|
||||||
fetch_room_distributions_into)
|
fetch_room_distributions_into)
|
||||||
|
|
||||||
|
def check_joined_room(room_id, user_id):
|
||||||
|
if user_id not in [u.to_string() for u in self.room_members]:
|
||||||
|
raise AuthError(401, "User is not in the room")
|
||||||
|
|
||||||
|
self.auth.check_joined_room = check_joined_room
|
||||||
|
|
||||||
# Some local users to test with
|
# Some local users to test with
|
||||||
self.u_apple = hs.parse_userid("@apple:test")
|
self.u_apple = hs.parse_userid("@apple:test")
|
||||||
self.u_banana = hs.parse_userid("@banana:test")
|
self.u_banana = hs.parse_userid("@banana:test")
|
||||||
|
@ -151,6 +163,8 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
def test_started_typing_local(self):
|
def test_started_typing_local(self):
|
||||||
self.room_members = [self.u_apple, self.u_banana]
|
self.room_members = [self.u_apple, self.u_banana]
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 0)
|
||||||
|
|
||||||
yield self.handler.started_typing(
|
yield self.handler.started_typing(
|
||||||
target_user=self.u_apple,
|
target_user=self.u_apple,
|
||||||
auth_user=self.u_apple,
|
auth_user=self.u_apple,
|
||||||
|
@ -158,13 +172,22 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
timeout=20000,
|
timeout=20000,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mock_update_client.assert_has_calls([
|
self.on_new_user_event.assert_has_calls([
|
||||||
call(observer_user=self.u_banana,
|
call(rooms=[self.room_id]),
|
||||||
observed_user=self.u_apple,
|
|
||||||
room_id=self.room_id,
|
|
||||||
typing=True),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 1)
|
||||||
|
self.assertEquals(
|
||||||
|
self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
|
||||||
|
[
|
||||||
|
{"type": "m.typing",
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"content": {
|
||||||
|
"user_ids": [self.u_apple.to_string()],
|
||||||
|
}},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_started_typing_remote_send(self):
|
def test_started_typing_remote_send(self):
|
||||||
self.room_members = [self.u_apple, self.u_onion]
|
self.room_members = [self.u_apple, self.u_onion]
|
||||||
|
@ -198,6 +221,8 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
def test_started_typing_remote_recv(self):
|
def test_started_typing_remote_recv(self):
|
||||||
self.room_members = [self.u_apple, self.u_onion]
|
self.room_members = [self.u_apple, self.u_onion]
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 0)
|
||||||
|
|
||||||
yield self.mock_federation_resource.trigger("PUT",
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
"/_matrix/federation/v1/send/1000000/",
|
"/_matrix/federation/v1/send/1000000/",
|
||||||
_make_edu_json("farm", "m.typing",
|
_make_edu_json("farm", "m.typing",
|
||||||
|
@ -209,13 +234,22 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mock_update_client.assert_has_calls([
|
self.on_new_user_event.assert_has_calls([
|
||||||
call(observer_user=self.u_apple,
|
call(rooms=[self.room_id]),
|
||||||
observed_user=self.u_onion,
|
|
||||||
room_id=self.room_id,
|
|
||||||
typing=True),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 1)
|
||||||
|
self.assertEquals(
|
||||||
|
self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
|
||||||
|
[
|
||||||
|
{"type": "m.typing",
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"content": {
|
||||||
|
"user_ids": [self.u_onion.to_string()],
|
||||||
|
}},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_stopped_typing(self):
|
def test_stopped_typing(self):
|
||||||
self.room_members = [self.u_apple, self.u_banana, self.u_onion]
|
self.room_members = [self.u_apple, self.u_banana, self.u_onion]
|
||||||
|
@ -238,9 +272,14 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
|
|
||||||
# Gut-wrenching
|
# Gut-wrenching
|
||||||
from synapse.handlers.typing import RoomMember
|
from synapse.handlers.typing import RoomMember
|
||||||
self.handler._member_typing_until[
|
member = RoomMember(self.room_id, self.u_apple)
|
||||||
RoomMember(self.room_id, self.u_apple)
|
self.handler._member_typing_until[member] = 1002000
|
||||||
] = 1002000
|
self.handler._member_typing_timer[member] = (
|
||||||
|
self.clock.call_later(1002, lambda: 0)
|
||||||
|
)
|
||||||
|
self.handler._room_typing[self.room_id] = set((self.u_apple,))
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 0)
|
||||||
|
|
||||||
yield self.handler.stopped_typing(
|
yield self.handler.stopped_typing(
|
||||||
target_user=self.u_apple,
|
target_user=self.u_apple,
|
||||||
|
@ -248,11 +287,68 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
room_id=self.room_id,
|
room_id=self.room_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mock_update_client.assert_has_calls([
|
self.on_new_user_event.assert_has_calls([
|
||||||
call(observer_user=self.u_banana,
|
call(rooms=[self.room_id]),
|
||||||
observed_user=self.u_apple,
|
|
||||||
room_id=self.room_id,
|
|
||||||
typing=False),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
yield put_json.await_calls()
|
yield put_json.await_calls()
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 1)
|
||||||
|
self.assertEquals(
|
||||||
|
self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
|
||||||
|
[
|
||||||
|
{"type": "m.typing",
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"content": {
|
||||||
|
"user_ids": [],
|
||||||
|
}},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_typing_timeout(self):
|
||||||
|
self.room_members = [self.u_apple, self.u_banana]
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 0)
|
||||||
|
|
||||||
|
yield self.handler.started_typing(
|
||||||
|
target_user=self.u_apple,
|
||||||
|
auth_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
timeout=10000,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.on_new_user_event.assert_has_calls([
|
||||||
|
call(rooms=[self.room_id]),
|
||||||
|
])
|
||||||
|
self.on_new_user_event.reset_mock()
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 1)
|
||||||
|
self.assertEquals(
|
||||||
|
self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
|
||||||
|
[
|
||||||
|
{"type": "m.typing",
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"content": {
|
||||||
|
"user_ids": [self.u_apple.to_string()],
|
||||||
|
}},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.clock.advance_time(11)
|
||||||
|
|
||||||
|
self.on_new_user_event.assert_has_calls([
|
||||||
|
call(rooms=[self.room_id]),
|
||||||
|
])
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 2)
|
||||||
|
self.assertEquals(
|
||||||
|
self.event_source.get_new_events_for_user(self.u_apple, 1, None)[0],
|
||||||
|
[
|
||||||
|
{"type": "m.typing",
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"content": {
|
||||||
|
"user_ids": [],
|
||||||
|
}},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
|
@ -1066,7 +1066,3 @@ class RoomInitialSyncTestCase(RestTestCase):
|
||||||
}
|
}
|
||||||
self.assertTrue(self.user_id in presence_by_user)
|
self.assertTrue(self.user_id in presence_by_user)
|
||||||
self.assertEquals("m.presence", presence_by_user[self.user_id]["type"])
|
self.assertEquals("m.presence", presence_by_user[self.user_id]["type"])
|
||||||
|
|
||||||
# (code, response) = yield self.mock_resource.trigger("GET", path, None)
|
|
||||||
# self.assertEquals(200, code, msg=str(response))
|
|
||||||
# self.assert_dict(json.loads(content), response)
|
|
||||||
|
|
115
tests/rest/test_typing.py
Normal file
115
tests/rest/test_typing.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Tests REST events for /rooms paths."""
|
||||||
|
|
||||||
|
# twisted imports
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
import synapse.rest.room
|
||||||
|
from synapse.server import HomeServer
|
||||||
|
|
||||||
|
from ..utils import MockHttpResource, SQLiteMemoryDbPool, MockKey
|
||||||
|
from .utils import RestTestCase
|
||||||
|
|
||||||
|
from mock import Mock, NonCallableMock
|
||||||
|
|
||||||
|
|
||||||
|
PATH_PREFIX = "/_matrix/client/api/v1"
|
||||||
|
|
||||||
|
|
||||||
|
class RoomTypingTestCase(RestTestCase):
|
||||||
|
""" Tests /rooms/$room_id/typing/$user_id REST API. """
|
||||||
|
user_id = "@sid:red"
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def setUp(self):
|
||||||
|
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
||||||
|
self.auth_user_id = self.user_id
|
||||||
|
|
||||||
|
self.mock_config = NonCallableMock()
|
||||||
|
self.mock_config.signing_key = [MockKey()]
|
||||||
|
|
||||||
|
db_pool = SQLiteMemoryDbPool()
|
||||||
|
yield db_pool.prepare()
|
||||||
|
|
||||||
|
hs = HomeServer(
|
||||||
|
"red",
|
||||||
|
db_pool=db_pool,
|
||||||
|
http_client=None,
|
||||||
|
replication_layer=Mock(),
|
||||||
|
ratelimiter=NonCallableMock(spec_set=[
|
||||||
|
"send_message",
|
||||||
|
]),
|
||||||
|
config=self.mock_config,
|
||||||
|
)
|
||||||
|
self.hs = hs
|
||||||
|
|
||||||
|
self.event_source = hs.get_event_sources().sources["typing"]
|
||||||
|
|
||||||
|
self.ratelimiter = hs.get_ratelimiter()
|
||||||
|
self.ratelimiter.send_message.return_value = (True, 0)
|
||||||
|
|
||||||
|
hs.get_handlers().federation_handler = Mock()
|
||||||
|
|
||||||
|
def _get_user_by_token(token=None):
|
||||||
|
return {
|
||||||
|
"user": hs.parse_userid(self.auth_user_id),
|
||||||
|
"admin": False,
|
||||||
|
"device_id": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
hs.get_auth().get_user_by_token = _get_user_by_token
|
||||||
|
|
||||||
|
def _insert_client_ip(*args, **kwargs):
|
||||||
|
return defer.succeed(None)
|
||||||
|
hs.get_datastore().insert_client_ip = _insert_client_ip
|
||||||
|
|
||||||
|
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
||||||
|
|
||||||
|
self.room_id = yield self.create_room_as(self.user_id)
|
||||||
|
# Need another user to make notifications actually work
|
||||||
|
yield self.join(self.room_id, user="@jim:red")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.hs.get_handlers().typing_notification_handler.tearDown()
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_set_typing(self):
|
||||||
|
(code, _) = yield self.mock_resource.trigger("PUT",
|
||||||
|
"/rooms/%s/typing/%s" % (self.room_id, self.user_id),
|
||||||
|
'{"typing": true, "timeout": 30000}'
|
||||||
|
)
|
||||||
|
self.assertEquals(200, code)
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 1)
|
||||||
|
self.assertEquals(
|
||||||
|
self.event_source.get_new_events_for_user(self.user_id, 0, None)[0],
|
||||||
|
[
|
||||||
|
{"type": "m.typing",
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"content": {
|
||||||
|
"user_ids": [self.user_id],
|
||||||
|
}},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_set_not_typing(self):
|
||||||
|
(code, _) = yield self.mock_resource.trigger("PUT",
|
||||||
|
"/rooms/%s/typing/%s" % (self.room_id, self.user_id),
|
||||||
|
'{"typing": false}'
|
||||||
|
)
|
||||||
|
self.assertEquals(200, code)
|
Loading…
Reference in a new issue