Merge branch 'develop' of github.com:matrix-org/synapse into neilj/admin_email

This commit is contained in:
Neil Johnson 2018-08-14 17:44:46 +01:00
commit 19b433e3f4
18 changed files with 372 additions and 75 deletions

48
.circleci/config.yml Normal file
View File

@ -0,0 +1,48 @@
version: 2
jobs:
sytestpy2:
machine: true
steps:
- checkout
- run: docker pull matrixdotorg/sytest-synapsepy2
- run: docker run --rm -it -v $(pwd)\:/src -v $(pwd)/logs\:/logs matrixdotorg/sytest-synapsepy2
- store_artifacts:
path: ~/project/logs
destination: logs
sytestpy2postgres:
machine: true
steps:
- checkout
- run: docker pull matrixdotorg/sytest-synapsepy2
- run: docker run --rm -it -v $(pwd)\:/src -v $(pwd)/logs\:/logs -e POSTGRES=1 matrixdotorg/sytest-synapsepy2
- store_artifacts:
path: ~/project/logs
destination: logs
sytestpy3:
machine: true
steps:
- checkout
- run: docker pull matrixdotorg/sytest-synapsepy3
- run: docker run --rm -it -v $(pwd)\:/src -v $(pwd)/logs\:/logs hawkowl/sytestpy3
- store_artifacts:
path: ~/project/logs
destination: logs
sytestpy3postgres:
machine: true
steps:
- checkout
- run: docker pull matrixdotorg/sytest-synapsepy3
- run: docker run --rm -it -v $(pwd)\:/src -v $(pwd)/logs\:/logs -e POSTGRES=1 matrixdotorg/sytest-synapsepy3
- store_artifacts:
path: ~/project/logs
destination: logs
workflows:
version: 2
build:
jobs:
- sytestpy2
- sytestpy2postgres
# Currently broken while the Python 3 port is incomplete
# - sytestpy3
# - sytestpy3postgres

View File

