Merge branch 'master' of github.com:matrix-org/synapse into sql_refactor

Conflicts:
	tests/rest/test_presence.py
	tests/rest/test_rooms.py
	tests/utils.py
paul/schema_breaking_changes
Erik Johnston 2014-08-19 14:48:19 +01:00
commit 347242a5c4
29 changed files with 1258 additions and 393 deletions

View File

@ -0,0 +1,285 @@
TODO(kegan): Tweak joinalias API keys/path? Event stream historical > live needs
a token (currently doesn't). im/sync responses include outdated event formats
(room membership change messages). Room config (specifically: message history,
public rooms). /register seems super simplistic compared to /login, maybe it
would be better if /register used the same technique as /login?
How to use the client-server API
================================
This guide focuses on how the client-server APIs *provided by the reference
home server* can be used. Since this is specific to a home server
implementation, there may be variations in relation to registering/logging in
which are not covered in extensive detail in this guide.
If you haven't already, get a home server up and running on
``http://localhost:8080``.
Accounts
========
Before you can send and receive messages, you must **register** for an account.
If you already have an account, you must **login** into it.
Registration
------------
The aim of registration is to get a user ID and access token which you will need
when accessing other APIs::
curl -XPOST -d '{"user_id":"example", "password":"wordpass"}' "http://localhost:8080/matrix/client/api/v1/register"
{
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
"home_server": "localhost",
"user_id": "@example:localhost"
}
NB: If a ``user_id`` is not specified, one will be randomly generated for you.
If you do not specify a ``password``, you will be unable to login to the account
if you forget the ``access_token``.
Implementation note: The matrix specification does not enforce how users
register with a server. It just specifies the URL path and absolute minimum
keys. The reference home server uses a username/password to authenticate user,
but other home servers may use different methods.
Login
-----
The aim when logging in is to get an access token for your existing user ID::
curl -XGET "http://localhost:8080/matrix/client/api/v1/login"
{
"type": "m.login.password"
}
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8080/matrix/client/api/v1/login"
{
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd",
"home_server": "localhost",
"user_id": "@example:localhost"
}
Implementation note: Different home servers may implement different methods for
logging in to an existing account. In order to check that you know how to login
to this home server, you must perform a ``GET`` first and make sure you
recognise the login type. If you do not know how to login, you can
``GET /login/fallback`` which will return a basic webpage which you can use to
login. The reference home server implementation support username/password login,
but other home servers may support different login methods (e.g. OAuth2).
Communicating
=============
In order to communicate with another user, you must **create a room** with that
user and **send a message** to that room.
Creating a room
---------------
If you want to send a message to someone, you have to be in a room with them. To
create a room::
curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8080/matrix/client/api/v1/rooms?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
{
"room_alias": "#tutorial:localhost",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
}
The "room alias" is a human-readable string which can be shared with other users
so they can join a room, rather than the room ID which is a randomly generated
string. You can have multiple room aliases per room.
TODO(kegan): How to add/remove aliases from an existing room.
Sending messages
----------------
You can now send messages to this room::
curl -XPUT -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8080/matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/messages/%40example%3Alocalhost/msgid1?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
NB: There are no limitations to the types of messages which can be exchanged.
The only requirement is that ``"msgtype"`` is specified.
NB: Depending on the room config, users who join the room may be able to see
message history from before they joined.
Users and rooms
===============
Each room can be configured to allow or disallow certain rules. In particular,
these rules may specify if you require an **invitation** from someone already in
the room in order to **join the room**. In addition, you may also be able to
join a room **via a room alias** if one was set up.
Inviting a user to a room
-------------------------
You can directly invite a user to a room like so::
curl -XPUT -d '{"membership":"invite"}' "http://localhost:8080/matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/members/%40myfriend%3Alocalhost/state?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
This informs ``@myfriend:localhost`` of the room ID
``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room.
Joining a room via an invite
----------------------------
If you receive an invite, you can join the room by changing the membership to
join::
curl -XPUT -d '{"membership":"join"}' "http://localhost:8080/matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/members/%40myfriend%3Alocalhost/state?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
NB: Only the person invited (``@myfriend:localhost``) can change the membership
state to ``"join"``.
Joining a room via an alias
---------------------------
Alternatively, if you know the room alias for this room and the room config
allows it, you can directly join a room via the alias::
curl -XPUT -d '{}' "http://localhost:8080/matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
{
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
}
You will need to use the room ID when sending messages, not the room alias.
NB: If the room is configured to be an invite-only room, you will still require
an invite in order to join the room even though you know the room alias. As a
result, it is more common to see a room alias in relation to a public room,
which do not require invitations.
Getting events
==============
An event is some interesting piece of data that a client may be interested in.
It can be a message in a room, a room invite, etc. There are many different ways
of getting events, depending on what the client already knows.
Getting all state
-----------------
If the client doesn't know any information on the rooms the user is
invited/joined on, they can get all the user's state for all rooms::
curl -XGET "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
[
{
"membership": "join",
"messages": {
"chunk": [
{
"content": {
"body": "@example:localhost joined the room.",
"hsob_ts": 1408444664249,
"membership": "join",
"membership_source": "@example:localhost",
"membership_target": "@example:localhost",
"msgtype": "m.text"
},
"event_id": "lZjmmlrEvo",
"msg_id": "m1408444664249",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"user_id": "_homeserver_"
},
{
"content": {
"body": "hello",
"hsob_ts": 1408445405672,
"msgtype": "m.text"
},
"event_id": "BiBJqamISg",
"msg_id": "msgid1",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"user_id": "@example:localhost"
},
[...]
{
"content": {
"body": "@myfriend:localhost joined the room.",
"hsob_ts": 1408446501661,
"membership": "join",
"membership_source": "@myfriend:localhost",
"membership_target": "@myfriend:localhost",
"msgtype": "m.text"
},
"event_id": "IMmXbOzFAa",
"msg_id": "m1408446501661",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"user_id": "_homeserver_"
}
],
"end": "20",
"start": "0"
},
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
}
]
This returns all the room IDs of rooms the user is invited/joined on, as well as
all of the messages and feedback for these rooms. This can be a LOT of data. You
may just want the most recent message for each room. This can be achieved by
applying pagination stream parameters to this request::
curl -XGET "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK&from=END&to=START&limit=1"
[
{
"membership": "join",
"messages": {
"chunk": [
{
"content": {
"body": "@myfriend:localhost joined the room.",
"hsob_ts": 1408446501661,
"membership": "join",
"membership_source": "@myfriend:localhost",
"membership_target": "@myfriend:localhost",
"msgtype": "m.text"
},
"event_id": "IMmXbOzFAa",
"msg_id": "m1408446501661",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"user_id": "_homeserver_"
}
],
"end": "20",
"start": "21"
},
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
}
]
Getting live state
------------------
Once you know which rooms the client has previously interacted with, you need to
listen for incoming events. This can be done like so::
curl -XGET "http://localhost:8080/matrix/client/api/v1/events?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK&from=END"
{
"chunk": [],
"end": "215",
"start": "215"
}
This will block waiting for an incoming event, timing out after several seconds.
Even if there are no new events (as in the example above), there will be some
pagination stream response keys. The client should make subsequent requests
using the value of the ``"end"`` key (in this case ``215``) as the ``from``
query parameter. This value should be stored so when the client reopens your app
after a period of inactivity, you can resume from where you got up to in the
event stream. If it has been a long period of inactivity, there may be LOTS of
events waiting for the user. In this case, you may wish to get all state instead
and then resume getting live state from a newer end token.
NB: The timeout can be changed by adding a ``timeout`` query parameter, which is
in milliseconds. A timeout of 0 will not block.

View File

@ -17,4 +17,5 @@
CLIENT_PREFIX = "/matrix/client/api/v1"
FEDERATION_PREFIX = "/matrix/federation/v1"
WEB_CLIENT_PREFIX = "/matrix/client"
WEB_CLIENT_PREFIX = "/matrix/client"
CONTENT_REPO_PREFIX = "/matrix/content"

View File

@ -24,9 +24,11 @@ from twisted.python.log import PythonLoggingObserver
from twisted.web.resource import Resource
from twisted.web.static import File
from twisted.web.server import Site
from synapse.http.server import JsonResource, RootRedirect
from synapse.http.server import JsonResource, RootRedirect, ContentRepoResource
from synapse.http.client import TwistedHttpClient
from synapse.api.urls import CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX
from synapse.api.urls import (
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX
)
from daemonize import Daemonize
@ -53,6 +55,9 @@ class SynapseHomeServer(HomeServer):
def build_resource_for_web_client(self):
return File("webclient") # TODO configurable?
def build_resource_for_content_repo(self):
return ContentRepoResource("uploads", self.auth)
def build_db_pool(self):
""" Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we
don't have to worry about overwriting existing content.
@ -101,7 +106,8 @@ class SynapseHomeServer(HomeServer):
# [ ("/aaa/bbb/cc", Resource1), ("/aaa/dummy", Resource2) ]
desired_tree = [
(CLIENT_PREFIX, self.get_resource_for_client()),
(FEDERATION_PREFIX, self.get_resource_for_federation())
(FEDERATION_PREFIX, self.get_resource_for_federation()),
(CONTENT_REPO_PREFIX, self.get_resource_for_content_repo())
]
if web_client:
logger.info("Adding the web client.")

View File

@ -158,6 +158,7 @@ class ReplicationLayer(object):
# TODO, add errback, etc.
self._transaction_queue.enqueue_edu(edu)
return defer.succeed(None)
@log_function
def make_query(self, destination, query_type, args):

View File

@ -383,7 +383,7 @@ class PresenceHandler(BaseHandler):
logger.debug("Start polling for presence from %s", user)
if target_user:
target_users = set(target_user)
target_users = set([target_user])
else:
presence = yield self.store.get_presence_list(
user.localpart, accepted=True
@ -463,9 +463,13 @@ class PresenceHandler(BaseHandler):
deferreds = []
if target_user:
raise NotImplementedError("TODO: remove one user")
if target_user not in self._remote_recvmap:
return
target_users = set([target_user])
else:
target_users = self._remote_recvmap.keys()
remoteusers = [u for u in self._remote_recvmap
remoteusers = [u for u in target_users
if user in self._remote_recvmap[u]]
remoteusers_by_domain = partition(remoteusers, lambda u: u.domain)

View File

@ -17,16 +17,23 @@
from syutil.jsonutil import (
encode_canonical_json, encode_pretty_printed_json
)
from synapse.api.errors import cs_exception, CodeMessageException
from synapse.api.errors import (
cs_exception, SynapseError, CodeMessageException, Codes, cs_error
)
from synapse.util.stringutils import random_string
from twisted.internet import defer, reactor
from twisted.protocols.basic import FileSender
from twisted.web import server, resource
from twisted.web.server import NOT_DONE_YET
from twisted.web.util import redirectTo
import base64
import collections
import json
import logging
import os
import re
logger = logging.getLogger(__name__)
@ -125,7 +132,11 @@ class JsonResource(HttpServer, resource.Resource):
{"error": "Unrecognized request"}
)
except CodeMessageException as e:
logger.exception(e)
if isinstance(e, SynapseError):
logger.error("%s SynapseError: %s - %s", request, e.code,
e.msg)
else:
logger.exception(e)
self._send_response(
request,
e.code,
@ -140,6 +151,14 @@ class JsonResource(HttpServer, resource.Resource):
)
def _send_response(self, request, code, response_json_object):
# could alternatively use request.notifyFinish() and flip a flag when
# the Deferred fires, but since the flag is RIGHT THERE it seems like
# a waste.
if request._disconnected:
logger.warn(
"Not sending response to request %s, already disconnected.",
request)
return
if not self._request_user_agent_is_curl(request):
json_bytes = encode_canonical_json(response_json_object)
@ -176,6 +195,141 @@ class RootRedirect(resource.Resource):
return resource.Resource.getChild(self, name, request)
class ContentRepoResource(resource.Resource):
"""Provides file uploading and downloading.
Uploads are POSTed to wherever this Resource is linked to. This resource
returns a "content token" which can be used to GET this content again. The
token is typically a path, but it may not be. Tokens can expire, be one-time
uses, etc.
In this case, the token is a path to the file and contains 3 interesting
sections:
- User ID base64d (for namespacing content to each user)
- random 24 char string
- Content type base64d (so we can return it when clients GET it)
"""
isLeaf = True
def __init__(self, directory, auth):
resource.Resource.__init__(self)
self.directory = directory
self.auth = auth
if not os.path.isdir(self.directory):
os.mkdir(self.directory)
logger.info("ContentRepoResource : Created %s directory.",
self.directory)
@defer.inlineCallbacks
def map_request_to_name(self, request):
# auth the user
auth_user = yield self.auth.get_user_by_req(request)
# namespace all file uploads on the user
prefix = base64.urlsafe_b64encode(
auth_user.to_string()
).replace('=', '')
# use a random string for the main portion
main_part = random_string(24)
# suffix with a file extension if we can make one. This is nice to
# provide a hint to clients on the file information. We will also reuse
# this info to spit back the content type to the client.
suffix = ""
if request.requestHeaders.hasHeader("Content-Type"):
content_type = request.requestHeaders.getRawHeaders(
"Content-Type")[0]
suffix = "." + base64.urlsafe_b64encode(content_type)
if (content_type.split("/")[0].lower() in
["image", "video", "audio"]):
file_ext = content_type.split("/")[-1]
# be a little paranoid and only allow a-z
file_ext = re.sub("[^a-z]", "", file_ext)
suffix += "." + file_ext
file_path = os.path.join(self.directory, prefix + main_part + suffix)
logger.info("User %s is uploading a file to path %s",
auth_user.to_string(),
file_path)
# keep trying to make a non-clashing file, with a sensible max attempts
attempts = 0
while os.path.exists(file_path):
main_part = random_string(24)
file_path = os.path.join(self.directory,
prefix + main_part + suffix)
attempts += 1
if attempts > 25: # really? Really?
raise SynapseError(500, "Unable to create file.")
defer.returnValue(file_path)
def render_GET(self, request):
# no auth here on purpose, to allow anyone to view, even across home
# servers.
# TODO: A little crude here, we could do this better.
filename = request.path.split(self.directory + "/")[1]
# be paranoid
filename = re.sub("[^0-9A-z.-_]", "", filename)
file_path = self.directory + "/" + filename
if os.path.isfile(file_path):
# filename has the content type
base64_contentype = filename.split(".")[1]
content_type = base64.urlsafe_b64decode(base64_contentype)
logger.info("Sending file %s", file_path)
f = open(file_path, 'rb')
request.setHeader('Content-Type', content_type)
d = FileSender().beginFileTransfer(f, request)
# after the file has been sent, clean up and finish the request
def cbFinished(ignored):
f.close()
request.finish()
d.addCallback(cbFinished)
else:
respond_with_json_bytes(
request,
404,
json.dumps(cs_error("Not found", code=Codes.NOT_FOUND)),
send_cors=True)
return server.NOT_DONE_YET
def render_POST(self, request):
self._async_render(request)
return server.NOT_DONE_YET
@defer.inlineCallbacks
def _async_render(self, request):
try:
fname = yield self.map_request_to_name(request)
# TODO I have a suspcious feeling this is just going to block
with open(fname, "wb") as f:
f.write(request.content.read())
respond_with_json_bytes(request, 200,
json.dumps({"content_token": fname}),
send_cors=True)
except CodeMessageException as e:
logger.exception(e)
respond_with_json_bytes(request, e.code,
json.dumps(cs_exception(e)))
except Exception as e:
logger.error("Failed to store file: %s" % e)
respond_with_json_bytes(
request,
500,
json.dumps({"error": "Internal server error"}),
send_cors=True)
def respond_with_json_bytes(request, code, json_bytes, send_cors=False):
"""Sends encoded JSON in response to the given request.

View File

@ -170,8 +170,10 @@ class RoomMemberRestServlet(RestServlet):
user = yield self.auth.get_user_by_req(request)
handler = self.handlers.room_member_handler
member = yield handler.get_room_member(room_id, target_user_id,
user.to_string())
member = yield handler.get_room_member(
room_id,
urllib.unquote(target_user_id),
user.to_string())
if not member:
raise SynapseError(404, "Member not found.",
errcode=Codes.NOT_FOUND)
@ -183,7 +185,7 @@ class RoomMemberRestServlet(RestServlet):
event = self.event_factory.create_event(
etype=self.get_event_type(),
target_user_id=target_user_id,
target_user_id=urllib.unquote(target_user_id),
room_id=urllib.unquote(roomid),
user_id=user.to_string(),
membership=Membership.LEAVE,
@ -210,7 +212,7 @@ class RoomMemberRestServlet(RestServlet):
event = self.event_factory.create_event(
etype=self.get_event_type(),
target_user_id=target_user_id,
target_user_id=urllib.unquote(target_user_id),
room_id=urllib.unquote(roomid),
user_id=user.to_string(),
membership=content["membership"],
@ -218,8 +220,8 @@ class RoomMemberRestServlet(RestServlet):
)
handler = self.handlers.room_member_handler
result = yield handler.change_membership(event, broadcast_msg=True)
defer.returnValue((200, result))
yield handler.change_membership(event, broadcast_msg=True)
defer.returnValue((200, ""))
class MessageRestServlet(RestServlet):
@ -235,7 +237,7 @@ class MessageRestServlet(RestServlet):
msg_handler = self.handlers.message_handler
msg = yield msg_handler.get_message(room_id=urllib.unquote(room_id),
sender_id=sender_id,
sender_id=urllib.unquote(sender_id),
msg_id=msg_id,
user_id=user.to_string(),
)
@ -250,7 +252,7 @@ class MessageRestServlet(RestServlet):
def on_PUT(self, request, room_id, sender_id, msg_id):
user = yield self.auth.get_user_by_req(request)
if user.to_string() != sender_id:
if user.to_string() != urllib.unquote(sender_id):
raise SynapseError(403, "Must send messages as yourself.",
errcode=Codes.FORBIDDEN)

View File

@ -72,6 +72,7 @@ class BaseHomeServer(object):
'resource_for_client',
'resource_for_federation',
'resource_for_web_client',
'resource_for_content_repo',
]
def __init__(self, hostname, **kwargs):
@ -140,6 +141,7 @@ class HomeServer(BaseHomeServer):
resource_for_client
resource_for_web_client
resource_for_federation
resource_for_content_repo
http_client
db_pool
"""

View File

@ -32,6 +32,12 @@ class DomainSpecificString(
HomeServer as being its own
"""
# Deny iteration because it will bite you if you try to create a singleton
# set by:
# users = set(user)
def __iter__(self):
raise ValueError("Attempted to iterate a %s" % (type(self).__name__))
@classmethod
def from_string(cls, s, hs):
"""Parse the string given by 's' into a structure object."""

View File

@ -20,7 +20,7 @@ from twisted.trial import unittest
from mock import Mock
import logging
from ..utils import MockHttpServer, MockClock
from ..utils import MockHttpResource, MockClock
from synapse.server import HomeServer
from synapse.federation import initialize_http_replication
@ -50,7 +50,7 @@ def make_pdu(prev_pdus=[], **kwargs):
class FederationTestCase(unittest.TestCase):
def setUp(self):
self.mock_http_server = MockHttpServer()
self.mock_resource = MockHttpResource()
self.mock_http_client = Mock(spec=[
"get_json",
"put_json",
@ -70,7 +70,7 @@ class FederationTestCase(unittest.TestCase):
)
self.clock = MockClock()
hs = HomeServer("test",
resource_for_federation=self.mock_http_server,
resource_for_federation=self.mock_resource,
http_client=self.mock_http_client,
db_pool=None,
datastore=self.mock_persistence,
@ -86,7 +86,7 @@ class FederationTestCase(unittest.TestCase):
)
# Empty context initially
(code, response) = yield self.mock_http_server.trigger("GET",
(code, response) = yield self.mock_resource.trigger("GET",
"/matrix/federation/v1/state/my-context/", None)
self.assertEquals(200, code)
self.assertFalse(response["pdus"])
@ -111,7 +111,7 @@ class FederationTestCase(unittest.TestCase):
])
)
(code, response) = yield self.mock_http_server.trigger("GET",
(code, response) = yield self.mock_resource.trigger("GET",
"/matrix/federation/v1/state/my-context/", None)
self.assertEquals(200, code)
self.assertEquals(1, len(response["pdus"]))
@ -122,7 +122,7 @@ class FederationTestCase(unittest.TestCase):
defer.succeed(None)
)
(code, response) = yield self.mock_http_server.trigger("GET",
(code, response) = yield self.mock_resource.trigger("GET",
"/matrix/federation/v1/pdu/red/abc123def456/", None)
self.assertEquals(404, code)
@ -141,7 +141,7 @@ class FederationTestCase(unittest.TestCase):
)
)
(code, response) = yield self.mock_http_server.trigger("GET",
(code, response) = yield self.mock_resource.trigger("GET",
"/matrix/federation/v1/pdu/red/abc123def456/", None)
self.assertEquals(200, code)
self.assertEquals(1, len(response["pdus"]))
@ -225,7 +225,7 @@ class FederationTestCase(unittest.TestCase):
self.federation.register_edu_handler("m.test", recv_observer)
yield self.mock_http_server.trigger("PUT",
yield self.mock_resource.trigger("PUT",
"/matrix/federation/v1/send/1001000/",
"""{
"origin": "remote",
@ -272,7 +272,7 @@ class FederationTestCase(unittest.TestCase):
self.federation.register_query_handler("a-question", recv_handler)
code, response = yield self.mock_http_server.trigger("GET",
code, response = yield self.mock_resource.trigger("GET",
"/matrix/federation/v1/query/a-question?three=3&four=4", None)
self.assertEquals(200, code)

View File

@ -19,8 +19,9 @@ from twisted.internet import defer
from mock import Mock, call, ANY
import logging
import json
from ..utils import MockClock
from ..utils import MockHttpResource, MockClock, DeferredMockCallable
from synapse.server import HomeServer
from synapse.api.constants import PresenceState
@ -34,17 +35,27 @@ ONLINE = PresenceState.ONLINE
logging.getLogger().addHandler(logging.NullHandler())
#logging.getLogger().addHandler(logging.StreamHandler())
#logging.getLogger().setLevel(logging.DEBUG)
class MockReplication(object):
def __init__(self):
self.edu_handlers = {}
def _expect_edu(destination, edu_type, content, origin="test"):
return {
"origin": origin,
"ts": 1000000,
"pdus": [],
"edus": [
{
"origin": origin,
"destination": destination,
"edu_type": edu_type,
"content": content,
}
],
}
def register_edu_handler(self, edu_type, handler):
self.edu_handlers[edu_type] = handler
def received_edu(self, origin, edu_type, content):
self.edu_handlers[edu_type](origin, content)
def _make_edu_json(origin, edu_type, content):
return json.dumps(_expect_edu("test", edu_type, content, origin=origin))
class JustPresenceHandlers(object):
@ -209,10 +220,13 @@ class PresenceInvitesTestCase(unittest.TestCase):
""" Tests presence management. """
def setUp(self):
self.replication = MockReplication()
self.replication.send_edu = Mock()
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=[
"has_presence_state",
@ -221,11 +235,17 @@ class PresenceInvitesTestCase(unittest.TestCase):
"set_presence_list_accepted",
"get_presence_list",
"del_presence_list",
# Bits that Federation needs
"prep_send_transaction",
"delivered_txn",
"get_received_txn_response",
"set_received_txn_response",
]),
handlers=None,
resource_for_client=Mock(),
http_client=None,
replication_layer=self.replication
resource_for_federation=self.mock_federation_resource,
http_client=self.mock_http_client,
)
hs.handlers = JustPresenceHandlers(hs)
@ -236,6 +256,10 @@ class PresenceInvitesTestCase(unittest.TestCase):
user_localpart in ("apple", "banana"))
self.datastore.has_presence_state = has_presence_state
def get_received_txn_response(*args):
return defer.succeed(None)
self.datastore.get_received_txn_response = get_received_txn_response
# Some local users to test with
self.u_apple = hs.parse_userid("@apple:test")
self.u_banana = hs.parse_userid("@banana:test")
@ -283,7 +307,19 @@ class PresenceInvitesTestCase(unittest.TestCase):
@defer.inlineCallbacks
def test_invite_remote(self):
self.replication.send_edu.return_value = defer.succeed((200, "OK"))
put_json = self.mock_http_client.put_json
put_json.expect_call_and_return(
call("elsewhere",
path="/matrix/federation/v1/send/1000000/",
data=_expect_edu("elsewhere", "m.presence_invite",
content={
"observer_user": "@apple:test",
"observed_user": "@cabbage:elsewhere",
}
)
),
defer.succeed((200, "OK"))
)
yield self.handler.send_invite(
observer_user=self.u_apple, observed_user=self.u_cabbage)
@ -291,67 +327,79 @@ class PresenceInvitesTestCase(unittest.TestCase):
self.datastore.add_presence_list_pending.assert_called_with(
"apple", "@cabbage:elsewhere")
self.replication.send_edu.assert_called_with(
destination="elsewhere",
edu_type="m.presence_invite",
content={
"observer_user": "@apple:test",
"observed_user": "@cabbage:elsewhere",
}
)
yield put_json.await_calls()
@defer.inlineCallbacks
def test_accept_remote(self):
# TODO(paul): This test will likely break if/when real auth permissions
# are added; for now the HS will always accept any invite
self.replication.send_edu.return_value = defer.succeed((200, "OK"))
put_json = self.mock_http_client.put_json
put_json.expect_call_and_return(
call("elsewhere",
path="/matrix/federation/v1/send/1000000/",
data=_expect_edu("elsewhere", "m.presence_accept",
content={
"observer_user": "@cabbage:elsewhere",
"observed_user": "@apple:test",
}
)
),
defer.succeed((200, "OK"))
)
yield self.replication.received_edu(
"elsewhere", "m.presence_invite", {
yield self.mock_federation_resource.trigger("PUT",
"/matrix/federation/v1/send/1000000/",
_make_edu_json("elsewhere", "m.presence_invite",
content={
"observer_user": "@cabbage:elsewhere",
"observed_user": "@apple:test",
}
)
)
self.datastore.allow_presence_visible.assert_called_with(
"apple", "@cabbage:elsewhere")
self.replication.send_edu.assert_called_with(
destination="elsewhere",
edu_type="m.presence_accept",
content={
"observer_user": "@cabbage:elsewhere",
"observed_user": "@apple:test",
}
)
yield put_json.await_calls()
@defer.inlineCallbacks
def test_invited_remote_nonexistant(self):
self.replication.send_edu.return_value = defer.succeed((200, "OK"))
yield self.replication.received_edu(
"elsewhere", "m.presence_invite", {
"observer_user": "@cabbage:elsewhere",
"observed_user": "@durian:test",
}
put_json = self.mock_http_client.put_json
put_json.expect_call_and_return(
call("elsewhere",
path="/matrix/federation/v1/send/1000000/",
data=_expect_edu("elsewhere", "m.presence_deny",
content={
"observer_user": "@cabbage:elsewhere",
"observed_user": "@durian:test",
}
)
),
defer.succeed((200, "OK"))
)
self.replication.send_edu.assert_called_with(
destination="elsewhere",
edu_type="m.presence_deny",
yield self.mock_federation_resource.trigger("PUT",
"/matrix/federation/v1/send/1000000/",
_make_edu_json("elsewhere", "m.presence_invite",
content={
"observer_user": "@cabbage:elsewhere",
"observed_user": "@durian:test",
}
)
)
yield put_json.await_calls()
@defer.inlineCallbacks
def test_accepted_remote(self):
yield self.replication.received_edu(
"elsewhere", "m.presence_accept", {
yield self.mock_federation_resource.trigger("PUT",
"/matrix/federation/v1/send/1000000/",
_make_edu_json("elsewhere", "m.presence_accept",
content={
"observer_user": "@apple:test",
"observed_user": "@cabbage:elsewhere",
}
)
)
self.datastore.set_presence_list_accepted.assert_called_with(
@ -362,11 +410,14 @@ class PresenceInvitesTestCase(unittest.TestCase):
@defer.inlineCallbacks
def test_denied_remote(self):
yield self.replication.received_edu(
"elsewhere", "m.presence_deny", {
yield self.mock_federation_resource.trigger("PUT",
"/matrix/federation/v1/send/1000000/",
_make_edu_json("elsewhere", "m.presence_deny",
content={
"observer_user": "@apple:test",
"observed_user": "@eggplant:elsewhere",
}
)
)
self.datastore.del_presence_list.assert_called_with(
@ -383,6 +434,14 @@ class PresenceInvitesTestCase(unittest.TestCase):
self.mock_stop.assert_called_with(
self.u_apple, target_user=self.u_banana)
@defer.inlineCallbacks
def test_drop_remote(self):
yield self.handler.drop(
observer_user=self.u_apple, observed_user=self.u_cabbage)
self.datastore.del_presence_list.assert_called_with(
"apple", "@cabbage:elsewhere")
@defer.inlineCallbacks
def test_get_presence_list(self):
self.datastore.get_presence_list.return_value = defer.succeed(
@ -424,22 +483,29 @@ class PresencePushTestCase(unittest.TestCase):
BE WARNED...
"""
def setUp(self):
self.replication = MockReplication()
self.replication.send_edu = Mock()
self.replication.send_edu.return_value = defer.succeed((200, "OK"))
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",
# Bits that Federation needs
"prep_send_transaction",
"delivered_txn",
"get_received_txn_response",
"set_received_txn_response",
]),
handlers=None,
resource_for_client=Mock(),
http_client=None,
replication_layer=self.replication,
resource_for_federation=self.mock_federation_resource,
http_client=self.mock_http_client,
)
hs.handlers = JustPresenceHandlers(hs)
@ -447,6 +513,11 @@ class PresencePushTestCase(unittest.TestCase):
self.mock_update_client.return_value = defer.succeed(None)
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.handler.push_update_to_clients = self.mock_update_client
@ -585,10 +656,43 @@ class PresencePushTestCase(unittest.TestCase):
@defer.inlineCallbacks
def test_push_remote(self):
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",
"state": "online",
"mtime_age": 0},
],
}
)
),
defer.succeed((200, "OK"))
)
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",
"state": "online",
"mtime_age": 0},
],
}
)
),
defer.succeed((200, "OK"))
)
self.room_members = [self.u_apple, self.u_onion]
self.datastore.set_presence_state.return_value = defer.succeed(
{"state": ONLINE})
{"state": ONLINE}
)
# TODO(paul): Gut-wrenching
self.handler._user_cachemap[self.u_apple] = UserPresenceCache()
@ -596,30 +700,10 @@ class PresencePushTestCase(unittest.TestCase):
apple_set.add(self.u_potato.domain)
yield self.handler.set_state(self.u_apple, self.u_apple,
{"state": ONLINE})
{"state": ONLINE}
)
self.replication.send_edu.assert_has_calls([
call(
destination="remote",
edu_type="m.presence",
content={
"push": [
{"user_id": "@apple:test",
"state": "online",
"mtime_age": 0},
],
}),
call(
destination="farm",
edu_type="m.presence",
content={
"push": [
{"user_id": "@apple:test",
"state": "online",
"mtime_age": 0},
],
})
], any_order=True)
yield put_json.await_calls()
@defer.inlineCallbacks
def test_recv_remote(self):
@ -630,14 +714,17 @@ class PresencePushTestCase(unittest.TestCase):
self.room_members = [self.u_banana, self.u_potato]
yield self.replication.received_edu(
"remote", "m.presence", {
yield self.mock_federation_resource.trigger("PUT",
"/matrix/federation/v1/send/1000000/",
_make_edu_json("elsewhere", "m.presence",
content={
"push": [
{"user_id": "@potato:remote",
"state": "online",
"mtime_age": 1000},
],
}
)
)
self.mock_update_client.assert_has_calls([
@ -683,6 +770,35 @@ class PresencePushTestCase(unittest.TestCase):
@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",
"state": "online"},
],
}
),
),
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",
"state": "offline"},
],
}
),
),
defer.succeed((200, "OK"))
)
# TODO(paul): Gut-wrenching
self.handler._user_cachemap[self.u_apple] = UserPresenceCache()
@ -694,31 +810,25 @@ class PresencePushTestCase(unittest.TestCase):
"a-room"
)
self.replication.send_edu.assert_has_calls([
call(
destination="remote",
edu_type="m.presence",
content={
"push": [
{"user_id": "@apple:test",
"state": "online"},
],
}),
call(
destination="remote",
edu_type="m.presence",
content={
"push": [
{"user_id": "@banana:test",
"state": "offline"},
],
}),
], any_order=True)
self.replication.send_edu.reset_mock()
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",
"state": "online"},
],
}
),
),
defer.succeed((200, "OK"))
)
self.handler._user_cachemap[self.u_clementine] = UserPresenceCache()
self.handler._user_cachemap[self.u_clementine].update(
{"state": ONLINE}, self.u_clementine)
@ -728,17 +838,7 @@ class PresencePushTestCase(unittest.TestCase):
"a-room"
)
self.replication.send_edu.assert_has_calls(
call(
destination="remote",
edu_type="m.presence",
content={
"push": [
{"user_id": "@clementine:test",
"state": "online"},
],
}),
)
put_json.await_calls()
class PresencePollingTestCase(unittest.TestCase):
@ -755,21 +855,34 @@ class PresencePollingTestCase(unittest.TestCase):
def setUp(self):
self.replication = MockReplication()
self.replication.send_edu = Mock()
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=[]),
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(),
http_client=None,
replication_layer=self.replication,
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()
self.mock_update_client.return_value = defer.succeed(None)
@ -827,8 +940,9 @@ class PresencePollingTestCase(unittest.TestCase):
def test_push_local(self):
# apple goes online
yield self.handler.set_state(
target_user=self.u_apple, auth_user=self.u_apple,
state={"state": ONLINE})
target_user=self.u_apple, auth_user=self.u_apple,
state={"state": ONLINE}
)
# apple should see both banana and clementine currently offline
self.mock_update_client.assert_has_calls([
@ -885,68 +999,92 @@ class PresencePollingTestCase(unittest.TestCase):
@defer.inlineCallbacks
def test_remote_poll_send(self):
put_json = self.mock_http_client.put_json
put_json.expect_call_and_return(
call("remote",
path="/matrix/federation/v1/send/1000000/",
data=_expect_edu("remote", "m.presence",
content={
"poll": [ "@potato:remote" ],
},
),
),
defer.succeed((200, "OK"))
)
# clementine goes online
yield self.handler.set_state(
target_user=self.u_clementine, auth_user=self.u_clementine,
state={"state": ONLINE})
self.replication.send_edu.assert_called_with(
destination="remote",
edu_type="m.presence",
content={
"poll": [ "@potato:remote" ],
},
)
yield put_json.await_calls()
# Gut-wrenching tests
self.assertTrue(self.u_potato in self.handler._remote_recvmap)
self.assertTrue(self.u_clementine in
self.handler._remote_recvmap[self.u_potato])
self.replication.send_edu.reset_mock()
put_json.expect_call_and_return(
call("remote",
path="/matrix/federation/v1/send/1000001/",
data=_expect_edu("remote", "m.presence",
content={
"unpoll": [ "@potato:remote" ],
},
),
),
defer.succeed((200, "OK"))
)
# clementine goes offline
yield self.handler.set_state(
target_user=self.u_clementine, auth_user=self.u_clementine,
state={"state": OFFLINE})
self.replication.send_edu.assert_called_with(
destination="remote",
edu_type="m.presence",
content={
"unpoll": [ "@potato:remote" ],
},
)
put_json.await_calls()
self.assertFalse(self.u_potato in self.handler._remote_recvmap)
@defer.inlineCallbacks
def test_remote_poll_receive(self):
yield self.replication.received_edu(
"remote", "m.presence", {
"poll": [ "@banana:test" ],
}
put_json = self.mock_http_client.put_json
put_json.expect_call_and_return(
call("remote",
path="/matrix/federation/v1/send/1000000/",
data=_expect_edu("remote", "m.presence",
content={
"push": [
{"user_id": "@banana:test",
"state": "offline",
"status_msg": None},
],
},
),
),
defer.succeed((200, "OK"))
)
yield self.mock_federation_resource.trigger("PUT",
"/matrix/federation/v1/send/1000000/",
_make_edu_json("remote", "m.presence",
content={
"poll": [ "@banana:test" ],
},
)
)
yield put_json.await_calls()
# Gut-wrenching tests
self.assertTrue(self.u_banana in self.handler._remote_sendmap)
self.replication.send_edu.assert_called_with(
destination="remote",
edu_type="m.presence",
yield self.mock_federation_resource.trigger("PUT",
"/matrix/federation/v1/send/1000001/",
_make_edu_json("remote", "m.presence",
content={
"push": [
{"user_id": "@banana:test",
"state": "offline",
"status_msg": None},
],
},
)
yield self.replication.received_edu(
"remote", "m.presence", {
"unpoll": [ "@banana:test" ],
}
)
)
# Gut-wrenching tests

View File

@ -29,7 +29,7 @@ from synapse.server import HomeServer
import json
import logging
from ..utils import MockHttpServer, MemoryDataStore
from ..utils import MockHttpResource, MemoryDataStore
from .utils import RestTestCase
from mock import Mock
@ -116,7 +116,7 @@ class EventStreamPermissionsTestCase(RestTestCase):
@defer.inlineCallbacks
def setUp(self):
self.mock_server = MockHttpServer(prefix=PATH_PREFIX)
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
state_handler = Mock(spec=["handle_new_event"])
state_handler.handle_new_event.return_value = True
@ -142,9 +142,9 @@ class EventStreamPermissionsTestCase(RestTestCase):
hs.get_clock().time_msec.return_value = 1000000
hs.datastore = MemoryDataStore()
synapse.rest.register.register_servlets(hs, self.mock_server)
synapse.rest.events.register_servlets(hs, self.mock_server)
synapse.rest.room.register_servlets(hs, self.mock_server)
synapse.rest.register.register_servlets(hs, self.mock_resource)
synapse.rest.events.register_servlets(hs, self.mock_resource)
synapse.rest.room.register_servlets(hs, self.mock_resource)
# register an account
self.user_id = "sid1"
@ -164,12 +164,12 @@ class EventStreamPermissionsTestCase(RestTestCase):
@defer.inlineCallbacks
def test_stream_basic_permissions(self):
# invalid token, expect 403
(code, response) = yield self.mock_server.trigger_get(
(code, response) = yield self.mock_resource.trigger_get(
"/events?access_token=%s" % ("invalid" + self.token))
self.assertEquals(403, code, msg=str(response))
# valid token, expect content
(code, response) = yield self.mock_server.trigger_get(
(code, response) = yield self.mock_resource.trigger_get(
"/events?access_token=%s&timeout=0" % (self.token))
self.assertEquals(200, code, msg=str(response))
self.assertTrue("chunk" in response)
@ -186,7 +186,7 @@ class EventStreamPermissionsTestCase(RestTestCase):
# invited to room (expect no content for room)
yield self.invite(room_id, src=self.other_user, targ=self.user_id,
tok=self.other_token)
(code, response) = yield self.mock_server.trigger_get(
(code, response) = yield self.mock_resource.trigger_get(
"/events?access_token=%s&timeout=0" % (self.token))
self.assertEquals(200, code, msg=str(response))

View File

@ -21,9 +21,10 @@ from twisted.internet import defer
from mock import Mock
import logging
from ..utils import MockHttpServer
from ..utils import MockHttpResource
from synapse.api.constants import PresenceState
from synapse.handlers.presence import PresenceHandler
from synapse.server import HomeServer
@ -39,29 +40,49 @@ myid = "@apple:test"
PATH_PREFIX = "/matrix/client/api/v1"
class JustPresenceHandlers(object):
def __init__(self, hs):
self.presence_handler = PresenceHandler(hs)
class PresenceStateTestCase(unittest.TestCase):
def setUp(self):
self.mock_server = MockHttpServer(prefix=PATH_PREFIX)
self.mock_handler = Mock(spec=[
"get_state",
"set_state",
])
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
hs = HomeServer("test",
db_pool=None,
datastore=Mock(spec=[
"get_presence_state",
"set_presence_state",
]),
http_client=None,
datastore=None,
resource_for_client=self.mock_server,
resource_for_federation=self.mock_server,
resource_for_client=self.mock_resource,
resource_for_federation=self.mock_resource,
)
hs.handlers = JustPresenceHandlers(hs)
self.datastore = hs.get_datastore()
def get_presence_list(*a, **kw):
return defer.succeed([])
self.datastore.get_presence_list = get_presence_list
def _get_user_by_token(token=None):
return hs.parse_userid(myid)
hs.get_auth().get_user_by_token = _get_user_by_token
hs.get_handlers().presence_handler = self.mock_handler
room_member_handler = hs.handlers.room_member_handler = Mock(
spec=[
"get_rooms_for_user",
]
)
def get_rooms_for_user(user):
return defer.succeed([])
room_member_handler.get_rooms_for_user = get_rooms_for_user
hs.register_servlets()
@ -69,58 +90,75 @@ class PresenceStateTestCase(unittest.TestCase):
@defer.inlineCallbacks
def test_get_my_status(self):
mocked_get = self.mock_handler.get_state
mocked_get = self.datastore.get_presence_state
mocked_get.return_value = defer.succeed(
{"state": ONLINE, "status_msg": "Available"})
{"state": ONLINE, "status_msg": "Available"}
)
(code, response) = yield self.mock_server.trigger("GET",
(code, response) = yield self.mock_resource.trigger("GET",
"/presence/%s/status" % (myid), None)
self.assertEquals(200, code)
self.assertEquals({"state": ONLINE, "status_msg": "Available"},
response)
mocked_get.assert_called_with(target_user=self.u_apple,
auth_user=self.u_apple)
mocked_get.assert_called_with("apple")
@defer.inlineCallbacks
def test_set_my_status(self):
mocked_set = self.mock_handler.set_state
mocked_set.return_value = defer.succeed(())
mocked_set = self.datastore.set_presence_state
mocked_set.return_value = defer.succeed({"state": OFFLINE})
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
"/presence/%s/status" % (myid),
'{"state": "unavailable", "status_msg": "Away"}')
self.assertEquals(200, code)
mocked_set.assert_called_with(target_user=self.u_apple,
auth_user=self.u_apple,
state={"state": UNAVAILABLE, "status_msg": "Away"})
mocked_set.assert_called_with("apple",
{"state": UNAVAILABLE, "status_msg": "Away"})
class PresenceListTestCase(unittest.TestCase):
def setUp(self):
self.mock_server = MockHttpServer(prefix=PATH_PREFIX)
self.mock_handler = Mock(spec=[
"get_presence_list",
"send_invite",
"drop",
])
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
hs = HomeServer("test",
db_pool=None,
datastore=Mock(spec=[
"has_presence_state",
"get_presence_state",
"allow_presence_visible",
"is_presence_visible",
"add_presence_list_pending",
"set_presence_list_accepted",
"del_presence_list",
"get_presence_list",
]),
http_client=None,
datastore=None,
resource_for_client=self.mock_server,
resource_for_federation=self.mock_server
resource_for_client=self.mock_resource,
resource_for_federation=self.mock_resource
)
hs.handlers = JustPresenceHandlers(hs)
self.datastore = hs.get_datastore()
def has_presence_state(user_localpart):
return defer.succeed(
user_localpart in ("apple", "banana",)
)
self.datastore.has_presence_state = has_presence_state
def _get_user_by_token(token=None):
return hs.parse_userid(myid)
hs.get_auth().get_user_by_token = _get_user_by_token
room_member_handler = hs.handlers.room_member_handler = Mock(
spec=[
"get_rooms_for_user",
]
)
hs.get_handlers().presence_handler = self.mock_handler
hs.get_auth().get_user_by_token = _get_user_by_token
hs.register_servlets()
@ -129,52 +167,66 @@ class PresenceListTestCase(unittest.TestCase):
@defer.inlineCallbacks
def test_get_my_list(self):
self.mock_handler.get_presence_list.return_value = defer.succeed(
[{"observed_user": self.u_banana}]
self.datastore.get_presence_list.return_value = defer.succeed(
[{"observed_user_id": "@banana:test"}],
)
(code, response) = yield self.mock_server.trigger("GET",
(code, response) = yield self.mock_resource.trigger("GET",
"/presence_list/%s" % (myid), None)
self.assertEquals(200, code)
self.assertEquals([{"user_id": "@banana:test"}], response)
self.assertEquals(
[{"user_id": "@banana:test", "state": OFFLINE}], response
)
self.datastore.get_presence_list.assert_called_with(
"apple", accepted=True
)
@defer.inlineCallbacks
def test_invite(self):
self.mock_handler.send_invite.return_value = defer.succeed(())
self.datastore.add_presence_list_pending.return_value = (
defer.succeed(())
)
self.datastore.is_presence_visible.return_value = defer.succeed(
True
)
(code, response) = yield self.mock_server.trigger("POST",
"/presence_list/%s" % (myid),
"""{
"invite": ["@banana:test"]
}""")
(code, response) = yield self.mock_resource.trigger("POST",
"/presence_list/%s" % (myid),
"""{"invite": ["@banana:test"]}"""
)
self.assertEquals(200, code)
self.mock_handler.send_invite.assert_called_with(
observer_user=self.u_apple, observed_user=self.u_banana)
self.datastore.add_presence_list_pending.assert_called_with(
"apple", "@banana:test"
)
self.datastore.set_presence_list_accepted.assert_called_with(
"apple", "@banana:test"
)
@defer.inlineCallbacks
def test_drop(self):
self.mock_handler.drop.return_value = defer.succeed(())
self.datastore.del_presence_list.return_value = (
defer.succeed(())
)
(code, response) = yield self.mock_server.trigger("POST",
"/presence_list/%s" % (myid),
"""{
"drop": ["@banana:test"]
}""")
(code, response) = yield self.mock_resource.trigger("POST",
"/presence_list/%s" % (myid),
"""{"drop": ["@banana:test"]}"""
)
self.assertEquals(200, code)
self.mock_handler.drop.assert_called_with(
observer_user=self.u_apple, observed_user=self.u_banana)
self.datastore.del_presence_list.assert_called_with(
"apple", "@banana:test"
)
class PresenceEventStreamTestCase(unittest.TestCase):
def setUp(self):
self.mock_server = MockHttpServer(prefix=PATH_PREFIX)
# TODO: mocked data store
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
# HIDEOUS HACKERY
# TODO(paul): This should be injected in via the HomeServer DI system
@ -187,8 +239,8 @@ class PresenceEventStreamTestCase(unittest.TestCase):
hs = HomeServer("test",
db_pool=None,
http_client=None,
resource_for_client=self.mock_server,
resource_for_federation=self.mock_server,
resource_for_client=self.mock_resource,
resource_for_federation=self.mock_resource,
datastore=Mock(spec=[
"set_presence_state",
"get_presence_list",
@ -228,7 +280,7 @@ class PresenceEventStreamTestCase(unittest.TestCase):
self.mock_datastore.get_presence_list.return_value = defer.succeed(
[])
(code, response) = yield self.mock_server.trigger("GET",
(code, response) = yield self.mock_resource.trigger("GET",
"/events?timeout=0", None)
self.assertEquals(200, code)
@ -254,7 +306,7 @@ class PresenceEventStreamTestCase(unittest.TestCase):
yield self.presence.set_state(self.u_banana, self.u_banana,
state={"state": ONLINE})
(code, response) = yield self.mock_server.trigger("GET",
(code, response) = yield self.mock_resource.trigger("GET",
"/events?from=1&timeout=0", None)
self.assertEquals(200, code)

View File

@ -20,7 +20,7 @@ from twisted.internet import defer
from mock import Mock
from ..utils import MockHttpServer
from ..utils import MockHttpResource
from synapse.api.errors import SynapseError, AuthError
from synapse.server import HomeServer
@ -32,7 +32,7 @@ class ProfileTestCase(unittest.TestCase):
""" Tests profile management. """
def setUp(self):
self.mock_server = MockHttpServer(prefix=PATH_PREFIX)
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
self.mock_handler = Mock(spec=[
"get_displayname",
"set_displayname",
@ -43,7 +43,7 @@ class ProfileTestCase(unittest.TestCase):
hs = HomeServer("test",
db_pool=None,
http_client=None,
resource_for_client=self.mock_server,
resource_for_client=self.mock_resource,
federation=Mock(),
replication_layer=Mock(),
datastore=None,
@ -63,7 +63,7 @@ class ProfileTestCase(unittest.TestCase):
mocked_get = self.mock_handler.get_displayname
mocked_get.return_value = defer.succeed("Frank")
(code, response) = yield self.mock_server.trigger("GET",
(code, response) = yield self.mock_resource.trigger("GET",
"/profile/%s/displayname" % (myid), None)
self.assertEquals(200, code)
@ -75,7 +75,7 @@ class ProfileTestCase(unittest.TestCase):
mocked_set = self.mock_handler.set_displayname
mocked_set.return_value = defer.succeed(())
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
"/profile/%s/displayname" % (myid),
'{"displayname": "Frank Jr."}')
@ -89,7 +89,7 @@ class ProfileTestCase(unittest.TestCase):
mocked_set = self.mock_handler.set_displayname
mocked_set.side_effect = AuthError(400, "message")
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
"/profile/%s/displayname" % ("@4567:test"), '"Frank Jr."')
self.assertTrue(400 <= code < 499,
@ -100,7 +100,7 @@ class ProfileTestCase(unittest.TestCase):
mocked_get = self.mock_handler.get_displayname
mocked_get.return_value = defer.succeed("Bob")
(code, response) = yield self.mock_server.trigger("GET",
(code, response) = yield self.mock_resource.trigger("GET",
"/profile/%s/displayname" % ("@opaque:elsewhere"), None)
self.assertEquals(200, code)
@ -111,7 +111,7 @@ class ProfileTestCase(unittest.TestCase):
mocked_set = self.mock_handler.set_displayname
mocked_set.side_effect = SynapseError(400, "message")
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
"/profile/%s/displayname" % ("@opaque:elsewhere"), None)
self.assertTrue(400 <= code <= 499,
@ -122,7 +122,7 @@ class ProfileTestCase(unittest.TestCase):
mocked_get = self.mock_handler.get_avatar_url
mocked_get.return_value = defer.succeed("http://my.server/me.png")
(code, response) = yield self.mock_server.trigger("GET",
(code, response) = yield self.mock_resource.trigger("GET",
"/profile/%s/avatar_url" % (myid), None)
self.assertEquals(200, code)
@ -134,7 +134,7 @@ class ProfileTestCase(unittest.TestCase):
mocked_set = self.mock_handler.set_avatar_url
mocked_set.return_value = defer.succeed(())
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
"/profile/%s/avatar_url" % (myid),
'{"avatar_url": "http://my.server/pic.gif"}')

View File

@ -27,7 +27,7 @@ from synapse.server import HomeServer
import json
import urllib
from ..utils import MockHttpServer, MemoryDataStore
from ..utils import MockHttpResource, MemoryDataStore
from .utils import RestTestCase
from mock import Mock
@ -42,7 +42,7 @@ class RoomPermissionsTestCase(RestTestCase):
@defer.inlineCallbacks
def setUp(self):
self.mock_server = MockHttpServer(prefix=PATH_PREFIX)
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
state_handler = Mock(spec=["handle_new_event"])
state_handler.handle_new_event.return_value = True
@ -67,7 +67,7 @@ class RoomPermissionsTestCase(RestTestCase):
self.auth_user_id = self.rmcreator_id
synapse.rest.room.register_servlets(hs, self.mock_server)
synapse.rest.room.register_servlets(hs, self.mock_resource)
self.auth = hs.get_auth()
@ -85,14 +85,14 @@ class RoomPermissionsTestCase(RestTestCase):
# send a message in one of the rooms
self.created_rmid_msg_path = ("/rooms/%s/messages/%s/midaaa1" %
(self.created_rmid, self.rmcreator_id))
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT",
self.created_rmid_msg_path,
'{"msgtype":"m.text","body":"test msg"}')
self.assertEquals(200, code, msg=str(response))
# set topic for public room
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT",
"/rooms/%s/topic" % self.created_public_rmid,
'{"topic":"Public Room Topic"}')
@ -107,31 +107,31 @@ class RoomPermissionsTestCase(RestTestCase):
# @defer.inlineCallbacks
# def test_get_message(self):
# # get message in uncreated room, expect 403
# (code, response) = yield self.mock_server.trigger_get(
# (code, response) = yield self.mock_resource.trigger_get(
# "/rooms/noroom/messages/someid/m1")
# self.assertEquals(403, code, msg=str(response))
#
# # get message in created room not joined (no state), expect 403
# (code, response) = yield self.mock_server.trigger_get(
# (code, response) = yield self.mock_resource.trigger_get(
# self.created_rmid_msg_path)
# self.assertEquals(403, code, msg=str(response))
#
# # get message in created room and invited, expect 403
# yield self.invite(room=self.created_rmid, src=self.rmcreator_id,
# targ=self.user_id)
# (code, response) = yield self.mock_server.trigger_get(
# (code, response) = yield self.mock_resource.trigger_get(
# self.created_rmid_msg_path)
# self.assertEquals(403, code, msg=str(response))
#
# # get message in created room and joined, expect 200
# yield self.join(room=self.created_rmid, user=self.user_id)
# (code, response) = yield self.mock_server.trigger_get(
# (code, response) = yield self.mock_resource.trigger_get(
# self.created_rmid_msg_path)
# self.assertEquals(200, code, msg=str(response))
#
# # get message in created room and left, expect 403
# yield self.leave(room=self.created_rmid, user=self.user_id)
# (code, response) = yield self.mock_server.trigger_get(
# (code, response) = yield self.mock_resource.trigger_get(
# self.created_rmid_msg_path)
# self.assertEquals(403, code, msg=str(response))
@ -142,33 +142,33 @@ class RoomPermissionsTestCase(RestTestCase):
(self.created_rmid, self.user_id))
# send message in uncreated room, expect 403
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT",
"/rooms/%s/messages/%s/mid1" %
(self.uncreated_rmid, self.user_id), msg_content)
self.assertEquals(403, code, msg=str(response))
# send message in created room not joined (no state), expect 403
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", send_msg_path, msg_content)
self.assertEquals(403, code, msg=str(response))
# send message in created room and invited, expect 403
yield self.invite(room=self.created_rmid, src=self.rmcreator_id,
targ=self.user_id)
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", send_msg_path, msg_content)
self.assertEquals(403, code, msg=str(response))
# send message in created room and joined, expect 200
yield self.join(room=self.created_rmid, user=self.user_id)
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", send_msg_path, msg_content)
self.assertEquals(200, code, msg=str(response))
# send message in created room and left, expect 403
yield self.leave(room=self.created_rmid, user=self.user_id)
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", send_msg_path, msg_content)
self.assertEquals(403, code, msg=str(response))
@ -178,56 +178,56 @@ class RoomPermissionsTestCase(RestTestCase):
topic_path = "/rooms/%s/topic" % self.created_rmid
# set/get topic in uncreated room, expect 403
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", "/rooms/%s/topic" % self.uncreated_rmid,
topic_content)
self.assertEquals(403, code, msg=str(response))
(code, response) = yield self.mock_server.trigger_get(
(code, response) = yield self.mock_resource.trigger_get(
"/rooms/%s/topic" % self.uncreated_rmid)
self.assertEquals(403, code, msg=str(response))
# set/get topic in created PRIVATE room not joined, expect 403
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", topic_path, topic_content)
self.assertEquals(403, code, msg=str(response))
(code, response) = yield self.mock_server.trigger_get(topic_path)
(code, response) = yield self.mock_resource.trigger_get(topic_path)
self.assertEquals(403, code, msg=str(response))
# set topic in created PRIVATE room and invited, expect 403
yield self.invite(room=self.created_rmid, src=self.rmcreator_id,
targ=self.user_id)
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", topic_path, topic_content)
self.assertEquals(403, code, msg=str(response))
# get topic in created PRIVATE room and invited, expect 200 (or 404)
(code, response) = yield self.mock_server.trigger_get(topic_path)
(code, response) = yield self.mock_resource.trigger_get(topic_path)
self.assertEquals(404, code, msg=str(response))
# set/get topic in created PRIVATE room and joined, expect 200
yield self.join(room=self.created_rmid, user=self.user_id)
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", topic_path, topic_content)
self.assertEquals(200, code, msg=str(response))
(code, response) = yield self.mock_server.trigger_get(topic_path)
(code, response) = yield self.mock_resource.trigger_get(topic_path)
self.assertEquals(200, code, msg=str(response))
self.assert_dict(json.loads(topic_content), response)
# set/get topic in created PRIVATE room and left, expect 403
yield self.leave(room=self.created_rmid, user=self.user_id)
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", topic_path, topic_content)
self.assertEquals(403, code, msg=str(response))
(code, response) = yield self.mock_server.trigger_get(topic_path)
(code, response) = yield self.mock_resource.trigger_get(topic_path)
self.assertEquals(403, code, msg=str(response))
# get topic in PUBLIC room, not joined, expect 200 (or 404)
(code, response) = yield self.mock_server.trigger_get(
(code, response) = yield self.mock_resource.trigger_get(
"/rooms/%s/topic" % self.created_public_rmid)
self.assertEquals(200, code, msg=str(response))
# set topic in PUBLIC room, not joined, expect 403
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT",
"/rooms/%s/topic" % self.created_public_rmid,
topic_content)
@ -237,7 +237,7 @@ class RoomPermissionsTestCase(RestTestCase):
def _test_get_membership(self, room=None, members=[], expect_code=None):
path = "/rooms/%s/members/%s/state"
for member in members:
(code, response) = yield self.mock_server.trigger_get(
(code, response) = yield self.mock_resource.trigger_get(
path %
(room, member))
self.assertEquals(expect_code, code)
@ -391,7 +391,7 @@ class RoomsMemberListTestCase(RestTestCase):
user_id = "@sid1:red"
def setUp(self):
self.mock_server = MockHttpServer(prefix=PATH_PREFIX)
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
state_handler = Mock(spec=["handle_new_event"])
state_handler.handle_new_event.return_value = True
@ -416,7 +416,7 @@ class RoomsMemberListTestCase(RestTestCase):
return hs.parse_userid(self.auth_user_id)
hs.get_auth().get_user_by_token = _get_user_by_token
synapse.rest.room.register_servlets(hs, self.mock_server)
synapse.rest.room.register_servlets(hs, self.mock_resource)
def tearDown(self):
pass
@ -425,13 +425,13 @@ class RoomsMemberListTestCase(RestTestCase):
def test_get_member_list(self):
room_id = "!aa:test"
yield self.create_room_as(room_id, self.user_id)
(code, response) = yield self.mock_server.trigger_get(
(code, response) = yield self.mock_resource.trigger_get(
"/rooms/%s/members/list" % room_id)
self.assertEquals(200, code, msg=str(response))
@defer.inlineCallbacks
def test_get_member_list_no_room(self):
(code, response) = yield self.mock_server.trigger_get(
(code, response) = yield self.mock_resource.trigger_get(
"/rooms/roomdoesnotexist/members/list")
self.assertEquals(403, code, msg=str(response))
@ -439,7 +439,7 @@ class RoomsMemberListTestCase(RestTestCase):
def test_get_member_list_no_permission(self):
room_id = "!bb:test"
yield self.create_room_as(room_id, "@some_other_guy:red")
(code, response) = yield self.mock_server.trigger_get(
(code, response) = yield self.mock_resource.trigger_get(
"/rooms/%s/members/list" % room_id)
self.assertEquals(403, code, msg=str(response))
@ -452,17 +452,17 @@ class RoomsMemberListTestCase(RestTestCase):
yield self.invite(room=room_id, src=room_creator,
targ=self.user_id)
# can't see list if you're just invited.
(code, response) = yield self.mock_server.trigger_get(room_path)
(code, response) = yield self.mock_resource.trigger_get(room_path)
self.assertEquals(403, code, msg=str(response))
yield self.join(room=room_id, user=self.user_id)
# can see list now joined
(code, response) = yield self.mock_server.trigger_get(room_path)
(code, response) = yield self.mock_resource.trigger_get(room_path)
self.assertEquals(200, code, msg=str(response))
yield self.leave(room=room_id, user=self.user_id)
# can no longer see list, you've left.
(code, response) = yield self.mock_server.trigger_get(room_path)
(code, response) = yield self.mock_resource.trigger_get(room_path)
self.assertEquals(403, code, msg=str(response))
@ -471,7 +471,7 @@ class RoomsCreateTestCase(RestTestCase):
user_id = "@sid1:red"
def setUp(self):
self.mock_server = MockHttpServer(prefix=PATH_PREFIX)
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
self.auth_user_id = self.user_id
state_handler = Mock(spec=["handle_new_event"])
@ -495,7 +495,7 @@ class RoomsCreateTestCase(RestTestCase):
return hs.parse_userid(self.auth_user_id)
hs.get_auth().get_user_by_token = _get_user_by_token
synapse.rest.room.register_servlets(hs, self.mock_server)
synapse.rest.room.register_servlets(hs, self.mock_resource)
def tearDown(self):
pass
@ -503,7 +503,7 @@ class RoomsCreateTestCase(RestTestCase):
@defer.inlineCallbacks
def test_post_room_no_keys(self):
# POST with no config keys, expect new room id
(code, response) = yield self.mock_server.trigger("POST", "/rooms",
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
"{}")
self.assertEquals(200, code, response)
self.assertTrue("room_id" in response)
@ -511,7 +511,7 @@ class RoomsCreateTestCase(RestTestCase):
@defer.inlineCallbacks
def test_post_room_visibility_key(self):
# POST with visibility config key, expect new room id
(code, response) = yield self.mock_server.trigger("POST", "/rooms",
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
'{"visibility":"private"}')
self.assertEquals(200, code)
self.assertTrue("room_id" in response)
@ -519,7 +519,7 @@ class RoomsCreateTestCase(RestTestCase):
@defer.inlineCallbacks
def test_post_room_custom_key(self):
# POST with custom config keys, expect new room id
(code, response) = yield self.mock_server.trigger("POST", "/rooms",
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
'{"custom":"stuff"}')
self.assertEquals(200, code)
self.assertTrue("room_id" in response)
@ -527,7 +527,7 @@ class RoomsCreateTestCase(RestTestCase):
@defer.inlineCallbacks
def test_post_room_known_and_unknown_keys(self):
# POST with custom + known config keys, expect new room id
(code, response) = yield self.mock_server.trigger("POST", "/rooms",
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
'{"visibility":"private","custom":"things"}')
self.assertEquals(200, code)
self.assertTrue("room_id" in response)
@ -535,18 +535,18 @@ class RoomsCreateTestCase(RestTestCase):
@defer.inlineCallbacks
def test_post_room_invalid_content(self):
# POST with invalid content / paths, expect 400
(code, response) = yield self.mock_server.trigger("POST", "/rooms",
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
'{"visibili')
self.assertEquals(400, code)
(code, response) = yield self.mock_server.trigger("POST", "/rooms",
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
'["hello"]')
self.assertEquals(400, code)
@defer.inlineCallbacks
def test_put_room_no_keys(self):
# PUT with no config keys, expect new room id
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", "/rooms/%21aa%3Atest", "{}"
)
self.assertEquals(200, code)
@ -555,7 +555,7 @@ class RoomsCreateTestCase(RestTestCase):
@defer.inlineCallbacks
def test_put_room_visibility_key(self):
# PUT with known config keys, expect new room id
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", "/rooms/%21bb%3Atest", '{"visibility":"private"}'
)
self.assertEquals(200, code)
@ -564,7 +564,7 @@ class RoomsCreateTestCase(RestTestCase):
@defer.inlineCallbacks
def test_put_room_custom_key(self):
# PUT with custom config keys, expect new room id
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", "/rooms/%21cc%3Atest", '{"custom":"stuff"}'
)
self.assertEquals(200, code)
@ -573,7 +573,7 @@ class RoomsCreateTestCase(RestTestCase):
@defer.inlineCallbacks
def test_put_room_known_and_unknown_keys(self):
# PUT with custom + known config keys, expect new room id
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", "/rooms/%21dd%3Atest",
'{"visibility":"private","custom":"things"}'
)
@ -584,12 +584,12 @@ class RoomsCreateTestCase(RestTestCase):
def test_put_room_invalid_content(self):
# PUT with invalid content / room names, expect 400
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", "/rooms/ee", '{"sdf"'
)
self.assertEquals(400, code)
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", "/rooms/ee", '["hello"]'
)
self.assertEquals(400, code)
@ -599,7 +599,7 @@ class RoomsCreateTestCase(RestTestCase):
yield self.create_room_as("!aa:test", self.user_id)
# PUT with conflicting room ID, expect 409
(code, response) = yield self.mock_server.trigger(
(code, response) = yield self.mock_resource.trigger(
"PUT", "/rooms/%21aa%3Atest", "{}"
)
self.assertEquals(409, code)
@ -611,7 +611,7 @@ class RoomTopicTestCase(RestTestCase):
@defer.inlineCallbacks
def setUp(self):
self.mock_server = MockHttpServer(prefix=PATH_PREFIX)
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
self.auth_user_id = self.user_id
self.room_id = "!rid1:test"
self.path = "/rooms/%s/topic" % self.room_id
@ -637,7 +637,7 @@ class RoomTopicTestCase(RestTestCase):
return hs.parse_userid(self.auth_user_id)
hs.get_auth().get_user_by_token = _get_user_by_token
synapse.rest.room.register_servlets(hs, self.mock_server)
synapse.rest.room.register_servlets(hs, self.mock_resource)
# create the room
yield self.create_room_as(self.room_id, self.user_id)
@ -648,50 +648,50 @@ class RoomTopicTestCase(RestTestCase):
@defer.inlineCallbacks
def test_invalid_puts(self):
# missing keys or invalid json
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
self.path, '{}')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
self.path, '{"_name":"bob"}')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
self.path, '{"nao')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
self.path, '[{"_name":"bob"},{"_name":"jill"}]')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
self.path, 'text only')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
self.path, '')
self.assertEquals(400, code, msg=str(response))
# valid key, wrong type
content = '{"topic":["Topic name"]}'
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
self.path, content)
self.assertEquals(400, code, msg=str(response))
@defer.inlineCallbacks
def test_rooms_topic(self):
# nothing should be there
(code, response) = yield self.mock_server.trigger_get(self.path)
(code, response) = yield self.mock_resource.trigger_get(self.path)
self.assertEquals(404, code, msg=str(response))
# valid put
content = '{"topic":"Topic name"}'
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
self.path, content)
self.assertEquals(200, code, msg=str(response))
# valid get
(code, response) = yield self.mock_server.trigger_get(self.path)
(code, response) = yield self.mock_resource.trigger_get(self.path)
self.assertEquals(200, code, msg=str(response))
self.assert_dict(json.loads(content), response)
@ -699,12 +699,12 @@ class RoomTopicTestCase(RestTestCase):
def test_rooms_topic_with_extra_keys(self):
# valid put with extra keys
content = '{"topic":"Seasons","subtopic":"Summer"}'
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
self.path, content)
self.assertEquals(200, code, msg=str(response))
# valid get
(code, response) = yield self.mock_server.trigger_get(self.path)
(code, response) = yield self.mock_resource.trigger_get(self.path)
self.assertEquals(200, code, msg=str(response))
self.assert_dict(json.loads(content), response)
@ -715,7 +715,7 @@ class RoomMemberStateTestCase(RestTestCase):
@defer.inlineCallbacks
def setUp(self):
self.mock_server = MockHttpServer(prefix=PATH_PREFIX)
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
self.auth_user_id = self.user_id
self.room_id = "!rid1:test"
@ -740,7 +740,7 @@ class RoomMemberStateTestCase(RestTestCase):
return hs.parse_userid(self.auth_user_id)
hs.get_auth().get_user_by_token = _get_user_by_token
synapse.rest.room.register_servlets(hs, self.mock_server)
synapse.rest.room.register_servlets(hs, self.mock_resource)
yield self.create_room_as(self.room_id, self.user_id)
@ -751,34 +751,34 @@ class RoomMemberStateTestCase(RestTestCase):
def test_invalid_puts(self):
path = "/rooms/%s/members/%s/state" % (self.room_id, self.user_id)
# missing keys or invalid json
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, '{}')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, '{"_name":"bob"}')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, '{"nao')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, '[{"_name":"bob"},{"_name":"jill"}]')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, 'text only')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, '')
self.assertEquals(400, code, msg=str(response))
# valid keys, wrong types
content = ('{"membership":["%s","%s","%s"]}' %
(Membership.INVITE, Membership.JOIN, Membership.LEAVE))
(code, response) = yield self.mock_server.trigger("PUT", path, content)
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
self.assertEquals(400, code, msg=str(response))
@defer.inlineCallbacks
@ -789,10 +789,10 @@ class RoomMemberStateTestCase(RestTestCase):
# valid join message (NOOP since we made the room)
content = '{"membership":"%s"}' % Membership.JOIN
(code, response) = yield self.mock_server.trigger("PUT", path, content)
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
self.assertEquals(200, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("GET", path, None)
(code, response) = yield self.mock_resource.trigger("GET", path, None)
self.assertEquals(200, code, msg=str(response))
self.assertEquals(json.loads(content), response)
@ -805,10 +805,10 @@ class RoomMemberStateTestCase(RestTestCase):
# valid invite message
content = '{"membership":"%s"}' % Membership.INVITE
(code, response) = yield self.mock_server.trigger("PUT", path, content)
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
self.assertEquals(200, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("GET", path, None)
(code, response) = yield self.mock_resource.trigger("GET", path, None)
self.assertEquals(200, code, msg=str(response))
self.assertEquals(json.loads(content), response)
@ -822,10 +822,10 @@ class RoomMemberStateTestCase(RestTestCase):
# valid invite message with custom key
content = ('{"membership":"%s","invite_text":"%s"}' %
(Membership.INVITE, "Join us!"))
(code, response) = yield self.mock_server.trigger("PUT", path, content)
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
self.assertEquals(200, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("GET", path, None)
(code, response) = yield self.mock_resource.trigger("GET", path, None)
self.assertEquals(200, code, msg=str(response))
self.assertEquals(json.loads(content), response)
@ -836,7 +836,7 @@ class RoomMessagesTestCase(RestTestCase):
@defer.inlineCallbacks
def setUp(self):
self.mock_server = MockHttpServer(prefix=PATH_PREFIX)
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
self.auth_user_id = self.user_id
self.room_id = "!rid1:test"
@ -861,7 +861,7 @@ class RoomMessagesTestCase(RestTestCase):
return hs.parse_userid(self.auth_user_id)
hs.get_auth().get_user_by_token = _get_user_by_token
synapse.rest.room.register_servlets(hs, self.mock_server)
synapse.rest.room.register_servlets(hs, self.mock_resource)
yield self.create_room_as(self.room_id, self.user_id)
@ -874,27 +874,27 @@ class RoomMessagesTestCase(RestTestCase):
urllib.quote(self.room_id), self.user_id
)
# missing keys or invalid json
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, '{}')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, '{"_name":"bob"}')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, '{"nao')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, '[{"_name":"bob"},{"_name":"jill"}]')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, 'text only')
self.assertEquals(400, code, msg=str(response))
(code, response) = yield self.mock_server.trigger("PUT",
(code, response) = yield self.mock_resource.trigger("PUT",
path, '')
self.assertEquals(400, code, msg=str(response))
@ -905,34 +905,34 @@ class RoomMessagesTestCase(RestTestCase):
)
content = '{"body":"test","msgtype":{"type":"a"}}'
(code, response) = yield self.mock_server.trigger("PUT", path, content)
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
self.assertEquals(400, code, msg=str(response))
# custom message types
content = '{"body":"test","msgtype":"test.custom.text"}'
(code, response) = yield self.mock_server.trigger("PUT", path, content)
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
self.assertEquals(200, code, msg=str(response))
# (code, response) = yield self.mock_server.trigger("GET", path, None)
# self.assertEquals(200, code, msg=str(response))
# self.assert_dict(json.loads(content), response)
# (code, response) = yield self.mock_resource.trigger("GET", path, None)
# self.assertEquals(200, code, msg=str(response))
# self.assert_dict(json.loads(content), response)
# m.text message type
path = "/rooms/%s/messages/%s/mid2" % (
urllib.quote(self.room_id), self.user_id
)
content = '{"body":"test2","msgtype":"m.text"}'
(code, response) = yield self.mock_server.trigger("PUT", path, content)
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
self.assertEquals(200, code, msg=str(response))
# (code, response) = yield self.mock_server.trigger("GET", path, None)
# self.assertEquals(200, code, msg=str(response))
# self.assert_dict(json.loads(content), response)
# (code, response) = yield self.mock_resource.trigger("GET", path, None)
# self.assertEquals(200, code, msg=str(response))
# self.assert_dict(json.loads(content), response)
# trying to send message in different user path
path = "/rooms/%s/messages/%s/mid2" % (
urllib.quote(self.room_id), "invalid" + self.user_id
)
content = '{"body":"test2","msgtype":"m.text"}'
(code, response) = yield self.mock_server.trigger("PUT", path, content)
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
self.assertEquals(403, code, msg=str(response))