@ -3,3 +3,6 @@ Dockerfile
.gitignore .gitignore
demo/etc demo/etc
tox.ini tox.ini
synctl
.git/*
.tox/*

View File

@ -36,3 +36,4 @@ recursive-include changelog.d *
prune .github prune .github
prune demo/etc prune demo/etc
prune docker prune docker
prune .circleci

1
changelog.d/3660.misc Normal file
View File

@ -0,0 +1 @@
Sytests can now be run inside a Docker container.

1
changelog.d/3669.misc Normal file
View File

@ -0,0 +1 @@
Update docker base image from alpine 3.7 to 3.8.

1
changelog.d/3670.feature Normal file
View File

@ -0,0 +1 @@
Where server is disabled, block ability for locked out users to read new messages

1
changelog.d/3681.bugfix Normal file
View File

@ -0,0 +1 @@
Fixes test_reap_monthly_active_users so it passes under postgres

1
changelog.d/3684.misc Normal file
View File

@ -0,0 +1 @@
Implemented a new testing base class to reduce test boilerplate.

1
changelog.d/3692.bugfix Normal file
View File

@ -0,0 +1 @@
Fix missing yield in synapse.storage.monthly_active_users.initialise_reserved_users

View File

@ -1,4 +1,4 @@
FROM docker.io/python:2-alpine3.7 FROM docker.io/python:2-alpine3.8
RUN apk add --no-cache --virtual .nacl_deps \ RUN apk add --no-cache --virtual .nacl_deps \
build-base \ build-base \

View File

@ -775,9 +775,13 @@ class Auth(object):
) )
@defer.inlineCallbacks @defer.inlineCallbacks
def check_auth_blocking(self): def check_auth_blocking(self, user_id=None):
"""Checks if the user should be rejected for some external reason, """Checks if the user should be rejected for some external reason,
such as monthly active user limiting or global disable flag such as monthly active user limiting or global disable flag
Args:
user_id(str|None): If present, checks for presence against existing
MAU cohort
""" """
if self.hs.config.hs_disabled: if self.hs.config.hs_disabled:
raise AuthError( raise AuthError(
@ -786,6 +790,12 @@ class Auth(object):
admin_email=self.hs.config.admin_email, admin_email=self.hs.config.admin_email,
) )
if self.hs.config.limit_usage_by_mau is True: if self.hs.config.limit_usage_by_mau is True:
# If the user is already part of the MAU cohort
if user_id:
timestamp = yield self.store.user_last_seen_monthly_active(user_id)
if timestamp:
return
# Else if there is no room in the MAU bucket, bail
current_mau = yield self.store.get_monthly_active_count() current_mau = yield self.store.get_monthly_active_count()
if current_mau >= self.hs.config.max_mau_value: if current_mau >= self.hs.config.max_mau_value:
raise AuthError( raise AuthError(

View File

@ -191,6 +191,7 @@ class SyncHandler(object):
self.clock = hs.get_clock() self.clock = hs.get_clock()
self.response_cache = ResponseCache(hs, "sync") self.response_cache = ResponseCache(hs, "sync")
self.state = hs.get_state_handler() self.state = hs.get_state_handler()
self.auth = hs.get_auth()
# ExpiringCache((User, Device)) -> LruCache(state_key => event_id) # ExpiringCache((User, Device)) -> LruCache(state_key => event_id)
self.lazy_loaded_members_cache = ExpiringCache( self.lazy_loaded_members_cache = ExpiringCache(
@ -198,19 +199,27 @@ class SyncHandler(object):
max_len=0, expiry_ms=LAZY_LOADED_MEMBERS_CACHE_MAX_AGE, max_len=0, expiry_ms=LAZY_LOADED_MEMBERS_CACHE_MAX_AGE,
) )
@defer.inlineCallbacks
def wait_for_sync_for_user(self, sync_config, since_token=None, timeout=0, def wait_for_sync_for_user(self, sync_config, since_token=None, timeout=0,
full_state=False): full_state=False):
"""Get the sync for a client if we have new data for it now. Otherwise """Get the sync for a client if we have new data for it now. Otherwise
wait for new data to arrive on the server. If the timeout expires, then wait for new data to arrive on the server. If the timeout expires, then
return an empty sync result. return an empty sync result.
Returns: Returns:
A Deferred SyncResult. Deferred[SyncResult]
""" """
return self.response_cache.wrap( # If the user is not part of the mau group, then check that limits have
# not been exceeded (if not part of the group by this point, almost certain
# auth_blocking will occur)
user_id = sync_config.user.to_string()
yield self.auth.check_auth_blocking(user_id)
res = yield self.response_cache.wrap(
sync_config.request_key, sync_config.request_key,
self._wait_for_sync_for_user, self._wait_for_sync_for_user,
sync_config, since_token, timeout, full_state, sync_config, since_token, timeout, full_state,
) )
defer.returnValue(res)
@defer.inlineCallbacks @defer.inlineCallbacks
def _wait_for_sync_for_user(self, sync_config, since_token, timeout, def _wait_for_sync_for_user(self, sync_config, since_token, timeout,

View File

@ -46,7 +46,7 @@ class MonthlyActiveUsersStore(SQLBaseStore):
tp["medium"], tp["address"] tp["medium"], tp["address"]
) )
if user_id: if user_id:
self.upsert_monthly_active_user(user_id) yield self.upsert_monthly_active_user(user_id)
reserved_user_list.append(user_id) reserved_user_list.append(user_id)
else: else:
logger.warning( logger.warning(
@ -64,23 +64,27 @@ class MonthlyActiveUsersStore(SQLBaseStore):
Deferred[] Deferred[]
""" """
def _reap_users(txn): def _reap_users(txn):
# Purge stale users
thirty_days_ago = ( thirty_days_ago = (
int(self._clock.time_msec()) - (1000 * 60 * 60 * 24 * 30) int(self._clock.time_msec()) - (1000 * 60 * 60 * 24 * 30)
) )
# Purge stale users
# questionmarks is a hack to overcome sqlite not supporting
# tuples in 'WHERE IN %s'
questionmarks = '?' * len(self.reserved_users)
query_args = [thirty_days_ago] query_args = [thirty_days_ago]
query_args.extend(self.reserved_users) base_sql = "DELETE FROM monthly_active_users WHERE timestamp < ?"
sql = """ # Need if/else since 'AND user_id NOT IN ({})' fails on Postgres
DELETE FROM monthly_active_users # when len(reserved_users) == 0. Works fine on sqlite.
WHERE timestamp < ? if len(self.reserved_users) > 0:
AND user_id NOT IN ({}) # questionmarks is a hack to overcome sqlite not supporting
""".format(','.join(questionmarks)) # tuples in 'WHERE IN %s'
questionmarks = '?' * len(self.reserved_users)
query_args.extend(self.reserved_users)
sql = base_sql + """ AND user_id NOT IN ({})""".format(
','.join(questionmarks)
)
else:
sql = base_sql
txn.execute(sql, query_args) txn.execute(sql, query_args)
@ -93,16 +97,24 @@ class MonthlyActiveUsersStore(SQLBaseStore):
# negative LIMIT values. So there is no way to write it that both can # negative LIMIT values. So there is no way to write it that both can
# support # support
query_args = [self.hs.config.max_mau_value] query_args = [self.hs.config.max_mau_value]
query_args.extend(self.reserved_users)
sql = """ base_sql = """
DELETE FROM monthly_active_users DELETE FROM monthly_active_users
WHERE user_id NOT IN ( WHERE user_id NOT IN (
SELECT user_id FROM monthly_active_users SELECT user_id FROM monthly_active_users
ORDER BY timestamp DESC ORDER BY timestamp DESC
LIMIT ? LIMIT ?
) )
AND user_id NOT IN ({}) """
""".format(','.join(questionmarks)) # Need if/else since 'AND user_id NOT IN ({})' fails on Postgres
# when len(reserved_users) == 0. Works fine on sqlite.
if len(self.reserved_users) > 0:
query_args.extend(self.reserved_users)
sql = base_sql + """ AND user_id NOT IN ({})""".format(
','.join(questionmarks)
)
else:
sql = base_sql
txn.execute(sql, query_args) txn.execute(sql, query_args)
yield self.runInteraction("reap_monthly_active_users", _reap_users) yield self.runInteraction("reap_monthly_active_users", _reap_users)
@ -113,7 +125,7 @@ class MonthlyActiveUsersStore(SQLBaseStore):
# is racy. # is racy.
# Have resolved to invalidate the whole cache for now and do # Have resolved to invalidate the whole cache for now and do
# something about it if and when the perf becomes significant # something about it if and when the perf becomes significant
self._user_last_seen_monthly_active.invalidate_all() self.user_last_seen_monthly_active.invalidate_all()
self.get_monthly_active_count.invalidate_all() self.get_monthly_active_count.invalidate_all()
@cached(num_args=0) @cached(num_args=0)
@ -152,11 +164,11 @@ class MonthlyActiveUsersStore(SQLBaseStore):
lock=False, lock=False,
) )
if is_insert: if is_insert:
self._user_last_seen_monthly_active.invalidate((user_id,)) self.user_last_seen_monthly_active.invalidate((user_id,))
self.get_monthly_active_count.invalidate(()) self.get_monthly_active_count.invalidate(())
@cached(num_args=1) @cached(num_args=1)
def _user_last_seen_monthly_active(self, user_id): def user_last_seen_monthly_active(self, user_id):
""" """
Checks if a given user is part of the monthly active user group Checks if a given user is part of the monthly active user group
Arguments: Arguments:
@ -173,7 +185,7 @@ class MonthlyActiveUsersStore(SQLBaseStore):
}, },
retcol="timestamp", retcol="timestamp",
allow_none=True, allow_none=True,
desc="_user_last_seen_monthly_active", desc="user_last_seen_monthly_active",
)) ))
@defer.inlineCallbacks @defer.inlineCallbacks
@ -185,7 +197,7 @@ class MonthlyActiveUsersStore(SQLBaseStore):
user_id(str): the user_id to query user_id(str): the user_id to query
""" """
if self.hs.config.limit_usage_by_mau: if self.hs.config.limit_usage_by_mau:
last_seen_timestamp = yield self._user_last_seen_monthly_active(user_id) last_seen_timestamp = yield self.user_last_seen_monthly_active(user_id)
now = self.hs.get_clock().time_msec() now = self.hs.get_clock().time_msec()
# We want to reduce to the total number of db writes, and are happy # We want to reduce to the total number of db writes, and are happy

View File

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# Copyright 2018 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from twisted.internet import defer
from synapse.api.errors import AuthError, Codes
from synapse.api.filtering import DEFAULT_FILTER_COLLECTION
from synapse.handlers.sync import SyncConfig, SyncHandler
from synapse.types import UserID
import tests.unittest
import tests.utils
from tests.utils import setup_test_homeserver
class SyncTestCase(tests.unittest.TestCase):
""" Tests Sync Handler. """
@defer.inlineCallbacks
def setUp(self):
self.hs = yield setup_test_homeserver(self.addCleanup)
self.sync_handler = SyncHandler(self.hs)
self.store = self.hs.get_datastore()
@defer.inlineCallbacks
def test_wait_for_sync_for_user_auth_blocking(self):
user_id1 = "@user1:server"
user_id2 = "@user2:server"
sync_config = self._generate_sync_config(user_id1)
self.hs.config.limit_usage_by_mau = True
self.hs.config.max_mau_value = 1
# Check that the happy case does not throw errors
yield self.store.upsert_monthly_active_user(user_id1)
yield self.sync_handler.wait_for_sync_for_user(sync_config)
# Test that global lock works
self.hs.config.hs_disabled = True
with self.assertRaises(AuthError) as e:
yield self.sync_handler.wait_for_sync_for_user(sync_config)
self.assertEquals(e.exception.errcode, Codes.HS_DISABLED)
self.hs.config.hs_disabled = False
sync_config = self._generate_sync_config(user_id2)
with self.assertRaises(AuthError) as e:
yield self.sync_handler.wait_for_sync_for_user(sync_config)
self.assertEquals(e.exception.errcode, Codes.MAU_LIMIT_EXCEEDED)
def _generate_sync_config(self, user_id):
return SyncConfig(
user=UserID(user_id.split(":")[0][1:], user_id.split(":")[1]),
filter_collection=DEFAULT_FILTER_COLLECTION,
is_guest=False,
request_key="request_key",
device_id="device_id",
)

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd # Copyright 2014-2016 OpenMarket Ltd
# Copyright 2018 New Vector
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -17,41 +18,32 @@
from mock import Mock, NonCallableMock from mock import Mock, NonCallableMock
# twisted imports
from twisted.internet import defer from twisted.internet import defer
import synapse.rest.client.v1.room from synapse.rest.client.v1 import room
from synapse.types import UserID from synapse.types import UserID
from ....utils import MockClock, MockHttpResource, setup_test_homeserver from tests import unittest
from .utils import RestTestCase
PATH_PREFIX = "/_matrix/client/api/v1" PATH_PREFIX = "/_matrix/client/api/v1"
class RoomTypingTestCase(RestTestCase): class RoomTypingTestCase(unittest.HomeserverTestCase):
""" Tests /rooms/$room_id/typing/$user_id REST API. """ """ Tests /rooms/$room_id/typing/$user_id REST API. """
user_id = "@sid:red" user_id = "@sid:red"
user = UserID.from_string(user_id) user = UserID.from_string(user_id)
servlets = [room.register_servlets]
@defer.inlineCallbacks def make_homeserver(self, reactor, clock):
def setUp(self):
self.clock = MockClock()
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX) hs = self.setup_test_homeserver(
self.auth_user_id = self.user_id
hs = yield setup_test_homeserver(
self.addCleanup,
"red", "red",
clock=self.clock,
http_client=None, http_client=None,
federation_client=Mock(), federation_client=Mock(),
ratelimiter=NonCallableMock(spec_set=["send_message"]), ratelimiter=NonCallableMock(spec_set=["send_message"]),
) )
self.hs = hs
self.event_source = hs.get_event_sources().sources["typing"] self.event_source = hs.get_event_sources().sources["typing"]
@ -100,25 +92,24 @@ class RoomTypingTestCase(RestTestCase):
fetch_room_distributions_into fetch_room_distributions_into
) )
synapse.rest.client.v1.room.register_servlets(hs, self.mock_resource) return hs
self.room_id = yield self.create_room_as(self.user_id) def prepare(self, reactor, clock, hs):
self.room_id = self.helper.create_room_as(self.user_id)
# Need another user to make notifications actually work # Need another user to make notifications actually work
yield self.join(self.room_id, user="@jim:red") self.helper.join(self.room_id, user="@jim:red")
@defer.inlineCallbacks
def test_set_typing(self): def test_set_typing(self):
(code, _) = yield self.mock_resource.trigger( request, channel = self.make_request(
"PUT", "PUT",
"/rooms/%s/typing/%s" % (self.room_id, self.user_id), "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
'{"typing": true, "timeout": 30000}', b'{"typing": true, "timeout": 30000}',
) )
self.assertEquals(200, code) self.render(request)
self.assertEquals(200, channel.code)
self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(self.event_source.get_current_key(), 1)
events = yield self.event_source.get_new_events( events = self.event_source.get_new_events(from_key=0, room_ids=[self.room_id])
from_key=0, room_ids=[self.room_id]
)
self.assertEquals( self.assertEquals(
events[0], events[0],
[ [
@ -130,35 +121,36 @@ class RoomTypingTestCase(RestTestCase):
], ],
) )
@defer.inlineCallbacks
def test_set_not_typing(self): def test_set_not_typing(self):
(code, _) = yield self.mock_resource.trigger( request, channel = self.make_request(
"PUT", "PUT",
"/rooms/%s/typing/%s" % (self.room_id, self.user_id), "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
'{"typing": false}', b'{"typing": false}',
) )
self.assertEquals(200, code) self.render(request)
self.assertEquals(200, channel.code)
@defer.inlineCallbacks
def test_typing_timeout(self): def test_typing_timeout(self):
(code, _) = yield self.mock_resource.trigger( request, channel = self.make_request(
"PUT", "PUT",
"/rooms/%s/typing/%s" % (self.room_id, self.user_id), "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
'{"typing": true, "timeout": 30000}', b'{"typing": true, "timeout": 30000}',
) )
self.assertEquals(200, code) self.render(request)
self.assertEquals(200, channel.code)
self.assertEquals(self.event_source.get_current_key(), 1) self.assertEquals(self.event_source.get_current_key(), 1)
self.clock.advance_time(36) self.reactor.advance(36)
self.assertEquals(self.event_source.get_current_key(), 2) self.assertEquals(self.event_source.get_current_key(), 2)
(code, _) = yield self.mock_resource.trigger( request, channel = self.make_request(
"PUT", "PUT",
"/rooms/%s/typing/%s" % (self.room_id, self.user_id), "/rooms/%s/typing/%s" % (self.room_id, self.user_id),
'{"typing": true, "timeout": 30000}', b'{"typing": true, "timeout": 30000}',
) )
self.assertEquals(200, code) self.render(request)
self.assertEquals(200, channel.code)
self.assertEquals(self.event_source.get_current_key(), 3) self.assertEquals(self.event_source.get_current_key(), 3)

View File

@ -63,7 +63,7 @@ class ClientIpStoreTestCase(tests.unittest.TestCase):
yield self.store.insert_client_ip( yield self.store.insert_client_ip(
user_id, "access_token", "ip", "user_agent", "device_id" user_id, "access_token", "ip", "user_agent", "device_id"
) )
active = yield self.store._user_last_seen_monthly_active(user_id) active = yield self.store.user_last_seen_monthly_active(user_id)
self.assertFalse(active) self.assertFalse(active)
@defer.inlineCallbacks @defer.inlineCallbacks
@ -79,7 +79,7 @@ class ClientIpStoreTestCase(tests.unittest.TestCase):
yield self.store.insert_client_ip( yield self.store.insert_client_ip(
user_id, "access_token", "ip", "user_agent", "device_id" user_id, "access_token", "ip", "user_agent", "device_id"
) )
active = yield self.store._user_last_seen_monthly_active(user_id) active = yield self.store.user_last_seen_monthly_active(user_id)
self.assertFalse(active) self.assertFalse(active)
@defer.inlineCallbacks @defer.inlineCallbacks
@ -87,13 +87,13 @@ class ClientIpStoreTestCase(tests.unittest.TestCase):
self.hs.config.limit_usage_by_mau = True self.hs.config.limit_usage_by_mau = True
self.hs.config.max_mau_value = 50 self.hs.config.max_mau_value = 50
user_id = "@user:server" user_id = "@user:server"
active = yield self.store._user_last_seen_monthly_active(user_id) active = yield self.store.user_last_seen_monthly_active(user_id)
self.assertFalse(active) self.assertFalse(active)
yield self.store.insert_client_ip( yield self.store.insert_client_ip(
user_id, "access_token", "ip", "user_agent", "device_id" user_id, "access_token", "ip", "user_agent", "device_id"
) )
active = yield self.store._user_last_seen_monthly_active(user_id) active = yield self.store.user_last_seen_monthly_active(user_id)
self.assertTrue(active) self.assertTrue(active)
@defer.inlineCallbacks @defer.inlineCallbacks
@ -102,7 +102,7 @@ class ClientIpStoreTestCase(tests.unittest.TestCase):
self.hs.config.max_mau_value = 50 self.hs.config.max_mau_value = 50
user_id = "@user:server" user_id = "@user:server"
active = yield self.store._user_last_seen_monthly_active(user_id) active = yield self.store.user_last_seen_monthly_active(user_id)
self.assertFalse(active) self.assertFalse(active)
yield self.store.insert_client_ip( yield self.store.insert_client_ip(
@ -111,5 +111,5 @@ class ClientIpStoreTestCase(tests.unittest.TestCase):
yield self.store.insert_client_ip( yield self.store.insert_client_ip(
user_id, "access_token", "ip", "user_agent", "device_id" user_id, "access_token", "ip", "user_agent", "device_id"
) )
active = yield self.store._user_last_seen_monthly_active(user_id) active = yield self.store.user_last_seen_monthly_active(user_id)
self.assertTrue(active) self.assertTrue(active)

View File

@ -33,7 +33,7 @@ class MonthlyActiveUsersTestCase(tests.unittest.TestCase):
@defer.inlineCallbacks @defer.inlineCallbacks
def test_initialise_reserved_users(self): def test_initialise_reserved_users(self):
self.hs.config.max_mau_value = 5
user1 = "@user1:server" user1 = "@user1:server"
user1_email = "user1@matrix.org" user1_email = "user1@matrix.org"
user2 = "@user2:server" user2 = "@user2:server"
@ -60,9 +60,9 @@ class MonthlyActiveUsersTestCase(tests.unittest.TestCase):
# Test user is marked as active # Test user is marked as active
timestamp = yield self.store._user_last_seen_monthly_active(user1) timestamp = yield self.store.user_last_seen_monthly_active(user1)
self.assertTrue(timestamp) self.assertTrue(timestamp)
timestamp = yield self.store._user_last_seen_monthly_active(user2) timestamp = yield self.store.user_last_seen_monthly_active(user2)
self.assertTrue(timestamp) self.assertTrue(timestamp)
# Test that users are never removed from the db. # Test that users are never removed from the db.
@ -86,17 +86,18 @@ class MonthlyActiveUsersTestCase(tests.unittest.TestCase):
self.assertEqual(1, count) self.assertEqual(1, count)
@defer.inlineCallbacks @defer.inlineCallbacks
def test__user_last_seen_monthly_active(self): def test_user_last_seen_monthly_active(self):
user_id1 = "@user1:server" user_id1 = "@user1:server"
user_id2 = "@user2:server" user_id2 = "@user2:server"
user_id3 = "@user3:server" user_id3 = "@user3:server"
result = yield self.store._user_last_seen_monthly_active(user_id1)
result = yield self.store.user_last_seen_monthly_active(user_id1)
self.assertFalse(result == 0) self.assertFalse(result == 0)
yield self.store.upsert_monthly_active_user(user_id1) yield self.store.upsert_monthly_active_user(user_id1)
yield self.store.upsert_monthly_active_user(user_id2) yield self.store.upsert_monthly_active_user(user_id2)
result = yield self.store._user_last_seen_monthly_active(user_id1) result = yield self.store.user_last_seen_monthly_active(user_id1)
self.assertTrue(result > 0) self.assertTrue(result > 0)
result = yield self.store._user_last_seen_monthly_active(user_id3) result = yield self.store.user_last_seen_monthly_active(user_id3)
self.assertFalse(result == 0) self.assertFalse(result == 0)
@defer.inlineCallbacks @defer.inlineCallbacks

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd # Copyright 2014-2016 OpenMarket Ltd
# Copyright 2018 New Vector
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -15,12 +16,19 @@
import logging import logging
from mock import Mock
import twisted import twisted
import twisted.logger import twisted.logger
from twisted.trial import unittest from twisted.trial import unittest
from synapse.http.server import JsonResource
from synapse.server import HomeServer
from synapse.types import UserID, create_requester
from synapse.util.logcontext import LoggingContextFilter from synapse.util.logcontext import LoggingContextFilter
from tests.server import get_clock, make_request, render, setup_test_homeserver
# Set up putting Synapse's logs into Trial's. # Set up putting Synapse's logs into Trial's.
rootLogger = logging.getLogger() rootLogger = logging.getLogger()
@ -129,3 +137,139 @@ def DEBUG(target):
Can apply to either a TestCase or an individual test method.""" Can apply to either a TestCase or an individual test method."""
target.loglevel = logging.DEBUG target.loglevel = logging.DEBUG
return target return target
class HomeserverTestCase(TestCase):
"""
A base TestCase that reduces boilerplate for HomeServer-using test cases.
Attributes:
servlets (list[function]): List of servlet registration function.
user_id (str): The user ID to assume if auth is hijacked.
hijack_auth (bool): Whether to hijack auth to return the user specified
in user_id.
"""
servlets = []
hijack_auth = True
def setUp(self):
"""
Set up the TestCase by calling the homeserver constructor, optionally
hijacking the authentication system to return a fixed user, and then
calling the prepare function.
"""
self.reactor, self.clock = get_clock()
self._hs_args = {"clock": self.clock, "reactor": self.reactor}
self.hs = self.make_homeserver(self.reactor, self.clock)
if self.hs is None:
raise Exception("No homeserver returned from make_homeserver.")
if not isinstance(self.hs, HomeServer):
raise Exception("A homeserver wasn't returned, but %r" % (self.hs,))
# Register the resources
self.resource = JsonResource(self.hs)
for servlet in self.servlets:
servlet(self.hs, self.resource)
if hasattr(self, "user_id"):
from tests.rest.client.v1.utils import RestHelper
self.helper = RestHelper(self.hs, self.resource, self.user_id)
if self.hijack_auth:
def get_user_by_access_token(token=None, allow_guest=False):
return {
"user": UserID.from_string(self.helper.auth_user_id),
"token_id": 1,
"is_guest": False,
}
def get_user_by_req(request, allow_guest=False, rights="access"):
return create_requester(
UserID.from_string(self.helper.auth_user_id), 1, False, None
)
self.hs.get_auth().get_user_by_req = get_user_by_req
self.hs.get_auth().get_user_by_access_token = get_user_by_access_token
self.hs.get_auth().get_access_token_from_request = Mock(
return_value="1234"
)
if hasattr(self, "prepare"):
self.prepare(self.reactor, self.clock, self.hs)
def make_homeserver(self, reactor, clock):
"""
Make and return a homeserver.
Args:
reactor: A Twisted Reactor, or something that pretends to be one.
clock (synapse.util.Clock): The Clock, associated with the reactor.
Returns:
A homeserver (synapse.server.HomeServer) suitable for testing.
Function to be overridden in subclasses.
"""
raise NotImplementedError()
def prepare(self, reactor, clock, homeserver):
"""
Prepare for the test. This involves things like mocking out parts of
the homeserver, or building test data common across the whole test
suite.
Args:
reactor: A Twisted Reactor, or something that pretends to be one.
clock (synapse.util.Clock): The Clock, associated with the reactor.
homeserver (synapse.server.HomeServer): The HomeServer to test
against.
Function to optionally be overridden in subclasses.
"""
def make_request(self, method, path, content=b""):
"""
Create a SynapseRequest at the path using the method and containing the
given content.
Args:
method (bytes/unicode): The HTTP request method ("verb").
path (bytes/unicode): The HTTP path, suitably URL encoded (e.g.
escaped UTF-8 & spaces and such).
content (bytes): The body of the request.
Returns:
A synapse.http.site.SynapseRequest.
"""
return make_request(method, path, content)
def render(self, request):
"""
Render a request against the resources registered by the test class's
servlets.
Args:
request (synapse.http.site.SynapseRequest): The request to render.
"""
render(request, self.resource, self.reactor)
def setup_test_homeserver(self, *args, **kwargs):
"""
Set up the test homeserver, meant to be called by the overridable
make_homeserver. It automatically passes through the test class's
clock & reactor.
Args:
See tests.utils.setup_test_homeserver.
Returns:
synapse.server.HomeServer
"""
kwargs = dict(kwargs)
kwargs.update(self._hs_args)
return setup_test_homeserver(self.addCleanup, *args, **kwargs)