View File

@ -27,12 +27,12 @@ class RestTestCase(unittest.TestCase):
"""Contains extra helper functions to quickly and clearly perform a given
REST action, which isn't the focus of the test.
This subclass assumes there are mock_server and auth_user_id attributes.
This subclass assumes there are mock_resource and auth_user_id attributes.
"""
def __init__(self, *args, **kwargs):
super(RestTestCase, self).__init__(*args, **kwargs)
self.mock_server = None
self.mock_resource = None
self.auth_user_id = None
def mock_get_user_by_token(self, token=None):
@ -48,7 +48,7 @@ class RestTestCase(unittest.TestCase):
content = '{"visibility":"private"}'
if tok:
path = path + "?access_token=%s" % tok
(code, response) = yield self.mock_server.trigger("PUT", path, content)
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
self.assertEquals(200, code, msg=str(response))
self.auth_user_id = temp_id
@ -81,11 +81,11 @@ class RestTestCase(unittest.TestCase):
path = path + "?access_token=%s" % tok
if membership == Membership.LEAVE:
(code, response) = yield self.mock_server.trigger("DELETE", path,
(code, response) = yield self.mock_resource.trigger("DELETE", path,
None)
self.assertEquals(expect_code, code, msg=str(response))
else:
(code, response) = yield self.mock_server.trigger("PUT", path,
(code, response) = yield self.mock_resource.trigger("PUT", path,
'{"membership":"%s"}' % membership)
self.assertEquals(expect_code, code, msg=str(response))
@ -93,7 +93,7 @@ class RestTestCase(unittest.TestCase):
@defer.inlineCallbacks
def register(self, user_id):
(code, response) = yield self.mock_server.trigger("POST", "/register",
(code, response) = yield self.mock_resource.trigger("POST", "/register",
'{"user_id":"%s"}' % user_id)
self.assertEquals(200, code)
defer.returnValue(response)
@ -111,7 +111,7 @@ class RestTestCase(unittest.TestCase):
if tok:
path = path + "?access_token=%s" % tok
(code, response) = yield self.mock_server.trigger("PUT", path, content)
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
self.assertEquals(expect_code, code, msg=str(response))
def assert_dict(self, required, actual):

View File

@ -29,7 +29,8 @@ import json
import urlparse
class MockHttpServer(HttpServer):
# This is a mock /resource/ not an entire server
class MockHttpResource(HttpServer):
def __init__(self, prefix=""):
self.callbacks = [] # 3-tuple of method/pattern/function
@ -210,3 +211,43 @@ class MemoryDataStore(object):
def get_room_events_max_id(self):
return 0 # TODO (erikj)
def _format_call(args, kwargs):
return ", ".join(
["%r" % (a) for a in args] +
["%s=%r" % (k, v) for k, v in kwargs.items()]
)
class DeferredMockCallable(object):
"""A callable instance that stores a set of pending call expectations and
return values for them. It allows a unit test to assert that the given set
of function calls are eventually made, by awaiting on them to be called.
"""
def __init__(self):
self.expectations = []
def __call__(self, *args, **kwargs):
if not self.expectations:
raise ValueError("%r has no pending calls to handle call(%s)" % (
self, _format_call(args, kwargs))
)
for (call, result, d) in self.expectations:
if args == call[1] and kwargs == call[2]:
d.callback(None)
return result
raise AssertionError("Was not expecting call(%s)" %
_format_call(args, kwargs)
)
def expect_call_and_return(self, call, result):
self.expectations.append((call, result, defer.Deferred()))
@defer.inlineCallbacks
def await_calls(self):
while self.expectations:
(_, _, d) = self.expectations.pop(0)
yield d

View File

@ -89,6 +89,7 @@ h1 {
height: 100px;
position: relative;
background-color: #000;
cursor: pointer;
}
.userAvatar .userAvatarImage {
@ -251,6 +252,7 @@ h1 {
height: 160px;
display:table-cell;
vertical-align: middle;
text-align: center;
}
.profile-avatar img {
@ -258,6 +260,14 @@ h1 {
max-height: 100%;
}
/*** User profile page ***/
#user-ids {
padding-left: 1em;
}
#user-displayname {
font-size: 16pt;
}
/******************************/
#header {

View File

@ -20,6 +20,7 @@ var matrixWebClient = angular.module('matrixWebClient', [
'LoginController',
'RoomController',
'RoomsController',
'UserController',
'matrixService',
'eventStreamService',
'eventHandlerService',
@ -33,7 +34,13 @@ matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
templateUrl: 'login/login.html',
controller: 'LoginController'
}).
when('/room/:room_id', {
when('/room/:room_id_or_alias', {
templateUrl: 'room/room.html',
controller: 'RoomController'
}).
when('/room/', { // room URL with room alias in it (ex: http://127.0.0.1:8000/#/room/#public:localhost:8080) will come here.
// The reason is that 2nd hash key breaks routeProvider parameters cutting so that the URL will not match with
// the previous '/room/:room_id_or_alias' URL rule
templateUrl: 'room/room.html',
controller: 'RoomController'
}).
@ -41,6 +48,10 @@ matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider',
templateUrl: 'rooms/rooms.html',
controller: 'RoomsController'
}).
when('/user/:user_matrix_id', {
templateUrl: 'user/user.html',
controller: 'UserController'
}).
otherwise({
redirectTo: '/rooms'
});

View File

@ -29,7 +29,7 @@ angular.module('mFileInput', [])
scope: {
selectedFile: '=mFileInput'
},
link: function(scope, element, attrs, ctrl) {
element.bind("click", function() {
element.find("input")[0].click();
@ -38,6 +38,9 @@ angular.module('mFileInput', [])
scope.$apply();
});
});
// Change the mouse icon on mouseover on this element
element.css("cursor", "pointer");
}
};
});

View File

@ -16,11 +16,12 @@
'use strict';
// TODO determine if this is really required as a separate service to matrixService.
/*
* Upload an HTML5 file to a server
*/
angular.module('mFileUpload', [])
.service('mFileUpload', ['$http', '$q', function ($http, $q) {
.service('mFileUpload', ['matrixService', '$q', function (matrixService, $q) {
/*
* Upload an HTML5 file to a server and returned a promise
@ -28,20 +29,19 @@ angular.module('mFileUpload', [])
*/
this.uploadFile = function(file) {
var deferred = $q.defer();
// @TODO: This service runs with the do_POST hacky implementation of /synapse/demos/webserver.py.
// This is temporary until we have a true file upload service
console.log("Uploading " + file.name + "...");
$http.post(file.name, file)
.success(function(data, status, headers, config) {
deferred.resolve(location.origin + data.url);
console.log(" -> Successfully uploaded! Available at " + location.origin + data.url);
}).
error(function(data, status, headers, config) {
console.log(" -> Failed to upload" + file.name);
deferred.reject();
});
console.log("Uploading " + file.name + "... to /matrix/content");
matrixService.uploadContent(file).then(
function(response) {
var content_url = location.origin + "/matrix/content/" + response.data.content_token;
console.log(" -> Successfully uploaded! Available at " + content_url);
deferred.resolve(content_url);
},
function(error) {
console.log(" -> Failed to upload " + file.name);
deferred.reject(error);
}
);
return deferred.promise;
};
}]);
}]);

View File

@ -54,13 +54,14 @@ angular.module('matrixService', [])
params.access_token = config.access_token;
if (path.indexOf(prefixPath) !== 0) {
path = prefixPath + path;
}
return doBaseRequest(config.homeserver, method, path, params, data, undefined);
};
var doBaseRequest = function(baseUrl, method, path, params, data, headers) {
if (path.indexOf(prefixPath) !== 0) {
path = prefixPath + path;
}
return $http({
method: method,
url: baseUrl + path,
@ -165,6 +166,16 @@ angular.module('matrixService', [])
return doRequest("DELETE", path, undefined, undefined);
},
// Retrieves the room ID corresponding to a room alias
resolveRoomAlias:function(room_alias) {
var path = "/matrix/client/api/v1/ds/room/$room_alias";
room_alias = encodeURIComponent(room_alias);
path = path.replace("$room_alias", room_alias);
return doRequest("GET", path, undefined, {});
},
sendMessage: function(room_id, msg_id, content) {
// The REST path spec
var path = "/rooms/$room_id/messages/$from/$msg_id";
@ -309,6 +320,17 @@ angular.module('matrixService', [])
return doBaseRequest(config.identityServer, "POST", path, {}, data, headers);
},
uploadContent: function(file) {
var path = "/matrix/content";
var headers = {
"Content-Type": undefined // undefined means angular will figure it out
};
var params = {
access_token: config.access_token
};
return doBaseRequest(config.homeserver, "POST", path, params, file, headers);
},
// start listening on /events
getEventStream: function(from, timeout) {
var path = "/events";

View File

@ -16,6 +16,7 @@
<script src="login/login-controller.js"></script>
<script src="room/room-controller.js"></script>
<script src="rooms/rooms-controller.js"></script>
<script src="user/user-controller.js"></script>
<script src="components/matrix/matrix-service.js"></script>
<script src="components/matrix/event-stream-service.js"></script>
<script src="components/matrix/event-handler-service.js"></script>

View File

@ -108,8 +108,11 @@ angular.module('RoomController', ['ngSanitize'])
function($scope, $http, $timeout, $routeParams, $location, matrixService, eventStreamService, eventHandlerService) {
'use strict';
var MESSAGES_PER_PAGINATION = 30;
$scope.room_id = $routeParams.room_id;
$scope.room_alias = matrixService.getRoomIdToAliasMapping($scope.room_id);
// Room ids. Computed and resolved in onInit
$scope.room_id = undefined;
$scope.room_alias = undefined;
$scope.state = {
user_id: matrixService.config().user_id,
events_from: "END", // when to start the event stream from.
@ -144,7 +147,7 @@ angular.module('RoomController', ['ngSanitize'])
if (document.hidden) {
var notification = new window.Notification(
($scope.members[event.user_id].displayname || event.user_id) +
" (" + $scope.room_alias + ")",
" (" + ($scope.room_alias || $scope.room_id) + ")", // FIXME: don't leak room_ids here
{
"body": event.content.body,
"icon": $scope.members[event.user_id].avatar_url,
@ -342,7 +345,57 @@ angular.module('RoomController', ['ngSanitize'])
$scope.onInit = function() {
// $timeout(function() { document.getElementById('textInput').focus() }, 0);
console.log("onInit");
// Does the room ID provided in the URL?
var room_id_or_alias;
if ($routeParams.room_id_or_alias) {
room_id_or_alias = decodeURIComponent($routeParams.room_id_or_alias);
}
if (room_id_or_alias && '!' === room_id_or_alias[0]) {
// Yes. We can start right now
$scope.room_id = room_id_or_alias;
$scope.room_alias = matrixService.getRoomIdToAliasMapping($scope.room_id);
onInit2();
}
else {
// No. The URL contains the room alias. Get this alias.
if (room_id_or_alias) {
// The room alias was passed urlencoded, use it as is
$scope.room_alias = room_id_or_alias;
}
else {
// Else get the room alias by hand from the URL
// ie: extract #public:localhost:8080 from http://127.0.0.1:8000/#/room/#public:localhost:8080
if (3 === location.hash.split("#").length) {
$scope.room_alias = "#" + location.hash.split("#")[2];
}
else {
// In case of issue, go to the default page
console.log("Error: cannot extract room alias");
$location.path("/");
return;
}
}
// Need a room ID required in Matrix API requests
console.log("Resolving alias: " + $scope.room_alias);
matrixService.resolveRoomAlias($scope.room_alias).then(function(response) {
$scope.room_id = response.data.room_id;
console.log(" -> Room ID: " + $scope.room_id);
// Now, we can start
onInit2();
},
function () {
// In case of issue, go to the default page
console.log("Error: cannot resolve room alias");
$location.path("/");
});
}
};
var onInit2 = function() {
// Join the room
matrixService.join($scope.room_id).then(
function() {
@ -380,6 +433,11 @@ angular.module('RoomController', ['ngSanitize'])
});
};
// Open the user profile page
$scope.goToUserPage = function(user_id) {
$location.url("/user/" + user_id);
};
$scope.leaveRoom = function() {
matrixService.leave($scope.room_id).then(

View File

@ -10,9 +10,13 @@
<div id="usersTableWrapper">
<table id="usersTable">
<tr ng-repeat="member in members | orderMembersList">
<td class="userAvatar">
<img class="userAvatarImage" ng-src="{{member.avatar_url || 'img/default-profile.jpg'}}" width="80" height="80"/>
<img class="userAvatarGradient" src="img/gradient.png" width="80" height="24"/>
<td class="userAvatar" ng-click="goToUserPage(member.id)">
<img class="userAvatarImage"
ng-src="{{member.avatar_url || 'img/default-profile.jpg'}}"
alt="{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}"
title="{{ member.id }}"
width="80" height="80"/>
<img class="userAvatarGradient" src="img/gradient.png" title="{{ member.id }}" width="80" height="24"/>
<div class="userName">{{ member.displayname || member.id.substr(0, member.id.indexOf(':')) }}<br/>{{ member.displayname ? "" : member.id.substr(member.id.indexOf(':')) }}</div>
</td>
<td class="userPresence" ng-class="member.presenceState === 'online' ? 'online' : (member.presenceState === 'unavailable' ? 'unavailable' : '')">

View File

@ -149,12 +149,8 @@ angular.module('RoomsController', ['matrixService', 'mFileInput', 'mFileUpload',
$scope.joinAlias = function(room_alias) {
matrixService.joinAlias(room_alias).then(
function(response) {
if (response.data.hasOwnProperty("room_id")) {
$location.path("room/" + response.data.room_id);
return;
} else {
// TODO (erikj): Do something here?
}
// Go to this room
$location.path("room/" + room_alias);
},
function(error) {
$scope.feedback = "Can't join room: " + error.data;

View File

@ -65,7 +65,7 @@
<div class="rooms" ng-repeat="(rm_id, room) in rooms">
<div>
<a href="#/room/{{ rm_id }}" >{{ room.room_alias }}</a> {{room.membership === 'invite' ? ' (invited)' : ''}}
<a href="#/room/{{ room.room_alias ? room.room_alias : rm_id }}" >{{ room.room_alias }}</a> {{room.membership === 'invite' ? ' (invited)' : ''}}
</div>
</div>
<br/>
@ -74,7 +74,7 @@
<div class="public_rooms" ng-repeat="room in public_rooms">
<div>
<a href="#/room/{{ room.room_id }}" >{{ room.room_alias }}</a>
<a href="#/room/{{ room.room_alias ? room.room_alias : room.room_id }}" >{{ room.room_alias }}</a>
</div>
</div>
<br/>

View File

@ -0,0 +1,38 @@
/*
Copyright 2014 matrix.org
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.
*/
'use strict';
angular.module('UserController', ['matrixService'])
.controller('UserController', ['$scope', '$routeParams', 'matrixService',
function($scope, $routeParams, matrixService) {
$scope.user = {
id: $routeParams.user_matrix_id,
displayname: "",
avatar_url: undefined
};
matrixService.getDisplayName($scope.user.id).then(
function(response) {
$scope.user.displayname = response.data.displayname;
}
);
matrixService.getProfilePictureUrl($scope.user.id).then(
function(response) {
$scope.user.avatar_url = response.data.avatar_url;
}
);
}]);

30
webclient/user/user.html Normal file
View File

@ -0,0 +1,30 @@
<div ng-controller="UserController" class="user">
<div id="page">
<div id="wrapper">
<div>
<form>
<table>
<tr>
<td>
<div class="profile-avatar">
<img ng-src="{{ user.avatar_url || 'img/default-profile.jpg' }}"/>
</div>
</td>
<td>
<div id="user-ids">
<div id="user-displayname">{{ user.displayname }}</div>
<div>{{ user.id }}</div>
</div>
</td>
</tr>
</table>
</form>
</div>
{{ feedback }}
</div>
</div>
</div>